swagger-mcp-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/build/config.js +28 -0
- package/build/index.js +25 -0
- package/build/swager_collection.js +26 -0
- package/build/swagger_mcp_server.js +186 -0
- package/build/swagger_parser.js +184 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Swagger MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol server for Swagger/OpenAPI endpoints. This tool allows you to expose Swagger-defined APIs through the Model Context Protocol, making them accessible to AI agents.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
You can install the package globally:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g swagger-mcp-server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or use it directly with npx:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx swagger-mcp-server <config-file>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
1. Create a configuration file (JSON) that defines the Swagger endpoints you want to expose:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"endpoints": [
|
|
26
|
+
{
|
|
27
|
+
"name": "example-api",
|
|
28
|
+
"url": "https://example.com/api/swagger.json"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. Run the server:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
swagger-mcp-server config.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or with npx:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx swagger-mcp-server config.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Configuration Options
|
|
47
|
+
|
|
48
|
+
The configuration file supports the following options:
|
|
49
|
+
|
|
50
|
+
- `endpoints`: An array of Swagger endpoints to expose
|
|
51
|
+
- `name`: A unique identifier for the endpoint
|
|
52
|
+
- `url`: URL to the Swagger/OpenAPI JSON definition
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
To build the project:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm run build
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
To run locally:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm run run
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
ISC
|
package/build/config.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
export const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
|
4
|
+
export const jsonSchema = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]));
|
|
5
|
+
export const ConfigSchema = z.object({
|
|
6
|
+
endpoints: z.array(z.object({
|
|
7
|
+
name: z.string(),
|
|
8
|
+
url: z.string().url(),
|
|
9
|
+
schema: jsonSchema
|
|
10
|
+
}))
|
|
11
|
+
});
|
|
12
|
+
export async function loadConfig(configPath) {
|
|
13
|
+
try {
|
|
14
|
+
const configFile = fs.readFileSync(configPath, 'utf8');
|
|
15
|
+
const parsedConfig = JSON.parse(configFile);
|
|
16
|
+
const endpointsWithSchema = await Promise.all(parsedConfig.endpoints
|
|
17
|
+
.map(async ({ url, name }) => {
|
|
18
|
+
const response = await fetch(url);
|
|
19
|
+
const schema = await response.json();
|
|
20
|
+
return { url, name, schema };
|
|
21
|
+
}));
|
|
22
|
+
return ConfigSchema.parse({ endpoints: endpointsWithSchema });
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error(`Error loading config from ${configPath}:`, error);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadConfig } from './config.js';
|
|
3
|
+
import { SwaggerCollection } from "./swager_collection.js";
|
|
4
|
+
import { SwaggerMcpServer } from "./swagger_mcp_server.js";
|
|
5
|
+
let config;
|
|
6
|
+
async function main() {
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
if (args.length > 0) {
|
|
9
|
+
const configPath = args[0];
|
|
10
|
+
console.error(`Loading configuration from: ${configPath}`);
|
|
11
|
+
config = await loadConfig(configPath);
|
|
12
|
+
console.error(`Loaded ${config.endpoints.length} endpoints from config`);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
console.error("No config file provided. Exiting.");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const swaggerCollection = new SwaggerCollection(config.endpoints);
|
|
19
|
+
const swaggerMcpServer = new SwaggerMcpServer(swaggerCollection);
|
|
20
|
+
await swaggerMcpServer.serve();
|
|
21
|
+
}
|
|
22
|
+
main().catch((error) => {
|
|
23
|
+
console.error("Fatal error in main():", error);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SwaggerParser } from "./swagger_parser.js";
|
|
2
|
+
export class SwaggerCollection {
|
|
3
|
+
swaggers;
|
|
4
|
+
constructor(swaggers) {
|
|
5
|
+
this.swaggers = swaggers;
|
|
6
|
+
this.swaggers = swaggers;
|
|
7
|
+
}
|
|
8
|
+
listSwaggers() {
|
|
9
|
+
return this.swaggers.map((swagger) => this.mapSwagger(swagger));
|
|
10
|
+
}
|
|
11
|
+
getSwagger(id) {
|
|
12
|
+
let result = this.swaggers.find((swagger) => swagger.name === id);
|
|
13
|
+
if (result) {
|
|
14
|
+
return this.mapSwagger(result);
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
mapSwagger(swagger) {
|
|
19
|
+
return {
|
|
20
|
+
id: swagger.name,
|
|
21
|
+
url: swagger.url,
|
|
22
|
+
name: swagger.schema.info.title,
|
|
23
|
+
instance: new SwaggerParser(swagger.name, swagger.schema)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
export class SwaggerMcpServer {
|
|
5
|
+
swaggerCollection;
|
|
6
|
+
server;
|
|
7
|
+
constructor(swaggerCollection) {
|
|
8
|
+
this.swaggerCollection = swaggerCollection;
|
|
9
|
+
this.server = new McpServer({
|
|
10
|
+
name: "swagger",
|
|
11
|
+
version: "1.0.0",
|
|
12
|
+
});
|
|
13
|
+
this.server.tool("list-swaggers", "List all connected swagger endpoints", async () => {
|
|
14
|
+
const swaggers = this.swaggerCollection.listSwaggers();
|
|
15
|
+
let result = "List of available swaggers (id | name | url):\n";
|
|
16
|
+
for (const swagger of swaggers) {
|
|
17
|
+
result += `${swagger.id} | ${swagger.name} | ${swagger.url}\n`;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: result
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
this.server.tool("list-endpoints", "List all available endpoints with a short description. " +
|
|
29
|
+
"If swagger is provided, only endpoints from that swagger will be listed.", {
|
|
30
|
+
swagger: z.string().optional().describe("Swagger id returned by list-swaggers")
|
|
31
|
+
}, async ({ swagger }) => {
|
|
32
|
+
let swaggers = swagger
|
|
33
|
+
? [swagger]
|
|
34
|
+
: this.swaggerCollection.listSwaggers().map((s) => s.id);
|
|
35
|
+
let endpoints = [];
|
|
36
|
+
for (const swaggerId of swaggers) {
|
|
37
|
+
const swagger = this.swaggerCollection.getSwagger(swaggerId);
|
|
38
|
+
if (swagger) {
|
|
39
|
+
endpoints.push(...swagger.instance.listEndpoints());
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
let result = "List of available endpoints (endpointId | method | path | description):\n";
|
|
43
|
+
for (const endpoint of endpoints) {
|
|
44
|
+
const mergedText = endpoint.summary ? `${endpoint.summary}${endpoint.summary.endsWith('.') ? '' : '.'} ${endpoint.description}`.trim() : endpoint.description;
|
|
45
|
+
result += [
|
|
46
|
+
`${endpoint.swaggerName}-${endpoint.operationId}`,
|
|
47
|
+
endpoint.method.toUpperCase() + ' ' + endpoint.path,
|
|
48
|
+
mergedText
|
|
49
|
+
].join(' | ') + '\n';
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: result
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
this.server.tool("get-endpoints", "Get detailed information about specific endpoints", {
|
|
61
|
+
endpointIds: z.array(z.string())
|
|
62
|
+
.describe("List of endpoint IDs to retrieve details for. " +
|
|
63
|
+
"Endpoint ids can be found in the list-endpoints tool.")
|
|
64
|
+
}, async ({ endpointIds }) => {
|
|
65
|
+
let result = [];
|
|
66
|
+
for (const endpointId of endpointIds) {
|
|
67
|
+
const lastHyphenIndex = endpointId.lastIndexOf('-');
|
|
68
|
+
if (lastHyphenIndex === -1) {
|
|
69
|
+
result.push(`Invalid endpoint ID format: ${endpointId}`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const swaggerName = endpointId.substring(0, lastHyphenIndex);
|
|
73
|
+
const operationId = endpointId.substring(lastHyphenIndex + 1);
|
|
74
|
+
if (!swaggerName || !operationId) {
|
|
75
|
+
result.push(`Invalid endpoint ID format: ${endpointId}`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const swagger = this.swaggerCollection.getSwagger(swaggerName);
|
|
79
|
+
if (!swagger) {
|
|
80
|
+
result.push(`Swagger not found: ${swaggerName} for endpoint ${endpointId}`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const endpoints = swagger.instance.listEndpoints();
|
|
84
|
+
const endpoint = Array.from(endpoints).find(e => e.operationId === operationId);
|
|
85
|
+
if (!endpoint) {
|
|
86
|
+
result.push(`Endpoint not found: ${operationId} in swagger ${swaggerName}`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
result.push(this.formatEndpointDetails(endpoint));
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
content: [
|
|
93
|
+
{
|
|
94
|
+
type: "text",
|
|
95
|
+
text: result.join("\n\n---\n\n") || "No endpoint details found."
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
formatEndpointDetails(endpoint) {
|
|
102
|
+
let result = "";
|
|
103
|
+
result += `## ${endpoint.operationId} ${endpoint.summary}\n`;
|
|
104
|
+
result += `### URL: ${endpoint.method.toUpperCase()} ${endpoint.path}\n`;
|
|
105
|
+
if (endpoint.description) {
|
|
106
|
+
result += `### Description\n${endpoint.description}\n`;
|
|
107
|
+
}
|
|
108
|
+
const pathParams = endpoint.parameters.filter(p => p.in === 'path');
|
|
109
|
+
const queryParams = endpoint.parameters.filter(p => p.in === 'query');
|
|
110
|
+
const bodyParams = endpoint.parameters.filter(p => p.in === 'body');
|
|
111
|
+
const otherParams = endpoint.parameters.filter(p => !['path', 'query', 'body'].includes(p.in));
|
|
112
|
+
if (pathParams.length > 0) {
|
|
113
|
+
result += `### Path Parameters\n`;
|
|
114
|
+
for (const param of pathParams) {
|
|
115
|
+
result += `- \`${param.name}\` (${param.type}${param.required ? ', required' : ''}): ${param.description}\n`;
|
|
116
|
+
}
|
|
117
|
+
result += `\n`;
|
|
118
|
+
}
|
|
119
|
+
if (queryParams.length > 0) {
|
|
120
|
+
result += `### Query Parameters\n`;
|
|
121
|
+
for (const param of queryParams) {
|
|
122
|
+
result += `- \`${param.name}\` (${param.type}${param.required ? ', required' : ''}): ${param.description}\n`;
|
|
123
|
+
}
|
|
124
|
+
result += `\n`;
|
|
125
|
+
}
|
|
126
|
+
if (bodyParams.length > 0) {
|
|
127
|
+
result += `### Body Parameters\n`;
|
|
128
|
+
for (const param of bodyParams) {
|
|
129
|
+
result += `- \`${param.name}\` (${param.type}${param.required ? ', required' : ''}): ${param.description}\n`;
|
|
130
|
+
}
|
|
131
|
+
result += `\n`;
|
|
132
|
+
}
|
|
133
|
+
if (otherParams.length > 0) {
|
|
134
|
+
result += `### Other Parameters\n`;
|
|
135
|
+
for (const param of otherParams) {
|
|
136
|
+
result += `- \`${param.name}\` (${param.in}, ${param.type}${param.required ? ', required' : ''}): ${param.description}\n`;
|
|
137
|
+
}
|
|
138
|
+
result += `\n`;
|
|
139
|
+
}
|
|
140
|
+
let exampleUrl = endpoint.path;
|
|
141
|
+
for (const param of pathParams) {
|
|
142
|
+
exampleUrl = exampleUrl.replace(`{${param.name}}`, param.example || `{${param.name}}`);
|
|
143
|
+
}
|
|
144
|
+
if (queryParams.length > 0) {
|
|
145
|
+
exampleUrl += '?';
|
|
146
|
+
exampleUrl += queryParams
|
|
147
|
+
.map(p => `${p.name}=${encodeURIComponent(p.example || `{${p.name}}`)}`)
|
|
148
|
+
.join('&');
|
|
149
|
+
}
|
|
150
|
+
result += `### Example Request\n`;
|
|
151
|
+
result += "```http\n";
|
|
152
|
+
result += `${endpoint.method.toUpperCase()} ${exampleUrl}\n`;
|
|
153
|
+
const hasRequestBody = bodyParams.length > 0;
|
|
154
|
+
if (hasRequestBody) {
|
|
155
|
+
result += "Content-Type: application/json\n";
|
|
156
|
+
}
|
|
157
|
+
for (const param of otherParams) {
|
|
158
|
+
if (param.in === 'header') {
|
|
159
|
+
result += `${param.name}: ${param.example || 'example-value'}\n`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (hasRequestBody) {
|
|
163
|
+
result += "\n";
|
|
164
|
+
result += JSON.stringify(endpoint.requestBodyExample, null, 2);
|
|
165
|
+
}
|
|
166
|
+
result += "\n```\n\n";
|
|
167
|
+
result += `### Example Response\n`;
|
|
168
|
+
result += "```http\n";
|
|
169
|
+
result += "HTTP/2 200 OK\n";
|
|
170
|
+
result += "Content-Type: application/json\n\n";
|
|
171
|
+
result += JSON.stringify(endpoint.successExampleResponse, null, 2);
|
|
172
|
+
result += "\n```\n\n";
|
|
173
|
+
result += `### Error Response Example\n`;
|
|
174
|
+
result += "```http\n";
|
|
175
|
+
result += "HTTP/2 400 Bad Request\n";
|
|
176
|
+
result += "Content-Type: application/json\n\n";
|
|
177
|
+
result += JSON.stringify(endpoint.errorExampleResponse, null, 2);
|
|
178
|
+
result += "\n```";
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
async serve() {
|
|
182
|
+
const transport = new StdioServerTransport();
|
|
183
|
+
await this.server.connect(transport);
|
|
184
|
+
console.error("Swagger MCP Server running on stdio");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
export class SwaggerParser {
|
|
2
|
+
name;
|
|
3
|
+
schema;
|
|
4
|
+
constructor(name, schema) {
|
|
5
|
+
this.name = name;
|
|
6
|
+
this.schema = schema;
|
|
7
|
+
}
|
|
8
|
+
extractParameters(operation) {
|
|
9
|
+
const parameters = [];
|
|
10
|
+
if (operation.parameters) {
|
|
11
|
+
for (const param of operation.parameters) {
|
|
12
|
+
parameters.push({
|
|
13
|
+
name: param.name,
|
|
14
|
+
in: param.in || 'query', // Default to query if not specified
|
|
15
|
+
type: param.schema?.type || param.type || 'string',
|
|
16
|
+
description: param.description || '',
|
|
17
|
+
required: param.required || false,
|
|
18
|
+
example: param.example || this.generateExampleForType(param.schema?.type || param.type || 'string')
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return parameters;
|
|
23
|
+
}
|
|
24
|
+
generateExampleForType(type) {
|
|
25
|
+
switch (type) {
|
|
26
|
+
case 'string': return 'example_string';
|
|
27
|
+
case 'integer': return '123';
|
|
28
|
+
case 'number': return '123.45';
|
|
29
|
+
case 'boolean': return 'true';
|
|
30
|
+
default: return '';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
generateSampleFromSchema(schema) {
|
|
34
|
+
if (!schema)
|
|
35
|
+
return {};
|
|
36
|
+
if (schema.$ref) {
|
|
37
|
+
const refPath = schema.$ref.replace('#/components/schemas/', '');
|
|
38
|
+
const schemaObj = this.schema;
|
|
39
|
+
if (schemaObj?.components?.schemas?.[refPath]) {
|
|
40
|
+
return this.generateSampleFromSchema(schemaObj.components.schemas[refPath]);
|
|
41
|
+
}
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
switch (schema.type) {
|
|
45
|
+
case 'object':
|
|
46
|
+
const result = {};
|
|
47
|
+
if (schema.properties) {
|
|
48
|
+
for (const propName in schema.properties) {
|
|
49
|
+
result[propName] = this.generateSampleFromSchema(schema.properties[propName]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
case 'array':
|
|
54
|
+
if (schema.items) {
|
|
55
|
+
return [this.generateSampleFromSchema(schema.items)];
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
case 'string':
|
|
59
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
60
|
+
return schema.enum[0];
|
|
61
|
+
}
|
|
62
|
+
if (schema.format === 'date-time')
|
|
63
|
+
return new Date().toISOString();
|
|
64
|
+
if (schema.format === 'date')
|
|
65
|
+
return new Date().toISOString().split('T')[0];
|
|
66
|
+
if (schema.format === 'email')
|
|
67
|
+
return 'user@example.com';
|
|
68
|
+
if (schema.format === 'uuid')
|
|
69
|
+
return '00000000-0000-0000-0000-000000000000';
|
|
70
|
+
return 'string';
|
|
71
|
+
case 'number':
|
|
72
|
+
case 'integer':
|
|
73
|
+
return 0;
|
|
74
|
+
case 'boolean':
|
|
75
|
+
return false;
|
|
76
|
+
case 'null':
|
|
77
|
+
return null;
|
|
78
|
+
default:
|
|
79
|
+
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
80
|
+
return this.generateSampleFromSchema(schema.oneOf[0]);
|
|
81
|
+
}
|
|
82
|
+
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
83
|
+
return this.generateSampleFromSchema(schema.anyOf[0]);
|
|
84
|
+
}
|
|
85
|
+
if (schema.allOf && schema.allOf.length > 0) {
|
|
86
|
+
let result = {};
|
|
87
|
+
for (const subSchema of schema.allOf) {
|
|
88
|
+
result = { ...result, ...this.generateSampleFromSchema(subSchema) };
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
extractRequestBodyExample(operation) {
|
|
96
|
+
if (operation.requestBody && operation.requestBody.content) {
|
|
97
|
+
const content = operation.requestBody.content['application/json'];
|
|
98
|
+
if (content) {
|
|
99
|
+
if (content.example) {
|
|
100
|
+
return content.example;
|
|
101
|
+
}
|
|
102
|
+
if (content.schema && content.schema.example) {
|
|
103
|
+
return content.schema.example;
|
|
104
|
+
}
|
|
105
|
+
if (content.schema) {
|
|
106
|
+
return this.generateSampleFromSchema(content.schema);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
resolveSuccessExampleResponse(operation) {
|
|
113
|
+
if (operation.responses && operation.responses['200']) {
|
|
114
|
+
const successResponse = operation.responses['200'];
|
|
115
|
+
if (successResponse.content && successResponse.content['application/json']) {
|
|
116
|
+
const content = successResponse.content['application/json'];
|
|
117
|
+
if (content.example) {
|
|
118
|
+
return content.example;
|
|
119
|
+
}
|
|
120
|
+
if (content.schema && content.schema.example) {
|
|
121
|
+
return content.schema.example;
|
|
122
|
+
}
|
|
123
|
+
if (content.schema) {
|
|
124
|
+
return this.generateSampleFromSchema(content.schema);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {};
|
|
129
|
+
}
|
|
130
|
+
resolveErrorExampleResponse(operation) {
|
|
131
|
+
if (operation.responses) {
|
|
132
|
+
for (const code in operation.responses) {
|
|
133
|
+
const codeNum = parseInt(code, 10);
|
|
134
|
+
if (!isNaN(codeNum) && Math.floor(codeNum / 100) >= 4) {
|
|
135
|
+
const errorResponse = operation.responses[code];
|
|
136
|
+
if (errorResponse.content && errorResponse.content['application/json']) {
|
|
137
|
+
const content = errorResponse.content['application/json'];
|
|
138
|
+
if (content.example) {
|
|
139
|
+
return content.example;
|
|
140
|
+
}
|
|
141
|
+
if (content.schema && content.schema.example) {
|
|
142
|
+
return content.schema.example;
|
|
143
|
+
}
|
|
144
|
+
if (content.schema) {
|
|
145
|
+
return this.generateSampleFromSchema(content.schema);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
error: {
|
|
153
|
+
code: 400,
|
|
154
|
+
message: "Bad Request"
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
listEndpoints() {
|
|
159
|
+
const endpoints = [];
|
|
160
|
+
const schemaObj = this.schema;
|
|
161
|
+
const paths = schemaObj?.paths || {};
|
|
162
|
+
for (const path in paths) {
|
|
163
|
+
const pathItem = paths[path];
|
|
164
|
+
for (const method in pathItem) {
|
|
165
|
+
if (['get', 'post', 'put', 'delete', 'patch', 'options', 'head'].includes(method)) {
|
|
166
|
+
const operation = pathItem[method];
|
|
167
|
+
endpoints.push({
|
|
168
|
+
swaggerName: this.name,
|
|
169
|
+
path,
|
|
170
|
+
operationId: operation.operationId || `${method}${path.replace(/\//g, '_')}`,
|
|
171
|
+
method,
|
|
172
|
+
summary: operation.summary || '',
|
|
173
|
+
description: operation.description || '',
|
|
174
|
+
parameters: this.extractParameters(operation),
|
|
175
|
+
requestBodyExample: this.extractRequestBodyExample(operation),
|
|
176
|
+
successExampleResponse: this.resolveSuccessExampleResponse(operation),
|
|
177
|
+
errorExampleResponse: this.resolveErrorExampleResponse(operation)
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return endpoints;
|
|
183
|
+
}
|
|
184
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "swagger-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"swagger-mcp-server": "build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc && chmod 755 build/index.js",
|
|
11
|
+
"run": "npx @modelcontextprotocol/inspector node build/index.js test_config.json",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"swagger",
|
|
16
|
+
"openapi",
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"ai",
|
|
20
|
+
"api"
|
|
21
|
+
],
|
|
22
|
+
"author": "Marcin Sucharski",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"description": "Model Context Protocol server for swagger endpoints",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/yourusername/swagger-mcp-server.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/yourusername/swagger-mcp-server/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/yourusername/swagger-mcp-server#readme",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
35
|
+
"zod": "^3.24.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.13.10",
|
|
39
|
+
"typescript": "^5.8.2"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=16.0.0"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"build/**/*"
|
|
46
|
+
]
|
|
47
|
+
}
|