Skip to content

Commit 4e8bc29

Browse files
committed
fix(app): emit title and omit absent outputSchema in tools/list
1 parent cbc2780 commit 4e8bc29

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

src/app-bridge.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,40 @@ describe("App <-> AppBridge integration", () => {
12871287
expect(result.tools.map((t) => t.name)).toContain("tool3");
12881288
});
12891289

1290+
it("emits core MCP Tool fields (title, outputSchema only when provided)", async () => {
1291+
const appCapabilities = { tools: { listChanged: true } };
1292+
app = new App(testAppInfo, appCapabilities, { autoResize: false });
1293+
1294+
app.registerTool(
1295+
"with-output",
1296+
{
1297+
title: "With Output",
1298+
description: "has structured output",
1299+
outputSchema: z.object({ ok: z.boolean() }),
1300+
},
1301+
async () => ({
1302+
content: [],
1303+
structuredContent: { ok: true },
1304+
}),
1305+
);
1306+
app.registerTool(
1307+
"no-output",
1308+
{ description: "no structured output" },
1309+
async () => ({ content: [] }),
1310+
);
1311+
1312+
await app.connect(appTransport);
1313+
const result = await bridge.listTools({});
1314+
const byName = Object.fromEntries(result.tools.map((t) => [t.name, t]));
1315+
1316+
expect(byName["with-output"].title).toBe("With Output");
1317+
expect(byName["with-output"].inputSchema).toBeDefined();
1318+
expect(byName["with-output"].outputSchema).toBeDefined();
1319+
// outputSchema is optional in core MCP — omitted when not declared
1320+
expect(byName["no-output"]).not.toHaveProperty("outputSchema");
1321+
expect(byName["no-output"].inputSchema).toBeDefined();
1322+
});
1323+
12901324
it("returns empty list when no tools registered", async () => {
12911325
const appCapabilities = { tools: { listChanged: true } };
12921326
app = new App(testAppInfo, appCapabilities, { autoResize: false });

src/app.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -424,20 +424,22 @@ export class App extends ProtocolWithEvents<
424424
.map(([name, tool]) => {
425425
const result: Tool = {
426426
name,
427+
title: tool.title,
427428
description: tool.description,
428429
inputSchema: (tool.inputSchema
429430
? z.toJSONSchema(tool.inputSchema as ZodSchema)
430431
: {
431432
type: "object" as const,
432433
properties: {},
433434
}) as Tool["inputSchema"],
434-
outputSchema: (tool.outputSchema
435-
? z.toJSONSchema(tool.outputSchema as ZodSchema)
436-
: {
437-
type: "object" as const,
438-
properties: {},
439-
}) as Tool["outputSchema"],
440435
};
436+
// outputSchema is optional in core MCP — only emit when the app
437+
// provided one, otherwise hosts would assume structuredContent.
438+
if (tool.outputSchema) {
439+
result.outputSchema = z.toJSONSchema(
440+
tool.outputSchema as ZodSchema,
441+
) as Tool["outputSchema"];
442+
}
441443
if (tool.annotations) {
442444
result.annotations = tool.annotations;
443445
}

0 commit comments

Comments
 (0)