FabricFabricHarness
Reference

MCP

Model Context Protocol integration in Fabric Harness.

Fabric Harness connects Model Context Protocol (MCP) servers to a session as normal Fabric tools. Remote Streamable HTTP, legacy SSE, and local stdio transports are supported.

It can also expose Harness jobs, persistent agents, and governed tools as an authenticated MCP Streamable HTTP server.

Expose Fabric through MCP

import { startDevServer } from '@fabric-harness/node';

await startDevServer({
  authenticate: companyAuthenticator,
  mcp: {
    enabled: true,
    exposeJobs: true,
    exposeAgents: true,
    tools: [lookupAccount],
  },
});

Connect an MCP client to https://agents.example.com/mcp. Finite jobs appear as job_<name> tools using their declared input schema. Persistent agents appear as agent_<name> tools accepting instanceId, message, and optional session. Set FABRIC_HARNESS_MCP_ENABLED=1 to enable the same surface in generated Node artifacts.

The /mcp route requires mcp:invoke. The authenticated principal and tenant propagate into job runs and persistent submissions, including tool attribution and audit entries. Direct tool calls receive the actor, tenant, MCP request id, canonical input digest, cancellation signal, and an empty default sandbox through ToolContext. MCP annotations are derived from Fabric tool effects, and tool errors are redacted before returning to the client.

Custom tools must declare metadata.effect. Read-only and no-effect tools can be exposed directly. write and execute tools must first be wrapped by server-side governance and declare metadata.governed: true; an unknown effect or ungoverned mutation fails server startup. Resolve approval grants and policy versions through the trusted mcp.resolveToolContext hook or another server-side store. Never accept an approval grant from model-supplied tool arguments.

await startDevServer({
  authenticate: companyAuthenticator,
  mcp: {
    enabled: true,
    tools: [governedWriteTool],
    resolveToolContext: async ({ toolCallId, input, actor, tenantId }) => ({
      policyVersion: await policyStore.currentVersion(tenantId),
      approval: await approvalStore.findGrant({ toolCallId, input, actor, tenantId }),
    }),
  },
});

If the effect is missing, the mutation has not been governance-wrapped, or the configured resolver cannot produce the approval expected by the tool's policy, the operation fails closed before provider execution.

Remote MCP server

Mount an MCP server's tools as session tools:

import { connectMcpServer } from '@fabric-harness/sdk';

const github = await connectMcpServer('github', {
  url: process.env.GITHUB_MCP_URL!,
  headers: {
    authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
  },
  allowTools: ['get_*', 'list_*', 'search_*'],
  denyTools: ['*delete*'],
});

await session.prompt('Look up project FAB-123', {
  tools: github.tools,
});

await github.close();

connectMcpServer() uses Streamable HTTP by default. Pass transport: 'sse' for a server that still exposes the legacy SSE transport. allowTools and denyTools accept exact names or * globs and filter the server catalog before any tool reaches the model. Provider credentials remain in transport or OAuth state and are not added to tool metadata or model context.

The returned connection has reconnect() and close() lifecycle methods. Prompt and task abort signals propagate to active MCP calls, including the protocol cancellation notification.

OAuth client credentials

Use the machine-to-machine helper when the MCP authorization server supports the client_credentials grant. The MCP SDK discovers authorization metadata, obtains a token, and refreshes it when required.

import { connectMcpServer, createMcpClientCredentialsAuth } from '@fabric-harness/sdk';

const authProvider = createMcpClientCredentialsAuth({
  clientId: process.env.MCP_CLIENT_ID!,
  clientSecret: process.env.MCP_CLIENT_SECRET!,
  scope: 'tools.read',
});

const connection = await connectMcpServer('enterprise-tools', {
  url: process.env.MCP_SERVER_URL!,
  authProvider,
  allowTools: ['catalog_*'],
});

OAuth authorization code

Authorization code uses PKCE and application-owned state storage. In production, implement loadState and saveState with an encrypted server-side session or secret store. Never serialize this state into a prompt, agent memory, job payload, or client-side transcript.

import { connectMcpServer, createMcpAuthorizationCodeAuth } from '@fabric-harness/sdk';

const authProvider = createMcpAuthorizationCodeAuth({
  redirectUrl: 'https://agents.example.com/oauth/mcp/callback',
  clientName: 'Fabric Harness',
  clientId: process.env.MCP_CLIENT_ID!,
  clientSecret: process.env.MCP_CLIENT_SECRET,
  scopes: ['tools.read'],
  onAuthorizationUrl: (url) => redirectUser(url),
  loadState: () => encryptedSession.get('mcp-oauth'),
  saveState: (state) => encryptedSession.set('mcp-oauth', state),
});

// On the callback request, pass the returned code once. Stored refresh tokens
// are reused by subsequent connections through the same provider state.
const connection = await connectMcpServer('user-tools', {
  url: process.env.MCP_SERVER_URL!,
  authProvider,
  authorizationCode: callbackUrl.searchParams.get('code') ?? undefined,
});

Local stdio server

Use createStdioMcpClient() with createMcpTools() for a local MCP process:

import { createMcpTools, createStdioMcpClient } from '@fabric-harness/sdk';

const client = createStdioMcpClient({
  command: 'npx',
  args: ['-y', '@modelcontextprotocol/server-filesystem', '/workspace'],
});

const tools = await createMcpTools(client, {
  prefix: 'workspace',
  effect: 'read',
  source: 'filesystem',
});

await session.prompt('Summarize the workspace', { tools });
await client.close();

Tool names are prefixed and normalized before they enter the model toolset. Apply toolPolicy rules to the generated names when an MCP server exposes write or execute effects.

Why MCP

MCP is becoming a converging standard for tool/resource exchange across agent ecosystems. Wiring Fabric agents and MCP servers together means you can:

  • give Fabric agents access to a growing catalog of MCP-served tools,
  • use the same Fabric policy and approval controls for local and remote MCP tools,
  • switch between remote and stdio transports without changing the session prompt loop,
  • expose the same governed job and agent operations to IDEs, automation clients, and agent peers.