<!--
  Canonical spec for fabric-harness MCP connectors.
  Shipped with @fabric-harness/sdk.

  Raw GitHub URL:
    https://raw.githubusercontent.com/Fabric-Pro/fabric-harness/main/packages/sdk/connector-spec/mcp.md
-->

# fabric-harness MCP Connector Spec

This document is the contract for building a fabric-harness MCP connector. An MCP connector wraps an external Model Context Protocol server (Linear, GitHub, Slack, Notion, custom) so its tools become callable from a fabric-harness agent.

If you are an AI coding agent reading this to build a connector for a user, follow this document literally and produce a single TypeScript file that exports a factory function returning a registry of `ToolDef` objects (or a `connect` function that lazy-initializes them).

---

## Choosing a Transport

MCP servers run over one of:

- **stdio** — local subprocess. Use `createStdioMcpClient` from `@fabric-harness/sdk`. This is the right choice for `npx @org/mcp-server` style usage.
- **HTTP / SSE** — networked server. Use `connectMcpServer({ transport: 'http', url })` from `@fabric-harness/sdk`.

Pick stdio when the provider ships an npm package; pick HTTP when they expose a hosted endpoint.

---

## High-Level Shape

```ts
// .fabricharness/connectors/<provider>.ts
import { connectMcpServer, createStdioMcpClient, createMcpTools } from '@fabric-harness/sdk';
import type { ToolDef } from '@fabric-harness/sdk';

export interface ProviderMcpOptions {
  /** API token / OAuth bearer / etc. */
  apiKey: string;
  /** Optional model-side description override. */
  description?: string;
}

export async function providerMcp(options: ProviderMcpOptions): Promise<ToolDef[]> {
  const client = await createStdioMcpClient({
    command: 'npx',
    args: ['-y', '@provider/mcp-server'],
    env: { PROVIDER_API_KEY: options.apiKey },
  });
  return createMcpTools(client, {
    namePrefix: 'provider',
    description: options.description,
  });
}
```

The agent then registers these tools at session creation:

```ts
const fabric = await init({ tools: await providerMcp({ apiKey: process.env.PROVIDER_API_KEY! }) });
```

---

## Naming and Namespacing

- Prefix every tool name with the provider's canonical short name (`linear_`, `github_`, `slack_`, etc.) using the `namePrefix` option on `createMcpTools`. This avoids collisions when multiple MCP connectors are loaded into the same agent.
- Keep the user's tool name unchanged (e.g. `create_issue`). The prefix is the connector's job.

---

## Auth

- API keys go through `options`, never hardcoded. Read from env in the user's app, not in the connector.
- OAuth tokens that need refresh must be wrapped in a getter. `createStdioMcpClient` accepts a function for `env` to re-read tokens before each call.
- Never log the key. Connectors must not mention secrets in error messages.

---

## Lifecycle

If the MCP server is stateful (long-running stdio process, persistent SSE connection), expose a `close` returned alongside the tools:

```ts
export async function providerMcp(options: ProviderMcpOptions) {
  const client = await createStdioMcpClient({ /* ... */ });
  const tools = await createMcpTools(client, { namePrefix: 'provider' });
  return { tools, close: () => client.close() };
}
```

The user's app calls `close()` on shutdown.

For one-shot agents (CI, webhook handlers), prefer the simple form returning `ToolDef[]` directly — no cleanup hook needed.

---

## Error Contract

- Connection failures during init → throw with a clear "could not start MCP server" message including the missing dep / wrong env var hint.
- Per-tool errors propagate from the MCP server through `createMcpTools` automatically. Don't wrap.
- Schema mismatches are surfaced by `fh doctor --tools`. Don't try to fix them in the connector.

---

## Worked Example (Linear)

```ts
// .fabricharness/connectors/linear.ts
import { createStdioMcpClient, createMcpTools } from '@fabric-harness/sdk';
import type { ToolDef } from '@fabric-harness/sdk';

export interface LinearMcpOptions {
  apiKey: string;
}

export async function linear(options: LinearMcpOptions): Promise<ToolDef[]> {
  const client = await createStdioMcpClient({
    command: 'npx',
    args: ['-y', '@modelcontextprotocol/server-linear'],
    env: { LINEAR_API_KEY: options.apiKey },
  });
  return createMcpTools(client, { namePrefix: 'linear' });
}
```

Use:

```ts
import { linear } from './connectors/linear.js';

const tools = await linear({ apiKey: process.env.LINEAR_API_KEY! });
const fabric = await init({ tools });
```

---

## Checklist Before Submitting

- [ ] Single TypeScript file exporting the factory function.
- [ ] Returns `ToolDef[]` (or `{ tools, close }` for stateful servers).
- [ ] Every tool is namespaced via `namePrefix`.
- [ ] No hardcoded secrets; all auth flows through options.
- [ ] Imports only from `@fabric-harness/sdk` and the MCP server's npm package.
- [ ] The factory throws a friendly error if the MCP server fails to start (mention the missing dep / env var).
- [ ] If the provider needs OAuth refresh, the example shows the refresh pattern.

If the provider doesn't have a published MCP server yet, leave a `// TODO` linking the upstream tracking issue rather than rolling your own protocol.
