mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
880 lines
27 KiB
JavaScript
880 lines
27 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { randomUUID, createHash } from "node:crypto";
|
|
|
|
const SCHEMA_DESCRIPTION =
|
|
"A2UI Component Schema — available components for generating UI surfaces. Use these component names and properties when creating A2UI operations.";
|
|
const BASIC_CATALOG = "https://a2ui.org/specification/v0_9/basic_catalog.json";
|
|
const schema = {
|
|
components: {
|
|
Column: { required: ["children"] },
|
|
Text: { required: ["text"] },
|
|
},
|
|
};
|
|
|
|
/** Create complete input without depending on a language's native model class. */
|
|
function input(extra = {}) {
|
|
return {
|
|
threadId: randomUUID(),
|
|
runId: randomUUID(),
|
|
messages: [{ id: randomUUID(), role: "user", content: "Show a card" }],
|
|
tools: [],
|
|
context: [],
|
|
state: {},
|
|
forwardedProps: {},
|
|
...extra,
|
|
};
|
|
}
|
|
|
|
/** Script an AG-UI tool stream with visible chunk boundaries. */
|
|
function toolStream(name, chunks, toolCallId = "render-call") {
|
|
return (request) => [
|
|
{ type: "RUN_STARTED", threadId: request.threadId, runId: request.runId },
|
|
{
|
|
type: "TOOL_CALL_START",
|
|
toolCallId,
|
|
toolCallName: name,
|
|
parentMessageId: "assistant-message",
|
|
},
|
|
...chunks.map((delta) => ({ type: "TOOL_CALL_ARGS", toolCallId, delta })),
|
|
{ type: "TOOL_CALL_END", toolCallId },
|
|
{ type: "RUN_FINISHED", threadId: request.threadId, runId: request.runId },
|
|
];
|
|
}
|
|
|
|
/** Await durable middleware output, not the immediate JSON run response. */
|
|
async function runAndWait(context, body = input()) {
|
|
assert.equal(
|
|
(await context.request("POST", "/agent/default/run", body)).status,
|
|
200,
|
|
);
|
|
await context.platform.waitFor(
|
|
() =>
|
|
context.platform.events.some(
|
|
(event) =>
|
|
event.runId === body.runId &&
|
|
["RUN_FINISHED", "RUN_ERROR"].includes(event.type),
|
|
),
|
|
10000,
|
|
);
|
|
return context.platform.events.filter((event) => event.runId === body.runId);
|
|
}
|
|
|
|
/** Point every driver at the same authenticated AIMock MCP server. */
|
|
function mcpConfiguration(platform) {
|
|
return {
|
|
mcpApps: {
|
|
servers: [
|
|
{
|
|
type: "http",
|
|
url: platform.mcpUrl,
|
|
serverId: "cards",
|
|
headers: { "x-fixture-auth": "mcp-fixture-token" },
|
|
},
|
|
],
|
|
},
|
|
};
|
|
}
|
|
|
|
export const uiCases = [
|
|
...["agent", "resource-proxy", "tool-proxy"].map((path) => ({
|
|
id: `mcp-apps.standard-mime-${path}`,
|
|
configuration: mcpConfiguration,
|
|
async run(context) {
|
|
context.platform.faults.agentEvents = toolStream("show_card", [
|
|
'{"title":"MIME contract"}',
|
|
]);
|
|
const proxy =
|
|
path === "resource-proxy"
|
|
? { method: "resources/read", params: { uri: "ui://fixture/card" } }
|
|
: {
|
|
method: "tools/call",
|
|
params: {
|
|
name: "show_card",
|
|
arguments: { title: "MIME contract" },
|
|
},
|
|
};
|
|
const events = await runAndWait(
|
|
context,
|
|
input(
|
|
path === "agent"
|
|
? {}
|
|
: {
|
|
forwardedProps: {
|
|
__proxiedMCPRequest: { serverId: "cards", ...proxy },
|
|
},
|
|
},
|
|
),
|
|
);
|
|
const requests = context.platform.requests.filter(
|
|
(request) => request.path === "/mcp",
|
|
);
|
|
const initializations = requests.filter(
|
|
(request) => request.body?.method === "initialize",
|
|
);
|
|
assert.ok(
|
|
initializations.length > 0,
|
|
"The test must observe MCP initialization",
|
|
);
|
|
for (const request of initializations) {
|
|
assert.ok(
|
|
request.body.params.capabilities?.extensions?.[
|
|
"io.modelcontextprotocol/ui"
|
|
]?.mimeTypes?.includes("text/html;profile=mcp-app"),
|
|
"Every MCP connection must advertise the January MCP Apps MIME type",
|
|
);
|
|
}
|
|
assert.ok(
|
|
requests.some(
|
|
(request) =>
|
|
request.body?.method ===
|
|
(path === "resource-proxy" ? "resources/read" : "tools/call"),
|
|
),
|
|
);
|
|
if (path === "agent") {
|
|
assert.ok(events.some((event) => event.activityType === "mcp-apps"));
|
|
} else {
|
|
assert.equal(context.platform.agentInputs.length, 0);
|
|
const result = events.find(
|
|
(event) => event.type === "RUN_FINISHED",
|
|
).result;
|
|
if (path === "resource-proxy") {
|
|
assert.equal(
|
|
result.contents[0].mimeType,
|
|
"text/html;profile=mcp-app",
|
|
);
|
|
assert.match(result.contents[0].text, /Fixture card/);
|
|
} else {
|
|
assert.equal(result.content[0].text, "Card: MIME contract");
|
|
}
|
|
}
|
|
},
|
|
})),
|
|
...["ambiguous", "explicit-first", "explicit-second", "unknown-id"].map(
|
|
(selection) => ({
|
|
id: `mcp-apps.server-selection-${selection}`,
|
|
configuration(platform) {
|
|
return {
|
|
mcpApps: {
|
|
servers: ["first", "second"].map((serverId) => ({
|
|
...mcpConfiguration(platform).mcpApps.servers[0],
|
|
serverId,
|
|
headers: {
|
|
"x-fixture-auth": "mcp-fixture-token",
|
|
"x-fixture-account": serverId,
|
|
},
|
|
})),
|
|
},
|
|
};
|
|
},
|
|
async run(context) {
|
|
const serverHash = createHash("md5")
|
|
.update(
|
|
JSON.stringify({ type: "http", url: context.platform.mcpUrl }),
|
|
)
|
|
.digest("hex");
|
|
const serverId =
|
|
selection === "ambiguous"
|
|
? undefined
|
|
: selection.replace("explicit-", "");
|
|
const events = await runAndWait(
|
|
context,
|
|
input({
|
|
forwardedProps: {
|
|
__proxiedMCPRequest: {
|
|
serverHash,
|
|
...(serverId ? { serverId } : {}),
|
|
method: "tools/call",
|
|
params: {
|
|
name: "show_card",
|
|
arguments: { title: "Account test" },
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
const requests = context.platform.requests.filter(
|
|
(request) => request.path === "/mcp",
|
|
);
|
|
const result = events.find(
|
|
(event) => event.type === "RUN_FINISHED",
|
|
).result;
|
|
if (selection === "ambiguous" || selection === "unknown-id") {
|
|
assert.equal(
|
|
requests.length,
|
|
0,
|
|
"Ambiguous hashes and unknown explicit IDs must fail before sending credentials",
|
|
);
|
|
assert.ok(result.error);
|
|
} else {
|
|
assert.deepEqual(context.platform.mcpCalls, [
|
|
{ title: "Account test" },
|
|
]);
|
|
assert.ok(requests.length > 0);
|
|
assert.ok(
|
|
requests.every(
|
|
(request) => request.headers["x-fixture-account"] === serverId,
|
|
),
|
|
"Explicit server ID must select its own credentials",
|
|
);
|
|
assert.equal(result.content[0].text, "Card: Account test");
|
|
}
|
|
assert.equal(context.platform.agentInputs.length, 0);
|
|
},
|
|
}),
|
|
),
|
|
...[
|
|
["omitted", undefined, true],
|
|
["model", ["model"], true],
|
|
["app", ["app"], false],
|
|
["both", ["app", "model"], true],
|
|
["empty", [], false],
|
|
].map(([label, visibility, modelVisible]) => ({
|
|
id: `mcp-apps.visibility-${label}`,
|
|
configuration: mcpConfiguration,
|
|
async run(context) {
|
|
context.platform.mcp.addTool({
|
|
name: "visibility_card",
|
|
description: "Visibility contract",
|
|
inputSchema: { type: "object", properties: {} },
|
|
_meta: {
|
|
ui: {
|
|
resourceUri: "ui://fixture/card",
|
|
...(visibility === undefined ? {} : { visibility }),
|
|
},
|
|
},
|
|
});
|
|
context.platform.mcp.onToolCall(
|
|
"visibility_card",
|
|
() => "Visible in app",
|
|
);
|
|
await runAndWait(context);
|
|
assert.equal(
|
|
context.platform.agentInputs[0].tools.some(
|
|
(tool) => tool.name === "visibility_card",
|
|
),
|
|
modelVisible,
|
|
"Explicit visibility must include model before a tool enters model input",
|
|
);
|
|
const events = await runAndWait(
|
|
context,
|
|
input({
|
|
forwardedProps: {
|
|
__proxiedMCPRequest: {
|
|
serverId: "cards",
|
|
method: "tools/call",
|
|
params: { name: "visibility_card", arguments: {} },
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.equal(
|
|
events.find((event) => event.type === "RUN_FINISHED").result.content[0]
|
|
.text,
|
|
"Visible in app",
|
|
);
|
|
assert.equal(
|
|
context.platform.agentInputs.length,
|
|
1,
|
|
"UI proxy must bypass the model",
|
|
);
|
|
},
|
|
})),
|
|
...["nested", "both"].map((format) => ({
|
|
id: `mcp-apps.current-metadata-${format}`,
|
|
configuration: mcpConfiguration,
|
|
async run(context) {
|
|
context.platform.mcp.addTool({
|
|
name: "current_card",
|
|
description: "Current MCP metadata",
|
|
inputSchema: { type: "object", properties: {} },
|
|
_meta: {
|
|
ui: { resourceUri: "ui://fixture/card" },
|
|
...(format === "both"
|
|
? { "ui/resourceUri": "ui://legacy-wrong" }
|
|
: {}),
|
|
},
|
|
});
|
|
context.platform.mcp.onToolCall("current_card", () => "Card");
|
|
context.platform.faults.agentEvents = toolStream(
|
|
"current_card",
|
|
["{}"],
|
|
"current-metadata-call",
|
|
);
|
|
const events = await runAndWait(context);
|
|
assert.ok(
|
|
context.platform.agentInputs[0].tools.some(
|
|
(tool) => tool.name === "current_card",
|
|
),
|
|
"current MCP metadata must discover the UI tool",
|
|
);
|
|
const activity = events.find(
|
|
(event) => event.activityType === "mcp-apps",
|
|
);
|
|
assert.equal(activity?.content.resourceUri, "ui://fixture/card");
|
|
},
|
|
})),
|
|
{
|
|
id: "a2ui.late-identifiers-preserve-painted-surface",
|
|
configuration: { a2ui: { enabled: true, injectA2UITool: true, schema } },
|
|
async run(context) {
|
|
context.platform.faults.agentChunkDelayMs = 15;
|
|
context.platform.faults.agentEvents = toolStream("render_a2ui", [
|
|
'{"components":[{"id":"root","component":"Column","children":["text"]},{"id":"text","component":"Text","text":"Hello"}]',
|
|
',"surfaceId":"late-surface"',
|
|
',"catalogId":"late-catalog","data":{"items":[{"name":"one"},',
|
|
'{"name":"two"}]}}',
|
|
]);
|
|
const events = await runAndWait(context);
|
|
const snapshots = events.filter(
|
|
(event) =>
|
|
event.activityType === "a2ui-surface" &&
|
|
event.content?.a2ui_operations,
|
|
);
|
|
assert.ok(
|
|
snapshots.length >= 2,
|
|
"Complete components and progressive data must produce snapshots",
|
|
);
|
|
const operations = snapshots.flatMap(
|
|
(event) => event.content.a2ui_operations,
|
|
);
|
|
const created = operations
|
|
.filter((operation) => operation.createSurface)
|
|
.map((operation) => operation.createSurface);
|
|
assert.equal(
|
|
new Set(created.map((surface) => surface.surfaceId)).size,
|
|
1,
|
|
"A late surfaceId must not replace a painted surface",
|
|
);
|
|
assert.equal(
|
|
new Set(created.map((surface) => surface.catalogId)).size,
|
|
1,
|
|
"A late catalogId must not replace a painted catalog",
|
|
);
|
|
const updates = operations
|
|
.filter((operation) => operation.updateDataModel)
|
|
.map((operation) => operation.updateDataModel);
|
|
assert.ok(
|
|
updates.length >= 2,
|
|
"Data must still stream after late identifiers",
|
|
);
|
|
assert.ok(
|
|
updates.every((update) => update.surfaceId === created[0].surfaceId),
|
|
);
|
|
assert.equal(updates.at(-1).value.items.length, 2);
|
|
},
|
|
},
|
|
...[
|
|
["self-cycle", [{ id: "root", component: "Column", children: ["root"] }]],
|
|
[
|
|
"two-node-cycle",
|
|
[
|
|
{ id: "root", component: "Column", children: ["child"] },
|
|
{ id: "child", component: "Column", children: ["root"] },
|
|
],
|
|
],
|
|
[
|
|
"duplicate-id",
|
|
[
|
|
{ id: "root", component: "Text", text: "One" },
|
|
{ id: "root", component: "Text", text: "Two" },
|
|
],
|
|
],
|
|
].map(([name, components]) => ({
|
|
id: `a2ui.reject-${name}`,
|
|
configuration: { a2ui: { enabled: true, injectA2UITool: true, schema } },
|
|
async run(context) {
|
|
context.platform.faults.agentEvents = toolStream("render_a2ui", [
|
|
JSON.stringify({ surfaceId: "card", components }),
|
|
]);
|
|
const events = await runAndWait(context);
|
|
assert.equal(
|
|
events.some((event) => event.content?.a2ui_operations),
|
|
false,
|
|
"Invalid component tree reached the browser",
|
|
);
|
|
assert.ok(
|
|
events.some(
|
|
(event) =>
|
|
event.content?.status === "retrying" &&
|
|
event.content.errors.length > 0,
|
|
),
|
|
);
|
|
},
|
|
})),
|
|
{
|
|
id: "a2ui.custom-tool-included-agent",
|
|
configuration: {
|
|
a2ui: {
|
|
enabled: true,
|
|
injectA2UITool: "custom_render",
|
|
agents: ["default"],
|
|
},
|
|
},
|
|
async run(context) {
|
|
context.platform.faults.agentEvents = toolStream("custom_render", [
|
|
'{"surfaceId":"card","components":[{"id":"root","component":"Text","text":"Hello"}]}',
|
|
]);
|
|
const events = await runAndWait(context);
|
|
const tools = context.platform.agentInputs[0].tools;
|
|
assert.equal(
|
|
tools.filter((tool) => tool.name === "custom_render").length,
|
|
1,
|
|
);
|
|
assert.equal(
|
|
tools.some((tool) => tool.name === "render_a2ui"),
|
|
false,
|
|
);
|
|
assert.ok(events.some((event) => event.content?.a2ui_operations));
|
|
},
|
|
},
|
|
{
|
|
id: "a2ui.atomic-components-and-progressive-data",
|
|
configuration: { a2ui: { enabled: true, injectA2UITool: true, schema } },
|
|
async run(context) {
|
|
const { platform } = context;
|
|
platform.faults.agentChunkDelayMs = 15;
|
|
platform.faults.agentEvents = toolStream("render_a2ui", [
|
|
'{"surfaceId":"card","components":[{"id":"root","component":"Column","children":["text"]},',
|
|
'{"id":"text","component":"Text","text":"Hello"}],"data":{"items":[{"name":"one"},',
|
|
'{"name":"two"}]}}',
|
|
]);
|
|
const events = await runAndWait(context);
|
|
assert.equal(
|
|
platform.agentInputs[0].tools.filter(
|
|
(tool) => tool.name === "render_a2ui",
|
|
).length,
|
|
1,
|
|
);
|
|
assert.ok(
|
|
platform.agentInputs[0].context.some(
|
|
(entry) => entry.description === SCHEMA_DESCRIPTION,
|
|
),
|
|
);
|
|
const activities = events.filter(
|
|
(event) => event.activityType === "a2ui-surface",
|
|
);
|
|
assert.equal(activities[0].content.status, "building");
|
|
assert.ok(
|
|
activities.every(
|
|
(event) =>
|
|
event.messageId === "a2ui-surface-render-call" &&
|
|
event.replace === true,
|
|
),
|
|
);
|
|
const painted = activities.filter(
|
|
(event) => event.content.a2ui_operations,
|
|
);
|
|
assert.ok(painted.length >= 2, "Missing progressive data snapshots");
|
|
for (const activity of painted) {
|
|
const components = activity.content.a2ui_operations.find(
|
|
(op) => op.updateComponents,
|
|
)?.updateComponents.components;
|
|
assert.equal(
|
|
components.length,
|
|
2,
|
|
"Partial component tree was painted",
|
|
);
|
|
assert.equal(
|
|
activity.content.a2ui_operations[0].createSurface.catalogId,
|
|
BASIC_CATALOG,
|
|
);
|
|
}
|
|
const firstPaintIndex = events.indexOf(painted[0]);
|
|
const firstArgsIndex = events.findIndex(
|
|
(event) => event.type === "TOOL_CALL_ARGS",
|
|
);
|
|
const closedArgsIndex = events.findIndex(
|
|
(event) =>
|
|
event.type === "TOOL_CALL_ARGS" && event.delta.includes('}],"data"'),
|
|
);
|
|
assert.ok(
|
|
firstPaintIndex > firstArgsIndex && firstPaintIndex < closedArgsIndex,
|
|
);
|
|
const sizes = painted.flatMap((event) =>
|
|
event.content.a2ui_operations
|
|
.filter((op) => op.updateDataModel)
|
|
.map((op) => op.updateDataModel.value.items.length),
|
|
);
|
|
assert.ok(sizes.includes(1) && sizes.includes(2));
|
|
const result = events.find(
|
|
(event) =>
|
|
event.type === "TOOL_CALL_RESULT" &&
|
|
event.toolCallId === "render-call",
|
|
);
|
|
assert.deepEqual(JSON.parse(result.content), { status: "rendered" });
|
|
assert.ok(
|
|
events.indexOf(result) <
|
|
events.findIndex((event) => event.type === "RUN_FINISHED"),
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "a2ui.action-history-and-catalog",
|
|
configuration: { a2ui: { enabled: true } },
|
|
async run(context) {
|
|
const action = {
|
|
name: "confirm",
|
|
surfaceId: "card",
|
|
sourceComponentId: "button",
|
|
context: { selected: 2 },
|
|
};
|
|
context.platform.faults.agentEvents = toolStream("render_a2ui", [
|
|
'{"surfaceId":"card","components":[{"id":"root","component":"Text","text":"Hello"}]}',
|
|
]);
|
|
const events = await runAndWait(
|
|
context,
|
|
input({
|
|
context: [
|
|
{
|
|
description: SCHEMA_DESCRIPTION,
|
|
value: JSON.stringify({
|
|
catalogId: "https://example.test/catalog",
|
|
components: {},
|
|
}),
|
|
},
|
|
],
|
|
forwardedProps: {
|
|
a2uiCatalogAvailable: true,
|
|
a2uiAction: { userAction: action },
|
|
},
|
|
}),
|
|
);
|
|
const messages = context.platform.agentInputs[0].messages;
|
|
const assistant = messages.find(
|
|
(message) => message.toolCalls?.[0]?.function.name === "log_a2ui_event",
|
|
);
|
|
assert.deepEqual(
|
|
JSON.parse(assistant.toolCalls[0].function.arguments),
|
|
action,
|
|
);
|
|
assert.ok(
|
|
messages.some(
|
|
(message) =>
|
|
message.role === "tool" &&
|
|
message.toolCallId === assistant.toolCalls[0].id &&
|
|
message.content ===
|
|
'User performed action "confirm" on surface "card" (component: button). Context: {"selected":2}',
|
|
),
|
|
);
|
|
const painted = events.find((event) => event.content?.a2ui_operations);
|
|
assert.equal(
|
|
painted.content.a2ui_operations[0].createSurface.catalogId,
|
|
"https://example.test/catalog",
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "a2ui.explicit-disable-wins-over-catalog",
|
|
configuration: { a2ui: { enabled: false, injectA2UITool: true } },
|
|
async run(context) {
|
|
context.platform.faults.agentEvents = toolStream("render_a2ui", [
|
|
'{"surfaceId":"card","components":[{"id":"root","component":"Text","text":"Hello"}]}',
|
|
]);
|
|
const events = await runAndWait(
|
|
context,
|
|
input({ forwardedProps: { a2uiCatalogAvailable: true } }),
|
|
);
|
|
assert.equal(
|
|
events.filter((event) => event.activityType === "a2ui-surface").length,
|
|
0,
|
|
);
|
|
assert.equal(
|
|
context.platform.agentInputs[0].tools.some(
|
|
(tool) => tool.name === "render_a2ui",
|
|
),
|
|
false,
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "a2ui.invalid-tree-does-not-paint",
|
|
configuration: { a2ui: { enabled: true, injectA2UITool: true, schema } },
|
|
async run(context) {
|
|
context.platform.faults.agentEvents = toolStream("render_a2ui", [
|
|
'{"surfaceId":"card","components":[{"id":"root","component":"Column","children":["missing"]}]}',
|
|
]);
|
|
const events = await runAndWait(context);
|
|
assert.equal(
|
|
events.some((event) => event.content?.a2ui_operations),
|
|
false,
|
|
);
|
|
const retry = events.find(
|
|
(event) => event.content?.status === "retrying",
|
|
);
|
|
assert.ok(
|
|
retry?.content.errors.some(
|
|
(error) => error.code === "unresolved_child",
|
|
),
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "a2ui.custom-tool-and-agent-scope",
|
|
configuration: {
|
|
a2ui: {
|
|
enabled: true,
|
|
injectA2UITool: "custom_render",
|
|
agents: ["another-agent"],
|
|
},
|
|
},
|
|
async run(context) {
|
|
const response = await context.request("GET", "/info");
|
|
assert.deepEqual(response.body.a2ui.agents, ["another-agent"]);
|
|
context.platform.faults.agentEvents = toolStream("custom_render", [
|
|
'{"surfaceId":"card","components":[{"id":"root","component":"Text","text":"Hello"}]}',
|
|
]);
|
|
const events = await runAndWait(context);
|
|
assert.equal(
|
|
events.some((event) => event.activityType === "a2ui-surface"),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
context.platform.agentInputs[0].tools.some(
|
|
(tool) => tool.name === "custom_render",
|
|
),
|
|
false,
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "mcp-apps.discover-execute-and-persist",
|
|
configuration: mcpConfiguration,
|
|
async run(context) {
|
|
const { platform } = context;
|
|
platform.faults.agentEvents = toolStream(
|
|
"show_card",
|
|
['{"title":"Example"}'],
|
|
"mcp-call",
|
|
);
|
|
const events = await runAndWait(context);
|
|
const tools = platform.agentInputs[0].tools;
|
|
assert.ok(
|
|
tools.some(
|
|
(tool) =>
|
|
tool.name === "show_card" &&
|
|
tool.description.includes("[UI Resource: ui://fixture/card]"),
|
|
),
|
|
);
|
|
assert.equal(
|
|
tools.some((tool) => tool.name === "internal_tool"),
|
|
false,
|
|
);
|
|
assert.deepEqual(platform.mcpCalls, [{ title: "Example" }]);
|
|
const activity = events.find(
|
|
(event) => event.activityType === "mcp-apps",
|
|
);
|
|
assert.equal(activity.content.resourceUri, "ui://fixture/card");
|
|
assert.equal(activity.content.serverId, "cards");
|
|
assert.equal(
|
|
activity.content.serverHash,
|
|
createHash("md5")
|
|
.update(JSON.stringify({ type: "http", url: platform.mcpUrl }))
|
|
.digest("hex"),
|
|
);
|
|
assert.deepEqual(activity.content.toolInput, { title: "Example" });
|
|
const result = events.find(
|
|
(event) =>
|
|
event.type === "TOOL_CALL_RESULT" && event.toolCallId === "mcp-call",
|
|
);
|
|
assert.equal(result.content, "Card: Example");
|
|
assert.ok(
|
|
events.indexOf(activity) <
|
|
events.findIndex((event) => event.type === "RUN_FINISHED"),
|
|
);
|
|
// Native hardening beyond pinned TS: trusted HTTP auth and explicit teardown.
|
|
assert.ok(
|
|
platform.requests.some(
|
|
(request) => request.path === "/mcp" && request.method === "DELETE",
|
|
),
|
|
"MCP session not closed",
|
|
);
|
|
await platform.waitFor(() => platform.mcp.getSessions().size === 0);
|
|
for (const value of [
|
|
platform.agentInputs,
|
|
platform.events,
|
|
platform.telemetry,
|
|
]) {
|
|
assert.equal(
|
|
JSON.stringify(value).includes("mcp-fixture-token"),
|
|
false,
|
|
"MCP credential leaked outside transport",
|
|
);
|
|
}
|
|
},
|
|
},
|
|
{
|
|
id: "mcp-apps.resource-reentry-bypasses-agent",
|
|
configuration: mcpConfiguration,
|
|
async run(context) {
|
|
const events = await runAndWait(
|
|
context,
|
|
input({
|
|
forwardedProps: {
|
|
__proxiedMCPRequest: {
|
|
serverId: "cards",
|
|
method: "resources/read",
|
|
params: { uri: "ui://fixture/card" },
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.equal(context.platform.agentInputs.length, 0);
|
|
const terminal = events.find((event) => event.type === "RUN_FINISHED");
|
|
assert.equal(
|
|
terminal.result.contents[0].mimeType,
|
|
"text/html;profile=mcp-app",
|
|
);
|
|
assert.match(terminal.result.contents[0].text, /Fixture card/);
|
|
},
|
|
},
|
|
{
|
|
id: "mcp-apps.tool-reentry-bypasses-agent",
|
|
configuration: mcpConfiguration,
|
|
async run(context) {
|
|
const events = await runAndWait(
|
|
context,
|
|
input({
|
|
forwardedProps: {
|
|
__proxiedMCPRequest: {
|
|
serverId: "cards",
|
|
method: "tools/call",
|
|
params: {
|
|
name: "show_card",
|
|
arguments: { title: "From iframe" },
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.equal(context.platform.agentInputs.length, 0);
|
|
assert.deepEqual(context.platform.mcpCalls, [{ title: "From iframe" }]);
|
|
assert.equal(
|
|
events.find((event) => event.type === "RUN_FINISHED").result.content[0]
|
|
.text,
|
|
"Card: From iframe",
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "mcp-apps.unknown-server-cannot-trigger-network",
|
|
configuration: mcpConfiguration,
|
|
async run(context) {
|
|
const events = await runAndWait(
|
|
context,
|
|
input({
|
|
forwardedProps: {
|
|
__proxiedMCPRequest: {
|
|
serverId: "unconfigured",
|
|
method: "resources/read",
|
|
params: { uri: "https://untrusted.test" },
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.equal(context.platform.agentInputs.length, 0);
|
|
assert.equal(
|
|
context.platform.requests.filter((request) => request.path === "/mcp")
|
|
.length,
|
|
0,
|
|
);
|
|
assert.ok(
|
|
events.find((event) => event.type === "RUN_FINISHED").result.error,
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "mcp-apps.agent-scope-blocks-reentry",
|
|
configuration(platform) {
|
|
const configuration = mcpConfiguration(platform);
|
|
configuration.mcpApps.servers[0].agentId = "another-agent";
|
|
return configuration;
|
|
},
|
|
async run(context) {
|
|
const events = await runAndWait(
|
|
context,
|
|
input({
|
|
forwardedProps: {
|
|
__proxiedMCPRequest: {
|
|
serverId: "cards",
|
|
method: "resources/read",
|
|
params: { uri: "ui://fixture/card" },
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.equal(context.platform.agentInputs.length, 0);
|
|
assert.equal(
|
|
context.platform.requests.filter((request) => request.path === "/mcp")
|
|
.length,
|
|
0,
|
|
);
|
|
assert.ok(
|
|
events.find((event) => event.type === "RUN_FINISHED").result.error,
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "mcp-apps.browser-cannot-override-transport",
|
|
configuration: mcpConfiguration,
|
|
async run(context) {
|
|
const events = await runAndWait(
|
|
context,
|
|
input({
|
|
forwardedProps: {
|
|
__proxiedMCPRequest: {
|
|
serverId: "cards",
|
|
url: "http://127.0.0.1:1/untrusted",
|
|
headers: { "x-fixture-auth": "browser-controlled" },
|
|
method: "resources/read",
|
|
params: { uri: "ui://fixture/card" },
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.equal(context.platform.agentInputs.length, 0);
|
|
assert.match(
|
|
events.find((event) => event.type === "RUN_FINISHED").result.contents[0]
|
|
.text,
|
|
/Fixture card/,
|
|
);
|
|
const requests = context.platform.requests.filter(
|
|
(request) => request.path === "/mcp",
|
|
);
|
|
assert.ok(requests.length > 0);
|
|
assert.ok(
|
|
requests.every(
|
|
(request) =>
|
|
request.headers["x-fixture-auth"] === "mcp-fixture-token",
|
|
),
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "mcp-apps.disallowed-method-cannot-trigger-network",
|
|
configuration: mcpConfiguration,
|
|
async run(context) {
|
|
const events = await runAndWait(
|
|
context,
|
|
input({
|
|
forwardedProps: {
|
|
__proxiedMCPRequest: {
|
|
serverId: "cards",
|
|
method: "roots/list",
|
|
params: {},
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.equal(context.platform.agentInputs.length, 0);
|
|
assert.equal(
|
|
context.platform.requests.filter((request) => request.path === "/mcp")
|
|
.length,
|
|
0,
|
|
);
|
|
assert.ok(
|
|
events.find((event) => event.type === "RUN_FINISHED").result.error,
|
|
);
|
|
},
|
|
},
|
|
];
|