swagger-digest 0.1.2 → 0.2.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 CHANGED
@@ -45,6 +45,52 @@ npx swagger-digest --url https://api.example.com/openapi.json --stdout | pbcopy
45
45
 
46
46
  Run `npx swagger-digest --help` for all CLI options. The package requires Node.js 20 or newer.
47
47
 
48
+ ## MCP server
49
+
50
+ The package ships an [MCP](https://modelcontextprotocol.io) server exposing the same pipeline as a tool for AI agents, so they can fetch and digest API docs on their own instead of being handed a Markdown file.
51
+
52
+ ```bash
53
+ npx swagger-digest-mcp
54
+ ```
55
+
56
+ It speaks the stdio transport: the MCP client spawns the process and communicates over stdin/stdout. Register it in your client's configuration:
57
+
58
+ ```json
59
+ {
60
+ "mcp": {
61
+ "swagger-digest": {
62
+ "command": ["npx", "-y", "swagger-digest-mcp"]
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### The `swagger_digest` tool
69
+
70
+ | Argument | Type | Description |
71
+ | --------------- | ---------------- | -------------------------------------------------------------------------- |
72
+ | `url` | string, required | Swagger UI page URL or direct OpenAPI spec URL |
73
+ | `spec_url` | string, optional | Explicit spec URL; overrides discovery from the page |
74
+ | `headers` | object, optional | Request headers (e.g. `Authorization`); sent only to the origin of `url` |
75
+ | `include_tags` | string[] | Keep only operations with these tags |
76
+ | `exclude_tags` | string[] | Drop operations with these tags |
77
+ | `path_prefix` | string | Keep only paths starting with this prefix |
78
+ | `max_tokens` | number | Trim the digest to an approximate token budget |
79
+ | `write_to_file` | object, optional | `{ directory, filename }` — save the digest instead of returning it inline |
80
+
81
+ By default the digest is returned directly in the tool result (so it lands in the agent's context), with a metadata footer: retrieval method, operation/model counts, and a token estimate.
82
+
83
+ ### Environment variables
84
+
85
+ - `SWAGGER_DIGEST_HEADER` — newline-separated `Name: value` pairs, merged with (and overridden by) the tool call's `headers` argument. Keeps secrets out of the conversation.
86
+ - `SWAGGER_DIGEST_ALLOW_PRIVATE=1` — opt out of the private-network guard (see below).
87
+
88
+ ### Security differences from the CLI
89
+
90
+ - **SSRF guard on by default:** URLs resolving to loopback, private, or link-local (cloud metadata) addresses are rejected, because MCP clients supply URLs on the model's behalf. Opt out with `SWAGGER_DIGEST_ALLOW_PRIVATE=1` — for example to digest an internal corporate API — only if you trust the hosts involved.
91
+ - **No headless-browser fallback:** the optional Playwright fallback is disabled in the MCP server.
92
+ - Diagnostics go to stderr only; stdout carries protocol frames exclusively.
93
+
48
94
  ### Filtering and token budget
49
95
 
50
96
  Large specs produce large digests. The CLI can remove whole operations before rendering and/or trim the remaining digest to an approximate token budget:
@@ -179,6 +225,10 @@ src/
179
225
  transformer.ts OpenAPI/Swagger to compact Markdown and TypeScript-like types
180
226
  writer.ts Output path, filename defaults and file writing
181
227
  index.ts Public exports
228
+ mcp/
229
+ index.ts MCP stdio entry point (`swagger-digest-mcp` bin)
230
+ server.ts MCP server and tool registration (transport-agnostic)
231
+ digest.ts Tool handler: fetch → transform → tool result
182
232
  ```
183
233
 
184
234
  ## Limitations
@@ -0,0 +1,143 @@
1
+ import {
2
+ VERSION,
3
+ estimateTokens,
4
+ fetchOpenApiSpec,
5
+ mergeHeaders,
6
+ parseHeaderPairs,
7
+ transformOpenApiToMarkdown,
8
+ writeDigest
9
+ } from "./chunk-U7OLLL6J.js";
10
+
11
+ // src/mcp/digest.ts
12
+ var TOKEN_HINT_THRESHOLD = 8e4;
13
+ function textContent(text) {
14
+ return [{ type: "text", text }];
15
+ }
16
+ function asError(message) {
17
+ return { content: textContent(message), isError: true };
18
+ }
19
+ function isHttpUrl(value) {
20
+ try {
21
+ const url = new URL(value);
22
+ return url.protocol === "http:" || url.protocol === "https:";
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+ function countDigestOperations(markdown) {
28
+ return (markdown.match(/^\| \*\*(?:GET|PUT|POST|DELETE|OPTIONS|HEAD|PATCH|TRACE)\*\* \|/gm) ?? []).length;
29
+ }
30
+ function countDigestModels(markdown) {
31
+ return (markdown.match(/^export (?:interface|type) \w+/gm) ?? []).length;
32
+ }
33
+ function buildRequestHeaders(args) {
34
+ const fromEnv = process.env.SWAGGER_DIGEST_HEADER ? parseHeaderPairs(process.env.SWAGGER_DIGEST_HEADER) : {};
35
+ return mergeHeaders(fromEnv, args.headers);
36
+ }
37
+ async function runSwaggerDigest(args, options = {}) {
38
+ if (!isHttpUrl(args.url)) {
39
+ return asError(`Invalid url "${args.url}": use an http:// or https:// URL.`);
40
+ }
41
+ if (args.spec_url !== void 0 && !isHttpUrl(args.spec_url)) {
42
+ return asError(`Invalid spec_url "${args.spec_url}": use an http:// or https:// URL.`);
43
+ }
44
+ if (args.max_tokens !== void 0 && (!Number.isInteger(args.max_tokens) || args.max_tokens <= 0)) {
45
+ return asError(`Invalid max_tokens ${args.max_tokens}: use a positive integer.`);
46
+ }
47
+ const blockPrivateNetworks = options.blockPrivateNetworks ?? process.env.SWAGGER_DIGEST_ALLOW_PRIVATE !== "1";
48
+ const warnings = [];
49
+ const warn = (message) => {
50
+ warnings.push(message);
51
+ };
52
+ let fetchResult;
53
+ try {
54
+ const fetchOptions = {
55
+ useBrowserFallback: false,
56
+ maxBytes: 1e7,
57
+ headers: buildRequestHeaders(args),
58
+ specUrl: args.spec_url,
59
+ blockPrivateNetworks,
60
+ warn
61
+ };
62
+ fetchResult = await fetchOpenApiSpec(args.url, fetchOptions);
63
+ } catch (error) {
64
+ const message = error instanceof Error ? error.message : String(error);
65
+ return asError(`Could not extract an OpenAPI definition from ${args.url}: ${message}`);
66
+ }
67
+ const markdown = transformOpenApiToMarkdown(fetchResult.spec, fetchResult.sourceUrl, {
68
+ includeTags: args.include_tags,
69
+ excludeTags: args.exclude_tags,
70
+ pathPrefix: args.path_prefix,
71
+ maxTokens: args.max_tokens,
72
+ onWarning: warn
73
+ });
74
+ const operationCount = countDigestOperations(markdown);
75
+ const modelCount = countDigestModels(markdown);
76
+ const tokens = estimateTokens(markdown).models.claude.tokens;
77
+ const metadata = [
78
+ `source: ${fetchResult.sourceUrl} (via ${fetchResult.retrievalMethod})`,
79
+ `${operationCount} operation${operationCount === 1 ? "" : "s"}`,
80
+ `${modelCount} data model${modelCount === 1 ? "" : "s"}`,
81
+ `~${tokens.toLocaleString("en-US")} tokens estimated`
82
+ ].join(" \xB7 ");
83
+ const warningBlock = warnings.length > 0 ? `
84
+
85
+ _Warnings:_
86
+ ${warnings.map((w) => `- ${w}`).join("\n")}` : "";
87
+ const budgetHint = tokens > TOKEN_HINT_THRESHOLD && args.max_tokens === void 0 ? "\n\n_This digest is large. Consider re-running with max_tokens, include_tags, exclude_tags or path_prefix to fit it into context._" : "";
88
+ if (args.write_to_file) {
89
+ try {
90
+ const outputPath = await writeDigest({
91
+ markdown,
92
+ directory: args.write_to_file.directory,
93
+ filename: args.write_to_file.filename
94
+ });
95
+ return { content: textContent(`Digest written to ${outputPath}
96
+
97
+ _${metadata}_${warningBlock}`) };
98
+ } catch (error) {
99
+ const message = error instanceof Error ? error.message : String(error);
100
+ return asError(`Could not write digest: ${message}`);
101
+ }
102
+ }
103
+ return { content: textContent(`${markdown}
104
+
105
+ ---
106
+ _Digest metadata: ${metadata}_${warningBlock}${budgetHint}`) };
107
+ }
108
+
109
+ // src/mcp/server.ts
110
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
111
+ import { z } from "zod";
112
+ function createMcpServer(options = {}) {
113
+ const server = new McpServer({ name: "swagger-digest", version: VERSION });
114
+ server.registerTool(
115
+ "swagger_digest",
116
+ {
117
+ title: "Generate API digest",
118
+ description: "Fetch a Swagger UI page or an OpenAPI/Swagger spec (JSON or YAML, versions 2.0/3.0/3.1) and convert it into a compact Markdown API reference optimized for LLM context: endpoint index, request/response details, authentication schemes, and TypeScript-like data types. Handles Swagger UI discovery automatically (initializer scripts, multi-spec dropdowns, common spec paths). Use this whenever the user asks to understand, summarize, or work against an API documented with Swagger/OpenAPI.",
119
+ inputSchema: {
120
+ url: z.string().describe("Swagger UI page URL or direct OpenAPI spec URL (http/https)"),
121
+ spec_url: z.string().optional().describe("Explicit OpenAPI spec URL; overrides discovery from the Swagger UI page"),
122
+ headers: z.record(z.string(), z.string()).optional().describe(
123
+ "Request headers (e.g. Authorization or Cookie). Sent ONLY to the origin of url; cross-origin requests never receive them. Values are not logged."
124
+ ),
125
+ include_tags: z.array(z.string()).optional().describe("Keep only operations carrying at least one of these tags"),
126
+ exclude_tags: z.array(z.string()).optional().describe("Drop operations carrying any of these tags"),
127
+ path_prefix: z.string().optional().describe("Keep only operations whose path starts with this prefix"),
128
+ max_tokens: z.number().int().positive().optional().describe(
129
+ "Trim the digest until an approximate token estimate fits this budget (heuristic, not tokenizer-accurate)"
130
+ ),
131
+ write_to_file: z.object({ directory: z.string(), filename: z.string() }).optional().describe("Save the digest to a file instead of returning it inline")
132
+ }
133
+ },
134
+ (args) => runSwaggerDigest(args, options)
135
+ );
136
+ return server;
137
+ }
138
+
139
+ export {
140
+ buildRequestHeaders,
141
+ runSwaggerDigest,
142
+ createMcpServer
143
+ };
@@ -1,14 +1,15 @@
1
1
  // src/version.ts
2
2
  import { createRequire } from "module";
3
3
  function readPackageVersion(moduleUrl) {
4
- try {
5
- const require2 = createRequire(moduleUrl);
6
- const pkg = require2("../package.json");
7
- if (typeof pkg?.version === "string" && pkg.version) return pkg.version;
8
- return "unknown";
9
- } catch {
10
- return "unknown";
4
+ const require2 = createRequire(moduleUrl);
5
+ for (const candidate of ["../package.json", "../../package.json"]) {
6
+ try {
7
+ const pkg = require2(candidate);
8
+ if (typeof pkg?.version === "string" && pkg.version) return pkg.version;
9
+ } catch {
10
+ }
11
11
  }
12
+ return "unknown";
12
13
  }
13
14
  var VERSION = readPackageVersion(import.meta.url);
14
15
  var USER_AGENT = `swagger-digest/${VERSION}`;
@@ -2103,6 +2104,61 @@ function transformOpenApiToMarkdown(spec, sourceUrl, options = {}) {
2103
2104
  return appendTrimNote(markdown, allActions, options.maxTokens);
2104
2105
  }
2105
2106
 
2107
+ // src/headers.ts
2108
+ var HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
2109
+ function validateHeaderName(name) {
2110
+ if (!name) throw new Error('Header name must not be empty. Expected "Name: value".');
2111
+ if (/[\r\n]/.test(name)) {
2112
+ throw new Error(`Invalid header name: header names must not contain CR/LF characters.`);
2113
+ }
2114
+ if (!HEADER_NAME_PATTERN.test(name)) {
2115
+ throw new Error(`Invalid header name "${name}": use a valid HTTP header token (letters, digits and -_).`);
2116
+ }
2117
+ }
2118
+ function validateHeaderValue(name, value) {
2119
+ if (/[\r\n]/.test(value)) {
2120
+ throw new Error(`Invalid value for header "${name}": values must not contain CR/LF.`);
2121
+ }
2122
+ if (/\0/.test(value)) {
2123
+ throw new Error(`Invalid value for header "${name}": values must not contain NUL bytes.`);
2124
+ }
2125
+ }
2126
+ function parseHeaderArgument(raw) {
2127
+ const separator = raw.indexOf(":");
2128
+ if (separator === -1) {
2129
+ if (/[\r\n]/.test(raw)) {
2130
+ throw new Error(`Invalid header: names and values must not contain CR/LF.`);
2131
+ }
2132
+ throw new Error(`Invalid header \u2014 expected "Name: value" format (for example "Authorization: Bearer <token>").`);
2133
+ }
2134
+ const name = raw.slice(0, separator).trim();
2135
+ const value = raw.slice(separator + 1).trim();
2136
+ validateHeaderName(name);
2137
+ validateHeaderValue(name, value);
2138
+ return { [name.toLowerCase()]: value };
2139
+ }
2140
+ function parseHeaderPairs(raw) {
2141
+ const result = {};
2142
+ for (const line of raw.split(/\r?\n/)) {
2143
+ if (!line.trim()) continue;
2144
+ Object.assign(result, parseHeaderArgument(line));
2145
+ }
2146
+ return result;
2147
+ }
2148
+ function mergeHeaders(...sources) {
2149
+ const result = {};
2150
+ for (const source of sources) {
2151
+ if (!source) continue;
2152
+ for (const [name, value] of Object.entries(source)) {
2153
+ if (/[\r\n]/.test(name) || /[\r\n]/.test(value)) {
2154
+ throw new Error(`Invalid header "${name}": CR/LF is not allowed in header names or values.`);
2155
+ }
2156
+ result[name.toLowerCase()] = value;
2157
+ }
2158
+ }
2159
+ return result;
2160
+ }
2161
+
2106
2162
  // src/writer.ts
2107
2163
  import { mkdir, writeFile } from "fs/promises";
2108
2164
  import os from "os";
@@ -2141,6 +2197,9 @@ export {
2141
2197
  fetchOpenApiSpec,
2142
2198
  estimateTokens,
2143
2199
  transformOpenApiToMarkdown,
2200
+ parseHeaderArgument,
2201
+ parseHeaderPairs,
2202
+ mergeHeaders,
2144
2203
  defaultOutputDirectory,
2145
2204
  defaultFilename,
2146
2205
  resolveOutputDirectory,
package/dist/cli.js CHANGED
@@ -5,72 +5,18 @@ import {
5
5
  defaultOutputDirectory,
6
6
  estimateTokens,
7
7
  fetchOpenApiSpec,
8
+ mergeHeaders,
9
+ parseHeaderArgument,
10
+ parseHeaderPairs,
8
11
  transformOpenApiToMarkdown,
9
12
  writeDigest
10
- } from "./chunk-CBSDTIKN.js";
13
+ } from "./chunk-U7OLLL6J.js";
11
14
 
12
15
  // src/cli.ts
13
16
  import { input } from "@inquirer/prompts";
14
17
  import chalk from "chalk";
15
18
  import { Command } from "commander";
16
19
  import ora from "ora";
17
-
18
- // src/headers.ts
19
- var HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
20
- function validateHeaderName(name) {
21
- if (!name) throw new Error('Header name must not be empty. Expected "Name: value".');
22
- if (/[\r\n]/.test(name)) {
23
- throw new Error(`Invalid header name: header names must not contain CR/LF characters.`);
24
- }
25
- if (!HEADER_NAME_PATTERN.test(name)) {
26
- throw new Error(`Invalid header name "${name}": use a valid HTTP header token (letters, digits and -_).`);
27
- }
28
- }
29
- function validateHeaderValue(name, value) {
30
- if (/[\r\n]/.test(value)) {
31
- throw new Error(`Invalid value for header "${name}": values must not contain CR/LF.`);
32
- }
33
- if (/\0/.test(value)) {
34
- throw new Error(`Invalid value for header "${name}": values must not contain NUL bytes.`);
35
- }
36
- }
37
- function parseHeaderArgument(raw) {
38
- const separator = raw.indexOf(":");
39
- if (separator === -1) {
40
- if (/[\r\n]/.test(raw)) {
41
- throw new Error(`Invalid header: names and values must not contain CR/LF.`);
42
- }
43
- throw new Error(`Invalid header \u2014 expected "Name: value" format (for example "Authorization: Bearer <token>").`);
44
- }
45
- const name = raw.slice(0, separator).trim();
46
- const value = raw.slice(separator + 1).trim();
47
- validateHeaderName(name);
48
- validateHeaderValue(name, value);
49
- return { [name.toLowerCase()]: value };
50
- }
51
- function parseHeaderPairs(raw) {
52
- const result = {};
53
- for (const line of raw.split(/\r?\n/)) {
54
- if (!line.trim()) continue;
55
- Object.assign(result, parseHeaderArgument(line));
56
- }
57
- return result;
58
- }
59
- function mergeHeaders(...sources) {
60
- const result = {};
61
- for (const source of sources) {
62
- if (!source) continue;
63
- for (const [name, value] of Object.entries(source)) {
64
- if (/[\r\n]/.test(name) || /[\r\n]/.test(value)) {
65
- throw new Error(`Invalid header "${name}": CR/LF is not allowed in header names or values.`);
66
- }
67
- result[name.toLowerCase()] = value;
68
- }
69
- }
70
- return result;
71
- }
72
-
73
- // src/cli.ts
74
20
  function validateUrl(value) {
75
21
  try {
76
22
  const url = new URL(value);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
+
1
4
  type JsonObject = Record<string, any>;
2
5
  interface FetchOptions {
3
6
  timeoutMs?: number;
@@ -38,6 +41,65 @@ declare function parseSpecText(text: string, source?: string): JsonObject;
38
41
  /** Resolve a Swagger UI page or a direct OpenAPI JSON/YAML URL into a parsed spec. */
39
42
  declare function fetchOpenApiSpec(input: string, options?: FetchOptions): Promise<FetchResult>;
40
43
 
44
+ /** Arguments of the `swagger_digest` MCP tool (mirrors the zod schema in server.ts). */
45
+ interface McpDigestArgs {
46
+ /** Swagger UI page URL or direct OpenAPI spec URL. */
47
+ url: string;
48
+ /** Explicit spec URL; overrides discovery from the Swagger UI page. */
49
+ spec_url?: string;
50
+ /** Request headers (e.g. Authorization). Sent only to the origin of `url`. */
51
+ headers?: Record<string, string>;
52
+ /** Keep only operations carrying at least one of these tags. */
53
+ include_tags?: string[];
54
+ /** Drop operations carrying any of these tags. */
55
+ exclude_tags?: string[];
56
+ /** Keep only operations whose path starts with this prefix. */
57
+ path_prefix?: string;
58
+ /** Trim the digest until the approximate token estimate fits this budget. */
59
+ max_tokens?: number;
60
+ /** Save the digest to a file instead of returning it inline. */
61
+ write_to_file?: {
62
+ directory: string;
63
+ filename: string;
64
+ };
65
+ }
66
+ /**
67
+ * Server-wide behavior switches. Tests pass these explicitly; the stdio entry
68
+ * point derives them from the environment.
69
+ */
70
+ interface DigestServerOptions {
71
+ /**
72
+ * Reject URLs resolving to loopback/private/metadata addresses. Defaults to
73
+ * true: an MCP client supplies URLs on the model's behalf, which is exactly
74
+ * the "server that accepts user-supplied URLs" case from the security notes.
75
+ * Users can opt out per deployment with SWAGGER_DIGEST_ALLOW_PRIVATE=1.
76
+ */
77
+ blockPrivateNetworks?: boolean;
78
+ }
79
+ /**
80
+ * Builds the request headers for a digest call: SWAGGER_DIGEST_HEADER
81
+ * environment entries first, then tool-call arguments (arguments win on
82
+ * conflicting names — same precedence as -H flags over the environment in the
83
+ * CLI). Header validation and origin scoping happen in headers.ts / fetcher.ts.
84
+ */
85
+ declare function buildRequestHeaders(args: Pick<McpDigestArgs, 'headers'>): Record<string, string>;
86
+ /**
87
+ * Runs one digest request and renders it as an MCP tool result. Expected
88
+ * failures (unreachable URLs, unparsable specs, invalid header arguments)
89
+ * become `isError` results carrying the message; unexpected errors propagate
90
+ * to the SDK, which converts them into protocol-level tool errors.
91
+ */
92
+ declare function runSwaggerDigest(args: McpDigestArgs, options?: DigestServerOptions): Promise<CallToolResult>;
93
+
94
+ /**
95
+ * Builds the MCP server exposing the `swagger_digest` tool.
96
+ *
97
+ * The instance is transport-agnostic: `src/mcp/index.ts` connects it to a
98
+ * StdioServerTransport, while tests connect it to an InMemoryTransport pair.
99
+ * No I/O happens at module scope or during construction.
100
+ */
101
+ declare function createMcpServer(options?: DigestServerOptions): McpServer;
102
+
41
103
  declare function toTypeScriptIdentifier(value: string): string;
42
104
 
43
105
  interface TransformOptions {
@@ -91,5 +153,5 @@ declare function writeDigest(options: {
91
153
  filename: string;
92
154
  }): Promise<string>;
93
155
 
94
- export { defaultFilename, defaultOutputDirectory, estimateTokens, fetchOpenApiSpec, isOpenApiSpec, normalizeFilename, parseSpecText, resolveOutputDirectory, toTypeScriptIdentifier, transformOpenApiToMarkdown, writeDigest };
95
- export type { DigestTokenReport, FetchOptions, FetchResult, JsonObject, ModelTokenEstimate, TransformOptions };
156
+ export { buildRequestHeaders, createMcpServer, defaultFilename, defaultOutputDirectory, estimateTokens, fetchOpenApiSpec, isOpenApiSpec, normalizeFilename, parseSpecText, resolveOutputDirectory, runSwaggerDigest, toTypeScriptIdentifier, transformOpenApiToMarkdown, writeDigest };
157
+ export type { DigestServerOptions, DigestTokenReport, FetchOptions, FetchResult, JsonObject, McpDigestArgs, ModelTokenEstimate, TransformOptions };
package/dist/index.js CHANGED
@@ -1,3 +1,8 @@
1
+ import {
2
+ buildRequestHeaders,
3
+ createMcpServer,
4
+ runSwaggerDigest
5
+ } from "./chunk-2NORV6KM.js";
1
6
  import {
2
7
  defaultFilename,
3
8
  defaultOutputDirectory,
@@ -10,8 +15,10 @@ import {
10
15
  toTypeScriptIdentifier,
11
16
  transformOpenApiToMarkdown,
12
17
  writeDigest
13
- } from "./chunk-CBSDTIKN.js";
18
+ } from "./chunk-U7OLLL6J.js";
14
19
  export {
20
+ buildRequestHeaders,
21
+ createMcpServer,
15
22
  defaultFilename,
16
23
  defaultOutputDirectory,
17
24
  estimateTokens,
@@ -20,6 +27,7 @@ export {
20
27
  normalizeFilename,
21
28
  parseSpecText,
22
29
  resolveOutputDirectory,
30
+ runSwaggerDigest,
23
31
  toTypeScriptIdentifier,
24
32
  transformOpenApiToMarkdown,
25
33
  writeDigest
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createMcpServer
4
+ } from "../chunk-2NORV6KM.js";
5
+ import "../chunk-U7OLLL6J.js";
6
+
7
+ // src/mcp/index.ts
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ async function main() {
10
+ const server = createMcpServer();
11
+ await server.connect(new StdioServerTransport());
12
+ }
13
+ main().catch((error) => {
14
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
15
+ process.exitCode = 1;
16
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swagger-digest",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Turn Swagger UI pages and OpenAPI specs into compact, AI-friendly Markdown API digests.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,7 +12,8 @@
12
12
  }
13
13
  },
14
14
  "bin": {
15
- "swagger-digest": "./dist/cli.js"
15
+ "swagger-digest": "./dist/cli.js",
16
+ "swagger-digest-mcp": "./dist/mcp/index.js"
16
17
  },
17
18
  "author": "VladLegkowski <VladLegkowski@users.noreply.github.com>",
18
19
  "repository": {
@@ -30,7 +31,7 @@
30
31
  "LICENSE"
31
32
  ],
32
33
  "scripts": {
33
- "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean",
34
+ "build": "tsup src/index.ts src/cli.ts src/mcp/index.ts --format esm --dts --clean",
34
35
  "dev": "tsx src/cli.ts",
35
36
  "start": "node dist/cli.js",
36
37
  "typecheck": "tsc --noEmit",
@@ -46,11 +47,13 @@
46
47
  },
47
48
  "dependencies": {
48
49
  "@inquirer/prompts": "^7.3.2",
50
+ "@modelcontextprotocol/sdk": "^1.32.2",
49
51
  "chalk": "^5.4.1",
50
52
  "cheerio": "^1.0.0",
51
53
  "commander": "^13.1.0",
52
54
  "ora": "^8.2.0",
53
- "yaml": "^2.7.0"
55
+ "yaml": "^2.7.0",
56
+ "zod": "^4.6.7"
54
57
  },
55
58
  "devDependencies": {
56
59
  "@types/node": "^22.15.0",