- Notifications
You must be signed in to change notification settings - Fork106
A framework for writing MCP (Model Context Protocol) servers in Typescript
License
QuantGeekDev/mcp-framework
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
MCP-Framework is a framework for building Model Context Protocol (MCP) servers elegantly in TypeScript.
MCP-Framework gives you architecture out of the box, with automatic directory-based discovery for tools, resources, and prompts. Use our powerful MCP abstractions to define tools, resources, or prompts in an elegant way. Our cli makes getting started with your own MCP server a breeze
- 🛠️ Automatic discovery and loading of tools, resources, and prompts
- Multiple transport support (stdio, SSE, HTTP Stream)
- TypeScript-first development with full type safety
- Built on the official MCP SDK
- Easy-to-use base classes for tools, prompts, and resources
- Out of the box authentication for SSE endpoints (OAuth 2.1, JWT, API Key)
The following projects and services are built using MCP Framework:
A crypto tipping service that enables AI assistants to help users send cryptocurrency tips to content creators directly from their chat interface. The MCP service allows for:
- Checking wallet types for users
- Preparing cryptocurrency tips for users/agents to completeSetup instructions for various clients (Cursor, Sage, Claude Desktop) are available in theirMCP Server documentation.
# Install the framework globallynpm install -g mcp-framework# Create a new MCP server projectmcp create my-mcp-server# Navigate to your projectcd my-mcp-server# Your server is ready to use!
The framework provides a powerful CLI for managing your MCP server projects:
# Create a new projectmcp create<your project name here># Create a new project with the new EXPERIMENTAL HTTP transportHeads up: This willset cors allowed origin to"*", modify itin the indexif you wishmcp create<your project name here> --http --port 1337 --cors
# Add a new toolmcp add tool price-fetcherThe framework provides comprehensive validation to ensure your tools are properly documented and functional:
# Build with automatic validation (recommended)npm run build# Build with custom validation settingsMCP_SKIP_TOOL_VALIDATION=false npm run build# Force validation (default)MCP_SKIP_TOOL_VALIDATION=true npm run build# Skip validation (not recommended)
# Validate all tools have proper descriptions (for Zod schemas)mcp validateThis command checks that all tools using Zod schemas have descriptions for every field. The validation runs automatically during build, but you can also run it standalone:
- ✅During build:
npm run buildautomatically validates tools - ✅Standalone:
mcp validatefor manual validation - ✅Development: Use
defineSchema()helper for immediate feedback - ✅Runtime: Server validates tools on startup
Example validation error:
❌ Tool validation failed: ❌ PriceFetcher.js: Missing descriptionsforfieldsin price_fetcher: symbol, currency. All fields must have descriptions when using Zod object schemas. Use.describe() on each field, e.g.,z.string().describe("Field description")
Integrating validation into CI/CD:
{"scripts": {"build":"tsc && mcp-build","test":"jest && mcp validate","prepack":"npm run build && mcp validate" }}# Add a new promptmcp add prompt price-analysis# Add a new promptmcp add resource market-dataCreate your project:
mcp create my-mcp-servercd my-mcp-serverAdd tools:
mcp add tool data-fetchermcp add tool data-processormcp add tool report-generator
Define your tool schemas with automatic validation:
// tools/DataFetcher.tsimport{MCPTool,MCPInputasAddToolInput}from"mcp-framework";
import { z } from "zod";
const AddToolSchema = z.object({a: z.number().describe("First number to add"),b: z.number().describe("Second number to add"),});
class AddTool extends MCPTool {name = "add";description = "Add tool description";schema = AddToolSchema;
async execute(input: AddToolInput) {const result = input.a + input.b;returnResult: ${result};}}export default AddTool;
4. **Build with automatic validation:**```bashnpm run build # Automatically validates schemas and compilesOptional: Run standalone validation:
mcp validate# Check all tools independentlyTest your server:
node dist/index.js# Server validates tools on startupAdd to MCP Client (see Claude Desktop example below)
Pro Tips:
- Use
defineSchema()during development for immediate feedback - Build process automatically catches missing descriptions
- Server startup validates all tools before accepting connections
- Use TypeScript's autocomplete with
McpInput<this>for better DX
Add this configuration to your Claude Desktop config file:
MacOS:~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:%APPDATA%/Claude/claude_desktop_config.json
{"mcpServers": {"${projectName}": {"command":"node","args":["/absolute/path/to/${projectName}/dist/index.js"]}}}Add this configuration to your Claude Desktop config file:
MacOS:~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:%APPDATA%/Claude/claude_desktop_config.json
{"mcpServers": {"${projectName}": {"command":"npx","args": ["${projectName}"]}}}- Make changes to your tools
- Run
npm run buildto compile - The server will automatically load your tools on startup
The framework supports the following environment variables for configuration:
| Variable | Description | Default |
|---|---|---|
| MCP_ENABLE_FILE_LOGGING | Enable logging to files (true/false) | false |
| MCP_LOG_DIRECTORY | Directory where log files will be stored | logs |
| MCP_DEBUG_CONSOLE | Display debug level messages in console (true/false) | false |
Example usage:
# Enable file loggingMCP_ENABLE_FILE_LOGGING=true node dist/index.js# Specify a custom log directoryMCP_ENABLE_FILE_LOGGING=true MCP_LOG_DIRECTORY=my-logs node dist/index.js# Enable debug messages in consoleMCP_DEBUG_CONSOLE=true node dist/index.js
MCP Framework uses Zod schemas for defining tool inputs, providing type safety, validation, and automatic documentation:
import{MCPTool,McpInput}from"mcp-framework";import{z}from"zod";constAddToolSchema=z.object({a:z.number().describe("First number to add"),b:z.number().describe("Second number to add"),});classAddToolextendsMCPTool{name="add";description="Add tool description";schema=AddToolSchema;asyncexecute(input:McpInput<this>){constresult=input.a+input.b;return`Result:${result}`;}}exportdefaultAddTool;
Key Benefits:
- ✅Single source of truth - Define types and validation in one place
- ✅Automatic type inference - TypeScript types are inferred from your schema
- ✅Rich validation - Leverage Zod's powerful validation features
- ✅Required descriptions - Framework enforces documentation
- ✅Better IDE support - Full autocomplete and type checking
- ✅Cleaner code - No duplicate type definitions
The framework supports all Zod features:
import{MCPTool,McpInput}from"mcp-framework";import{z}from"zod";constAdvancedSchema=z.object({// String constraints and formatsemail:z.string().email().describe("User email address"),name:z.string().min(2).max(50).describe("User name"),website:z.string().url().optional().describe("Optional website URL"),// Number constraintsage:z.number().int().positive().max(120).describe("User age"),rating:z.number().min(1).max(5).describe("Rating from 1 to 5"),// Arrays and objectstags:z.array(z.string()).describe("List of tags"),metadata:z.object({priority:z.enum(['low','medium','high']).describe("Task priority"),dueDate:z.string().optional().describe("Due date in ISO format")}).describe("Additional metadata"),// Default valuesstatus:z.string().default('pending').describe("Current status"),// Unions and enumscategory:z.union([z.literal('personal'),z.literal('work'),z.literal('other')]).describe("Category type")});classAdvancedToolextendsMCPTool{name="advanced_tool";description="Tool demonstrating advanced Zod features";schema=AdvancedSchema;asyncexecute(input:McpInput<this>){// TypeScript automatically knows all the types!const{ email, name, website, age, rating, tags, metadata, status, category}=input;console.log(input.name.toUpperCase());// ✅ TypeScript knows this is validconsole.log(input.age.toFixed(2));// ✅ Number methods availableconsole.log(input.tags.length);// ✅ Array methods availableconsole.log(input.website?.includes("https"));// ✅ Optional handlingreturn`Processed user:${name}`;}}
TheMcpInput<this> type automatically infers the correct input type from your schema, eliminating the need for manual type definitions:
classMyToolextendsMCPTool{schema=z.object({name:z.string().describe("User name"),age:z.number().optional().describe("User age"),tags:z.array(z.string()).describe("User tags")});asyncexecute(input:McpInput<this>){// TypeScript automatically knows:// input.name is string// input.age is number | undefined// input.tags is string[]console.log(input.name.toUpperCase());// ✅ TypeScript knows this is validconsole.log(input.age?.toFixed(2));// ✅ Handles optional correctlyconsole.log(input.tags.length);// ✅ Array methods available}}
No more duplicate interfaces or generic type parameters needed!
All schema fields must have descriptions. This ensures your tools are well-documented and provides better user experience in MCP clients.
The framework validates descriptions at multiple levels:
npm run build# Automatically validates during compilationUse thedefineSchema helper for immediate feedback:
import{defineSchema}from"mcp-framework";// This will throw an error immediately if descriptions are missingconstMySchema=defineSchema({name:z.string(),// ❌ Error: Missing descriptionage:z.number().describe("User age")// ✅ Good});
mcp validate# Check all tools for proper descriptionsThe server automatically validates tools on startup.
To skip validation (not recommended):
# Skip during buildMCP_SKIP_TOOL_VALIDATION=true npm run build# Skip during developmentNODE_ENV=production npm run dev
import{MCPServer}from"mcp-framework";constserver=newMCPServer();// OR (mutually exclusive!) with SSE transportconstserver=newMCPServer({transport:{type:"sse",options:{port:8080// Optional (default: 8080)}}});// Start the serverawaitserver.start();
The stdio transport is used by default if no transport configuration is provided:
constserver=newMCPServer();// or explicitly:constserver=newMCPServer({transport:{type:"stdio"}});
To use Server-Sent Events (SSE) transport:
constserver=newMCPServer({transport:{type:"sse",options:{port:8080,// Optional (default: 8080)endpoint:"/sse",// Optional (default: "/sse")messageEndpoint:"/messages",// Optional (default: "/messages")cors:{allowOrigin:"*",// Optional (default: "*")allowMethods:"GET, POST, OPTIONS",// Optional (default: "GET, POST, OPTIONS")allowHeaders:"Content-Type, Authorization, x-api-key",// Optional (default: "Content-Type, Authorization, x-api-key")exposeHeaders:"Content-Type, Authorization, x-api-key",// Optional (default: "Content-Type, Authorization, x-api-key")maxAge:"86400"// Optional (default: "86400")}}}});
To use HTTP Stream transport:
constserver=newMCPServer({transport:{type:"http-stream",options:{port:8080,// Optional (default: 8080)endpoint:"/mcp",// Optional (default: "/mcp")responseMode:"batch",// Optional (default: "batch"), can be "batch" or "stream"batchTimeout:30000,// Optional (default: 30000ms) - timeout for batch responsesmaxMessageSize:"4mb",// Optional (default: "4mb") - maximum message size// Session configurationsession:{enabled:true,// Optional (default: true)headerName:"Mcp-Session-Id",// Optional (default: "Mcp-Session-Id")allowClientTermination:true,// Optional (default: true)},// Stream resumability (for missed messages)resumability:{enabled:false,// Optional (default: false)historyDuration:300000,// Optional (default: 300000ms = 5min) - how long to keep message history},// CORS configurationcors:{allowOrigin:"*"// Other CORS options use defaults}}}});
The HTTP Stream transport supports two response modes:
Batch Mode (Default): Responses are collected and sent as a single JSON-RPC response. This is suitable for typical request-response patterns and is more efficient for most use cases.
Stream Mode: All responses are sent over a persistent SSE connection opened for each request. This is ideal for long-running operations or when the server needs to send multiple messages in response to a single request.
You can configure the response mode based on your specific needs:
// For batch mode (default):constserver=newMCPServer({transport:{type:"http-stream",options:{responseMode:"batch"}}});// For stream mode:constserver=newMCPServer({transport:{type:"http-stream",options:{responseMode:"stream"}}});
- Session Management: Automatic session tracking and management
- Stream Resumability: Optional support for resuming streams after connection loss
- Batch Processing: Support for JSON-RPC batch requests/responses
- Comprehensive Error Handling: Detailed error responses with JSON-RPC error codes
MCP Framework provides optional authentication for SSE endpoints. You can choose between JWT, API Key, OAuth 2.1 authentication, or implement your own custom authentication provider.
The framework supports OAuth 2.1 authorization with PKCE, implementing the MCP authorization specification. This is ideal for integrating with authorization servers like AWS Cognito, Auth0, Okta, etc.
import{MCPServer,OAuthProvider}from"mcp-framework";constserver=newMCPServer({transport:{type:"sse",options:{auth:{provider:newOAuthProvider({// Your authorization server (e.g., Cognito)authorizationServer:"https://your-domain.auth.us-east-1.amazoncognito.com",// OAuth client credentialsclientId:process.env.OAUTH_CLIENT_ID,clientSecret:process.env.OAUTH_CLIENT_SECRET,// Optional for public clients// The canonical URI of this MCP serverresourceUri:"https://mcp.example.com",// Required scopesrequiredScopes:["openid","profile"],}),endpoints:{sse:false,// SSE endpoint is publicmessages:true,// Messages require authentication}},// Handle OAuth callbacksoauth:{onCallback:async({ accessToken, refreshToken})=>{console.log("User authorized successfully!");},onError:async(error)=>{console.error("Authorization failed:",error);}}}}});
OAuth Features:
- 🔐OAuth 2.1 with PKCE: Enhanced security with Proof Key for Code Exchange
- 🌐Protected Resource Metadata (RFC 9728): Automatic authorization server discovery
- 🎯Resource Indicators (RFC 8707): Explicit token audience binding
- ✅Token Validation: Support for JWT and opaque tokens
- 🔄Token Caching: Configurable token validation caching
- 🛡️Strict Audience Validation: Prevents token misuse across services
Quick Links:
import{MCPServer,JWTAuthProvider}from"mcp-framework";import{Algorithm}from"jsonwebtoken";constserver=newMCPServer({transport:{type:"sse",options:{auth:{provider:newJWTAuthProvider({secret:process.env.JWT_SECRET,algorithms:["HS256"asAlgorithm],// Optional (default: ["HS256"])headerName:"Authorization"// Optional (default: "Authorization")}),endpoints:{sse:true,// Protect SSE endpoint (default: false)messages:true// Protect message endpoint (default: true)}}}}});
Clients must include a valid JWT token in the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...import{MCPServer,APIKeyAuthProvider}from"mcp-framework";constserver=newMCPServer({transport:{type:"sse",options:{auth:{provider:newAPIKeyAuthProvider({keys:[process.env.API_KEY],headerName:"X-API-Key"// Optional (default: "X-API-Key")})}}}});
Clients must include a valid API key in the X-API-Key header:
X-API-Key: your-api-keyMCP Framework supports OAuth 2.1 authentication per the MCP specification (2025-06-18), including Protected Resource Metadata (RFC 9728) and proper token validation with JWKS support.
OAuth authentication works with both SSE and HTTP Stream transports and supports two validation strategies:
JWT validation fetches public keys from your authorization server's JWKS endpoint and validates tokens locally. This is the fastest option as it doesn't require a round-trip to the auth server for each request.
import{MCPServer,OAuthAuthProvider}from"mcp-framework";constserver=newMCPServer({transport:{type:"http-stream",options:{port:8080,auth:{provider:newOAuthAuthProvider({authorizationServers:[process.env.OAUTH_AUTHORIZATION_SERVER],resource:process.env.OAUTH_RESOURCE,validation:{type:'jwt',jwksUri:process.env.OAUTH_JWKS_URI,audience:process.env.OAUTH_AUDIENCE,issuer:process.env.OAUTH_ISSUER,algorithms:['RS256','ES256']// Optional (default: ['RS256', 'ES256'])}}),endpoints:{initialize:true,// Protect session initializationmessages:true// Protect MCP messages}}}}});
Environment Variables:
OAUTH_AUTHORIZATION_SERVER=https://auth.example.comOAUTH_RESOURCE=https://mcp.example.comOAUTH_JWKS_URI=https://auth.example.com/.well-known/jwks.jsonOAUTH_AUDIENCE=https://mcp.example.comOAUTH_ISSUER=https://auth.example.com
Token introspection validates tokens by calling your authorization server's introspection endpoint. This provides centralized control and is useful when you need real-time token revocation.
import{MCPServer,OAuthAuthProvider}from"mcp-framework";constserver=newMCPServer({transport:{type:"sse",options:{auth:{provider:newOAuthAuthProvider({authorizationServers:[process.env.OAUTH_AUTHORIZATION_SERVER],resource:process.env.OAUTH_RESOURCE,validation:{type:'introspection',audience:process.env.OAUTH_AUDIENCE,issuer:process.env.OAUTH_ISSUER,introspection:{endpoint:process.env.OAUTH_INTROSPECTION_ENDPOINT,clientId:process.env.OAUTH_CLIENT_ID,clientSecret:process.env.OAUTH_CLIENT_SECRET}}})}}}});
Environment Variables:
OAUTH_AUTHORIZATION_SERVER=https://auth.example.comOAUTH_RESOURCE=https://mcp.example.comOAUTH_AUDIENCE=https://mcp.example.comOAUTH_ISSUER=https://auth.example.comOAUTH_INTROSPECTION_ENDPOINT=https://auth.example.com/oauth/introspectOAUTH_CLIENT_ID=mcp-serverOAUTH_CLIENT_SECRET=your-client-secret
- RFC 9728 Compliance: Automatic Protected Resource Metadata endpoint at
/.well-known/oauth-protected-resource - RFC 6750 WWW-Authenticate Headers: Proper OAuth error responses with challenge headers
- JWKS Key Caching: Public keys cached for 15 minutes (configurable)
- Token Introspection Caching: Introspection results cached for 5 minutes (configurable)
- Security: Tokens in query strings are automatically rejected
- Claims Extraction: Access token claims in your tool handlers via
AuthResult
The OAuth provider works with any RFC-compliant OAuth 2.1 authorization server:
- Auth0: Use your Auth0 tenant's JWKS URI and issuer
- Okta: Use your Okta authorization server configuration
- AWS Cognito: Use your Cognito user pool's JWKS endpoint
- Azure AD / Entra ID: Use Microsoft Entra ID endpoints
- Custom: Any OAuth 2.1 compliant authorization server
For detailed setup guides with specific providers, see theOAuth Setup Guide.
Clients must include a valid OAuth access token in the Authorization header:
# Make a request with OAuth tokencurl -X POST http://localhost:8080/mcp \ -H"Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \ -H"Content-Type: application/json" \ -d'{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'# Discover OAuth configurationcurl http://localhost:8080/.well-known/oauth-protected-resource
- Always use HTTPS in production - OAuth tokens should never be transmitted over unencrypted connections
- Validate audience claims - Prevents token reuse across different services
- Use short-lived tokens - Reduces risk if tokens are compromised
- Enable token introspection caching - Reduces load on authorization server while maintaining security
- Monitor token errors - Track failed authentication attempts for security insights
You can implement your own authentication provider by implementing theAuthProvider interface:
import{AuthProvider,AuthResult}from"mcp-framework";import{IncomingMessage}from"node:http";classCustomAuthProviderimplementsAuthProvider{asyncauthenticate(req:IncomingMessage):Promise<boolean|AuthResult>{// Implement your custom authentication logicreturntrue;}getAuthError(){return{status:401,message:"Authentication failed"};}}
MIT
About
A framework for writing MCP (Model Context Protocol) servers in Typescript
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors11
Uh oh!
There was an error while loading.Please reload this page.