truss-api-mcp 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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +89 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/code-generator.d.ts +6 -0
  8. package/dist/lib/code-generator.d.ts.map +1 -0
  9. package/dist/lib/code-generator.js +890 -0
  10. package/dist/lib/code-generator.js.map +1 -0
  11. package/dist/lib/http-client.d.ts +6 -0
  12. package/dist/lib/http-client.d.ts.map +1 -0
  13. package/dist/lib/http-client.js +76 -0
  14. package/dist/lib/http-client.js.map +1 -0
  15. package/dist/lib/license.d.ts +4 -0
  16. package/dist/lib/license.d.ts.map +1 -0
  17. package/dist/lib/license.js +97 -0
  18. package/dist/lib/license.js.map +1 -0
  19. package/dist/lib/openapi-parser.d.ts +11 -0
  20. package/dist/lib/openapi-parser.d.ts.map +1 -0
  21. package/dist/lib/openapi-parser.js +390 -0
  22. package/dist/lib/openapi-parser.js.map +1 -0
  23. package/dist/lib/schema-validator.d.ts +15 -0
  24. package/dist/lib/schema-validator.d.ts.map +1 -0
  25. package/dist/lib/schema-validator.js +206 -0
  26. package/dist/lib/schema-validator.js.map +1 -0
  27. package/dist/tools/compare-specs.d.ts +3 -0
  28. package/dist/tools/compare-specs.d.ts.map +1 -0
  29. package/dist/tools/compare-specs.js +59 -0
  30. package/dist/tools/compare-specs.js.map +1 -0
  31. package/dist/tools/generate-client.d.ts +3 -0
  32. package/dist/tools/generate-client.d.ts.map +1 -0
  33. package/dist/tools/generate-client.js +65 -0
  34. package/dist/tools/generate-client.js.map +1 -0
  35. package/dist/tools/generate-openapi.d.ts +3 -0
  36. package/dist/tools/generate-openapi.d.ts.map +1 -0
  37. package/dist/tools/generate-openapi.js +57 -0
  38. package/dist/tools/generate-openapi.js.map +1 -0
  39. package/dist/tools/generate-tests.d.ts +3 -0
  40. package/dist/tools/generate-tests.d.ts.map +1 -0
  41. package/dist/tools/generate-tests.js +59 -0
  42. package/dist/tools/generate-tests.js.map +1 -0
  43. package/dist/tools/mock-server.d.ts +3 -0
  44. package/dist/tools/mock-server.d.ts.map +1 -0
  45. package/dist/tools/mock-server.js +60 -0
  46. package/dist/tools/mock-server.js.map +1 -0
  47. package/dist/tools/parse-openapi.d.ts +3 -0
  48. package/dist/tools/parse-openapi.d.ts.map +1 -0
  49. package/dist/tools/parse-openapi.js +48 -0
  50. package/dist/tools/parse-openapi.js.map +1 -0
  51. package/dist/tools/test-endpoint.d.ts +3 -0
  52. package/dist/tools/test-endpoint.d.ts.map +1 -0
  53. package/dist/tools/test-endpoint.js +66 -0
  54. package/dist/tools/test-endpoint.js.map +1 -0
  55. package/dist/tools/validate-response.d.ts +3 -0
  56. package/dist/tools/validate-response.d.ts.map +1 -0
  57. package/dist/tools/validate-response.js +44 -0
  58. package/dist/tools/validate-response.js.map +1 -0
  59. package/dist/types.d.ts +121 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +3 -0
  62. package/dist/types.js.map +1 -0
  63. package/evals/eval-http.ts +163 -0
  64. package/evals/eval-openapi.ts +506 -0
  65. package/evals/run-evals.ts +29 -0
  66. package/glama.json +4 -0
  67. package/package.json +37 -0
  68. package/smithery.yaml +9 -0
  69. package/src/index.ts +110 -0
  70. package/src/lib/code-generator.ts +1045 -0
  71. package/src/lib/http-client.ts +87 -0
  72. package/src/lib/license.ts +121 -0
  73. package/src/lib/openapi-parser.ts +456 -0
  74. package/src/lib/schema-validator.ts +234 -0
  75. package/src/tools/compare-specs.ts +67 -0
  76. package/src/tools/generate-client.ts +75 -0
  77. package/src/tools/generate-openapi.ts +67 -0
  78. package/src/tools/generate-tests.ts +69 -0
  79. package/src/tools/mock-server.ts +68 -0
  80. package/src/tools/parse-openapi.ts +54 -0
  81. package/src/tools/test-endpoint.ts +71 -0
  82. package/src/tools/validate-response.ts +54 -0
  83. package/src/types.ts +156 -0
  84. package/tsconfig.json +19 -0
@@ -0,0 +1,234 @@
1
+ import type { JsonSchema, ValidationError, ValidationResult } from '../types.js';
2
+
3
+ /**
4
+ * Validate a value against a JSON Schema (draft-07 subset).
5
+ * Supports: type, properties, required, items, enum, minimum, maximum,
6
+ * minLength, maxLength, pattern, additionalProperties, oneOf, anyOf, allOf, nullable.
7
+ */
8
+ export function validateSchema(
9
+ value: unknown,
10
+ schema: JsonSchema,
11
+ path = '$'
12
+ ): ValidationResult {
13
+ const errors: ValidationError[] = [];
14
+ validate(value, schema, path, errors);
15
+ return { valid: errors.length === 0, errors };
16
+ }
17
+
18
+ /**
19
+ * Validate an HTTP response against expected status and optional schema.
20
+ */
21
+ export function validateResponse(
22
+ response: { status: number; body: unknown },
23
+ expectedStatus: number,
24
+ expectedSchema?: JsonSchema
25
+ ): ValidationResult {
26
+ const errors: ValidationError[] = [];
27
+
28
+ if (response.status !== expectedStatus) {
29
+ errors.push({
30
+ path: '$.status',
31
+ message: `Expected status ${expectedStatus}, got ${response.status}`,
32
+ expected: String(expectedStatus),
33
+ actual: String(response.status),
34
+ });
35
+ }
36
+
37
+ if (expectedSchema) {
38
+ const bodyResult = validateSchema(response.body, expectedSchema, '$.body');
39
+ errors.push(...bodyResult.errors);
40
+ }
41
+
42
+ return { valid: errors.length === 0, errors };
43
+ }
44
+
45
+ // ── Internal Validation ─────────────────────────────────────────────
46
+
47
+ function validate(
48
+ value: unknown,
49
+ schema: JsonSchema,
50
+ path: string,
51
+ errors: ValidationError[]
52
+ ): void {
53
+ // Handle nullable
54
+ if (schema.nullable && (value === null || value === undefined)) {
55
+ return;
56
+ }
57
+
58
+ // allOf: all must match
59
+ if (schema.allOf) {
60
+ for (const sub of schema.allOf) {
61
+ validate(value, sub, path, errors);
62
+ }
63
+ return;
64
+ }
65
+
66
+ // anyOf: at least one must match
67
+ if (schema.anyOf) {
68
+ const anyErrors: ValidationError[][] = [];
69
+ for (const sub of schema.anyOf) {
70
+ const subErrors: ValidationError[] = [];
71
+ validate(value, sub, path, subErrors);
72
+ if (subErrors.length === 0) return; // matched
73
+ anyErrors.push(subErrors);
74
+ }
75
+ errors.push({
76
+ path,
77
+ message: `Value does not match any of the ${schema.anyOf.length} schemas in anyOf`,
78
+ });
79
+ return;
80
+ }
81
+
82
+ // oneOf: exactly one must match
83
+ if (schema.oneOf) {
84
+ let matchCount = 0;
85
+ for (const sub of schema.oneOf) {
86
+ const subErrors: ValidationError[] = [];
87
+ validate(value, sub, path, subErrors);
88
+ if (subErrors.length === 0) matchCount++;
89
+ }
90
+ if (matchCount !== 1) {
91
+ errors.push({
92
+ path,
93
+ message: `Value must match exactly one schema in oneOf, but matched ${matchCount}`,
94
+ });
95
+ }
96
+ return;
97
+ }
98
+
99
+ // enum
100
+ if (schema.enum) {
101
+ if (!schema.enum.some((e) => JSON.stringify(e) === JSON.stringify(value))) {
102
+ errors.push({
103
+ path,
104
+ message: `Value must be one of: ${schema.enum.map((e) => JSON.stringify(e)).join(', ')}`,
105
+ actual: JSON.stringify(value),
106
+ });
107
+ }
108
+ return;
109
+ }
110
+
111
+ // type checking
112
+ if (schema.type) {
113
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type];
114
+ const actualType = getJsonType(value);
115
+
116
+ if (!types.includes(actualType)) {
117
+ // Allow integer to match number
118
+ if (!(actualType === 'integer' && types.includes('number'))) {
119
+ // Allow null if nullable
120
+ if (!(actualType === 'null' && schema.nullable)) {
121
+ errors.push({
122
+ path,
123
+ message: `Expected type ${types.join('|')}, got ${actualType}`,
124
+ expected: types.join('|'),
125
+ actual: actualType,
126
+ });
127
+ return;
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ // string validations
134
+ if (typeof value === 'string') {
135
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
136
+ errors.push({
137
+ path,
138
+ message: `String length ${value.length} is less than minimum ${schema.minLength}`,
139
+ });
140
+ }
141
+ if (schema.maxLength !== undefined && value.length > schema.maxLength) {
142
+ errors.push({
143
+ path,
144
+ message: `String length ${value.length} exceeds maximum ${schema.maxLength}`,
145
+ });
146
+ }
147
+ if (schema.pattern) {
148
+ try {
149
+ if (!new RegExp(schema.pattern).test(value)) {
150
+ errors.push({
151
+ path,
152
+ message: `String does not match pattern: ${schema.pattern}`,
153
+ });
154
+ }
155
+ } catch {
156
+ // Invalid regex — skip validation
157
+ }
158
+ }
159
+ }
160
+
161
+ // number validations
162
+ if (typeof value === 'number') {
163
+ if (schema.minimum !== undefined && value < schema.minimum) {
164
+ errors.push({
165
+ path,
166
+ message: `Value ${value} is less than minimum ${schema.minimum}`,
167
+ });
168
+ }
169
+ if (schema.maximum !== undefined && value > schema.maximum) {
170
+ errors.push({
171
+ path,
172
+ message: `Value ${value} exceeds maximum ${schema.maximum}`,
173
+ });
174
+ }
175
+ }
176
+
177
+ // object validations
178
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
179
+ const obj = value as Record<string, unknown>;
180
+
181
+ // required
182
+ if (schema.required) {
183
+ for (const req of schema.required) {
184
+ if (!(req in obj)) {
185
+ errors.push({
186
+ path: `${path}.${req}`,
187
+ message: `Missing required property: ${req}`,
188
+ });
189
+ }
190
+ }
191
+ }
192
+
193
+ // properties
194
+ if (schema.properties) {
195
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
196
+ if (key in obj) {
197
+ validate(obj[key], propSchema, `${path}.${key}`, errors);
198
+ }
199
+ }
200
+ }
201
+
202
+ // additionalProperties
203
+ if (schema.additionalProperties === false && schema.properties) {
204
+ const allowed = new Set(Object.keys(schema.properties));
205
+ for (const key of Object.keys(obj)) {
206
+ if (!allowed.has(key)) {
207
+ errors.push({
208
+ path: `${path}.${key}`,
209
+ message: `Additional property not allowed: ${key}`,
210
+ });
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ // array validations
217
+ if (Array.isArray(value)) {
218
+ if (schema.items) {
219
+ for (let i = 0; i < value.length; i++) {
220
+ validate(value[i], schema.items, `${path}[${i}]`, errors);
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ function getJsonType(value: unknown): string {
227
+ if (value === null) return 'null';
228
+ if (value === undefined) return 'null';
229
+ if (Array.isArray(value)) return 'array';
230
+ if (typeof value === 'number') {
231
+ return Number.isInteger(value) ? 'integer' : 'number';
232
+ }
233
+ return typeof value; // 'string', 'boolean', 'object'
234
+ }
@@ -0,0 +1,67 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { requirePro } from '../lib/license.js';
4
+ import { parseOpenApiSpec } from '../lib/openapi-parser.js';
5
+ import { compareSpecs } from '../lib/code-generator.js';
6
+
7
+ export function registerCompareSpecs(server: McpServer): void {
8
+ server.tool(
9
+ 'compare_specs',
10
+ '[Pro] Diff two OpenAPI specs and find breaking changes, non-breaking changes, new endpoints, and removed endpoints. Essential for API versioning.',
11
+ {
12
+ old_spec: z.string().describe('The old/previous OpenAPI spec as JSON or YAML'),
13
+ new_spec: z.string().describe('The new/updated OpenAPI spec as JSON or YAML'),
14
+ },
15
+ async ({ old_spec, new_spec }) => {
16
+ try {
17
+ await requirePro();
18
+
19
+ const oldParsed = parseOpenApiSpec(old_spec);
20
+ const newParsed = parseOpenApiSpec(new_spec);
21
+ const comparison = compareSpecs(oldParsed, newParsed);
22
+
23
+ return {
24
+ content: [
25
+ {
26
+ type: 'text' as const,
27
+ text: JSON.stringify({
28
+ summary: {
29
+ breaking_changes: comparison.breaking_changes.length,
30
+ non_breaking_changes: comparison.non_breaking.length,
31
+ new_endpoints: comparison.new_endpoints.length,
32
+ removed_endpoints: comparison.removed_endpoints.length,
33
+ is_backwards_compatible: comparison.breaking_changes.length === 0,
34
+ },
35
+ breaking_changes: comparison.breaking_changes,
36
+ non_breaking_changes: comparison.non_breaking,
37
+ new_endpoints: comparison.new_endpoints,
38
+ removed_endpoints: comparison.removed_endpoints,
39
+ }, null, 2),
40
+ },
41
+ ],
42
+ };
43
+ } catch (err) {
44
+ const message = err instanceof Error ? err.message : String(err);
45
+ const isLicense = message.includes('TRUSS Pro license');
46
+
47
+ return {
48
+ content: [
49
+ {
50
+ type: 'text' as const,
51
+ text: JSON.stringify({
52
+ error: message,
53
+ ...(isLicense
54
+ ? {
55
+ upgrade_url: 'https://truss.dev/pricing',
56
+ price: '$25/mo',
57
+ }
58
+ : {}),
59
+ }, null, 2),
60
+ },
61
+ ],
62
+ isError: true,
63
+ };
64
+ }
65
+ }
66
+ );
67
+ }
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { requirePro } from '../lib/license.js';
4
+ import { parseOpenApiSpec } from '../lib/openapi-parser.js';
5
+ import { generateClient } from '../lib/code-generator.js';
6
+
7
+ export function registerGenerateClient(server: McpServer): void {
8
+ server.tool(
9
+ 'generate_client',
10
+ '[Pro] Generate a typed API client from an OpenAPI spec. Supports TypeScript (fetch/axios), Python (requests), and Go (net/http). Produces ready-to-use code with type definitions.',
11
+ {
12
+ spec: z.string().describe('OpenAPI/Swagger spec as JSON or YAML string'),
13
+ language: z
14
+ .enum(['typescript', 'python', 'go'])
15
+ .describe('Target language for the client'),
16
+ style: z
17
+ .enum(['fetch', 'axios'])
18
+ .optional()
19
+ .default('fetch')
20
+ .describe('HTTP library style (TypeScript only, default: fetch)'),
21
+ },
22
+ async ({ spec, language, style }) => {
23
+ try {
24
+ await requirePro();
25
+
26
+ const parsed = parseOpenApiSpec(spec);
27
+ const code = generateClient(parsed, language, style);
28
+
29
+ const extensions: Record<string, string> = {
30
+ typescript: '.ts',
31
+ python: '.py',
32
+ go: '.go',
33
+ };
34
+
35
+ const filename = `api_client${extensions[language]}`;
36
+
37
+ return {
38
+ content: [
39
+ {
40
+ type: 'text' as const,
41
+ text: JSON.stringify({
42
+ filename,
43
+ language,
44
+ style: language === 'typescript' ? style : undefined,
45
+ endpoint_count: parsed.endpoints.length,
46
+ code,
47
+ }, null, 2),
48
+ },
49
+ ],
50
+ };
51
+ } catch (err) {
52
+ const message = err instanceof Error ? err.message : String(err);
53
+ const isLicense = message.includes('TRUSS Pro license');
54
+
55
+ return {
56
+ content: [
57
+ {
58
+ type: 'text' as const,
59
+ text: JSON.stringify({
60
+ error: message,
61
+ ...(isLicense
62
+ ? {
63
+ upgrade_url: 'https://truss.dev/pricing',
64
+ price: '$25/mo',
65
+ }
66
+ : {}),
67
+ }, null, 2),
68
+ },
69
+ ],
70
+ isError: true,
71
+ };
72
+ }
73
+ }
74
+ );
75
+ }
@@ -0,0 +1,67 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { requirePro } from '../lib/license.js';
4
+ import { generateOpenApiFromExamples } from '../lib/openapi-parser.js';
5
+
6
+ export function registerGenerateOpenApi(server: McpServer): void {
7
+ server.tool(
8
+ 'generate_openapi',
9
+ '[Pro] Auto-generate an OpenAPI 3.0 spec from example requests and responses. Infers schemas, path parameters, query parameters, and response types.',
10
+ {
11
+ examples: z.array(
12
+ z.object({
13
+ method: z.string().describe('HTTP method (GET, POST, etc.)'),
14
+ url: z.string().describe('Full URL of the request'),
15
+ request_body: z.unknown().optional().describe('Request body sent'),
16
+ response_body: z.unknown().describe('Response body received'),
17
+ status: z.number().describe('HTTP status code'),
18
+ headers: z.record(z.string(), z.string()).optional().describe('Request headers'),
19
+ })
20
+ ).min(1).describe('Array of example request/response pairs'),
21
+ },
22
+ async ({ examples }) => {
23
+ try {
24
+ await requirePro();
25
+
26
+ const yamlSpec = generateOpenApiFromExamples(examples as import('../types.js').RequestExample[]);
27
+
28
+ return {
29
+ content: [
30
+ {
31
+ type: 'text' as const,
32
+ text: yamlSpec,
33
+ },
34
+ ],
35
+ };
36
+ } catch (err) {
37
+ const message = err instanceof Error ? err.message : String(err);
38
+ const isLicense = message.includes('TRUSS Pro license');
39
+
40
+ return {
41
+ content: [
42
+ {
43
+ type: 'text' as const,
44
+ text: JSON.stringify({
45
+ error: message,
46
+ ...(isLicense
47
+ ? {
48
+ upgrade_url: 'https://truss.dev/pricing',
49
+ price: '$25/mo',
50
+ features: [
51
+ 'Generate OpenAPI specs from examples',
52
+ 'Generate API test suites',
53
+ 'Mock server generation',
54
+ 'Spec comparison & breaking change detection',
55
+ 'Multi-language client code generation',
56
+ ],
57
+ }
58
+ : {}),
59
+ }, null, 2),
60
+ },
61
+ ],
62
+ isError: true,
63
+ };
64
+ }
65
+ }
66
+ );
67
+ }
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { requirePro } from '../lib/license.js';
4
+ import { parseOpenApiSpec } from '../lib/openapi-parser.js';
5
+ import { generateTests } from '../lib/code-generator.js';
6
+
7
+ export function registerGenerateTests(server: McpServer): void {
8
+ server.tool(
9
+ 'generate_tests',
10
+ '[Pro] Generate a complete API test suite from an OpenAPI spec. Supports Jest, Vitest, and Pytest frameworks. Generates happy-path and error-case tests.',
11
+ {
12
+ spec: z.string().describe('OpenAPI/Swagger spec as JSON or YAML string'),
13
+ base_url: z.string().describe('Base URL of the API to test (e.g., http://localhost:3000)'),
14
+ framework: z
15
+ .enum(['jest', 'vitest', 'pytest'])
16
+ .optional()
17
+ .default('vitest')
18
+ .describe('Test framework to generate for (default: vitest)'),
19
+ },
20
+ async ({ spec, base_url, framework }) => {
21
+ try {
22
+ await requirePro();
23
+
24
+ const parsed = parseOpenApiSpec(spec);
25
+ const testCode = generateTests(parsed, base_url, framework);
26
+
27
+ const filename =
28
+ framework === 'pytest'
29
+ ? 'test_api.py'
30
+ : `api.test.${framework === 'jest' ? 'js' : 'ts'}`;
31
+
32
+ return {
33
+ content: [
34
+ {
35
+ type: 'text' as const,
36
+ text: JSON.stringify({
37
+ filename,
38
+ framework,
39
+ endpoint_count: parsed.endpoints.length,
40
+ code: testCode,
41
+ }, null, 2),
42
+ },
43
+ ],
44
+ };
45
+ } catch (err) {
46
+ const message = err instanceof Error ? err.message : String(err);
47
+ const isLicense = message.includes('TRUSS Pro license');
48
+
49
+ return {
50
+ content: [
51
+ {
52
+ type: 'text' as const,
53
+ text: JSON.stringify({
54
+ error: message,
55
+ ...(isLicense
56
+ ? {
57
+ upgrade_url: 'https://truss.dev/pricing',
58
+ price: '$25/mo',
59
+ }
60
+ : {}),
61
+ }, null, 2),
62
+ },
63
+ ],
64
+ isError: true,
65
+ };
66
+ }
67
+ }
68
+ );
69
+ }
@@ -0,0 +1,68 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { requirePro } from '../lib/license.js';
4
+ import { parseOpenApiSpec } from '../lib/openapi-parser.js';
5
+ import { generateMockServer } from '../lib/code-generator.js';
6
+
7
+ export function registerMockServer(server: McpServer): void {
8
+ server.tool(
9
+ 'mock_server',
10
+ '[Pro] Generate a mock server configuration from an OpenAPI spec. Produces routes with sample responses and a db.json file ready for json-server or similar.',
11
+ {
12
+ spec: z.string().describe('OpenAPI/Swagger spec as JSON or YAML string'),
13
+ },
14
+ async ({ spec }) => {
15
+ try {
16
+ await requirePro();
17
+
18
+ const parsed = parseOpenApiSpec(spec);
19
+ const mockConfig = generateMockServer(parsed);
20
+
21
+ return {
22
+ content: [
23
+ {
24
+ type: 'text' as const,
25
+ text: JSON.stringify({
26
+ route_count: mockConfig.routes.length,
27
+ routes: mockConfig.routes,
28
+ mock_data: mockConfig.mock_data,
29
+ usage_instructions: {
30
+ json_server: [
31
+ '1. Save mock_data to db.json',
32
+ '2. npx json-server db.json --port 3001',
33
+ '3. Access endpoints at http://localhost:3001',
34
+ ],
35
+ custom: [
36
+ 'Use the routes array to configure any mock server.',
37
+ 'Each route has: method, path, status, response, headers.',
38
+ ],
39
+ },
40
+ }, null, 2),
41
+ },
42
+ ],
43
+ };
44
+ } catch (err) {
45
+ const message = err instanceof Error ? err.message : String(err);
46
+ const isLicense = message.includes('TRUSS Pro license');
47
+
48
+ return {
49
+ content: [
50
+ {
51
+ type: 'text' as const,
52
+ text: JSON.stringify({
53
+ error: message,
54
+ ...(isLicense
55
+ ? {
56
+ upgrade_url: 'https://truss.dev/pricing',
57
+ price: '$25/mo',
58
+ }
59
+ : {}),
60
+ }, null, 2),
61
+ },
62
+ ],
63
+ isError: true,
64
+ };
65
+ }
66
+ }
67
+ );
68
+ }
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { parseOpenApiSpec } from '../lib/openapi-parser.js';
4
+
5
+ export function registerParseOpenApi(server: McpServer): void {
6
+ server.tool(
7
+ 'parse_openapi',
8
+ 'Parse an OpenAPI 3.x or Swagger 2.0 spec (JSON or YAML) and list all endpoints with their parameters, request bodies, and response schemas.',
9
+ {
10
+ spec: z.string().describe('OpenAPI/Swagger spec content as JSON or YAML string'),
11
+ },
12
+ async ({ spec }) => {
13
+ try {
14
+ const parsed = parseOpenApiSpec(spec);
15
+
16
+ return {
17
+ content: [
18
+ {
19
+ type: 'text' as const,
20
+ text: JSON.stringify({
21
+ info: parsed.info,
22
+ servers: parsed.servers,
23
+ endpoint_count: parsed.endpoints.length,
24
+ endpoints: parsed.endpoints.map((ep) => ({
25
+ method: ep.method,
26
+ path: ep.path,
27
+ summary: ep.summary,
28
+ operationId: ep.operationId,
29
+ tags: ep.tags,
30
+ parameters: ep.parameters,
31
+ request_body: ep.requestBody,
32
+ responses: ep.responses,
33
+ })),
34
+ }, null, 2),
35
+ },
36
+ ],
37
+ };
38
+ } catch (err) {
39
+ return {
40
+ content: [
41
+ {
42
+ type: 'text' as const,
43
+ text: JSON.stringify({
44
+ error: err instanceof Error ? err.message : String(err),
45
+ hint: 'Provide a valid OpenAPI 3.x or Swagger 2.0 spec as JSON or YAML string.',
46
+ }, null, 2),
47
+ },
48
+ ],
49
+ isError: true,
50
+ };
51
+ }
52
+ }
53
+ );
54
+ }