fastmcp
Version:
A TypeScript framework for building MCP servers.
334 lines (296 loc) • 9.28 kB
text/typescript
/**
* Example FastMCP server demonstrating core functionality plus streaming output.
*
* Features demonstrated:
* - Basic tool with type-safe parameters
* - Streaming-enabled tool for incremental output
* - Advanced tool annotations
*
* For a complete project template, see https://github.com/punkpeye/fastmcp-boilerplate
*/
import { type } from "arktype";
import * as v from "valibot";
import { z } from "zod";
import { FastMCP } from "../FastMCP.js";
const server = new FastMCP({
name: "Addition",
ping: {
// enabled: undefined,
// Automatically enabled/disabled based on transport type
// Using a longer interval to reduce log noise
intervalMs: 10000, // default is 5000ms
// Reduce log verbosity
logLevel: "debug", // default
},
roots: {
// You can explicitly disable roots support if needed
// enabled: false,
},
version: "1.0.0",
});
// --- Zod Example ---
const AddParamsZod = z.object({
a: z.number().describe("The first number"),
b: z.number().describe("The second number"),
});
server.addTool({
annotations: {
openWorldHint: false, // This tool doesn't interact with external systems
readOnlyHint: true, // This tool doesn't modify anything
title: "Addition (Zod)",
},
description: "Add two numbers (using Zod schema)",
execute: async (args) => {
// args is typed as { a: number, b: number }
console.log(`[Zod] Adding ${args.a} and ${args.b}`);
return String(args.a + args.b);
},
name: "add-zod",
parameters: AddParamsZod,
});
// --- ArkType Example ---
const AddParamsArkType = type({
a: "number",
b: "number",
});
server.addTool({
annotations: {
destructiveHint: true, // This would perform destructive operations
idempotentHint: true, // But operations can be repeated safely
openWorldHint: true, // Interacts with external systems
readOnlyHint: false, // Example showing a modifying tool
title: "Addition (ArkType)",
},
description: "Add two numbers (using ArkType schema)",
execute: async (args, { log }) => {
// args is typed as { a: number, b: number } based on AddParamsArkType.infer
console.log(`[ArkType] Adding ${args.a} and ${args.b}`);
// Demonstrate long-running operation that might need a timeout
log.info("Starting calculation with potential delay...");
// Simulate a complex calculation process
if (args.a > 1000 || args.b > 1000) {
log.warn("Large numbers detected, operation might take longer");
// In a real implementation, this delay might be a slow operation
await new Promise((resolve) => setTimeout(resolve, 3000));
}
return String(args.a + args.b);
},
name: "add-arktype",
parameters: AddParamsArkType,
// Will abort execution after 2s
timeoutMs: 2000,
});
// --- Valibot Example ---
const AddParamsValibot = v.object({
a: v.number("The first number"),
b: v.number("The second number"),
});
server.addTool({
annotations: {
openWorldHint: false,
readOnlyHint: true,
title: "Addition (Valibot)",
},
description: "Add two numbers (using Valibot schema)",
execute: async (args) => {
console.log(`[Valibot] Adding ${args.a} and ${args.b}`);
return String(args.a + args.b);
},
name: "add-valibot",
parameters: AddParamsValibot,
});
server.addResource({
async load() {
return {
text: "Example log content",
};
},
mimeType: "text/plain",
name: "Application Logs",
uri: "file:///logs/app.log",
});
server.addTool({
annotations: {
openWorldHint: false,
readOnlyHint: true,
streamingHint: true,
},
description: "Generate a poem line by line with streaming output",
execute: async (args, context) => {
const { theme } = args;
const lines = [
`Poem about ${theme} - line 1`,
`Poem about ${theme} - line 2`,
`Poem about ${theme} - line 3`,
`Poem about ${theme} - line 4`,
];
for (const line of lines) {
await context.streamContent({
text: line,
type: "text",
});
await new Promise((resolve) => setTimeout(resolve, 1000));
}
return;
},
name: "stream-poem",
parameters: z.object({
theme: z.string().describe("Theme for the poem"),
}),
});
server.addTool({
annotations: {
openWorldHint: false,
readOnlyHint: false,
},
description: "Test progress reporting without buffering delays",
execute: async (args, { reportProgress }) => {
console.log("Testing progress reporting fix for HTTP Stream buffering...");
await reportProgress({ progress: 0, total: 100 });
await new Promise((resolve) => setTimeout(resolve, 500));
await reportProgress({ progress: 25, total: 100 });
await new Promise((resolve) => setTimeout(resolve, 500));
await reportProgress({ progress: 75, total: 100 });
await new Promise((resolve) => setTimeout(resolve, 500));
// This progress should be received immediately
await reportProgress({ progress: 100, total: 100 });
return `Buffering test completed for ${args.testCase}`;
},
name: "test-buffering-fix",
parameters: z.object({
testCase: z.string().describe("Test case description"),
}),
});
server.addPrompt({
arguments: [
{
description: "Git diff or description of changes",
name: "changes",
required: true,
},
],
description: "Generate a Git commit message",
load: async (args) => {
return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
},
name: "git-commit",
});
server.addResourceTemplate({
arguments: [
{
description: "Documentation section to retrieve",
name: "section",
required: true,
},
],
description: "Get project documentation",
load: async (args) => {
const docs = {
"api-reference":
"# API Reference\n\n## Authentication\nAll API requests require a valid API key in the Authorization header.\n\n## Endpoints\n- GET /users - List all users\n- POST /users - Create new user",
deployment:
"# Deployment Guide\n\nTo deploy this application:\n\n1. Build the project: `npm run build`\n2. Set environment variables\n3. Deploy to your hosting platform",
"getting-started":
"# Getting Started\n\nWelcome to our project! Follow these steps to set up your development environment:\n\n1. Clone the repository\n2. Install dependencies with `npm install`\n3. Run `npm start` to begin",
};
return {
text:
docs[args.section as keyof typeof docs] ||
"Documentation section not found",
};
},
mimeType: "text/markdown",
name: "Project Documentation",
uriTemplate: "docs://project/{section}",
});
server.addTool({
annotations: {
openWorldHint: false,
readOnlyHint: true,
title: "Get Documentation (Embedded)",
},
description:
"Retrieve project documentation using embedded resources - demonstrates the new embedded() feature",
execute: async (args) => {
return {
content: [
{
resource: await server.embedded(`docs://project/${args.section}`),
type: "resource",
},
],
};
},
name: "get-documentation",
parameters: z.object({
section: z
.enum(["getting-started", "api-reference", "deployment"])
.describe("Documentation section to retrieve"),
}),
});
// Select transport type based on command line arguments
const transportType = process.argv.includes("--http-stream")
? "httpStream"
: "stdio";
if (transportType === "httpStream") {
// Start with HTTP streaming transport
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 8080;
server.start({
httpStream: {
port: PORT,
},
transportType: "httpStream",
});
console.log(
`HTTP Stream MCP server is running at http://localhost:${PORT}/mcp`,
);
console.log("Use StreamableHTTPClientTransport to connect to this server");
console.log("For example:");
console.log(`
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const client = new Client(
{
name: "example-client",
version: "1.0.0",
},
{
capabilities: {},
},
);
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:${PORT}/mcp"),
);
await client.connect(transport);
`);
} else if (process.argv.includes("--explicit-ping-config")) {
server.start({
transportType: "stdio",
});
console.log(
"Started stdio transport with explicit ping configuration from server options",
);
} else if (process.argv.includes("--disable-roots")) {
// Example of disabling roots at runtime
const serverWithDisabledRoots = new FastMCP({
name: "Addition (No Roots)",
ping: {
intervalMs: 10000,
logLevel: "debug",
},
roots: {
enabled: false,
},
version: "1.0.0",
});
serverWithDisabledRoots.start({
transportType: "stdio",
});
console.log("Started stdio transport with roots support disabled");
} else {
// Disable by default for:
server.start({
transportType: "stdio",
});
console.log("Started stdio transport with ping disabled by default");
}