feat: add streamable http mcp server
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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(";")
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
Reference in New Issue
Block a user