# Directory Structure ``` ├── .gitignore ├── example-usage.mjs ├── index.mjs ├── package-lock.json ├── package.json ├── README.md └── sample-petstore.yaml ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | node_modules 2 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # OpenAPI Schema Model Context Protocol Server 2 | 3 | A Model Context Protocol (MCP) server that exposes OpenAPI schema information to Large Language Models (LLMs) like Claude. This server allows an LLM to explore and understand OpenAPI specifications through a set of specialized tools. 4 | 5 | ## Features 6 | 7 | - Load any OpenAPI schema file (JSON or YAML) specified via command line argument 8 | - Explore API paths, operations, parameters, and schemas 9 | - View detailed request and response schemas 10 | - Look up component definitions and examples 11 | - Search across the entire API specification 12 | - Get responses in YAML format for better LLM comprehension 13 | 14 | ## Usage 15 | 16 | ### Command Line 17 | 18 | Run the MCP server with a specific schema file: 19 | 20 | ```bash 21 | # Use the default openapi.yaml in current directory 22 | npx -y mcp-openapi-schema 23 | 24 | # Use a specific schema file (relative path) 25 | npx -y mcp-openapi-schema ../petstore.json 26 | 27 | # Use a specific schema file (absolute path) 28 | npx -y mcp-openapi-schema /absolute/path/to/api-spec.yaml 29 | 30 | # Show help 31 | npx -y mcp-openapi-schema --help 32 | ``` 33 | 34 | ### Claude Desktop Integration 35 | 36 | To use this MCP server with Claude Desktop, edit your `claude_desktop_config.json` configuration file: 37 | 38 | ```json 39 | { 40 | "mcpServers": { 41 | "OpenAPI Schema": { 42 | "command": "npx", 43 | "args": ["-y", "mcp-openapi-schema", "/ABSOLUTE/PATH/TO/openapi.yaml"] 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | Location of the configuration file: 50 | 51 | - macOS/Linux: `~/Library/Application Support/Claude/claude_desktop_config.json` 52 | - Windows: `$env:AppData\Claude\claude_desktop_config.json` 53 | 54 | ### Claude Code Integration 55 | 56 | To use this MCP server with Claude Code CLI, follow these steps: 57 | 58 | 1. **Add the OpenAPI Schema MCP server to Claude Code** 59 | 60 | ```bash 61 | # Basic syntax 62 | claude mcp add openapi-schema npx -y mcp-openapi-schema 63 | 64 | # Example with specific schema 65 | claude mcp add petstore-api npx -y mcp-openapi-schema ~/Projects/petstore.yaml 66 | ``` 67 | 68 | 2. **Verify the MCP server is registered** 69 | 70 | ```bash 71 | # List all configured servers 72 | claude mcp list 73 | 74 | # Get details for your OpenAPI schema server 75 | claude mcp get openapi-schema 76 | ``` 77 | 78 | 3. **Remove the server if needed** 79 | 80 | ```bash 81 | claude mcp remove openapi-schema 82 | ``` 83 | 84 | 4. **Use the tool in Claude Code** 85 | 86 | Once configured, you can invoke the tool in your Claude Code session by asking questions about the OpenAPI schema. 87 | 88 | **Tips:** 89 | 90 | - Use the `-s` or `--scope` flag with `project` (default) or `global` to specify where the configuration is stored 91 | - Add multiple MCP servers for different APIs with different names 92 | 93 | ## MCP Tools 94 | 95 | The server provides the following tools for LLMs to interact with OpenAPI schemas: 96 | 97 | - `list-endpoints`: Lists all API paths and their HTTP methods with summaries in a nested object structure 98 | - `get-endpoint`: Gets detailed information about a specific endpoint including parameters and responses 99 | - `get-request-body`: Gets the request body schema for a specific endpoint and method 100 | - `get-response-schema`: Gets the response schema for a specific endpoint, method, and status code 101 | - `get-path-parameters`: Gets the parameters for a specific path 102 | - `list-components`: Lists all schema components (schemas, responses, parameters, etc.) 103 | - `get-component`: Gets detailed definition for a specific component 104 | - `list-security-schemes`: Lists all available security schemes 105 | - `get-examples`: Gets examples for a specific component or endpoint 106 | - `search-schema`: Searches across paths, operations, and schemas 107 | 108 | ## Examples 109 | 110 | Example queries to try: 111 | 112 | ``` 113 | What endpoints are available in this API? 114 | Show me the details for the POST /pets endpoint. 115 | What parameters does the GET /pets/{petId} endpoint take? 116 | What is the request body schema for creating a new pet? 117 | What response will I get from the DELETE /pets/{petId} endpoint? 118 | What schemas are defined in this API? 119 | Show me the definition of the Pet schema. 120 | What are the available security schemes for this API? 121 | Are there any example responses for getting a pet by ID? 122 | Search for anything related to "user" in this API. 123 | ``` ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "mcp-openapi-schema", 3 | "version": "0.0.1", 4 | "description": "A Model Context Protocol server that exposes OpenAPI schema information to Large Language Models", 5 | "type": "module", 6 | "main": "index.mjs", 7 | "bin": { 8 | "mcp-openapi-schema": "index.mjs" 9 | }, 10 | "scripts": { 11 | "start": "node index.mjs", 12 | "test": "node example-usage.mjs" 13 | }, 14 | "keywords": [ 15 | "openapi", 16 | "swagger", 17 | "mcp", 18 | "llm", 19 | "claude" 20 | ], 21 | "author": "Hannes Junnila", 22 | "license": "MIT", 23 | "dependencies": { 24 | "@apidevtools/swagger-parser": "^10.1.1", 25 | "@modelcontextprotocol/sdk": "^1.7.0", 26 | "js-yaml": "^4.1.0", 27 | "zod": "^3.24.2" 28 | } 29 | } 30 | ``` -------------------------------------------------------------------------------- /example-usage.mjs: -------------------------------------------------------------------------------- ``` 1 | // This is a simple example of how to test the MCP OpenAPI Schema server 2 | // using the official MCP SDK client 3 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; 4 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; 5 | import { dirname, resolve } from "path"; 6 | import { fileURLToPath } from "url"; 7 | 8 | const __dirname = dirname(fileURLToPath(import.meta.url)); 9 | 10 | // Set up the MCP client to communicate with our server 11 | const transport = new StdioClientTransport({ 12 | command: "node", 13 | args: [ 14 | resolve(__dirname, "index.mjs"), 15 | resolve(__dirname, "./sample-petstore.yaml") 16 | ], 17 | }); 18 | 19 | const client = new Client({ 20 | name: "openapi-schema-client", 21 | version: "1.0.0", 22 | }); 23 | 24 | // Connect to the server 25 | console.log("Connecting to MCP server..."); 26 | await client.connect(transport); 27 | console.log("Connected to MCP server successfully!"); 28 | 29 | // Run example tool calls 30 | try { 31 | // List endpoints 32 | console.log("\n--- LISTING ENDPOINTS ---"); 33 | const endpoints = await client.callTool({ 34 | name: "list-endpoints", 35 | arguments: {}, 36 | }); 37 | console.log(endpoints.content[0].text); 38 | 39 | // Get endpoint details 40 | console.log("\n--- GET ENDPOINT DETAILS ---"); 41 | const endpointDetails = await client.callTool({ 42 | name: "get-endpoint", 43 | arguments: { 44 | path: "/pets", 45 | method: "get", 46 | }, 47 | }); 48 | console.log(endpointDetails.content[0].text); 49 | 50 | // Get request body schema 51 | console.log("\n--- GET REQUEST BODY SCHEMA ---"); 52 | const requestBody = await client.callTool({ 53 | name: "get-request-body", 54 | arguments: { 55 | path: "/pets", 56 | method: "post", 57 | }, 58 | }); 59 | console.log(requestBody.content[0].text); 60 | 61 | // List components 62 | console.log("\n--- LIST COMPONENTS ---"); 63 | const components = await client.callTool({ 64 | name: "list-components", 65 | arguments: {}, 66 | }); 67 | console.log(components.content[0].text); 68 | 69 | // Get component schema 70 | console.log("\n--- GET COMPONENT SCHEMA ---"); 71 | const component = await client.callTool({ 72 | name: "get-component", 73 | arguments: { 74 | type: "schemas", 75 | name: "Pet", 76 | }, 77 | }); 78 | console.log(component.content[0].text); 79 | 80 | // Search schema 81 | console.log("\n--- SEARCH SCHEMA ---"); 82 | const searchResults = await client.callTool({ 83 | name: "search-schema", 84 | arguments: { 85 | pattern: "pet", 86 | }, 87 | }); 88 | console.log(searchResults.content[0].text); 89 | 90 | // Get path parameters 91 | console.log("\n--- GET PATH PARAMETERS ---"); 92 | const parameters = await client.callTool({ 93 | name: "get-path-parameters", 94 | arguments: { 95 | path: "/pets/{petId}", 96 | method: "get", 97 | }, 98 | }); 99 | console.log(parameters.content[0].text); 100 | 101 | // Get response schema 102 | console.log("\n--- GET RESPONSE SCHEMA ---"); 103 | const response = await client.callTool({ 104 | name: "get-response-schema", 105 | arguments: { 106 | path: "/pets/{petId}", 107 | method: "get", 108 | statusCode: "200", 109 | }, 110 | }); 111 | console.log(response.content[0].text); 112 | 113 | // List security schemes 114 | console.log("\n--- LIST SECURITY SCHEMES ---"); 115 | const security = await client.callTool({ 116 | name: "list-security-schemes", 117 | arguments: {}, 118 | }); 119 | console.log(security.content[0].text); 120 | } catch (error) { 121 | console.error("Error during testing:", error); 122 | } finally { 123 | // Close the connection 124 | await client.close(); 125 | console.log("\nTests completed, disconnected from server."); 126 | } 127 | ``` -------------------------------------------------------------------------------- /sample-petstore.yaml: -------------------------------------------------------------------------------- ```yaml 1 | openapi: 3.0.0 2 | info: 3 | title: Petstore API 4 | version: 1.0.0 5 | description: A sample API for managing pets 6 | servers: 7 | - url: https://petstore.example.com/api/v1 8 | paths: 9 | /pets: 10 | get: 11 | summary: List all pets 12 | description: Returns all pets from the system 13 | operationId: listPets 14 | tags: 15 | - pets 16 | parameters: 17 | - name: limit 18 | in: query 19 | description: Maximum number of pets to return 20 | required: false 21 | schema: 22 | type: integer 23 | format: int32 24 | minimum: 1 25 | maximum: 100 26 | default: 20 27 | responses: 28 | '200': 29 | description: A paged array of pets 30 | content: 31 | application/json: 32 | schema: 33 | $ref: '#/components/schemas/PetsResponse' 34 | example: 35 | pets: 36 | - id: 1 37 | name: "Fluffy" 38 | status: "available" 39 | category: "cat" 40 | - id: 2 41 | name: "Rex" 42 | status: "pending" 43 | category: "dog" 44 | total: 2 45 | limit: 20 46 | offset: 0 47 | post: 48 | summary: Create a pet 49 | description: Creates a new pet in the store 50 | operationId: createPet 51 | tags: 52 | - pets 53 | requestBody: 54 | description: Pet to add to the store 55 | required: true 56 | content: 57 | application/json: 58 | schema: 59 | $ref: '#/components/schemas/NewPet' 60 | example: 61 | name: "Fluffy" 62 | category: "cat" 63 | responses: 64 | '201': 65 | description: Pet created 66 | content: 67 | application/json: 68 | schema: 69 | $ref: '#/components/schemas/Pet' 70 | example: 71 | id: 1 72 | name: "Fluffy" 73 | status: "available" 74 | category: "cat" 75 | '400': 76 | description: Invalid input 77 | content: 78 | application/json: 79 | schema: 80 | $ref: '#/components/schemas/Error' 81 | example: 82 | code: 400 83 | message: "Invalid input: name is required" 84 | /pets/{petId}: 85 | get: 86 | summary: Get a pet by ID 87 | description: Returns a single pet by ID 88 | operationId: getPetById 89 | tags: 90 | - pets 91 | parameters: 92 | - name: petId 93 | in: path 94 | description: ID of pet to return 95 | required: true 96 | schema: 97 | type: integer 98 | format: int64 99 | responses: 100 | '200': 101 | description: Expected response to a valid request 102 | content: 103 | application/json: 104 | schema: 105 | $ref: '#/components/schemas/Pet' 106 | example: 107 | id: 1 108 | name: "Fluffy" 109 | status: "available" 110 | category: "cat" 111 | '404': 112 | description: Pet not found 113 | content: 114 | application/json: 115 | schema: 116 | $ref: '#/components/schemas/Error' 117 | example: 118 | code: 404 119 | message: "Pet not found" 120 | delete: 121 | summary: Delete a pet 122 | description: Deletes a pet by ID 123 | operationId: deletePet 124 | tags: 125 | - pets 126 | parameters: 127 | - name: petId 128 | in: path 129 | description: ID of pet to delete 130 | required: true 131 | schema: 132 | type: integer 133 | format: int64 134 | responses: 135 | '204': 136 | description: Pet deleted 137 | '404': 138 | description: Pet not found 139 | content: 140 | application/json: 141 | schema: 142 | $ref: '#/components/schemas/Error' 143 | components: 144 | schemas: 145 | Pet: 146 | type: object 147 | required: 148 | - id 149 | - name 150 | properties: 151 | id: 152 | type: integer 153 | format: int64 154 | description: Unique identifier for the pet 155 | name: 156 | type: string 157 | description: Name of the pet 158 | status: 159 | type: string 160 | description: Status of the pet 161 | enum: 162 | - available 163 | - pending 164 | - sold 165 | default: available 166 | category: 167 | type: string 168 | description: Category of the pet 169 | NewPet: 170 | type: object 171 | required: 172 | - name 173 | properties: 174 | name: 175 | type: string 176 | description: Name of the pet 177 | category: 178 | type: string 179 | description: Category of the pet 180 | PetsResponse: 181 | type: object 182 | required: 183 | - pets 184 | - total 185 | properties: 186 | pets: 187 | type: array 188 | items: 189 | $ref: '#/components/schemas/Pet' 190 | total: 191 | type: integer 192 | description: Total number of pets 193 | limit: 194 | type: integer 195 | description: Limit used in the request 196 | offset: 197 | type: integer 198 | description: Offset used in the request 199 | Error: 200 | type: object 201 | required: 202 | - code 203 | - message 204 | properties: 205 | code: 206 | type: integer 207 | format: int32 208 | description: Error code 209 | message: 210 | type: string 211 | description: Error message 212 | securitySchemes: 213 | ApiKeyAuth: 214 | type: apiKey 215 | in: header 216 | name: X-API-Key 217 | description: API key for authorization 218 | security: 219 | - ApiKeyAuth: [] ``` -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- ``` 1 | #!/usr/bin/env node 2 | import SwaggerParser from "@apidevtools/swagger-parser"; 3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 5 | import yaml from "js-yaml"; 6 | import { Console } from "node:console"; 7 | import { readFile } from "node:fs/promises"; 8 | import { resolve } from "node:path"; 9 | import { z } from "zod"; 10 | 11 | // Redirect console output to stderr to avoid interfering with MCP comms 12 | globalThis.console = new Console(process.stderr); 13 | 14 | const args = process.argv.slice(2); 15 | 16 | if (args.includes("--help") || args.includes("-h")) { 17 | console.log(` 18 | OpenAPI Schema Model Context Protocol Server 19 | 20 | Usage: 21 | node index.mjs [path/to/openapi.yaml] 22 | 23 | Arguments: 24 | path/to/openapi.yaml Path to the OpenAPI schema file (JSON or YAML) (optional) 25 | If not provided, defaults to openapi.yaml 26 | 27 | Examples: 28 | node index.mjs # Uses default openapi.yaml 29 | node index.mjs ../petstore.json # Uses petstore OpenAPI spec 30 | node index.mjs /absolute/path/to/api-schema.yaml 31 | `); 32 | process.exit(0); 33 | } 34 | 35 | const schemaArg = args[0]; 36 | 37 | const loadSchema = async () => { 38 | // Default to openapi.yaml if no argument provided 39 | const schemaPath = resolve(schemaArg ?? "openapi.yaml"); 40 | 41 | try { 42 | // Parse and validate the OpenAPI document 43 | return await SwaggerParser.validate(schemaPath, { validate: { schema: false } }); 44 | } catch (error) { 45 | console.error(`Error loading schema: ${error.message}`); 46 | process.exit(1); 47 | } 48 | }; 49 | 50 | const openApiDoc = await loadSchema(); 51 | 52 | // Extract schema name from file path or from the OpenAPI info 53 | const schemaName = 54 | openApiDoc.info?.title || 55 | (schemaArg 56 | ? schemaArg 57 | .split("/") 58 | .pop() 59 | .replace(/\.(yaml|json)$/i, "") 60 | : "openapi"); 61 | 62 | const server = new McpServer({ 63 | name: `OpenAPI Schema: ${schemaName}`, 64 | version: "1.0.0", 65 | description: `Provides OpenAPI schema information for ${schemaName} (${openApiDoc.info?.version || "unknown version"})`, 66 | }); 67 | 68 | // Helper to convert objects to YAML for better readability 69 | const toYaml = (obj) => yaml.dump(obj, { lineWidth: 100, noRefs: true }); 70 | 71 | // List all API paths and operations 72 | server.tool( 73 | "list-endpoints", 74 | "Lists all API paths and their HTTP methods with summaries, organized by path", 75 | () => { 76 | const pathMap = {}; 77 | 78 | for (const [path, pathItem] of Object.entries(openApiDoc.paths || {})) { 79 | // Get all HTTP methods for this path 80 | const methods = Object.keys(pathItem).filter((key) => 81 | ["get", "post", "put", "delete", "patch", "options", "head"].includes(key.toLowerCase()), 82 | ); 83 | 84 | // Create a methods object for this path 85 | pathMap[path] = {}; 86 | 87 | // Add each method with its summary 88 | for (const method of methods) { 89 | const operation = pathItem[method]; 90 | pathMap[path][method.toUpperCase()] = operation.summary || "No summary"; 91 | } 92 | } 93 | 94 | return { 95 | content: [ 96 | { 97 | type: "text", 98 | text: toYaml(pathMap), 99 | }, 100 | ], 101 | }; 102 | }, 103 | ); 104 | 105 | // Get details for a specific endpoint 106 | server.tool( 107 | "get-endpoint", 108 | "Gets detailed information about a specific API endpoint", 109 | { path: z.string(), method: z.string() }, 110 | ({ path, method }) => { 111 | const pathItem = openApiDoc.paths?.[path]; 112 | if (!pathItem) { 113 | return { content: [{ type: "text", text: `Path ${path} not found` }] }; 114 | } 115 | 116 | const operation = pathItem[method.toLowerCase()]; 117 | if (!operation) { 118 | return { content: [{ type: "text", text: `Method ${method} not found for path ${path}` }] }; 119 | } 120 | 121 | // Extract relevant information 122 | const endpoint = { 123 | path, 124 | method: method.toUpperCase(), 125 | summary: operation.summary, 126 | description: operation.description, 127 | tags: operation.tags, 128 | parameters: operation.parameters, 129 | requestBody: operation.requestBody, 130 | responses: operation.responses, 131 | security: operation.security, 132 | deprecated: operation.deprecated, 133 | }; 134 | 135 | return { 136 | content: [ 137 | { 138 | type: "text", 139 | text: toYaml(endpoint), 140 | }, 141 | ], 142 | }; 143 | }, 144 | ); 145 | 146 | // Get request body schema for a specific endpoint 147 | server.tool( 148 | "get-request-body", 149 | "Gets the request body schema for a specific endpoint", 150 | { path: z.string(), method: z.string() }, 151 | ({ path, method }) => { 152 | const pathItem = openApiDoc.paths?.[path]; 153 | if (!pathItem) { 154 | return { content: [{ type: "text", text: `Path ${path} not found` }] }; 155 | } 156 | 157 | const operation = pathItem[method.toLowerCase()]; 158 | if (!operation) { 159 | return { content: [{ type: "text", text: `Method ${method} not found for path ${path}` }] }; 160 | } 161 | 162 | const requestBody = operation.requestBody; 163 | if (!requestBody) { 164 | return { content: [{ type: "text", text: `No request body defined for ${method} ${path}` }] }; 165 | } 166 | 167 | return { 168 | content: [ 169 | { 170 | type: "text", 171 | text: toYaml(requestBody), 172 | }, 173 | ], 174 | }; 175 | }, 176 | ); 177 | 178 | // Get response schema for a specific endpoint and status code 179 | server.tool( 180 | "get-response-schema", 181 | "Gets the response schema for a specific endpoint, method, and status code", 182 | { 183 | path: z.string(), 184 | method: z.string(), 185 | statusCode: z.string().default("200"), 186 | }, 187 | ({ path, method, statusCode }) => { 188 | const pathItem = openApiDoc.paths?.[path]; 189 | if (!pathItem) { 190 | return { content: [{ type: "text", text: `Path ${path} not found` }] }; 191 | } 192 | 193 | const operation = pathItem[method.toLowerCase()]; 194 | if (!operation) { 195 | return { content: [{ type: "text", text: `Method ${method} not found for path ${path}` }] }; 196 | } 197 | 198 | const responses = operation.responses; 199 | if (!responses) { 200 | return { content: [{ type: "text", text: `No responses defined for ${method} ${path}` }] }; 201 | } 202 | 203 | const response = responses[statusCode] || responses.default; 204 | if (!response) { 205 | return { 206 | content: [ 207 | { 208 | type: "text", 209 | text: `No response for status code ${statusCode} (or default) found for ${method} ${path}.\nAvailable status codes: ${Object.keys(responses).join(", ")}`, 210 | }, 211 | ], 212 | }; 213 | } 214 | 215 | return { 216 | content: [ 217 | { 218 | type: "text", 219 | text: toYaml(response), 220 | }, 221 | ], 222 | }; 223 | }, 224 | ); 225 | 226 | // Get parameters for a specific path 227 | server.tool( 228 | "get-path-parameters", 229 | "Gets the parameters for a specific path", 230 | { path: z.string(), method: z.string().optional() }, 231 | ({ path, method }) => { 232 | const pathItem = openApiDoc.paths?.[path]; 233 | if (!pathItem) { 234 | return { content: [{ type: "text", text: `Path ${path} not found` }] }; 235 | } 236 | 237 | let parameters = [...(pathItem.parameters || [])]; 238 | 239 | // If method is specified, add method-specific parameters 240 | if (method) { 241 | const operation = pathItem[method.toLowerCase()]; 242 | if (operation && operation.parameters) { 243 | parameters = [...parameters, ...operation.parameters]; 244 | } 245 | } 246 | 247 | if (parameters.length === 0) { 248 | return { 249 | content: [ 250 | { 251 | type: "text", 252 | text: `No parameters found for ${method ? `${method.toUpperCase()} ` : ""}${path}`, 253 | }, 254 | ], 255 | }; 256 | } 257 | 258 | return { 259 | content: [ 260 | { 261 | type: "text", 262 | text: toYaml(parameters), 263 | }, 264 | ], 265 | }; 266 | }, 267 | ); 268 | 269 | // List all components 270 | server.tool( 271 | "list-components", 272 | "Lists all schema components (schemas, parameters, responses, etc.)", 273 | () => { 274 | const components = openApiDoc.components || {}; 275 | const result = {}; 276 | 277 | // For each component type, list the keys 278 | for (const [type, items] of Object.entries(components)) { 279 | if (items && typeof items === "object") { 280 | result[type] = Object.keys(items); 281 | } 282 | } 283 | 284 | return { 285 | content: [ 286 | { 287 | type: "text", 288 | text: toYaml(result), 289 | }, 290 | ], 291 | }; 292 | }, 293 | ); 294 | 295 | // Get a specific component 296 | server.tool( 297 | "get-component", 298 | "Gets detailed definition for a specific component", 299 | { 300 | type: z.string().describe("Component type (e.g., schemas, parameters, responses)"), 301 | name: z.string().describe("Component name"), 302 | }, 303 | ({ type, name }) => { 304 | const components = openApiDoc.components || {}; 305 | const componentType = components[type]; 306 | 307 | if (!componentType) { 308 | return { 309 | content: [ 310 | { 311 | type: "text", 312 | text: `Component type '${type}' not found. Available types: ${Object.keys(components).join(", ")}`, 313 | }, 314 | ], 315 | }; 316 | } 317 | 318 | const component = componentType[name]; 319 | if (!component) { 320 | return { 321 | content: [ 322 | { 323 | type: "text", 324 | text: `Component '${name}' not found in '${type}'. Available components: ${Object.keys(componentType).join(", ")}`, 325 | }, 326 | ], 327 | }; 328 | } 329 | 330 | return { 331 | content: [ 332 | { 333 | type: "text", 334 | text: toYaml(component), 335 | }, 336 | ], 337 | }; 338 | }, 339 | ); 340 | 341 | // List security schemes 342 | server.tool("list-security-schemes", "Lists all available security schemes", () => { 343 | const securitySchemes = openApiDoc.components?.securitySchemes || {}; 344 | const result = {}; 345 | 346 | for (const [name, scheme] of Object.entries(securitySchemes)) { 347 | result[name] = { 348 | type: scheme.type, 349 | description: scheme.description, 350 | ...(scheme.type === "oauth2" ? { flows: Object.keys(scheme.flows || {}) } : {}), 351 | ...(scheme.type === "apiKey" ? { in: scheme.in, name: scheme.name } : {}), 352 | ...(scheme.type === "http" ? { scheme: scheme.scheme } : {}), 353 | }; 354 | } 355 | 356 | if (Object.keys(result).length === 0) { 357 | return { content: [{ type: "text", text: "No security schemes defined in this API" }] }; 358 | } 359 | 360 | return { 361 | content: [ 362 | { 363 | type: "text", 364 | text: toYaml(result), 365 | }, 366 | ], 367 | }; 368 | }); 369 | 370 | // Get examples 371 | server.tool( 372 | "get-examples", 373 | "Gets examples for a specific component or endpoint", 374 | { 375 | type: z.enum(["request", "response", "component"]).describe("Type of example to retrieve"), 376 | path: z.string().optional().describe("API path (required for request/response examples)"), 377 | method: z.string().optional().describe("HTTP method (required for request/response examples)"), 378 | statusCode: z.string().optional().describe("Status code (for response examples)"), 379 | componentType: z 380 | .string() 381 | .optional() 382 | .describe("Component type (required for component examples)"), 383 | componentName: z 384 | .string() 385 | .optional() 386 | .describe("Component name (required for component examples)"), 387 | }, 388 | ({ type, path, method, statusCode, componentType, componentName }) => { 389 | if (type === "request") { 390 | if (!path || !method) { 391 | return { 392 | content: [{ type: "text", text: "Path and method are required for request examples" }], 393 | }; 394 | } 395 | 396 | const operation = openApiDoc.paths?.[path]?.[method.toLowerCase()]; 397 | if (!operation) { 398 | return { 399 | content: [{ type: "text", text: `Operation ${method.toUpperCase()} ${path} not found` }], 400 | }; 401 | } 402 | 403 | if (!operation.requestBody?.content) { 404 | return { 405 | content: [ 406 | { type: "text", text: `No request body defined for ${method.toUpperCase()} ${path}` }, 407 | ], 408 | }; 409 | } 410 | 411 | const examples = {}; 412 | for (const [contentType, content] of Object.entries(operation.requestBody.content)) { 413 | if (content.examples) { 414 | examples[contentType] = content.examples; 415 | } else if (content.example) { 416 | examples[contentType] = { default: { value: content.example } }; 417 | } 418 | } 419 | 420 | if (Object.keys(examples).length === 0) { 421 | return { 422 | content: [ 423 | { type: "text", text: `No examples found for ${method.toUpperCase()} ${path} request` }, 424 | ], 425 | }; 426 | } 427 | 428 | return { 429 | content: [ 430 | { 431 | type: "text", 432 | text: toYaml(examples), 433 | }, 434 | ], 435 | }; 436 | } else if (type === "response") { 437 | if (!path || !method) { 438 | return { 439 | content: [{ type: "text", text: "Path and method are required for response examples" }], 440 | }; 441 | } 442 | 443 | const operation = openApiDoc.paths?.[path]?.[method.toLowerCase()]; 444 | if (!operation) { 445 | return { 446 | content: [{ type: "text", text: `Operation ${method.toUpperCase()} ${path} not found` }], 447 | }; 448 | } 449 | 450 | if (!operation.responses) { 451 | return { 452 | content: [ 453 | { type: "text", text: `No responses defined for ${method.toUpperCase()} ${path}` }, 454 | ], 455 | }; 456 | } 457 | 458 | const responseObj = statusCode 459 | ? operation.responses[statusCode] 460 | : Object.values(operation.responses)[0]; 461 | if (!responseObj) { 462 | return { 463 | content: [ 464 | { 465 | type: "text", 466 | text: `Response ${statusCode} not found for ${method.toUpperCase()} ${path}. Available: ${Object.keys(operation.responses).join(", ")}`, 467 | }, 468 | ], 469 | }; 470 | } 471 | 472 | if (!responseObj.content) { 473 | return { content: [{ type: "text", text: `No content defined in response` }] }; 474 | } 475 | 476 | const examples = {}; 477 | for (const [contentType, content] of Object.entries(responseObj.content)) { 478 | if (content.examples) { 479 | examples[contentType] = content.examples; 480 | } else if (content.example) { 481 | examples[contentType] = { default: { value: content.example } }; 482 | } 483 | } 484 | 485 | if (Object.keys(examples).length === 0) { 486 | return { 487 | content: [ 488 | { 489 | type: "text", 490 | text: `No examples found for ${method.toUpperCase()} ${path} response${statusCode ? ` ${statusCode}` : ""}`, 491 | }, 492 | ], 493 | }; 494 | } 495 | 496 | return { 497 | content: [ 498 | { 499 | type: "text", 500 | text: toYaml(examples), 501 | }, 502 | ], 503 | }; 504 | } else if (type === "component") { 505 | if (!componentType || !componentName) { 506 | return { 507 | content: [ 508 | { type: "text", text: "Component type and name are required for component examples" }, 509 | ], 510 | }; 511 | } 512 | 513 | const component = openApiDoc.components?.[componentType]?.[componentName]; 514 | if (!component) { 515 | return { 516 | content: [ 517 | { type: "text", text: `Component ${componentType}.${componentName} not found` }, 518 | ], 519 | }; 520 | } 521 | 522 | const examples = 523 | component.examples || (component.example ? { default: component.example } : null); 524 | if (!examples) { 525 | return { 526 | content: [ 527 | { 528 | type: "text", 529 | text: `No examples found for component ${componentType}.${componentName}`, 530 | }, 531 | ], 532 | }; 533 | } 534 | 535 | return { 536 | content: [ 537 | { 538 | type: "text", 539 | text: toYaml(examples), 540 | }, 541 | ], 542 | }; 543 | } 544 | }, 545 | ); 546 | 547 | // Search across the API specification 548 | server.tool( 549 | "search-schema", 550 | "Searches across paths, operations, and schemas", 551 | { pattern: z.string().describe("Search pattern (case-insensitive)") }, 552 | ({ pattern }) => { 553 | const searchRegex = new RegExp(pattern, "i"); 554 | const results = { 555 | paths: [], 556 | operations: [], 557 | components: [], 558 | parameters: [], 559 | securitySchemes: [], 560 | }; 561 | 562 | // Search paths 563 | for (const path of Object.keys(openApiDoc.paths || {})) { 564 | if (searchRegex.test(path)) { 565 | results.paths.push(path); 566 | } 567 | 568 | // Search operations within paths 569 | const pathItem = openApiDoc.paths[path]; 570 | for (const method of ["get", "post", "put", "delete", "patch", "options", "head"]) { 571 | const operation = pathItem[method]; 572 | if (!operation) continue; 573 | 574 | if ( 575 | searchRegex.test(operation.summary || "") || 576 | searchRegex.test(operation.description || "") || 577 | (operation.tags && operation.tags.some((tag) => searchRegex.test(tag))) 578 | ) { 579 | results.operations.push(`${method.toUpperCase()} ${path}`); 580 | } 581 | 582 | // Search parameters 583 | for (const param of operation.parameters || []) { 584 | if (searchRegex.test(param.name || "") || searchRegex.test(param.description || "")) { 585 | results.parameters.push(`${param.name} (${method.toUpperCase()} ${path})`); 586 | } 587 | } 588 | } 589 | } 590 | 591 | // Search components 592 | const components = openApiDoc.components || {}; 593 | for (const [type, typeObj] of Object.entries(components)) { 594 | if (!typeObj || typeof typeObj !== "object") continue; 595 | 596 | for (const [name, component] of Object.entries(typeObj)) { 597 | if (searchRegex.test(name) || searchRegex.test(component.description || "")) { 598 | results.components.push(`${type}.${name}`); 599 | } 600 | } 601 | } 602 | 603 | // Search security schemes 604 | for (const [name, scheme] of Object.entries(components.securitySchemes || {})) { 605 | if (searchRegex.test(name) || searchRegex.test(scheme.description || "")) { 606 | results.securitySchemes.push(name); 607 | } 608 | } 609 | 610 | // Clean up empty arrays 611 | for (const key of Object.keys(results)) { 612 | if (results[key].length === 0) { 613 | delete results[key]; 614 | } 615 | } 616 | 617 | if (Object.keys(results).length === 0) { 618 | return { content: [{ type: "text", text: `No matches found for "${pattern}"` }] }; 619 | } 620 | 621 | return { 622 | content: [ 623 | { 624 | type: "text", 625 | text: toYaml(results), 626 | }, 627 | ], 628 | }; 629 | }, 630 | ); 631 | 632 | const transport = new StdioServerTransport(); 633 | await server.connect(transport); 634 | ```