4.6 KiB
mcp-server
(no submodules)
Purpose
MCPServer is the host-side surface of the Model Context Protocol. Owners register tools, resources, prompts, and roots; the server then exposes them to a remote MCPClient (or any spec-compliant peer) over the JSON-RPC dispatcher and a pluggable transport. Every public method documented here either mutates the registration tables, configures a hook (authorize, sampling, completion, log level), or drives the dispatcher (start / stop / direct invocation).
Features
- Register / unregister tools, resources, resource templates, prompts, and roots — every collision raises
MCPError(DUPLICATE_REGISTRATION). - Lifecycle:
start(transport)attaches a JSON-RPC dispatcher and registers handlers for every MCP method;stop(reason?)rejects pending requests and aborts in-flight handlers. - Authorize hook:
setAuthorizer(fn)gates every method; rejection yields-32001mapped toMCPError(UNAUTHORIZED). - Sampling + completion providers — surfaces are pluggable so consumers can wire LLM backends.
- Progress + abort: tool handlers receive
progress(value, total?)and anAbortSignal; clients seenotifications/progressandnotifications/cancelledover the wire. - Resource subscriptions:
markResourceUpdated(uri)fans outnotifications/resources/updatedto subscribed peers. - Direct invocation API (
invokeTool,readResource,getPromptMessages) — useful in tests and in-process callers.
Usage: Basic Example
import { InMemoryTransport, MCPServer } from "@suaveplan/mcp";
const server = new MCPServer({ info: { name: "demo", version: "0.1.0" } });
server.registerTool({
name: "echo",
description: "Echo back the input",
inputSchema: { type: "object", properties: { msg: { type: "string" } } },
async handler({ msg }) {
return { content: [{ type: "text", text: String(msg) }] };
},
});
const [a, b] = InMemoryTransport.pair();
await server.start(a);
Usage: Advanced Example
import { MCPServer } from "@suaveplan/mcp";
import z from "zod";
const server = new MCPServer({ info: { name: "admin", version: "0.1.0" } });
server.setAuthorizer(async (req) => req.method !== "tools/call" || hasPermission(req.params));
server.registerTool({
name: "deploy",
description: "Deploy a release",
inputSchema: { type: "object", properties: { tag: { type: "string" } }, required: ["tag"] },
paramsSchema: z.object({ tag: z.string().min(1) }),
async handler(args, ctx) {
for (let p = 0; p < 100 && !ctx.signal.aborted; p += 10) {
await sleep(50);
ctx.progress(p, 100);
}
return { content: [{ type: "text", text: `Deployed ${(args as { tag: string }).tag}` }] };
},
});
server.registerResource({
uri: "config://current",
name: "Active configuration",
mimeType: "application/json",
async handler() {
return { uri: "config://current", mimeType: "application/json", text: JSON.stringify(currentConfig) };
},
});
API Reference
new MCPServer(options: MCPServerOptions).- Registration:
registerTool,unregisterTool,registerResource,unregisterResource,registerResourceTemplate,unregisterResourceTemplate,registerPrompt,unregisterPrompt,registerRoot,unregisterRoot. - Lists:
listTools,listResources,listResourceTemplates,listPrompts,listRoots. - Hooks:
setAuthorizer,setSamplingProvider,setCompletionProvider,setLogLevel. - Direct API:
invokeTool,readResource,getPromptMessages,createSamplingMessage,listClientRoots,emitLog. - Lifecycle:
start(transport),stop(reason?),running,initialized,getInfo(). events: EventBus<MCPEventMap>— subscribe to connection / request / notification lifecycle.
Implementation Notes
start builds a paramSchemas map keyed by method name so the dispatcher validates incoming params before invoking the handler — invalid params respond with JSON-RPC -32602 mapped to MCPError(INVALID_PARAMS). List-changed notifications fan out from registration mutators only when a dispatcher is attached; the rejection is swallowed because the transport may have closed between the registration and the notify. invokeTool wraps any thrown error into a MCPToolResult with isError: true so JSON-RPC level errors are reserved for protocol failures, not tool-handler failures (matching the MCP spec convention). createSamplingMessage and listClientRoots use the dispatcher to call out to the connected client — these methods exist on the server side because MCP's sampling and roots flows are client-side capabilities that the server invokes.