Skip to main content
Glama

Huawei CCE MCP Server

0
1
index.ts16.4 kB
#!/usr/bin/env node import { InMemoryEventStore } from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js'; import { randomUUID } from 'node:crypto'; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; // src import { HuaweiListClusters, HuaweiGetClusterById, HuaweiListNamespaces, HuaweiDeleteNamespace, HuaweiCreateNamespace, HuaweiListPods, HuaweiCreatePod, HuaweiReadPod, HuaweiGetNamespaceByName, HuaweiDeletePod, HuaweiListPodsByNamespace, HuaweiDeletePodsByNamespace } from './src/huawei/index.js'; import { HUAWEI_CCE_AUTH_TOKEN } from './src/huawei/constants.js'; import { HuaweiListClustersParamsSchema, HuaweiGetClusterByIdParamsSchema, HuaweiListNamespacesParamsSchema, HuaweiDeleteNamespaceParamsSchema, HuaweiCreateNamespaceParamsSchema, HuaweiListPodsParamsSchema, HuaweiCreatePodParamsSchema, HuaweiReadPodParamsSchema, HuaweiGetNamespaceByNameParamsSchema, HuaweiDeletePodParamsSchema, HuaweiListPodsByNamespaceParamsSchema, HuaweiDeletePodsByNamespaceParamsSchema, } from './schemas/index.js'; import express, { Request, Response } from "express"; const server = new Server({ name: "huawei-cce-mcp-server", version: "0.5.1", }, { capabilities: { tools: {}, resources: {} } }); logInfo("Huawei CCE MCP Server running on stdio"); logInfo(process.pid.toString()); import dotenv from 'dotenv'; import { logInfo } from "./utils/logs.js"; logInfo(process.cwd()); // dotenv.config(); logInfo(process.env.HUAWEI_CCE_AUTH_TOKEN?.toString() || "HUAWEI_CCE_AUTH_TOKEN environment variable is not set"); if (!HUAWEI_CCE_AUTH_TOKEN) { console.error("HUAWEI_CCE_AUTH_TOKEN environment ssssssss variable is not set"); process.exit(1); } // set up request handler for list tools, for each show name, description and input schema server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Cluster tools { name: "list_clusters", description: "List all clusters in a Huawei CCE project", inputSchema: zodToJsonSchema(HuaweiListClustersParamsSchema) }, { name: "get_cluster_by_id", description: "Get a specific cluster by ID", inputSchema: zodToJsonSchema(HuaweiGetClusterByIdParamsSchema) }, // Namespace tools { name: "list_namespaces", description: "List all namespaces in a Huawei CCE cluster", inputSchema: zodToJsonSchema(HuaweiListNamespacesParamsSchema) }, { name: "get_namespace_by_name", description: "Get a namespace in a Huawei CCE cluster by name", inputSchema: zodToJsonSchema(HuaweiGetNamespaceByNameParamsSchema) }, { name: "create_namespace", description: "Create a namespace in a Huawei CCE cluster", inputSchema: zodToJsonSchema(HuaweiCreateNamespaceParamsSchema) }, { name: "delete_namespace", description: "Delete a namespace in a Huawei CCE cluster", inputSchema: zodToJsonSchema(HuaweiDeleteNamespaceParamsSchema) }, // Pod tools { name: "list_pods", description: "List all pods in a Huawei CCE cluster", inputSchema: zodToJsonSchema(HuaweiListPodsParamsSchema) }, { name: "list_pods_by_namespace", description: "List all pods in a namespace in a Huawei CCE cluster", inputSchema: zodToJsonSchema(HuaweiListPodsByNamespaceParamsSchema) }, { name: "get_pod_by_name_and_namespace", description: "Get a specific pod in a Huawei CCE cluster by name and namespace", inputSchema: zodToJsonSchema(HuaweiReadPodParamsSchema) }, { name: "create_pod", description: "Create a pod in a Huawei CCE cluster", inputSchema: zodToJsonSchema(HuaweiCreatePodParamsSchema) }, { name: "delete_pod", description: "Delete a pod by name and namespace in a Huawei CCE cluster", inputSchema: zodToJsonSchema(HuaweiDeletePodParamsSchema) }, { name: "delete_pods_by_namespace", description: "Delete all pods in a namespace in a Huawei CCE cluster", inputSchema: zodToJsonSchema(HuaweiDeletePodsByNamespaceParamsSchema) }, ] }; }); // set up request handler for call tool, based on tool name, parse arguments with paramsSchema, // call the corresponding function, and return uniform response server.setRequestHandler(CallToolRequestSchema, async (request) => { try { if (!request.params.arguments) { throw new Error("Arguments are required"); } switch (request.params.name) { // Cluster tools case "list_clusters": { const args = HuaweiListClustersParamsSchema.parse(request.params.arguments); const clusters = await HuaweiListClusters(args.region, args.project_id); return { content: [{ type: "text", text: JSON.stringify(clusters, null, 2) }] }; } case "get_cluster_by_id": { const args = HuaweiGetClusterByIdParamsSchema.parse(request.params.arguments); const cluster = await HuaweiGetClusterById(args.region, args.project_id, args.cluster_id); return { content: [{ type: "text", text: JSON.stringify(cluster, null, 2) }] }; } // Namespace tools case "list_namespaces": { const args = HuaweiListNamespacesParamsSchema.parse(request.params.arguments); const namespaces = await HuaweiListNamespaces(args.region, args.cluster_id); return { content: [{ type: "text", text: JSON.stringify(namespaces, null, 2) }] }; } case "get_namespace_by_name": { const args = HuaweiGetNamespaceByNameParamsSchema.parse(request.params.arguments); const ns = await HuaweiGetNamespaceByName(args.region, args.cluster_id, args.name); return { content: [{ type: "text", text: JSON.stringify(ns, null, 2) }] }; } case "create_namespace": { const args = HuaweiCreateNamespaceParamsSchema.parse(request.params.arguments); const body = { apiVersion: "v1", kind: "Namespace", metadata: { name: args.name, } }; const { region, cluster_id } = args; const result = await HuaweiCreateNamespace(region, cluster_id, body); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } case "delete_namespace": { const args = HuaweiDeleteNamespaceParamsSchema.parse(request.params.arguments); const { region, cluster_id, name } = args; const result = await HuaweiDeleteNamespace(region, cluster_id, name); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } // Pod tools case "list_pods": { const args = HuaweiListPodsParamsSchema.parse(request.params.arguments); const { region, cluster_id } = args; const pods = await HuaweiListPods(region, cluster_id); return { content: [{ type: "text", text: JSON.stringify(pods, null, 2) }] }; } case "create_pod": { const args = HuaweiCreatePodParamsSchema.parse(request.params.arguments); const { region, cluster_id, namespace, pod_name, container_name, image } = args; const result = await HuaweiCreatePod(region, cluster_id, namespace, pod_name, container_name, image); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } case "get_pod_by_name_and_namespace": { const args = HuaweiReadPodParamsSchema.parse(request.params.arguments); const pod = await HuaweiReadPod(args.region, args.cluster_id, args.namespace, args.pod_name); return { content: [{ type: "text", text: JSON.stringify(pod, null, 2) }] }; } case "list_pods_by_namespace": { const args = HuaweiListPodsByNamespaceParamsSchema.parse(request.params.arguments); const pods = await HuaweiListPodsByNamespace(args.region, args.cluster_id, args.namespace); return { content: [{ type: "text", text: JSON.stringify(pods, null, 2) }] }; } case "delete_pod": { const args = HuaweiDeletePodParamsSchema.parse(request.params.arguments); const pod = await HuaweiDeletePod(args.region, args.cluster_id, args.namespace, args.pod_name); return { content: [{ type: "text", text: JSON.stringify(pod, null, 2) }] }; } case "delete_pods_by_namespace": { const args = HuaweiDeletePodsByNamespaceParamsSchema.parse(request.params.arguments); const podList = await HuaweiDeletePodsByNamespace(args.region, args.cluster_id, args.namespace); return { content: [{ type: "text", text: JSON.stringify(podList, null, 2) }] }; } // error in case of unknown tool name default: throw new Error(`Unknown tool: ${request.params.name}`); } } catch (error) { // catch parsing errors if (error instanceof z.ZodError) { throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`); } // catch other errors throw error; } }); // set up request handler to list resources // List available resources server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: "file:///logs/app.log", name: "Application Logs", mimeType: "text/plain" } ] }; }); // Read resource contents server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; if (uri === "file:///logs/app.log") { // const logContents = await readLogFile(); const logContents = "fdafdafds" return { contents: [ { uri, mimeType: "text/plain", text: logContents } ] }; } throw new Error("Resource not found"); }); // Parse CLI arguments to select mode const mode = process.argv[2] === 'streamableHttp' ? 'streamableHttp' : 'stdio'; // run stdio server async function runServer() { logInfo("Huawei CCE MCP Server running in stdio mode"); dotenv.config(); const transport = new StdioServerTransport(); console.error("Huawei CCE MCP Server running in stdio mode"); await server.connect(transport); } if (mode === 'stdio') { runServer().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); } else if (mode === 'streamableHttp') { // run streamable http server const app = express(); const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; app.post('/mcp', async (req: Request, res: Response) => { console.log('Received MCP POST request'); try { // Check for existing session ID const sessionId = req.headers['mcp-session-id'] as string | undefined; let transport: StreamableHTTPServerTransport; if (sessionId && transports[sessionId]) { // Reuse existing transport transport = transports[sessionId]; } else if (!sessionId) { // New initialization request const eventStore = new InMemoryEventStore(); transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), eventStore, // Enable resumability onsessioninitialized: (sessionId: string) => { // Store the transport by session ID when session is initialized // This avoids race conditions where requests might come in before the session is stored console.log(`Session initialized with ID: ${sessionId}`); transports[sessionId] = transport; } }); // Set up onclose handler to clean up transport when closed transport.onclose = () => { const sid = transport.sessionId; if (sid && transports[sid]) { console.log(`Transport closed for session ${sid}, removing from transports map`); delete transports[sid]; } }; // Connect the transport to the MCP server BEFORE handling the request // so responses can flow back through the same transport await server.connect(transport); await transport.handleRequest(req, res); return; // Already handled } else { // Invalid request - no session ID or not initialization request res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided', }, id: req?.body?.id, }); return; } // Handle the request with existing transport - no need to reconnect // The existing transport is already connected to the server await transport.handleRequest(req, res); } catch (error) { console.error('Error handling MCP request:', error); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: req?.body?.id, }); return; } } }); // Handle GET requests for SSE streams (using built-in support from StreamableHTTP) app.get('/mcp', async (req: Request, res: Response) => { console.log('Received MCP GET request'); const sessionId = req.headers['mcp-session-id'] as string | undefined; if (!sessionId || !transports[sessionId]) { res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided', }, id: req?.body?.id, }); return; } // Check for Last-Event-ID header for resumability const lastEventId = req.headers['last-event-id'] as string | undefined; if (lastEventId) { console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`); } else { console.log(`Establishing new SSE stream for session ${sessionId}`); } const transport = transports[sessionId]; await transport.handleRequest(req, res); }); // Handle DELETE requests for session termination (according to MCP spec) app.delete('/mcp', async (req: Request, res: Response) => { const sessionId = req.headers['mcp-session-id'] as string | undefined; if (!sessionId || !transports[sessionId]) { res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided', }, id: req?.body?.id, }); return; } console.log(`Received session termination request for session ${sessionId}`); try { const transport = transports[sessionId]; await transport.handleRequest(req, res); } catch (error) { console.error('Error handling session termination:', error); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Error handling session termination', }, id: req?.body?.id, }); return; } } }); // Start the server const PORT = process.env.PORT || 3001; app.listen(PORT, () => { console.log(`MCP Streamable HTTP Server listening on port ${PORT}`); }); // Handle server shutdown process.on('SIGINT', async () => { console.log('Shutting down server...'); // Close all active transports to properly clean up resources for (const sessionId in transports) { try { console.log(`Closing transport for session ${sessionId}`); await transports[sessionId].close(); delete transports[sessionId]; } catch (error) { console.error(`Error closing transport for session ${sessionId}:`, error); } } await server.close(); console.log('Server shutdown complete'); process.exit(0); }); }

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/TehilaTheStudent/try-cce-gitlab'

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