Skip to content

Commit 2168029

Browse files
authored
e2e: add self-host coverage for emit() content blocks (#1592) (#1820)
1 parent 06a7b75 commit 2168029

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Selfhost: emit() output actually reaches the MCP client's content blocks,
2+
// not merely the `emitted` count in the envelope. Regression coverage for
3+
// issue #1592 — a self-hosted user saw a non-zero `emitted` count but no
4+
// content blocks in the tool result, only `logs` and the return value. There
5+
// is cloud coverage for the envelope count
6+
// (`e2e/cloud/execute-emit-envelope.test.ts`), but nothing that asserts on
7+
// the wire-level `content` array on self-host, and nothing on either target
8+
// that covers all three emit shapes from the report: a plain string, a
9+
// ToolFile, and a structured/object value.
10+
import { expect } from "@effect/vitest";
11+
import { Effect } from "effect";
12+
13+
import { scenario } from "../src/scenario";
14+
import { Mcp, Target } from "../src/services";
15+
16+
interface ContentBlock {
17+
readonly type: string;
18+
readonly text?: string;
19+
}
20+
21+
// Mirrors the issue's repro exactly: a plain string emit, a ToolFile emit, a
22+
// structured/object emit, then a return.
23+
const EXECUTE_CODE = `
24+
const attachment = {
25+
_tag: "ToolFile",
26+
name: "report.txt",
27+
mimeType: "text/plain",
28+
encoding: "base64",
29+
data: "aGVsbG8gZmlsZQ==",
30+
byteLength: 10,
31+
};
32+
emit("plain string emit");
33+
emit(attachment);
34+
emit({ hello: "object emit" });
35+
return { returnedValue: "this is the return" };
36+
`;
37+
38+
scenario(
39+
"Execute · emit() output reaches the MCP client's content blocks",
40+
{},
41+
Effect.gen(function* () {
42+
const target = yield* Target;
43+
const mcp = yield* Mcp;
44+
const identity = yield* target.newIdentity();
45+
const session = mcp.session(identity);
46+
47+
const result = yield* session.call("execute", { code: EXECUTE_CODE });
48+
expect(result.ok, `execute completed (got: ${result.text.slice(0, 300)})`).toBe(true);
49+
50+
const structured = (result.raw as { structuredContent?: Record<string, unknown> })
51+
.structuredContent;
52+
expect(structured?.status, "the run completed").toBe("completed");
53+
expect(structured?.emitted, "the envelope counts all three emitted items").toBe(3);
54+
expect(structured?.result, "the return value still comes back to the caller").toEqual({
55+
returnedValue: "this is the return",
56+
});
57+
58+
// The real assertion: the JSON-RPC tool result's `content` array — what
59+
// an MCP client actually renders — carries all three emitted items, not
60+
// just the `emitted` count.
61+
const content = (result.raw as { content?: ContentBlock[] }).content ?? [];
62+
expect(Array.isArray(content) && content.length > 0, "the wire result carries content").toBe(
63+
true,
64+
);
65+
66+
expect(
67+
content.some((block) => block.type === "text" && block.text === "plain string emit"),
68+
`content carries the plain string emit; got: ${JSON.stringify(content)}`,
69+
).toBe(true);
70+
71+
// A text-mimetype ToolFile renders as a summary line plus its decoded
72+
// text content (see outputFileContent/toolFileContent in tool-server.ts).
73+
expect(
74+
content.some(
75+
(block) =>
76+
block.type === "text" &&
77+
typeof block.text === "string" &&
78+
block.text.includes("report.txt"),
79+
),
80+
`content carries the ToolFile emit's summary line; got: ${JSON.stringify(content)}`,
81+
).toBe(true);
82+
expect(
83+
content.some((block) => block.type === "text" && block.text === "hello file"),
84+
`content carries the ToolFile emit's decoded text; got: ${JSON.stringify(content)}`,
85+
).toBe(true);
86+
87+
// A plain object emit is not a ToolFile or an MCP content block, so it
88+
// falls back to a JSON text block.
89+
expect(
90+
content.some((block) => block.type === "text" && block.text === '{"hello":"object emit"}'),
91+
`content carries the structured/object emit; got: ${JSON.stringify(content)}`,
92+
).toBe(true);
93+
}),
94+
);

0 commit comments

Comments
 (0)