This document provides the necessary information to implement a client for the ARP (Agent-native ERP) GraphQL server.
The ARP server exposes a GraphQL API for managing users, roles, permissions, services, tasks, notes, and messages. All operations except login require authentication via a JWT bearer token.
/query (GraphQL endpoint)application/jsonPOST for queries and mutations{
"query": "string (GraphQL query or mutation)",
"variables": "object (optional variables)",
"operationName": "string (optional operation name)"
}
login mutation with email and passwordAuthorization headermutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
user {
id
email
roles {
id
name
permissions {
id
code
description
}
}
}
}
}
Variables:
{
"email": "user@example.com",
"password": "your-password"
}
Response:
{
"data": {
"login": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "1",
"email": "user@example.com",
"roles": [...]
}
}
}
}
Include the token in all subsequent requests:
Authorization: Bearer <token>
| Property | Value |
|---|---|
| Algorithm | HS256 |
| Expiration | 10 years from issuance |
| Claims | user_id, email, roles, permissions |
Many operations require specific permissions. The permission format is resource:action.
| Operation | Required Permission |
|---|---|
updateUser |
user:update |
deleteUser |
user:delete |
updateNote |
note:update |
deleteNote |
note:delete |
updateRole |
role:update |
deleteRole |
role:delete |
updatePermission |
permission:update |
deletePermission |
permission:delete |
updateService |
service:update |
deleteService |
service:delete |
updateTask |
task:update |
deleteTask |
task:delete |
updateTaskStatus |
taskstatus:update |
deleteTaskStatus |
taskstatus:delete |
updateMessage |
message:update |
deleteMessage |
message:delete |
If authentication or authorization fails, the API returns an error:
{
"errors": [
{
"message": "unauthorized: authentication required"
}
]
}
Or for missing permissions:
{
"errors": [
{
"message": "unauthorized: missing user:update permission"
}
]
}
All queries require authentication.
# Get all users
query Users {
users {
id
email
roles {
id
name
}
createdAt
updatedAt
}
}
# Get single user by ID
query User($id: ID!) {
user(id: $id) {
id
email
roles {
id
name
permissions {
id
code
description
}
}
createdAt
updatedAt
}
}
# Get all notes
query Notes {
notes {
id
title
content
userId
user { id email }
serviceId
service { id name }
createdAt
updatedAt
}
}
# Get single note by ID
query Note($id: ID!) {
note(id: $id) {
id
title
content
userId
serviceId
createdAt
updatedAt
}
}
# Get all roles
query Roles {
roles {
id
name
description
permissions {
id
code
description
}
}
}
# Get single role by ID
query Role($id: ID!) {
role(id: $id) {
id
name
description
permissions {
id
code
}
}
}
# Get all permissions
query Permissions {
permissions {
id
code
description
}
}
# Get single permission by ID
query Permission($id: ID!) {
permission(id: $id) {
id
code
description
}
}
# Get all services
query Services {
services {
id
name
description
createdById
createdBy { id email }
participants { id email }
tasks { id title }
createdAt
updatedAt
}
}
# Get single service by ID
query Service($id: ID!) {
service(id: $id) {
id
name
description
participants { id email }
tasks { id title status { code label } }
createdAt
updatedAt
}
}
# Get all tasks
query Tasks {
tasks {
id
title
content
createdById
createdBy { id email }
updatedById
updatedBy { id email }
assigneeId
assignee { id email }
statusId
status { id code label }
dueDate
priority
createdAt
updatedAt
}
}
# Get single task by ID
query Task($id: ID!) {
task(id: $id) {
id
title
content
createdById
createdBy { id email }
updatedById
updatedBy { id email }
assignee { id email }
status { code label }
dueDate
priority
createdAt
updatedAt
}
}
# Get all task statuses
query TaskStatuses {
taskStatuses {
id
code
label
tasks { id title }
createdAt
updatedAt
}
}
# Get single task status by ID
query TaskStatus($id: ID!) {
taskStatus(id: $id) {
id
code
label
tasks { id title }
createdAt
updatedAt
}
}
# Get all messages
query Messages {
messages {
id
senderId
sender { id email }
content
sentAt
receivers
createdAt
updatedAt
}
}
# Get single message by ID
query Message($id: ID!) {
message(id: $id) {
id
senderId
sender { id email }
content
sentAt
receivers
createdAt
updatedAt
}
}
All mutations require authentication. Some require additional permissions (see Authorization section).
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
user { id email }
}
}
# Create user
mutation CreateUser($input: NewUser!) {
createUser(input: $input) {
id
email
roles { id name }
createdAt
}
}
# Variables: { "input": { "email": "...", "password": "...", "roles": ["1", "2"] } }
# Update user (requires user:update permission)
mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
updateUser(id: $id, input: $input) {
id
email
roles { id name }
updatedAt
}
}
# Variables: { "id": "1", "input": { "email": "new@example.com" } }
# Delete user (requires user:delete permission)
mutation DeleteUser($id: ID!) {
deleteUser(id: $id)
}
# Create note
mutation CreateNote($input: NewNote!) {
createNote(input: $input) {
id
title
content
userId
serviceId
createdAt
}
}
# Variables: { "input": { "title": "...", "content": "...", "userId": "1", "serviceId": "1" } }
# Update note (requires note:update permission)
mutation UpdateNote($id: ID!, $input: UpdateNoteInput!) {
updateNote(id: $id, input: $input) {
id
title
content
updatedAt
}
}
# Delete note (requires note:delete permission)
mutation DeleteNote($id: ID!) {
deleteNote(id: $id)
}
# Create role
mutation CreateRole($input: NewRole!) {
createRole(input: $input) {
id
name
description
permissions { id code }
createdAt
}
}
# Variables: { "input": { "name": "...", "description": "...", "permissions": ["1", "2"] } }
# Update role (requires role:update permission)
mutation UpdateRole($id: ID!, $input: UpdateRoleInput!) {
updateRole(id: $id, input: $input) {
id
name
permissions { id code }
updatedAt
}
}
# Delete role (requires role:delete permission)
mutation DeleteRole($id: ID!) {
deleteRole(id: $id)
}
# Create permission
mutation CreatePermission($input: NewPermission!) {
createPermission(input: $input) {
id
code
description
createdAt
}
}
# Variables: { "input": { "code": "resource:action", "description": "..." } }
# Update permission (requires permission:update permission)
mutation UpdatePermission($id: ID!, $input: UpdatePermissionInput!) {
updatePermission(id: $id, input: $input) {
id
code
updatedAt
}
}
# Delete permission (requires permission:delete permission)
mutation DeletePermission($id: ID!) {
deletePermission(id: $id)
}
# Create service
mutation CreateService($input: NewService!) {
createService(input: $input) {
id
name
description
createdById
participants { id email }
createdAt
}
}
# Variables: { "input": { "name": "...", "description": "...", "createdById": "1", "participants": ["1", "2"] } }
# Update service (requires service:update permission)
mutation UpdateService($id: ID!, $input: UpdateServiceInput!) {
updateService(id: $id, input: $input) {
id
name
participants { id email }
updatedAt
}
}
# Delete service (requires service:delete permission)
mutation DeleteService($id: ID!) {
deleteService(id: $id)
}
# Create task
mutation CreateTask($input: NewTask!) {
createTask(input: $input) {
id
title
content
createdById
assigneeId
statusId
dueDate
priority
createdAt
}
}
# Variables: { "input": { "title": "...", "content": "...", "createdById": "1", "priority": "high" } }
# Update task (requires task:update permission)
# Note: updatedBy is automatically set to the current authenticated user
mutation UpdateTask($id: ID!, $input: UpdateTaskInput!) {
updateTask(id: $id, input: $input) {
id
title
status { code label }
assignee { id email }
updatedBy { id email }
dueDate
priority
updatedAt
}
}
# Delete task (requires task:delete permission)
mutation DeleteTask($id: ID!) {
deleteTask(id: $id)
}
# Create task status
mutation CreateTaskStatus($input: NewTaskStatus!) {
createTaskStatus(input: $input) {
id
code
label
createdAt
}
}
# Variables: { "input": { "code": "in_progress", "label": "In Progress" } }
# Update task status (requires taskstatus:update permission)
mutation UpdateTaskStatus($id: ID!, $input: UpdateTaskStatusInput!) {
updateTaskStatus(id: $id, input: $input) {
id
code
label
updatedAt
}
}
# Delete task status (requires taskstatus:delete permission)
mutation DeleteTaskStatus($id: ID!) {
deleteTaskStatus(id: $id)
}
# Create message
# Note: sender is automatically set to the authenticated user
mutation CreateMessage($input: NewMessage!) {
createMessage(input: $input) {
id
senderId
sender { id email }
content
sentAt
receivers
createdAt
}
}
# Variables: { "input": { "content": "Hello!", "receivers": ["2", "3"] } }
# Update message (requires message:update permission)
mutation UpdateMessage($id: ID!, $input: UpdateMessageInput!) {
updateMessage(id: $id, input: $input) {
id
content
receivers
updatedAt
}
}
# Delete message (requires message:delete permission)
mutation DeleteMessage($id: ID!) {
deleteMessage(id: $id)
}
Errors are returned in the standard GraphQL error format:
{
"errors": [
{
"message": "Error description",
"path": ["fieldName"],
"locations": [{ "line": 1, "column": 2 }]
}
],
"data": null
}
| Error Message | Cause |
|---|---|
unauthorized: authentication required |
Missing or invalid JWT token |
unauthorized: missing X:Y permission |
User lacks required permission |
invalid credentials |
Wrong email/password on login |
invalid X ID |
Malformed ID provided |
X not found |
Resource with given ID doesn't exist |
failed to create/update/delete X |
Database operation failed |
The API supports real-time updates via GraphQL subscriptions. Subscriptions use WebSocket connections and provide filtered events based on user context.
Subscriptions are filtered by user context. Users only receive events that are relevant to them:
| Subscription | Filtering Rule |
|---|---|
taskCreated |
Only if user is the assignee |
taskUpdated |
Only if user is the assignee |
taskDeleted |
Only if user is the assignee |
messageAdded |
Only if user is a receiver of the message |
This means:
# Task created - received only by assignee
subscription TaskCreated {
taskCreated {
id
title
content
assigneeId
status { code label }
priority
createdAt
}
}
# Task updated - received only by assignee
subscription TaskUpdated {
taskUpdated {
id
title
content
assigneeId
updatedBy { id email }
status { code label }
priority
updatedAt
}
}
# Task deleted - received only by assignee
subscription TaskDeleted {
taskDeleted {
id
title
assigneeId
}
}
# Message added - received by message receivers
subscription MessageAdded {
messageAdded {
id
senderId
sender { id email }
content
sentAt
receivers
}
}
To use subscriptions, establish a WebSocket connection:
/query (same as GraphQL endpoint)The server uses the standard GraphQL over WebSocket protocol (graphql-ws):
{
"type": "connection_init",
"payload": {
"Authorization": "Bearer <token>"
}
}
{
"id": "1",
"type": "start",
"payload": {
"query": "subscription { taskCreated { id title assigneeId } }"
}
}
{
"id": "1",
"type": "data",
"payload": {
"data": {
"taskCreated": {
"id": "5",
"title": "New Task",
"assigneeId": "2"
}
}
}
}
{
"id": "1",
"type": "stop"
}
Using the graphql-ws library:
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'wss://api.example.com/query',
connectionParams: {
Authorization: `Bearer ${token}`,
},
});
// Subscribe to taskCreated events
const unsubscribe = client.subscribe(
{
query: `subscription { taskCreated { id title assigneeId } }`,
},
{
next: (data) => {
console.log('Task created:', data.data.taskCreated);
},
error: (error) => {
console.error('Subscription error:', error);
},
complete: () => {
console.log('Subscription closed');
},
}
);
// Later: unsubscribe
unsubscribe();
Using github.com/99designs/gqlgen/client:
import (
"github.com/99designs/gqlgen/client"
)
// Create WebSocket client with authentication
wsClient := client.New(server, client.AddHeader("Authorization", "Bearer "+token))
// Subscribe to taskCreated
subscription := wsClient.Websocket(`subscription { taskCreated { id title } }`)
defer subscription.Close()
for {
var response struct {
TaskCreated *model.Task `json:"taskCreated"`
}
err := subscription.Next(&response)
if err != nil {
break // Connection closed or error
}
if response.TaskCreated != nil {
fmt.Printf("Task created: %s\n", response.TaskCreated.Title)
}
}
| Use Case | Subscription | Notes |
|---|---|---|
| Task assignment notifications | taskCreated |
User receives event when assigned a new task |
| Task status updates | taskUpdated |
User receives event when their assigned task is modified |
| Task removal | taskDeleted |
User receives event when their assigned task is deleted |
| Direct messages | messageAdded |
Message receivers receive new message events |
| Type | Description |
|---|---|
ID |
Unique identifier (string representation) |
String |
UTF-8 string |
Boolean |
true or false |
Task Priority: low, medium, high (string values)
Task Status Codes: Customizable (e.g., open, in_progress, done)
All timestamps use ISO 8601 / RFC 3339 format:
2024-01-15T10:30:00Z
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Before performing privileged operations, verify the user has the required permission by checking the permissions array in the JWT claims or the user's roles.
POST /query HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
{
"query": "query { users { id email } }"
}
curl -X POST https://api.example.com/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"query": "query { users { id email } }"}'
The ARP server also exposes a Model Context Protocol (MCP) interface for AI agent integration. MCP allows AI assistants to discover and use the GraphQL API through a standardized tool interface.
/mcp (SSE connection endpoint)/message (for sending JSON-RPC messages)2024-11-05All MCP operations require authentication. The authentication flow is:
login mutation to get a JWT tokenAuthorization header when establishing the SSE connection to /mcp/message requests inherit authenticationInclude the JWT token when connecting to the MCP endpoint:
GET /mcp HTTP/1.1
Host: api.example.com
Accept: text/event-stream
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
┌─────────────────────────────────────────────────────────────────┐
│ 1. Client: POST /query { login(email, password) } │
│ ↓ Response: { token: "jwt..." } │
│ │
│ 2. Client: GET /mcp Authorization: Bearer jwt... │
│ ↓ Server validates token, creates session with user context │
│ ↓ Server sends: event: endpoint, data: /message?sessionId=X │
│ │
│ 3. Client: POST /message?sessionId=X { method: "tools/call" } │
│ ↓ Server uses stored user context for authorization │
│ ↓ Response sent via SSE event stream │
└─────────────────────────────────────────────────────────────────┘
// Step 1: Login to get JWT token
async function login(email, password) {
const response = await fetch('/query', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: 'mutation Login($email: String!, $password: String!) { login(email: $email, password: $password) { token } }',
variables: { email, password }
})
});
const data = await response.json();
return data.data.login.token;
}
// Step 2: Establish authenticated SSE connection
function createMCPConnection(token) {
const eventSource = new EventSource('/mcp', {
headers: {
'Authorization': `Bearer ${token}`
}
});
// Note: EventSource doesn't support headers natively
// Use a library like 'event-source-polyfill' or fetch-based SSE
return eventSource;
}
// Alternative: Using fetch-based SSE with authentication
async function createAuthenticatedMCP(token) {
const response = await fetch('/mcp', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'text/event-stream'
}
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
return { reader, decoder };
}
import { EventSourcePolyfill } from 'event-source-polyfill';
async function connectMCP() {
// First, login to get token
const token = await login('user@example.com', 'password');
// Create authenticated SSE connection
const eventSource = new EventSourcePolyfill('/mcp', {
headers: {
'Authorization': `Bearer ${token}`
}
});
let messageEndpoint = null;
eventSource.addEventListener('endpoint', (event) => {
messageEndpoint = event.data;
console.log('Message endpoint:', messageEndpoint);
// Initialize the MCP session
initializeMCP(messageEndpoint);
});
eventSource.addEventListener('message', (event) => {
const response = JSON.parse(event.data);
console.log('MCP response:', response);
});
eventSource.onerror = (error) => {
console.error('SSE error:', error);
};
}
async function initializeMCP(messageEndpoint) {
await fetch(messageEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: '2024-11-05',
capabilities: {},
clientInfo: { name: 'my-client', version: '1.0.0' }
}
})
});
}
If authentication fails, you'll receive an error when trying to execute operations:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{ "type": "text", "text": "unauthorized: authentication required" }
],
"isError": true
}
}
MCP resource subscriptions (for real-time updates) also require authentication. The subscription endpoints check for user context before allowing subscriptions:
{
"jsonrpc": "2.0",
"id": 8,
"method": "resources/subscribe",
"params": {
"uri": "graphql://subscription/taskCreated"
}
}
If not authenticated:
{
"jsonrpc": "2.0",
"id": 8,
"error": {
"code": -32603,
"message": "Authentication required for subscriptions"
}
}
The MCP server exposes three tools:
| Tool | Description |
|---|---|
introspect |
Discover the GraphQL schema - types, fields, queries, mutations |
query |
Execute GraphQL queries (read operations) |
mutate |
Execute GraphQL mutations (create/update/delete operations) |
/mcp endpointendpoint event with the message URLinitialize request via the message endpointtools/call to execute introspect, query, or mutate{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "my-client",
"version": "1.0.0"
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": { "listChanged": false }
},
"serverInfo": {
"name": "ARP MCP Server",
"version": "1.0.0"
},
"instructions": "Use the introspect tool to discover the GraphQL schema..."
}
}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "introspect",
"description": "Get GraphQL schema information...",
"inputSchema": {
"type": "object",
"properties": {
"typeName": {
"type": "string",
"description": "Optional - specific type to introspect"
}
}
}
},
{
"name": "query",
"description": "Execute GraphQL queries...",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" },
"variables": { "type": "object" }
},
"required": ["query"]
}
},
{
"name": "mutate",
"description": "Execute GraphQL mutations...",
"inputSchema": {
"type": "object",
"properties": {
"mutation": { "type": "string" },
"variables": { "type": "object" }
},
"required": ["mutation"]
}
}
]
}
}
Get full schema overview:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "introspect",
"arguments": {}
}
}
Get specific type information:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "introspect",
"arguments": {
"typeName": "User"
}
}
}
Execute a GraphQL query:
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "query",
"arguments": {
"query": "query { users { id email roles { name } } }"
}
}
}
With variables:
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "query",
"arguments": {
"query": "query User($id: ID!) { user(id: $id) { id email } }",
"variables": { "id": "1" }
}
}
}
Execute a GraphQL mutation:
{
"jsonrpc": "2.0",
"id": 6,
"method": "tools/call",
"params": {
"name": "mutate",
"arguments": {
"mutation": "mutation CreateUser($input: NewUser!) { createUser(input: $input) { id email } }",
"variables": {
"input": {
"email": "newuser@example.com",
"password": "securepassword",
"roles": []
}
}
}
}
}
The MCP server inherits authentication from the SSE connection. If you need authenticated operations:
login mutation// Establish SSE connection
const eventSource = new EventSource('/mcp');
eventSource.addEventListener('endpoint', (event) => {
const messageUrl = event.data;
console.log('Message endpoint:', messageUrl);
// Initialize the connection
fetch(messageUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: '2024-11-05',
capabilities: {},
clientInfo: { name: 'js-client', version: '1.0.0' }
}
})
});
});
eventSource.addEventListener('message', (event) => {
const response = JSON.parse(event.data);
console.log('Received:', response);
});
// Call a tool
async function callTool(name, arguments) {
const response = await fetch('/message?sessionId=YOUR_SESSION_ID', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: Date.now(),
method: 'tools/call',
params: { name, arguments }
})
});
return response.json();
}
// Usage
await callTool('query', { query: '{ users { id email } }' });
| Use Case | Tool | Example |
|---|---|---|
| Discover API structure | introspect |
Get schema before making queries |
| Read user data | query |
Fetch users, tasks, messages |
| Create new records | mutate |
Create users, tasks, notes |
| Update existing records | mutate |
Update task status, user info |
| Delete records | mutate |
Remove tasks, notes, users |
The MCP server also supports resources for real-time subscriptions. Resources allow AI agents to subscribe to GraphQL subscription events through the MCP protocol.
| Resource URI | Description |
|---|---|
graphql://subscription/taskCreated |
Task creation events (received by assignee) |
graphql://subscription/taskUpdated |
Task update events (received by assignee) |
graphql://subscription/taskDeleted |
Task deletion events (received by assignee) |
graphql://subscription/messageAdded |
New message events (received by receivers) |
{
"jsonrpc": "2.0",
"id": 7,
"method": "resources/list"
}
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"resources": [
{
"uri": "graphql://subscription/taskCreated",
"name": "taskCreated",
"description": "Subscribe to task creation events...",
"mimeType": "application/json"
}
]
}
}
{
"jsonrpc": "2.0",
"id": 8,
"method": "resources/subscribe",
"params": {
"uri": "graphql://subscription/taskCreated"
}
}
{
"jsonrpc": "2.0",
"id": 8,
"result": {
"subscribed": true,
"uri": "graphql://subscription/taskCreated"
}
}
After subscribing, you'll receive notifications via the SSE connection:
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"uri": "graphql://subscription/taskCreated",
"contents": {
"uri": "graphql://subscription/taskCreated",
"mimeType": "application/json",
"text": "{\"id\":\"5\",\"title\":\"New Task\",\"assigneeId\":\"2\",...}"
}
}
}
{
"jsonrpc": "2.0",
"id": 9,
"method": "resources/unsubscribe",
"params": {
"uri": "graphql://subscription/taskCreated"
}
}
// Subscribe to task creation events
async function subscribeToTasks(sessionId) {
const response = await fetch(`/message?sessionId=${sessionId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'resources/subscribe',
params: { uri: 'graphql://subscription/taskCreated' }
})
});
return response.json();
}
// Listen for notifications on the SSE connection
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.method === 'notifications/resources/updated') {
const task = JSON.parse(data.params.contents.text);
console.log('New task:', task);
}
});
introspect to understand the schema before making queriesisError: true in tool results