From 01e99897c4adaedd485ae21c4b606df8e9a94600 Mon Sep 17 00:00:00 2001 From: nexon33 Date: Fri, 16 May 2025 19:59:31 +0200 Subject: [PATCH 1/6] Refactor weather server example implementation inside of prompt to use new MCP SDK features --- .../prompts/instructions/create-mcp-server.ts | 397 +++++++----------- 1 file changed, 142 insertions(+), 255 deletions(-) diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index 71982528ef..839e281ad5 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -64,7 +64,7 @@ cd ${await mcpHub.getMcpServersPath()} npx @modelcontextprotocol/create-server weather-server cd weather-server # Install dependencies -npm install axios +npm install axios zod \`\`\` This will create a new project with the following structure: @@ -91,263 +91,148 @@ weather-server/ \`\`\`typescript #!/usr/bin/env node -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { - CallToolRequestSchema, - ErrorCode, - ListResourcesRequestSchema, - ListResourceTemplatesRequestSchema, - ListToolsRequestSchema, - McpError, - ReadResourceRequestSchema, -} from '@modelcontextprotocol/sdk/types.js'; +import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; import axios from 'axios'; const API_KEY = process.env.OPENWEATHER_API_KEY; // provided by MCP config if (!API_KEY) { - throw new Error('OPENWEATHER_API_KEY environment variable is required'); + throw new Error('OPENWEATHER_API_KEY environment variable is required'); } -interface OpenWeatherResponse { - main: { - temp: number; - humidity: number; - }; - weather: [{ description: string }]; - wind: { speed: number }; - dt_txt?: string; -} - -const isValidForecastArgs = ( - args: any -): args is { city: string; days?: number } => - typeof args === 'object' && - args !== null && - typeof args.city === 'string' && - (args.days === undefined || typeof args.days === 'number'); - -class WeatherServer { - private server: Server; - private axiosInstance; - - constructor() { - this.server = new Server( - { - name: 'example-weather-server', - version: '0.1.0', - }, - { - capabilities: { - resources: {}, - tools: {}, - }, - } - ); - - this.axiosInstance = axios.create({ - baseURL: 'http://api.openweathermap.org/data/2.5', - params: { - appid: API_KEY, - units: 'metric', - }, - }); - - this.setupResourceHandlers(); - this.setupToolHandlers(); - - // Error handling - this.server.onerror = (error) => console.error('[MCP Error]', error); - process.on('SIGINT', async () => { - await this.server.close(); - process.exit(0); - }); - } - - // MCP Resources represent any kind of UTF-8 encoded data that an MCP server wants to make available to clients, such as database records, API responses, log files, and more. Servers define direct resources with a static URI or dynamic resources with a URI template that follows the format \`[protocol]://[host]/[path]\`. - private setupResourceHandlers() { - // For static resources, servers can expose a list of resources: - this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ - resources: [ - // This is a poor example since you could use the resource template to get the same information but this demonstrates how to define a static resource - { - uri: \`weather://San Francisco/current\`, // Unique identifier for San Francisco weather resource - name: \`Current weather in San Francisco\`, // Human-readable name - mimeType: 'application/json', // Optional MIME type - // Optional description - description: - 'Real-time weather data for San Francisco including temperature, conditions, humidity, and wind speed', - }, - ], - })); - - // For dynamic resources, servers can expose resource templates: - this.server.setRequestHandler( - ListResourceTemplatesRequestSchema, - async () => ({ - resourceTemplates: [ - { - uriTemplate: 'weather://{city}/current', // URI template (RFC 6570) - name: 'Current weather for a given city', // Human-readable name - mimeType: 'application/json', // Optional MIME type - description: 'Real-time weather data for a specified city', // Optional description - }, - ], - }) - ); - - // ReadResourceRequestSchema is used for both static resources and dynamic resource templates - this.server.setRequestHandler( - ReadResourceRequestSchema, - async (request) => { - const match = request.params.uri.match( - /^weather:\/\/([^/]+)\/current$/ - ); - if (!match) { - throw new McpError( - ErrorCode.InvalidRequest, - \`Invalid URI format: \${request.params.uri}\` - ); - } - const city = decodeURIComponent(match[1]); - - try { - const response = await this.axiosInstance.get( - 'weather', // current weather - { - params: { q: city }, - } - ); - - return { - contents: [ - { - uri: request.params.uri, - mimeType: 'application/json', - text: JSON.stringify( - { - 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(), - }, - null, - 2 - ), - }, - ], - }; - } catch (error) { - if (axios.isAxiosError(error)) { - throw new McpError( - ErrorCode.InternalError, - \`Weather API error: \${ - error.response?.data.message ?? error.message - }\` - ); - } - throw error; - } - } - ); - } - - /* MCP Tools enable servers to expose executable functionality to the system. Through these tools, you can interact with external systems, perform computations, and take actions in the real world. - * - Like resources, tools are identified by unique names and can include descriptions to guide their usage. However, unlike resources, tools represent dynamic operations that can modify state or interact with external systems. - * - While resources and tools are similar, you should prefer to create tools over resources when possible as they provide more flexibility. - */ - private setupToolHandlers() { - this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: [ - { - name: 'get_forecast', // Unique identifier - description: 'Get weather forecast for a city', // Human-readable description - inputSchema: { - // JSON Schema for parameters - type: 'object', - properties: { - city: { - type: 'string', - description: 'City name', - }, - days: { - type: 'number', - description: 'Number of days (1-5)', - minimum: 1, - maximum: 5, - }, - }, - required: ['city'], // Array of required property names - }, - }, - ], - })); - - 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[]; - }>('forecast', { - params: { - q: city, - cnt: days * 8, - }, - }); - - return { - content: [ - { - type: 'text', - text: JSON.stringify(response.data.list, 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; - } - }); - } - - async run() { - const transport = new StdioServerTransport(); - await this.server.connect(transport); - console.error('Weather MCP server running on stdio'); - } -} - -const server = new WeatherServer(); -server.run().catch(console.error); +// Create an MCP server +const server = new McpServer({ + name: "weather-server", + version: "0.1.0" +}); + +// Create axios instance for OpenWeather API +const weatherApi = axios.create({ + baseURL: 'http://api.openweathermap.org/data/2.5', + params: { + appid: API_KEY, + units: 'metric', + }, +}); + +// Add a tool for getting weather forecasts +server.tool( + "get_forecast", + { + city: z.string().describe("City name"), + days: z.number().min(1).max(5).optional().describe("Number of days (1-5)"), + }, + async ({ city, days = 3 }) => { + try { + const response = await weatherApi.get('forecast', { + params: { + q: city, + cnt: Math.min(days, 5) * 8, + }, + }); + + return { + content: [ + { + type: "text", + text: JSON.stringify(response.data.list, 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; + } + } +); + +// Add a resource for current weather in San Francisco +server.resource( + "sf_weather", + { uri: "weather://San Francisco/current", list: true }, + async (uri) => { + try { + const response = await weatherApi.get('weather', { + params: { q: "San Francisco" }, + }); + + return { + contents: [ + { + uri: uri.href, + mimeType: "application/json", + text: JSON.stringify( + { + 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(), + }, + null, + 2 + ), + }, + ], + }; + } catch (error) { + throw error; + } + } +); + +// Add a dynamic resource template for current weather by city +server.resource( + "current_weather", + new ResourceTemplate("weather://{city}/current", { list: true }), + async (uri, { city }) => { + try { + const response = await weatherApi.get('weather', { + params: { q: city }, + }); + + return { + contents: [ + { + uri: uri.href, + mimeType: "application/json", + text: JSON.stringify( + { + 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(), + }, + null, + 2 + ), + }, + ], + }; + } catch (error) { + throw error; + } + } +); + +// Start receiving messages on stdin and sending messages on stdout +const transport = new StdioServerTransport(); +await server.connect(transport); +console.error('Weather MCP server running on stdio'); \`\`\` (Remember: This is just an example–you may use different dependencies, break the implementation up into multiple files, etc.) @@ -389,9 +274,11 @@ IMPORTANT: Regardless of what else you see in the MCP settings file, you must de The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' above: ${ mcpHub - .getServers() - .map((server) => server.name) - .join(", ") || "(None running currently)" + ? mcpHub + .getServers() + .map((server) => server.name) + .join(", ") || "(None running currently)" + : "(None running currently)" }, e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file${diffStrategy ? " or apply_diff" : ""} to make changes to the files. However some MCP servers may be running from installed packages rather than a local repository, in which case it may make more sense to create a new MCP server. From 5a76a178bcd2dd23b386f41eaee48f1d961c0b20 Mon Sep 17 00:00:00 2001 From: nexon33 Date: Fri, 16 May 2025 21:18:52 +0200 Subject: [PATCH 2/6] update NPM install instructions as well --- src/core/prompts/instructions/create-mcp-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index 839e281ad5..543f97e0b4 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -64,7 +64,7 @@ cd ${await mcpHub.getMcpServersPath()} npx @modelcontextprotocol/create-server weather-server cd weather-server # Install dependencies -npm install axios zod +npm install axios zod @modelcontextprotocol/sdk \`\`\` This will create a new project with the following structure: From c8ef80278cbfc8a45fef364c9f65374aebd0a3b7 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Wed, 28 May 2025 07:56:50 -0500 Subject: [PATCH 3/6] docs: minor improvements --- .../prompts/instructions/create-mcp-server.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index 543f97e0b4..de30640102 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -83,8 +83,7 @@ weather-server/ } ├── tsconfig.json └── src/ - └── weather-server/ - └── index.ts # Main server implementation + └── index.ts # Main server implementation \`\`\` 2. Replace \`src/index.ts\` with the following: @@ -189,8 +188,12 @@ server.resource( ], }; } catch (error) { + if (axios.isAxiosError(error)) { + throw new Error(\`Weather API error: \${ + error.response?.data.message ?? error.message + }\`); + } throw error; - } } ); @@ -224,8 +227,12 @@ server.resource( ], }; } catch (error) { + if (axios.isAxiosError(error)) { + throw new Error(\`Weather API error: \${ + error.response?.data.message ?? error.message + }\`); + } throw error; - } } ); From aefca433886e532386babd6b7bd3fda56a6929ec Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Wed, 28 May 2025 07:58:31 -0500 Subject: [PATCH 4/6] refactor: improve readability on mcpHub check --- .../prompts/instructions/create-mcp-server.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index de30640102..41a621c6cd 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -279,14 +279,14 @@ IMPORTANT: Regardless of what else you see in the MCP settings file, you must de ## Editing MCP Servers -The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' above: ${ - mcpHub - ? mcpHub - .getServers() - .map((server) => server.name) - .join(", ") || "(None running currently)" - : "(None running currently)" - }, e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file${diffStrategy ? " or apply_diff" : ""} to make changes to the files. +The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' above: ${(() => { + if (!mcpHub) return "(None running currently)" + const servers = mcpHub + .getServers() + .map((server) => server.name) + .join(", ") + return servers || "(None running currently)" + })()}, e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file${diffStrategy ? " or apply_diff" : ""} to make changes to the files. However some MCP servers may be running from installed packages rather than a local repository, in which case it may make more sense to create a new MCP server. From 9221b079a231e4d3d6ff4ba1d52794576c05ef9a Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Wed, 28 May 2025 08:08:30 -0500 Subject: [PATCH 5/6] fix: add missing bracket --- src/core/prompts/instructions/create-mcp-server.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index 41a621c6cd..dbc830e68c 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -194,6 +194,7 @@ server.resource( }\`); } throw error; + } } ); @@ -233,6 +234,7 @@ server.resource( }\`); } throw error; + } } ); From 9d5e4617ef96d6717d03683c9b9dfed13e6af75a Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Wed, 28 May 2025 08:11:16 -0500 Subject: [PATCH 6/6] refactor: add interfaces --- .../prompts/instructions/create-mcp-server.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index dbc830e68c..3d1d2a20cf 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -100,6 +100,26 @@ if (!API_KEY) { throw new Error('OPENWEATHER_API_KEY environment variable is required'); } +// Define types for OpenWeather API responses +interface WeatherData { + main: { + temp: number; + humidity: number; + }; + weather: Array<{ + description: string; + }>; + wind: { + speed: number; + }; +} + +interface ForecastData { + list: Array; +} + // Create an MCP server const server = new McpServer({ name: "weather-server", @@ -124,7 +144,7 @@ server.tool( }, async ({ city, days = 3 }) => { try { - const response = await weatherApi.get('forecast', { + const response = await weatherApi.get('forecast', { params: { q: city, cnt: Math.min(days, 5) * 8, @@ -164,7 +184,7 @@ server.resource( { uri: "weather://San Francisco/current", list: true }, async (uri) => { try { - const response = await weatherApi.get('weather', { + const response = weatherApi.get('weather', { params: { q: "San Francisco" }, });