> ## Documentation Index
> Fetch the complete documentation index at: https://dev-docs.persona.hasanraiyan.me/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Connectors

> Register MCP servers, test connections, call tools, read resources, and drive OAuth connection flows.

`persona.mcps` (+ `persona.mcps.oauth`) — `/api/v1/developer/mcps`.

MCP (Model Context Protocol) connectors let your Agents call tools and read resources from
external MCP servers. Project-owned, or — with `externalUserId` — owned by that end user.

## Methods

| Method | Endpoint | Returns |
| - | - | - |
| `create(input, idempotencyKey?)` | `POST /api/v1/developer/mcps` | `Mcp` |
| `list(params?)` | `GET /api/v1/developer/mcps` | `PaginatedResult<Mcp>` |
| `get(mcpId)` | `GET /api/v1/developer/mcps/{id}` | `Mcp` |
| `update(mcpId, input)` | `PATCH /api/v1/developer/mcps/{id}` | `Mcp` |
| `delete(mcpId)` | `DELETE /api/v1/developer/mcps/{id}` | `void` |
| `getUsage(mcpId)` | `GET /api/v1/developer/mcps/{id}/usage` | `ResourceUsage` |
| `bulkDelete(ids)` | `POST /api/v1/developer/mcps/bulk-delete` | `BulkDeleteResult` |
| `testConnection(mcpId)` | `POST /api/v1/developer/mcps/{id}/test` | `McpTestConnectionResult` |
| `readResource(mcpId, uri)` | `GET /api/v1/developer/mcps/{id}/resource?uri=...` | `McpReadResourceResult` |
| `callTool(mcpId, name, args?)` | `POST /api/v1/developer/mcps/{id}/call-tool` | `unknown` (depends on the tool) |

Plus `persona.mcps.oauth` for OAuth flows — see
[OAuth connection flows](#oauth-connection-flows-mcps-oauth) below.

## create(input, idempotencyKey?)

`name`, `transport`, and `url` are required.

```ts theme={null}
// Unauthenticated HTTP MCP server.
const mcp = await persona.mcps.create({
  name: 'Internal Ticketing',
  transport: 'http', // 'http' | 'sse'
  url: 'https://mcp.example.com',
});

// API-key MCP server (static bearer token — always owner-shared regardless of authMode).
const mcpKey = await persona.mcps.create({
  name: 'Internal Ticketing',
  transport: 'http',
  url: 'https://mcp.example.com',
  authType: 'apiKey',
  apiKey: process.env.TICKETING_MCP_KEY!,
});
```

Optional fields and defaults:

| Field | Type | Default | Notes |
| - | - | - | - |
| `description` | `string` | — | |
| `authType` | `'none' \| 'oauth' \| 'apiKey'` | `'none'` | |
| `authMode` | `'owner' \| 'user'` | `'owner'` | `owner`: one shared connection for the whole Project. `user`: each external user connects their own. |
| `oauth` | `McpOAuthInput` | — | Required when `authType: 'oauth'` and `useDynamicRegistration` isn't set. |
| `apiKey` | `string` | — | Required when `authType: 'apiKey'`. Sent as a static bearer token — always owner-shared. |
| `useDynamicRegistration` | `boolean` | `false` | Use RFC 7591 Dynamic Client Registration instead of a manually-configured `oauth` block. |
| `isEnabled` | `boolean` | `true` | |

`McpOAuthInput` is `{ clientId: string, clientSecret: string, scopes?: string[] }`.

<Note>
  Creating an `authType: 'oauth'` MCP synchronously probes the target URL's OAuth discovery
  endpoints — pointing it at a URL that doesn't actually implement OAuth discovery will **fail the
  `create()` call itself**, not just a later connection test.
</Note>

## list(params?)

```ts theme={null}
const { items, pagination } = await persona.mcps.list({
  page: 1,
  limit: 20,
  search: 'ticketing', // free-text match against name/description
  scope: 'mine',
});
```

Lists/searches MCP servers visible to this credential. Returns `PaginatedResult<Mcp>`.

## testConnection(mcpId)

Connects to the MCP server right now, lists its tools/resources/resource templates, **and
persists that summary onto the stored MCP document** — so `get()`/`list()` reflect it afterward.

```ts theme={null}
const { tools, resources, resourceTemplates } = await persona.mcps.testConnection(mcp._id);
```

* Returns `{ tools, resources, resourceTemplates }`.
* Until `testConnection()` has been called at least once, an MCP's `tools`/`resources`/
  `resourceTemplates` are empty arrays.

## readResource(mcpId, uri)

Reads one MCP resource by URI (as opposed to calling a tool).

```ts theme={null}
const { text, mimeType } = await persona.mcps.readResource(mcp._id, resources[0].uri);
```

* `uri` comes from `mcp.resources`/`mcp.resourceTemplates` (after filling any template params).
* Returns `{ text: string, mimeType: string }`.

## callTool(mcpId, name, args?)

Invokes one tool exposed by this MCP server.

```ts theme={null}
const result = await persona.mcps.callTool(mcp._id, 'create_ticket', {
  title: 'Bug report',
  severity: 'high',
});
```

* `name` — tool name, from `mcp.tools`.
* `args` — arguments matching that tool's own input schema (optional).
* Returns `unknown` — the return shape is whatever the underlying MCP tool returns, inherently
  dynamic.

## getUsage / delete

* `getUsage(mcpId)` returns `ResourceUsage` — check before `delete()`; an MCP still referenced by
  an Agent rejects the delete.
* `delete(mcpId)` deletes the MCP server. Returns `void`.

## OAuth connection flows (`mcps.oauth`)

For `authType: 'oauth'` MCPs, `mcps.oauth` drives the owner- and user-mode authorization flows:

| Method | Endpoint | Returns |
| - | - | - |
| `oauth.getOwnerAuthorizeUrl(mcpId)` | `GET /mcps/{id}/oauth/owner/authorize` | `{ url: string }` |
| `oauth.getUserAuthorizeUrl(mcpId, returnTo?)` | `GET /mcps/{id}/oauth/user/authorize` | `{ url: string }` |
| `oauth.getUserConnectionStatus(mcpId)` | `GET /mcps/{id}/oauth/user/status` | `{ connected: boolean }` |
| `oauth.disconnectUserConnection(mcpId)` | `DELETE /mcps/{id}/oauth/user/connection` | `void` |
| `oauth.disconnectOwnerConnection(mcpId)` | `DELETE /mcps/{id}/oauth/owner/connection` | `void` |

* **Owner-mode** (`authMode: 'owner'`) authorizes the MCP itself, shared by every user of your
  Project. Call `getOwnerAuthorizeUrl()` from a control-plane client and redirect your admin
  through it once.
* **User-mode** (`authMode: 'user'`) authorizes one specific end user's own token. Call
  `getUserAuthorizeUrl()` from a runtime-plane client (`externalUserId` set) and redirect that
  user through it; `returnTo` is an optional client-chosen URL to send them back to once the flow
  completes. `getUserConnectionStatus()`/`disconnectUserConnection()` also require the
  runtime-plane client — the server rejects them with a 400 otherwise.
* `getOwnerAuthorizeUrl()`/`disconnectOwnerConnection()` are control-plane calls.

## Complete example

```ts theme={null}
import { PersonaClient } from '@personaai/sdk';

const persona = new PersonaClient({
  baseUrl: process.env.PERSONA_BASE_URL!,
  credential: process.env.PERSONA_CREDENTIAL!,
});

const mcp = await persona.mcps.create({
  name: 'Internal Ticketing',
  transport: 'http',
  url: 'https://mcp.example.com',
  authType: 'apiKey',
  apiKey: process.env.TICKETING_MCP_KEY!,
});

const { tools } = await persona.mcps.testConnection(mcp._id);
console.log('Tools:', tools.map((t) => t.name));

const result = await persona.mcps.callTool(mcp._id, 'create_ticket', { title: 'Bug' });
console.log(result);

// Attach to an Agent so it can use these tools.
await persona.agents.update('agent_123', { mcps: [mcp._id] });
```
