From c8d3a4cb471c0fba48c5115d5e6cb59cfc66477e Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Sat, 17 Oct 2026 09:26:29 +0000 Subject: [PATCH 1/2] Return compact JSON from MCP tool and resource responses --- src/lib/mcp/resource-templates.ts | 4 ++-- src/lib/mcp/responses.ts | 2 +- src/lib/mcp/tools/auth-connections.ts | 2 +- src/lib/mcp/tools/browser-repl.ts | 2 +- src/lib/mcp/tools/browsers.ts | 2 -- src/lib/mcp/tools/durable-contracts.test.ts | 4 ++-- src/lib/mcp/tools/shell.ts | 25 ++++++--------------- 7 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/lib/mcp/resource-templates.ts b/src/lib/mcp/resource-templates.ts index 109256d..5abee5d 100644 --- a/src/lib/mcp/resource-templates.ts +++ b/src/lib/mcp/resource-templates.ts @@ -85,7 +85,7 @@ export function registerJsonResourceCollection( mimeType: "application/json", text: resources.length > 0 - ? JSON.stringify(resources, null, 2) + ? JSON.stringify(resources) : options.emptyText, }, ], @@ -127,7 +127,7 @@ export function registerJsonResourceTemplate( { uri: uri.toString(), mimeType: "application/json", - text: JSON.stringify(resource, null, 2), + text: JSON.stringify(resource), }, ], }; diff --git a/src/lib/mcp/responses.ts b/src/lib/mcp/responses.ts index 0553203..4532437 100644 --- a/src/lib/mcp/responses.ts +++ b/src/lib/mcp/responses.ts @@ -31,7 +31,7 @@ export function textResponse(text: string) { } export function jsonResponse(value: unknown) { - return textResponse(JSON.stringify(value, null, 2) ?? String(value)); + return textResponse(JSON.stringify(value) ?? String(value)); } export function itemsJsonResponse( diff --git a/src/lib/mcp/tools/auth-connections.ts b/src/lib/mcp/tools/auth-connections.ts index 6883e80..5bba7fb 100644 --- a/src/lib/mcp/tools/auth-connections.ts +++ b/src/lib/mcp/tools/auth-connections.ts @@ -31,7 +31,7 @@ function safeJsonResponse(value: unknown) { content: [ { type: "text" as const, - text: JSON.stringify(value, null, 2), + text: JSON.stringify(value), }, ], structuredContent: value as Record, diff --git a/src/lib/mcp/tools/browser-repl.ts b/src/lib/mcp/tools/browser-repl.ts index f9a5f16..1e9eed5 100644 --- a/src/lib/mcp/tools/browser-repl.ts +++ b/src/lib/mcp/tools/browser-repl.ts @@ -137,7 +137,7 @@ function replToolContent(result: BrowserReplResult): ReplToolContent[] { }; const content: ReplToolContent[] = [ - { type: "text", text: JSON.stringify(summary, null, 2) }, + { type: "text", text: JSON.stringify(summary) }, ]; for (const item of result.content ?? []) { if (item.type === "image") { diff --git a/src/lib/mcp/tools/browsers.ts b/src/lib/mcp/tools/browsers.ts index dcd30ff..73f1bca 100644 --- a/src/lib/mcp/tools/browsers.ts +++ b/src/lib/mcp/tools/browsers.ts @@ -346,8 +346,6 @@ async function readBrowserTelemetry( if (rawError) return errorResponse(`error: ${rawError}`); } - // Single-line JSON rather than the pretty-printed house helpers: a page - // carries up to 100 events and indentation would inflate the token cost. const serializedResponse = JSON.stringify(response); if ( params.compact === false && diff --git a/src/lib/mcp/tools/durable-contracts.test.ts b/src/lib/mcp/tools/durable-contracts.test.ts index 0f2ce27..b8906cf 100644 --- a/src/lib/mcp/tools/durable-contracts.test.ts +++ b/src/lib/mcp/tools/durable-contracts.test.ts @@ -271,7 +271,7 @@ describe("durable profile contracts", () => { expect(result.content).toEqual([ { type: "text", - text: JSON.stringify({ id: "profile-1", name: "Renamed" }, null, 2), + text: JSON.stringify({ id: "profile-1", name: "Renamed" }), }, ]); } @@ -426,7 +426,7 @@ describe("durable proxy contracts", () => { expect(result.content).toEqual([ { type: "text", - text: JSON.stringify({ id: "proxy-1", name: "Renamed" }, null, 2), + text: JSON.stringify({ id: "proxy-1", name: "Renamed" }), }, ]); } finally { diff --git a/src/lib/mcp/tools/shell.ts b/src/lib/mcp/tools/shell.ts index 32ef4b5..5cb977b 100644 --- a/src/lib/mcp/tools/shell.ts +++ b/src/lib/mcp/tools/shell.ts @@ -5,7 +5,7 @@ import { type McpDependencies, } from "@/lib/mcp/dependencies"; import { longOperationOptions } from "@/lib/mcp/request-options"; -import { throwToolError } from "@/lib/mcp/responses"; +import { jsonResponse, throwToolError } from "@/lib/mcp/responses"; import { projectForOperation, projectSelectionInputSchema, @@ -102,23 +102,12 @@ export function registerShellTool( ? Buffer.from(result.stderr_b64, "base64").toString("utf-8") : ""; - return { - content: [ - { - type: "text", - text: JSON.stringify( - { - exit_code: result.exit_code, - duration_ms: result.duration_ms, - stdout, - stderr, - }, - null, - 2, - ), - }, - ], - }; + return jsonResponse({ + exit_code: result.exit_code, + duration_ms: result.duration_ms, + stdout, + stderr, + }); } catch (error) { throwToolError("exec_command", "exec", error); } From 48b3327bda3aaf98ae98bbd980dace2c23871f53 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:05:27 +0000 Subject: [PATCH 2/2] Pin compact JSON responses with a test and document the convention --- AGENTS.md | 4 ++++ src/lib/mcp/responses.test.ts | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 65c6828..031c2f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -81,6 +81,10 @@ bun run format Prettier formats the following file types: `*.ts`, `*.js`, `*.json`, `*.md` +### MCP responses + +Model-facing JSON (tool results and resources) is compact: use `jsonResponse` or `JSON.stringify(value)` without indentation. + ## Project Structure - `src/` - Source code directory diff --git a/src/lib/mcp/responses.test.ts b/src/lib/mcp/responses.test.ts index 2f59af5..9266a37 100644 --- a/src/lib/mcp/responses.test.ts +++ b/src/lib/mcp/responses.test.ts @@ -10,7 +10,11 @@ import { } from "@onkernel/sdk"; import { describe, expect, test } from "bun:test"; -import { errorResponse, throwToolError } from "@/lib/mcp/responses"; +import { + errorResponse, + jsonResponse, + throwToolError, +} from "@/lib/mcp/responses"; import { z } from "zod"; function apiError(status: number, message: string) { @@ -30,6 +34,14 @@ function caught(error: unknown) { throw new Error("throwToolError did not throw"); } +describe("jsonResponse", () => { + test("emits compact JSON", () => { + expect(jsonResponse({ a: 1, b: [2] }).content[0].text).toBe( + '{"a":1,"b":[2]}', + ); + }); +}); + describe("throwToolError classification", () => { test("names Kernel API failures after their status", () => { expect(caught(apiError(404, "not found")).name).toBe("KernelApiError404");