# ARP Server Client Implementation Guide

This document provides the necessary information to implement a client for the ARP (Agent-native ERP) GraphQL server.

## Table of Contents

1. [Overview](#overview)
2. [Connection & Endpoint](#connection--endpoint)
3. [Authentication](#authentication)
4. [Authorization & Permissions](#authorization--permissions)
5. [GraphQL Operations](#graphql-operations)
6. [Error Handling](#error-handling)
7. [Subscriptions](#subscriptions)
8. [MCP Server](#mcp-server)

---

## Overview

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.

---

## Connection & Endpoint

- **Protocol**: HTTP/HTTPS
- **Endpoint**: `/query` (GraphQL endpoint)
- **Content-Type**: `application/json`
- **Request Method**: `POST` for queries and mutations

### Example Request Structure

```json
{
  "query": "string (GraphQL query or mutation)",
  "variables": "object (optional variables)",
  "operationName": "string (optional operation name)"
}
```

---

## Authentication

### Login Flow

1. Call the `login` mutation with email and password
2. Receive a JWT token in the response
3. Include the token in subsequent requests via the `Authorization` header

### Login Mutation

```graphql
mutation Login($email: String!, $password: String!) {
  login(email: $email, password: $password) {
    token
    user {
      id
      email
      roles {
        id
        name
        permissions {
          id
          code
          description
        }
      }
    }
  }
}
```

**Variables:**
```json
{
  "email": "user@example.com",
  "password": "your-password"
}
```

**Response:**
```json
{
  "data": {
    "login": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "user": {
        "id": "1",
        "email": "user@example.com",
        "roles": [...]
      }
    }
  }
}
```

### Using the Token

Include the token in all subsequent requests:

```
Authorization: Bearer <token>
```

### Token Details

| Property | Value |
|----------|-------|
| Algorithm | HS256 |
| Expiration | 10 years from issuance |
| Claims | `user_id`, `email`, `roles`, `permissions` |

---

## Authorization & Permissions

### Permission-Based Access Control

Many operations require specific permissions. The permission format is `resource:action`.

### Required Permissions by Operation

| 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` |

### Authorization Errors

If authentication or authorization fails, the API returns an error:

```json
{
  "errors": [
    {
      "message": "unauthorized: authentication required"
    }
  ]
}
```

Or for missing permissions:

```json
{
  "errors": [
    {
      "message": "unauthorized: missing user:update permission"
    }
  ]
}
```

---

## GraphQL Operations

### Queries (Read Operations)

All queries require authentication.

#### Users

```graphql
# 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
  }
}
```

#### Notes

```graphql
# 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
  }
}
```

#### Roles

```graphql
# 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
    }
  }
}
```

#### Permissions

```graphql
# Get all permissions
query Permissions {
  permissions {
    id
    code
    description
  }
}

# Get single permission by ID
query Permission($id: ID!) {
  permission(id: $id) {
    id
    code
    description
  }
}
```

#### Services

```graphql
# 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
  }
}
```

#### Tasks

```graphql
# 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
  }
}
```

#### Task Statuses

```graphql
# 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
  }
}
```

#### Messages

```graphql
# 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
  }
}
```

### Mutations (Write Operations)

All mutations require authentication. Some require additional permissions (see Authorization section).

#### Authentication

```graphql
mutation Login($email: String!, $password: String!) {
  login(email: $email, password: $password) {
    token
    user { id email }
  }
}
```

#### Users

```graphql
# 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)
}
```

#### Notes

```graphql
# 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)
}
```

#### Roles

```graphql
# 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)
}
```

#### Permissions

```graphql
# 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)
}
```

#### Services

```graphql
# 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)
}
```

#### Tasks

```graphql
# 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)
}
```

#### Task Statuses

```graphql
# 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)
}
```

#### Messages

```graphql
# 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)
}
```

---

## Error Handling

### Error Response Format

Errors are returned in the standard GraphQL error format:

```json
{
  "errors": [
    {
      "message": "Error description",
      "path": ["fieldName"],
      "locations": [{ "line": 1, "column": 2 }]
    }
  ],
  "data": null
}
```

### Common Error Messages

| 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 |

---

## Subscriptions

The API supports real-time updates via GraphQL subscriptions. Subscriptions use WebSocket connections and provide filtered events based on user context.

### Event Filtering

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:
- A user will only receive task events for tasks assigned to them
- A user will only receive message events for messages where they are a receiver
- Unassigned tasks do not trigger notifications to any user

### Available Subscriptions

```graphql
# 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
  }
}
```

### WebSocket Connection

To use subscriptions, establish a WebSocket connection:

1. **Protocol**: WebSocket over HTTP/HTTPS
2. **Endpoint**: `/query` (same as GraphQL endpoint)
3. **Authentication**: Include the JWT token in the connection parameters or headers

### WebSocket Subprotocol

The server uses the standard GraphQL over WebSocket protocol (`graphql-ws`):

#### Connection Initialization

```json
{
  "type": "connection_init",
  "payload": {
    "Authorization": "Bearer <token>"
  }
}
```

#### Subscribe to a Topic

```json
{
  "id": "1",
  "type": "start",
  "payload": {
    "query": "subscription { taskCreated { id title assigneeId } }"
  }
}
```

#### Receive Events

```json
{
  "id": "1",
  "type": "data",
  "payload": {
    "data": {
      "taskCreated": {
        "id": "5",
        "title": "New Task",
        "assigneeId": "2"
      }
    }
  }
}
```

#### Unsubscribe

```json
{
  "id": "1",
  "type": "stop"
}
```

### Example: JavaScript/TypeScript Client

Using the `graphql-ws` library:

```javascript
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();
```

### Example: Go Client

Using `github.com/99designs/gqlgen/client`:

```go
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)
    }
}
```

### Subscription Use Cases

| 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 |

### Best Practices

1. **Reconnect on Disconnect**: Implement automatic reconnection with exponential backoff
2. **Handle Auth Errors**: If authentication fails, re-authenticate and retry
3. **Filter Client-Side**: Even though server filters, consider additional client-side filtering if needed
4. **Connection Management**: Close subscriptions when no longer needed to free resources
5. **Error Handling**: Always handle subscription errors gracefully

---

## Data Types Reference

### Scalar Types

| Type | Description |
|------|-------------|
| `ID` | Unique identifier (string representation) |
| `String` | UTF-8 string |
| `Boolean` | true or false |

### Enum Values

**Task Priority**: `low`, `medium`, `high` (string values)

**Task Status Codes**: Customizable (e.g., `open`, `in_progress`, `done`)

### Date/Time Format

All timestamps use **ISO 8601 / RFC 3339** format:

```
2024-01-15T10:30:00Z
```

---

## Quick Reference

### Authentication Header

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

### Typical Client Flow

1. **Login** → Obtain JWT token
2. **Store Token** → Securely persist the token
3. **Authenticated Requests** → Include token in all subsequent requests
4. **Handle Errors** → Check for authentication/authorization errors
5. **Token Refresh** → Token has long expiration (10 years), but handle expiry if needed

### Permission Check Pattern

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.

---

## Implementation Tips

### Language-Agnostic Considerations

1. **Use a GraphQL Client Library**: Most languages have mature GraphQL clients (Apollo, urql, gql-request, etc.)
2. **Handle JWT Securely**: Store tokens securely; never in localStorage for web apps (use httpOnly cookies or secure storage)
3. **Implement Retry Logic**: For network failures, implement exponential backoff
4. **Cache Responses**: Use client-side caching to reduce redundant queries
5. **Batch Requests**: Combine multiple queries in a single request when possible

### Example HTTP Request

```http
POST /query HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

{
  "query": "query { users { id email } }"
}
```

### Example cURL Command

```bash
curl -X POST https://api.example.com/query \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"query": "query { users { id email } }"}'
```

---

## MCP Server

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 Endpoint

- **Protocol**: HTTP with Server-Sent Events (SSE)
- **Endpoint**: `/mcp` (SSE connection endpoint)
- **Message Endpoint**: `/message` (for sending JSON-RPC messages)
- **Protocol Version**: `2024-11-05`

### MCP Authentication

All MCP operations require authentication. The authentication flow is:

1. **Obtain JWT Token**: First, authenticate via the GraphQL `login` mutation to get a JWT token
2. **Include Token in SSE Connection**: Pass the `Authorization` header when establishing the SSE connection to `/mcp`
3. **Session Maintains Auth**: The user context is stored in the session, so subsequent `/message` requests inherit authentication

#### Authentication Header

Include the JWT token when connecting to the MCP endpoint:

```http
GET /mcp HTTP/1.1
Host: api.example.com
Accept: text/event-stream
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

#### Authenticated Connection Flow

```
┌─────────────────────────────────────────────────────────────────┐
│ 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                         │
└─────────────────────────────────────────────────────────────────┘
```

#### JavaScript Example: Authenticated MCP Client

```javascript
// 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 };
}
```

#### Using event-source-polyfill Library

```javascript
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' }
      }
    })
  });
}
```

#### Authentication Errors

If authentication fails, you'll receive an error when trying to execute operations:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "unauthorized: authentication required" }
    ],
    "isError": true
  }
}
```

#### Subscription Authentication

MCP resource subscriptions (for real-time updates) also require authentication. The subscription endpoints check for user context before allowing subscriptions:

```json
{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "resources/subscribe",
  "params": {
    "uri": "graphql://subscription/taskCreated"
  }
}
```

If not authenticated:

```json
{
  "jsonrpc": "2.0",
  "id": 8,
  "error": {
    "code": -32603,
    "message": "Authentication required for subscriptions"
  }
}
```

### Available Tools

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) |

### Connection Flow

1. **Establish SSE Connection**: Connect to `/mcp` endpoint
2. **Receive Endpoint URL**: Server sends an `endpoint` event with the message URL
3. **Initialize**: Send an `initialize` request via the message endpoint
4. **Call Tools**: Use `tools/call` to execute introspect, query, or mutate

### MCP Protocol Messages

#### Initialize Request

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {},
    "clientInfo": {
      "name": "my-client",
      "version": "1.0.0"
    }
  }
}
```

#### Initialize Response

```json
{
  "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..."
  }
}
```

#### List Tools Request

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}
```

#### List Tools Response

```json
{
  "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"]
        }
      }
    ]
  }
}
```

### Tool Usage Examples

#### Introspect Tool

Get full schema overview:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "introspect",
    "arguments": {}
  }
}
```

Get specific type information:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "introspect",
    "arguments": {
      "typeName": "User"
    }
  }
}
```

#### Query Tool

Execute a GraphQL query:

```json
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "query",
    "arguments": {
      "query": "query { users { id email roles { name } } }"
    }
  }
}
```

With variables:

```json
{
  "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" }
    }
  }
}
```

#### Mutate Tool

Execute a GraphQL mutation:

```json
{
  "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": []
        }
      }
    }
  }
}
```

### Authentication

The MCP server inherits authentication from the SSE connection. If you need authenticated operations:

1. First obtain a JWT token via the GraphQL `login` mutation
2. Include the token when establishing the SSE connection or in request headers

### Example: JavaScript MCP Client

```javascript
// 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 } }' });
```

### MCP Use Cases

| 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 |

### MCP Resources (Subscriptions)

The MCP server also supports **resources** for real-time subscriptions. Resources allow AI agents to subscribe to GraphQL subscription events through the MCP protocol.

#### Available Resources

| 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) |

#### List Resources Request

```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "resources/list"
}
```

#### List Resources Response

```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "resources": [
      {
        "uri": "graphql://subscription/taskCreated",
        "name": "taskCreated",
        "description": "Subscribe to task creation events...",
        "mimeType": "application/json"
      }
    ]
  }
}
```

#### Subscribe to Resource

```json
{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "resources/subscribe",
  "params": {
    "uri": "graphql://subscription/taskCreated"
  }
}
```

#### Subscribe Response

```json
{
  "jsonrpc": "2.0",
  "id": 8,
  "result": {
    "subscribed": true,
    "uri": "graphql://subscription/taskCreated"
  }
}
```

#### Receive Resource Updates

After subscribing, you'll receive notifications via the SSE connection:

```json
{
  "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\",...}"
    }
  }
}
```

#### Unsubscribe

```json
{
  "jsonrpc": "2.0",
  "id": 9,
  "method": "resources/unsubscribe",
  "params": {
    "uri": "graphql://subscription/taskCreated"
  }
}
```

#### Subscription Example

```javascript
// 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);
  }
});
```

### MCP Best Practices

1. **Introspect First**: Always call `introspect` to understand the schema before making queries
2. **Handle Errors**: Check for `isError: true` in tool results
3. **Use Variables**: Pass variables separately for better readability and security
4. **Session Management**: Maintain the SSE connection for the duration of your session
5. **Reconnect on Disconnect**: Implement reconnection logic for long-running sessions
6. **Use Resources for Real-time**: Subscribe to resources for real-time updates instead of polling

---

## Workflow Engine

The workflow engine enables you to define complex multi-step processes as JSON-based DAGs (Directed Acyclic Graphs). Each node in the DAG represents a task or decision point, and dependencies between nodes are automatically resolved.

### Workflow Types

| Type | Description |
|------|-------------|
| `task` | A task node that creates a task for a user |
| `condition` | A decision node that evaluates conditions |
| `parallel` | A node that spawns parallel branches |
| `join` | A node that waits for multiple branches to complete |
| `trigger` | A node that starts the workflow |

### Workflow Permissions

| Operation | Required Permission |
|-----------|---------------------|
| `createWorkflowTemplate` | `workflow:create` |
| `updateWorkflowTemplate` | `workflow:manage` |
| `deleteWorkflowTemplate` | `workflow:manage` |
| `startWorkflow` | `workflow:start` |
| `cancelWorkflow` | `workflow:manage` |
| `retryWorkflowNode` | `workflow:intervene` |
| `workflowTemplates` query | `workflow:view` |
| `workflowInstance` query | `workflow:view` |

### Workflow Definition Format

Workflows are defined as JSON with a `nodes` object where each key is a unique node identifier:

```json
{
  "nodes": {
    "start": {
      "type": "task",
      "title": "Initial Review",
      "content": "Review the initial request",
      "assignee": "1",
      "dependsOn": []
    },
    "analysis": {
      "type": "task",
      "title": "Data Analysis",
      "content": "Analyze the data",
      "assignee": "2",
      "dependsOn": ["start"]
    },
    "approval": {
      "type": "task",
      "title": "Manager Approval",
      "content": "Get manager approval",
      "assignee": "3",
      "dependsOn": ["analysis"]
    },
    "end": {
      "type": "task",
      "title": "Complete",
      "content": "Mark workflow as complete",
      "assignee": "1",
      "dependsOn": ["approval"]
    }
  }
}
```

### Workflow API Examples

#### Create a Workflow Template

```graphql
mutation CreateWorkflowTemplate($input: NewWorkflowTemplate!) {
  createWorkflowTemplate(input: $input) {
    id
    name
    description
    definition
    isActive
    createdAt
  }
}
```

**Variables:**
```json
{
  "input": {
    "name": "Onboarding",
    "description": "New employee onboarding workflow",
    "definition": "{\"nodes\":{\"start\":{\"type\":\"task\",\"title\":\"Welcome\",\"content\":\"Send welcome email\",\"assignee\":\"1\",\"dependsOn\":[]},\"setup\":{\"type\":\"task\",\"title\":\"Setup Account\",\"content\":\"Create user account\",\"assignee\":\"2\",\"dependsOn\":[\"start\"]}}}",
    "isActive": true
  }
}
```

#### Start a Workflow Instance

```graphql
mutation StartWorkflow($templateId: ID!, $input: StartWorkflowInput!) {
  startWorkflow(templateId: $templateId, input: $input) {
    id
    status
    context
    template {
      id
      name
    }
    service {
      id
      name
    }
    createdAt
  }
}
```

**Variables:**
```json
{
  "templateId": "1",
  "input": {
    "serviceId": "1",
    "context": "New hire onboarding"
  }
}
```

#### Get All Workflow Instances

```graphql
query WorkflowInstances {
  workflowInstances {
    id
    status
    context
    template {
      id
      name
    }
    service {
      id
      name
    }
    createdAt
    completedAt
  }
}
```

#### Get Single Workflow Instance

```graphql
query WorkflowInstance($id: ID!) {
  workflowInstance(id: $id) {
    id
    status
    context
    template {
      id
      name
      definition
    }
    service {
      id
      name
    }
    createdAt
    completedAt
  }
}
```

#### Cancel a Workflow

```graphql
mutation CancelWorkflow($id: ID!) {
  cancelWorkflow(id: $id) {
    id
    status
    completedAt
  }
}
```

#### Retry a Failed Node

```graphql
mutation RetryWorkflowNode($nodeId: ID!) {
  retryWorkflowNode(nodeId: $nodeId) {
    id
    nodeKey
    nodeType
    status
    task {
      id
      title
    }
    retryCount
  }
}
```

### MCP Tools for Workflows

The MCP server also supports workflow operations through the `query` and `mutate` tools.

#### Query Workflow Templates

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "query",
    "arguments": {
      "query": "query { workflowTemplates { id name isActive } }"
    }
  }
}
```

#### Start Workflow via MCP

```json
{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "tools/call",
  "params": {
    "name": "mutate",
    "arguments": {
      "mutation": "mutation StartWorkflow($templateId: ID!, $input: StartWorkflowInput!) { startWorkflow(templateId: $templateId, input: $input) { id status } }",
      "variables": {
        "templateId": "1",
        "input": {
          "serviceId": "1",
          "context": "Workflow started via MCP"
        }
      }
    }
  }
}
```

### Workflow Node States

| State | Description |
|-------|-------------|
| `pending` | Node is waiting for dependencies to complete |
| `ready` | Node is ready to execute (all dependencies satisfied) |
| `running` | Node is currently executing |
| `completed` | Node has completed successfully |
| `failed` | Node has failed (may be retried) |
| `skipped` | Node was skipped |

### Automatic Task Completion Integration

When a task associated with a workflow node is marked as "done", the workflow engine automatically:

1. **Marks the node as completed** - The workflow node status changes to `completed`
2. **Evaluates downstream dependencies** - Checks which nodes are now ready to execute
3. **Creates tasks for newly ready nodes** - Automatically creates tasks for nodes whose dependencies are satisfied

This enables workflows to progress automatically as users complete their assigned tasks without manual intervention.

### Retry Logic

Workflows support automatic retry with configurable max retries:

- **Default Max Retries**: 3
- **Retry Delay**: 60 seconds (configurable via `WORKFLOW_RETRY_DELAY`)

To manually retry a failed node:

```graphql
mutation RetryWorkflowNode($nodeId: ID!) {
  retryWorkflowNode(nodeId: $nodeId) {
    id
    nodeKey
    status
    retryCount
  }
}
```

### Best Practices

1. **Design DAGs Carefully**: Ensure workflows have clear start and end points
2. **Use Meaningful Node Keys**: Node keys should be descriptive and unique
3. **Handle Failures**: Design workflows to handle node failures gracefully
4. **Monitor Workflow Progress**: Use `workflowInstances` query to track progress
5. **Set Proper Assignees**: Ensure each task node has an appropriate assignee
6. **Test Workflows**: Test workflows with sample data before production use
