68 lines
2.9 KiB
TypeScript
68 lines
2.9 KiB
TypeScript
/**
|
|
* Tools primitive — start here.
|
|
*
|
|
* Register tools with `McpServer.registerTool`: typed input via any
|
|
* Standard-Schema-with-JSON library (Zod here), inferred output schema +
|
|
* `structuredContent` from `outputSchema`, `annotations` for behavioral hints
|
|
* (`readOnlyHint`, `destructiveHint`). One binary, either transport.
|
|
*/
|
|
import { serve } from '@hono/node-server';
|
|
import { parseExampleArgs } from '@mcp-examples/shared';
|
|
import { createMcpHonoApp } from '@modelcontextprotocol/hono';
|
|
import type { CallToolResult } from '@modelcontextprotocol/server';
|
|
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
|
|
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
import * as z from 'zod/v4';
|
|
|
|
function buildServer(): McpServer {
|
|
const server = new McpServer({ name: 'tools-example', version: '1.0.0' });
|
|
|
|
// A read-only tool with typed input and inferred structured output.
|
|
server.registerTool(
|
|
'calc',
|
|
{
|
|
title: 'Calculator',
|
|
description: 'Apply an arithmetic operation to two numbers',
|
|
inputSchema: z.object({
|
|
op: z.enum(['add', 'sub', 'mul']).describe('the operation to apply'),
|
|
a: z.number().describe('left operand'),
|
|
b: z.number().describe('right operand')
|
|
}),
|
|
outputSchema: z.object({ op: z.string(), result: z.number() }),
|
|
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
// Icons a client may render in its UI. `src` is required;
|
|
// `mimeType`, `sizes`, and `theme` are optional hints.
|
|
icons: [{ src: 'https://example.test/calc.svg', mimeType: 'image/svg+xml', sizes: ['any'] }]
|
|
},
|
|
async ({ op, a, b }) => {
|
|
const result = op === 'add' ? a + b : op === 'sub' ? a - b : a * b;
|
|
const structuredContent = { op, result };
|
|
return { content: [{ type: 'text', text: `${a} ${op} ${b} = ${result}` }], structuredContent };
|
|
}
|
|
);
|
|
|
|
// A plain string-returning tool (no structuredContent).
|
|
server.registerTool(
|
|
'echo',
|
|
{ description: 'Echoes the input', inputSchema: z.object({ text: z.string() }) },
|
|
async ({ text }): Promise<CallToolResult> => ({ content: [{ type: 'text', text }] })
|
|
);
|
|
|
|
return server;
|
|
}
|
|
|
|
const { transport, port } = parseExampleArgs();
|
|
|
|
if (transport === 'stdio') {
|
|
void serveStdio(buildServer);
|
|
console.error('[server] serving over stdio');
|
|
} else {
|
|
const handler = createMcpHandler(buildServer);
|
|
// `createMcpHonoApp()` binds the endpoint behind localhost host/origin
|
|
// validation by default, matching the framework factories' defaults.
|
|
const app = createMcpHonoApp();
|
|
app.all('/mcp', c => handler.fetch(c.req.raw));
|
|
serve({ fetch: app.fetch, port, hostname: '127.0.0.1' }, () => {
|
|
console.error(`[server] listening on http://127.0.0.1:${port}/mcp`);
|
|
});
|
|
}
|