@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.
- package/dist/analyzer/dom-parser.d.ts.map +1 -1
- package/dist/analyzer/dom-parser.js +34 -19
- package/dist/analyzer/dom-parser.js.map +1 -1
- package/dist/analyzer/semantic-analyzer.d.ts.map +1 -1
- package/dist/analyzer/semantic-analyzer.js +10 -3
- package/dist/analyzer/semantic-analyzer.js.map +1 -1
- package/dist/emitter/a2a.d.ts +101 -0
- package/dist/emitter/a2a.d.ts.map +1 -0
- package/dist/emitter/a2a.js +366 -0
- package/dist/emitter/a2a.js.map +1 -0
- package/dist/emitter/code-writer.d.ts.map +1 -1
- package/dist/emitter/code-writer.js +18 -1
- package/dist/emitter/code-writer.js.map +1 -1
- package/dist/emitter/composite-tools.d.ts +99 -0
- package/dist/emitter/composite-tools.d.ts.map +1 -0
- package/dist/emitter/composite-tools.js +391 -0
- package/dist/emitter/composite-tools.js.map +1 -0
- package/dist/emitter/mcp-ui.d.ts +42 -0
- package/dist/emitter/mcp-ui.d.ts.map +1 -0
- package/dist/emitter/mcp-ui.js +146 -0
- package/dist/emitter/mcp-ui.js.map +1 -0
- package/dist/emitter/mcpb-bundler.d.ts +56 -7
- package/dist/emitter/mcpb-bundler.d.ts.map +1 -1
- package/dist/emitter/mcpb-bundler.js +60 -11
- package/dist/emitter/mcpb-bundler.js.map +1 -1
- package/dist/emitter/normalize-tree.d.ts +17 -0
- package/dist/emitter/normalize-tree.d.ts.map +1 -0
- package/dist/emitter/normalize-tree.js +87 -0
- package/dist/emitter/normalize-tree.js.map +1 -0
- package/dist/emitter/project-scaffolder.d.ts.map +1 -1
- package/dist/emitter/project-scaffolder.js +47 -1
- package/dist/emitter/project-scaffolder.js.map +1 -1
- package/dist/emitter/python-template-loader.d.ts.map +1 -1
- package/dist/emitter/python-template-loader.js +6 -1
- package/dist/emitter/python-template-loader.js.map +1 -1
- package/dist/emitter/templates/server-main-http.ts.hbs +48 -0
- package/dist/emitter/templates/server-main.ts.hbs +38 -0
- package/dist/emitter/templates/tsconfig.json.hbs +3 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/parser/overlay-loader.js +5 -1
- package/dist/parser/overlay-loader.js.map +1 -1
- package/dist/parser/postman-loader.js +7 -0
- package/dist/parser/postman-loader.js.map +1 -1
- package/dist/rescan/rescan-scheduler.js +14 -5
- package/dist/rescan/rescan-scheduler.js.map +1 -1
- package/dist/site-transformer/selector-healer.d.ts.map +1 -1
- package/dist/site-transformer/selector-healer.js +8 -0
- package/dist/site-transformer/selector-healer.js.map +1 -1
- package/dist/site-transformer/tool-generator.js +19 -8
- package/dist/site-transformer/tool-generator.js.map +1 -1
- package/dist/transformer/har-to-operations.d.ts.map +1 -1
- package/dist/transformer/har-to-operations.js +17 -6
- package/dist/transformer/har-to-operations.js.map +1 -1
- package/dist/transformer/resource-builder.d.ts.map +1 -1
- package/dist/transformer/resource-builder.js +15 -2
- package/dist/transformer/resource-builder.js.map +1 -1
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,391 @@
|
|
|
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
|
+
/** Thrown when a composite-tool declaration is invalid (surfaced at build time). */
|
|
31
|
+
export class CompositeToolError extends Error {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = 'CompositeToolError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function isPlainObject(v) {
|
|
38
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
39
|
+
}
|
|
40
|
+
function isInputRef(v) {
|
|
41
|
+
return isPlainObject(v) && typeof v.$input === 'string';
|
|
42
|
+
}
|
|
43
|
+
function isStepRef(v) {
|
|
44
|
+
if (!isPlainObject(v))
|
|
45
|
+
return false;
|
|
46
|
+
const ref = v.$step;
|
|
47
|
+
if (!Array.isArray(ref) || ref.length < 1 || ref.length > 2)
|
|
48
|
+
return false;
|
|
49
|
+
if (typeof ref[0] !== 'number' || !Number.isInteger(ref[0]))
|
|
50
|
+
return false;
|
|
51
|
+
if (ref.length === 2 && typeof ref[1] !== 'string')
|
|
52
|
+
return false;
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
function isLiteral(v) {
|
|
56
|
+
return typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parse + validate the raw `compositeTools` value from `.mcpmake.yaml` into typed
|
|
60
|
+
* specs. Throws a {@link CompositeToolError} (clear, build-time) on any malformed
|
|
61
|
+
* entry. Loosely-typed input (the YAML loader returns `unknown`) is checked here.
|
|
62
|
+
*/
|
|
63
|
+
export function parseCompositeToolSpecs(raw) {
|
|
64
|
+
if (raw === undefined || raw === null)
|
|
65
|
+
return [];
|
|
66
|
+
if (!Array.isArray(raw)) {
|
|
67
|
+
throw new CompositeToolError('compositeTools must be a list of composite-tool declarations');
|
|
68
|
+
}
|
|
69
|
+
return raw.map((entry, idx) => parseOne(entry, idx));
|
|
70
|
+
}
|
|
71
|
+
function parseOne(entry, idx) {
|
|
72
|
+
const where = `compositeTools[${idx}]`;
|
|
73
|
+
if (!isPlainObject(entry)) {
|
|
74
|
+
throw new CompositeToolError(`${where} must be a mapping`);
|
|
75
|
+
}
|
|
76
|
+
const name = entry.name;
|
|
77
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
78
|
+
throw new CompositeToolError(`${where} is missing a non-empty "name"`);
|
|
79
|
+
}
|
|
80
|
+
const description = typeof entry.description === 'string' ? entry.description : undefined;
|
|
81
|
+
const rawSteps = entry.steps;
|
|
82
|
+
if (!Array.isArray(rawSteps) || rawSteps.length === 0) {
|
|
83
|
+
throw new CompositeToolError(`Composite tool "${name}" must declare a non-empty "steps" list`);
|
|
84
|
+
}
|
|
85
|
+
const steps = rawSteps.map((s, sIdx) => {
|
|
86
|
+
if (!isPlainObject(s)) {
|
|
87
|
+
throw new CompositeToolError(`Composite tool "${name}" step ${sIdx} must be a mapping`);
|
|
88
|
+
}
|
|
89
|
+
if (typeof s.tool !== 'string' || !s.tool.trim()) {
|
|
90
|
+
throw new CompositeToolError(`Composite tool "${name}" step ${sIdx} is missing a non-empty "tool"`);
|
|
91
|
+
}
|
|
92
|
+
let withMap;
|
|
93
|
+
if (s.with !== undefined) {
|
|
94
|
+
if (!isPlainObject(s.with)) {
|
|
95
|
+
throw new CompositeToolError(`Composite tool "${name}" step ${sIdx} "with" must be a mapping`);
|
|
96
|
+
}
|
|
97
|
+
withMap = {};
|
|
98
|
+
for (const [argName, value] of Object.entries(s.with)) {
|
|
99
|
+
withMap[argName] = parseValue(value, name, sIdx, argName);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { tool: s.tool, with: withMap };
|
|
103
|
+
});
|
|
104
|
+
let returns;
|
|
105
|
+
if (entry.returns !== undefined) {
|
|
106
|
+
if (!isStepRef(entry.returns)) {
|
|
107
|
+
throw new CompositeToolError(`Composite tool "${name}" "returns" must be a { $step: [i] } / { $step: [i, field] } reference`);
|
|
108
|
+
}
|
|
109
|
+
returns = entry.returns;
|
|
110
|
+
}
|
|
111
|
+
return { name, description, steps, returns };
|
|
112
|
+
}
|
|
113
|
+
function parseValue(value, toolName, stepIdx, argName) {
|
|
114
|
+
if (isLiteral(value))
|
|
115
|
+
return value;
|
|
116
|
+
if (isInputRef(value)) {
|
|
117
|
+
if (!value.$input.trim()) {
|
|
118
|
+
throw new CompositeToolError(`Composite tool "${toolName}" step ${stepIdx} arg "${argName}": $input name must be non-empty`);
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
if (isStepRef(value))
|
|
123
|
+
return value;
|
|
124
|
+
throw new CompositeToolError(`Composite tool "${toolName}" step ${stepIdx} arg "${argName}" must be a literal, ` +
|
|
125
|
+
`{ $input: <name> }, or { $step: [i] } / { $step: [i, field] }`);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validate a single composite spec against the set of existing tool names and
|
|
129
|
+
* derive its build result (the `$input` names that form its inputSchema). Throws
|
|
130
|
+
* a {@link CompositeToolError} on any invalid reference. Validation rules:
|
|
131
|
+
* • every step's `tool` must be an existing generated tool name;
|
|
132
|
+
* • a `$step: [i]` may reference only an EARLIER step (i < current step index)
|
|
133
|
+
* and a valid step index (0 ≤ i);
|
|
134
|
+
* • `returns` must reference a valid step index (0 ≤ i < steps.length);
|
|
135
|
+
* • the composite name must not collide with an existing tool name.
|
|
136
|
+
*/
|
|
137
|
+
export function validateCompositeTool(spec, existingToolNames) {
|
|
138
|
+
if (existingToolNames.has(spec.name)) {
|
|
139
|
+
throw new CompositeToolError(`Composite tool "${spec.name}" collides with an existing generated tool of the same name`);
|
|
140
|
+
}
|
|
141
|
+
const inputNames = new Set();
|
|
142
|
+
spec.steps.forEach((step, stepIdx) => {
|
|
143
|
+
if (!existingToolNames.has(step.tool)) {
|
|
144
|
+
throw new CompositeToolError(`Composite tool "${spec.name}" step ${stepIdx} references unknown tool "${step.tool}". ` +
|
|
145
|
+
`It must name an existing generated tool.`);
|
|
146
|
+
}
|
|
147
|
+
for (const [argName, value] of Object.entries(step.with ?? {})) {
|
|
148
|
+
if (isInputRef(value)) {
|
|
149
|
+
inputNames.add(value.$input);
|
|
150
|
+
}
|
|
151
|
+
else if (isStepRef(value)) {
|
|
152
|
+
const target = value.$step[0];
|
|
153
|
+
if (target < 0 || target >= stepIdx) {
|
|
154
|
+
throw new CompositeToolError(`Composite tool "${spec.name}" step ${stepIdx} arg "${argName}" references step ${target}, ` +
|
|
155
|
+
`but a step may only reference an EARLIER step (0..${stepIdx - 1}).`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
const returnsTarget = spec.returns ? spec.returns.$step[0] : spec.steps.length - 1;
|
|
161
|
+
if (returnsTarget < 0 || returnsTarget >= spec.steps.length) {
|
|
162
|
+
throw new CompositeToolError(`Composite tool "${spec.name}" "returns" references step ${returnsTarget}, ` +
|
|
163
|
+
`but valid steps are 0..${spec.steps.length - 1}.`);
|
|
164
|
+
}
|
|
165
|
+
return { inputNames: [...inputNames].sort() };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Emit the runtime expression that resolves one composite {@link CompositeValue}
|
|
169
|
+
* at handler runtime. Literals and refs are JSON.stringify'd so no config text
|
|
170
|
+
* can break out of the generated source. `$step` references read from the
|
|
171
|
+
* `steps` result array; `[i, field]` calls a runtime helper that throws a clear
|
|
172
|
+
* error when the result is not an object or the field is missing.
|
|
173
|
+
*/
|
|
174
|
+
function emitValueExpr(value) {
|
|
175
|
+
if (isInputRef(value)) {
|
|
176
|
+
// Read from the composite tool's own input object.
|
|
177
|
+
return `input[${JSON.stringify(value.$input)}]`;
|
|
178
|
+
}
|
|
179
|
+
if (isStepRef(value)) {
|
|
180
|
+
const [i, field] = value.$step;
|
|
181
|
+
if (field === undefined) {
|
|
182
|
+
return `steps[${JSON.stringify(i)}]`;
|
|
183
|
+
}
|
|
184
|
+
return `stepField(steps, ${JSON.stringify(i)}, ${JSON.stringify(field)})`;
|
|
185
|
+
}
|
|
186
|
+
// Literal: JSON-encode (covers string/number/boolean inertly).
|
|
187
|
+
return JSON.stringify(value);
|
|
188
|
+
}
|
|
189
|
+
/** Emit the `arguments` object literal for one step's tool call. */
|
|
190
|
+
function emitArgsObject(withMap) {
|
|
191
|
+
const entries = Object.entries(withMap ?? {});
|
|
192
|
+
if (entries.length === 0)
|
|
193
|
+
return '{}';
|
|
194
|
+
const lines = entries.map(([argName, value]) => ` ${JSON.stringify(argName)}: ${emitValueExpr(value)},`);
|
|
195
|
+
return `{\n${lines.join('\n')}\n }`;
|
|
196
|
+
}
|
|
197
|
+
/** Emit the JS for one step inside the handler's sequential run loop. */
|
|
198
|
+
function emitStep(step, stepIdx) {
|
|
199
|
+
return (` // Step ${stepIdx}: ${'invoke an existing generated tool'}\n` +
|
|
200
|
+
` steps[${stepIdx}] = await callStep(client, ${JSON.stringify(step.tool)}, ${emitArgsObject(step.with)});`);
|
|
201
|
+
}
|
|
202
|
+
/** Emit the registration for one composite tool (its inputSchema + handler). */
|
|
203
|
+
function emitCompositeRegistration(spec, build) {
|
|
204
|
+
const schemaFields = build.inputNames.length === 0
|
|
205
|
+
? '{}'
|
|
206
|
+
: `{\n${build.inputNames
|
|
207
|
+
.map((n) => ` ${JSON.stringify(n)}: z.string().describe('Input "${escapeForLineComment(n)}" for composite tool ${escapeForLineComment(spec.name)}'),`)
|
|
208
|
+
.join('\n')}\n }`;
|
|
209
|
+
const stepsBody = spec.steps.map((s, i) => emitStep(s, i)).join('\n');
|
|
210
|
+
const returnsTarget = spec.returns ? spec.returns.$step[0] : spec.steps.length - 1;
|
|
211
|
+
const returnsField = spec.returns && spec.returns.$step.length === 2 ? spec.returns.$step[1] : undefined;
|
|
212
|
+
const returnExpr = returnsField === undefined
|
|
213
|
+
? `steps[${JSON.stringify(returnsTarget)}]`
|
|
214
|
+
: `stepField(steps, ${JSON.stringify(returnsTarget)}, ${JSON.stringify(returnsField)})`;
|
|
215
|
+
return ` server.registerTool(
|
|
216
|
+
${JSON.stringify(spec.name)},
|
|
217
|
+
{
|
|
218
|
+
title: ${JSON.stringify(spec.name)},
|
|
219
|
+
description: ${JSON.stringify(spec.description ?? `Composite tool: runs ${spec.steps.length} step(s) in order.`)},
|
|
220
|
+
inputSchema: ${schemaFields},
|
|
221
|
+
},
|
|
222
|
+
async (rawInput) => {
|
|
223
|
+
const input = rawInput as Record<string, unknown>;
|
|
224
|
+
const steps: unknown[] = [];
|
|
225
|
+
try {
|
|
226
|
+
const client = await getClient(registerTools);
|
|
227
|
+
${stepsBody}
|
|
228
|
+
const result = ${returnExpr};
|
|
229
|
+
return {
|
|
230
|
+
content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
|
|
231
|
+
};
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
234
|
+
return {
|
|
235
|
+
content: [{ type: 'text' as const, text: \`Error: \${message}\` }],
|
|
236
|
+
isError: true,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
);`;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Escape a string for safe inclusion inside a single-quoted JS string that is
|
|
244
|
+
* itself emitted as part of a `.describe('...')` call. Strips quotes/backslashes/
|
|
245
|
+
* newlines so an input/tool name from config can never break the literal.
|
|
246
|
+
*/
|
|
247
|
+
function escapeForLineComment(s) {
|
|
248
|
+
return s.replace(/[\\'\r\n]/g, '');
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Build the `src/composite-tools.ts` module that registers every composite tool.
|
|
252
|
+
* Validates each spec against the existing tool names first (throws a
|
|
253
|
+
* {@link CompositeToolError} on any invalid reference). Returns the module
|
|
254
|
+
* source.
|
|
255
|
+
*
|
|
256
|
+
* The handler dispatches each step through an in-process MCP loopback client
|
|
257
|
+
* (the same `InMemoryTransport` + `Client.callTool` pattern as `a2a.ts`), so the
|
|
258
|
+
* step's real generated request logic runs — no HTTP is reinvented. The
|
|
259
|
+
* `registerTools` callback (passed by the server entry) populates the dedicated
|
|
260
|
+
* loopback server with the same tool surface the primary server exposes.
|
|
261
|
+
*/
|
|
262
|
+
export function buildCompositeToolsModule(specs, tools) {
|
|
263
|
+
const existingToolNames = new Set(tools.map((t) => t.name));
|
|
264
|
+
// Reject a composite whose name duplicates another composite, too — they are
|
|
265
|
+
// all registered on the same server and a dup would crash registration.
|
|
266
|
+
const seenComposite = new Set();
|
|
267
|
+
const registrations = specs.map((spec) => {
|
|
268
|
+
if (seenComposite.has(spec.name)) {
|
|
269
|
+
throw new CompositeToolError(`Duplicate composite tool name "${spec.name}"`);
|
|
270
|
+
}
|
|
271
|
+
seenComposite.add(spec.name);
|
|
272
|
+
const build = validateCompositeTool(spec, existingToolNames);
|
|
273
|
+
return emitCompositeRegistration(spec, build);
|
|
274
|
+
});
|
|
275
|
+
return `import { z } from 'zod';
|
|
276
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
277
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
278
|
+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
279
|
+
|
|
280
|
+
/* Composite tools: each runs several EXISTING generated tools in order, threading
|
|
281
|
+
* data between them. Steps are dispatched through an in-process MCP loopback
|
|
282
|
+
* client connected to a DEDICATED server populated with the same tool surface, so
|
|
283
|
+
* each step reuses that tool's real request logic (method, path, params, auth, jq
|
|
284
|
+
* filtering). Generated by @mcpmake/core — do not edit by hand. */
|
|
285
|
+
|
|
286
|
+
type ToolResult = Awaited<ReturnType<Client['callTool']>>;
|
|
287
|
+
|
|
288
|
+
/* Parse a step tool's CallToolResult into a plain JS value for later steps and
|
|
289
|
+
* the composite's return. Prefers structuredContent (the SDK validates it against
|
|
290
|
+
* the tool's outputSchema); otherwise parses the first text block as JSON, falling
|
|
291
|
+
* back to the raw text. A tool that reported isError fails the whole composite. */
|
|
292
|
+
function parseStepResult(result: ToolResult): unknown {
|
|
293
|
+
if ((result as { isError?: unknown }).isError === true) {
|
|
294
|
+
const content = (result as { content?: unknown }).content;
|
|
295
|
+
const text =
|
|
296
|
+
Array.isArray(content) && content[0] && typeof content[0] === 'object'
|
|
297
|
+
? String((content[0] as { text?: unknown }).text ?? '')
|
|
298
|
+
: 'step tool reported an error';
|
|
299
|
+
throw new Error(text);
|
|
300
|
+
}
|
|
301
|
+
const structured = (result as { structuredContent?: unknown }).structuredContent;
|
|
302
|
+
if (structured !== undefined) return structured;
|
|
303
|
+
const content = (result as { content?: unknown }).content;
|
|
304
|
+
if (Array.isArray(content)) {
|
|
305
|
+
for (const block of content) {
|
|
306
|
+
if (block && typeof block === 'object' && (block as { type?: unknown }).type === 'text') {
|
|
307
|
+
const text = String((block as { text?: unknown }).text ?? '');
|
|
308
|
+
try {
|
|
309
|
+
return JSON.parse(text);
|
|
310
|
+
} catch {
|
|
311
|
+
return text;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* Resolve a { $step: [i, field] } reference: read \`field\` off step i's result.
|
|
320
|
+
* Throws a clear error when the result is not an object or the field is missing,
|
|
321
|
+
* so the composite tool call fails with a useful message. */
|
|
322
|
+
function stepField(steps: unknown[], i: number, field: string): unknown {
|
|
323
|
+
const r = steps[i];
|
|
324
|
+
if (r === null || typeof r !== 'object' || Array.isArray(r)) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
\`Cannot read field "\${field}" from step \${i}: its result is not a JSON object\`,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (!(field in (r as Record<string, unknown>))) {
|
|
330
|
+
throw new Error(\`Step \${i} result has no field "\${field}"\`);
|
|
331
|
+
}
|
|
332
|
+
return (r as Record<string, unknown>)[field];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* Invoke one step's existing generated tool via the loopback client and return
|
|
336
|
+
* its parsed result. \`args\` values come from the composite's own input or an
|
|
337
|
+
* earlier step's result. */
|
|
338
|
+
async function callStep(
|
|
339
|
+
client: Client,
|
|
340
|
+
toolName: string,
|
|
341
|
+
args: Record<string, unknown>,
|
|
342
|
+
): Promise<unknown> {
|
|
343
|
+
// Strip args whose resolved value is undefined (an absent optional input) so the
|
|
344
|
+
// step tool sees only the arguments actually supplied.
|
|
345
|
+
const clean: Record<string, unknown> = {};
|
|
346
|
+
for (const [k, v] of Object.entries(args)) {
|
|
347
|
+
if (v !== undefined) clean[k] = v;
|
|
348
|
+
}
|
|
349
|
+
const result = await client.callTool({ name: toolName, arguments: clean });
|
|
350
|
+
return parseStepResult(result);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* Lazily stand up (once) the DEDICATED loopback McpServer + in-process MCP client
|
|
354
|
+
* that composite steps dispatch through. \`registerTools\` populates the loopback
|
|
355
|
+
* server with the same tool surface the primary server exposes, so each step
|
|
356
|
+
* reuses that tool's real request logic. Memoized: the first composite call
|
|
357
|
+
* connects the pair; subsequent calls reuse it. Public SDK API only. */
|
|
358
|
+
let clientPromise: Promise<Client> | undefined;
|
|
359
|
+
function getClient(registerTools: (s: McpServer) => void): Promise<Client> {
|
|
360
|
+
if (!clientPromise) {
|
|
361
|
+
clientPromise = (async () => {
|
|
362
|
+
const { McpServer: McpServerCtor } = await import(
|
|
363
|
+
'@modelcontextprotocol/sdk/server/mcp.js'
|
|
364
|
+
);
|
|
365
|
+
const loopback = new McpServerCtor({ name: 'composite-loopback', version: '0.0.0' });
|
|
366
|
+
registerTools(loopback);
|
|
367
|
+
const client = new Client({ name: 'composite-loopback-client', version: '0.0.0' });
|
|
368
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
369
|
+
await loopback.connect(serverTransport);
|
|
370
|
+
await client.connect(clientTransport);
|
|
371
|
+
return client;
|
|
372
|
+
})();
|
|
373
|
+
}
|
|
374
|
+
return clientPromise;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Register every composite tool on \`server\`. \`registerTools\` populates the
|
|
379
|
+
* dedicated loopback server (lazily, on the first composite call) with the same
|
|
380
|
+
* tool surface the primary server exposes, so a composite step never contends
|
|
381
|
+
* with the primary transport.
|
|
382
|
+
*/
|
|
383
|
+
export function registerCompositeTools(
|
|
384
|
+
server: McpServer,
|
|
385
|
+
registerTools: (s: McpServer) => void,
|
|
386
|
+
): void {
|
|
387
|
+
${registrations.join('\n\n')}
|
|
388
|
+
}
|
|
389
|
+
`;
|
|
390
|
+
}
|
|
391
|
+
//# sourceMappingURL=composite-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composite-tools.js","sourceRoot":"","sources":["../../src/emitter/composite-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Apps output (`mcp-ui`): generate a sandboxed-iframe HTML UI for a generated MCP
|
|
3
|
+
* server, conformant to the MCP-UI standard (MCP-UI-Org/mcp-ui):
|
|
4
|
+
* • the server exposes a UIResource at `ui://<server>/tools`,
|
|
5
|
+
* • mimeType `text/html;profile=mcp-app`,
|
|
6
|
+
* • the HTML runs in the host's sandboxed iframe and asks the host to invoke a tool via
|
|
7
|
+
* `window.parent.postMessage({ type: 'tool', payload: { toolName, params } }, '*')`.
|
|
8
|
+
*
|
|
9
|
+
* The generated UI is a TOOL LAUNCHER: it lists every tool with a JSON-params box and an
|
|
10
|
+
* Invoke button that posts the tool-call message. Everything is inline (no external assets)
|
|
11
|
+
* so it works under a strict iframe sandbox. PURE: a deterministic function of the inputs,
|
|
12
|
+
* with all interpolated values HTML/JS-escaped (tool descriptions come from the source API
|
|
13
|
+
* and must never break out of the markup).
|
|
14
|
+
*/
|
|
15
|
+
/** MCP-UI standard MIME type for inline HTML app content. */
|
|
16
|
+
export declare const MCP_UI_MIME = "text/html;profile=mcp-app";
|
|
17
|
+
export interface McpUiTool {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
}
|
|
21
|
+
export interface McpUiResource {
|
|
22
|
+
uri: string;
|
|
23
|
+
mimeType: string;
|
|
24
|
+
text: string;
|
|
25
|
+
}
|
|
26
|
+
/** The `ui://` resource URI for a server's tool launcher. */
|
|
27
|
+
export declare function mcpUiUri(serverName: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Build the tool-launcher HTML. Each tool gets a JSON-params textarea and an Invoke button
|
|
30
|
+
* that posts the MCP-UI `tool` message to the host. Tool names are JSON-encoded into the
|
|
31
|
+
* script (so a name can never break the string literal); descriptions are HTML-escaped.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildMcpUiHtml(serverName: string, tools: readonly McpUiTool[]): string;
|
|
34
|
+
/** Build the full MCP-UI resource (uri + mimeType + HTML) for a server's tools. */
|
|
35
|
+
export declare function mcpUiResource(serverName: string, tools: readonly McpUiTool[]): McpUiResource;
|
|
36
|
+
/**
|
|
37
|
+
* Generate the `src/mcp-ui.ts` module the server ships when MCP Apps output is enabled.
|
|
38
|
+
* It registers the `ui://` UIResource via the MCP SDK; the HTML is embedded as a JSON-encoded
|
|
39
|
+
* string constant (so any character in it is inert in the generated source).
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildMcpUiModule(serverName: string, tools: readonly McpUiTool[]): string;
|
|
42
|
+
//# sourceMappingURL=mcp-ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-ui.d.ts","sourceRoot":"","sources":["../../src/emitter/mcp-ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,6DAA6D;AAC7D,eAAO,MAAM,WAAW,8BAA8B,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAqBD,6DAA6D;AAC7D,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CA4DtF;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,aAAa,CAM5F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CA+BxF"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Apps output (`mcp-ui`): generate a sandboxed-iframe HTML UI for a generated MCP
|
|
3
|
+
* server, conformant to the MCP-UI standard (MCP-UI-Org/mcp-ui):
|
|
4
|
+
* • the server exposes a UIResource at `ui://<server>/tools`,
|
|
5
|
+
* • mimeType `text/html;profile=mcp-app`,
|
|
6
|
+
* • the HTML runs in the host's sandboxed iframe and asks the host to invoke a tool via
|
|
7
|
+
* `window.parent.postMessage({ type: 'tool', payload: { toolName, params } }, '*')`.
|
|
8
|
+
*
|
|
9
|
+
* The generated UI is a TOOL LAUNCHER: it lists every tool with a JSON-params box and an
|
|
10
|
+
* Invoke button that posts the tool-call message. Everything is inline (no external assets)
|
|
11
|
+
* so it works under a strict iframe sandbox. PURE: a deterministic function of the inputs,
|
|
12
|
+
* with all interpolated values HTML/JS-escaped (tool descriptions come from the source API
|
|
13
|
+
* and must never break out of the markup).
|
|
14
|
+
*/
|
|
15
|
+
/** MCP-UI standard MIME type for inline HTML app content. */
|
|
16
|
+
export const MCP_UI_MIME = 'text/html;profile=mcp-app';
|
|
17
|
+
/** Escape a string for use in HTML text / double-quoted attribute context. */
|
|
18
|
+
function escapeHtml(s) {
|
|
19
|
+
return s
|
|
20
|
+
.replace(/&/g, '&')
|
|
21
|
+
.replace(/</g, '<')
|
|
22
|
+
.replace(/>/g, '>')
|
|
23
|
+
.replace(/"/g, '"')
|
|
24
|
+
.replace(/'/g, ''');
|
|
25
|
+
}
|
|
26
|
+
/** Slugify a server name for the `ui://` host segment (kebab, alnum + dashes). */
|
|
27
|
+
function uiSlug(serverName) {
|
|
28
|
+
const slug = serverName
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
31
|
+
.replace(/^-+|-+$/g, '');
|
|
32
|
+
return slug || 'server';
|
|
33
|
+
}
|
|
34
|
+
/** The `ui://` resource URI for a server's tool launcher. */
|
|
35
|
+
export function mcpUiUri(serverName) {
|
|
36
|
+
return `ui://${uiSlug(serverName)}/tools`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the tool-launcher HTML. Each tool gets a JSON-params textarea and an Invoke button
|
|
40
|
+
* that posts the MCP-UI `tool` message to the host. Tool names are JSON-encoded into the
|
|
41
|
+
* script (so a name can never break the string literal); descriptions are HTML-escaped.
|
|
42
|
+
*/
|
|
43
|
+
export function buildMcpUiHtml(serverName, tools) {
|
|
44
|
+
const title = escapeHtml(serverName);
|
|
45
|
+
const items = tools
|
|
46
|
+
.map((t) => {
|
|
47
|
+
const name = escapeHtml(t.name);
|
|
48
|
+
const desc = escapeHtml(t.description || '');
|
|
49
|
+
// data-tool carries the raw (escaped) name; the click handler reads it back.
|
|
50
|
+
return (` <li class="tool">\n` +
|
|
51
|
+
` <div class="tool-head"><code>${name}</code>${desc ? ` — <span class="desc">${desc}</span>` : ''}</div>\n` +
|
|
52
|
+
` <details><summary>params (JSON)</summary><textarea data-params="${name}">{}</textarea></details>\n` +
|
|
53
|
+
` <button type="button" data-tool="${name}">Invoke</button>\n` +
|
|
54
|
+
` </li>`);
|
|
55
|
+
})
|
|
56
|
+
.join('\n');
|
|
57
|
+
return `<!doctype html>
|
|
58
|
+
<html lang="en">
|
|
59
|
+
<head>
|
|
60
|
+
<meta charset="utf-8">
|
|
61
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
62
|
+
<title>${title} — MCP tools</title>
|
|
63
|
+
<style>
|
|
64
|
+
body { font: 14px/1.5 system-ui, sans-serif; margin: 1rem; color: #111; }
|
|
65
|
+
h1 { font-size: 1.2rem; }
|
|
66
|
+
ul { list-style: none; padding: 0; }
|
|
67
|
+
.tool { border: 1px solid #ddd; border-radius: 6px; padding: .6rem .8rem; margin: .5rem 0; }
|
|
68
|
+
.desc { color: #555; }
|
|
69
|
+
textarea { width: 100%; box-sizing: border-box; font: 12px monospace; min-height: 3rem; }
|
|
70
|
+
button { margin-top: .4rem; cursor: pointer; }
|
|
71
|
+
code { background: #f4f4f4; padding: 0 .25rem; border-radius: 3px; }
|
|
72
|
+
</style>
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
<h1>${title}</h1>
|
|
76
|
+
<p>${tools.length} tool${tools.length === 1 ? '' : 's'}. Edit the JSON params and click Invoke — the host runs the tool.</p>
|
|
77
|
+
<ul>
|
|
78
|
+
${items}
|
|
79
|
+
</ul>
|
|
80
|
+
<script>
|
|
81
|
+
document.querySelectorAll('button[data-tool]').forEach(function (btn) {
|
|
82
|
+
btn.addEventListener('click', function () {
|
|
83
|
+
var name = btn.getAttribute('data-tool');
|
|
84
|
+
var ta = document.querySelector('textarea[data-params="' + name + '"]');
|
|
85
|
+
var params = {};
|
|
86
|
+
try {
|
|
87
|
+
params = ta && ta.value.trim() ? JSON.parse(ta.value) : {};
|
|
88
|
+
} catch (e) {
|
|
89
|
+
alert('Invalid JSON params for ' + name);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// MCP-UI host interaction: ask the host to call the tool.
|
|
93
|
+
window.parent.postMessage({ type: 'tool', payload: { toolName: name, params: params } }, '*');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
</script>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
/** Build the full MCP-UI resource (uri + mimeType + HTML) for a server's tools. */
|
|
102
|
+
export function mcpUiResource(serverName, tools) {
|
|
103
|
+
return {
|
|
104
|
+
uri: mcpUiUri(serverName),
|
|
105
|
+
mimeType: MCP_UI_MIME,
|
|
106
|
+
text: buildMcpUiHtml(serverName, tools),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Generate the `src/mcp-ui.ts` module the server ships when MCP Apps output is enabled.
|
|
111
|
+
* It registers the `ui://` UIResource via the MCP SDK; the HTML is embedded as a JSON-encoded
|
|
112
|
+
* string constant (so any character in it is inert in the generated source).
|
|
113
|
+
*/
|
|
114
|
+
export function buildMcpUiModule(serverName, tools) {
|
|
115
|
+
const res = mcpUiResource(serverName, tools);
|
|
116
|
+
// Mirrors the generated resources.ts registration pattern (McpServer + ResourceTemplate)
|
|
117
|
+
// so it compiles against the same MCP SDK the rest of the server uses.
|
|
118
|
+
return `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
119
|
+
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
120
|
+
|
|
121
|
+
const MCP_UI_URI = ${JSON.stringify(res.uri)};
|
|
122
|
+
const MCP_UI_MIME = ${JSON.stringify(res.mimeType)};
|
|
123
|
+
const MCP_UI_HTML = ${JSON.stringify(res.text)};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Register the MCP Apps (mcp-ui) tool-launcher UI as a ui:// resource. Hosts that support
|
|
127
|
+
* MCP Apps render MCP_UI_HTML in a sandboxed iframe; its Invoke buttons post a
|
|
128
|
+
* { type: 'tool', payload: { toolName, params } } message to the host to call a tool.
|
|
129
|
+
*/
|
|
130
|
+
export function registerMcpUi(server: McpServer): void {
|
|
131
|
+
server.registerResource(
|
|
132
|
+
'mcp-ui',
|
|
133
|
+
new ResourceTemplate(MCP_UI_URI, { list: undefined }),
|
|
134
|
+
{
|
|
135
|
+
title: ${JSON.stringify(`${serverName} UI`)},
|
|
136
|
+
description: 'Interactive tool launcher (MCP Apps / mcp-ui)',
|
|
137
|
+
mimeType: MCP_UI_MIME,
|
|
138
|
+
},
|
|
139
|
+
async (uri) => ({
|
|
140
|
+
contents: [{ uri: uri.href, mimeType: MCP_UI_MIME, text: MCP_UI_HTML }],
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=mcp-ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-ui.js","sourceRoot":"","sources":["../../src/emitter/mcp-ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,6DAA6D;AAC7D,MAAM,CAAC,MAAM,WAAW,GAAG,2BAA2B,CAAC;AAavD,8EAA8E;AAC9E,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC;SACL,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED,kFAAkF;AAClF,SAAS,MAAM,CAAC,UAAkB;IAChC,MAAM,IAAI,GAAG,UAAU;SACpB,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,OAAO,IAAI,IAAI,QAAQ,CAAC;AAC1B,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,QAAQ,CAAC,UAAkB;IACzC,OAAO,QAAQ,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,KAA2B;IAC5E,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,KAAK;SAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QAC7C,6EAA6E;QAC7E,OAAO,CACL,2BAA2B;YAC3B,wCAAwC,IAAI,UAAU,IAAI,CAAC,CAAC,CAAC,yBAAyB,IAAI,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU;YAClH,2EAA2E,IAAI,6BAA6B;YAC5G,4CAA4C,IAAI,qBAAqB;YACrE,aAAa,CACd,CAAC;IACJ,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;;;;;WAKE,KAAK;;;;;;;;;;;;;QAaR,KAAK;OACN,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG;;EAEtD,KAAK;;;;;;;;;;;;;;;;;;;;;CAqBN,CAAC;AACF,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,KAA2B;IAC3E,OAAO;QACL,GAAG,EAAE,QAAQ,CAAC,UAAU,CAAC;QACzB,QAAQ,EAAE,WAAW;QACrB,IAAI,EAAE,cAAc,CAAC,UAAU,EAAE,KAAK,CAAC;KACxC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,KAA2B;IAC9E,MAAM,GAAG,GAAG,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC7C,yFAAyF;IACzF,uEAAuE;IACvE,OAAO;;;qBAGY,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;sBACtB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;sBAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;;;;;;;;;;;;eAY/B,IAAI,CAAC,SAAS,CAAC,GAAG,UAAU,KAAK,CAAC;;;;;;;;;CAShD,CAAC;AACF,CAAC"}
|