diff --git a/packages/server/package.json b/packages/server/package.json index fc64d7c..ea0d8a6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,19 +1,20 @@ { - "version": "0.1.0", - "dependencies": { - "@yuzu-gca/shared": "workspace:*", - "@modelcontextprotocol/sdk": "^1.0.0" - }, - "types": "dist/index.d.ts", - "scripts": { - "dev": "tsc --watch", - "build": "tsc" - }, - "name": "@yuzu-gca/server", - "private": true, - "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "^5.0.0" - }, - "main": "dist/index.js" + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "@yuzu-gca/shared": "workspace:*", + "zod": "^4.4.3" + }, + "types": "dist/index.d.ts", + "scripts": { + "dev": "tsc --watch", + "build": "tsc" + }, + "name": "@yuzu-gca/server", + "private": true, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + }, + "main": "dist/index.js" } \ No newline at end of file diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 25a7245..f152311 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1,84 +1,34 @@ /** - * C-001: MCP Server Framework - * - * Bootstrap the MCP server with SSE transport using @modelcontextprotocol/sdk. - * Registers all 36 tools from @yuzu-gca/shared and exposes lifecycle hooks. + * C-001: MCP Server Framework — stdio transport for Gateway compatibility. */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { ALL_TOOL_DEFS, type ToolDef, type DeviceConfig } from "@yuzu-gca/shared"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +import * as tools from "./tools.js"; -export interface ServerOptions { - config: DeviceConfig; - toolHandlers: Map Promise>; -} +export function createServer(): McpServer { + const server = new McpServer({ name: "yuzu-gca-device", version: "0.1.0" }); -/** - * Creates and returns an MCP Server instance with all tools registered. - * Call `server.connect(transport)` to start handling requests. - */ -export function createServer(opts: ServerOptions): McpServer { - const server = new McpServer({ - name: "yuzu-gca-device", - version: opts.config.identity.id, - }); - - // Register every tool from the shared type definitions - for (const toolDef of ALL_TOOL_DEFS) { - const handler = opts.toolHandlers.get(toolDef.name); - if (handler) { - server.tool( - toolDef.name, - toolDef.description, - toolDef.inputSchema.properties, - async (args: any) => { - const result = await handler(args); - return { - content: [{ type: "text", text: JSON.stringify(result, null, 2) }], - }; - }, - ); - } else { - // Register placeholder for unimplemented tools - server.tool( - toolDef.name, - toolDef.description, - { ...toolDef.inputSchema.properties, _required: toolDef.inputSchema.required }, - async (args: any) => { - content: [{ type: "text", text: JSON.stringify({ error: "Tool not yet implemented", tool: toolDef.name }) }], - }), - ); - } - } + server.tool("file_list", "List directory contents", { path: z.string(), recursive: z.boolean().optional() }, async a => ({ content: [{ type: "text" as const, text: JSON.stringify(await tools.file_list(a)) }] })); + server.tool("file_read", "Read file content", { path: z.string(), encoding: z.string().optional(), offset: z.number().optional(), limit: z.number().optional() }, async a => ({ content: [{ type: "text" as const, text: (await tools.file_read(a)).content }] })); + server.tool("file_write", "Write file content", { path: z.string(), content: z.string(), encoding: z.string().optional() }, async a => ({ content: [{ type: "text" as const, text: JSON.stringify(await tools.file_write(a)) }] })); + server.tool("file_move", "Move/rename file", { from: z.string(), to: z.string() }, async a => ({ content: [{ type: "text" as const, text: JSON.stringify(await tools.file_move(a)) }] })); + server.tool("exec", "Execute shell command", { command: z.string(), cwd: z.string().optional(), timeout: z.number().optional() }, async a => ({ content: [{ type: "text" as const, text: JSON.stringify(await tools.exec(a)) }] })); + server.tool("process_list","List running processes", { filter: z.string().optional() }, async a => ({ content: [{ type: "text" as const, text: JSON.stringify(await tools.process_list(a)) }] })); + server.tool("sysinfo", "System resource usage", {}, async () => ({ content: [{ type: "text" as const, text: JSON.stringify(await tools.sysinfo({})) }] })); return server; } -/** - * Start the server with SSE transport on a given port. - */ -export async function startServer( - server: McpServer, - port: number = 3001, -): Promise { - const transport = new SSEServerTransport({ - port, - endpoint: "/sse", - }); - +export async function startServer(server: McpServer): Promise { + const transport = new StdioServerTransport(); await server.connect(transport); - console.log(`[Yuzu-GCA] MCP Server listening on :${port}/sse`); + console.error("[Yuzu-GCA] MCP Server ready (stdio)"); } -/** - * Convenience — create + start in one call. - */ -export async function bootstrap( - opts: ServerOptions, - port?: number, -): Promise { - const server = createServer(opts); - await startServer(server, port); +export async function bootstrap(): Promise { + const server = createServer(); + await startServer(server); return server; } diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts index f24e511..f97eb41 100644 --- a/packages/server/src/main.ts +++ b/packages/server/src/main.ts @@ -1,78 +1,10 @@ #!/usr/bin/env node -/** - * Yuzu-GCA MCP Server Entry Point - * - * Usage: node dist/main.js - * Or: npx tsx src/main.ts - */ +import { bootstrap } from "./index.js"; -import { bootstrap, type ServerOptions } from "./index.js"; -import * as tools from "./tools.js"; -import { ConnectionManager } from "./connection.js"; -import type { DeviceConfig } from "@yuzu-gca/shared"; -import * as os from "node:os"; -import { randomUUID } from "node:crypto"; +console.error("[Yuzu-GCA] MCP Server starting (stdio)..."); +console.error(" Tools: file_list, file_read, file_write, file_move, exec, process_list, sysinfo"); -// Build device config from environment -const config: DeviceConfig = { - identity: { - id: process.env.GCA_DEVICE_ID ?? randomUUID(), - name: process.env.GCA_DEVICE_NAME ?? os.hostname(), - platform: os.platform(), - arch: os.arch(), - hostname: os.hostname(), - osVersion: os.release(), - }, - gateway: { - url: process.env.GCA_GATEWAY_URL ?? "http://localhost:3000", - transport: (process.env.GCA_TRANSPORT as any) ?? "sse", - reconnectDelay: 1000, - maxReconnectDelay: 30000, - heartbeatInterval: 15000, - }, - capabilities: [ - "exec", "process_list", - "file_list", "file_read", "file_write", "file_move", - "sysinfo", - ], - logLevel: (process.env.GCA_LOG_LEVEL as any) ?? "info", -}; - -// Build tool handlers map -const toolHandlers = new Map Promise>([ - ["file_list", tools.file_list], - ["file_read", tools.file_read], - ["file_write", tools.file_write], - ["file_move", tools.file_move], - ["exec", tools.exec], - ["process_list",tools.process_list], - ["sysinfo", tools.sysinfo], -]); - -const opts: ServerOptions = { config, toolHandlers }; -const port = Number(process.env.GCA_PORT ?? 3001); - -console.log(`[Yuzu-GCA] Starting MCP Server...`); -console.log(` Device: ${config.identity.name} (${config.identity.platform}/${config.identity.arch})`); -console.log(` Gateway: ${config.gateway.url}`); -console.log(` Tools: ${toolHandlers.size} implemented`); -console.log(` Port: ${port}`); - -bootstrap(opts, port).then(async () => { - console.log(`[Yuzu-GCA] Ready.`); - - // Start Gateway connection lifecycle - const connMgr = new ConnectionManager(config, (state) => { - console.log(`[Gateway] ${state.status} (attempt ${state.reconnectAttempt})`); - }); - - // Register signal handlers BEFORE connection start - const shutdown = async () => { connMgr.stop(); setTimeout(() => process.exit(0), 300); }; - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); - - await connMgr.start(); -}).catch(err => { - console.error(`[Yuzu-GCA] Fatal:`, err); +bootstrap().catch(err => { + console.error("[Yuzu-GCA] Fatal:", err); process.exit(1); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e404b57..2ac0567 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: '@yuzu-gca/shared': specifier: workspace:* version: link:../shared + zod: + specifier: ^4.4.3 + version: 4.4.3 devDependencies: '@types/node': specifier: ^20.0.0 @@ -7884,7 +7887,7 @@ snapshots: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 + strip-ansi: 6.0.0 string-width@4.2.3: dependencies: