UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

140 lines (137 loc) 4.71 kB
import { createServer } from 'http'; import { l as logger } from './types-DETLaopx.mjs'; import { v as validateMcpToken, g as generateMcpToken } from './index-DiNLHtkZ.mjs'; import 'axios'; import 'chalk'; import 'fs'; import 'node:fs'; import 'node:os'; import 'node:path'; import 'node:events'; import 'socket.io-client'; import 'zod'; import 'node:crypto'; import 'tweetnacl'; import 'child_process'; import 'util'; import 'fs/promises'; import 'crypto'; import 'path'; import 'url'; import 'os'; import 'node:child_process'; import 'node:fs/promises'; import 'node:module'; import 'node:util'; import 'expo-server-sdk'; import 'node:readline'; import 'ink'; import 'react'; import 'node:url'; import 'ps-list'; import 'cross-spawn'; import 'tmp'; import 'qrcode-terminal'; import 'open'; import 'fastify'; import 'fastify-type-provider-zod'; import '@modelcontextprotocol/sdk/client/index.js'; import '@modelcontextprotocol/sdk/client/streamableHttp.js'; import 'readline'; import '@modelcontextprotocol/sdk/server/mcp.js'; import 'node:http'; import '@modelcontextprotocol/sdk/server/streamableHttp.js'; const toolCallTimestamps = /* @__PURE__ */ new Map(); const TOOL_RATE_LIMIT = 30; const TOOL_RATE_WINDOW = 6e4; function checkToolRateLimit(deploymentId, toolName) { const key = `${deploymentId}:${toolName}`; const now = Date.now(); const stamps = toolCallTimestamps.get(key) ?? []; const recent = stamps.filter((t) => now - t < TOOL_RATE_WINDOW); if (recent.length >= TOOL_RATE_LIMIT) return false; recent.push(now); toolCallTimestamps.set(key, recent); return true; } function startPluginDaemonServer(options = {}) { const host = options.host ?? "127.0.0.1"; let resolvedPort = options.port ?? 0; const server = createServer(async (req, res) => { if (req.method !== "POST" || req.url !== "/tool-call") { res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not found" })); return; } try { let body; try { body = await readBody(req); } catch (readErr) { if (readErr instanceof Error && readErr.message === "Payload too large") { res.writeHead(413, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Payload too large" })); return; } throw readErr; } const parsed = JSON.parse(body); const authHeader = req.headers["authorization"] ?? ""; const token = authHeader.replace(/^Bearer\s+/, ""); if (!validateMcpToken(token, parsed.deploymentId)) { res.writeHead(401, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Invalid or expired token" })); return; } if (!checkToolRateLimit(parsed.deploymentId, parsed.toolName)) { logger.debug(`[PLUGIN-DAEMON] Rate limit exceeded for ${parsed.deploymentId}:${parsed.toolName}`); res.writeHead(429, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: `Rate limit exceeded for tool "${parsed.toolName}"` })); return; } logger.debug(`[PLUGIN-DAEMON] Tool call: ${parsed.deploymentId}:${parsed.toolName}`); res.writeHead(501, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "error", error: "Tool execution not yet implemented" })); } catch (err) { const message = err instanceof Error ? err.message : String(err); logger.debug(`[PLUGIN-DAEMON] Error handling tool call: ${message}`); res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: message })); } }); server.listen(resolvedPort, host, () => { const addr = server.address(); if (addr && typeof addr === "object") { resolvedPort = addr.port; } logger.debug(`[PLUGIN-DAEMON] Server listening on ${host}:${resolvedPort}`); }); return { get port() { return resolvedPort; }, stop: () => new Promise((resolve, reject) => { server.close((err) => err ? reject(err) : resolve()); }), generateTokenForDeployment: (deploymentId) => generateMcpToken(deploymentId) }; } const MAX_BODY_SIZE = 1048576; function readBody(req) { return new Promise((resolve, reject) => { const chunks = []; let totalBytes = 0; req.on("data", (chunk) => { totalBytes += chunk.length; if (totalBytes > MAX_BODY_SIZE) { req.destroy(); reject(new Error("Payload too large")); return; } chunks.push(chunk); }); req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8"))); req.on("error", reject); }); } export { startPluginDaemonServer };