MCP server framework with typed tools for Outfitter
npm install @outfitter/mcpMCP (Model Context Protocol) server framework with typed tools and Result-based error handling.
``bash`
bun add @outfitter/mcp
`typescript
import { createMcpServer, defineTool } from "@outfitter/mcp";
import { Result } from "@outfitter/contracts";
import { z } from "zod";
const server = createMcpServer({
name: "calculator",
version: "1.0.0",
});
server.registerTool(
defineTool({
name: "add",
description: "Add two numbers together",
inputSchema: z.object({
a: z.number(),
b: z.number(),
}),
handler: async (input, ctx) => {
ctx.logger.debug("Adding numbers", { a: input.a, b: input.b });
return Result.ok({ sum: input.a + input.b });
},
})
);
await server.start();
`
- Typed Tools — Define tools with Zod schemas for automatic input validation
- Result-Based Errors — All operations return Result for explicit error handlingHandler
- Handler Contract — Tools use the same pattern as other Outfitter packagesdeferLoading
- Core Tools — Built-in docs, config, and query tools for common patterns
- Deferred Loading — Support for MCP tool search with flag
Creates an MCP server instance.
`typescript
interface McpServerOptions {
name: string; // Server name for MCP handshake
version: string; // Server version (semver)
logger?: Logger; // Optional structured logger
}
const server = createMcpServer({
name: "my-server",
version: "1.0.0",
logger: createLogger({ name: "mcp" }),
});
`
Helper for defining typed tools with better type inference.
`typescript
interface ToolDefinition
name: string; // Unique tool name (kebab-case)
description: string; // Human-readable description
inputSchema: z.ZodType
handler: Handler
deferLoading?: boolean; // Default: true
}
const getUserTool = defineTool({
name: "get-user",
description: "Retrieve a user by their unique ID",
inputSchema: z.object({ userId: z.string().uuid() }),
handler: async (input, ctx) => {
const user = await db.users.find(input.userId);
if (!user) {
return Result.err(new NotFoundError("user", input.userId));
}
return Result.ok(user);
},
});
`
Helper for defining MCP resources.
`typescript
interface ResourceDefinition {
uri: string; // Unique resource URI
name: string; // Human-readable name
description?: string; // Optional description
mimeType?: string; // Content MIME type
handler?: ResourceReadHandler; // Optional resources/read handler
}
const configResource = defineResource({
uri: "file:///etc/app/config.json",
name: "Application Config",
description: "Main configuration file",
mimeType: "application/json",
handler: async (uri, ctx) => {
ctx.logger.debug("Reading config resource", { uri });
return Result.ok([
{
uri,
mimeType: "application/json",
text: JSON.stringify({ debug: true }),
},
]);
},
});
`
Registered resources with handlers are exposed through MCP resources/read.
`typescript
server.registerResource(configResource);
const contentResult = await server.readResource("file:///etc/app/config.json");
`
`typescript
interface McpServer {
readonly name: string;
readonly version: string;
// Registration
registerTool
registerResource(resource: ResourceDefinition): void;
registerResourceTemplate(template: ResourceTemplateDefinition): void;
// Introspection
getTools(): SerializedTool[];
getResources(): ResourceDefinition[];
getResourceTemplates(): ResourceTemplateDefinition[];
// Invocation
readResource(uri: string): Promise
invokeTool
// Lifecycle
start(): Promise
stop(): Promise
}
`
Extended handler context for MCP tools with additional metadata:
`typescript`
interface McpHandlerContext extends HandlerContext {
toolName?: string; // Name of the tool being invoked
}
Pre-built tools for common MCP patterns. These are marked with deferLoading: false for immediate availability.
Provides documentation, usage patterns, and examples.
`typescript
import { defineDocsTool, createCoreTools } from "@outfitter/mcp";
const docsTool = defineDocsTool({
docs: {
overview: "Calculator server for arithmetic operations",
tools: [{ name: "add", summary: "Add two numbers" }],
examples: [{ input: { a: 2, b: 3 }, description: "Basic addition" }],
},
});
// Or use getDocs for dynamic content
const dynamicDocsTool = defineDocsTool({
getDocs: async (section) => {
return loadDocsFromFile(section);
},
});
`
Read and modify server configuration.
`typescript
import { defineConfigTool } from "@outfitter/mcp";
const configTool = defineConfigTool({
initial: { debug: false, maxRetries: 3 },
});
// With custom store
const persistedConfigTool = defineConfigTool({
store: {
get: async (key) => db.config.get(key),
set: async (key, value) => db.config.set(key, value),
list: async () => db.config.all(),
},
});
`
Search and discovery with pagination.
`typescript
import { defineQueryTool } from "@outfitter/mcp";
const queryTool = defineQueryTool({
handler: async (input, ctx) => {
const results = await searchIndex(input.q, {
limit: input.limit,
cursor: input.cursor,
filters: input.filters,
});
return Result.ok({
results: results.items,
nextCursor: results.nextCursor,
});
},
});
`
`typescript
import { createCoreTools } from "@outfitter/mcp";
const coreTools = createCoreTools({
docs: { docs: myDocs },
config: { initial: myConfig },
query: { handler: myQueryHandler },
});
for (const tool of coreTools) {
server.registerTool(tool);
}
`
Connect server to stdio transport for Claude Desktop integration.
`typescript
import { createMcpServer, connectStdio } from "@outfitter/mcp";
const server = createMcpServer({ name: "my-server", version: "1.0.0" });
// ... register tools ...
await connectStdio(server);
`
Create the underlying @modelcontextprotocol/sdk server.
`typescript
import { createSdkServer } from "@outfitter/mcp";
const { server: sdkServer, toolsList, callTool } = createSdkServer(mcpServer);
`
Tools return Results with typed errors. The framework automatically translates OutfitterError categories to JSON-RPC error codes:
| Category | JSON-RPC Code | Description |
|----------|--------------|-------------|
| validation | -32602 | Invalid params |not_found
| | -32601 | Method not found |permission
| | -32600 | Invalid request |internal
| | -32603 | Internal error |
`typescript
const result = await server.invokeTool("get-user", { userId: "123" });
if (result.isErr()) {
// result.error is McpError with code and context
console.error(result.error.message, result.error.code);
}
`
Convert Zod schemas to JSON Schema for MCP protocol.
`typescript
import { zodToJsonSchema } from "@outfitter/mcp";
const schema = z.object({
name: z.string(),
age: z.number().optional(),
});
const jsonSchema = zodToJsonSchema(schema);
// { type: "object", properties: { name: { type: "string" }, ... } }
`
Build MCP tools from an action registry (for structured action-based servers).
`typescript
import { buildMcpTools } from "@outfitter/mcp";
const tools = buildMcpTools({
actions: myActionRegistry,
prefix: "myapp",
});
for (const tool of tools) {
server.registerTool(tool);
}
`
Add your MCP server to Claude Desktop:
`json`
{
"mcpServers": {
"my-server": {
"command": "bun",
"args": ["run", "/path/to/server.ts"]
}
}
}
Config location:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json
- Windows: ~/.config/claude/claude_desktop_config.json`
- Linux:
- @outfitter/contracts — Result types and error taxonomy
- @outfitter/logging — Structured logging
- @outfitter/config — Configuration loading