Skip to main content
Glama
index.ts12 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { CallToolRequest, CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import minimist from 'minimist'; import { FireflyClient, InventoryArgs, CodifyArgs, GetInsightsArgs, CreateInsightArgs, UpdateInsightArgs, DeleteInsightArgs } from './fireflyClient'; import { InventoryTool, CodifyTool, GetPolicesTool, CreatePolicyTool, UpdatePolicyTool, DeletePolicyTool } from './tools'; import express from 'express'; import * as logger from 'loglevel'; import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/dist/esm/shared/protocol"; async function main() { const argv = minimist(process.argv.slice(2)); const accessKey = argv["access-key"] || process.env.FIREFLY_ACCESS_KEY; const secretKey = argv["secret-key"] || process.env.FIREFLY_SECRET_KEY; const sse = process.argv.includes("--sse"); const hosting = process.argv.includes("--hosting"); const debug = process.argv.includes("debug"); const port = argv["port"] || 6001; const transports: { [sessionId: string]: {transport: SSEServerTransport, fireflyClient: FireflyClient} } = {}; let localFireflyClient: FireflyClient; if (!hosting) { if (!accessKey || !secretKey) { throw new Error("Missing access-key or secret-key"); } localFireflyClient = new FireflyClient(logger, false, accessKey, secretKey); await localFireflyClient.login(); } // MCP server should not write stdout logs as it listens to the stdout. Check if it is changed if (debug) { logger.setLevel(logger.levels.DEBUG, true); } else { logger.setLevel(logger.levels.SILENT, true); } logger.info("Starting Firefly MCP Server..."); // Create MCP server const server = new Server( { name: "Firefly MCP Server", version: "1.0.0", }, { capabilities: { tools: { inventoryTool: InventoryTool, codifyTool: CodifyTool, getPolicesTool: GetPolicesTool, createPolicyTool: CreatePolicyTool, updatePolicyTool: UpdatePolicyTool, deletePolicyTool: DeletePolicyTool, }, }, }, ); // Handle tool requests server.setRequestHandler( CallToolRequestSchema, async (request: CallToolRequest, extra: RequestHandlerExtra) => { logger.error("Received CallToolRequest:", request); try { const toolName = request.params.name; const args = request.params.arguments || {}; let fireflyClient: FireflyClient | null = null; if (!hosting) { fireflyClient = localFireflyClient; } if (hosting && extra.sessionId) { const sessionId = extra.sessionId; const transport = transports[sessionId]; if (!transport) { return { content: [{ type: "text", text: JSON.stringify({ error: "No active SSE connection found for sessionId:" + sessionId }) }], }; } fireflyClient = transport.fireflyClient; } if (!fireflyClient) { return { content: [{ type: "text", text: JSON.stringify({ error: "No active SSE connection found" }) }], }; } switch (toolName) { case "firefly_inventory": { const inventoryArgs = args as unknown as InventoryArgs; const response = await fireflyClient.inventory(inventoryArgs); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "firefly_codify": { const codifyArgs = args as unknown as CodifyArgs; if (!codifyArgs.assetType || !codifyArgs.assetId || !codifyArgs.iacType || !codifyArgs.provider || !codifyArgs.accountNumber) { throw new Error("Missing required arguments for codification"); } const response = await fireflyClient.codify(codifyArgs); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "firefly_get_policies": { const policiesArgs = args as unknown as GetInsightsArgs; const response = await fireflyClient.getInsights(policiesArgs); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "firefly_create_policy": { const createArgs = args as unknown as CreateInsightArgs; if (!createArgs.name || !createArgs.code || !createArgs.type || !createArgs.providerIds) { throw new Error("Missing required arguments for creating policy"); } const response = await fireflyClient.createInsight(createArgs); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "firefly_update_policy": { const updateArgs = args as unknown as UpdateInsightArgs; if (!updateArgs.id || !updateArgs.name || !updateArgs.code || !updateArgs.type || !updateArgs.providerIds) { throw new Error("Missing required arguments for updating policy"); } const response = await fireflyClient.updateInsight(updateArgs); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "firefly_delete_policy": { const deleteArgs = args as unknown as DeleteInsightArgs; if (!deleteArgs.id) { throw new Error("Missing required argument 'id' for deleting policy"); } const response = await fireflyClient.deleteInsight(deleteArgs); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } default: throw new Error(`Unknown tool: ${toolName}`); } } catch (error) { logger.error("Error executing tool:", error); return { content: [ { type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error), }), }, ], }; } }, ); // Handle tool listing server.setRequestHandler(ListToolsRequestSchema, async () => { logger.error("Received ListToolsRequest"); return { tools: [InventoryTool, CodifyTool, GetPolicesTool, CreatePolicyTool, UpdatePolicyTool, DeletePolicyTool], }; }); // Hosted server needs to reset every hour to prevent timeout requests and issues connecting to the server if (hosting) { const app = express(); app.get("/sse", async (req: express.Request, res: express.Response) => { logger.debug("Received connection"); // Authentication improved since then, this is not best practice let basicAuth = null; const authHeader = req.headers.authorization; if (authHeader && authHeader.startsWith("Basic ")) { basicAuth = authHeader.split(" ")[1]; } if (!basicAuth) { basicAuth = req.query.auth as string; } if (!basicAuth) { logger.warn("Authentication failed: Missing or invalid Authorization header."); res.status(401).send("Unauthorized"); return; } const [accessKey, secretKey] = Buffer.from(basicAuth, "base64").toString().split(":"); if (!accessKey || !secretKey) { logger.warn("Authentication failed: Missing or invalid Authorization header."); res.status(401).send("Unauthorized"); return; } const transport = new SSEServerTransport("/message", res); try { const fireflyClient = new FireflyClient(logger, true, accessKey, secretKey); await fireflyClient.login(); transports[transport.sessionId] = {transport, fireflyClient}; } catch (error) { if (error instanceof Error && error.message.includes("Authentication failed")) { logger.error("Authentication failed:", error); res.status(401).send("Unauthorized"); return; } logger.error("Error creating FireflyClient:", error); res.status(500).send("Internal Server Error"); return; } res.on("close", () => { delete transports[transport.sessionId]; }); await server.connect(transport); }); app.post("/message", async (req: express.Request, res: express.Response) => { logger.debug("Received message"); const sessionId = req.query.sessionId as string; const transport = transports[sessionId]; if (!transport) { logger.error("No active SSE connection found for sessionId:", sessionId); res.status(404).send("No active SSE connection found"); return; } await transport.transport.handlePostMessage(req, res); }); app.listen(port, () => { logger.info(`Server is running on port ${port}`); }); } else if (sse) { const app = express(); let transport: SSEServerTransport; app.get("/sse", async (req: express.Request, res: express.Response) => { logger.debug("Received connection"); transport = new SSEServerTransport("/message", res); await server.connect(transport); }); app.post("/message", async (req: express.Request, res: express.Response) => { logger.debug("Received message"); await transport.handlePostMessage(req, res); }); app.listen(port, () => { logger.info(`Server is running on port ${port}`); }); } else { // Connect server to Stdio transport const transport = new StdioServerTransport(); logger.info("Connecting server to transport..."); await server.connect(transport); logger.info("Server connected to transport"); } } main().catch((error) => { logger.error("Fatal error in main():", error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gofireflyio/firefly-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server