swagger-mcp-server 1.0.1 → 1.0.3

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/build/config.js CHANGED
@@ -2,23 +2,64 @@ import { z } from "zod";
2
2
  import fs from "fs";
3
3
  export const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
4
4
  export const jsonSchema = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]));
5
+ const ConfigEndpointBaseSchema = z.object({
6
+ name: z.string(),
7
+ url: z.string().url()
8
+ });
9
+ const AvailableConfigEndpointSchema = ConfigEndpointBaseSchema.extend({
10
+ schema: jsonSchema
11
+ });
12
+ const UnreachableConfigEndpointSchema = ConfigEndpointBaseSchema.extend({
13
+ error: z.string()
14
+ });
5
15
  export const ConfigSchema = z.object({
6
- endpoints: z.array(z.object({
7
- name: z.string(),
8
- url: z.string().url(),
9
- schema: jsonSchema
10
- }))
16
+ endpoints: z.array(z.union([AvailableConfigEndpointSchema, UnreachableConfigEndpointSchema]))
17
+ });
18
+ const ConfigFileSchema = z.object({
19
+ endpoints: z.array(ConfigEndpointBaseSchema)
11
20
  });
21
+ function errorMessage(error) {
22
+ if (error instanceof Error) {
23
+ return error.message;
24
+ }
25
+ return String(error);
26
+ }
27
+ async function loadEndpoint({ url, name }) {
28
+ try {
29
+ const response = await fetch(url);
30
+ if (!response.ok) {
31
+ return {
32
+ url,
33
+ name,
34
+ error: `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ''}`
35
+ };
36
+ }
37
+ const schema = await response.json();
38
+ return { url, name, schema };
39
+ }
40
+ catch (error) {
41
+ return {
42
+ url,
43
+ name,
44
+ error: errorMessage(error)
45
+ };
46
+ }
47
+ }
48
+ export function isAvailableConfigEndpoint(endpoint) {
49
+ return 'schema' in endpoint;
50
+ }
51
+ export function isUnreachableConfigEndpoint(endpoint) {
52
+ return 'error' in endpoint;
53
+ }
12
54
  export async function loadConfig(configPath) {
13
55
  try {
14
56
  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
- }));
57
+ const parsedConfig = ConfigFileSchema.parse(JSON.parse(configFile));
58
+ const endpointsWithSchema = await Promise.all(parsedConfig.endpoints.map(loadEndpoint));
59
+ const unavailableEndpoints = endpointsWithSchema.filter(isUnreachableConfigEndpoint);
60
+ for (const endpoint of unavailableEndpoints) {
61
+ console.error(`Swagger endpoint unreachable: ${endpoint.name} (${endpoint.url}): ${endpoint.error}`);
62
+ }
22
63
  return ConfigSchema.parse({ endpoints: endpointsWithSchema });
23
64
  }
24
65
  catch (error) {
@@ -1,9 +1,9 @@
1
+ import { isAvailableConfigEndpoint } from "./config.js";
1
2
  import { SwaggerParser } from "./swagger_parser.js";
2
3
  export class SwaggerCollection {
3
4
  swaggers;
4
5
  constructor(swaggers) {
5
6
  this.swaggers = swaggers;
6
- this.swaggers = swaggers;
7
7
  }
8
8
  listSwaggers() {
9
9
  return this.swaggers.map((swagger) => this.mapSwagger(swagger));
@@ -16,10 +16,21 @@ export class SwaggerCollection {
16
16
  return null;
17
17
  }
18
18
  mapSwagger(swagger) {
19
+ if (!isAvailableConfigEndpoint(swagger)) {
20
+ return {
21
+ id: swagger.name,
22
+ url: swagger.url,
23
+ name: swagger.name,
24
+ status: 'unreachable',
25
+ error: swagger.error
26
+ };
27
+ }
28
+ const schema = swagger.schema;
19
29
  return {
20
30
  id: swagger.name,
21
31
  url: swagger.url,
22
- name: swagger.schema.info.title,
32
+ name: schema.info?.title || swagger.name,
33
+ status: 'available',
23
34
  instance: new SwaggerParser(swagger.name, swagger.schema)
24
35
  };
25
36
  }
@@ -10,49 +10,26 @@ export class SwaggerMcpServer {
10
10
  name: "swagger",
11
11
  version: "1.0.0",
12
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
- }
13
+ this.server.tool("list-swaggers", "List all configured swagger endpoints and their availability status", async () => {
19
14
  return {
20
15
  content: [
21
16
  {
22
17
  type: "text",
23
- text: result
18
+ text: this.formatSwaggerList()
24
19
  }
25
20
  ]
26
21
  };
27
22
  });
28
23
  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.", {
24
+ "If swagger is provided, only endpoints from that swagger will be listed. " +
25
+ "Unavailable swaggers are reported separately.", {
30
26
  swagger: z.string().optional().describe("Swagger id returned by list-swaggers")
31
27
  }, 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
28
  return {
52
29
  content: [
53
30
  {
54
31
  type: "text",
55
- text: result
32
+ text: this.formatEndpointList(swagger)
56
33
  }
57
34
  ]
58
35
  };
@@ -62,42 +39,126 @@ export class SwaggerMcpServer {
62
39
  .describe("List of endpoint IDs to retrieve details for. " +
63
40
  "Endpoint ids can be found in the list-endpoints tool.")
64
41
  }, 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
42
  return {
92
43
  content: [
93
44
  {
94
45
  type: "text",
95
- text: result.join("\n\n---\n\n") || "No endpoint details found."
46
+ text: this.formatEndpointDetailsByIds(endpointIds)
96
47
  }
97
48
  ]
98
49
  };
99
50
  });
100
51
  }
52
+ formatSwaggerList() {
53
+ const swaggers = this.swaggerCollection.listSwaggers();
54
+ let result = "List of configured swaggers (id | status | name | url):\n";
55
+ for (const swagger of swaggers) {
56
+ result += `${swagger.id} | ${swagger.status} | ${swagger.name} | ${swagger.url}`;
57
+ if (swagger.status === 'unreachable') {
58
+ result += ` | ${swagger.error}`;
59
+ }
60
+ result += "\n";
61
+ }
62
+ return result;
63
+ }
64
+ formatEndpointList(swagger) {
65
+ const swaggerIds = swagger
66
+ ? [swagger]
67
+ : this.swaggerCollection.listSwaggers().map((s) => s.id);
68
+ let endpoints = [];
69
+ const unavailableSwaggers = [];
70
+ const missingSwaggers = [];
71
+ for (const swaggerId of swaggerIds) {
72
+ const swaggerInfo = this.swaggerCollection.getSwagger(swaggerId);
73
+ if (!swaggerInfo) {
74
+ missingSwaggers.push(swaggerId);
75
+ continue;
76
+ }
77
+ if (swaggerInfo.status === 'unreachable') {
78
+ unavailableSwaggers.push(swaggerInfo);
79
+ continue;
80
+ }
81
+ endpoints.push(...swaggerInfo.instance.listEndpoints());
82
+ }
83
+ let result = "List of available endpoints (endpointId | method | path | description):\n";
84
+ for (const endpoint of endpoints) {
85
+ const mergedText = endpoint.summary ? `${endpoint.summary}${endpoint.summary.endsWith('.') ? '' : '.'} ${endpoint.description}`.trim() : endpoint.description;
86
+ result += [
87
+ `${endpoint.swaggerName}-${endpoint.operationId}`,
88
+ endpoint.method.toUpperCase() + ' ' + endpoint.path,
89
+ mergedText
90
+ ].join(' | ') + '\n';
91
+ }
92
+ if (unavailableSwaggers.length > 0) {
93
+ result += "\nUnavailable swaggers:\n";
94
+ for (const unavailableSwagger of unavailableSwaggers) {
95
+ result += this.formatUnreachableSwagger(unavailableSwagger) + "\n";
96
+ }
97
+ }
98
+ if (missingSwaggers.length > 0) {
99
+ result += "\nUnknown swaggers:\n";
100
+ for (const missingSwagger of missingSwaggers) {
101
+ result += `${missingSwagger}\n`;
102
+ }
103
+ }
104
+ return result;
105
+ }
106
+ formatEndpointDetailsByIds(endpointIds) {
107
+ let result = [];
108
+ for (const endpointId of endpointIds) {
109
+ const lastHyphenIndex = endpointId.lastIndexOf('-');
110
+ if (lastHyphenIndex === -1) {
111
+ result.push(`Invalid endpoint ID format: ${endpointId}`);
112
+ continue;
113
+ }
114
+ const swaggerName = endpointId.substring(0, lastHyphenIndex);
115
+ const operationId = endpointId.substring(lastHyphenIndex + 1);
116
+ if (!swaggerName || !operationId) {
117
+ result.push(`Invalid endpoint ID format: ${endpointId}`);
118
+ continue;
119
+ }
120
+ const swagger = this.swaggerCollection.getSwagger(swaggerName);
121
+ if (!swagger) {
122
+ result.push(`Swagger not found: ${swaggerName} for endpoint ${endpointId}`);
123
+ continue;
124
+ }
125
+ if (swagger.status === 'unreachable') {
126
+ result.push(`Swagger unreachable: ${this.formatUnreachableSwagger(swagger)} for endpoint ${endpointId}`);
127
+ continue;
128
+ }
129
+ const endpoints = swagger.instance.listEndpoints();
130
+ const endpoint = Array.from(endpoints).find(e => e.operationId === operationId);
131
+ if (!endpoint) {
132
+ result.push(`Endpoint not found: ${operationId} in swagger ${swaggerName}`);
133
+ continue;
134
+ }
135
+ result.push(this.formatEndpointDetails(endpoint));
136
+ }
137
+ return result.join("\n\n---\n\n") || "No endpoint details found.";
138
+ }
139
+ formatUnreachableSwagger(swagger) {
140
+ return `${swagger.id} | ${swagger.url} | ${swagger.error}`;
141
+ }
142
+ formatBodyExample(body) {
143
+ if (!body.hasBody) {
144
+ return "";
145
+ }
146
+ if (body.isJson) {
147
+ return JSON.stringify(body.value ?? {}, null, 2);
148
+ }
149
+ return String(body.value ?? '<non-JSON response body>');
150
+ }
151
+ appendResponseExample(result, response) {
152
+ result += "```http\n";
153
+ result += `HTTP/2 ${response.statusCode}${response.statusText ? ` ${response.statusText}` : ''}\n`;
154
+ if (response.hasBody) {
155
+ result += `Content-Type: ${response.contentType || (response.isJson ? 'application/json' : 'application/octet-stream')}\n\n`;
156
+ result += this.formatBodyExample(response);
157
+ result += "\n";
158
+ }
159
+ result += "```";
160
+ return result;
161
+ }
101
162
  formatEndpointDetails(endpoint) {
102
163
  let result = "";
103
164
  result += `## ${endpoint.operationId} ${endpoint.summary}\n`;
@@ -142,31 +203,24 @@ export class SwaggerMcpServer {
142
203
  result += `### Example Request\n`;
143
204
  result += "```http\n";
144
205
  result += `${endpoint.method.toUpperCase()} ${exampleUrl}\n`;
145
- if (endpoint.requestBodyExample) {
146
- result += "Content-Type: application/json\n";
206
+ if (endpoint.requestBodyExample.hasBody) {
207
+ result += `Content-Type: ${endpoint.requestBodyExample.contentType || 'application/json'}\n`;
147
208
  }
148
209
  for (const param of otherParams) {
149
210
  if (param.in === 'header') {
150
211
  result += `${param.name}: ${param.example || 'example-value'}\n`;
151
212
  }
152
213
  }
153
- if (endpoint.requestBodyExample) {
214
+ if (endpoint.requestBodyExample.hasBody) {
154
215
  result += "\n";
155
- result += JSON.stringify(endpoint.requestBodyExample, null, 2);
216
+ result += this.formatBodyExample(endpoint.requestBodyExample);
156
217
  }
157
218
  result += "\n```\n\n";
158
219
  result += `### Example Response\n`;
159
- result += "```http\n";
160
- result += "HTTP/2 200 OK\n";
161
- result += "Content-Type: application/json\n\n";
162
- result += JSON.stringify(endpoint.successExampleResponse, null, 2);
163
- result += "\n```\n\n";
220
+ result = this.appendResponseExample(result, endpoint.successExampleResponse);
221
+ result += "\n\n";
164
222
  result += `### Error Response Example\n`;
165
- result += "```http\n";
166
- result += "HTTP/2 400 Bad Request\n";
167
- result += "Content-Type: application/json\n\n";
168
- result += JSON.stringify(endpoint.errorExampleResponse, null, 2);
169
- result += "\n```";
223
+ result = this.appendResponseExample(result, endpoint.errorExampleResponse);
170
224
  return result;
171
225
  }
172
226
  async serve() {
@@ -1,3 +1,4 @@
1
+ import { STATUS_CODES } from "node:http";
1
2
  export class SwaggerParser {
2
3
  name;
3
4
  schema;
@@ -30,29 +31,185 @@ export class SwaggerParser {
30
31
  default: return '';
31
32
  }
32
33
  }
33
- generateSampleFromSchema(schema) {
34
+ resolveRef(ref) {
35
+ if (!ref.startsWith('#/')) {
36
+ return undefined;
37
+ }
38
+ const path = ref
39
+ .slice(2)
40
+ .split('/')
41
+ .map((part) => decodeURIComponent(part.replace(/~1/g, '/').replace(/~0/g, '~')));
42
+ let current = this.schema;
43
+ for (const part of path) {
44
+ if (current === undefined || current === null) {
45
+ return undefined;
46
+ }
47
+ current = current[part];
48
+ }
49
+ return current;
50
+ }
51
+ normalizeMediaType(mediaType) {
52
+ return mediaType.split(';', 1)[0].trim().toLowerCase();
53
+ }
54
+ isJsonMediaType(mediaType) {
55
+ const normalized = this.normalizeMediaType(mediaType);
56
+ return normalized === 'application/json'
57
+ || normalized === 'application/*+json'
58
+ || normalized.endsWith('+json');
59
+ }
60
+ resolveSchemaType(schema) {
61
+ if (Array.isArray(schema?.type)) {
62
+ return schema.type.find((type) => type !== 'null');
63
+ }
64
+ if (schema?.type) {
65
+ return schema.type;
66
+ }
67
+ if (schema?.properties || schema?.additionalProperties) {
68
+ return 'object';
69
+ }
70
+ return undefined;
71
+ }
72
+ schemaRepresentsBinary(schema, seenRefs = new Set()) {
73
+ if (!schema || typeof schema !== 'object') {
74
+ return false;
75
+ }
76
+ if (schema.$ref) {
77
+ if (seenRefs.has(schema.$ref)) {
78
+ return false;
79
+ }
80
+ const resolved = this.resolveRef(schema.$ref);
81
+ if (!resolved) {
82
+ return false;
83
+ }
84
+ seenRefs.add(schema.$ref);
85
+ const result = this.schemaRepresentsBinary(resolved, seenRefs);
86
+ seenRefs.delete(schema.$ref);
87
+ return result;
88
+ }
89
+ const schemaType = this.resolveSchemaType(schema);
90
+ if (schemaType === 'string' && ['binary', 'byte'].includes(schema.format)) {
91
+ return true;
92
+ }
93
+ return ['oneOf', 'anyOf', 'allOf'].some((key) => Array.isArray(schema[key]) && schema[key].some((subSchema) => this.schemaRepresentsBinary(subSchema, seenRefs)));
94
+ }
95
+ wildcardContentLooksJson(media) {
96
+ if (!media) {
97
+ return false;
98
+ }
99
+ if (media.schema) {
100
+ return !this.schemaRepresentsBinary(media.schema);
101
+ }
102
+ return media.example !== undefined || this.extractFirstExample(media.examples) !== undefined;
103
+ }
104
+ selectJsonContent(content) {
105
+ if (!content || typeof content !== 'object') {
106
+ return undefined;
107
+ }
108
+ const entries = Object.entries(content);
109
+ const applicationJson = entries.find(([mediaType]) => this.normalizeMediaType(mediaType) === 'application/json');
110
+ if (applicationJson) {
111
+ return { mediaType: applicationJson[0], media: applicationJson[1] };
112
+ }
113
+ const jsonLike = entries.find(([mediaType]) => this.isJsonMediaType(mediaType));
114
+ if (jsonLike) {
115
+ return { mediaType: jsonLike[0], media: jsonLike[1] };
116
+ }
117
+ const wildcard = entries.find(([mediaType, media]) => this.normalizeMediaType(mediaType) === '*/*' && this.wildcardContentLooksJson(media));
118
+ if (wildcard) {
119
+ return { mediaType: 'application/json', media: wildcard[1] };
120
+ }
121
+ return undefined;
122
+ }
123
+ selectFirstContent(content) {
124
+ if (!content || typeof content !== 'object') {
125
+ return undefined;
126
+ }
127
+ const [first] = Object.entries(content);
128
+ if (!first) {
129
+ return undefined;
130
+ }
131
+ return { mediaType: first[0], media: first[1] };
132
+ }
133
+ extractFirstExample(examples) {
134
+ if (!examples || typeof examples !== 'object') {
135
+ return undefined;
136
+ }
137
+ for (const example of Object.values(examples)) {
138
+ if (example && typeof example === 'object' && 'value' in example) {
139
+ return example.value;
140
+ }
141
+ if (example !== undefined && (typeof example !== 'object' || example === null)) {
142
+ return example;
143
+ }
144
+ }
145
+ return undefined;
146
+ }
147
+ extractMediaValue(media) {
148
+ if (media?.example !== undefined) {
149
+ return media.example;
150
+ }
151
+ const example = this.extractFirstExample(media?.examples);
152
+ if (example !== undefined) {
153
+ return example;
154
+ }
155
+ if (media?.schema?.example !== undefined) {
156
+ return media.schema.example;
157
+ }
158
+ if (media?.schema) {
159
+ return this.generateSampleFromSchema(media.schema);
160
+ }
161
+ return {};
162
+ }
163
+ generateSampleFromSchema(schema, seenRefs = new Set()) {
34
164
  if (!schema)
35
165
  return {};
166
+ if (schema.example !== undefined) {
167
+ return schema.example;
168
+ }
36
169
  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]);
170
+ if (seenRefs.has(schema.$ref)) {
171
+ return {};
172
+ }
173
+ const resolvedSchema = this.resolveRef(schema.$ref);
174
+ if (resolvedSchema) {
175
+ seenRefs.add(schema.$ref);
176
+ const result = this.generateSampleFromSchema(resolvedSchema, seenRefs);
177
+ seenRefs.delete(schema.$ref);
178
+ return result;
41
179
  }
42
180
  return {};
43
181
  }
44
- switch (schema.type) {
182
+ if (schema.allOf && schema.allOf.length > 0) {
183
+ let result = {};
184
+ for (const subSchema of schema.allOf) {
185
+ result = { ...result, ...this.generateSampleFromSchema(subSchema, seenRefs) };
186
+ }
187
+ if (schema.properties) {
188
+ result = { ...result, ...this.generateSampleFromSchema({ type: 'object', properties: schema.properties }, seenRefs) };
189
+ }
190
+ return result;
191
+ }
192
+ if (schema.oneOf && schema.oneOf.length > 0) {
193
+ return this.generateSampleFromSchema(schema.oneOf[0], seenRefs);
194
+ }
195
+ if (schema.anyOf && schema.anyOf.length > 0) {
196
+ return this.generateSampleFromSchema(schema.anyOf[0], seenRefs);
197
+ }
198
+ switch (this.resolveSchemaType(schema)) {
45
199
  case 'object':
46
200
  const result = {};
47
201
  if (schema.properties) {
48
202
  for (const propName in schema.properties) {
49
- result[propName] = this.generateSampleFromSchema(schema.properties[propName]);
203
+ result[propName] = this.generateSampleFromSchema(schema.properties[propName], seenRefs);
50
204
  }
51
205
  }
206
+ if (Object.keys(result).length === 0 && schema.additionalProperties && typeof schema.additionalProperties === 'object') {
207
+ result.additionalProperty = this.generateSampleFromSchema(schema.additionalProperties, seenRefs);
208
+ }
52
209
  return result;
53
210
  case 'array':
54
211
  if (schema.items) {
55
- return [this.generateSampleFromSchema(schema.items)];
212
+ return [this.generateSampleFromSchema(schema.items, seenRefs)];
56
213
  }
57
214
  return [];
58
215
  case 'string':
@@ -76,38 +233,71 @@ export class SwaggerParser {
76
233
  case 'null':
77
234
  return null;
78
235
  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
236
  return {};
93
237
  }
94
238
  }
239
+ noBodyExample() {
240
+ return {
241
+ hasBody: false,
242
+ isJson: false
243
+ };
244
+ }
245
+ jsonBodyExample(contentType, media) {
246
+ const normalizedContentType = this.normalizeMediaType(contentType);
247
+ const displayContentType = normalizedContentType === 'application/*+json' ? 'application/json' : contentType;
248
+ return {
249
+ contentType: displayContentType,
250
+ hasBody: true,
251
+ isJson: true,
252
+ value: this.extractMediaValue(media)
253
+ };
254
+ }
255
+ nonJsonBodyExample(contentType, media) {
256
+ const normalizedContentType = this.normalizeMediaType(contentType);
257
+ const displayContentType = normalizedContentType === '*/*' ? 'application/octet-stream' : contentType;
258
+ const isBinary = normalizedContentType.startsWith('image/')
259
+ || normalizedContentType === 'application/octet-stream'
260
+ || this.schemaRepresentsBinary(media?.schema);
261
+ return {
262
+ contentType: displayContentType,
263
+ hasBody: true,
264
+ isJson: false,
265
+ value: isBinary ? '<binary response body>' : '<non-JSON response body>'
266
+ };
267
+ }
268
+ extractJsonBodyExampleFromContent(content) {
269
+ const selectedContent = this.selectJsonContent(content);
270
+ if (!selectedContent) {
271
+ return this.noBodyExample();
272
+ }
273
+ return this.jsonBodyExample(selectedContent.mediaType, selectedContent.media);
274
+ }
275
+ extractResponseBodyExample(content) {
276
+ const selectedJsonContent = this.selectJsonContent(content);
277
+ if (selectedJsonContent) {
278
+ return this.jsonBodyExample(selectedJsonContent.mediaType, selectedJsonContent.media);
279
+ }
280
+ const selectedContent = this.selectFirstContent(content);
281
+ if (selectedContent) {
282
+ return this.nonJsonBodyExample(selectedContent.mediaType, selectedContent.media);
283
+ }
284
+ return this.noBodyExample();
285
+ }
95
286
  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
- }
287
+ if (operation.requestBody?.content) {
288
+ return this.extractJsonBodyExampleFromContent(operation.requestBody.content);
109
289
  }
110
- return {};
290
+ return this.noBodyExample();
291
+ }
292
+ statusText(statusCode) {
293
+ return STATUS_CODES[statusCode] || '';
294
+ }
295
+ responseExample(statusCode, body) {
296
+ return {
297
+ ...body,
298
+ statusCode,
299
+ statusText: this.statusText(statusCode)
300
+ };
111
301
  }
112
302
  resolveSuccessExampleResponse(operation) {
113
303
  if (operation.responses) {
@@ -115,23 +305,11 @@ export class SwaggerParser {
115
305
  const codeNum = parseInt(code, 10);
116
306
  if (!isNaN(codeNum) && Math.floor(codeNum / 100) === 2) {
117
307
  const successResponse = operation.responses[code];
118
- if (successResponse.content && successResponse.content['application/json']) {
119
- const content = successResponse.content['application/json'];
120
- if (content.example) {
121
- return content.example;
122
- }
123
- if (content.schema && content.schema.example) {
124
- return content.schema.example;
125
- }
126
- if (content.schema) {
127
- return this.generateSampleFromSchema(content.schema);
128
- }
129
- }
130
- return {};
308
+ return this.responseExample(codeNum, this.extractResponseBodyExample(successResponse.content));
131
309
  }
132
310
  }
133
311
  }
134
- return {};
312
+ return this.responseExample(200, this.noBodyExample());
135
313
  }
136
314
  resolveErrorExampleResponse(operation) {
137
315
  if (operation.responses) {
@@ -139,27 +317,21 @@ export class SwaggerParser {
139
317
  const codeNum = parseInt(code, 10);
140
318
  if (!isNaN(codeNum) && Math.floor(codeNum / 100) >= 4) {
141
319
  const errorResponse = operation.responses[code];
142
- if (errorResponse.content && errorResponse.content['application/json']) {
143
- const content = errorResponse.content['application/json'];
144
- if (content.example) {
145
- return content.example;
146
- }
147
- if (content.schema && content.schema.example) {
148
- return content.schema.example;
149
- }
150
- if (content.schema) {
151
- return this.generateSampleFromSchema(content.schema);
152
- }
153
- }
320
+ return this.responseExample(codeNum, this.extractResponseBodyExample(errorResponse.content));
154
321
  }
155
322
  }
156
323
  }
157
- return {
158
- error: {
159
- code: 400,
160
- message: "Bad Request"
324
+ return this.responseExample(400, {
325
+ contentType: 'application/json',
326
+ hasBody: true,
327
+ isJson: true,
328
+ value: {
329
+ error: {
330
+ code: 400,
331
+ message: "Bad Request"
332
+ }
161
333
  }
162
- };
334
+ });
163
335
  }
164
336
  listEndpoints() {
165
337
  const endpoints = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swagger-mcp-server",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "main": "build/index.js",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "scripts": {
10
10
  "build": "tsc && chmod 755 build/index.js",
11
+ "test": "npm run build && node test/swagger_parser.test.mjs && node test/degraded_startup.test.mjs",
11
12
  "run": "npx @modelcontextprotocol/inspector node build/index.js test_config.json",
12
13
  "prepublishOnly": "npm run build"
13
14
  },
@@ -24,12 +25,12 @@
24
25
  "description": "Model Context Protocol server for swagger endpoints",
25
26
  "repository": {
26
27
  "type": "git",
27
- "url": "git+https://github.com/yourusername/swagger-mcp-server.git"
28
+ "url": "git+https://github.com/marcin-sucharski/swagger-mcp-server.git"
28
29
  },
29
30
  "bugs": {
30
- "url": "https://github.com/yourusername/swagger-mcp-server/issues"
31
+ "url": "https://github.com/marcin-sucharski/swagger-mcp-server/issues"
31
32
  },
32
- "homepage": "https://github.com/yourusername/swagger-mcp-server#readme",
33
+ "homepage": "https://github.com/marcin-sucharski/swagger-mcp-server#readme",
33
34
  "dependencies": {
34
35
  "@modelcontextprotocol/sdk": "^1.7.0",
35
36
  "zod": "^3.24.2"