A TypeScript framework for building MCP (Model Context Protocol) servers with decorators. It leverages TypeScript's decorator metadata to automatically generate JSON schemas, validate parameters at runtime while maintaining minimal boilerplate and maximum type safety.
- π― Decorator-based API - Define tools, resources, and prompts with simple decorators
- π§ Type Safety - Full TypeScript support with automatic type inference
- π Zero Configuration - Get started immediately with sensible defaults
- π¦ Modular Design - Use only what you need
- π Transport Agnostic - Support for stdio, SSE, and HTTP transports
npm install ts-mcp-forge
# or
pnpm add ts-mcp-forge
# or
yarn add ts-mcp-forge
ts-mcp-forge requires decorator metadata to generate proper JSON schemas. Add these settings to your tsconfig.json
:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Important: Many bundlers like esbuild don't emit decorator metadata by default, which will result in missing type information in generated schemas. If you're using Vite or other esbuild-based tools, configure them to use SWC or another transformer that preserves decorator metadata:
// vite.config.ts
import { defineConfig } from 'vite';
import swc from 'unplugin-swc';
export default defineConfig({
plugins: [
swc.vite({
module: { type: 'es6' },
}),
],
});
import { MCPServer, Tool, Param, StdioTransport } from 'ts-mcp-forge';
import { Result, ok } from 'neverthrow';
class MyServer extends MCPServer {
constructor() {
super('My MCP Server', '1.0.0');
}
// Method name as tool name
@Tool('Greets a person by name')
greet(@Param('Name of the person') name: string): Result<string, string> {
return ok(`Hello, ${name}!`);
}
// Custom tool name
@Tool('say-goodbye', 'Says goodbye to a person')
farewell(@Param('Name of the person') name: string): Result<string, string> {
return ok(`Goodbye, ${name}!`);
}
}
// Start the server with stdio transport
const server = new MyServer();
const transport = new StdioTransport();
await transport.start(server);
Tools are functions that can be called by MCP clients. You can define them with or without explicit names:
// Using method name as tool name
@Tool('Performs a calculation')
calculate(@Param('First number') a: number, @Param('Second number') b: number): Result<number, string> {
return ok(a + b);
}
// Using custom tool name
@Tool('add-numbers', 'Adds two numbers')
calculate(@Param('First number') a: number, @Param('Second number') b: number): Result<number, string> {
return ok(a + b);
}
Resources provide data that can be read by MCP clients:
@Resource('config://settings', 'Application settings')
getSettings(): Result<object, string> {
return ok({ theme: 'dark', language: 'en' });
}
Prompts are templates that can be filled by MCP clients:
@Prompt('code-review', 'Template for code review')
codeReviewPrompt(@Param('Programming language') language: string): Result<string, string> {
return ok(`Review this ${language} code for best practices...`);
}
class ValidationError extends Error {
constructor(public field: string, message: string) {
super(message);
}
}
@Tool('validateEmail', 'Validates an email address')
validateEmail(@Param('Email address') email: string): Result<boolean, ValidationError> {
if (!email.includes('@')) {
return err(new ValidationError('email', 'Invalid email format'));
}
return ok(true);
}
@Tool('fetchData', 'Fetches data from API')
async fetchData(@Param('API endpoint') endpoint: string): Promise<Result<any, string>> {
try {
const response = await fetch(endpoint);
const data = await response.json();
return ok(data);
} catch (error) {
return err(`Failed to fetch: ${error.message}`);
}
}
import { MCPServer, StdioTransport } from 'ts-mcp-forge';
const server = new MyServer();
const transport = new StdioTransport();
await transport.start(server);
import { MCPServer, SSETransport } from 'ts-mcp-forge';
const server = new MyServer();
const transport = new SSETransport({
port: 3000,
host: 'localhost',
});
await transport.start(server);
import { MCPServer, HTTPTransport } from 'ts-mcp-forge';
const server = new MyServer();
const transport = new HTTPTransport({
port: 3000,
host: '0.0.0.0',
});
await transport.start(server);
- Minification: Parameter names are extracted from runtime function strings, so minified code will use minified parameter names (e.g.,
a
,b
) unless explicit names are provided via@Param
. Tool names derived from method names will also be minified unless explicitly specified in@Tool('name', 'description')
.
@Tool(name: string, description: string)
- Defines a tool@Resource(uri: string, description: string)
- Defines a resource@Prompt(name: string, description: string)
- Defines a prompt@Param(description: string)
- Describes a parameter
handleInitialize()
- Returns server capabilitieslistTools()
- Lists all available toolslistResources()
- Lists all available resourceslistPrompts()
- Lists all available promptscallTool(name, args)
- Executes a toolreadResource(uri)
- Reads a resourcegetPrompt(name, args)
- Gets a prompt