diff --git a/client.js b/client.js index 26042b7..bb8fb8f 100644 --- a/client.js +++ b/client.js @@ -28,8 +28,11 @@ try { // const prompts = await client.listPrompts(); // console.log(prompts); - // const prompt = await client.getPrompt("example-prompt", { - // arg1: "value", + // const prompt = await client.getPrompt({ + // name: "echo", + // arguments: { + // message: "aaac", + // }, // }); // console.log(prompt); @@ -46,26 +49,10 @@ try { // console.log(resources); // Read a resource - const resource = await client.readResource("echo://example.txt"); - console.log(resource); + // const resource = await client.readResource({ + // uri: "echo://asa", + // }); + // console.log(resource); } catch (error) { console.error("Failed to connect:", error); } - -// // List prompts -// const prompts = await client.listPrompts(); -// console.log(prompts); - -// Get a prompt -// const prompt = await client.getPrompt("example-prompt", { -// arg1: "value" -// }); -// console.log(11); -// console.log(prompt); - -// List resources -// const resources = await client.listResources(); -// console.log(resources); - -// Read a resource -// const resource = await client.readResource("file:///example.txt"); diff --git a/package.json b/package.json index 22104c0..b9f939c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "description": "", "main": "index.js", "scripts": { - "test": "mcp-inspector node server.js" + "start": "node client.js", + "test": "mcp-inspector node server.js", + "sse": "node server-sse.js" }, "keywords": [], "author": "", @@ -14,6 +16,7 @@ "dependencies": { "@modelcontextprotocol/inspector": "^0.6.0", "@modelcontextprotocol/sdk": "^1.7.0", + "express": "^4.21.2", "zod": "^3.24.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18cf88f..0b78ee5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.7.0 version: 1.7.0 + express: + specifier: ^4.21.2 + version: 4.21.2 zod: specifier: ^3.24.2 version: 3.24.2 @@ -2119,7 +2122,7 @@ snapshots: send@1.1.0: dependencies: - debug: 4.3.6 + debug: 4.4.0 destroy: 1.2.0 encodeurl: 2.0.0 escape-html: 1.0.3 diff --git a/readme.md b/readme.md index ca04f2e..f275c18 100644 --- a/readme.md +++ b/readme.md @@ -3,4 +3,30 @@ - https://www.npmjs.com/package/@modelcontextprotocol/inspector - https://github.com/modelcontextprotocol/servers - https://mcpcn.com/docs/quickstart/client/ -- https://www.npmjs.com/package/@modelcontextprotocol/sdk#writing-mcp-clients \ No newline at end of file +- https://www.npmjs.com/package/@modelcontextprotocol/sdk#writing-mcp-clients +- https://mcpcn.com/docs/examples/ +- https://zhuanlan.zhihu.com/p/16934783594 +- https://mcpcn.com/tools/inspector/ +- https://modelcontextprotocol.io/sdk/java/mcp-client +- https://github1s.com/modelcontextprotocol/servers/blob/main/src/sentry/src/mcp_server_sentry/server.py#L233 +- https://mcpservers.org/ +- https://www.npmjs.com/package/@modelcontextprotocol/sdk#server-capabilities + + +{ + "mcpServers": { + "天气": { + "url": "http://localhost:3001/sse", + "env": {} + }, + "filesystem": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem", + "D:/@code/demo/x6-demo", + "D:/@code/demo/x6-demo" + ] + } + } +} \ No newline at end of file diff --git a/server-sse.js b/server-sse.js new file mode 100644 index 0000000..644421d --- /dev/null +++ b/server-sse.js @@ -0,0 +1,58 @@ +import express from "express"; +import { + ResourceTemplate, + McpServer, +} from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { z } from "zod"; + +const server = new McpServer({ + name: "Echo", + version: "1.0.0", +}); + +server.resource( + "echo", + new ResourceTemplate("echo://{message}", { list: undefined }), + async (uri, { message }) => ({ + contents: [ + { + uri: uri.href, + text: `Resource echo: ${message}`, + }, + ], + }) +); + +server.tool("echo", { message: z.string() }, async ({ message }) => ({ + content: [{ type: "text", text: `Tool echo: ${message}` }], +})); + +server.prompt("echo", { message: z.string() }, ({ message }) => ({ + messages: [ + { + role: "user", + content: { + type: "text", + text: `Please process this message: ${message}`, + }, + }, + ], +})); + +const app = express(); +let transport +app.get("/sse", async (req, res) => { + transport = new SSEServerTransport("/messages", res); + await server.connect(transport); +}); + +app.post("/messages", async (req, res) => { + // Note: to support multiple simultaneous connections, these messages will + // need to be routed to a specific matching transport. (This logic isn't + // implemented here, for simplicity.) + await transport.handlePostMessage(req, res); +}); + +app.listen(3001); +console.log("Server running on http://localhost:3001"); diff --git a/weather/client.js b/weather/client.js new file mode 100644 index 0000000..8c5bcb9 --- /dev/null +++ b/weather/client.js @@ -0,0 +1,29 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; + +const transport = new StdioClientTransport({ + command: "node", + args: ["./server.js"], + cwd: process.cwd(), +}); + +const client = new Client( + { + name: "weather-client", + version: "0.0.1", + }, + { + capabilities: { + prompts: {}, + resources: {}, + tools: {}, + }, + } +); + +try { + await client.connect(transport); + console.log("Connected successfully"); +} catch (error) { + console.error("Failed to connect:", error); +} diff --git a/weather/server-sse.js b/weather/server-sse.js new file mode 100644 index 0000000..1a7683f --- /dev/null +++ b/weather/server-sse.js @@ -0,0 +1,47 @@ +import express from "express"; +import { + ResourceTemplate, + McpServer, +} from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { z } from "zod"; + +const server = new McpServer( + { + name: "天气工具包", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + }, + } +); + +server.tool("get-weather", { state: z.string() }, async ({ state }) => { + const url = `https://cn.apihz.cn/api/tianqi/tqyb.php?id=88888888&key=88888888&sheng=四川&place=绵阳`; + const data = await fetch(url).then((res) => res.json()); + + if (!data) { + return "无法获取天气"; + } + + return data; +}); + +const app = express(); +let transport; +app.get("/sse", async (req, res) => { + transport = new SSEServerTransport("/messages", res); + await server.connect(transport); +}); + +app.post("/messages", async (req, res) => { + // Note: to support multiple simultaneous connections, these messages will + // need to be routed to a specific matching transport. (This logic isn't + // implemented here, for simplicity.) + await transport.handlePostMessage(req, res); +}); + +app.listen(3001); +console.log("Server running on http://localhost:3001"); diff --git a/weather/server.js b/weather/server.js new file mode 100644 index 0000000..50da82b --- /dev/null +++ b/weather/server.js @@ -0,0 +1,76 @@ +import { + McpServer, + ResourceTemplate, +} from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + makeNWSRequest, + formatAlert, + NWS_API_BASE, + USER_AGENT, +} from "./util.js"; +import { z } from "zod"; + +const server = new McpServer({ + name: "天气工具包", + version: "0.0.1", +}); + +server.tool("获取天气", { state: z.string() }, async ({ state }) => { + const url = `https://cn.apihz.cn/api/tianqi/tqyb.php?id=88888888&key=88888888&sheng=四川&place=绵阳`; + const data = await fetch(url).then(res => res.json()); + + if (!data) { + return "无法获取天气"; + } + + return data; +}); + +// server.tool({ +// name: "get-forecast", +// description: "获取位置的天气预报", +// parameters: { +// latitude: { +// type: "number", +// description: "位置的纬度", +// }, +// longitude: { +// type: "number", +// description: "位置的经度", +// }, +// }, +// async execute({ latitude, longitude }) { +// // 首先获取预报网格端点 +// const pointsUrl = `${NWS_API_BASE}/points/${latitude},${longitude}`; +// const pointsData = await makeNWSRequest(pointsUrl); + +// if (!pointsData) { +// return "无法为此位置获取预报数据。"; +// } + +// // 从点响应中获取预报URL +// const forecastUrl = pointsData.properties.forecast; +// const forecastData = await makeNWSRequest(forecastUrl); + +// if (!forecastData) { +// return "无法获取详细预报。"; +// } + +// // 将时段格式化为可读预报 +// const periods = forecastData.properties.periods; +// const forecasts = periods.slice(0, 5).map( +// (period) => ` +// ${period.name}: +// Temperature: ${period.temperature}°${period.temperatureUnit} +// Wind: ${period.windSpeed} ${period.windDirection} +// Forecast: ${period.detailedForecast} +// ` +// ); + +// return forecasts.join("\n---\n"); +// }, +// }); + +const transport = new StdioServerTransport(); +await server.connect(transport); diff --git a/weather/test.js b/weather/test.js new file mode 100644 index 0000000..f82ec55 --- /dev/null +++ b/weather/test.js @@ -0,0 +1,4 @@ + +fetch("https://cn.apihz.cn/api/tianqi/tqyb.php?id=88888888&key=88888888&sheng=四川&place=绵阳").then(res => res.json()).then(data => { + console.log(data) +}) \ No newline at end of file diff --git a/weather/util.js b/weather/util.js new file mode 100644 index 0000000..12654a5 --- /dev/null +++ b/weather/util.js @@ -0,0 +1,28 @@ +// 常量 +export const NWS_API_BASE = "https://api.weather.gov"; +export const USER_AGENT = "weather-app/1.0"; + +export async function makeNWSRequest(url) { + const headers = { + // "User-Agent": USER_AGENT, + // Accept: "application/geo+json", + }; + try { + const response = await fetch(url); + if (!response.ok) throw new Error("API request failed"); + return await response.json(); + } catch (error) { + return null; + } +} + +export function formatAlert(feature) { + const props = feature.properties; + return ` +Event: ${props.event || "Unknown"} +Area: ${props.areaDesc || "Unknown"} +Severity: ${props.severity || "Unknown"} +Description: ${props.description || "No description available"} +Instructions: ${props.instruction || "No specific instructions provided"} +`; +}