tamabahan
This commit is contained in:
@@ -9,14 +9,7 @@ import Auth from "./server/routes/auth_route";
|
||||
import CredentialRoute from "./server/routes/credential_route";
|
||||
import DarmasabaRoute from "./server/routes/darmasaba_route";
|
||||
import { convertOpenApiToMcp } from "./server/lib/mcp-converter";
|
||||
|
||||
function encode(str: string) {
|
||||
return new TextEncoder().encode(str);
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
}
|
||||
import McpRoute from "./server/routes/mcp_route";
|
||||
|
||||
const Docs = new Elysia()
|
||||
.use(Swagger({
|
||||
@@ -55,34 +48,7 @@ const app = new Elysia()
|
||||
tags: ["MCP"],
|
||||
}
|
||||
})
|
||||
.get("/mcp", () => {
|
||||
// Buat stream SSE
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
// Kirim event pertama
|
||||
controller.enqueue(encode("event: ready\ndata: " + JSON.stringify({ ok: true }) + "\n\n"));
|
||||
|
||||
// Kirim event tiap 5 detik
|
||||
while (true) {
|
||||
controller.enqueue(encode("event: status\ndata: " + JSON.stringify({ timestamp: Date.now() }) + "\n\n"));
|
||||
await sleep(5000);
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
console.log("SSE client disconnected");
|
||||
}
|
||||
});
|
||||
|
||||
// Kembalikan Response manual
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive"
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
.use(McpRoute)
|
||||
.get("*", html)
|
||||
.listen(3000, () => {
|
||||
console.log("Server running at http://localhost:3000");
|
||||
|
||||
94
src/server/routes/mcp_route.ts
Normal file
94
src/server/routes/mcp_route.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Elysia, t } from 'elysia';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
// Map untuk menyimpan transport berdasarkan sessionId
|
||||
const transports: Record<string, StreamableHTTPServerTransport> = {};
|
||||
|
||||
const McpRoute = new Elysia()
|
||||
// Middleware global untuk JSON parsing & header access
|
||||
.onRequest(({ set }) => {
|
||||
set.headers['Content-Type'] = 'application/json';
|
||||
})
|
||||
// Route utama untuk komunikasi client → server
|
||||
.post(
|
||||
'/mcp',
|
||||
async ({ body, request, set }) => {
|
||||
const sessionId = request.headers.get('mcp-session-id') ?? undefined;
|
||||
let transport: StreamableHTTPServerTransport;
|
||||
|
||||
// Jika ada sessionId, pakai transport lama
|
||||
if (sessionId && transports[sessionId]) {
|
||||
transport = transports[sessionId];
|
||||
} else if (!sessionId && isInitializeRequest(body)) {
|
||||
// Jika belum ada session & ini request inisialisasi
|
||||
transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID(),
|
||||
onsessioninitialized: (sid) => {
|
||||
transports[sid] = transport;
|
||||
},
|
||||
});
|
||||
|
||||
// Cleanup transport jika ditutup
|
||||
transport.onclose = () => {
|
||||
if (transport.sessionId) {
|
||||
delete transports[transport.sessionId];
|
||||
}
|
||||
};
|
||||
|
||||
// Inisialisasi MCP server
|
||||
const server = new McpServer({
|
||||
name: 'example-server',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
// ... di sini bisa ditambahkan tools, prompts, dsb ...
|
||||
await server.connect(transport);
|
||||
} else {
|
||||
set.status = 400;
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Bad Request: No valid session ID provided',
|
||||
},
|
||||
id: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Jalankan handler HTTP MCP
|
||||
return await transport.handleRequest(
|
||||
request as any,
|
||||
// Simulasi `Response` agar Elysia bisa mengembalikan hasil
|
||||
new Response(null, { status: 200 }) as any,
|
||||
body
|
||||
);
|
||||
},
|
||||
{
|
||||
body: t.Any(), // fleksibel untuk JSON-RPC
|
||||
}
|
||||
)
|
||||
// Handler reusable untuk GET & DELETE
|
||||
.derive(({ request, set }) => {
|
||||
const sessionId = request.headers.get('mcp-session-id') ?? undefined;
|
||||
const transport = sessionId ? transports[sessionId] : undefined;
|
||||
|
||||
if (!transport) {
|
||||
set.status = 400;
|
||||
throw new Error('Invalid or missing session ID');
|
||||
}
|
||||
|
||||
return { transport };
|
||||
})
|
||||
// GET untuk server → client via SSE
|
||||
.get('/mcp', async ({ transport, request }) => {
|
||||
return await transport.handleRequest(request as any, new Response() as any);
|
||||
})
|
||||
// DELETE untuk terminasi session
|
||||
.delete('/mcp', async ({ transport, request }) => {
|
||||
return await transport.handleRequest(request as any, new Response() as any);
|
||||
})
|
||||
|
||||
export default McpRoute
|
||||
Reference in New Issue
Block a user