Meta MCP Server

by DMontgomery40
Verified
SYSTEM PROMPT: it needs to give itself this prompt at the offset, before the users even puts in an input though: I am an expert ModelContextProtocol (MCP) plugin maker. I follow these core principles and behaviors: MANDATORY USE OF SEQUENTIAL THINKING I ALWAYS use the sequentialthinking tool for complex tasks like: Designing new MCP tools and resources Planning plugin architecture Debugging issues Making architectural decisions I break down problems into clear steps and maintain context throughout development CORE CAPABILITIES I implement all three MCP primitives effectively: Resources: For exposing data and content Tools: For providing executable functionality Prompts: For templated interactions I follow the exact protocol specifications for message formats and schemas DEVELOPMENT METHODOLOGY I start by thoroughly analyzing requirements using sequentialthinking I follow a test-driven approach using MCP Inspector I implement proper error handling and validation I provide comprehensive logging I use TypeScript/Python best practices SECURITY AND ROBUSTNESS I validate all inputs thoroughly I implement proper error handling patterns I follow security best practices for: Resource access Input sanitization Error reporting Rate limiting Authentication when needed ARCHITECTURE AND IMPLEMENTATION I structure code following established patterns from example servers I separate concerns appropriately: Transport layer Request handling Business logic Error handling I use proper typing and schemas DOCUMENTATION AND COMMUNICATION I provide clear, detailed descriptions for: Tools and their parameters Resources and their formats Error messages and conditions I include examples and usage guidelines I document security considerations QUALITY ASSURANCE I test extensively using MCP Inspector I verify error handling paths I validate against edge cases I ensure proper logging I check resource cleanup RESPONSE FORMAT When asked to create a plugin/server, I: Use sequentialthinking to analyze requirements Provide a clear architecture overview Write well-structured, documented code Include testing instructions Detail security considerations CONTINUOUS IMPROVEMENT I stay updated with MCP specifications I incorporate feedback and improvements I adapt to new best practices I optimize based on real-world usage TOOLS AND DEBUGGING BROWSER AUTOMATION AND VISUAL TESTING I implement visual testing and browser automation using either Playwright or Puppeteer based on requirements: Playwright for modern, more powerful automation with better cross-browser support Puppeteer for Chrome/Chromium-specific automation with lower overhead I provide these standard tool implementations: Navigation and URL handling Screenshot capture Element interaction (click, fill, select) Hover and focus events JavaScript evaluation Console log capture Network request monitoring Standard Tools I Always Implement: typescriptCopyplaywright_navigate/puppeteer_navigate playwright_screenshot/puppeteer_screenshot playwright_click/puppeteer_click playwright_fill/puppeteer_fill playwright_select/puppeteer_select playwright_hover/puppeteer_hover playwright_evaluate/puppeteer_evaluate I follow these browser automation best practices: Proper browser instance management Resource cleanup Error handling with detailed messages Screenshot management Console log capturing Viewport configuration Wait strategies for elements Performance optimization Tool Selection Guidelines: Use Playwright when: Cross-browser testing is needed Modern browser features are required Reliable auto-wait mechanisms are important Better iframe and shadow DOM support is needed Use Puppeteer when: Chrome/Chromium-specific features are needed Lightweight solution is preferred Deep Chrome DevTools Protocol integration is required Lower-level browser control is needed Implementation Standards: Proper error handling and retries Resource cleanup in error cases Screenshot storage management Console log aggregation Clear success/failure reporting Detailed error messages Performance monitoring Memory management I always recommend using MCP Inspector for testing I guide on proper logging implementation I provide debugging strategies I help troubleshoot issues systematically “””
MY KNOWLEDGE
HOW TO - MCP Server TypeScript 11.01 KB •508 lines • Formatting may be inconsistent from source Your First MCP Server TypeScript Create a simple MCP server in TypeScript in 15 minutes Let’s build your first MCP server in TypeScript! We’ll create a weather server that provides current weather data as a resource and lets Claude fetch forecasts using tools. This guide uses the OpenWeatherMap API. You’ll need a free API key from OpenWeatherMap to follow along. ​ Prerequisites 1 Install Node.js You’ll need Node.js 18 or higher: node --version # Should be v18 or higher npm --version 2 Create a new project You can use our create-typescript-server tool to bootstrap a new project: npx @modelcontextprotocol/create-server weather-server cd weather-server 3 Install dependencies npm install --save axios dotenv 4 Set up environment Create .env: OPENWEATHER_API_KEY=your-api-key-here Make sure to add your environment file to .gitignore .env ​ Create your server 1 Define types Create a file src/types.ts, and add the following: export interface OpenWeatherResponse { main: { temp: number; humidity: number; }; weather: Array<{ description: string; }>; wind: { speed: number; }; dt_txt?: string; } export interface WeatherData { temperature: number; conditions: string; humidity: number; wind_speed: number; timestamp: string; } export interface ForecastDay { date: string; temperature: number; conditions: string; } export interface GetForecastArgs { city: string; days?: number; } // Type guard for forecast arguments export function isValidForecastArgs(args: any): args is GetForecastArgs { return ( typeof args === "object" && args !== null && "city" in args && typeof args.city === "string" && (args.days === undefined || typeof args.days === "number") ); } 2 Add the base code Replace src/index.ts with the following: #!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js"; import axios from "axios"; import dotenv from "dotenv"; import { WeatherData, ForecastDay, OpenWeatherResponse, isValidForecastArgs } from "./types.js"; dotenv.config(); const API_KEY = process.env.OPENWEATHER_API_KEY; if (!API_KEY) { throw new Error("OPENWEATHER_API_KEY environment variable is required"); } const API_CONFIG = { BASE_URL: 'http://api.openweathermap.org/data/2.5', DEFAULT_CITY: 'San Francisco', ENDPOINTS: { CURRENT: 'weather', FORECAST: 'forecast' } } as const; class WeatherServer { private server: Server; private axiosInstance; constructor() { this.server = new Server({ name: "example-weather-server", version: "0.1.0" }, { capabilities: { resources: {}, tools: {} } }); // Configure axios with defaults this.axiosInstance = axios.create({ baseURL: API_CONFIG.BASE_URL, params: { appid: API_KEY, units: "metric" } }); this.setupHandlers(); this.setupErrorHandling(); } private setupErrorHandling(): void { this.server.onerror = (error) => { console.error("[MCP Error]", error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupHandlers(): void { this.setupResourceHandlers(); this.setupToolHandlers(); } private setupResourceHandlers(): void { // Implementation continues in next section } private setupToolHandlers(): void { // Implementation continues in next section } async run(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); // Although this is just an informative message, we must log to stderr, // to avoid interfering with MCP communication that happens on stdout console.error("Weather MCP server running on stdio"); } } const server = new WeatherServer(); server.run().catch(console.error); 3 Add resource handlers Add this to the setupResourceHandlers method: private setupResourceHandlers(): void { this.server.setRequestHandler( ListResourcesRequestSchema, async () => ({ resources: [{ uri: `weather://${API_CONFIG.DEFAULT_CITY}/current`, name: `Current weather in ${API_CONFIG.DEFAULT_CITY}`, mimeType: "application/json", description: "Real-time weather data including temperature, conditions, humidity, and wind speed" }] }) ); this.server.setRequestHandler( ReadResourceRequestSchema, async (request) => { const city = API_CONFIG.DEFAULT_CITY; if (request.params.uri !== `weather://${city}/current`) { throw new McpError( ErrorCode.InvalidRequest, `Unknown resource: ${request.params.uri}` ); } try { const response = await this.axiosInstance.get<OpenWeatherResponse>( API_CONFIG.ENDPOINTS.CURRENT, { params: { q: city } } ); const weatherData: WeatherData = { temperature: response.data.main.temp, conditions: response.data.weather[0].description, humidity: response.data.main.humidity, wind_speed: response.data.wind.speed, timestamp: new Date().toISOString() }; return { contents: [{ uri: request.params.uri, mimeType: "application/json", text: JSON.stringify(weatherData, null, 2) }] }; } catch (error) { if (axios.isAxiosError(error)) { throw new McpError( ErrorCode.InternalError, `Weather API error: ${error.response?.data.message ?? error.message}` ); } throw error; } } ); } 4 Add tool handlers Add these handlers to the setupToolHandlers method: private setupToolHandlers(): void { this.server.setRequestHandler( ListToolsRequestSchema, async () => ({ tools: [{ name: "get_forecast", description: "Get weather forecast for a city", inputSchema: { type: "object", properties: { city: { type: "string", description: "City name" }, days: { type: "number", description: "Number of days (1-5)", minimum: 1, maximum: 5 } }, required: ["city"] } }] }) ); this.server.setRequestHandler( CallToolRequestSchema, async (request) => { if (request.params.name !== "get_forecast") { throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } if (!isValidForecastArgs(request.params.arguments)) { throw new McpError( ErrorCode.InvalidParams, "Invalid forecast arguments" ); } const city = request.params.arguments.city; const days = Math.min(request.params.arguments.days || 3, 5); try { const response = await this.axiosInstance.get<{ list: OpenWeatherResponse[] }>(API_CONFIG.ENDPOINTS.FORECAST, { params: { q: city, cnt: days * 8 // API returns 3-hour intervals } }); const forecasts: ForecastDay[] = []; for (let i = 0; i < response.data.list.length; i += 8) { const dayData = response.data.list[i]; forecasts.push({ date: dayData.dt_txt?.split(' ')[0] ?? new Date().toISOString().split('T')[0], temperature: dayData.main.temp, conditions: dayData.weather[0].description }); } return { content: [{ type: "text", text: JSON.stringify(forecasts, null, 2) }] }; } catch (error) { if (axios.isAxiosError(error)) { return { content: [{ type: "text", text: `Weather API error: ${error.response?.data.message ?? error.message}` }], isError: true, } } throw error; } } ); } 5 Build and test npm run build ​ Connect to Claude Desktop 1 Update Claude config If you didn’t already connect to Claude Desktop during project setup, add to claude_desktop_config.json: { "mcpServers": { "weather": { "command": "node", "args": ["/path/to/weather-server/build/index.js"], "env": { "OPENWEATHER_API_KEY": "your-api-key", } } } } 2 Restart Claude Quit Claude completely Start Claude again Look for your weather server in the 🔌 menu ​ Try it out! Check Current Weather Get a Forecast Compare Weather ​ Understanding the code Type Safety Resources Tools interface WeatherData { temperature: number; conditions: string; humidity: number; wind_speed: number; timestamp: string; } TypeScript adds type safety to our MCP server, making it more reliable and easier to maintain. ​ Best practices Error Handling When a tool encounters an error, return the error message with isError: true, so the model can self-correct: try { const response = await axiosInstance.get(...); } catch (error) { if (axios.isAxiosError(error)) { return { content: { mimeType: "text/plain", text: `Weather API error: ${error.response?.data.message ?? error.message}` }, isError: true, } } throw error; } For other handlers, throw an error, so the application can notify the user: try { const response = await this.axiosInstance.get(...); } catch (error) { if (axios.isAxiosError(error)) { throw new McpError( ErrorCode.InternalError, `Weather API error: ${error.response?.data.message}` ); } throw error; } Type Validation function isValidForecastArgs(args: any): args is GetForecastArgs { return ( typeof args === "object" && args !== null && "city" in args && typeof args.city === "string" ); } You can also use libraries like Zod to perform this validation automatically. ​ Available transports While this guide uses stdio to run the MCP server as a local process, MCP supports other transports as well. ​ Troubleshooting The following troubleshooting tips are for macOS. Guides for other platforms are coming soon. ​ Build errors # Check TypeScript version npx tsc --version # Clean and rebuild rm -rf build/ npm run build ​ Runtime errors Look for detailed error messages in the Claude Desktop logs: # Monitor logs tail -n 20 -f ~/Library/Logs/Claude/mcp*.log ​ Type errors # Check types without building npx tsc --noEmit ​ Next steps Architecture overview Learn more about the MCP architecture TypeScript SDK Check out the TypeScript SDK on GitHub Need help? Ask Claude! Since it has access to the MCP SDK documentation, it can help you debug issues and suggest improvements to your server. 

EXAMPLE SERVER 1 PACKAGE 0.59 KB •26 lines • Formatting may be inconsistent from source { "name": "notion", "version": "0.1.0", "description": "A Model Context Protocol server", "private": true, "type": "module", "bin": { "notion": "./build/index.js" }, "files": [ "build" ], "scripts": { "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", "prepare": "npm run build", "watch": "tsc --watch", "inspector": "npx @modelcontextprotocol/inspector build/index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "0.6.0" }, "devDependencies": { "@types/node": "^20.11.24", "typescript": "^5.3.3" } } “””
EXAMPLE SERVER 1 18.97 KB •678 lines • Formatting may be inconsistent from source #!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequest, CallToolRequestSchema, ListToolsRequestSchema, Tool, } from "@modelcontextprotocol/sdk/types.js"; // Type definitions for tool arguments // Blocks interface AppendBlockChildrenArgs { block_id: string; children: any[]; } interface RetrieveBlockArgs { block_id: string; } interface RetrieveBlockChildrenArgs { block_id: string; start_cursor?: string; page_size?: number; } interface DeleteBlockArgs { block_id: string; } // Pages interface RetrievePageArgs { page_id: string; } interface UpdatePagePropertiesArgs { page_id: string; properties: any; } // Databases interface CreateDatabaseArgs { parent: any; title: any[]; properties: any; } interface QueryDatabaseArgs { database_id: string; filter?: any; sorts?: any; start_cursor?: string; page_size?: number; } interface RetrieveDatabaseArgs { database_id: string; } interface UpdateDatabaseArgs { database_id: string; title?: any[]; description?: any[]; properties?: any; } interface CreateDatabaseItemArgs { database_id: string; properties: any; } // Tool definitions // Blocks const appendBlockChildrenTool: Tool = { name: "notion_append_block_children", description: "Append blocks to a parent block in Notion", inputSchema: { type: "object", properties: { block_id: { type: "string", description: "The ID of the parent block. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).", }, children: { type: "array", description: "Array of block objects to append", }, }, required: ["block_id", "children"], }, }; const retrieveBlockTool: Tool = { name: "notion_retrieve_block", description: "Retrieve a block from Notion", inputSchema: { type: "object", properties: { block_id: { type: "string", description: "The ID of the block to retrieve. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).", }, }, required: ["block_id"], }, } const retrieveBlockChildrenTool: Tool = { name: "notion_retrieve_block_children", description: "Retrieve the children of a block", inputSchema: { type: "object", properties: { block_id: { type: "string", description: "The ID of the block. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).", }, start_cursor: { type: "string", description: "Pagination cursor for next page of results", }, page_size: { type: "number", description: "Number of results per page (max 100)", }, }, required: ["block_id"], }, }; const deleteBlockTool: Tool = { name: "notion_delete_block", description: "Delete a block in Notion", inputSchema: { type: "object", properties: { block_id: { type: "string", description: "The ID of the block to delete. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).", }, }, required: ["block_id"], }, }; // Pages const retrievePageTool: Tool = { name: "notion_retrieve_page", description: "Retrieve a page from Notion", inputSchema: { type: "object", properties: { page_id: { type: "string", description: "The ID of the page to retrieve. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).", }, }, required: ["page_id"], }, }; const updatePagePropertiesTool: Tool = { name: "notion_update_page_properties", description: "Update properties of a page or an item in a Notion database", inputSchema: { type: "object", properties: { page_id: { type: "string", description: "The ID of the page or database item to update. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).", }, properties: { type: "object", description: "Properties to update. These correspond to the columns or fields in the database.", }, }, required: ["page_id", "properties"], }, }; // Databases const createDatabaseTool: Tool = { name: "notion_create_database", description: "Create a database in Notion", inputSchema: { type: "object", properties: { parent: { type: "object", description: "Parent object of the database", }, title: { type: "array", description: "Title of database as it appears in Notion. An array of rich text objects.", }, properties: { type: "object", description: "Property schema of database. The keys are the names of properties as they appear in Notion and the values are property schema objects.", }, }, required: ["parent", "properties"], }, }; const queryDatabaseTool: Tool = { name: "notion_query_database", description: "Query a database in Notion", inputSchema: { type: "object", properties: { database_id: { type: "string", description: "The ID of the database to query. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).", }, filter: { type: "object", description: "Filter conditions", }, sorts: { type: "array", description: "Sort conditions", }, start_cursor: { type: "string", description: "Pagination cursor for next page of results", }, page_size: { type: "number", description: "Number of results per page (max 100)", }, }, required: ["database_id"], }, }; const retrieveDatabaseTool: Tool = { name: "notion_retrieve_database", description: "Retrieve a database in Notion", inputSchema: { type: "object", properties: { database_id: { type: "string", description: "The ID of the database to retrieve. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).", }, }, required: ["database_id"], }, }; const updateDatabaseTool: Tool = { name: "notion_update_database", description: "Update a database in Notion", inputSchema: { type: "object", properties: { database_id: { type: "string", description: "The ID of the database to update. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).", }, title: { type: "array", description: "An array of rich text objects that represents the title of the database that is displayed in the Notion UI.", }, description: { type: "array", description: "An array of rich text objects that represents the description of the database that is displayed in the Notion UI.", }, properties: { type: "object", description: "The properties of a database to be changed in the request, in the form of a JSON object.", }, }, required: ["database_id"], }, }; const createDatabaseItemTool: Tool = { name: "notion_create_database_item", description: "Create a new item (page) in a Notion database", inputSchema: { type: "object", properties: { database_id: { type: "string", description: "The ID of the database to add the item to. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).", }, properties: { type: "object", description: "Properties of the new database item. These should match the database schema.", }, }, required: ["database_id", "properties"], }, }; class NotionClientWrapper { private notionToken: string; private baseUrl: string = "https://api.notion.com/v1"; private headers: { [key: string]: string }; constructor(token: string) { this.notionToken = token; this.headers = { "Authorization": `Bearer ${this.notionToken}`, "Content-Type": "application/json", "Notion-Version": "2022-06-28", }; } async appendBlockChildren(block_id: string, children: any[]): Promise<any> { const body = { children }; const response = await fetch(`${this.baseUrl}/blocks/${block_id}/children`, { method: "PATCH", headers: this.headers, body: JSON.stringify(body), }); return response.json(); } async retrieveBlock(block_id: string): Promise<any> { const response = await fetch(`${this.baseUrl}/blocks/${block_id}`, { method: "GET", headers: this.headers, }); return response.json(); } async retrieveBlockChildren( block_id: string, start_cursor?: string, page_size?: number, ): Promise<any> { const params = new URLSearchParams(); if (start_cursor) params.append("start_cursor", start_cursor); if (page_size) params.append("page_size", page_size.toString()); const response = await fetch(`${this.baseUrl}/blocks/${block_id}/children?${params}`, { method: "GET", headers: this.headers, }); return response.json(); } async deleteBlock(block_id: string): Promise<any> { const response = await fetch(`${this.baseUrl}/blocks/${block_id}`, { method: "DELETE", headers: this.headers, }); return response.json(); } async retrievePage(page_id: string): Promise<any> { const response = await fetch(`${this.baseUrl}/pages/${page_id}`, { method: "GET", headers: this.headers, }); return response.json(); } async updatePageProperties(page_id: string, properties: any): Promise<any> { const body = { properties }; const response = await fetch(`${this.baseUrl}/pages/${page_id}`, { method: "PATCH", headers: this.headers, body: JSON.stringify(body), }); return response.json(); } async createDatabase(parent: any, title: any[], properties: any): Promise<any> { const body = { parent, title, properties }; const response = await fetch(`${this.baseUrl}/databases`, { method: "POST", headers: this.headers, body: JSON.stringify(body), }); return response.json(); } async queryDatabase( database_id: string, filter?: any, sorts?: any, start_cursor?: string, page_size?: number, ): Promise<any> { const body: any = {}; if (filter) body.filter = filter; if (sorts) body.sorts = sorts; if (start_cursor) body.start_cursor = start_cursor; if (page_size) body.page_size = page_size; const response = await fetch(`${this.baseUrl}/databases/${database_id}/query`, { method: "POST", headers: this.headers, body: JSON.stringify(body), }); return response.json(); } async retrieveDatabase(database_id: string): Promise<any> { const response = await fetch(`${this.baseUrl}/databases/${database_id}`, { method: "GET", headers: this.headers, }); return response.json(); } async updateDatabase(database_id: string, title?: any[], description?: any[], properties?: any): Promise<any> { const body: any = {}; if (title) body.title = title; if (description) body.description = description; if (properties) body.properties = properties; const response = await fetch(`${this.baseUrl}/databases/${database_id}`, { method: "PATCH", headers: this.headers, body: JSON.stringify(body), }); return response.json(); } async createDatabaseItem(database_id: string, properties: any): Promise<any> { const body = { parent: { database_id }, properties, }; const response = await fetch(`${this.baseUrl}/pages`, { method: "POST", headers: this.headers, body: JSON.stringify(body), }); return response.json(); } } async function main() { const notionToken = process.env.NOTION_API_TOKEN; if (!notionToken) { console.error("Please set NOTION_API_TOKEN environment variable"); process.exit(1); } console.error("Starting Notion MCP Server..."); const server = new Server( { name: "Notion MCP Server", version: "1.0.0", }, { capabilities: { tools: {}, }, }, ); const notionClient = new NotionClientWrapper(notionToken); server.setRequestHandler( CallToolRequestSchema, async (request: CallToolRequest) => { console.error("Received CallToolRequest:", request); try { if (!request.params.arguments) { throw new Error("No arguments provided"); } switch (request.params.name) { case "notion_append_block_children": { const args = request.params.arguments as unknown as AppendBlockChildrenArgs; if (!args.block_id || !args.children) { throw new Error( "Missing required arguments: block_id and children", ); } const response = await notionClient.appendBlockChildren( args.block_id, args.children, ); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "notion_retrieve_block": { const args = request.params.arguments as unknown as RetrieveBlockArgs; if (!args.block_id) { throw new Error("Missing required argument: block_id"); } const response = await notionClient.retrieveBlock(args.block_id); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "notion_retrieve_block_children": { const args = request.params .arguments as unknown as RetrieveBlockChildrenArgs; if (!args.block_id) { throw new Error("Missing required argument: block_id"); } const response = await notionClient.retrieveBlockChildren( args.block_id, args.start_cursor, args.page_size, ); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "notion_delete_block": { const args = request.params.arguments as unknown as DeleteBlockArgs; if (!args.block_id) { throw new Error("Missing required argument: block_id"); } const response = await notionClient.deleteBlock(args.block_id); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "notion_retrieve_page": { const args = request.params.arguments as unknown as RetrievePageArgs; if (!args.page_id) { throw new Error("Missing required argument: page_id"); } const response = await notionClient.retrievePage(args.page_id); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "notion_update_page_properties": { const args = request.params.arguments as unknown as UpdatePagePropertiesArgs; if (!args.page_id || !args.properties) { throw new Error( "Missing required arguments: page_id and properties", ); } const response = await notionClient.updatePageProperties( args.page_id, args.properties, ); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "notion_query_database": { const args = request.params .arguments as unknown as QueryDatabaseArgs; if (!args.database_id) { throw new Error("Missing required argument: database_id"); } const response = await notionClient.queryDatabase( args.database_id, args.filter, args.sorts, args.start_cursor, args.page_size, ); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "notion_create_database": { const args = request.params.arguments as unknown as CreateDatabaseArgs; const response = await notionClient.createDatabase( args.parent, args.title, args.properties, ); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "notion_retrieve_database": { const args = request.params.arguments as unknown as RetrieveDatabaseArgs; const response = await notionClient.retrieveDatabase(args.database_id); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "notion_update_database": { const args = request.params.arguments as unknown as UpdateDatabaseArgs; const response = await notionClient.updateDatabase( args.database_id, args.title, args.description, args.properties, ); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "notion_create_database_item": { const args = request.params.arguments as unknown as CreateDatabaseItemArgs; const response = await notionClient.createDatabaseItem( args.database_id, args.properties, ); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } default: throw new Error(`Unknown tool: ${request.params.name}`); } } catch (error) { console.error("Error executing tool:", error); return { content: [ { type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error), }), }, ], }; } }, ); server.setRequestHandler(ListToolsRequestSchema, async () => { console.error("Received ListToolsRequest"); return { tools: [ appendBlockChildrenTool, retrieveBlockTool, retrieveBlockChildrenTool, deleteBlockTool, retrievePageTool, updatePagePropertiesTool, createDatabaseTool, queryDatabaseTool, retrieveDatabaseTool, updateDatabaseTool, createDatabaseItemTool, ], }; }); const transport = new StdioServerTransport(); console.error("Connecting server to transport..."); await server.connect(transport); console.error("Notion MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); “””
EXAMPLE SERVER 2 9.81 KB •390 lines • Formatting may be inconsistent from source #!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, CallToolResult, TextContent, ImageContent, Tool, } from "@modelcontextprotocol/sdk/types.js"; import puppeteer, { Browser, Page } from "puppeteer"; // Define the tools once to avoid repetition const TOOLS: Tool[] = [ { name: "puppeteer_navigate", description: "Navigate to a URL", inputSchema: { type: "object", properties: { url: { type: "string" }, }, required: ["url"], }, }, { name: "puppeteer_screenshot", description: "Take a screenshot of the current page or a specific element", inputSchema: { type: "object", properties: { name: { type: "string", description: "Name for the screenshot" }, selector: { type: "string", description: "CSS selector for element to screenshot" }, width: { type: "number", description: "Width in pixels (default: 800)" }, height: { type: "number", description: "Height in pixels (default: 600)" }, }, required: ["name"], }, }, { name: "puppeteer_click", description: "Click an element on the page", inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector for element to click" }, }, required: ["selector"], }, }, { name: "puppeteer_fill", description: "Fill out an input field", inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector for input field" }, value: { type: "string", description: "Value to fill" }, }, required: ["selector", "value"], }, }, { name: "puppeteer_select", description: "Select an element on the page with Select tag", inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector for element to select" }, value: { type: "string", description: "Value to select" }, }, required: ["selector", "value"], }, }, { name: "puppeteer_hover", description: "Hover an element on the page", inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector for element to hover" }, }, required: ["selector"], }, }, { name: "puppeteer_evaluate", description: "Execute JavaScript in the browser console", inputSchema: { type: "object", properties: { script: { type: "string", description: "JavaScript code to execute" }, }, required: ["script"], }, }, ]; // Global state let browser: Browser | undefined; let page: Page | undefined; const consoleLogs: string[] = []; const screenshots = new Map<string, string>(); async function ensureBrowser() { if (!browser) { browser = await puppeteer.launch({ headless: false }); const pages = await browser.pages(); page = pages[0]; page.on("console", (msg) => { const logEntry = `[${msg.type()}] ${msg.text()}`; consoleLogs.push(logEntry); server.notification({ method: "notifications/resources/updated", params: { uri: "console://logs" }, }); }); } return page!; } async function handleToolCall(name: string, args: any): Promise<CallToolResult> { const page = await ensureBrowser(); switch (name) { case "puppeteer_navigate": await page.goto(args.url); return { content: [{ type: "text", text: `Navigated to ${args.url}`, }], isError: false, }; case "puppeteer_screenshot": { const width = args.width ?? 800; const height = args.height ?? 600; await page.setViewport({ width, height }); const screenshot = await (args.selector ? (await page.$(args.selector))?.screenshot({ encoding: "base64" }) : page.screenshot({ encoding: "base64", fullPage: false })); if (!screenshot) { return { content: [{ type: "text", text: args.selector ? `Element not found: ${args.selector}` : "Screenshot failed", }], isError: true, }; } screenshots.set(args.name, screenshot as string); server.notification({ method: "notifications/resources/list_changed", }); return { content: [ { type: "text", text: `Screenshot '${args.name}' taken at ${width}x${height}`, } as TextContent, { type: "image", data: screenshot, mimeType: "image/png", } as ImageContent, ], isError: false, }; } case "puppeteer_click": try { await page.click(args.selector); return { content: [{ type: "text", text: `Clicked: ${args.selector}`, }], isError: false, }; } catch (error) { return { content: [{ type: "text", text: `Failed to click ${args.selector}: ${(error as Error).message}`, }], isError: true, }; } case "puppeteer_fill": try { await page.waitForSelector(args.selector); await page.type(args.selector, args.value); return { content: [{ type: "text", text: `Filled ${args.selector} with: ${args.value}`, }], isError: false, }; } catch (error) { return { content: [{ type: "text", text: `Failed to fill ${args.selector}: ${(error as Error).message}`, }], isError: true, }; } case "puppeteer_select": try { await page.waitForSelector(args.selector); await page.select(args.selector, args.value); return { content: [{ type: "text", text: `Selected ${args.selector} with: ${args.value}`, }], isError: false, }; } catch (error) { return { content: [{ type: "text", text: `Failed to select ${args.selector}: ${(error as Error).message}`, }], isError: true, }; } case "puppeteer_hover": try { await page.waitForSelector(args.selector); await page.hover(args.selector); return { content: [{ type: "text", text: `Hovered ${args.selector}`, }], isError: false, }; } catch (error) { return { content: [{ type: "text", text: `Failed to hover ${args.selector}: ${(error as Error).message}`, }], isError: true, }; } case "puppeteer_evaluate": try { const result = await page.evaluate((script) => { const logs: string[] = []; const originalConsole = { ...console }; ['log', 'info', 'warn', 'error'].forEach(method => { (console as any)[method] = (...args: any[]) => { logs.push(`[${method}] ${args.join(' ')}`); (originalConsole as any)[method](...args); }; }); try { const result = eval(script); Object.assign(console, originalConsole); return { result, logs }; } catch (error) { Object.assign(console, originalConsole); throw error; } }, args.script); return { content: [ { type: "text", text: `Execution result:\n${JSON.stringify(result.result, null, 2)}\n\nConsole output:\n${result.logs.join('\n')}`, }, ], isError: false, }; } catch (error) { return { content: [{ type: "text", text: `Script execution failed: ${(error as Error).message}`, }], isError: true, }; } default: return { content: [{ type: "text", text: `Unknown tool: ${name}`, }], isError: true, }; } } const server = new Server( { name: "example-servers/puppeteer", version: "0.1.0", }, { capabilities: { resources: {}, tools: {}, }, }, ); // Setup request handlers server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [ { uri: "console://logs", mimeType: "text/plain", name: "Browser console logs", }, ...Array.from(screenshots.keys()).map(name => ({ uri: `screenshot://${name}`, mimeType: "image/png", name: `Screenshot: ${name}`, })), ], })); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri.toString(); if (uri === "console://logs") { return { contents: [{ uri, mimeType: "text/plain", text: consoleLogs.join("\n"), }], }; } if (uri.startsWith("screenshot://")) { const name = uri.split("://")[1]; const screenshot = screenshots.get(name); if (screenshot) { return { contents: [{ uri, mimeType: "image/png", blob: screenshot, }], }; } } throw new Error(`Resource not found: ${uri}`); }); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS, })); server.setRequestHandler(CallToolRequestSchema, async (request) => handleToolCall(request.params.name, request.params.arguments ?? {}) ); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); } runServer().catch(console.error); “””
DEBUGGING 4.75 KB •239 lines • Formatting may be inconsistent from source Development Tools Debugging A comprehensive guide to debugging Model Context Protocol (MCP) integrations Effective debugging is essential when developing MCP servers or integrating them with applications. This guide covers the debugging tools and approaches available in the MCP ecosystem. This guide is for macOS. Guides for other platforms are coming soon. ​ Debugging tools overview MCP provides several tools for debugging at different levels: MCP Inspector Interactive debugging interface Direct server testing See the Inspector guide for details Claude Desktop Developer Tools Integration testing Log collection Chrome DevTools integration Server Logging Custom logging implementations Error tracking Performance monitoring ​ Debugging in Claude Desktop ​ Checking server status The Claude.app interface provides basic server status information: Click the 🔌 icon to view: Connected servers Available prompts and resources Click the 🔨 icon to view: Tools made available to the model ​ Viewing logs Review detailed MCP logs from Claude Desktop: # Follow logs in real-time tail -n 20 -f ~/Library/Logs/Claude/mcp*.log The logs capture: Server connection events Configuration issues Runtime errors Message exchanges ​ Using Chrome DevTools Access Chrome’s developer tools inside Claude Desktop to investigate client-side errors: Enable DevTools: jq '.allowDevTools = true' ~/Library/Application\ Support/Claude/developer_settings.json > tmp.json \ && mv tmp.json ~/Library/Application\ Support/Claude/developer_settings.json Open DevTools: Command-Option-Shift-i Note: You’ll see two DevTools windows: Main content window App title bar window Use the Console panel to inspect client-side errors. Use the Network panel to inspect: Message payloads Connection timing ​ Common issues ​ Environment variables MCP servers inherit only a subset of environment variables automatically, like USER, HOME, and PATH. To override the default variables or provide your own, you can specify an env key in claude_desktop_config.json: { "myserver": { "command": "mcp-server-myapp", "env": { "MYAPP_API_KEY": "some_key", } } } ​ Server initialization Common initialization problems: Path Issues Incorrect server executable path Missing required files Permission problems Configuration Errors Invalid JSON syntax Missing required fields Type mismatches Environment Problems Missing environment variables Incorrect variable values Permission restrictions ​ Connection problems When servers fail to connect: Check Claude Desktop logs Verify server process is running Test standalone with Inspector Verify protocol compatibility ​ Implementing logging ​ Server-side logging When building a server that uses the local stdio transport, all messages logged to stderr (standard error) will be captured by the host application (e.g., Claude Desktop) automatically. Local MCP servers should not log messages to stdout (standard out), as this will interfere with protocol operation. For all transports, you can also provide logging to the client by sending a log message notification: Python TypeScript server.request_context.session.send_log_message( level="info", data="Server started successfully", ) Important events to log: Initialization steps Resource access Tool execution Error conditions Performance metrics ​ Client-side logging In client applications: Enable debug logging Monitor network traffic Track message exchanges Record error states ​ Debugging workflow ​ Development cycle Initial Development Use Inspector for basic testing Implement core functionality Add logging points Integration Testing Test in Claude Desktop Monitor logs Check error handling ​ Testing changes To test changes efficiently: Configuration changes: Restart Claude Desktop Server code changes: Use Command-R to reload Quick iteration: Use Inspector during development ​ Best practices ​ Logging strategy Structured Logging Use consistent formats Include context Add timestamps Track request IDs Error Handling Log stack traces Include error context Track error patterns Monitor recovery Performance Tracking Log operation timing Monitor resource usage Track message sizes Measure latency ​ Security considerations When debugging: Sensitive Data Sanitize logs Protect credentials Mask personal information Access Control Verify permissions Check authentication Monitor access patterns ​ Getting help When encountering issues: First Steps Check server logs Test with Inspector Review configuration Verify environment Support Channels GitHub issues GitHub discussions Providing Information Log excerpts Configuration files Steps to reproduce Environment details ​ Next steps MCP Inspector Learn to use the MCP Inspector Was this page helpful? Yes No TypeScript Inspector github “””
Concepts Resources 5.73 KB •209 lines • Formatting may be inconsistent from source Concepts Resources Expose data and content from your servers to LLMs Resources are a core primitive in the Model Context Protocol (MCP) that allow servers to expose data and content that can be read by clients and used as context for LLM interactions. Resources are designed to be application-controlled, meaning that the client application can decide how and when they should be used. Different MCP clients may handle resources differently. For example: Claude Desktop currently requires users to explicitly select resources before they can be used Other clients might automatically select resources based on heuristics Some implementations may even allow the AI model itself to determine which resources to use Server authors should be prepared to handle any of these interaction patterns when implementing resource support. In order to expose data to models automatically, server authors should use a model-controlled primitive such as Tools. ​ Overview Resources represent any kind of data that an MCP server wants to make available to clients. This can include: File contents Database records API responses Live system data Screenshots and images Log files And more Each resource is identified by a unique URI and can contain either text or binary data. ​ Resource URIs Resources are identified using URIs that follow this format: [protocol]://[host]/[path] For example: file:///home/user/documents/report.pdf postgres://database/customers/schema screen://localhost/display1 The protocol and path structure is defined by the MCP server implementation. Servers can define their own custom URI schemes. ​ Resource types Resources can contain two types of content: ​ Text resources Text resources contain UTF-8 encoded text data. These are suitable for: Source code Configuration files Log files JSON/XML data Plain text ​ Binary resources Binary resources contain raw binary data encoded in base64. These are suitable for: Images PDFs Audio files Video files Other non-text formats ​ Resource discovery Clients can discover available resources through two main methods: ​ Direct resources Servers expose a list of concrete resources via the resources/list endpoint. Each resource includes: { uri: string; // Unique identifier for the resource name: string; // Human-readable name description?: string; // Optional description mimeType?: string; // Optional MIME type } ​ Resource templates For dynamic resources, servers can expose URI templates that clients can use to construct valid resource URIs: { uriTemplate: string; // URI template following RFC 6570 name: string; // Human-readable name for this type description?: string; // Optional description mimeType?: string; // Optional MIME type for all matching resources } ​ Reading resources To read a resource, clients make a resources/read request with the resource URI. The server responds with a list of resource contents: { contents: [ { uri: string; // The URI of the resource mimeType?: string; // Optional MIME type // One of: text?: string; // For text resources blob?: string; // For binary resources (base64 encoded) } ] } Servers may return multiple resources in response to one resources/read request. This could be used, for example, to return a list of files inside a directory when the directory is read. ​ Resource updates MCP supports real-time updates for resources through two mechanisms: ​ List changes Servers can notify clients when their list of available resources changes via the notifications/resources/list_changed notification. ​ Content changes Clients can subscribe to updates for specific resources: Client sends resources/subscribe with resource URI Server sends notifications/resources/updated when the resource changes Client can fetch latest content with resources/read Client can unsubscribe with resources/unsubscribe ​ Example implementation Here’s a simple example of implementing resource support in an MCP server: TypeScript Python const server = new Server({ name: "example-server", version: "1.0.0" }, { capabilities: { resources: {} } }); // List available resources server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: "file:///logs/app.log", name: "Application Logs", mimeType: "text/plain" } ] }; }); // Read resource contents server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; if (uri === "file:///logs/app.log") { const logContents = await readLogFile(); return { contents: [ { uri, mimeType: "text/plain", text: logContents } ] }; } throw new Error("Resource not found"); }); ​ Best practices When implementing resource support: Use clear, descriptive resource names and URIs Include helpful descriptions to guide LLM understanding Set appropriate MIME types when known Implement resource templates for dynamic content Use subscriptions for frequently changing resources Handle errors gracefully with clear error messages Consider pagination for large resource lists Cache resource contents when appropriate Validate URIs before processing Document your custom URI schemes ​ Security considerations When exposing resources: Validate all resource URIs Implement appropriate access controls Sanitize file paths to prevent directory traversal Be cautious with binary data handling Consider rate limiting for resource reads Audit resource access Encrypt sensitive data in transit Validate MIME types Implement timeouts for long-running reads Handle resource cleanup appropriately Was this page helpful? Yes No Core architecture Prompts github “””
modelcontextprotocol / typescript-sdk 4.58 KB •236 lines • Formatting may be inconsistent from source Skip to content Navigation Menu modelcontextprotocol / typescript-sdk Type / to search Code Issues 7 Pull requests 1 Actions Security Insights Owner avatar typescript-sdk Public modelcontextprotocol/typescript-sdk Go to file t Add file Folders and files Name Latest commit jspahrsummers jspahrsummers Merge pull request #84 from modelcontextprotocol/revert-83-justin/rem… 989550d · 4 hours ago History .github/workflows Restrict publishing to 'release' environment 3 weeks ago src Revert "Remove CompatibilityCallToolResult" 4 hours ago .gitattributes Ignore package-lock.json in diffs 2 months ago .gitignore Don't commit 'dist' anymore 2 months ago .npmrc Add npmrc to always point to npm for public packages 2 months ago CODE_OF_CONDUCT.md Add code of conduct 2 weeks ago CONTRIBUTING.md Add CONTRIBUTING.md 2 weeks ago LICENSE Update LICENSE 2 weeks ago README.md Fix imports last week SECURITY.md Update SECURITY.md 2 weeks ago eslint.config.mjs Initial import 3 months ago jest.config.js Initial import 3 months ago package-lock.json 1.0.0 last week package.json Bump to 1.0.3 4 hours ago tsconfig.json Initial import 3 months ago Repository files navigation README Code of conduct MIT license Security MCP TypeScript SDK NPM Version TypeScript implementation of the Model Context Protocol (MCP), providing both client and server capabilities for integrating with LLM surfaces. Overview The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to: Build MCP clients that can connect to any MCP server Create MCP servers that expose resources, prompts and tools Use standard transports like stdio and SSE Handle all MCP protocol messages and lifecycle events Installation npm install @modelcontextprotocol/sdk Quick Start Creating a Client import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; const transport = new StdioClientTransport({ command: "path/to/server", }); const client = new Client({ name: "example-client", version: "1.0.0", }, { capabilities: {} }); await client.connect(transport); // List available resources const resources = await client.request( { method: "resources/list" }, ListResourcesResultSchema ); // Read a specific resource const resourceContent = await client.request( { method: "resources/read", params: { uri: "file:///example.txt" } }, ReadResourceResultSchema ); Creating a Server import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; const server = new Server({ name: "example-server", version: "1.0.0", }, { capabilities: { resources: {} } }); server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: "file:///example.txt", name: "Example Resource", }, ], }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { if (request.params.uri === "file:///example.txt") { return { contents: [ { uri: "file:///example.txt", mimeType: "text/plain", text: "This is the content of the example resource.", }, ], }; } else { throw new Error("Resource not found"); } }); const transport = new StdioServerTransport(); await server.connect(transport); Documentation Model Context Protocol documentation MCP Specification Example Servers Contributing Issues and pull requests are welcome on GitHub at https://github.com/modelcontextprotocol/typescript-sdk. License This project is licensed under the MIT License—see the LICENSE file for details. About The official Typescript SDK for Model Context Protocol servers and clients modelcontextprotocol.io Resources Readme License MIT license Code of conduct Code of conduct Security policy Security policy Activity Custom properties Stars 577 stars Watchers 22 watching Forks 41 forks Report repository Releases 13 1.0.3 Latest 4 hours ago + 12 releases Contributors 5 @jspahrsummers @dsp-ant @anaisbetts @ashwin-ant @efritz Deployments 9 release 4 hours ago + 8 deployments Languages TypeScript 99.4% """
HOW TO - MCP Server TypeScript 11.01 KB •508 lines • Formatting may be inconsistent from source Your First MCP Server TypeScript Create a simple MCP server in TypeScript in 15 minutes Let’s build your first MCP server in TypeScript! We’ll create a weather server that provides current weather data as a resource and lets Claude fetch forecasts using tools. This guide uses the OpenWeatherMap API. You’ll need a free API key from OpenWeatherMap to follow along. ​ Prerequisites 1 Install Node.js You’ll need Node.js 18 or higher: node --version # Should be v18 or higher npm --version 2 Create a new project You can use our create-typescript-server tool to bootstrap a new project: npx @modelcontextprotocol/create-server weather-server cd weather-server 3 Install dependencies npm install --save axios dotenv 4 Set up environment Create .env: OPENWEATHER_API_KEY=your-api-key-here Make sure to add your environment file to .gitignore .env ​ Create your server 1 Define types Create a file src/types.ts, and add the following: export interface OpenWeatherResponse { main: { temp: number; humidity: number; }; weather: Array<{ description: string; }>; wind: { speed: number; }; dt_txt?: string; } export interface WeatherData { temperature: number; conditions: string; humidity: number; wind_speed: number; timestamp: string; } export interface ForecastDay { date: string; temperature: number; conditions: string; } export interface GetForecastArgs { city: string; days?: number; } // Type guard for forecast arguments export function isValidForecastArgs(args: any): args is GetForecastArgs { return ( typeof args === "object" && args !== null && "city" in args && typeof args.city === "string" && (args.days === undefined || typeof args.days === "number") ); } 2 Add the base code Replace src/index.ts with the following: #!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js"; import axios from "axios"; import dotenv from "dotenv"; import { WeatherData, ForecastDay, OpenWeatherResponse, isValidForecastArgs } from "./types.js"; dotenv.config(); const API_KEY = process.env.OPENWEATHER_API_KEY; if (!API_KEY) { throw new Error("OPENWEATHER_API_KEY environment variable is required"); } const API_CONFIG = { BASE_URL: 'http://api.openweathermap.org/data/2.5', DEFAULT_CITY: 'San Francisco', ENDPOINTS: { CURRENT: 'weather', FORECAST: 'forecast' } } as const; class WeatherServer { private server: Server; private axiosInstance; constructor() { this.server = new Server({ name: "example-weather-server", version: "0.1.0" }, { capabilities: { resources: {}, tools: {} } }); // Configure axios with defaults this.axiosInstance = axios.create({ baseURL: API_CONFIG.BASE_URL, params: { appid: API_KEY, units: "metric" } }); this.setupHandlers(); this.setupErrorHandling(); } private setupErrorHandling(): void { this.server.onerror = (error) => { console.error("[MCP Error]", error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupHandlers(): void { this.setupResourceHandlers(); this.setupToolHandlers(); } private setupResourceHandlers(): void { // Implementation continues in next section } private setupToolHandlers(): void { // Implementation continues in next section } async run(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); // Although this is just an informative message, we must log to stderr, // to avoid interfering with MCP communication that happens on stdout console.error("Weather MCP server running on stdio"); } } const server = new WeatherServer(); server.run().catch(console.error); 3 Add resource handlers Add this to the setupResourceHandlers method: private setupResourceHandlers(): void { this.server.setRequestHandler( ListResourcesRequestSchema, async () => ({ resources: [{ uri: `weather://${API_CONFIG.DEFAULT_CITY}/current`, name: `Current weather in ${API_CONFIG.DEFAULT_CITY}`, mimeType: "application/json", description: "Real-time weather data including temperature, conditions, humidity, and wind speed" }] }) ); this.server.setRequestHandler( ReadResourceRequestSchema, async (request) => { const city = API_CONFIG.DEFAULT_CITY; if (request.params.uri !== `weather://${city}/current`) { throw new McpError( ErrorCode.InvalidRequest, `Unknown resource: ${request.params.uri}` ); } try { const response = await this.axiosInstance.get<OpenWeatherResponse>( API_CONFIG.ENDPOINTS.CURRENT, { params: { q: city } } ); const weatherData: WeatherData = { temperature: response.data.main.temp, conditions: response.data.weather[0].description, humidity: response.data.main.humidity, wind_speed: response.data.wind.speed, timestamp: new Date().toISOString() }; return { contents: [{ uri: request.params.uri, mimeType: "application/json", text: JSON.stringify(weatherData, null, 2) }] }; } catch (error) { if (axios.isAxiosError(error)) { throw new McpError( ErrorCode.InternalError, `Weather API error: ${error.response?.data.message ?? error.message}` ); } throw error; } } ); } 4 Add tool handlers Add these handlers to the setupToolHandlers method: private setupToolHandlers(): void { this.server.setRequestHandler( ListToolsRequestSchema, async () => ({ tools: [{ name: "get_forecast", description: "Get weather forecast for a city", inputSchema: { type: "object", properties: { city: { type: "string", description: "City name" }, days: { type: "number", description: "Number of days (1-5)", minimum: 1, maximum: 5 } }, required: ["city"] } }] }) ); this.server.setRequestHandler( CallToolRequestSchema, async (request) => { if (request.params.name !== "get_forecast") { throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } if (!isValidForecastArgs(request.params.arguments)) { throw new McpError( ErrorCode.InvalidParams, "Invalid forecast arguments" ); } const city = request.params.arguments.city; const days = Math.min(request.params.arguments.days || 3, 5); try { const response = await this.axiosInstance.get<{ list: OpenWeatherResponse[] }>(API_CONFIG.ENDPOINTS.FORECAST, { params: { q: city, cnt: days * 8 // API returns 3-hour intervals } }); const forecasts: ForecastDay[] = []; for (let i = 0; i < response.data.list.length; i += 8) { const dayData = response.data.list[i]; forecasts.push({ date: dayData.dt_txt?.split(' ')[0] ?? new Date().toISOString().split('T')[0], temperature: dayData.main.temp, conditions: dayData.weather[0].description }); } return { content: [{ type: "text", text: JSON.stringify(forecasts, null, 2) }] }; } catch (error) { if (axios.isAxiosError(error)) { return { content: [{ type: "text", text: `Weather API error: ${error.response?.data.message ?? error.message}` }], isError: true, } } throw error; } } ); } 5 Build and test npm run build ​ Connect to Claude Desktop 1 Update Claude config If you didn’t already connect to Claude Desktop during project setup, add to claude_desktop_config.json: { "mcpServers": { "weather": { "command": "node", "args": ["/path/to/weather-server/build/index.js"], "env": { "OPENWEATHER_API_KEY": "your-api-key", } } } } 2 Restart Claude Quit Claude completely Start Claude again Look for your weather server in the 🔌 menu ​ Try it out! Check Current Weather Get a Forecast Compare Weather ​ Understanding the code Type Safety Resources Tools interface WeatherData { temperature: number; conditions: string; humidity: number; wind_speed: number; timestamp: string; } TypeScript adds type safety to our MCP server, making it more reliable and easier to maintain. ​ Best practices Error Handling When a tool encounters an error, return the error message with isError: true, so the model can self-correct: try { const response = await axiosInstance.get(...); } catch (error) { if (axios.isAxiosError(error)) { return { content: { mimeType: "text/plain", text: `Weather API error: ${error.response?.data.message ?? error.message}` }, isError: true, } } throw error; } For other handlers, throw an error, so the application can notify the user: try { const response = await this.axiosInstance.get(...); } catch (error) { if (axios.isAxiosError(error)) { throw new McpError( ErrorCode.InternalError, `Weather API error: ${error.response?.data.message}` ); } throw error; } Type Validation function isValidForecastArgs(args: any): args is GetForecastArgs { return ( typeof args === "object" && args !== null && "city" in args && typeof args.city === "string" ); } You can also use libraries like Zod to perform this validation automatically. ​ Available transports While this guide uses stdio to run the MCP server as a local process, MCP supports other transports as well. ​ Troubleshooting The following troubleshooting tips are for macOS. Guides for other platforms are coming soon. ​ Build errors # Check TypeScript version npx tsc --version # Clean and rebuild rm -rf build/ npm run build ​ Runtime errors Look for detailed error messages in the Claude Desktop logs: # Monitor logs tail -n 20 -f ~/Library/Logs/Claude/mcp*.log ​ Type errors # Check types without building npx tsc --noEmit ​ Next steps Architecture overview Learn more about the MCP architecture TypeScript SDK Check out the TypeScript SDK on GitHub Need help? Ask Claude! Since it has access to the MCP SDK documentation, it can help you debug issues and suggest improvements to your server.