Skip to main content
Glama

Swagger/Postman MCP Server

by AlanGreyjoy
swagger-mcp-simple.ts16.5 kB
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import axios from "axios"; import SwaggerParser from "@apidevtools/swagger-parser"; import { Request, Response } from "express"; import { AuthConfig } from "./types.js"; export class SimpleSwaggerMcpServer { private mcpServer: McpServer; private swaggerSpec: any = null; private apiBaseUrl: string; private defaultAuth: AuthConfig | undefined; constructor(apiBaseUrl: string, defaultAuth?: AuthConfig) { this.apiBaseUrl = apiBaseUrl; this.defaultAuth = defaultAuth; this.mcpServer = new McpServer({ name: "Simple Swagger API MCP Server", version: "1.0.0", }); } async loadSwaggerSpec(specUrlOrFile: string) { console.debug("Loading Swagger specification from:", specUrlOrFile); try { this.swaggerSpec = (await SwaggerParser.parse(specUrlOrFile)) as any; const info = this.swaggerSpec.info; console.debug("Loaded Swagger spec:", { title: info.title, version: info.version, }); this.mcpServer = new McpServer({ name: info.title || "Swagger API Server", version: info.version || "1.0.0", description: info.description || undefined, }); await this.registerTools(); } catch (error) { console.error("Failed to load Swagger specification:", error); throw error; } } private getAuthHeaders(auth?: AuthConfig): Record<string, string> { const authConfig = auth || this.defaultAuth; if (!authConfig) return {}; switch (authConfig.type) { case "basic": if (authConfig.username && authConfig.password) { const credentials = Buffer.from( `${authConfig.username}:${authConfig.password}` ).toString("base64"); return { Authorization: `Basic ${credentials}` }; } break; case "bearer": if (authConfig.token) { return { Authorization: `Bearer ${authConfig.token}` }; } break; case "apiKey": if (authConfig.apiKey && authConfig.apiKeyName) { if (authConfig.apiKeyIn === "header") { return { [authConfig.apiKeyName]: authConfig.apiKey }; } } break; case "oauth2": if (authConfig.token) { return { Authorization: `Bearer ${authConfig.token}` }; } break; } return {}; } private getAuthQueryParams(auth?: AuthConfig): Record<string, string> { const authConfig = auth || this.defaultAuth; if (!authConfig) return {}; if ( authConfig.type === "apiKey" && authConfig.apiKey && authConfig.apiKeyName && authConfig.apiKeyIn === "query" ) { return { [authConfig.apiKeyName]: authConfig.apiKey }; } return {}; } private async registerTools() { console.debug("Starting tool registration process"); if (!this.swaggerSpec || !this.swaggerSpec.paths) { console.warn("No paths found in Swagger spec"); return; } const paths = this.swaggerSpec.paths; const totalPaths = Object.keys(paths).length; console.debug(`Found ${totalPaths} paths to process`); // Tool 1: List all available endpoints this.mcpServer.tool( "list_endpoints", "List all available API endpoints with basic information including path, method, summary, and tags", { input: z.object({ method: z .string() .optional() .describe("Filter by HTTP method (GET, POST, PUT, DELETE, etc.)"), tag: z.string().optional().describe("Filter by OpenAPI tag"), limit: z .number() .optional() .default(50) .describe("Maximum number of endpoints to return"), }), }, async ({ input }) => { const endpoints = []; for (const [path, pathItem] of Object.entries(paths)) { if (!pathItem) continue; for (const [method, operation] of Object.entries(pathItem as any)) { if (method === "$ref" || !operation) continue; const op = operation as any; const operationId = op.operationId || `${method}-${path}`; // Apply filters if ( input.method && method.toLowerCase() !== input.method.toLowerCase() ) continue; if (input.tag && (!op.tags || !op.tags.includes(input.tag))) continue; endpoints.push({ operationId, method: method.toUpperCase(), path, summary: op.summary || "", description: op.description || "", tags: op.tags || [], deprecated: op.deprecated || false, }); if (endpoints.length >= input.limit) break; } if (endpoints.length >= input.limit) break; } return { content: [ { type: "text", text: JSON.stringify( { total: endpoints.length, endpoints, }, null, 2 ), }, ], }; } ); // Tool 2: Get detailed information about a specific endpoint this.mcpServer.tool( "get_endpoint_details", "Get detailed information about a specific API endpoint including parameters, request/response schemas, and authentication requirements", { input: z.object({ operationId: z.string().describe("The operation ID of the endpoint"), path: z .string() .optional() .describe("The API path (alternative to operationId)"), method: z .string() .optional() .describe("The HTTP method (required if using path)"), }), }, async ({ input }) => { let targetOperation = null; let targetPath = ""; let targetMethod = ""; // Find the operation by operationId or path+method for (const [path, pathItem] of Object.entries(paths)) { if (!pathItem) continue; for (const [method, operation] of Object.entries(pathItem as any)) { if (method === "$ref" || !operation) continue; const op = operation as any; const operationId = op.operationId || `${method}-${path}`; if ( input.operationId === operationId || (input.path === path && input.method?.toLowerCase() === method.toLowerCase()) ) { targetOperation = op; targetPath = path; targetMethod = method; break; } } if (targetOperation) break; } if (!targetOperation) { return { content: [ { type: "text", text: `Endpoint not found. Use list_endpoints to see available endpoints.`, }, ], }; } // Extract parameter information const parameters = (targetOperation.parameters || []).map( (param: any) => ({ name: param.name, in: param.in, required: param.required || false, type: param.schema?.type || param.type, description: param.description || "", example: param.example || param.schema?.example, }) ); // Extract request body schema let requestBody = null; if (targetOperation.requestBody) { const rb = targetOperation.requestBody; const content = rb.content; if (content) { requestBody = Object.keys(content).map((mediaType) => ({ mediaType, schema: content[mediaType].schema, required: rb.required || false, })); } } // Extract response schemas const responses = Object.entries(targetOperation.responses || {}).map( ([code, resp]: [string, any]) => ({ statusCode: code, description: resp.description || "", schema: resp.content ? Object.keys(resp.content).map((mt) => ({ mediaType: mt, schema: resp.content[mt].schema, })) : null, }) ); return { content: [ { type: "text", text: JSON.stringify( { operationId: targetOperation.operationId || `${targetMethod}-${targetPath}`, method: targetMethod.toUpperCase(), path: targetPath, summary: targetOperation.summary || "", description: targetOperation.description || "", tags: targetOperation.tags || [], deprecated: targetOperation.deprecated || false, parameters, requestBody, responses, }, null, 2 ), }, ], }; } ); // Tool 3: Search endpoints by keyword this.mcpServer.tool( "search_endpoints", "Search API endpoints by keyword in path, summary, description, or tags", { input: z.object({ query: z .string() .describe( "Search term to look for in endpoint paths, summaries, descriptions, or tags" ), limit: z .number() .optional() .default(20) .describe("Maximum number of results to return"), }), }, async ({ input }) => { const results = []; const query = input.query.toLowerCase(); for (const [path, pathItem] of Object.entries(paths)) { if (!pathItem) continue; for (const [method, operation] of Object.entries(pathItem as any)) { if (method === "$ref" || !operation) continue; const op = operation as any; const operationId = op.operationId || `${method}-${path}`; // Search in various fields const searchText = [ path, op.summary || "", op.description || "", ...(op.tags || []), operationId, ] .join(" ") .toLowerCase(); if (searchText.includes(query)) { results.push({ operationId, method: method.toUpperCase(), path, summary: op.summary || "", description: op.description || "", tags: op.tags || [], }); } if (results.length >= input.limit) break; } if (results.length >= input.limit) break; } return { content: [ { type: "text", text: JSON.stringify( { query: input.query, total: results.length, results, }, null, 2 ), }, ], }; } ); // Tool 4: Make API call this.mcpServer.tool( "make_api_call", "Make an API call to any endpoint with the specified parameters and authentication", { input: z.object({ operationId: z .string() .optional() .describe("The operation ID of the endpoint"), path: z .string() .optional() .describe("The API path (alternative to operationId)"), method: z .string() .optional() .describe("The HTTP method (required if using path)"), parameters: z .record(z.any()) .optional() .describe("Query parameters, path parameters, or form data"), body: z .any() .optional() .describe("Request body (for POST, PUT, PATCH requests)"), auth: z .object({ type: z .enum(["none", "basic", "bearer", "apiKey", "oauth2"]) .default("none"), username: z.string().optional(), password: z.string().optional(), token: z.string().optional(), apiKey: z.string().optional(), apiKeyName: z.string().optional(), apiKeyIn: z.enum(["header", "query"]).optional(), }) .optional() .describe("Authentication configuration"), }), }, async ({ input }) => { // Find the operation let targetOperation = null; let targetPath = ""; let targetMethod = ""; for (const [path, pathItem] of Object.entries(paths)) { if (!pathItem) continue; for (const [method, operation] of Object.entries(pathItem as any)) { if (method === "$ref" || !operation) continue; const op = operation as any; const operationId = op.operationId || `${method}-${path}`; if ( input.operationId === operationId || (input.path === path && input.method?.toLowerCase() === method.toLowerCase()) ) { targetOperation = op; targetPath = path; targetMethod = method; break; } } if (targetOperation) break; } if (!targetOperation) { return { content: [ { type: "text", text: `Endpoint not found. Use list_endpoints to see available endpoints.`, }, ], }; } try { const params = input.parameters || {}; let url = this.apiBaseUrl + targetPath; // Handle path parameters const pathParams = new Set(); targetPath.split("/").forEach((segment) => { if (segment.startsWith("{") && segment.endsWith("}")) { pathParams.add(segment.slice(1, -1)); } }); Object.entries(params).forEach(([key, value]) => { if (pathParams.has(key)) { url = url.replace(`{${key}}`, encodeURIComponent(String(value))); } }); // Separate query parameters const queryParams = Object.entries(params) .filter(([key]) => !pathParams.has(key)) .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); const headers = this.getAuthHeaders( input.auth?.type !== "none" ? (input.auth as AuthConfig) : undefined ); const authQueryParams = this.getAuthQueryParams( input.auth?.type !== "none" ? (input.auth as AuthConfig) : undefined ); const response = await axios({ method: targetMethod as string, url: url, headers, data: input.body, params: { ...queryParams, ...authQueryParams }, }); return { content: [ { type: "text", text: JSON.stringify(response.data, null, 2) }, { type: "text", text: `HTTP Status Code: ${response.status}` }, ], }; } catch (error) { console.error(`Error in API call:`, error); if (axios.isAxiosError(error) && error.response) { return { content: [ { type: "text", text: `Error ${error.response.status}: ${JSON.stringify( error.response.data, null, 2 )}`, }, ], }; } return { content: [{ type: "text", text: `Error: ${error}` }], }; } } ); console.debug( "Successfully registered 4 strategic tools for API navigation" ); } getServer() { return this.mcpServer; } }

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/AlanGreyjoy/swag-mcp'

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