feat: add streamable http mcp server

This commit is contained in:
Ou
2025-05-02 20:59:02 +08:00
parent 2e32ca13a7
commit b784389021
5 changed files with 93 additions and 1 deletions
+30
View File
@@ -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,
}
}
})
+10
View File
@@ -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(";")
+51
View File
@@ -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<CallToolResult> => {
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
}
+1 -1
View File
@@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => {
if (!url.pathname.startsWith("/api")) return if (!url.pathname.startsWith("/api")) return
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) { if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
event.context.disabledLogin = true 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" }) throw createError({ statusCode: 506, message: "Server not configured, disable login" })
} else { } else {
if (["/api/s", "/api/me"].find(p => url.pathname.startsWith(p))) { if (["/api/s", "/api/me"].find(p => url.pathname.startsWith(p))) {
+1
View File
@@ -8,6 +8,7 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"strict": true, "strict": true,
"allowJs": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,