@cyanheads/mcp-ts-core 0.13.10 → 0.13.12
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/AGENTS.md +9 -6
- package/CLAUDE.md +9 -6
- package/README.md +29 -3
- package/biome.json +1 -1
- package/changelog/0.13.x/0.13.11.md +113 -0
- package/changelog/0.13.x/0.13.12.md +50 -0
- package/dist/cli/app-render.d.ts +11 -0
- package/dist/cli/app-render.d.ts.map +1 -0
- package/dist/cli/app-render.js +150 -0
- package/dist/cli/app-render.js.map +1 -0
- package/dist/cli/init.d.ts +2 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +10 -2
- package/dist/cli/init.js.map +1 -1
- package/dist/core/context.d.ts +3 -2
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +25 -16
- package/dist/core/context.js.map +1 -1
- package/dist/mcp-server/handlerContext.d.ts +14 -8
- package/dist/mcp-server/handlerContext.d.ts.map +1 -1
- package/dist/mcp-server/handlerContext.js +16 -9
- package/dist/mcp-server/handlerContext.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +15 -7
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts +8 -2
- package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/inputPrevalidation.js +18 -7
- package/dist/mcp-server/tools/utils/inputPrevalidation.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +79 -8
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.d.ts +20 -0
- package/dist/mcp-server/transports/auth/lib/authUtils.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.js +28 -2
- package/dist/mcp-server/transports/auth/lib/authUtils.js.map +1 -1
- package/dist/mcp-server/transports/auth/lib/checkScopes.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/lib/checkScopes.js +2 -1
- package/dist/mcp-server/transports/auth/lib/checkScopes.js.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.js +4 -2
- package/dist/mcp-server/transports/http/httpErrorHandler.js.map +1 -1
- package/dist/services/mirror/sqlite/handle.d.ts +22 -5
- package/dist/services/mirror/sqlite/handle.d.ts.map +1 -1
- package/dist/services/mirror/sqlite/handle.js +61 -12
- package/dist/services/mirror/sqlite/handle.js.map +1 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.d.ts +6 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.d.ts.map +1 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js +3 -2
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js.map +1 -1
- package/dist/testing/apps/browser.d.ts +27 -0
- package/dist/testing/apps/browser.d.ts.map +1 -0
- package/dist/testing/apps/browser.js +162 -0
- package/dist/testing/apps/browser.js.map +1 -0
- package/dist/testing/apps/cdp-pipe.d.ts +79 -0
- package/dist/testing/apps/cdp-pipe.d.ts.map +1 -0
- package/dist/testing/apps/cdp-pipe.js +200 -0
- package/dist/testing/apps/cdp-pipe.js.map +1 -0
- package/dist/testing/apps/csp.d.ts +17 -0
- package/dist/testing/apps/csp.d.ts.map +1 -0
- package/dist/testing/apps/csp.js +50 -0
- package/dist/testing/apps/csp.js.map +1 -0
- package/dist/testing/apps/host-pages.d.ts +47 -0
- package/dist/testing/apps/host-pages.d.ts.map +1 -0
- package/dist/testing/apps/host-pages.js +138 -0
- package/dist/testing/apps/host-pages.js.map +1 -0
- package/dist/testing/apps/index.d.ts +167 -0
- package/dist/testing/apps/index.d.ts.map +1 -0
- package/dist/testing/apps/index.js +36 -0
- package/dist/testing/apps/index.js.map +1 -0
- package/dist/testing/apps/partial-json.d.ts +14 -0
- package/dist/testing/apps/partial-json.d.ts.map +1 -0
- package/dist/testing/apps/partial-json.js +71 -0
- package/dist/testing/apps/partial-json.js.map +1 -0
- package/dist/testing/apps/run.d.ts +13 -0
- package/dist/testing/apps/run.d.ts.map +1 -0
- package/dist/testing/apps/run.js +659 -0
- package/dist/testing/apps/run.js.map +1 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.d.ts +1 -0
- package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.js +22 -11
- package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
- package/dist/utils/internal/error-handler/helpers.d.ts +16 -5
- package/dist/utils/internal/error-handler/helpers.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/helpers.js +12 -7
- package/dist/utils/internal/error-handler/helpers.js.map +1 -1
- package/dist/utils/internal/error-handler/types.d.ts +11 -4
- package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
- package/dist/utils/internal/logger.d.ts +21 -1
- package/dist/utils/internal/logger.d.ts.map +1 -1
- package/dist/utils/internal/logger.js +31 -14
- package/dist/utils/internal/logger.js.map +1 -1
- package/dist/utils/internal/observabilityCap.d.ts +35 -0
- package/dist/utils/internal/observabilityCap.d.ts.map +1 -0
- package/dist/utils/internal/observabilityCap.js +43 -0
- package/dist/utils/internal/observabilityCap.js.map +1 -0
- package/dist/utils/internal/performance.d.ts +3 -1
- package/dist/utils/internal/performance.d.ts.map +1 -1
- package/dist/utils/internal/performance.js +8 -3
- package/dist/utils/internal/performance.js.map +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts +3 -1
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +70 -16
- package/dist/utils/network/fetchWithTimeout.js.map +1 -1
- package/dist/utils/network/retry.d.ts +22 -2
- package/dist/utils/network/retry.d.ts.map +1 -1
- package/dist/utils/network/retry.js +29 -1
- package/dist/utils/network/retry.js.map +1 -1
- package/dist/utils/security/sanitization.d.ts +16 -13
- package/dist/utils/security/sanitization.d.ts.map +1 -1
- package/dist/utils/security/sanitization.js +74 -33
- package/dist/utils/security/sanitization.js.map +1 -1
- package/dist/utils/telemetry/attributes.d.ts +7 -1
- package/dist/utils/telemetry/attributes.d.ts.map +1 -1
- package/dist/utils/telemetry/attributes.js +7 -1
- package/dist/utils/telemetry/attributes.js.map +1 -1
- package/framework-skills/add-app-tool/SKILL.md +2 -1
- package/framework-skills/add-test/SKILL.md +15 -15
- package/framework-skills/add-tool/SKILL.md +2 -2
- package/framework-skills/api-auth/SKILL.md +3 -1
- package/framework-skills/api-canvas/SKILL.md +2 -2
- package/framework-skills/api-config/SKILL.md +11 -2
- package/framework-skills/api-context/SKILL.md +5 -5
- package/framework-skills/api-errors/SKILL.md +6 -6
- package/framework-skills/api-mirror/SKILL.md +6 -3
- package/framework-skills/api-telemetry/SKILL.md +15 -9
- package/framework-skills/api-testing/SKILL.md +5 -1
- package/framework-skills/api-utils/SKILL.md +5 -5
- package/framework-skills/code-simplifier/SKILL.md +2 -2
- package/framework-skills/field-test/SKILL.md +53 -8
- package/framework-skills/git-wrapup/SKILL.md +4 -4
- package/framework-skills/orchestrations/SKILL.md +1 -1
- package/framework-skills/orchestrations/workflows/greenfield-build.md +2 -2
- package/framework-skills/polish-docs-meta/SKILL.md +4 -3
- package/framework-skills/polish-docs-meta/references/server-json.md +24 -26
- package/framework-skills/release-and-publish/SKILL.md +2 -2
- package/framework-skills/release-pr-review/SKILL.md +5 -5
- package/framework-skills/setup/SKILL.md +2 -2
- package/framework-skills/tool-defs-analysis/SKILL.md +2 -2
- package/package.json +38 -23
- package/scripts/devcheck.ts +43 -21
- package/scripts/lint-packaging.ts +91 -1
- package/scripts/prune-musl-packages.ts +146 -0
- package/templates/.github/workflows/codeql.yml +10 -1
- package/templates/Dockerfile +13 -4
- package/templates/_.dockerignore +3 -5
- package/templates/_.gitignore +4 -4
- package/templates/package.json +4 -4
- package/templates/server.json +7 -21
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Chrome DevTools Protocol client over `--remote-debugging-pipe`. The
|
|
3
|
+
* browser reads commands from its fd 3 and writes responses and events to its fd 4, each
|
|
4
|
+
* message one JSON text ended by a NUL byte. No DevTools port is opened, and Chromium
|
|
5
|
+
* exits when fd 3 closes, so a harness that dies takes its browser with it.
|
|
6
|
+
*
|
|
7
|
+
* Sessions are flat: a command for a target carries the `sessionId` from
|
|
8
|
+
* `Target.attachToTarget({ flatten: true })` or `Target.setAutoAttach({ flatten: true })`,
|
|
9
|
+
* and events arrive on one stream tagged with theirs. Events are dispatched synchronously
|
|
10
|
+
* in arrival order, so subscribe before sending the command whose events you need.
|
|
11
|
+
* @module src/testing/apps/cdp-pipe
|
|
12
|
+
*/
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
/** A command that failed; an aborted command rejects with its signal's reason instead. */
|
|
15
|
+
export class CdpError extends Error {
|
|
16
|
+
name = 'CdpError';
|
|
17
|
+
kind;
|
|
18
|
+
method;
|
|
19
|
+
code;
|
|
20
|
+
constructor(kind, method, message, code) {
|
|
21
|
+
super(`${method}: ${message}`);
|
|
22
|
+
this.kind = kind;
|
|
23
|
+
this.method = method;
|
|
24
|
+
this.code = code;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** A CDP connection over a pair of streams: `input` from the browser, `output` to it. */
|
|
28
|
+
export class CdpPipe {
|
|
29
|
+
#output;
|
|
30
|
+
#pending = new Map();
|
|
31
|
+
#listeners = new Map();
|
|
32
|
+
#closed = Promise.withResolvers();
|
|
33
|
+
#nextId = 1;
|
|
34
|
+
#isClosed = false;
|
|
35
|
+
#closeReason = '';
|
|
36
|
+
#partial = [];
|
|
37
|
+
constructor(input, output) {
|
|
38
|
+
this.#output = output;
|
|
39
|
+
input.on('data', (chunk) => this.#receive(chunk));
|
|
40
|
+
input.on('end', () => this.close('the browser closed the DevTools pipe'));
|
|
41
|
+
input.on('error', (err) => this.close(`the DevTools pipe failed: ${err.message}`));
|
|
42
|
+
output.on('error', (err) => this.close(`the DevTools pipe failed: ${err.message}`));
|
|
43
|
+
}
|
|
44
|
+
/** Resolves once the pipe has closed, from either end. */
|
|
45
|
+
get closed() {
|
|
46
|
+
return this.#closed.promise;
|
|
47
|
+
}
|
|
48
|
+
get isClosed() {
|
|
49
|
+
return this.#isClosed;
|
|
50
|
+
}
|
|
51
|
+
/** Send one command and resolve with its result. */
|
|
52
|
+
send(method, params = {}, options = {}) {
|
|
53
|
+
const { sessionId, signal } = options;
|
|
54
|
+
if (signal?.aborted)
|
|
55
|
+
return Promise.reject(signal.reason);
|
|
56
|
+
if (this.#isClosed)
|
|
57
|
+
return Promise.reject(new CdpError('closed', method, this.#closeReason));
|
|
58
|
+
const id = this.#nextId++;
|
|
59
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
60
|
+
const onAbort = () => {
|
|
61
|
+
this.#pending.delete(id);
|
|
62
|
+
reject(signal?.reason);
|
|
63
|
+
};
|
|
64
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
65
|
+
this.#pending.set(id, {
|
|
66
|
+
method,
|
|
67
|
+
sessionId,
|
|
68
|
+
resolve: resolve,
|
|
69
|
+
reject,
|
|
70
|
+
release: () => signal?.removeEventListener('abort', onAbort),
|
|
71
|
+
});
|
|
72
|
+
const message = { id, method, params, ...(sessionId !== undefined && { sessionId }) };
|
|
73
|
+
this.#output.write(`${JSON.stringify(message)}\0`);
|
|
74
|
+
return promise;
|
|
75
|
+
}
|
|
76
|
+
/** Call `listener` for every `method` event until the returned function is called. */
|
|
77
|
+
on(method, listener) {
|
|
78
|
+
let listeners = this.#listeners.get(method);
|
|
79
|
+
if (!listeners) {
|
|
80
|
+
listeners = new Set();
|
|
81
|
+
this.#listeners.set(method, listeners);
|
|
82
|
+
}
|
|
83
|
+
const entry = listener;
|
|
84
|
+
listeners.add(entry);
|
|
85
|
+
return () => listeners.delete(entry);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* End the pipe to the browser (Chromium exits on reading EOF) and reject every pending
|
|
89
|
+
* command as `closed`. Idempotent.
|
|
90
|
+
*/
|
|
91
|
+
close(reason = 'the DevTools pipe was closed') {
|
|
92
|
+
if (this.#isClosed)
|
|
93
|
+
return;
|
|
94
|
+
this.#isClosed = true;
|
|
95
|
+
this.#closeReason = reason;
|
|
96
|
+
this.#partial = [];
|
|
97
|
+
if (!this.#output.writableEnded && !this.#output.destroyed)
|
|
98
|
+
this.#output.end();
|
|
99
|
+
for (const pending of this.#pending.values()) {
|
|
100
|
+
pending.release();
|
|
101
|
+
pending.reject(new CdpError('closed', pending.method, reason));
|
|
102
|
+
}
|
|
103
|
+
this.#pending.clear();
|
|
104
|
+
this.#closed.resolve();
|
|
105
|
+
}
|
|
106
|
+
#receive(chunk) {
|
|
107
|
+
let start = 0;
|
|
108
|
+
for (let end = chunk.indexOf(0); end !== -1; end = chunk.indexOf(0, start)) {
|
|
109
|
+
if (this.#isClosed)
|
|
110
|
+
return;
|
|
111
|
+
this.#partial.push(chunk.subarray(start, end));
|
|
112
|
+
const text = Buffer.concat(this.#partial).toString('utf8');
|
|
113
|
+
this.#partial = [];
|
|
114
|
+
this.#dispatch(text);
|
|
115
|
+
start = end + 1;
|
|
116
|
+
}
|
|
117
|
+
if (start < chunk.length && !this.#isClosed) {
|
|
118
|
+
this.#partial.push(Buffer.from(chunk.subarray(start)));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
#dispatch(text) {
|
|
122
|
+
let message;
|
|
123
|
+
try {
|
|
124
|
+
message = JSON.parse(text);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
this.close(`the browser sent a message that is not JSON: ${text.slice(0, 80)}`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (message.id !== undefined) {
|
|
131
|
+
this.#settle(message.id, message);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (message.method === undefined)
|
|
135
|
+
return;
|
|
136
|
+
const params = message.params ?? {};
|
|
137
|
+
if (message.method === 'Target.detachedFromTarget')
|
|
138
|
+
this.#detach(params.sessionId);
|
|
139
|
+
for (const listener of this.#listeners.get(message.method) ?? []) {
|
|
140
|
+
listener(params, message.sessionId);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
#settle(id, message) {
|
|
144
|
+
const pending = this.#pending.get(id);
|
|
145
|
+
if (!pending)
|
|
146
|
+
return;
|
|
147
|
+
this.#pending.delete(id);
|
|
148
|
+
pending.release();
|
|
149
|
+
const { error } = message;
|
|
150
|
+
if (error) {
|
|
151
|
+
const detail = error.data ? `${error.message} (${error.data})` : error.message;
|
|
152
|
+
pending.reject(new CdpError('protocol', pending.method, detail, error.code));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
pending.resolve(message.result ?? {});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
#detach(sessionId) {
|
|
159
|
+
for (const [id, pending] of this.#pending) {
|
|
160
|
+
if (pending.sessionId !== sessionId)
|
|
161
|
+
continue;
|
|
162
|
+
this.#pending.delete(id);
|
|
163
|
+
pending.release();
|
|
164
|
+
pending.reject(new CdpError('detached', pending.method, `session ${String(sessionId)} detached`));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const STDERR_TAIL_CHARS = 8 * 1024;
|
|
169
|
+
/**
|
|
170
|
+
* Spawn `executable` with a CDP pipe on its fds 3 (commands in) and 4 (messages out).
|
|
171
|
+
* Rejects with the spawn error (`ENOENT`, `EACCES`, ...) when the process cannot start.
|
|
172
|
+
* Stdin and stdout are discarded.
|
|
173
|
+
*/
|
|
174
|
+
export async function spawnPiped(executable, args) {
|
|
175
|
+
const child = spawn(executable, args, {
|
|
176
|
+
stdio: ['ignore', 'ignore', 'pipe', 'pipe', 'pipe'],
|
|
177
|
+
});
|
|
178
|
+
const exited = new Promise((resolve) => child.once('exit', (code, signal) => resolve({ code, signal })));
|
|
179
|
+
await new Promise((resolve, reject) => {
|
|
180
|
+
child.once('spawn', resolve);
|
|
181
|
+
child.once('error', reject);
|
|
182
|
+
});
|
|
183
|
+
const cdp = new CdpPipe(child.stdio[4], child.stdio[3]);
|
|
184
|
+
void exited.then(({ code, signal }) => cdp.close(`the browser exited ${signal ? `on ${signal}` : `with code ${code}`}`));
|
|
185
|
+
let tail = '';
|
|
186
|
+
child.stderr?.setEncoding('utf8').on('data', (text) => {
|
|
187
|
+
tail = (tail + text).slice(-STDERR_TAIL_CHARS);
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
pid: child.pid,
|
|
191
|
+
cdp,
|
|
192
|
+
exited,
|
|
193
|
+
stderrTail: () => tail,
|
|
194
|
+
kill: () => {
|
|
195
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
196
|
+
child.kill('SIGKILL');
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=cdp-pipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp-pipe.js","sourceRoot":"","sources":["../../../src/testing/apps/cdp-pipe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Builds the `Content-Security-Policy` header the sandbox applies to an MCP
|
|
3
|
+
* Apps view, from the resource's `_meta.ui.csp`, as the MCP Apps specification prescribes
|
|
4
|
+
* ("Content Security Policy Enforcement"; "Restrictive Default" when no metadata is given).
|
|
5
|
+
* `eval` stays blocked: no `'unsafe-eval'` is ever added.
|
|
6
|
+
* @module src/testing/apps/csp
|
|
7
|
+
*/
|
|
8
|
+
/** The domain lists of a resource's `_meta.ui.csp`. */
|
|
9
|
+
export interface AppCspMetadata {
|
|
10
|
+
baseUriDomains?: string[];
|
|
11
|
+
connectDomains?: string[];
|
|
12
|
+
frameDomains?: string[];
|
|
13
|
+
resourceDomains?: string[];
|
|
14
|
+
}
|
|
15
|
+
/** The CSP header value for a view whose resource declared `csp` (or nothing). */
|
|
16
|
+
export declare function buildCsp(csp: AppCspMetadata | undefined): string;
|
|
17
|
+
//# sourceMappingURL=csp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../../../src/testing/apps/csp.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC7B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAiBD,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,SAAS,GAAG,MAAM,CA4BhE"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Builds the `Content-Security-Policy` header the sandbox applies to an MCP
|
|
3
|
+
* Apps view, from the resource's `_meta.ui.csp`, as the MCP Apps specification prescribes
|
|
4
|
+
* ("Content Security Policy Enforcement"; "Restrictive Default" when no metadata is given).
|
|
5
|
+
* `eval` stays blocked: no `'unsafe-eval'` is ever added.
|
|
6
|
+
* @module src/testing/apps/csp
|
|
7
|
+
*/
|
|
8
|
+
import { validationError } from '../../types-global/errors.js';
|
|
9
|
+
const RESTRICTIVE_DEFAULT = [
|
|
10
|
+
"default-src 'none'",
|
|
11
|
+
"script-src 'self' 'unsafe-inline'",
|
|
12
|
+
"style-src 'self' 'unsafe-inline'",
|
|
13
|
+
"img-src 'self' data:",
|
|
14
|
+
"media-src 'self' data:",
|
|
15
|
+
"connect-src 'none'",
|
|
16
|
+
"frame-src 'none'",
|
|
17
|
+
"object-src 'none'",
|
|
18
|
+
"base-uri 'self'",
|
|
19
|
+
].join('; ');
|
|
20
|
+
/** Characters that would let a domain entry end its directive or add a source keyword. */
|
|
21
|
+
const UNSAFE_ENTRY = /[;\r\n'"\s,]/;
|
|
22
|
+
/** The CSP header value for a view whose resource declared `csp` (or nothing). */
|
|
23
|
+
export function buildCsp(csp) {
|
|
24
|
+
if (!csp)
|
|
25
|
+
return RESTRICTIVE_DEFAULT;
|
|
26
|
+
const list = (field) => {
|
|
27
|
+
const entries = csp[field] ?? [];
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
if (typeof entry !== 'string' || entry === '' || UNSAFE_ENTRY.test(entry)) {
|
|
30
|
+
throw validationError(`The resource's _meta.ui.csp.${field} holds ${JSON.stringify(entry)}, which is not a plain origin. Remove separators, quotes, and whitespace from CSP domain entries.`, { reason: 'invalid_csp_entry', field });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return entries.join(' ');
|
|
34
|
+
};
|
|
35
|
+
const resources = list('resourceDomains');
|
|
36
|
+
const withSources = (head, sources) => (sources ? `${head} ${sources}` : head);
|
|
37
|
+
return [
|
|
38
|
+
"default-src 'none'",
|
|
39
|
+
withSources("script-src 'self' 'unsafe-inline'", resources),
|
|
40
|
+
withSources("style-src 'self' 'unsafe-inline'", resources),
|
|
41
|
+
withSources("connect-src 'self'", list('connectDomains')),
|
|
42
|
+
withSources("img-src 'self' data:", resources),
|
|
43
|
+
withSources("font-src 'self'", resources),
|
|
44
|
+
withSources("media-src 'self' data:", resources),
|
|
45
|
+
`frame-src ${list('frameDomains') || "'none'"}`,
|
|
46
|
+
"object-src 'none'",
|
|
47
|
+
`base-uri ${list('baseUriDomains') || "'self'"}`,
|
|
48
|
+
].join('; ');
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=csp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.js","sourceRoot":"","sources":["../../../src/testing/apps/csp.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAU3D,MAAM,mBAAmB,GAAG;IAC1B,oBAAoB;IACpB,mCAAmC;IACnC,kCAAkC;IAClC,sBAAsB;IACtB,wBAAwB;IACxB,oBAAoB;IACpB,kBAAkB;IAClB,mBAAmB;IACnB,iBAAiB;CAClB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,0FAA0F;AAC1F,MAAM,YAAY,GAAG,cAAc,CAAC;AAEpC,kFAAkF;AAClF,MAAM,UAAU,QAAQ,CAAC,GAA+B;IACtD,IAAI,CAAC,GAAG;QAAE,OAAO,mBAAmB,CAAC;IACrC,MAAM,IAAI,GAAG,CAAC,KAA2B,EAAU,EAAE;QACnD,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACjC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1E,MAAM,eAAe,CACnB,+BAA+B,KAAK,UAAU,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,mGAAmG,EACtK,EAAE,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,CACvC,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC/F,OAAO;QACL,oBAAoB;QACpB,WAAW,CAAC,mCAAmC,EAAE,SAAS,CAAC;QAC3D,WAAW,CAAC,kCAAkC,EAAE,SAAS,CAAC;QAC1D,WAAW,CAAC,oBAAoB,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACzD,WAAW,CAAC,sBAAsB,EAAE,SAAS,CAAC;QAC9C,WAAW,CAAC,iBAAiB,EAAE,SAAS,CAAC;QACzC,WAAW,CAAC,wBAAwB,EAAE,SAAS,CAAC;QAChD,aAAa,IAAI,CAAC,cAAc,CAAC,IAAI,QAAQ,EAAE;QAC/C,mBAAmB;QACnB,YAAY,IAAI,CAAC,gBAAgB,CAAC,IAAI,QAAQ,EAAE;KACjD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The two loopback origins of the headless MCP Apps host. The host origin
|
|
3
|
+
* serves a page holding the sandbox iframe; the sandbox origin serves the sandbox proxy
|
|
4
|
+
* page with the view's CSP as an HTTP header. The proxy announces
|
|
5
|
+
* `ui/notifications/sandbox-proxy-ready`; on `ui/notifications/sandbox-resource-ready` it
|
|
6
|
+
* creates the inner view iframe and writes the view HTML into it (an about:blank child
|
|
7
|
+
* inherits the proxy's origin and CSP); it relays every other message both ways except a
|
|
8
|
+
* `ui/notifications/sandbox-*` one, a name the spec reserves for host and proxy.
|
|
9
|
+
*
|
|
10
|
+
* Both iframes carry the view's permissions-policy `allow` value, set before each frame
|
|
11
|
+
* loads: a frame can only delegate a feature its own frame was granted, and writing the
|
|
12
|
+
* view into the inner frame is not a navigation, so its policy is fixed when it is created.
|
|
13
|
+
*
|
|
14
|
+
* The host-side relay is not part of the host page: it runs in an isolated world
|
|
15
|
+
* ({@link HOST_WORLD}) installed over CDP, and reports through a binding only that world
|
|
16
|
+
* can see, so the view cannot reach the channel to Node.
|
|
17
|
+
* @module src/testing/apps/host-pages
|
|
18
|
+
*/
|
|
19
|
+
/** The isolated world the host relay runs in. */
|
|
20
|
+
export declare const HOST_WORLD = "__mcpAppsHost";
|
|
21
|
+
/** Binding the relay calls with each message from the sandbox, as JSON. */
|
|
22
|
+
export declare const RECEIVE_BINDING = "__mcpAppsHostReceive";
|
|
23
|
+
/** Function the relay defines in its world; Node calls it with each message for the view. */
|
|
24
|
+
export declare const SEND_FUNCTION = "__mcpAppsHostSend";
|
|
25
|
+
/** Function the relay defines to set the sandbox iframe's height in CSS pixels. */
|
|
26
|
+
export declare const RESIZE_FUNCTION = "__mcpAppsHostResize";
|
|
27
|
+
/** The id of the sandbox iframe in the host page. */
|
|
28
|
+
export declare const SANDBOX_FRAME_ID = "mcp-apps-sandbox";
|
|
29
|
+
/** Both origins, listening on 127.0.0.1. */
|
|
30
|
+
export interface HostPages {
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
readonly hostUrl: string;
|
|
33
|
+
/** The relay script, installed in {@link HOST_WORLD} before the host page loads. */
|
|
34
|
+
readonly relayScript: string;
|
|
35
|
+
readonly sandboxOrigin: string;
|
|
36
|
+
}
|
|
37
|
+
/** Layout and background of the host page, and the view's permissions. */
|
|
38
|
+
export interface HostPageOptions {
|
|
39
|
+
/** The iframe `allow` value for the resource's `_meta.ui.permissions`; empty for none. */
|
|
40
|
+
allow: string;
|
|
41
|
+
background: string;
|
|
42
|
+
height: number;
|
|
43
|
+
width: number;
|
|
44
|
+
}
|
|
45
|
+
/** Start both origins. `csp` is sent with the sandbox page. */
|
|
46
|
+
export declare function startHostPages(csp: string, options: HostPageOptions): Promise<HostPages>;
|
|
47
|
+
//# sourceMappingURL=host-pages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-pages.d.ts","sourceRoot":"","sources":["../../../src/testing/apps/host-pages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,iDAAiD;AACjD,eAAO,MAAM,UAAU,kBAAkB,CAAC;AAC1C,2EAA2E;AAC3E,eAAO,MAAM,eAAe,yBAAyB,CAAC;AACtD,6FAA6F;AAC7F,eAAO,MAAM,aAAa,sBAAsB,CAAC;AACjD,mFAAmF;AACnF,eAAO,MAAM,eAAe,wBAAwB,CAAC;AACrD,qDAAqD;AACrD,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD,4CAA4C;AAC5C,MAAM,WAAW,SAAS;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,+DAA+D;AAC/D,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAqB9F"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The two loopback origins of the headless MCP Apps host. The host origin
|
|
3
|
+
* serves a page holding the sandbox iframe; the sandbox origin serves the sandbox proxy
|
|
4
|
+
* page with the view's CSP as an HTTP header. The proxy announces
|
|
5
|
+
* `ui/notifications/sandbox-proxy-ready`; on `ui/notifications/sandbox-resource-ready` it
|
|
6
|
+
* creates the inner view iframe and writes the view HTML into it (an about:blank child
|
|
7
|
+
* inherits the proxy's origin and CSP); it relays every other message both ways except a
|
|
8
|
+
* `ui/notifications/sandbox-*` one, a name the spec reserves for host and proxy.
|
|
9
|
+
*
|
|
10
|
+
* Both iframes carry the view's permissions-policy `allow` value, set before each frame
|
|
11
|
+
* loads: a frame can only delegate a feature its own frame was granted, and writing the
|
|
12
|
+
* view into the inner frame is not a navigation, so its policy is fixed when it is created.
|
|
13
|
+
*
|
|
14
|
+
* The host-side relay is not part of the host page: it runs in an isolated world
|
|
15
|
+
* ({@link HOST_WORLD}) installed over CDP, and reports through a binding only that world
|
|
16
|
+
* can see, so the view cannot reach the channel to Node.
|
|
17
|
+
* @module src/testing/apps/host-pages
|
|
18
|
+
*/
|
|
19
|
+
import http from 'node:http';
|
|
20
|
+
/** The isolated world the host relay runs in. */
|
|
21
|
+
export const HOST_WORLD = '__mcpAppsHost';
|
|
22
|
+
/** Binding the relay calls with each message from the sandbox, as JSON. */
|
|
23
|
+
export const RECEIVE_BINDING = '__mcpAppsHostReceive';
|
|
24
|
+
/** Function the relay defines in its world; Node calls it with each message for the view. */
|
|
25
|
+
export const SEND_FUNCTION = '__mcpAppsHostSend';
|
|
26
|
+
/** Function the relay defines to set the sandbox iframe's height in CSS pixels. */
|
|
27
|
+
export const RESIZE_FUNCTION = '__mcpAppsHostResize';
|
|
28
|
+
/** The id of the sandbox iframe in the host page. */
|
|
29
|
+
export const SANDBOX_FRAME_ID = 'mcp-apps-sandbox';
|
|
30
|
+
/** Start both origins. `csp` is sent with the sandbox page. */
|
|
31
|
+
export async function startHostPages(csp, options) {
|
|
32
|
+
let sandboxOrigin = '';
|
|
33
|
+
let hostOrigin = '';
|
|
34
|
+
const host = await listen((req, res) => {
|
|
35
|
+
if (req.url !== '/')
|
|
36
|
+
return notFound(res);
|
|
37
|
+
send(res, hostPage(sandboxOrigin, options), {});
|
|
38
|
+
});
|
|
39
|
+
const sandbox = await listen((req, res) => {
|
|
40
|
+
if (req.url !== '/sandbox.html')
|
|
41
|
+
return notFound(res);
|
|
42
|
+
send(res, sandboxPage(hostOrigin, options.allow), { 'content-security-policy': csp });
|
|
43
|
+
});
|
|
44
|
+
hostOrigin = `http://127.0.0.1:${host.address().port}`;
|
|
45
|
+
sandboxOrigin = `http://127.0.0.1:${sandbox.address().port}`;
|
|
46
|
+
return {
|
|
47
|
+
hostUrl: `${hostOrigin}/`,
|
|
48
|
+
sandboxOrigin,
|
|
49
|
+
relayScript: relayScript(sandboxOrigin),
|
|
50
|
+
close: async () => {
|
|
51
|
+
await Promise.all([stop(host), stop(sandbox)]);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function listen(handler) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const server = http.createServer(handler);
|
|
58
|
+
server.once('error', reject);
|
|
59
|
+
server.listen(0, '127.0.0.1', () => resolve(server));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function stop(server) {
|
|
63
|
+
server.closeAllConnections();
|
|
64
|
+
return new Promise((resolve) => server.close(() => resolve()));
|
|
65
|
+
}
|
|
66
|
+
function send(res, body, headers) {
|
|
67
|
+
res.writeHead(200, {
|
|
68
|
+
'content-type': 'text/html; charset=utf-8',
|
|
69
|
+
'cache-control': 'no-store',
|
|
70
|
+
...headers,
|
|
71
|
+
});
|
|
72
|
+
res.end(body);
|
|
73
|
+
}
|
|
74
|
+
function notFound(res) {
|
|
75
|
+
res.writeHead(404, { 'content-type': 'text/plain' });
|
|
76
|
+
res.end('not found');
|
|
77
|
+
}
|
|
78
|
+
function hostPage(sandboxOrigin, { allow, background, height, width }) {
|
|
79
|
+
return `<!doctype html>
|
|
80
|
+
<html><head><meta charset="utf-8"><title>MCP Apps host</title>
|
|
81
|
+
<style>html,body{margin:0;padding:0;background:${background}}iframe{display:block;border:0}</style>
|
|
82
|
+
</head><body>
|
|
83
|
+
<iframe id="${SANDBOX_FRAME_ID}" src="${sandboxOrigin}/sandbox.html" sandbox="allow-scripts allow-same-origin allow-forms"${allow ? ` allow="${allow}"` : ''} style="width:${width}px;height:${height}px"></iframe>
|
|
84
|
+
</body></html>`;
|
|
85
|
+
}
|
|
86
|
+
function sandboxPage(hostOrigin, allow) {
|
|
87
|
+
return `<!doctype html>
|
|
88
|
+
<html><head><meta charset="utf-8"><style>html,body{margin:0;padding:0;height:100%;overflow:hidden}iframe{display:block;border:0;width:100%;height:100%}</style></head>
|
|
89
|
+
<body><script>
|
|
90
|
+
(() => {
|
|
91
|
+
const HOST_ORIGIN = ${JSON.stringify(hostOrigin)};
|
|
92
|
+
const ALLOW = ${JSON.stringify(allow)};
|
|
93
|
+
const reserved = (data) => typeof data?.method === 'string' && data.method.startsWith('ui/notifications/sandbox-');
|
|
94
|
+
let inner;
|
|
95
|
+
window.addEventListener('message', (event) => {
|
|
96
|
+
const data = event.data;
|
|
97
|
+
if (event.source === window.parent) {
|
|
98
|
+
if (event.origin !== HOST_ORIGIN) return;
|
|
99
|
+
if (data?.method === 'ui/notifications/sandbox-resource-ready') {
|
|
100
|
+
inner = document.createElement('iframe');
|
|
101
|
+
inner.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms');
|
|
102
|
+
if (ALLOW) inner.setAttribute('allow', ALLOW);
|
|
103
|
+
document.body.appendChild(inner);
|
|
104
|
+
const doc = inner.contentDocument;
|
|
105
|
+
doc.open();
|
|
106
|
+
doc.write(String(data.params.html));
|
|
107
|
+
doc.close();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (!reserved(data) && inner?.contentWindow) inner.contentWindow.postMessage(data, '*');
|
|
111
|
+
} else if (inner && event.source === inner.contentWindow) {
|
|
112
|
+
if (event.origin !== location.origin || reserved(data)) return;
|
|
113
|
+
window.parent.postMessage(data, HOST_ORIGIN);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: 'ui/notifications/sandbox-proxy-ready', params: {} }, HOST_ORIGIN);
|
|
117
|
+
})();
|
|
118
|
+
</script></body></html>`;
|
|
119
|
+
}
|
|
120
|
+
function relayScript(sandboxOrigin) {
|
|
121
|
+
return `(() => {
|
|
122
|
+
if (window.top !== window) return;
|
|
123
|
+
const SANDBOX_ORIGIN = ${JSON.stringify(sandboxOrigin)};
|
|
124
|
+
const frame = () => document.getElementById(${JSON.stringify(SANDBOX_FRAME_ID)});
|
|
125
|
+
window.addEventListener('message', (event) => {
|
|
126
|
+
const sandbox = frame();
|
|
127
|
+
if (!sandbox || event.source !== sandbox.contentWindow || event.origin !== SANDBOX_ORIGIN) return;
|
|
128
|
+
globalThis[${JSON.stringify(RECEIVE_BINDING)}](JSON.stringify(event.data));
|
|
129
|
+
});
|
|
130
|
+
globalThis[${JSON.stringify(SEND_FUNCTION)}] = (json) => {
|
|
131
|
+
frame().contentWindow.postMessage(JSON.parse(json), SANDBOX_ORIGIN);
|
|
132
|
+
};
|
|
133
|
+
globalThis[${JSON.stringify(RESIZE_FUNCTION)}] = (height) => {
|
|
134
|
+
frame().style.height = height + 'px';
|
|
135
|
+
};
|
|
136
|
+
})();`;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=host-pages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-pages.js","sourceRoot":"","sources":["../../../src/testing/apps/host-pages.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Headless MCP Apps host. `renderAppTool` connects to a server as a client
|
|
3
|
+
* advertising `io.modelcontextprotocol/ui`, calls a tool, loads its `ui://` resource into
|
|
4
|
+
* headless `chrome-headless-shell` inside the double-iframe sandbox and CSP the MCP Apps
|
|
5
|
+
* specification prescribes, plays the host half of the protocol through the official
|
|
6
|
+
* `AppBridge`, runs scripted steps against the view, and returns a report.
|
|
7
|
+
*
|
|
8
|
+
* Import from `@cyanheads/mcp-ts-core/testing/apps`. The optional peers
|
|
9
|
+
* `@modelcontextprotocol/client` and `@modelcontextprotocol/ext-apps` are loaded on first
|
|
10
|
+
* use; importing this subpath without them succeeds.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { renderAppTool } from '@cyanheads/mcp-ts-core/testing/apps';
|
|
15
|
+
*
|
|
16
|
+
* const run = await renderAppTool({
|
|
17
|
+
* server: { command: 'bun', args: ['run', 'dist/index.js'] },
|
|
18
|
+
* tool: 'my_app_tool',
|
|
19
|
+
* arguments: { query: 'probe' },
|
|
20
|
+
* steps: [{ click: '#action-btn' }, { screenshot: 'after-click' }],
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
* @module src/testing/apps
|
|
24
|
+
*/
|
|
25
|
+
/** How to reach the server: a stdio command, or a Streamable HTTP endpoint. */
|
|
26
|
+
export type AppServerTarget = {
|
|
27
|
+
args?: string[];
|
|
28
|
+
command: string;
|
|
29
|
+
cwd?: string;
|
|
30
|
+
/** The server's environment. Default: the MCP client's inherited-variable allowlist. */
|
|
31
|
+
env?: Record<string, string>;
|
|
32
|
+
} | {
|
|
33
|
+
headers?: Record<string, string>;
|
|
34
|
+
url: string;
|
|
35
|
+
};
|
|
36
|
+
/** One scripted action against the view, run after the tool result is delivered. */
|
|
37
|
+
export type AppRenderStep = {
|
|
38
|
+
click: string;
|
|
39
|
+
} | {
|
|
40
|
+
fill: string;
|
|
41
|
+
value: string;
|
|
42
|
+
} | {
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
waitFor: string;
|
|
45
|
+
} | {
|
|
46
|
+
evaluate: string;
|
|
47
|
+
} | {
|
|
48
|
+
screenshot: string;
|
|
49
|
+
};
|
|
50
|
+
/** Display modes a host can offer a view. */
|
|
51
|
+
export type AppDisplayMode = 'inline' | 'fullscreen' | 'pip';
|
|
52
|
+
/** The host the view sees. */
|
|
53
|
+
export interface AppHostOptions {
|
|
54
|
+
/** Display modes the host accepts on `ui/request-display-mode`. Default: `['inline']`. */
|
|
55
|
+
availableDisplayModes?: AppDisplayMode[];
|
|
56
|
+
/** The initial display mode. Default: `'inline'`. */
|
|
57
|
+
displayMode?: AppDisplayMode;
|
|
58
|
+
/** Fixed container height in CSS pixels. Default: follows the view's size-changed reports. */
|
|
59
|
+
height?: number;
|
|
60
|
+
locale?: string;
|
|
61
|
+
/** Send `tool-input-partial` notifications (growing prefixes of the arguments) before `tool-input`. */
|
|
62
|
+
streamInput?: boolean;
|
|
63
|
+
/** CSS custom properties passed as `styles.variables`. */
|
|
64
|
+
styles?: Record<string, string>;
|
|
65
|
+
/** Default: `'light'`. */
|
|
66
|
+
theme?: 'light' | 'dark';
|
|
67
|
+
timeZone?: string;
|
|
68
|
+
/** Container width in CSS pixels. Default: 720. */
|
|
69
|
+
width?: number;
|
|
70
|
+
}
|
|
71
|
+
/** Options for one render. */
|
|
72
|
+
export interface RenderAppToolOptions {
|
|
73
|
+
arguments?: Record<string, unknown>;
|
|
74
|
+
/** Path to a `chrome-headless-shell` executable; overrides `MCP_APPS_BROWSER_PATH` and the cache. */
|
|
75
|
+
browserPath?: string;
|
|
76
|
+
host?: AppHostOptions;
|
|
77
|
+
/** Where screenshots are written. Default: a new directory under the OS temp dir. */
|
|
78
|
+
outDir?: string;
|
|
79
|
+
server: AppServerTarget;
|
|
80
|
+
steps?: AppRenderStep[];
|
|
81
|
+
/** How long to wait for the view's `initialized` and for each step. Default: 15000 ms. */
|
|
82
|
+
timeoutMs?: number;
|
|
83
|
+
tool: string;
|
|
84
|
+
}
|
|
85
|
+
/** A message between the view and the host, as it crossed the sandbox. */
|
|
86
|
+
export interface AppRenderMessage {
|
|
87
|
+
/** Milliseconds since the run started. */
|
|
88
|
+
at: number;
|
|
89
|
+
direction: 'view-to-host' | 'host-to-view';
|
|
90
|
+
message: Record<string, unknown>;
|
|
91
|
+
}
|
|
92
|
+
/** An uncaught exception or console error. */
|
|
93
|
+
export interface AppRenderError {
|
|
94
|
+
column?: number;
|
|
95
|
+
/** The frame it came from: the view, the sandbox proxy, or the host page. */
|
|
96
|
+
frame: 'view' | 'sandbox' | 'host' | 'unknown';
|
|
97
|
+
line?: number;
|
|
98
|
+
message: string;
|
|
99
|
+
source: 'exception' | 'console';
|
|
100
|
+
url?: string;
|
|
101
|
+
}
|
|
102
|
+
/** A Content Security Policy violation in the sandbox or the view. */
|
|
103
|
+
export interface AppCspViolation {
|
|
104
|
+
/** The blocked URL, or `eval` / `inline` / `wasm-eval` for a blocked script evaluation. */
|
|
105
|
+
blockedURI: string;
|
|
106
|
+
directive: string;
|
|
107
|
+
line?: number;
|
|
108
|
+
sourceURL?: string;
|
|
109
|
+
violationType: string;
|
|
110
|
+
}
|
|
111
|
+
/** The outcome of one step. */
|
|
112
|
+
export interface AppRenderStepResult {
|
|
113
|
+
error?: string;
|
|
114
|
+
ok: boolean;
|
|
115
|
+
/** Path of the PNG a screenshot step wrote. */
|
|
116
|
+
screenshot?: string;
|
|
117
|
+
step: AppRenderStep;
|
|
118
|
+
/** The value an evaluate step returned. */
|
|
119
|
+
value?: unknown;
|
|
120
|
+
}
|
|
121
|
+
/** What one render observed. */
|
|
122
|
+
export interface AppRenderReport {
|
|
123
|
+
/** The view's `appInfo` from `ui/initialize`. */
|
|
124
|
+
appInfo?: {
|
|
125
|
+
name: string;
|
|
126
|
+
version: string;
|
|
127
|
+
};
|
|
128
|
+
arguments: Record<string, unknown>;
|
|
129
|
+
browser: {
|
|
130
|
+
executable: string;
|
|
131
|
+
pid: number;
|
|
132
|
+
profileDir: string;
|
|
133
|
+
};
|
|
134
|
+
/** The CSP header the sandbox applied to the view. */
|
|
135
|
+
csp: string;
|
|
136
|
+
cspViolations: AppCspViolation[];
|
|
137
|
+
durationMs: number;
|
|
138
|
+
errors: AppRenderError[];
|
|
139
|
+
/** A failure after the browser was reached, such as the view never initializing. */
|
|
140
|
+
failure?: string;
|
|
141
|
+
/** The view completed `ui/initialize`. */
|
|
142
|
+
initialized: boolean;
|
|
143
|
+
messages: AppRenderMessage[];
|
|
144
|
+
resourceUri: string;
|
|
145
|
+
screenshots: string[];
|
|
146
|
+
/** The last `ui/notifications/size-changed`. */
|
|
147
|
+
size?: {
|
|
148
|
+
height?: number;
|
|
149
|
+
width?: number;
|
|
150
|
+
};
|
|
151
|
+
steps: AppRenderStepResult[];
|
|
152
|
+
/** The view's rendered text after the steps. */
|
|
153
|
+
text: string;
|
|
154
|
+
tool: string;
|
|
155
|
+
/** Why the tool call failed at the protocol level; the view then received `tool-cancelled`. */
|
|
156
|
+
toolError?: string;
|
|
157
|
+
toolResult?: Record<string, unknown>;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Render an app tool's view in a headless MCP Apps host and report what happened.
|
|
161
|
+
* Resolves with the report whenever the run reached the browser, even when the view
|
|
162
|
+
* failed. Throws only on setup failures: a missing optional peer, no browser, the server
|
|
163
|
+
* unreachable, the tool missing, or its UI resource absent, unreadable, or declaring a
|
|
164
|
+
* `_meta.ui.csp` entry that is not a plain origin.
|
|
165
|
+
*/
|
|
166
|
+
export declare function renderAppTool(options: RenderAppToolOptions): Promise<AppRenderReport>;
|
|
167
|
+
//# sourceMappingURL=index.d.ts.map
|