@powerduck/dev-mcp-server 0.1.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.
@@ -0,0 +1,384 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { Tool, Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
3
+ import { AuthConfig, CliConfig, DeclarativeAssertion, TestResult, TestReport, ScenarioReport } from '@powerduck/openapi-cli';
4
+ import { RequestValues } from '@powerduck/openapi-request';
5
+
6
+ interface ServerFact {
7
+ url: string;
8
+ description?: string;
9
+ /** Names of variables the server URL still requires. */
10
+ variables: string[];
11
+ }
12
+ interface SecuritySchemeFact {
13
+ name: string;
14
+ type: string;
15
+ scheme?: string;
16
+ bearerFormat?: string;
17
+ in?: string;
18
+ parameterName?: string;
19
+ openIdConnectUrl?: string;
20
+ /** OAuth2 flow names and the scopes each declares. */
21
+ flows: Record<string, string[]>;
22
+ }
23
+ interface CompactOperation {
24
+ /** Stable reference, e.g. "POST /orders". */
25
+ ref: string;
26
+ operationId?: string;
27
+ method: string;
28
+ path: string;
29
+ summary?: string;
30
+ tags: string[];
31
+ /** A non-empty security requirement applies to this operation. */
32
+ secured: boolean;
33
+ statusCodes: string[];
34
+ hasParameters: boolean;
35
+ hasRequestBody: boolean;
36
+ protocol: string;
37
+ }
38
+ interface ParameterFact {
39
+ name: string;
40
+ in: "path" | "query" | "header" | "cookie";
41
+ required: boolean;
42
+ description?: string;
43
+ deprecated: boolean;
44
+ /** Compact JSON Schema describing the parameter value. */
45
+ schema: Record<string, unknown>;
46
+ example?: unknown;
47
+ }
48
+ interface MediaFact {
49
+ /** Dereferenced JSON Schema for the payload. */
50
+ schema: unknown;
51
+ example?: unknown;
52
+ /** Named examples declared by the specification. */
53
+ examples: string[];
54
+ }
55
+ interface HeaderFact {
56
+ name: string;
57
+ required: boolean;
58
+ description?: string;
59
+ schema: Record<string, unknown>;
60
+ }
61
+ interface ResponseFact {
62
+ status: string;
63
+ description?: string;
64
+ headers: HeaderFact[];
65
+ content: Record<string, MediaFact>;
66
+ }
67
+ interface RequestBodyFact {
68
+ required: boolean;
69
+ description?: string;
70
+ content: Record<string, MediaFact>;
71
+ }
72
+ interface OperationFact {
73
+ ref: string;
74
+ operationId?: string;
75
+ method: string;
76
+ path: string;
77
+ summary?: string;
78
+ description?: string;
79
+ tags: string[];
80
+ deprecated: boolean;
81
+ idempotent: boolean;
82
+ protocol: string;
83
+ parameters: ParameterFact[];
84
+ requestBody?: RequestBodyFact;
85
+ responses: ResponseFact[];
86
+ /** Effective security requirements after operation-level overrides. */
87
+ security: SecuritySchemeFact[];
88
+ servers: ServerFact[];
89
+ }
90
+ type IssueSeverity = "error" | "warning" | "info";
91
+ interface ValidationIssue {
92
+ message: string;
93
+ severity: IssueSeverity;
94
+ path?: string;
95
+ }
96
+ interface ValidationFact {
97
+ valid: boolean;
98
+ openapiVersion?: string;
99
+ issues: ValidationIssue[];
100
+ }
101
+ interface OverviewFact {
102
+ title: string;
103
+ version: string;
104
+ description?: string;
105
+ openapiVersion?: string;
106
+ servers: ServerFact[];
107
+ endpointCount: number;
108
+ tags: string[];
109
+ protocols: string[];
110
+ schemaCount: number;
111
+ securitySchemes: SecuritySchemeFact[];
112
+ }
113
+ interface PagedOperations {
114
+ items: CompactOperation[];
115
+ total: number;
116
+ nextCursor?: string;
117
+ }
118
+ interface OperationLocator {
119
+ /** "METHOD /path" reference. */
120
+ ref?: string;
121
+ operationId?: string;
122
+ method?: string;
123
+ path?: string;
124
+ }
125
+ /** A failed tool call whose cause is the caller's input, not the server. */
126
+ declare class FactError extends Error {
127
+ readonly code: string;
128
+ readonly details?: unknown;
129
+ constructor(code: string, message: string, details?: unknown);
130
+ }
131
+
132
+ /**
133
+ * Loads an OpenAPI document from disk and keeps it fresh.
134
+ *
135
+ * A developer edits the spec while the coding agent is connected, so the
136
+ * store re-reads the file whenever its mtime changes. Parsing and validation
137
+ * are best effort: an in-progress document with validation issues must still
138
+ * answer fact queries, and the validate_spec tool surfaces those problems.
139
+ */
140
+
141
+ interface SpecSnapshot {
142
+ /** Absolute source path. */
143
+ path: string;
144
+ /** Document normalized to 3.x when conversion is safe; otherwise as authored. */
145
+ document: Record<string, any>;
146
+ /** Document with internal $ref values inlined where possible. */
147
+ dereferenced: Record<string, any>;
148
+ /** Raw source text, served by the `source` resource. */
149
+ sourceText: string;
150
+ mediaType: "application/yaml" | "application/json";
151
+ validation: ValidationFact;
152
+ mtimeMs: number;
153
+ }
154
+ declare class SpecStore {
155
+ readonly specPath: string;
156
+ private cache;
157
+ constructor(specPath: string);
158
+ /** Read and normalize the document, reusing the cache while mtime is stable. */
159
+ load(force?: boolean): Promise<SpecSnapshot>;
160
+ }
161
+
162
+ /**
163
+ * Builds the developer MCP server: read-only contract facts plus verification
164
+ * against a running backend.
165
+ *
166
+ * The server is transport agnostic. A SpecStore supplies a live document, and
167
+ * every request re-reads the file when it changed on disk. Long-running tools
168
+ * stream MCP $/progress and honor notifications/cancelled.
169
+ */
170
+
171
+ interface CreateServerOptions {
172
+ name?: string;
173
+ version?: string;
174
+ /** Free-form usage instructions surfaced during MCP initialization. */
175
+ instructions?: string;
176
+ }
177
+ declare function createDevMcpServer(store: SpecStore, options?: CreateServerOptions): Server;
178
+
179
+ /**
180
+ * stdio transport for AI coding clients (Claude Code, Codex CLI, Cursor,
181
+ * Claude Desktop). stdout is the JSON-RPC channel, so all accidental logging
182
+ * is diverted to stderr for the lifetime of the server.
183
+ */
184
+ interface StdioHandle {
185
+ closed: Promise<void>;
186
+ close(): Promise<void>;
187
+ }
188
+ interface StartStdioOptions {
189
+ specPath: string;
190
+ name?: string;
191
+ version?: string;
192
+ handleSignals?: boolean;
193
+ }
194
+ declare function startStdioServer(options: StartStdioOptions): Promise<StdioHandle>;
195
+
196
+ /**
197
+ * Builds the @powerduck/openapi-cli runner configuration for a verification
198
+ * request. Fact tools read an in-memory document; the runner also needs a real
199
+ * spec path (used as an identifier and fallback loader), which the SpecStore
200
+ * always has.
201
+ */
202
+
203
+ interface RunOverrides {
204
+ baseUrl?: string;
205
+ timeoutMs?: number;
206
+ concurrency?: number;
207
+ headers?: Record<string, string>;
208
+ variables?: Record<string, string>;
209
+ proxy?: string;
210
+ auth?: AuthConfig;
211
+ }
212
+ interface ProgressSink {
213
+ /** Emit MCP $/progress when a progress token was supplied by the client. */
214
+ report?: (progress: number, total: number, message?: string) => void;
215
+ /** Cancellation propagated from notifications/cancelled. */
216
+ signal?: AbortSignal;
217
+ }
218
+ declare function buildRunnerConfig(snapshot: SpecSnapshot, overrides: RunOverrides): CliConfig;
219
+ /** Resolves the target base URL from an explicit override or the spec servers. */
220
+ declare function resolveBaseUrl(snapshot: SpecSnapshot, explicit: string | undefined): string | undefined;
221
+ /** Maps raw tool arguments into typed runner overrides with validation. */
222
+ declare function runOverridesFromArgs(snapshot: SpecSnapshot, args: Record<string, unknown>): RunOverrides;
223
+
224
+ /**
225
+ * Developer MCP tools.
226
+ *
227
+ * Fact tools (M0) expose read-only contract truth. Verification tools (M1)
228
+ * send requests and run contract/scenario tests against a running backend via
229
+ * @powerduck/openapi-cli.
230
+ */
231
+
232
+ declare const devTools: Tool[];
233
+ /** Executes one tool call and returns a JSON-serializable result. */
234
+ declare function executeDevTool(name: string, args: Record<string, unknown>, store: SpecStore, sink?: ProgressSink): Promise<unknown>;
235
+
236
+ /**
237
+ * MCP resources expose the same facts as stable URIs, so agents can attach
238
+ * contract context directly to a conversation.
239
+ */
240
+
241
+ declare function listSpecResources(store: SpecStore): Promise<Resource[]>;
242
+ declare const specResourceTemplates: ResourceTemplate[];
243
+ interface ResourceContents {
244
+ uri: string;
245
+ mimeType: string;
246
+ text: string;
247
+ }
248
+ declare function readSpecResource(uri: string, store: SpecStore): Promise<ResourceContents>;
249
+
250
+ /**
251
+ * Pure fact extraction over an OpenAPI document.
252
+ *
253
+ * Every function here is deterministic and side-effect free. The MCP layer is
254
+ * a thin transport over these functions so the same logic is unit-testable
255
+ * without a JSON-RPC client.
256
+ */
257
+
258
+ declare const HTTP_METHODS: readonly ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
259
+ /** Resolves a local JSON pointer ("#/components/schemas/Foo"). */
260
+ declare function resolvePointer(doc: unknown, ref: string): unknown;
261
+ declare function overview(doc: Record<string, any>): OverviewFact;
262
+ interface ListOptions {
263
+ tag?: string;
264
+ method?: string;
265
+ protocol?: string;
266
+ secured?: boolean;
267
+ search?: string;
268
+ pageSize?: number;
269
+ cursor?: string;
270
+ }
271
+ declare function listOperations(doc: Record<string, any>, options?: ListOptions): PagedOperations;
272
+ declare function getOperation(doc: Record<string, any>, rawDoc: Record<string, any>, locator: OperationLocator): OperationFact;
273
+ interface SchemaLocator {
274
+ name?: string;
275
+ ref?: string;
276
+ }
277
+ declare function getSchema(doc: Record<string, any>, rawDoc: Record<string, any>, locator: SchemaLocator): {
278
+ name?: string;
279
+ schema: unknown;
280
+ };
281
+ declare function authRequirements(doc: Record<string, any>, locator?: OperationLocator): {
282
+ global: SecuritySchemeFact[];
283
+ operation?: SecuritySchemeFact[];
284
+ };
285
+
286
+ /**
287
+ * Project-level developer configuration (powerduck.dev.json).
288
+ *
289
+ * This file is safe to commit: it points at the specification and the local
290
+ * development server. Secrets must come from environment variables, never from
291
+ * this file.
292
+ */
293
+ interface DevConfig {
294
+ /** Path to the OpenAPI document, relative to the config file. */
295
+ spec?: string;
296
+ /** Default base URL of the local backend under development. */
297
+ baseUrl?: string;
298
+ /** Backend project root, used by future code-aware tools. */
299
+ project?: string;
300
+ }
301
+ /**
302
+ * Loads optional configuration. Explicit `--config` wins; otherwise a
303
+ * powerduck.dev.json in the current working directory is used when present.
304
+ */
305
+ declare function loadDevConfig(configPath?: string): {
306
+ config: DevConfig;
307
+ configDir: string;
308
+ };
309
+ /** Resolves a possibly config-relative path to an absolute one. */
310
+ declare function resolveFrom(configDir: string, target?: string): string | undefined;
311
+
312
+ /**
313
+ * Lightweight reachability probe. Unlike send_request this does not require a
314
+ * matching operation; it confirms the local backend is up before testing.
315
+ */
316
+ interface PingResult {
317
+ url: string;
318
+ reachable: boolean;
319
+ status?: number;
320
+ statusText?: string;
321
+ contentType?: string;
322
+ latencyMs: number;
323
+ error?: string;
324
+ }
325
+ declare function pingTarget(rawUrl: string, timeoutMs: number): Promise<PingResult>;
326
+
327
+ /**
328
+ * Single-operation verification: send one request to the local backend and
329
+ * report the normalized response plus spec-derived assertion results.
330
+ */
331
+
332
+ interface SendInput {
333
+ values?: RequestValues;
334
+ variables?: Record<string, string>;
335
+ assertions?: DeclarativeAssertion[];
336
+ /** Per-call target override; wins over the configured base URL. */
337
+ baseUrl?: string;
338
+ }
339
+ declare function sendOne(snapshot: SpecSnapshot, overrides: RunOverrides, locator: OperationLocator, input: SendInput): Promise<TestResult>;
340
+
341
+ /**
342
+ * Batch contract verification against a running backend.
343
+ *
344
+ * Mirrors openapi-cli's runTests but executes against the in-memory live
345
+ * document so the MCP never depends on a stale file on disk.
346
+ */
347
+
348
+ interface ContractFilter {
349
+ methods?: string[];
350
+ paths?: string[];
351
+ tags?: string[];
352
+ operationIds?: string[];
353
+ }
354
+ interface ContractSuiteResult {
355
+ summary: TestReport["summary"];
356
+ results: TestResult[];
357
+ generatedAt: string;
358
+ version: string;
359
+ }
360
+ declare function contractFilterFromArgs(args: Record<string, unknown>): ContractFilter;
361
+ declare function runContractSuite(snapshot: SpecSnapshot, overrides: RunOverrides, filter: ContractFilter, sink?: ProgressSink): Promise<ContractSuiteResult>;
362
+
363
+ /**
364
+ * Scenario validation and execution.
365
+ *
366
+ * Validation resolves every step reference up front and never touches the
367
+ * network, so an agent can confirm a plan before running it. Execution reuses
368
+ * openapi-cli's ordered, stateful engine with live progress and cancellation.
369
+ */
370
+
371
+ interface ScenarioValidation {
372
+ valid: boolean;
373
+ errors?: string[];
374
+ steps?: Array<{
375
+ index: number;
376
+ ref: string;
377
+ name?: string;
378
+ }>;
379
+ count?: number;
380
+ }
381
+ declare function validateScenario(snapshot: SpecSnapshot, rawScenario: unknown): ScenarioValidation;
382
+ declare function runScenarioTool(snapshot: SpecSnapshot, rawScenario: unknown, overrides: RunOverrides, sink?: ProgressSink): Promise<Omit<ScenarioReport, "config">>;
383
+
384
+ export { type CompactOperation, type ContractFilter, type ContractSuiteResult, type CreateServerOptions, type DevConfig, FactError, HTTP_METHODS, type HeaderFact, type ListOptions, type MediaFact, type OperationFact, type OperationLocator, type OverviewFact, type PagedOperations, type ParameterFact, type PingResult, type ProgressSink, type RequestBodyFact, type ResponseFact, type RunOverrides, type ScenarioValidation, type SecuritySchemeFact, type SendInput, type ServerFact, type SpecSnapshot, SpecStore, type StartStdioOptions, type StdioHandle, type ValidationFact, type ValidationIssue, authRequirements, buildRunnerConfig, contractFilterFromArgs, createDevMcpServer, devTools, executeDevTool, getOperation, getSchema, listOperations, listSpecResources, loadDevConfig, overview, pingTarget, readSpecResource, resolveBaseUrl, resolveFrom, resolvePointer, runContractSuite, runOverridesFromArgs, runScenarioTool, sendOne, specResourceTemplates, startStdioServer, validateScenario };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{createRequire as e}from"module";import{Server as t}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as r,CallToolRequestSchema as o,ListResourcesRequestSchema as n,ListResourceTemplatesRequestSchema as s,ReadResourceRequestSchema as i}from"@modelcontextprotocol/sdk/types.js";import{resolveConfig as a,executeStep as c,collectOperations as p,buildSummary as u,resolveSteps as d,runScenario as m}from"@powerduck/openapi-cli";import{createClient as l}from"@powerduck/openapi-request";import{StdioServerTransport as f}from"@modelcontextprotocol/sdk/server/stdio.js";import h from"fs";import{load as y}from"js-yaml";import{upgradeOasTo32 as g,validate as v,dereference as b}from"@powerduck/openapi-parser";import w from"path";e(import.meta.url);var x=class extends Error{code;details;constructor(e,t,r){super(t),this.name="FactError",this.code=e,this.details=r}},S=["get","post","put","patch","delete","head","options","trace"],j=new Set(["get","head","put","delete","options","trace"]);function _(e){return!!e&&"object"==typeof e&&!Array.isArray(e)}function O(e){return Array.isArray(e)?e:[]}function q(e){return"string"==typeof e&&e.trim()?e:void 0}function I(e,t){if(!t.startsWith("#/"))return;let r=e;for(const e of t.slice(2).split("/")){const t=e.replace(/~1/g,"/").replace(/~0/g,"~");if(!_(r)||!(t in r))return;r=r[t]}return r}function T(e,t,r=new Set,o=0){if(o>6)return e;if(_(e)){if("string"==typeof e.$ref){const n=e.$ref;if(r.has(n))return{$ref:n};const s=I(t,n);if(void 0===s)return e;const i=new Set(r).add(n);return T(s,t,i,o+1)}const n={};for(const[s,i]of Object.entries(e))n[s]=T(i,t,r,o+1);return n}return Array.isArray(e)?e.map(e=>T(e,t,r,o+1)):e}function A(e,t){const r=_(e)&&e["x-protocol"]||_(t)&&t["x-protocol"];if("string"==typeof r&&r.trim())return r.trim().toLowerCase();if(_(e)&&e["x-grpc"]||_(t)&&t["x-grpc"])return"grpc";if(_(e)&&e["x-graphql"]||_(t)&&t["x-graphql"])return"graphql";if(_(e)&&e["x-mcp"]||_(t)&&t["x-mcp"])return"mcp";if(_(e)&&(e["x-ws"]||e["x-websocket"])||_(t)&&t["x-ws"])return"websocket";const o=_(e)?e.responses:void 0;for(const e of Object.values(_(o)?o:{}))if(_(e)&&e.content?.["text/event-stream"])return"sse";return"http"}function U(e){const t=_(e.paths)?e.paths:{},r=[];for(const[e,o]of Object.entries(t))if(_(o))for(const t of S){const n=o[t];_(n)&&r.push({method:t.toUpperCase(),path:e,pathItem:o,operation:n,protocol:A(n,o)})}return r}function $(e){const t=new Map;for(const r of O(e.pathItem.parameters))_(r)&&r.name&&r.in&&t.set(`${r.in}:${r.name}`,r);for(const r of O(e.operation.parameters))_(r)&&r.name&&r.in&&t.set(`${r.in}:${r.name}`,r);return[...t.values()]}function P(e,t){return(Array.isArray(t.security)?t.security:Array.isArray(e.security)?e.security:[]).filter(_).map(e=>{const t={};for(const[r,o]of Object.entries(e))t[r]=Array.isArray(o)?o.map(String):[];return t})}function k(e){const t=_(e.components?.securitySchemes)?e.components.securitySchemes:{},r=[];for(const[e,o]of Object.entries(t)){const t=_(o)?o:{},n={};if(_(t.flows))for(const[e,r]of Object.entries(t.flows))_(r)&&_(r.scopes)&&(n[e]=Object.keys(r.scopes));const s={name:e,type:"string"==typeof t.type?t.type:"unknown",flows:n};q(t.scheme)&&(s.scheme=t.scheme),q(t.bearerFormat)&&(s.bearerFormat=t.bearerFormat),q(t.in)&&(s.in=t.in),q(t.name)&&(s.parameterName=t.name),q(t.openIdConnectUrl)&&(s.openIdConnectUrl=t.openIdConnectUrl),r.push(s)}return r}function C(e){return O(e).filter(e=>"string"==typeof e?.url).map(e=>({url:e.url,...q(e.description)?{description:e.description}:{},variables:_(e.variables)?Object.keys(e.variables):[]}))}function M(e){const t=U(e),r=new Set,o=new Set;for(const e of t){for(const t of O(e.operation.tags))"string"==typeof t&&r.add(t);o.add(e.protocol)}const n=_(e.components?.schemas)?e.components.schemas:{};return{title:q(e.info?.title)??"Untitled API",version:q(e.info?.version)??"0.0.0",...q(e.info?.description)?{description:e.info.description}:{},...q(e.openapi)?{openapiVersion:e.openapi}:{},servers:C(e.servers),endpointCount:t.length,tags:[...r].sort(),protocols:[...o].sort(),schemaCount:Object.keys(n).length,securitySchemes:k(e)}}function E(e){return Buffer.from(JSON.stringify({offset:e}),"utf8").toString("base64url")}function N(e,t={}){const r=t.method?.trim().toUpperCase(),o=t.protocol?.trim().toLowerCase(),n=t.tag?.trim().toLowerCase(),s=t.search?.trim().toLowerCase(),i=t.secured,a=U(e).map(t=>function(e,t){const r=$(t),o=_(t.operation.responses)?t.operation.responses:{};return{ref:`${t.method} ${t.path}`,...q(t.operation.operationId)?{operationId:t.operation.operationId}:{},method:t.method,path:t.path,...q(t.operation.summary)?{summary:t.operation.summary}:{},tags:O(t.operation.tags).filter(e=>"string"==typeof e),secured:P(e,t.operation).length>0,statusCodes:Object.keys(o),hasParameters:r.length>0,hasRequestBody:_(t.operation.requestBody),protocol:t.protocol}}(e,t)).filter(e=>{if(r&&e.method!==r)return!1;if(o&&e.protocol!==o)return!1;if(n&&!e.tags.some(e=>e.toLowerCase()===n))return!1;if(void 0!==i&&e.secured!==i)return!1;if(s){if(![e.ref,e.operationId??"",e.summary??"",e.path,e.tags.join(" ")].join(" ").toLowerCase().includes(s))return!1}return!0}),c=a.length,p=Math.min(Math.max(t.pageSize??100,1),500),u=function(e){if(!e)return 0;try{const t=JSON.parse(Buffer.from(e,"base64url").toString("utf8"));return Number.isInteger(t.offset)&&(t.offset??0)>=0?t.offset??0:0}catch{throw new x("invalid_cursor","The pagination cursor is malformed.")}}(t.cursor),d=a.slice(u,u+p),m=u+d.length;return{items:d,total:c,...m<c?{nextCursor:E(m)}:{}}}function L(e,t){const r={};for(const[o,n]of Object.entries(e)){if(!_(n))continue;const e={schema:n.schema?T(n.schema,t):{},examples:_(n.examples)?Object.keys(n.examples):[]};"example"in n&&(e.example=n.example),r[o]=e}return r}function R(e,t,r){const o=function(e,t){const r=U(e);let o;if(t.operationId)o=r.find(e=>e.operation.operationId===t.operationId);else if(t.method&&t.path){const e=t.method.trim().toUpperCase();o=r.find(r=>r.method===e&&r.path===t.path.trim())}else t.ref&&(o=r.find(e=>`${e.method} ${e.path}`===t.ref.trim()));if(!o)throw new x("operation_not_found",`No operation matches ${t.ref??t.operationId??`${t.method??""} ${t.path??""}`.trim()}.`);return o}(e,r),{operation:n,method:s,path:i}=o,a=$(o).map(e=>{const r=e.in,o={name:String(e.name),in:r,required:"path"===r||Boolean(e.required),deprecated:Boolean(e.deprecated),schema:e.schema?T(e.schema,t):{}};return q(e.description)&&(o.description=e.description),"example"in e&&(o.example=e.example),o}).sort((e,t)=>"path"===e.in!=("path"===t.in)?"path"===e.in?-1:1:e.required!==t.required?e.required?-1:1:e.name.localeCompare(t.name));let c;_(n.requestBody)&&_(n.requestBody.content)&&(c={required:Boolean(n.requestBody.required),...q(n.requestBody.description)?{description:n.requestBody.description}:{},content:L(n.requestBody.content,t)});const p=Object.entries(_(n.responses)?n.responses:{}).map(([e,r])=>{const o=_(r)?r:{},n=[];for(const[e,r]of Object.entries(_(o.headers)?o.headers:{})){const o=_(r)?r:{};n.push({name:e,required:Boolean(o.required),...q(o.description)?{description:o.description}:{},schema:o.schema?T(o.schema,t):{}})}return{status:e,...q(o.description)?{description:o.description}:{},headers:n,content:_(o.content)?L(o.content,t):{}}}),u=P(e,n),d=k(e),m=new Set(u.flatMap(e=>Object.keys(e))),l=d.filter(e=>m.has(e.name));return{ref:`${s} ${i}`,...q(n.operationId)?{operationId:n.operationId}:{},method:s,path:i,...q(n.summary)?{summary:n.summary}:{},...q(n.description)?{description:n.description}:{},tags:O(n.tags).filter(e=>"string"==typeof e),deprecated:Boolean(n.deprecated),idempotent:j.has(s.toLowerCase()),protocol:o.protocol,parameters:a,...c?{requestBody:c}:{},responses:p,security:l,servers:C(n.servers??e.servers)}}function B(e,t,r){let o,n;if(r.ref){o=I(t,r.ref);const e=r.ref.split("/");n=e[e.length-1]}else r.name&&(n=r.name,o=t.components?.schemas?.[r.name]);if(void 0===o)throw new x("schema_not_found",`No schema matches ${r.ref??r.name??"(empty locator)"}.`);return{...n?{name:n}:{},schema:T(o,t)}}function D(e,t){const r=k(e).filter(t=>(e.security??[]).some(e=>_(e)&&t.name in e));if(!t)return{global:r};return{global:r,operation:R(e,e,t).security}}async function J(e,t){let r;try{r=new URL(e)}catch{return{url:e,reachable:!1,latencyMs:0,error:"Invalid URL."}}if("http:"!==r.protocol&&"https:"!==r.protocol)return{url:e,reachable:!1,latencyMs:0,error:"Only http and https targets are supported."};const o=new AbortController,n=setTimeout(()=>o.abort(),t),s=Date.now();try{const t=await fetch(r,{method:"GET",signal:o.signal,redirect:"follow"}),n=t.headers.get("content-type");return{url:e,reachable:!0,status:t.status,statusText:t.statusText,...n?{contentType:n}:{},latencyMs:Date.now()-s}}catch(r){const o=r instanceof DOMException&&"AbortError"===r.name;return{url:e,reachable:!1,latencyMs:Date.now()-s,error:o?`Timed out after ${t}ms.`:r instanceof Error?r.message:String(r)}}finally{clearTimeout(n)}}function F(e,t){if(!e)return;const r=Object.entries(e).map(([e,r])=>`${e}${t}${r}`);return r.length?r:void 0}function H(e,t){const r={spec:e.path};t.baseUrl&&(r.server=t.baseUrl),t.timeoutMs&&(r.timeout=t.timeoutMs),t.concurrency&&(r.concurrency=t.concurrency),t.proxy&&(r.proxy=t.proxy);const o=F(t.headers,":");o&&(r.header=o);const n=F(t.variables,"=");n&&(r.variable=n);const s=a(r);return s.formats=[],s.outputDir="",s.failOnError=!1,t.auth&&(s.auth=t.auth),s}function z(e,t){if(t?.trim())return t.trim();const r=e.document.servers;if(Array.isArray(r))for(const e of r)if(e&&"string"==typeof e.url&&e.url.trim()&&!e.url.includes("{"))return e.url.trim()}function G(e,t){if(void 0===e)return;if(!e||"object"!=typeof e||Array.isArray(e))throw new Error(`${t} must be an object of strings.`);const r={};for(const[o,n]of Object.entries(e)){if("string"!=typeof n)throw new Error(`${t}.${o} must be a string.`);r[o]=n}return r}function W(e,t){const r=z(e,"string"==typeof t.baseUrl?t.baseUrl:void 0),o={};r&&(o.baseUrl=r),"number"==typeof t.timeoutMs&&(o.timeoutMs=V(t.timeoutMs,"timeoutMs",3e4,1,6e5)),"number"==typeof t.concurrency&&(o.concurrency=V(t.concurrency,"concurrency",5,1,20));const n=G(t.headers,"headers");n&&(o.headers=n);const s=G(t.variables,"variables");return s&&(o.variables=s),"string"==typeof t.proxy&&t.proxy.trim()&&(o.proxy=t.proxy.trim()),o}function V(e,t,r,o,n){if(null==e)return r;const s=Number(e);if(!Number.isFinite(s)||s<o)throw new Error(`${t} must be a number >= ${o}.`);return Math.min(Math.round(s),n)}var Y={};function K(e){return p(e.dereferenced,Y)}async function Q(e,t,r,o){const n=H(e,{...t,...o.baseUrl?{baseUrl:o.baseUrl}:{}}),s=function(e,t){const r=K(e);let o;if(t.operationId)o=r.find(e=>e.operationId===t.operationId);else if(t.method&&t.path){const e=t.method.toUpperCase();o=r.find(r=>r.method.toUpperCase()===e&&r.path===t.path)}else t.ref&&(o=r.find(e=>`${e.method.toUpperCase()} ${e.path}`===t.ref.trim()));if(!o)throw new x("operation_not_found",`No executable operation matches ${t.ref??t.operationId??`${t.method??""} ${t.path??""}`.trim()}.`);return o}(e,r),i=l(),a={config:n,spec:e.dereferenced},p=function(e){if(void 0!==e){if(!e||"object"!=typeof e||Array.isArray(e))throw new Error("values must be an object with path/query/header/body keys.");return e}}(o.values);p&&(a.values=p),o.variables&&(a.variables=o.variables);const u=function(e){if(void 0!==e){if(!Array.isArray(e))throw new Error("assertions must be an array.");return e.filter(e=>!!e&&"object"==typeof e&&"string"==typeof e.assert)}}(o.assertions);u&&(a.extraAssertions=u),o.baseUrl&&(a.serverUrl=o.baseUrl);return(await c(i,s,a)).result}function X(e,t){if(void 0!==e){if(!Array.isArray(e)||e.some(e=>"string"!=typeof e))throw new x("invalid_arguments",`${t} must be an array of strings.`);return e}}function Z(e){const t={},r=X(e.methods,"methods");r&&(t.methods=r.map(e=>e.toLowerCase()));const o=X(e.paths,"paths");o&&(t.paths=o);const n=X(e.tags,"tags");n&&(t.tags=n);const s=X(e.operationIds,"operationIds");return s&&(t.operationIds=s),t}async function ee(e,t,r,o={}){const n=H(e,t);Object.keys(r).length&&(n.filter=r);const s=p(e.dereferenced,n);if(0===s.length)throw new x("no_operations","No operations match the current specification and filter.");const i=l(),a=Math.max(1,Math.min(n.concurrency??5,s.length)),d=[...s],m=[],f=Date.now();let h=!1;const y=Array.from({length:a},async()=>{for(;d.length>0;){if(o.signal?.aborted)return void(h=!0);const t=d.shift();if(!t)return;const r={config:n,spec:e.dereferenced};n.variables&&(r.variables=n.variables);const a=await c(i,t,r);m.push(a.result),o.report?.(m.length,s.length,`${t.method.toUpperCase()} ${t.path}`)}});await Promise.all(y),m.sort((e,t)=>e.path.localeCompare(t.path)||e.method.localeCompare(t.method));const g=u(m,Date.now()-f);return h&&(g.skipped+=s.length-m.length,g.total=s.length),{summary:g,results:m,generatedAt:(new Date).toISOString(),version:"0.1.0"}}function te(e){if(!e||"object"!=typeof e||Array.isArray(e))throw new x("invalid_scenario","scenario must be an object with name and steps.");const t=e;if("string"!=typeof t.name||!t.name.trim())throw new x("invalid_scenario","scenario.name is required.");if(!Array.isArray(t.steps)||0===t.steps.length)throw new x("invalid_scenario","scenario.steps must be a non-empty array.");return t}function re(e,t){let r;try{r=te(t)}catch(e){if(e instanceof x)return{valid:!1,errors:[e.message]};throw e}try{const t=d(r,K(e));return{valid:!0,count:t.length,steps:t.map((e,t)=>({index:t,ref:e.ref,...e.step.name?{name:e.step.name}:{}}))}}catch(e){return{valid:!1,errors:[e instanceof Error?e.message:String(e)]}}}async function oe(e,t,r,o={}){const n=te(t),s=re(e,n);if(!s.valid)throw new x("invalid_scenario","Scenario failed validation.",s.errors);const i={config:H(e,r),spec:e.dereferenced,onEvent:e=>{"step:start"===e.type&&"number"==typeof e.stepIndex?o.report?.(e.stepIndex+1,n.steps.length,e.type):"scenario:finish"===e.type&&o.report?.(n.steps.length,n.steps.length,e.type)}};o.signal&&(i.signal=o.signal);return function(e){const{config:t,...r}=e;return r}(await m(n,i))}var ne=["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS","TRACE"],se={ref:{type:"string",description:'Operation reference, e.g. "POST /orders".'},operationId:{type:"string",description:"The operationId declared in the specification."},method:{type:"string",enum:ne},path:{type:"string",description:'The path, e.g. "/orders/{orderId}".'}},ie={baseUrl:{type:"string",description:"Local backend base URL. Defaults to the first spec server URL."},timeoutMs:{type:"integer",minimum:1,maximum:6e5,default:3e4},headers:{type:"object",description:"Extra headers sent with every request.",additionalProperties:{type:"string"}},variables:{type:"object",description:"Postman-style {{name}} variables.",additionalProperties:{type:"string"}},proxy:{type:"string",description:"HTTP/HTTPS proxy URL."}},ae={type:"object",description:"ScenarioDefinition: { name, description?, variables?, stopOnFailure?, steps: [{ ref | method+path, name?, skip?, request: { values?, serverUrl?, extract?: [{name, from: body|header|status, path?, key?}], assertions?: [{name, assert, ...}] } }] }",properties:{name:{type:"string"},description:{type:"string"},stopOnFailure:{type:"boolean"},variables:{type:"object",additionalProperties:{type:"string"}},steps:{type:"array",items:{type:"object"}}},required:["name","steps"]};function ce(e){const t="string"==typeof e.ref?e.ref.trim():void 0,r="string"==typeof e.operationId?e.operationId.trim():void 0,o="string"==typeof e.method?e.method.trim():void 0,n="string"==typeof e.path?e.path.trim():void 0;if(t||r||o&&n)return{...t?{ref:t}:{},...r?{operationId:r}:{},...o&&n?{method:o,path:n}:{}};throw new x("invalid_arguments","Provide either ref, operationId, or both method and path.")}var pe=[{name:"spec_overview",description:"Get the API title, version, servers, tags, protocols, security schemes, and counts. Call this first to understand the specification.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"list_operations",description:"List operations as compact rows (reference, operationId, summary, tags, status codes, auth). Filter by tag, method, protocol, auth, or free-text search. Results are paginated.",inputSchema:{type:"object",properties:{tag:{type:"string",description:"Exact tag name."},method:{type:"string",enum:ne},protocol:{type:"string",description:"http, grpc, graphql, websocket, sse, or mcp."},secured:{type:"boolean",description:"Filter by secured operations."},search:{type:"string",description:"Case-insensitive substring."},pageSize:{type:"integer",minimum:1,maximum:500,default:100},cursor:{type:"string",description:"Opaque nextCursor from a previous page."}},additionalProperties:!1}},{name:"get_operation",description:"Get the full implementation contract for one operation: parameters with validation rules, request body JSON Schemas and examples, every documented response (status, headers, body), and effective security. Use this before writing handler code so field names, types, required flags, enums, and response shapes match the spec exactly.",inputSchema:{type:"object",properties:se,additionalProperties:!1}},{name:"get_schema",description:"Get one reusable component schema (dereferenced) by component name or JSON pointer.",inputSchema:{type:"object",properties:{name:{type:"string",description:'Component schema name, e.g. "Order".'},ref:{type:"string",description:'Local pointer, e.g. "#/components/schemas/Order".'}},additionalProperties:!1}},{name:"validate_spec",description:"Validate the current specification and list errors and warnings with their locations. The document is re-read from disk, so recent edits are reflected.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"get_auth_requirements",description:"List every security scheme and, when an operation is given, the exact auth that operation requires (scheme type, header/query placement, OAuth flows and scopes). Use before implementing auth middleware or guards.",inputSchema:{type:"object",properties:se,additionalProperties:!1}},{name:"ping_target",description:"Check that the local backend is reachable before testing. Returns status and latency. Use this first when a contract test fails at the connection layer.",inputSchema:{type:"object",properties:{...ie,timeoutMs:{type:"integer",minimum:100,maximum:6e4,default:5e3}},additionalProperties:!1}},{name:"send_request",description:"Send one operation request to the running backend with optional path/query/header/body values, variables, and extra assertions. Returns the normalized response and per-assertion pass/fail. Use this to verify a single handler you just implemented.",inputSchema:{type:"object",properties:{...se,...ie,values:{type:"object",description:"Request values merged over sampled defaults: { path, query, header, body }."},assertions:{type:"array",description:"Declarative assertions added to the operation's spec assertions.",items:{type:"object"}}},additionalProperties:!1}},{name:"run_contract_tests",description:"Execute the selected operations against the backend and evaluate the spec contract (status, schema, declared assertions). Returns per-operation results and a summary. Filter by methods, tags, path regexes, or operationIds.",inputSchema:{type:"object",properties:{...ie,concurrency:{type:"integer",minimum:1,maximum:20,default:5},methods:{type:"array",items:{type:"string"}},paths:{type:"array",items:{type:"string"}},tags:{type:"array",items:{type:"string"}},operationIds:{type:"array",items:{type:"string"}}},additionalProperties:!1}},{name:"validate_scenario",description:"Statically validate an ordered multi-step scenario: every step reference must resolve, and extraction/assertion shapes are checked. Never sends a request. Call this before run_scenario.",inputSchema:{type:"object",properties:{scenario:ae},required:["scenario"],additionalProperties:!1}},{name:"run_scenario",description:"Run an ordered, stateful scenario (for example register then login then create order). Steps share variables; each step can extract response values (body JSONPath, header, status) for later steps and add assertions. Streams progress; supports cancellation.",inputSchema:{type:"object",properties:{scenario:ae,...ie},required:["scenario"],additionalProperties:!1}}],ue=new Set(pe.map(e=>e.name));async function de(e,t,r,o={}){if(!ue.has(e))throw new x("unknown_tool",`Unknown tool: ${e}`);const n=await r.load(),s=n.dereferenced,i=n.document;switch(e){case"spec_overview":return M(s);case"list_operations":{const e={};return"string"==typeof t.tag&&(e.tag=t.tag),"string"==typeof t.method&&(e.method=t.method),"string"==typeof t.protocol&&(e.protocol=t.protocol),"boolean"==typeof t.secured&&(e.secured=t.secured),"string"==typeof t.search&&(e.search=t.search),"number"==typeof t.pageSize&&(e.pageSize=t.pageSize),"string"==typeof t.cursor&&(e.cursor=t.cursor),N(s,e)}case"get_operation":return R(s,i,ce(t));case"get_schema":{const e="string"==typeof t.name?t.name.trim():void 0,r="string"==typeof t.ref?t.ref.trim():void 0;if(!e&&!r)throw new x("invalid_arguments","Provide either a schema name or a $ref pointer.");return B(0,i,{...e?{name:e}:{},...r?{ref:r}:{}})}case"validate_spec":return(await r.load(!0)).validation;case"get_auth_requirements":return void 0!==t.ref||void 0!==t.operationId||void 0!==t.method?D(s,ce(t)):D(s);case"ping_target":{const e=W(n,t).baseUrl;if(!e)throw new x("missing_base_url","Provide baseUrl or declare a concrete server URL in the specification.");return J(e,"number"==typeof t.timeoutMs?V(t.timeoutMs,"timeoutMs",5e3,100,6e4):5e3)}case"send_request":return Q(n,W(n,t),ce(t),{values:t.values??void 0,variables:t.variables??void 0,assertions:t.assertions??void 0});case"run_contract_tests":return ee(n,W(n,t),Z(t),o);case"validate_scenario":return re(n,t.scenario);case"run_scenario":{const e=W(n,t);return oe(n,t.scenario,e,o)}default:throw new x("unknown_tool",`Unknown tool: ${e}`)}}var me="powerduck://spec/";async function le(e){const t=await e.load();return[{uri:`${me}source`,name:"OpenAPI source",description:"The raw OpenAPI document exactly as authored.",mimeType:t.mediaType},{uri:`${me}overview`,name:"API overview",description:"Servers, tags, protocols, security schemes, and counts.",mimeType:"application/json"},{uri:`${me}operations`,name:"Operations index",description:"Compact index of all operations (up to 500).",mimeType:"application/json"}]}var fe=[{uriTemplate:`${me}operation/{ref}`,name:"Operation contract",description:'Full contract for one operation. Use the reference form "METHOD /path", URL-encoded, e.g. operation/POST%20/orders.',mimeType:"application/json"},{uriTemplate:`${me}schema/{name}`,name:"Component schema",description:"A dereferenced component schema by name.",mimeType:"application/json"}];async function he(e,t){if(!e.startsWith(me))throw new x("resource_not_found",`Unknown resource: ${e}`);const r=await t.load(),o=r.dereferenced,n=r.document,s=decodeURIComponent(e.slice(17));if("source"===s)return{uri:e,mimeType:r.mediaType,text:r.sourceText};if("overview"===s)return{uri:e,mimeType:"application/json",text:JSON.stringify(M(o),null,2)};if("operations"===s)return{uri:e,mimeType:"application/json",text:JSON.stringify(N(o,{pageSize:500}),null,2)};if(s.startsWith("operation/")){const t=R(o,n,{ref:s.slice(10)});return{uri:e,mimeType:"application/json",text:JSON.stringify(t,null,2)}}if(s.startsWith("schema/")){const t=B(0,n,{name:s.slice(7)});return{uri:e,mimeType:"application/json",text:JSON.stringify(t,null,2)}}throw new x("resource_not_found",`Unknown resource: ${e}`)}var ye=["PowerDuck developer MCP gives you accurate, dereferenced OpenAPI facts for the backend you are implementing and verifies it against a running server.","Call spec_overview and list_operations to orient yourself, then get_operation before writing each handler so parameters, request bodies, responses, and auth match the specification.","Use ping_target, send_request, run_contract_tests, validate_scenario, and run_scenario to verify the implementation as you build it.","Call validate_spec after the specification changes. The document is re-read from disk on every request."].join(" ");function ge(e){return{isError:!0,content:[{type:"text",text:JSON.stringify({error:e.code,message:e.message,...void 0!==e.details?{details:e.details}:{}},null,2)}]}}function ve(e,a={}){const c=new t({name:a.name??"powerduck-dev-mcp",version:a.version??"0.1.0"},{capabilities:{tools:{},resources:{}},instructions:a.instructions??ye});return c.setRequestHandler(r,async()=>({tools:pe})),c.setRequestHandler(o,async(t,r)=>{const{name:o,arguments:n,_meta:s}=t.params,i=s?.progressToken,a={signal:r.signal};void 0!==i&&(a.report=(e,t,o)=>{r.sendNotification({method:"notifications/progress",params:{progressToken:i,progress:e,...t?{total:t}:{},...o?{message:o}:{}}})});try{const t=await de(o,n??{},e,a);return c=t,{content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(e){if(e instanceof x)return ge(e);const t=e instanceof Error?e.message:String(e);return ge(new x("internal_error",t))}var c}),c.setRequestHandler(n,async()=>({resources:await le(e)})),c.setRequestHandler(s,async()=>({resourceTemplates:fe})),c.setRequestHandler(i,async t=>{const{uri:r}=t.params;try{const t=await he(r,e);return{contents:[{uri:t.uri,mimeType:t.mimeType,text:t.text}]}}catch(e){if(e instanceof x)throw e;throw new x("resource_error",e instanceof Error?e.message:String(e))}}),c}function be(e){return!!e&&"object"==typeof e&&!Array.isArray(e)}function we(e){if("string"==typeof e&&e.trim())return{message:e,severity:"error"};if(be(e)&&"string"==typeof e.message){const t="warning"===e.severity||"info"===e.severity?e.severity:"error",r=Array.isArray(e.path)?e.path.join("/"):"string"==typeof e.path?e.path:void 0;return r?{message:e.message,severity:t,path:r}:{message:e.message,severity:t}}return null}var xe=class{constructor(e){this.specPath=e}specPath;cache=null;async load(e=!1){const t=h.statSync(this.specPath);if(!e&&this.cache&&this.cache.mtimeMs===t.mtimeMs)return this.cache.snapshot;const r=h.readFileSync(this.specPath,"utf8"),o=/\.ya?ml$/i.test(this.specPath),n=function(e,t){return t?y(e):JSON.parse(e)}(r,o);if(!be(n))throw new Error("The OpenAPI document must be a JSON or YAML object.");let s=n;const i="string"==typeof n.openapi?n.openapi:"";if(i&&!/^3\./.test(i))try{s=await g(n)}catch{s=n}const a=await async function(e){try{const t=await v(e),r=[];for(const e of t.errors??[]){const t=we(e);t&&r.push(t)}return{valid:Boolean(t.valid),..."string"==typeof t.version?{openapiVersion:t.version}:{},issues:r}}catch(e){return{valid:!1,issues:[{message:e instanceof Error?e.message:String(e),severity:"error"}]}}}(s),c=await async function(e){try{const t=await b(e);return!t.errors?.length&&be(t.schema)?t.schema:e}catch{return e}}(s),p={path:this.specPath,document:s,dereferenced:c,sourceText:r,mediaType:o?"application/yaml":"application/json",validation:a,mtimeMs:t.mtimeMs};return this.cache={mtimeMs:t.mtimeMs,snapshot:p},p}};async function Se(e){const t=function(){const e={log:console.log,info:console.info,debug:console.debug,dir:console.dir},t=(...e)=>console.error(...e);return console.log=t,console.info=t,console.debug=t,console.dir=t,()=>{console.log=e.log,console.info=e.info,console.debug=e.debug,console.dir=e.dir}}(),r=ve(new xe(e.specPath),{...e.name?{name:e.name}:{},...e.version?{version:e.version}:{}}),o=new f;let n=()=>{};const s=new Promise(e=>{n=e});let i=!1;const a=[],c=()=>{if(!i){i=!0;for(const e of a)e();t(),n()}};if(o.onerror=e=>{console.error("[dev-mcp:stdio] transport error:",e)},o.onclose=()=>c(),await r.connect(o),!1!==e.handleSignals)for(const e of["SIGINT","SIGTERM"]){const t=()=>{r.close().catch(()=>{})};process.once(e,t),a.push(()=>process.removeListener(e,t))}return{closed:s,close:async()=>{try{await r.close()}finally{c()}}}}function je(e){const t=h.readFileSync(e,"utf8");let r;try{r=JSON.parse(t)}catch(t){throw new Error(`Invalid JSON in ${e}: ${t instanceof Error?t.message:String(t)}`)}if(!r||"object"!=typeof r||Array.isArray(r))throw new Error(`${e} must contain a JSON object.`);const o={},n=r;return"string"==typeof n.spec&&(o.spec=n.spec),"string"==typeof n.baseUrl&&(o.baseUrl=n.baseUrl),"string"==typeof n.project&&(o.project=n.project),o}function _e(e){const t=e?w.resolve(e):w.resolve(process.cwd(),"powerduck.dev.json");return h.existsSync(t)?{config:je(t),configDir:w.dirname(t)}:{config:{},configDir:process.cwd()}}function Oe(e,t){return t?w.resolve(e,t):void 0}export{x as FactError,S as HTTP_METHODS,xe as SpecStore,D as authRequirements,H as buildRunnerConfig,Z as contractFilterFromArgs,ve as createDevMcpServer,pe as devTools,de as executeDevTool,R as getOperation,B as getSchema,N as listOperations,le as listSpecResources,_e as loadDevConfig,M as overview,J as pingTarget,he as readSpecResource,z as resolveBaseUrl,Oe as resolveFrom,I as resolvePointer,ee as runContractSuite,W as runOverridesFromArgs,oe as runScenarioTool,Q as sendOne,fe as specResourceTemplates,Se as startStdioServer,re as validateScenario};
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@powerduck/dev-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "Developer-facing MCP server that gives AI coding agents accurate OpenAPI facts and runs contract/scenario tests against a local backend. Build APIs correctly, verify them immediately.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.cjs"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "bin": {
19
+ "powerduck-dev-mcp": "./dist/cli.mjs"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "engines": {
27
+ "node": ">=20.11"
28
+ },
29
+ "scripts": {
30
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
31
+ "typecheck": "tsc -p tsconfig.json",
32
+ "bundle": "tsup",
33
+ "build": "npm run clean && npm run typecheck && npm run bundle",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
36
+ "prepublishOnly": "npm run build && npm run test"
37
+ },
38
+ "keywords": [
39
+ "openapi",
40
+ "mcp",
41
+ "model-context-protocol",
42
+ "ai",
43
+ "coding-agent",
44
+ "claude",
45
+ "codex",
46
+ "cursor",
47
+ "backend",
48
+ "contract-testing",
49
+ "scenario-testing",
50
+ "developer-tools"
51
+ ],
52
+ "author": "Powerduck limited",
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/powerducklab/dev-mcp-server.git"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/powerducklab/dev-mcp-server/issues"
60
+ },
61
+ "homepage": "https://www.powerduck.com/",
62
+ "dependencies": {
63
+ "@modelcontextprotocol/sdk": "^1.30.0",
64
+ "@powerduck/openapi-cli": "^0.2.7",
65
+ "@powerduck/openapi-parser": "^0.3.3",
66
+ "@powerduck/openapi-request": "^0.2.11",
67
+ "commander": "^15.0.0",
68
+ "js-yaml": "^5.4.1"
69
+ },
70
+ "devDependencies": {
71
+ "@types/js-yaml": "^4.0.9",
72
+ "@types/node": "^22.20.1",
73
+ "tsup": "^8.5.1",
74
+ "tsx": "^4.19.1",
75
+ "typescript": "^5.6.3",
76
+ "vitest": "^2.1.0"
77
+ }
78
+ }