# Model Context Protocol (MCP)

URL: https://cogitator.app/docs/integrations/mcp

> Full MCP support -- connect to external MCP servers to use their tools, or expose Cogitator tools as an MCP server.

## Overview

The `@cogitator-ai/mcp` package provides bidirectional MCP integration:

* **MCPClient** -- connect to any MCP server (filesystem, database, GitHub, etc.) and use its tools inside Cogitator agents.
* **MCPServer** -- expose your Cogitator tools, resources, and prompts as an MCP server for Claude Desktop, other AI assistants, or any MCP client.
* **Tool Adapter** -- convert between Cogitator and MCP tool formats, including Zod-to-JSON Schema conversion.

```bash
pnpm add @cogitator-ai/mcp @cogitator-ai/core
```

## MCPClient

### Connecting to an MCP Server

Use `MCPClient.connect()` to establish a connection. The client discovers server capabilities automatically and provides access to tools, resources, and prompts.

```typescript
import { MCPClient } from '@cogitator-ai/mcp';

const client = await MCPClient.connect({
  transport: 'stdio',
  command: 'npx',
  args: ['-y', '@modelcontextprotocol/server-filesystem', '/home/user/documents'],
  timeout: 10000,
});

console.log(client.getCapabilities());

const tools = await client.getTools();
const resources = await client.listResources();
const prompts = await client.listPrompts();

await client.close();
```

### Using MCP Tools with Agents

The tools returned by `getTools()` are standard Cogitator `Tool` instances -- pass them directly to an agent:

```typescript
import { Cogitator, Agent } from '@cogitator-ai/core';
import { MCPClient } from '@cogitator-ai/mcp';

const fsClient = await MCPClient.connect({
  transport: 'stdio',
  command: 'npx',
  args: ['-y', '@modelcontextprotocol/server-filesystem', '/workspace'],
});

const dbClient = await MCPClient.connect({
  transport: 'http',
  url: 'http://localhost:3001/mcp',
});

const fsTools = await fsClient.getTools();
const dbTools = await dbClient.getTools();

const agent = new Agent({
  name: 'file-manager',
  model: 'openai/gpt-6.1-sol',
  instructions: 'Help users manage files and query databases.',
  tools: [...fsTools, ...dbTools],
});

const cog = new Cogitator({/* ... */});
const result = await cog.run(agent, {
  input: 'List all .ts files in /workspace/src and count them',
});

await fsClient.close();
await dbClient.close();
```

### connectMCPServer Helper

For the common pattern of connect-get-tools-cleanup, use the shorthand:

```typescript
import { connectMCPServer } from '@cogitator-ai/mcp';

const { tools, cleanup } = await connectMCPServer({
  transport: 'stdio',
  command: 'npx',
  args: ['-y', '@modelcontextprotocol/server-github'],
  env: { GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_TOKEN! },
});

const agent = new Agent({
  name: 'github-bot',
  model: 'openai/gpt-6.1-sol',
  instructions: 'Answer questions about GitHub repositories.',
  tools,
});

await cleanup();
```

It also returns the connected `client`. For stdio servers, `env` is merged over the current process environment.

### MCPClientConfig

| Option                                                 | Type                         | Description                                          |
| ------------------------------------------------------ | ---------------------------- | ---------------------------------------------------- |
| `transport`                                            | `'stdio' \| 'http' \| 'sse'` | Transport protocol (`'sse'` is an alias of `'http'`) |
| `command`                                              | `string`                     | Command for stdio transport                          |
| `args`                                                 | `string[]`                   | Command arguments for stdio                          |
| `env`                                                  | `Record<string, string>`     | Environment variables for stdio                      |
| `cwd`                                                  | `string`                     | Working directory for the stdio process              |
| `url`                                                  | `string`                     | Server URL for HTTP/SSE transport                    |
| `headers`                                              | `Record<string, string>`     | Extra HTTP headers (e.g. `Authorization`)            |
| `timeout`                                              | `number`                     | Connection timeout in ms (default: none)             |
| `clientName`                                           | `string`                     | Client identification name                           |
| `clientVersion`                                        | `string`                     | Client version string                                |
| `retry`                                                | `MCPRetryConfig`             | Retry settings for failed operations                 |
| `autoReconnect`                                        | `boolean`                    | Auto-reconnect on connection loss (default: true)    |
| `onReconnecting`, `onReconnected`, `onReconnectFailed` | callbacks                    | Reconnection lifecycle hooks                         |

The `http` transport speaks MCP Streamable HTTP; servers that only offer the legacy HTTP+SSE transport are not supported. Besides `getTools()`, the client has `listToolDefinitions()` (raw MCP definitions), `isConnected()`, `reconnect()` and `close()`.

### Retry and Reconnection

The client has built-in retry with exponential backoff for all operations. On connection loss, it automatically reconnects:

```typescript
const client = await MCPClient.connect({
  transport: 'http',
  url: 'http://mcp-server:3000/mcp',
  retry: {
    maxRetries: 5,
    initialDelay: 1000,
    maxDelay: 30000,
    backoffMultiplier: 2,
    retryOnConnectionLoss: true,
  },
  onReconnecting: (attempt) => console.log(`Reconnecting (attempt ${attempt})...`),
  onReconnected: () => console.log('Reconnected'),
  onReconnectFailed: (err) => console.error('Reconnection failed:', err),
});
```

Only transient failures are retried (connection loss, request timeouts, network errors). Protocol errors returned by the server, tool failures and aborted calls fail immediately, and concurrent calls that lose the connection share a single reconnect.

### Tool Results and Errors

`callTool` returns `structuredContent` when the server provides it, otherwise the unwrapped content (text blocks are JSON-parsed when possible, a single block is returned on its own, an empty result is `null`). A tool result with `isError: true` throws `MCPToolError`, so MCP failures show up as ordinary tool errors inside agent runs. Pass an `AbortSignal` to cancel a call; tools created by `getTools()` forward the run's signal automatically.

```typescript
import { MCPToolError } from '@cogitator-ai/mcp';

try {
  await client.callTool('write_file', { path: '/etc/passwd', content: '' }, { timeout: 5_000 });
} catch (error) {
  if (error instanceof MCPToolError) {
    console.error(`${error.toolName} failed: ${error.message}`);
  }
}
```

### Resources and Prompts

Beyond tools, MCP servers can expose resources (data sources) and prompts (reusable prompt templates):

```typescript
const resources = await client.listResources();

const content = await client.readResource('file:///workspace/config.json');
console.log(content.text);

const prompts = await client.listPrompts();

const messages = await client.getPrompt('code-review', {
  language: 'typescript',
  style: 'thorough',
});
```

`readResource` returns the first content block; `readResourceContents` returns all of them.

## MCPServer

### Exposing Cogitator Tools

Create an MCP server to make your Cogitator tools available to any MCP client:

```typescript
import { MCPServer } from '@cogitator-ai/mcp';
import { tool } from '@cogitator-ai/core';
import { z } from 'zod';

const calculator = tool({
  name: 'add',
  description: 'Add two numbers',
  parameters: z.object({ a: z.number(), b: z.number() }),
  execute: async ({ a, b }) => ({ sum: a + b }),
});

const server = new MCPServer({
  name: 'my-tools',
  version: '1.0.0',
  transport: 'stdio',
});

server.registerTools([calculator]);
await server.start();
```

`serveMCPTools(tools, config)` does the same in one call and returns the started server. Tools, resources and prompts can be added one by one (`registerTool`, `registerResource`, `registerPrompt`) or in batches (`registerTools`, `registerResources`, `registerPrompts`), removed with the matching `unregister*` methods and listed with `getRegisteredTools()` / `getRegisteredResources()` / `getRegisteredPrompts()`.

### HTTP Transport

For remote access, use the HTTP transport:

```typescript
const server = new MCPServer({
  name: 'remote-tools',
  version: '1.0.0',
  transport: 'http',
  port: 3000,
  host: 'localhost',
  logging: true,
});

server.registerTools([calculator, webSearch, fileReader]);
await server.start();
```

Clients connect to `http://localhost:3000/mcp` (`port` defaults to `3000`, `host` to `localhost`; other paths answer `404`). Use `port: 0` to bind a random free port and read it back with `server.getPort()`. `maxBodySize` (default 10 MB), `corsOrigin` (default `'*'`) and `auth` (see below) tune the HTTP endpoint; `start()` rejects if the port cannot be bound and `stop()` closes open connections.

Tool arguments are validated against the tool's full Zod schema before `execute` runs; invalid arguments and thrown errors come back to the client as `isError` tool results, while errors thrown by resource `read` and prompt `get` handlers are returned as JSON-RPC errors. With `logging: true`, diagnostics go to stderr so the stdio transport's stdout stays reserved for JSON-RPC.

### Authentication and Per-User Tools

Over HTTP the server is open to anyone who can reach it. Pass `auth` to establish the caller of every request, usually from its bearer token; it receives the raw `http.IncomingMessage` and returns an `MCPCaller` (`{ userId?, scopes?, metadata? }`). Requests it returns `undefined` for (or throws on) are answered `401` with `WWW-Authenticate: Bearer`. The caller's `userId` reaches tools as `context.userId`, and resource `read` and prompt `get` handlers receive the caller as their second argument:

```typescript
import { MCPServer, type MCPAuthFunction } from '@cogitator-ai/mcp';

const auth: MCPAuthFunction = async (request) => {
  const token = request.headers.authorization?.replace(/^Bearer /, '');
  const session = token ? await sessions.find(token) : undefined;
  return session ? { userId: session.userId, scopes: session.scopes } : undefined;
};

const listMyOrders = tool({
  name: 'list_my_orders',
  description: "List the calling customer's orders",
  parameters: z.object({}),
  execute: async (_args, context) => orders.forUser(context.userId!),
});

const server = new MCPServer({ name: 'shop', version: '1.0.0', transport: 'http', auth });
server.registerTool(listMyOrders);
```

An agent then acts for one user by connecting with that user's token:

```typescript
const client = await MCPClient.connect({
  transport: 'http',
  url: 'https://shop.example.com/mcp',
  headers: { Authorization: `Bearer ${userToken}` },
});
const agentForUser = support.clone({ id: support.id, tools: await client.getTools() });
await cog.run(agentForUser, { input, userId, threadId });
```

The full flow, including thread isolation between users, is in [`examples/mcp/03-per-user-mcp.ts`](https://github.com/cogitator-ai/Cogitator-AI/blob/main/examples/mcp/03-per-user-mcp.ts).

### Registering Resources

Expose data sources as MCP resources:

```typescript
server.registerResource({
  uri: 'config://app/settings',
  name: 'App Settings',
  description: 'Current application configuration',
  mimeType: 'application/json',
  read: async () => ({
    uri: 'config://app/settings',
    text: JSON.stringify(await loadAppSettings()),
  }),
});
```

A URI with `{placeholders}` is registered as a resource template; the matched values arrive in `params`. `read` may return one content or an array, and `uri` defaults to the URI that was read:

```typescript
server.registerResource({
  uri: 'db://users/{userId}',
  name: 'User Profile',
  description: 'Fetch a user profile by ID',
  read: async (params) => ({
    uri: `db://users/${params.userId}`,
    text: JSON.stringify(await db.getUser(params.userId)),
  }),
});
```

### Registering Prompts

Expose reusable prompt templates:

```typescript
server.registerPrompt({
  name: 'code-review',
  title: 'Code Review',
  description: 'Generate a code review for the given language',
  arguments: [
    { name: 'language', description: 'Programming language', required: true },
    { name: 'style', description: 'Review depth: quick or thorough' },
  ],
  get: async (args) => ({
    messages: [
      {
        role: 'user',
        content: {
          type: 'text',
          text: `Review this ${args.language} code. Style: ${args.style ?? 'thorough'}.`,
        },
      },
    ],
  }),
});
```

## Serving Agents

`serveAgents` turns Cogitator agents into an MCP server in one call. Each agent becomes a tool that takes a `task` (and optionally the `threadId` of an earlier answer, to continue that conversation — needs `memory` on the Cogitator) and answers with the agent's reply:

```typescript
import { serveAgents } from '@cogitator-ai/mcp';

await serveAgents(cog, [researcher, writer]); // stdio — for Claude Desktop, Cursor, Claude Code
await serveAgents(cog, support, { transport: 'http', port: 3333, auth }); // remote, per-user
```

In the client's MCP config:

```json
{ "mcpServers": { "cogitator": { "command": "npx", "args": ["tsx", "my-agents.ts"] } } }
```

The tool answers `{ status: 'completed', output, threadId }`. Each tool is named after its agent, with characters other than letters, digits, `_` and `-` replaced (`toolNames: { [agent.name]: 'tool_name' }` renames them), and described by the agent's `description` — or by the first sentence of its instructions when it has none. `serveAgents` takes every `MCPServer` option (defaults: name `cogitator-agents`, `transport: 'stdio'`, `sessions: true`) and returns the started server. With `auth`, runs carry the caller's `userId`, so threads and memory stay per user ([Multiple Users](/docs/advanced/multi-user)), and the client's cancellation aborts the run.

**Approvals.** When an agent's tool needs [approval](/docs/tools/approvals), the server asks the person at the client through MCP elicitation and the run goes on with their answer. Clients without elicitation get a paused answer (`{ status: 'paused', threadId, pendingApprovals, next }`) and continue with the `<agent>_resume` tool (the agent's tool name plus `_resume`) — registered only for agents with a tool whose `requiresApproval` is `true` or a check function — passing `{ threadId, approved, reason? }` once the user decided. Elicitation needs a connection the client can reply on: stdio, or HTTP with `sessions: true` — which `serveAgents` turns on.

`agentTools(cog, agents, toolNames?)` returns the same tools to register on an `MCPServer` you configure yourself. Both accept anything with Cogitator's `run` and `resume` (`AgentHost`).

### Sessions

`MCPServer` over HTTP serves each request on its own by default. `sessions: true` keeps a session per client (`mcp-session-id`), which requests from the server to the client — such as elicitation — need. A session belongs to the caller that started it: a request with another caller's credentials gets `403`. Elicitation works over stdio or HTTP with `sessions: true`; elsewhere `elicit` is not available.

Your own tools reach the elicitation channel through their context. `elicit` resolves to `{ action: 'accept', content }`, `{ action: 'decline' | 'cancel' }`, or `undefined` when the client cannot answer:

```typescript
import type { MCPToolContext } from '@cogitator-ai/mcp';

const deleteOrder = tool({
  name: 'delete_order',
  description: 'Delete an order after the user confirms',
  parameters: z.object({ orderId: z.string() }),
  execute: async ({ orderId }, context) => {
    const reply = await (context as MCPToolContext).elicit?.({
      message: `Delete order ${orderId}?`,
      schema: {
        type: 'object',
        properties: { confirm: { type: 'boolean', title: 'Confirm' } },
        required: ['confirm'],
      },
    });
    if (reply?.action !== 'accept' || reply.content.confirm !== true) return { deleted: false };
    await orders.delete(orderId);
    return { deleted: true };
  },
});
```

## Tool Adapter

Convert tools between Cogitator and MCP formats manually when you need fine-grained control.

### Cogitator to MCP

```typescript
import { cogitatorToMCP } from '@cogitator-ai/mcp';

const mcpDef = cogitatorToMCP(calculator);
```

### MCP to Cogitator

```typescript
import { mcpToCogitator, wrapMCPTools } from '@cogitator-ai/mcp';

const cogTool = mcpToCogitator(mcpToolDefinition, client, {
  namePrefix: 'github_',
  descriptionTransform: (desc) => `[GitHub] ${desc}`,
});

const allTools = await wrapMCPTools(client, { namePrefix: 'fs_' });
```

### Schema Conversion

The adapter includes utilities for converting between Zod schemas and JSON Schema, which is useful when working with MCP's JSON Schema-based tool definitions:

```typescript
import { zodToJsonSchema, jsonSchemaToZod } from '@cogitator-ai/mcp';
import { z } from 'zod';

const schema = z.object({
  query: z.string().describe('Search query'),
  limit: z.number().int().min(1).max(100).optional(),
});

const jsonSchema = zodToJsonSchema(schema);

const zodSchema = jsonSchemaToZod({
  type: 'object',
  properties: {
    query: { type: 'string', description: 'Search query' },
    limit: { type: 'integer', minimum: 1, maximum: 100 },
  },
  required: ['query'],
});
```

## Claude Desktop Integration

To use your Cogitator tools in Claude Desktop, run an MCP server with stdio transport and add it to your Claude config:

```json title="claude_desktop_config.json"
{
  "mcpServers": {
    "my-cogitator-tools": {
      "command": "node",
      "args": ["./dist/mcp-server.js"]
    }
  }
}
```

```typescript title="mcp-server.ts"
import { MCPServer } from '@cogitator-ai/mcp';
import { calculator, webSearch } from './tools';

const server = new MCPServer({
  name: 'my-cogitator-tools',
  version: '1.0.0',
  transport: 'stdio',
});

server.registerTools([calculator, webSearch]);
await server.start();
```
