From b7843890216cf587bff64ef9b28d378bb5f1b264 Mon Sep 17 00:00:00 2001 From: Ou Date: Fri, 2 May 2025 20:59:02 +0800 Subject: [PATCH] feat: add streamable http mcp server --- server/api/mcp.post.ts | 30 +++++++++++++++++++++++ server/mcp/desc.js | 10 ++++++++ server/mcp/server.ts | 51 +++++++++++++++++++++++++++++++++++++++ server/middleware/auth.ts | 2 +- tsconfig.base.json | 1 + 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 server/api/mcp.post.ts create mode 100644 server/mcp/desc.js create mode 100644 server/mcp/server.ts diff --git a/server/api/mcp.post.ts b/server/api/mcp.post.ts new file mode 100644 index 0000000..33defb0 --- /dev/null +++ b/server/api/mcp.post.ts @@ -0,0 +1,30 @@ +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js" +import { getServer } from "#/mcp/server" + +export default defineEventHandler(async (event) => { + const req = event.node.req + const res = event.node.res + const server = getServer() + try { + const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }) + transport.onerror = console.error.bind(console) + await server.connect(transport) + await transport.handleRequest(req, res, await readBody(event)) + res.on("close", () => { + // console.log("Request closed") + transport.close() + server.close() + }) + return res + } catch (e) { + console.error(e) + return { + jsonrpc: "2.0", + error: { + code: -32603, + message: "Internal server error", + }, + id: null, + } + } +}) diff --git a/server/mcp/desc.js b/server/mcp/desc.js new file mode 100644 index 0000000..a043e87 --- /dev/null +++ b/server/mcp/desc.js @@ -0,0 +1,10 @@ +import sources from "../../shared/sources.json" + +export const description = Object.entries(sources).filter(([_, source]) => { + if (source.redirect) { + return false + } + return true +}).map(([id, source]) => { + return source.title ? `${source.name}-${source.title} id is ${id}` : `${source.name} id is ${id}` +}).join(";") diff --git a/server/mcp/server.ts b/server/mcp/server.ts new file mode 100644 index 0000000..1bc47b6 --- /dev/null +++ b/server/mcp/server.ts @@ -0,0 +1,51 @@ +import { z } from "zod" +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js" +import packageJSON from "../../package.json" +import { description } from "./desc.js" + +export function getServer() { + const server = new McpServer( + { + name: "NewsNow", + version: packageJSON.version, + }, + { capabilities: { logging: {} } }, + ) + + server.tool( + "get_hotest_latest_news", + `get hotest or latest news from source by {id}, return {count: 10} news.`, + { + id: z.string().describe(`source id. e.g. ${description}`), + count: z.any().default(10).describe("count of news to return."), + }, + async ({ count }): Promise => { + let n = Number(count) + if (Number.isNaN(n) || n < 1) { + n = 10 + } + await delay(5000) + return { + content: [{ + text: "hello", + type: "text", + }], + } + + // const res: SourceResponse = await $fetch(`https://newsnow.busiyi.world/api/s?id=${id}`) + // return { + // content: res.items.slice(0, count).map((item) => { + // return { + // text: `[${item.title}](${item.url})`, + // type: "text", + // } + // }), + // } + }, + ) + + server.server.onerror = console.error.bind(console) + + return server +} diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts index 68de973..a965235 100644 --- a/server/middleware/auth.ts +++ b/server/middleware/auth.ts @@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => { if (!url.pathname.startsWith("/api")) return if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) { event.context.disabledLogin = true - if (["/api/s", "/api/proxy", "/api/latest"].every(p => !url.pathname.startsWith(p))) + if (["/api/s", "/api/proxy", "/api/latest", "/api/mcp"].every(p => !url.pathname.startsWith(p))) throw createError({ statusCode: 506, message: "Server not configured, disable login" }) } else { if (["/api/s", "/api/me"].find(p => url.pathname.startsWith(p))) { diff --git a/tsconfig.base.json b/tsconfig.base.json index 1a44dc0..ebf7ff6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -8,6 +8,7 @@ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "strict": true, + "allowJs": true, "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true,