UNPKG

@compligent-mcp/csf

Version:

Compligent MCP Client - NIST CSF 2.0 with Prompts (connects to hosted compliance database)

254 lines (220 loc) • 7.4 kB
#!/usr/bin/env node /** * Compligent MCP Client - NIST CSF 2.0 * * This is a thin client that connects to the hosted CSF 2.0 service * on Railway, providing all 11 tools and 12 prompts through the MCP protocol. */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import axios, { AxiosError } from "axios"; import { z } from "zod"; // Configuration const HOSTED_SERVICE_URL = "https://csf-mcp-server-production.up.railway.app"; const CLIENT_VERSION = "2.0.0"; const API_TIMEOUT = 30000; // 30 seconds // Types interface MCPRequest { jsonrpc: string; method: string; params?: any; id: number | string; } interface MCPResponse { jsonrpc: string; result?: any; error?: { code: number; message: string; data?: any; }; id: number | string; } class CSFClient { private server: Server; private requestId = 1; constructor() { this.server = new Server( { name: "@compligent-mcp/csf", version: CLIENT_VERSION, }, { capabilities: { tools: {}, prompts: {}, }, } ); this.setupHandlers(); } private setupHandlers(): void { // List tools - proxy to hosted service this.server.setRequestHandler(ListToolsRequestSchema, async () => { try { const response = await this.proxyRequest({ jsonrpc: "2.0", method: "tools/list", id: this.requestId++, }); return { tools: response.result?.tools || [], }; } catch (error) { console.error("Failed to list tools:", error); return { tools: [] }; } }); // Call tool - proxy to hosted service this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { const response = await this.proxyRequest({ jsonrpc: "2.0", method: "tools/call", params: { name, arguments: args || {}, }, id: this.requestId++, }); if (response.error) { throw new Error(`Tool execution failed: ${response.error.message}`); } return response.result; } catch (error) { console.error(`Failed to call tool ${name}:`, error); // Return a user-friendly error message return { content: [ { type: "text", text: `Error: Unable to connect to SP 800-53 Suite service. Please check your internet connection and try again.\n\nTechnical details: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], }; } }); // List prompts - proxy to hosted service this.server.setRequestHandler(ListPromptsRequestSchema, async () => { try { const response = await this.proxyRequest({ jsonrpc: "2.0", method: "prompts/list", id: this.requestId++, }); return { prompts: response.result?.prompts || [], }; } catch (error) { console.error("Failed to list prompts:", error); return { prompts: [] }; } }); // Get prompt - proxy to hosted service this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { const response = await this.proxyRequest({ jsonrpc: "2.0", method: "prompts/get", params: { name, arguments: args || {}, }, id: this.requestId++, }); if (response.error) { throw new Error(`Prompt execution failed: ${response.error.message}`); } return response.result; } catch (error) { console.error(`Failed to get prompt ${name}:`, error); // Return a user-friendly error message return { description: `Error: Unable to connect to CSF service. Please check your internet connection and try again.`, messages: [ { role: "assistant", content: { type: "text", text: `Technical details: ${error instanceof Error ? error.message : 'Unknown error'}`, }, }, ], }; } }); } private async proxyRequest(mcpRequest: MCPRequest): Promise<MCPResponse> { try { // For MCP protocol, we need to send the request via stdio to the hosted service // Since the hosted service expects MCP protocol over HTTP, we'll use the /mcp endpoint const response = await axios.post( `${HOSTED_SERVICE_URL}/mcp`, mcpRequest, { headers: { "Content-Type": "application/json", "Accept": "application/json, text/event-stream", "User-Agent": `@compligent-mcp/csf/${CLIENT_VERSION}`, // Add authentication header if API key is provided ...(process.env.COMPLIGENT_API_KEY && { Authorization: `Bearer ${process.env.COMPLIGENT_API_KEY}`, }), }, timeout: API_TIMEOUT, } ); return response.data; } catch (error) { if (error instanceof AxiosError) { if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND") { throw new Error("Unable to connect to Compligent CSF 2.0 service. Please check your internet connection."); } if (error.response?.status === 401) { throw new Error("Authentication failed. Please check your COMPLIGENT_API_KEY environment variable."); } if (error.response?.status === 429) { throw new Error("Rate limit exceeded. Please try again later."); } throw new Error(`Service error (${error.response?.status || error.code}): ${error.message}`); } throw error; } } public async start(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); // Log startup info to stderr (won't interfere with MCP protocol on stdout) console.error(`šŸš€ Compligent CSF 2.0 MCP Client v${CLIENT_VERSION}`); console.error(` Connected to: ${HOSTED_SERVICE_URL}`); console.error(` Ready to serve 11 tools + 12 prompts via MCP protocol`); console.error(` ${process.env.COMPLIGENT_API_KEY ? "āœ“" : "⚠"} ${process.env.COMPLIGENT_API_KEY ? "Authenticated" : "Running without API key"}`); } } // Error handling process.on("SIGINT", async () => { console.error("\nšŸ‘‹ Shutting down Compligent CSF 2.0 client..."); process.exit(0); }); process.on("uncaughtException", (error) => { console.error("šŸ’„ Uncaught Exception:", error); process.exit(1); }); process.on("unhandledRejection", (reason) => { console.error("šŸ’„ Unhandled Rejection:", reason); process.exit(1); }); // Start the client const client = new CSFClient(); client.start().catch((error) => { console.error("Failed to start CSF 2.0 client:", error); process.exit(1); });