@mcpmake/core 0.3.6 → 0.4.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 (62) hide show
  1. package/dist/analyzer/dom-parser.d.ts.map +1 -1
  2. package/dist/analyzer/dom-parser.js +34 -19
  3. package/dist/analyzer/dom-parser.js.map +1 -1
  4. package/dist/analyzer/semantic-analyzer.d.ts.map +1 -1
  5. package/dist/analyzer/semantic-analyzer.js +10 -3
  6. package/dist/analyzer/semantic-analyzer.js.map +1 -1
  7. package/dist/emitter/a2a.d.ts +101 -0
  8. package/dist/emitter/a2a.d.ts.map +1 -0
  9. package/dist/emitter/a2a.js +366 -0
  10. package/dist/emitter/a2a.js.map +1 -0
  11. package/dist/emitter/code-writer.d.ts.map +1 -1
  12. package/dist/emitter/code-writer.js +18 -1
  13. package/dist/emitter/code-writer.js.map +1 -1
  14. package/dist/emitter/composite-tools.d.ts +99 -0
  15. package/dist/emitter/composite-tools.d.ts.map +1 -0
  16. package/dist/emitter/composite-tools.js +391 -0
  17. package/dist/emitter/composite-tools.js.map +1 -0
  18. package/dist/emitter/mcp-ui.d.ts +42 -0
  19. package/dist/emitter/mcp-ui.d.ts.map +1 -0
  20. package/dist/emitter/mcp-ui.js +146 -0
  21. package/dist/emitter/mcp-ui.js.map +1 -0
  22. package/dist/emitter/mcpb-bundler.d.ts +56 -7
  23. package/dist/emitter/mcpb-bundler.d.ts.map +1 -1
  24. package/dist/emitter/mcpb-bundler.js +60 -11
  25. package/dist/emitter/mcpb-bundler.js.map +1 -1
  26. package/dist/emitter/normalize-tree.d.ts +17 -0
  27. package/dist/emitter/normalize-tree.d.ts.map +1 -0
  28. package/dist/emitter/normalize-tree.js +87 -0
  29. package/dist/emitter/normalize-tree.js.map +1 -0
  30. package/dist/emitter/project-scaffolder.d.ts.map +1 -1
  31. package/dist/emitter/project-scaffolder.js +47 -1
  32. package/dist/emitter/project-scaffolder.js.map +1 -1
  33. package/dist/emitter/python-template-loader.d.ts.map +1 -1
  34. package/dist/emitter/python-template-loader.js +6 -1
  35. package/dist/emitter/python-template-loader.js.map +1 -1
  36. package/dist/emitter/templates/server-main-http.ts.hbs +48 -0
  37. package/dist/emitter/templates/server-main.ts.hbs +38 -0
  38. package/dist/emitter/templates/tsconfig.json.hbs +3 -2
  39. package/dist/index.d.ts +8 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +4 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/parser/overlay-loader.js +5 -1
  44. package/dist/parser/overlay-loader.js.map +1 -1
  45. package/dist/parser/postman-loader.js +7 -0
  46. package/dist/parser/postman-loader.js.map +1 -1
  47. package/dist/rescan/rescan-scheduler.js +14 -5
  48. package/dist/rescan/rescan-scheduler.js.map +1 -1
  49. package/dist/site-transformer/selector-healer.d.ts.map +1 -1
  50. package/dist/site-transformer/selector-healer.js +8 -0
  51. package/dist/site-transformer/selector-healer.js.map +1 -1
  52. package/dist/site-transformer/tool-generator.js +19 -8
  53. package/dist/site-transformer/tool-generator.js.map +1 -1
  54. package/dist/transformer/har-to-operations.d.ts.map +1 -1
  55. package/dist/transformer/har-to-operations.js +17 -6
  56. package/dist/transformer/har-to-operations.js.map +1 -1
  57. package/dist/transformer/resource-builder.d.ts.map +1 -1
  58. package/dist/transformer/resource-builder.js +15 -2
  59. package/dist/transformer/resource-builder.js.map +1 -1
  60. package/dist/types/index.d.ts +12 -0
  61. package/dist/types/index.d.ts.map +1 -1
  62. package/package.json +1 -1
@@ -0,0 +1,366 @@
1
+ /**
2
+ * A2A (Agent2Agent) output (`--a2a`): wrap a generated MCP server in the published,
3
+ * stable A2A protocol surface so A2A-speaking clients can discover and drive it.
4
+ *
5
+ * Two artifacts are derived deterministically from the project manifest:
6
+ *
7
+ * 1. An A2A **AgentCard** (served at `/.well-known/agent.json`) — every value comes
8
+ * from the manifest (server name/version, base URL, generated tool list, auth
9
+ * schemes). One A2A *skill* is emitted per generated MCP tool, so an A2A client
10
+ * sees the same capability surface MCP clients see via `tools/list`.
11
+ *
12
+ * 2. A JSON-RPC 2.0 **server-wrapper module** (`src/a2a.ts`) that implements the A2A
13
+ * transport methods `message/send` and `tasks/get` and serves the AgentCard. It
14
+ * maps an inbound A2A message to an MCP `tools/call` on the SAME generated server
15
+ * (via an in-process loopback MCP client — public SDK API only, no internals).
16
+ *
17
+ * PURE: a deterministic function of the inputs, with every interpolated value safely
18
+ * encoded (tool descriptions/names come from the source API and are JSON.stringify'd
19
+ * into the generated source, so no character can break out of a string literal or the
20
+ * emitted JSON). No Date.now()/random — identical input yields byte-identical output.
21
+ *
22
+ * Conformance: AgentCard fields (name, description, version, url, protocolVersion,
23
+ * capabilities, defaultInputModes, defaultOutputModes, skills, provider) and the
24
+ * AgentSkill shape (id, name, description, tags) are the published A2A AgentCard
25
+ * surface; the JSON-RPC methods `message/send`/`tasks/get` are the stable A2A v0.3.0
26
+ * transport methods.
27
+ */
28
+ /** Published, stable A2A protocol version this wrapper targets. */
29
+ export const A2A_PROTOCOL_VERSION = '0.3.0';
30
+ /** The path the AgentCard is served from (the A2A well-known discovery location). */
31
+ export const A2A_AGENT_CARD_PATH = '/.well-known/agent.json';
32
+ /**
33
+ * Build the A2A AgentCard as a plain object. Every field is derived from the manifest:
34
+ * • `name`/`version` ← serverName/serverVersion
35
+ * • `url` ← the agent's A2A JSON-RPC endpoint (`<baseUrl>/a2a`)
36
+ * • `protocolVersion` ← the fixed {@link A2A_PROTOCOL_VERSION} constant
37
+ * • `skills[]` ← one per generated tool (id = tool.name)
38
+ * • `provider` ← the generator's identity (constant, not API-derived)
39
+ * No value is invented from outside the manifest.
40
+ */
41
+ export function buildAgentCard(inputs) {
42
+ const toolCount = inputs.tools.length;
43
+ return {
44
+ name: inputs.serverName,
45
+ description: `A2A agent wrapping the ${inputs.serverName} MCP server ` +
46
+ `(${toolCount} tool${toolCount === 1 ? '' : 's'}).`,
47
+ version: inputs.serverVersion,
48
+ // The A2A JSON-RPC endpoint this card describes. Derived from the API base URL;
49
+ // operators override it via the A2A_PUBLIC_URL env var at runtime (see module).
50
+ url: a2aEndpointUrl(inputs.baseUrl),
51
+ protocolVersion: A2A_PROTOCOL_VERSION,
52
+ capabilities: { streaming: true },
53
+ defaultInputModes: ['application/json', 'text/plain'],
54
+ defaultOutputModes: ['application/json', 'text/plain'],
55
+ skills: inputs.tools.map((t) => ({
56
+ id: t.name,
57
+ name: t.title || t.name,
58
+ description: t.description ?? '',
59
+ tags: [],
60
+ })),
61
+ provider: { organization: 'mcpmake', url: 'https://github.com/starkyru/mcpmake-cli' },
62
+ };
63
+ }
64
+ /** Derive the A2A JSON-RPC endpoint URL from the API base URL (`<base>/a2a`). */
65
+ function a2aEndpointUrl(baseUrl) {
66
+ const trimmed = baseUrl.replace(/\/+$/, '');
67
+ return `${trimmed}/a2a`;
68
+ }
69
+ /** The AgentCard as pretty (2-space) JSON — stable key order, deterministic. */
70
+ export function agentCardJson(inputs) {
71
+ return JSON.stringify(buildAgentCard(inputs), null, 2);
72
+ }
73
+ /**
74
+ * Generate the `src/a2a.ts` module the server ships when A2A output is enabled.
75
+ *
76
+ * The AgentCard is embedded as a JSON-encoded string constant (so any character in a
77
+ * tool description is inert in the generated source). `registerA2a(server, registerTools)`
78
+ * stands up an A2A JSON-RPC listener that:
79
+ * • GET /.well-known/agent.json → returns the AgentCard.
80
+ * • POST /a2a (JSON-RPC 2.0):
81
+ * - `message/send` → extracts the tool name + arguments from the A2A Message and
82
+ * dispatches an MCP `tools/call` to the wrapped server, returning
83
+ * the result as an A2A Message (text part) / completed Task.
84
+ * - `tasks/get` → returns the stored Task for a prior `message/send` id.
85
+ *
86
+ * Tool dispatch uses an in-process loopback MCP client (`InMemoryTransport`) connected to
87
+ * a dedicated McpServer that `registerTools` populates — public SDK API only, so it never
88
+ * reaches into the primary transport's server or any SDK internals.
89
+ */
90
+ export function buildA2aModule(inputs) {
91
+ const card = buildAgentCard(inputs);
92
+ // The full card, JSON-encoded into a single inert string constant.
93
+ const cardLiteral = JSON.stringify(JSON.stringify(card, null, 2));
94
+ // The set of valid skill ids (= tool names) the wrapper will accept, JSON-encoded so
95
+ // each name is inert; used to reject unknown tool requests before dispatch.
96
+ const skillIdsLiteral = JSON.stringify(card.skills.map((s) => s.id));
97
+ return `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
98
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
99
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
100
+ import http from 'node:http';
101
+ import crypto from 'node:crypto';
102
+
103
+ /* The AgentCard, baked at generation time and served at the A2A well-known path.
104
+ * Embedded as a JSON-encoded string so no source-API text can break out of source. */
105
+ const AGENT_CARD_JSON = ${cardLiteral};
106
+ const AGENT_CARD = JSON.parse(AGENT_CARD_JSON) as {
107
+ url: string;
108
+ skills: { id: string }[];
109
+ [k: string]: unknown;
110
+ };
111
+ const A2A_AGENT_CARD_PATH = ${JSON.stringify(A2A_AGENT_CARD_PATH)};
112
+ const A2A_RPC_PATH = '/a2a';
113
+ const A2A_PROTOCOL_VERSION = ${JSON.stringify(A2A_PROTOCOL_VERSION)};
114
+ /* Valid skill ids = generated tool names. An A2A request naming anything else is
115
+ * rejected before dispatch (never forwarded to the MCP server). */
116
+ const SKILL_IDS = new Set<string>(${skillIdsLiteral});
117
+
118
+ type ToolResult = Awaited<ReturnType<Client['callTool']>>;
119
+
120
+ /* A2A Task store: message/send may answer with a Task that the client later polls via
121
+ * tasks/get. Kept in-memory and bounded — A2A is a stateless wrapper over MCP tools. */
122
+ interface A2aTask {
123
+ id: string;
124
+ contextId: string;
125
+ status: { state: 'completed' | 'failed'; timestamp: string };
126
+ artifacts: { artifactId: string; parts: { kind: 'text'; text: string }[] }[];
127
+ }
128
+ const A2A_MAX_TASKS = 1000;
129
+ const tasks = new Map<string, A2aTask>();
130
+
131
+ function rememberTask(task: A2aTask): void {
132
+ // Bound the store: drop the oldest entry once at capacity (insertion order).
133
+ if (tasks.size >= A2A_MAX_TASKS) {
134
+ const oldest = tasks.keys().next().value;
135
+ if (oldest !== undefined) tasks.delete(oldest);
136
+ }
137
+ tasks.set(task.id, task);
138
+ }
139
+
140
+ /* Extract a tool name + JSON arguments from an inbound A2A Message. The skill (tool)
141
+ * name comes from message.metadata.skillId; arguments come from a single DataPart
142
+ * (kind:'data') or a JSON-encoded TextPart. Returns null when the message is malformed
143
+ * or names an unknown skill — the caller maps that to a JSON-RPC error. */
144
+ function extractToolCall(
145
+ message: unknown,
146
+ ): { name: string; args: Record<string, unknown> } | null {
147
+ if (!message || typeof message !== 'object') return null;
148
+ const meta = (message as { metadata?: unknown }).metadata;
149
+ const skillId =
150
+ meta && typeof meta === 'object'
151
+ ? (meta as { skillId?: unknown }).skillId
152
+ : undefined;
153
+ if (typeof skillId !== 'string' || !SKILL_IDS.has(skillId)) return null;
154
+
155
+ const parts = (message as { parts?: unknown }).parts;
156
+ let args: Record<string, unknown> = {};
157
+ if (Array.isArray(parts)) {
158
+ for (const part of parts) {
159
+ if (!part || typeof part !== 'object') continue;
160
+ const kind = (part as { kind?: unknown }).kind;
161
+ if (kind === 'data') {
162
+ const data = (part as { data?: unknown }).data;
163
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
164
+ args = data as Record<string, unknown>;
165
+ }
166
+ } else if (kind === 'text') {
167
+ const text = (part as { text?: unknown }).text;
168
+ if (typeof text === 'string' && text.trim()) {
169
+ try {
170
+ const parsed = JSON.parse(text);
171
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
172
+ args = parsed as Record<string, unknown>;
173
+ }
174
+ } catch {
175
+ // A non-JSON text part is treated as having no structured args.
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ return { name: skillId, args };
182
+ }
183
+
184
+ /* Flatten an MCP CallToolResult's content blocks to a single text string for the A2A
185
+ * Message/artifact. Text blocks are concatenated; non-text blocks are JSON-encoded. */
186
+ function resultToText(result: ToolResult): string {
187
+ const content = (result as { content?: unknown }).content;
188
+ if (!Array.isArray(content)) return '';
189
+ return content
190
+ .map((block) => {
191
+ if (block && typeof block === 'object' && (block as { type?: unknown }).type === 'text') {
192
+ return String((block as { text?: unknown }).text ?? '');
193
+ }
194
+ return JSON.stringify(block);
195
+ })
196
+ .join('\\n');
197
+ }
198
+
199
+ function sendJson(res: http.ServerResponse, status: number, body: unknown): void {
200
+ res.writeHead(status, { 'Content-Type': 'application/json' });
201
+ res.end(JSON.stringify(body));
202
+ }
203
+
204
+ function rpcResult(id: unknown, result: unknown): Record<string, unknown> {
205
+ return { jsonrpc: '2.0', id: id ?? null, result };
206
+ }
207
+
208
+ function rpcError(id: unknown, code: number, message: string): Record<string, unknown> {
209
+ return { jsonrpc: '2.0', id: id ?? null, error: { code, message } };
210
+ }
211
+
212
+ const MAX_BODY = 4 * 1024 * 1024; // 4 MB — same cap the MCP HTTP server uses.
213
+ function readBody(req: http.IncomingMessage): Promise<Buffer | null> {
214
+ return new Promise((resolve) => {
215
+ const chunks: Buffer[] = [];
216
+ let total = 0;
217
+ let settled = false;
218
+ const done = (v: Buffer | null) => {
219
+ if (!settled) {
220
+ settled = true;
221
+ resolve(v);
222
+ }
223
+ };
224
+ req.on('data', (c: Buffer) => {
225
+ total += c.length;
226
+ if (total > MAX_BODY) {
227
+ done(null);
228
+ req.destroy();
229
+ return;
230
+ }
231
+ chunks.push(c);
232
+ });
233
+ req.on('end', () => done(Buffer.concat(chunks)));
234
+ req.on('error', () => done(null));
235
+ req.on('aborted', () => done(null));
236
+ });
237
+ }
238
+
239
+ /**
240
+ * Stand up the A2A wrapper. \`registerTools\` populates a DEDICATED McpServer (the same
241
+ * registrar the primary server uses), which an in-process MCP client drives — so A2A
242
+ * dispatch never contends with the primary transport. Returns the listening http.Server
243
+ * (when A2A_HTTP=true) or undefined; callers can ignore the return value.
244
+ */
245
+ export async function registerA2a(
246
+ _server: McpServer,
247
+ registerTools: (s: McpServer) => void,
248
+ ): Promise<http.Server | undefined> {
249
+ // Dedicated MCP server + loopback client for A2A → tools/call dispatch.
250
+ const a2aMcpServer = new McpServer({ name: 'a2a-bridge', version: A2A_PROTOCOL_VERSION });
251
+ registerTools(a2aMcpServer);
252
+ const client = new Client({ name: 'a2a-bridge-client', version: A2A_PROTOCOL_VERSION });
253
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
254
+ await a2aMcpServer.connect(serverTransport);
255
+ await client.connect(clientTransport);
256
+
257
+ async function handleMessageSend(id: unknown, params: unknown): Promise<Record<string, unknown>> {
258
+ const message = (params as { message?: unknown })?.message;
259
+ const call = extractToolCall(message);
260
+ if (!call) {
261
+ return rpcError(id, -32602, 'Invalid A2A message: missing or unknown skillId (tool name)');
262
+ }
263
+ const result = await client.callTool({ name: call.name, arguments: call.args });
264
+ const text = resultToText(result);
265
+ const isError = (result as { isError?: unknown }).isError === true;
266
+ const taskId = crypto.randomUUID();
267
+ const contextId = crypto.randomUUID();
268
+ const task: A2aTask = {
269
+ id: taskId,
270
+ contextId,
271
+ status: {
272
+ state: isError ? 'failed' : 'completed',
273
+ // A2A Task timestamps are wall-clock; derived from the request, not baked.
274
+ timestamp: new Date().toISOString(),
275
+ },
276
+ artifacts: [
277
+ { artifactId: crypto.randomUUID(), parts: [{ kind: 'text', text }] },
278
+ ],
279
+ };
280
+ rememberTask(task);
281
+ return rpcResult(id, task);
282
+ }
283
+
284
+ function handleTasksGet(id: unknown, params: unknown): Record<string, unknown> {
285
+ const taskId = (params as { id?: unknown })?.id;
286
+ if (typeof taskId !== 'string') {
287
+ return rpcError(id, -32602, 'Invalid tasks/get params: "id" is required');
288
+ }
289
+ const task = tasks.get(taskId);
290
+ if (!task) return rpcError(id, -32001, 'Task not found');
291
+ return rpcResult(id, task);
292
+ }
293
+
294
+ async function handleRpc(body: unknown): Promise<Record<string, unknown>> {
295
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
296
+ return rpcError(null, -32600, 'Invalid Request');
297
+ }
298
+ const id = (body as { id?: unknown }).id;
299
+ const method = (body as { method?: unknown }).method;
300
+ const params = (body as { params?: unknown }).params;
301
+ switch (method) {
302
+ case 'message/send':
303
+ return handleMessageSend(id, params);
304
+ case 'tasks/get':
305
+ return handleTasksGet(id, params);
306
+ default:
307
+ return rpcError(id, -32601, 'Method not found');
308
+ }
309
+ }
310
+
311
+ // The A2A HTTP listener is opt-in (A2A_HTTP=true) so enabling --a2a never changes a
312
+ // stdio server's runtime footprint unless the operator wants the A2A endpoint.
313
+ if (process.env.A2A_HTTP !== 'true') return undefined;
314
+
315
+ const port = parseInt(process.env.A2A_PORT ?? '3001', 10);
316
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
317
+ throw new Error('Invalid A2A_PORT value, must be 1-65535');
318
+ }
319
+
320
+ const httpServer = http.createServer(async (req, res) => {
321
+ try {
322
+ const url = new URL(req.url ?? '/', \`http://localhost:\${port}\`);
323
+ const method = req.method ?? 'GET';
324
+
325
+ if (url.pathname === A2A_AGENT_CARD_PATH && method === 'GET') {
326
+ // The agent's own endpoint may be pinned at runtime without rebuilding.
327
+ const publicUrl = process.env.A2A_PUBLIC_URL;
328
+ const card = publicUrl ? { ...AGENT_CARD, url: publicUrl } : AGENT_CARD;
329
+ sendJson(res, 200, card);
330
+ return;
331
+ }
332
+
333
+ if (url.pathname === A2A_RPC_PATH && method === 'POST') {
334
+ const raw = await readBody(req);
335
+ if (raw === null) {
336
+ sendJson(res, 200, rpcError(null, -32600, 'Request body too large'));
337
+ return;
338
+ }
339
+ let parsed: unknown;
340
+ try {
341
+ parsed = JSON.parse(raw.toString('utf-8'));
342
+ } catch {
343
+ sendJson(res, 200, rpcError(null, -32700, 'Parse error'));
344
+ return;
345
+ }
346
+ sendJson(res, 200, await handleRpc(parsed));
347
+ return;
348
+ }
349
+
350
+ res.writeHead(404);
351
+ res.end('Not found');
352
+ } catch {
353
+ if (!res.headersSent) sendJson(res, 200, rpcError(null, -32603, 'Internal error'));
354
+ }
355
+ });
356
+
357
+ httpServer.listen(port, () => {
358
+ process.stderr.write(
359
+ JSON.stringify({ msg: 'A2A wrapper listening', port, card: A2A_AGENT_CARD_PATH }) + '\\n',
360
+ );
361
+ });
362
+ return httpServer;
363
+ }
364
+ `;
365
+ }
366
+ //# sourceMappingURL=a2a.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a2a.js","sourceRoot":"","sources":["../../src/emitter/a2a.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"code-writer.d.ts","sourceRoot":"","sources":["../../src/emitter/code-writer.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAKD,wBAAsB,cAAc,CAClC,KAAK,EAAE,QAAQ,EAAE,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5D,OAAO,CAAC,IAAI,CAAC,CAmEf"}
1
+ {"version":3,"file":"code-writer.d.ts","sourceRoot":"","sources":["../../src/emitter/code-writer.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAKD,wBAAsB,cAAc,CAClC,KAAK,EAAE,QAAQ,EAAE,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5D,OAAO,CAAC,IAAI,CAAC,CAuFf"}
@@ -8,13 +8,30 @@ export async function writeCodeUnits(units, outputDir, options) {
8
8
  const resolvedOutputDir = resolve(outputDir);
9
9
  // Resolve + traversal-guard every unit up front so a malicious relative path
10
10
  // can never escape the output dir, and so we fail before touching the disk.
11
- const planned = units.map((unit) => {
11
+ const resolved = units.map((unit) => {
12
12
  const absPath = resolve(outputDir, unit.filePath);
13
13
  if (!absPath.startsWith(resolvedOutputDir + '/') && absPath !== resolvedOutputDir) {
14
14
  throw new Error(`Path traversal detected: ${unit.filePath} resolves outside output directory`);
15
15
  }
16
16
  return { absPath, unit };
17
17
  });
18
+ // Defense-in-depth: collapse units that resolve to the SAME absolute path so
19
+ // the force/atomic path never stages one temp once but renames it twice
20
+ // (→ ENOENT on the second rename). Two site tools sharing a generated
21
+ // filename is the known trigger; the tool-generator now keeps them distinct,
22
+ // but a future collision must not crash or silently drop a file. Last writer
23
+ // wins, first-occurrence order is preserved, and every drop is warned once —
24
+ // a no-op when there are no collisions.
25
+ const byPath = new Map();
26
+ for (const entry of resolved) {
27
+ const existing = byPath.get(entry.absPath);
28
+ if (existing) {
29
+ logger.warn(`Duplicate output path: ${entry.unit.filePath} overwrites ${existing.unit.filePath} ` +
30
+ `(both resolve to the same file) — keeping the last.`);
31
+ }
32
+ byPath.set(entry.absPath, entry);
33
+ }
34
+ const planned = [...byPath.values()];
18
35
  if (options.dryRun) {
19
36
  for (const { unit } of planned) {
20
37
  logger.info(`[dry-run] Would write: ${unit.filePath}`);
@@ -1 +1 @@
1
- {"version":3,"file":"code-writer.js","sourceRoot":"","sources":["../../src/emitter/code-writer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"code-writer.js","sourceRoot":"","sources":["../../src/emitter/code-writer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Composite tools (`compositeTools:` in `.mcpmake.yaml`): one MCP tool that
3
+ * orchestrates several EXISTING generated tools in sequence, threading data
4
+ * between them. Opt-in — absent config emits nothing.
5
+ *
6
+ * A composite is declared as an ordered list of steps. Each step names an
7
+ * existing generated tool (by its MCP tool name) and an argument map (`with`)
8
+ * whose values are either:
9
+ * • a literal (string / number / boolean),
10
+ * • `{ $input: <name> }` — a value the composite tool takes as its OWN input,
11
+ * • `{ $step: [i] }` — the whole JSON result of an earlier step i, or
12
+ * • `{ $step: [i, field] }` — a top-level `field` of an earlier step i's result.
13
+ * The composite's `returns` (default: the last step) selects which step result
14
+ * is returned. The composite's inputSchema is the set of distinct `$input` names
15
+ * referenced across every step — each a `z.string()` for v1 (documented limit).
16
+ *
17
+ * The generated handler runs the steps IN ORDER. It invokes each step's
18
+ * underlying request by calling that step's existing generated tool through an
19
+ * in-process MCP loopback client (InMemoryTransport + Client.callTool) — the
20
+ * SAME public-SDK pattern `a2a.ts` uses. This reuses each tool's real request
21
+ * logic verbatim (method, path template, param mapping, auth, jq filtering); no
22
+ * HTTP is reinvented here. Each step's parsed JSON result is stored so later
23
+ * steps and `returns` can reference it.
24
+ *
25
+ * PURE: a deterministic function of the inputs. Every interpolated value (tool
26
+ * names, input names, field names, literals) is JSON.stringify'd into the
27
+ * generated source, so no character from the (untrusted) config can break out of
28
+ * a string literal. No Date.now()/random — identical input → byte-identical output.
29
+ */
30
+ import type { ToolDefinition } from '../types/index.js';
31
+ /** A literal value usable directly as a step argument. */
32
+ export type CompositeLiteral = string | number | boolean;
33
+ /** A reference to one of the composite tool's own inputs. */
34
+ export interface CompositeInputRef {
35
+ $input: string;
36
+ }
37
+ /**
38
+ * A reference to a prior step's result. `[i]` is the whole result of step i;
39
+ * `[i, field]` is the top-level `field` of step i's result.
40
+ */
41
+ export interface CompositeStepRef {
42
+ $step: [number] | [number, string];
43
+ }
44
+ /** A resolved value for a step argument or the composite's `returns`. */
45
+ export type CompositeValue = CompositeLiteral | CompositeInputRef | CompositeStepRef;
46
+ /** One step of a composite tool: invoke `tool` with the resolved `with` map. */
47
+ export interface CompositeStepSpec {
48
+ /** The MCP tool name of an EXISTING generated tool to invoke. */
49
+ tool: string;
50
+ /** Argument-name → value map for this step's tool call. */
51
+ with?: Record<string, CompositeValue>;
52
+ }
53
+ /** A composite-tool declaration (one entry under `compositeTools:`). */
54
+ export interface CompositeToolSpec {
55
+ name: string;
56
+ description?: string;
57
+ steps: CompositeStepSpec[];
58
+ /** Which step's result the composite returns. Default: the last step. */
59
+ returns?: CompositeStepRef;
60
+ }
61
+ /** Thrown when a composite-tool declaration is invalid (surfaced at build time). */
62
+ export declare class CompositeToolError extends Error {
63
+ constructor(message: string);
64
+ }
65
+ /**
66
+ * Parse + validate the raw `compositeTools` value from `.mcpmake.yaml` into typed
67
+ * specs. Throws a {@link CompositeToolError} (clear, build-time) on any malformed
68
+ * entry. Loosely-typed input (the YAML loader returns `unknown`) is checked here.
69
+ */
70
+ export declare function parseCompositeToolSpecs(raw: unknown): CompositeToolSpec[];
71
+ export interface CompositeBuildResult {
72
+ /** The distinct `$input` names referenced across all steps (inputSchema keys), sorted. */
73
+ inputNames: string[];
74
+ }
75
+ /**
76
+ * Validate a single composite spec against the set of existing tool names and
77
+ * derive its build result (the `$input` names that form its inputSchema). Throws
78
+ * a {@link CompositeToolError} on any invalid reference. Validation rules:
79
+ * • every step's `tool` must be an existing generated tool name;
80
+ * • a `$step: [i]` may reference only an EARLIER step (i < current step index)
81
+ * and a valid step index (0 ≤ i);
82
+ * • `returns` must reference a valid step index (0 ≤ i < steps.length);
83
+ * • the composite name must not collide with an existing tool name.
84
+ */
85
+ export declare function validateCompositeTool(spec: CompositeToolSpec, existingToolNames: ReadonlySet<string>): CompositeBuildResult;
86
+ /**
87
+ * Build the `src/composite-tools.ts` module that registers every composite tool.
88
+ * Validates each spec against the existing tool names first (throws a
89
+ * {@link CompositeToolError} on any invalid reference). Returns the module
90
+ * source.
91
+ *
92
+ * The handler dispatches each step through an in-process MCP loopback client
93
+ * (the same `InMemoryTransport` + `Client.callTool` pattern as `a2a.ts`), so the
94
+ * step's real generated request logic runs — no HTTP is reinvented. The
95
+ * `registerTools` callback (passed by the server entry) populates the dedicated
96
+ * loopback server with the same tool surface the primary server exposes.
97
+ */
98
+ export declare function buildCompositeToolsModule(specs: readonly CompositeToolSpec[], tools: readonly ToolDefinition[]): string;
99
+ //# sourceMappingURL=composite-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composite-tools.d.ts","sourceRoot":"","sources":["../../src/emitter/composite-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,0DAA0D;AAC1D,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEzD,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,yEAAyE;AACzE,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAErF,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACvC;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,yEAAyE;IACzE,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,oFAAoF;AACpF,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAuBD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,EAAE,CAMzE;AA4ED,MAAM,WAAW,oBAAoB;IACnC,0FAA0F;IAC1F,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,iBAAiB,EACvB,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,GACrC,oBAAoB,CAuCtB;AAqGD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,KAAK,EAAE,SAAS,cAAc,EAAE,GAC/B,MAAM,CAkIR"}