This commit is contained in:
bipproduction
2025-10-27 10:05:36 +08:00
parent 648ea0cf56
commit 2a2906081e

View File

@@ -49,10 +49,41 @@ function broadcast(sessionId: string, event: string, data: any) {
return clients.size; return clients.size;
} }
// =====================
// Tools Definition
// =====================
type Tool = {
name: string;
description: string;
run: (input?: any) => Promise<any>;
};
// contoh tools sederhana (bisa dikembangkan)
const tools: Tool[] = [
{
name: "ping",
description: "Mengembalikan timestamp saat ini dari server.",
run: async () => ({ pong: Date.now() }),
},
{
name: "uuid",
description: "Menghasilkan UUID v4 unik.",
run: async () => ({ uuid: uuidv4() }),
},
{
name: "echo",
description: "Mengembalikan data yang dikirim.",
run: async (input) => ({ echo: input }),
},
];
// ===================== // =====================
// Server Initialization // Server Initialization
// ===================== // =====================
export const MCPRoute = new Elysia() export const MCPRoute = new Elysia()
// =====================
// SSE Stream
// =====================
.get("/mcp/:sessionId", ({ params, set }) => { .get("/mcp/:sessionId", ({ params, set }) => {
const { sessionId } = params; const { sessionId } = params;
@@ -61,7 +92,6 @@ export const MCPRoute = new Elysia()
set.headers["Connection"] = "keep-alive"; set.headers["Connection"] = "keep-alive";
set.headers["Access-Control-Allow-Origin"] = "*"; set.headers["Access-Control-Allow-Origin"] = "*";
// Create a readable stream for SSE
const stream = new TransformStream(); const stream = new TransformStream();
const writer = stream.writable.getWriter(); const writer = stream.writable.getWriter();
@@ -81,10 +111,8 @@ export const MCPRoute = new Elysia()
if (!sessions.has(sessionId)) sessions.set(sessionId, new Set()); if (!sessions.has(sessionId)) sessions.set(sessionId, new Set());
sessions.get(sessionId)!.add(client); sessions.get(sessionId)!.add(client);
// Send "connected" event
client.send(formatSSE("connected", { sessionId, id: client.id })); client.send(formatSSE("connected", { sessionId, id: client.id }));
// Keepalive ping
const ping = setInterval(() => { const ping = setInterval(() => {
client.send(formatSSE("ping", { ts: Date.now() })); client.send(formatSSE("ping", { ts: Date.now() }));
}, PING_INTERVAL_MS); }, PING_INTERVAL_MS);
@@ -102,6 +130,10 @@ export const MCPRoute = new Elysia()
status: 200, status: 200,
}); });
}) })
// =====================
// MCP Session Status
// =====================
.get("/mcp/:sessionId/status", ({ params, set }) => { .get("/mcp/:sessionId/status", ({ params, set }) => {
set.headers["Access-Control-Allow-Origin"] = "*"; set.headers["Access-Control-Allow-Origin"] = "*";
const clients = sessions.get(params.sessionId); const clients = sessions.get(params.sessionId);
@@ -110,6 +142,10 @@ export const MCPRoute = new Elysia()
connected: clients?.size ?? 0, connected: clients?.size ?? 0,
}; };
}) })
// =====================
// MCP Broadcast
// =====================
.post("/mcp/:sessionId", async ({ params, request, set }) => { .post("/mcp/:sessionId", async ({ params, request, set }) => {
set.headers["Access-Control-Allow-Origin"] = "*"; set.headers["Access-Control-Allow-Origin"] = "*";
if (!isAuthorized(request.headers)) { if (!isAuthorized(request.headers)) {
@@ -123,6 +159,10 @@ export const MCPRoute = new Elysia()
const sentTo = broadcast(params.sessionId, event, data); const sentTo = broadcast(params.sessionId, event, data);
return { ok: true, sentTo }; return { ok: true, sentTo };
}) })
// =====================
// Delete /mcp/:sessionId
// =====================
.delete("/mcp/:sessionId", ({ params, request, set }) => { .delete("/mcp/:sessionId", ({ params, request, set }) => {
set.headers["Access-Control-Allow-Origin"] = "*"; set.headers["Access-Control-Allow-Origin"] = "*";
if (!isAuthorized(request.headers)) { if (!isAuthorized(request.headers)) {
@@ -136,6 +176,40 @@ export const MCPRoute = new Elysia()
} }
return { ok: true }; return { ok: true };
}) })
// =====================
// Tools Introspection
// =====================
.get("/mcp/tools", ({ set }) => {
set.headers["Access-Control-Allow-Origin"] = "*";
return tools.map(({ name, description }) => ({ name, description }));
})
// =====================
// Run Tool
// =====================
.post("/mcp/tools/:toolName", async ({ params, request, set }) => {
set.headers["Access-Control-Allow-Origin"] = "*";
if (!isAuthorized(request.headers)) {
return new Response("Unauthorized", { status: 401 });
}
const tool = tools.find((t) => t.name === params.toolName);
if (!tool) {
return new Response(
JSON.stringify({ error: `Tool '${params.toolName}' not found` }),
{ status: 404 }
);
}
const body = await request.json().catch(() => ({}));
const result = await tool.run(body);
return { ok: true, tool: tool.name, result };
})
// =====================
// CORS preflight
// =====================
.options("/mcp/:sessionId", ({ set }) => { .options("/mcp/:sessionId", ({ set }) => {
set.headers["Access-Control-Allow-Origin"] = "*"; set.headers["Access-Control-Allow-Origin"] = "*";
set.headers["Access-Control-Allow-Methods"] = "GET,POST,DELETE,OPTIONS"; set.headers["Access-Control-Allow-Methods"] = "GET,POST,DELETE,OPTIONS";