About Fastmcp Ts
Fastmcp Ts is an MCP server in the Developer Tools category: 🏎️ The official FastMCP TypeScript library - build MCP servers and clients, fast 🏎️.
The repository has 76 stars and 0 forks, with the last commit yesterday. Recent commit activity is the strongest signal that reported issues will get attention.
Install
claude mcp add fastmcp-ts -- npx -y @prefecthq/fastmcp-tsnpx -y @prefecthq/fastmcp-tsThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Fastmcp Ts
Powered by Claude · Grounded in docs
Security checks
- ✓README presentThe repository ships documentation.
5 checks have not been run yet.
README
FastMCP-TS
English | 简体中文
A TypeScript framework for building Model Context Protocol (MCP) servers with decorator-based syntax. This package simplifies the creation of MCP servers by providing intuitive decorators for defining tools, prompts, and resources.
Features
- 🎯 Decorator-based API - Use simple decorators to define MCP tools, prompts, and resources
- 🚀 Multiple Transport Types - Support for stdio, SSE, and streamable HTTP transports
- 📝 TypeScript First - Built with TypeScript for excellent type safety and IntelliSense
- 🔧 Zod Integration - Built-in parameter validation using Zod schemas
- 🌐 HTTP Integrations - Works with Express, Koa v3, hapi, AdonisJS, NestJS
- 📝 Doc-Driven Metadata (TSDoc-like) - Infer tool description and parameter schema from doc comments when decorator options are omitted
- ⚖️ Decorator Precedence - Decorator options always override doc comments for the same fields
- 🧪 DX-Friendly Dev/Test - Run tests with tsx against TypeScript sources via an alias; build src and tests into a flat
dist/ - 📒 Structured Logging - Pino-based logging with pretty output in development
Installation
npm install fastmcp-ts
Quick Start
1. Basic Tool Server
import { FastMCP, tool } from 'fastmcp-ts';
import { z } from 'zod';
class MathTools {
/**
* Add two numbers together.
* @param a First number
* @param b Second number
* @returns Sum of a and b
*/
@tool() // name defaults to method, description/params inferred from docs
async add({ a, b }: { a: number; b: number }) {
return a + b;
}
/**
* Multiplies two numbers.
* @param a First number
* @param b Second number
* @returns Product of a and b
*/
@tool({
name: 'product', // decorator overrides doc name
parameters: z.object({ // decorator overrides doc-inferred params
a: z.number(),
b: z.number(),
}),
})
async multiply({ a, b }: { a: number; b: number }) {
return a * b;
}
}
const server = new FastMCP({ name: 'math-server', version: '1.0.0' });
server.register(new MathTools());
await server.serve(); // stdio by default
2. HTTP Server with Express (optional)
import express from 'express'; // optional, for HTTP example only
import { FastMCP, tool } from 'fastmcp-ts';
import { z } from 'zod';
class MyTools {
@tool({
name: "greet",
description: "Greet someone",
parameters: z.object({
name: z.string(),
}),
})
async greet({ name }: { name: string }) {
return `Hello, ${name}!`;
}
}
const server = new FastMCP({
name: "greeting-server",
version: "1.0.0"
});
server.register(new MyTools());
// Configure for HTTP transport
const transportConfig = FastMCP.createStreamableHTTPConfig({
sessionIdGenerator: undefined
});
await server.serve(transportConfig);
// Set up Express app
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
const transport = server.getTransport();
await transport.handleRequest(req, res, req.body);
});
app.listen(3000, () => {
console.log('MCP server running on port 3000');
});
3. HTTP Server with Koa v3 (optional)
import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import Router from '@koa/router';
import { FastMCP, tool } from 'fastmcp-ts';
import { z } from 'zod';
class MyTools {
@tool({
name: 'greet',
parameters: z.object({ name: z.string() })
})
async greet({ name }: { name: string }) { return `Hello, ${name}!`; }
}
const server = new FastMCP({ name: 'greeting-server', version: '1.0.0' });
server.register(new MyTools());
await server.serve(FastMCP.createStreamableHTTPConfig({ sessionIdGenerator: undefined }));
const transport = server.getTransport();
const app = new Koa();
const router = new Router();
app.use(bodyParser());
router.post('/mcp', async (ctx) => {
await transport.handleRequest(
// Express-like shim: req, res, body
ctx.req as any,
ctx.res as any,
ctx.request.body
);
// Transport writes to res directly; ensure Koa doesn’t re-send
ctx.respond = false;
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
4. HTTP Server with hapi (optional)
import Hapi from '@hapi/hapi';
import { FastMCP, tool } from 'fastmcp-ts';
import { z } from 'zod';
class MyTools {
@tool({ name: 'greet', parameters: z.object({ name: z.string() }) })
async greet({ name }: { name: string }) { return `Hello, ${name}!`; }
}
const server = new FastMCP({ name: 'greeting-server', version: '1.0.0' });
server.register(new MyTools());
await server.serve(FastMCP.createStreamableHTTPConfig({ sessionIdGenerator: undefined }));
const transport = server.getTransport();
const app = Hapi.server({ port: 3000, host: 'localhost' });
app.route({
method: 'POST',
path: '/mcp',
options: { payload: { parse: true, output: 'data' } },
handler: async (request, h) => {
const res = request.raw.res;
await transport.handleRequest(request.raw.req as any, res as any, request.payload);
return h.abandon; // response already written
},
});
await app.start();
5. HTTP Server with AdonisJS (optional)
// start/routes.ts
import Route from '@ioc:Adonis/Core/Route';
import { FastMCP, tool } from 'fastmcp-ts';
import { z } from 'zod';
class MyTools {
@tool({ name: 'greet', parameters: z.object({ name: z.string() }) })
async greet({ name }: { name: string }) { return `Hello, ${name}!`; }
}
const mcp = new FastMCP({ name: 'greeting-server', version: '1.0.0' });
mcp.register(new MyTools());
await mcp.serve(FastMCP.createStreamableHTTPConfig({ sessionIdGenerator: undefined }));
const transport = mcp.getTransport();
Route.post('/mcp', async ({ request, response }) => {
await transport.handleRequest(request.request as any, response.response as any, request.body());
});
6. HTTP Server with NestJS (optional)
import { Controller, Post, Req, Res } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { Module } from '@nestjs/common';
import { FastMCP, tool } from 'fastmcp-ts';
import { z } from 'zod';
class MyTools {
@tool({ name: 'greet', parameters: z.object({ name: z.string() }) })
async greet({ name }: { name: string }) { return `Hello, ${name}!`; }
}
const mcp = new FastMCP({ name: 'greeting-server', version: '1.0.0' });
mcp.register(new MyTools());
await mcp.serve(FastMCP.createStreamableHTTPConfig({ sessionIdGenerator: undefined }));
const transport = mcp.getTransport();
@Controller()
class AppController {
@Post('mcp')
async mcp(@Req() req: any, @Res() res: any) {
await transport.handleRequest(req, res, req.body);
}
}
@Module({ controllers: [AppController] })
class AppModule {}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
API Reference
Decorators
@tool(options: ToolOptions)
Define a method as an MCP tool.
interface ToolOptions {
name?: string; // Tool name; defaults to method name
description?: string; // Description; inferred from doc comments if omitted
parameters?: z.ZodSchema<any>; // Zod schema; inferred from @param docs if omitted
}
Example:
@tool({
name: "file_read",
description: "Read contents of a file",
parameters: z.object({
path: z.string().describe("File path to read"),
encoding: z.string().optional().describe("File encoding (default: utf-8)"),
}),
})
async readFile({ path, encoding = 'utf-8' }: { path: string; encoding?: string }) {
// Implementation here
return fs.readFileSync(path, encoding);
}
Notes:
- Arguments are a single object (MCP protocol). Even for “two numbers,” define your method as
async fn({ a, b }: { a:number; b:number }). - Doc inference supports summary lines,
@param name desc, and@returns desc. - Decorator precedence: if a field is provided in the decorator, doc comments won’t override it.
@prompt(options: PromptOptions)
Define a method as an MCP prompt.
interface PromptOptions {
name: string; // Prompt name
description?: string; // Prompt description
arguments?: z.ZodSchema<any>; // Zod schema for arguments
}
Example:
@prompt({
name: "code_review",
description: "Generate a code review prompt",
arguments: z.object({
language: z.string(),
code: z.string(),
}),
})
async codeReviewPrompt({ language, code }: { language: string; code: string }) {
return `Please review this ${language} code:\n\n${code}`;
}
@resource(options: ResourceOptions)
Define a method as an MCP resource.
interface ResourceOptions {
uri: string | RegExp; // Resource URI or pattern
name?: string; // Resource name
description?: string; // Resource description
mimeType?: string; // MIME type (default: text/plain)
}
Example:
@resource({
uri: /^file:\/\/(.+)$/,
name: "file_system",
description: "File system resource",
mimeType: "text/plain",
})
async readResource({ uri }: { uri: string }) {
const match = uri.match(/^file:\/\/(.+)$/);
if (match) {
return fs.readFileSync(match[1], 'utf-8');
}
throw new Error('Invalid file URI');
}
FastMCP Class
Constructor
new FastMCP(options: { name: string; version: string })
Methods
register(instance: any)
Register a class instance containing decorated methods.
serve(transportConfig?: TransportConfig)
Start the server with the specified transport configuration.
close()
Close the server and transport.
Static Transport Configuration Helpers
FastMCP.createStdioConfig(): StdioTransportConfig
FastMCP.createSSEConfig(endpoint: string, response: ServerResponse, options?: SSEServerTransportOptions): SSETransportConfig
FastMCP.createStreamableHTTPConfig(options: StreamableHTTPServerTransportOptions): StreamableHTTPTransportConfig
Transport Types
1. Stdio Transport (Default)
await server.serve(); // Uses stdio by default
// or explicitly:
await server.serve(FastMCP.createStdioConfig());
2. SSE (Server-Sent Events) Transport
import type { ServerResponse } from 'node:http';
const config = FastMCP.createSSEConfig('/sse', response, {
// SSE options
});
await server.serve(config);
3. Streamable HTTP Transport
const config = FastMCP.createStreamableHTTPConfig({
sessionIdGenerator: undefined
});
await server.serve(config);
Doc-Driven Metadata
When you omit description or parameters in @tool, FastMCP-TS will try to read the doc block above the decorator and infer:
- Description: from the summary text.
- Parameters: from
@paramannotations. Simple heuristics map words like “number”, “boolean”, “array” to corresponding Zod types; otherwise string.
Decorator options always win. For example:
/**
* Multiplies two numbers
* @param a First number
* @param b Second number
* @returns Product of a and b
*/
@tool({
name: 'multiply1',
parameters: z.object({ a: z.number(), b: z.number() })
})
async multiply({ a, b }: { a: number; b: number }) { /* ... */ }
// Result: name=multiply1, description from docs, parameters from decorator
Development & Build
This repo compiles both src/ and tests/ to a flat dist/:
tsconfig.src.jsonbuildssrc → dist(emits d.ts)tsconfig.tests.jsonbuildstests → dist(no d.ts)npm run buildruns both projects sequentially
Dev workflow with tsx (no build):
- We use a package
importsalias inpackage.json:#fastmcp→./src/index.tsin development#fastmcp→./dist/index.jsby default
- Tests import the alias:
import { FastMCP, tool } from '#fastmcp' - Run with tsx:
tsx tests/simple-demo.ts
Build and run compiled JS:
npm run build
node dist/simple-demo.js
Collision note: If src/foo.ts and tests/foo.ts both exist, they would emit the same dist/foo.js. Rename to avoid overwrites, or adjust tests outDir to dist/tests.
Logging
FastMCP-TS uses pino for structured logs.
- Level: set
FASTMCP_LOG_LEVEL=debug(orLOG_LEVEL). - Pretty output: set
PINO_PRETTY=1for human-readable logs.
Example (PowerShell):
$env:FASTMCP_LOG_LEVEL = 'debug'
$env:PINO_PRETTY = '1'
tsx tests/simple-demo.ts
Advanced Examples
File Operations Server
import { FastMCP, tool, resource } from 'fastmcp-ts';
import { z } from 'zod';
import * as fs from 'fs';
import * as path from 'path';
class FileOperations {
@tool({
name: "list_directory",
description: "List contents of a directory",
parameters: z.object({
path: z.string().describe("Directory path"),
}),
})
async listDirectory({ path: dirPath }: { path: string }) {
const files = fs.readdirSync(dirPath);
return files.map(file => ({
name: file,
isDirectory: fs.statSync(path.join(dirPath, file)).isDirectory()
}));
}
@tool({
name: "write_file",
description: "Write content to a file",
parameters: z.object({
path: z.string(),
content: z.string(),
}),
})
async writeFile({ path: filePath, content }: { path: string; content: string }) {
fs.writeFileSync(filePath, content, 'utf-8');
return `File written to ${filePath}`;
}
@resource({
uri: /^file:\/\/(.+)$/,
description: "Read file contents as resource",
mimeType: "text/plain",
})
async readFileResource({ uri }: { uri: string }) {
const match = uri.match(/^file:\/\/(.+)$/);
if (!match) throw new Error('Invalid file URI');
const filePath = match[1];
return fs.readFileSync(filePath, 'utf-8');
}
}
const server = new FastMCP({
name: "file-operations-server",
version: "1.0.0"
});
server.register(new FileOperations());
await server.serve();
Building and Running
# Install dependencies
npm install
# Build the project
npm run build
# Run in development mode with watch
npm run dev
# Run tests
npm test
# Start the demo server
npm start
Requirements
- Node.js 18+
- TypeScript 5.0+
Dependencies
@modelcontextprotocol/sdk- Core MCP SDKzod- Schema validationreflect-metadata- Decorator metadata support
HTTP frameworks are optional and only needed for examples/tests:
express,koa,@koa/router,koa-bodyparser@hapi/hapi@adonisjs/*(depending on your app)@nestjs/common,@nestjs/core(if using NestJS)
License
ISC
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
README copy captured when this server was indexed; it may lag the repository. The original is authoritative.