Skip to content

Exposing Tools as MCP Server

The framework provides two approaches for exposing your ToolRegistry tools as an MCP server: a stateless JSON-RPC handler for serverless deployments, and integration points for the official MCP SDK.

Import

import {
  createMcpStatelessHandlerFromToolRegistry,
  McpProviderConfig,
} from "@modernpath/agent-framework";

createMcpStatelessHandlerFromToolRegistry

function createMcpStatelessHandlerFromToolRegistry(
  registry: ToolRegistry,
  config?: McpProviderConfig,
): (event: McpHttpEvent) => Promise<McpHttpResponse>

Creates a minimal, stateless MCP Streamable HTTP handler backed by the ToolRegistry. Implements the following JSON-RPC methods:

Method Description
tools/list Returns all tools that pass the provider filter.
tools/call Executes a tool via the registry.
resources/list Lists tool schema resources (if schema resources are configured).
resources/read Reads a specific tool schema resource.

McpHttpEvent

interface McpHttpEvent {
  method?: string;
  headers?: Record<string, string | undefined>;
  body?: string | null;
}

McpHttpResponse

interface McpHttpResponse {
  statusCode: number;
  headers: Record<string, string>;
  body: string;
}

McpProviderConfig

interface McpProviderConfig {
  serverName?: string;
  serverVersion?: string;
  allowTools?: string[];
  denyTools?: string[];
  allowCategories?: string[];
  allowTags?: string[];
  getUserId?: (req: { headers?: Record<string, string | undefined> }) => Promise<number> | number;
  getAuditingId?: (req: { headers?: Record<string, string | undefined> }) => Promise<number> | number;
  originService?: string;
}
Property Type Default Description
serverName string -- Optional MCP server name (informational).
serverVersion string -- Optional MCP server version (informational).
allowTools string[] -- Whitelist of tool names to expose. If empty/absent, all tools are exposed (subject to deny filters).
denyTools string[] -- Blacklist of tool names to hide.
allowCategories string[] -- Only expose tools with these categories.
allowTags string[] -- Only expose tools that have at least one of these tags.
getUserId function Returns 0 Extracts userId from the request headers.
getAuditingId function Returns 0 Extracts auditingId from the request headers.
originService string -- Identifier attached to call metadata for audit trail.

Filter Evaluation Order

  1. denyTools -- if the tool name is in the deny list, it is hidden
  2. allowTools -- if set and the tool name is not in the allow list, it is hidden
  3. allowCategories -- if set and the tool's category is not in the list, it is hidden
  4. allowTags -- if set and the tool has no matching tag, it is hidden

Code Example

Basic HTTP Server

import {
  ToolRegistry,
  createMcpStatelessHandlerFromToolRegistry,
} from "@modernpath/agent-framework";

const registry = new ToolRegistry();
// ... register tools ...

const mcpHandler = createMcpStatelessHandlerFromToolRegistry(registry, {
  serverName: "my-agent-tools",
  serverVersion: "1.0.0",
  allowCategories: ["public", "telemetry"],
  denyTools: ["internal.debug"],
  getUserId: (req) => parseInt(req.headers?.["x-user-id"] ?? "0", 10),
  getAuditingId: (req) => parseInt(req.headers?.["x-auditing-id"] ?? "0", 10),
  originService: "my-agent-backend",
});

// Express
app.post("/mcp", async (req, res) => {
  const result = await mcpHandler({
    method: req.method,
    headers: req.headers as Record<string, string>,
    body: JSON.stringify(req.body),
  });
  res.status(result.statusCode);
  for (const [k, v] of Object.entries(result.headers)) {
    res.set(k, v);
  }
  res.send(result.body);
});

GCP Cloud Function

import { createMcpStatelessHandlerFromToolRegistry } from "@modernpath/agent-framework";

const mcpHandler = createMcpStatelessHandlerFromToolRegistry(registry, {
  serverName: "my-cloud-function-tools",
});

export const mcpEndpoint = async (req: any, res: any) => {
  const result = await mcpHandler({
    method: req.method,
    headers: req.headers,
    body: typeof req.body === "string" ? req.body : JSON.stringify(req.body),
  });
  res.status(result.statusCode);
  for (const [k, v] of Object.entries(result.headers)) {
    res.set(k, v);
  }
  res.send(result.body);
};

Tool Call Context

When a tool is called via the MCP provider, the framework injects context into the tool parameters:

  • userId -- from getUserId() or default 0
  • auditingId -- from getAuditingId() or default 0
  • __metadata -- includes callStack for loop safety and originService