From 8aaf41e1f226f8add43a13c1b76b89b4de7e2ed5 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Thu, 27 Aug 2026 15:03:37 -0700
Subject: [PATCH 1/5] Add research page on MCP tool presentation across
harnesses
---
.../pages/research/mcp-tool-visibility.astro | 598 ++++++++++++++++++
1 file changed, 598 insertions(+)
create mode 100644 apps/marketing/src/pages/research/mcp-tool-visibility.astro
diff --git a/apps/marketing/src/pages/research/mcp-tool-visibility.astro b/apps/marketing/src/pages/research/mcp-tool-visibility.astro
new file mode 100644
index 000000000..66f57c277
--- /dev/null
+++ b/apps/marketing/src/pages/research/mcp-tool-visibility.astro
@@ -0,0 +1,598 @@
+---
+import Layout from "../../layouts/Layout.astro";
+
+const GH = "https://github.com/UsefulSoftwareCo/executor";
+const title = "How agents actually see your MCP tools";
+const description =
+ "The same MCP server was connected to five coding agents and the model-bound traffic was measured. Each harness shows the model something different: full definitions, bare names, or nothing at all.";
+
+// Δ prompt tokens for the 31 per-integration search tools, provider-reported.
+const costs = [
+ { name: "Claude Code", detail: "2.1.247 · inline", delta: 3464, plain: "36,013", search: "39,477" },
+ { name: "opencode (beta)", detail: "default · inline", delta: 3400, plain: "22,118", search: "25,518" },
+ { name: "opencode", detail: "1.18.15 · inline", delta: 3399, plain: "17,458", search: "20,857" },
+ { name: "opencode (beta)", detail: "code mode · TS signatures", delta: 1280, plain: "16,126", search: "17,406" },
+ { name: "Cursor CLI", detail: "2026.07.23 · names only", delta: 170, plain: "16,140", search: "16,310" },
+ { name: "Codex CLI", detail: "0.147.0 · code mode", delta: 0, plain: "15,776", search: "15,776" },
+];
+const max = Math.max(...costs.map((c) => c.delta));
+---
+
+
+
+
+
+
+
+
+
+
+
+ {/* ─── HEADER ─── */}
+
+
+ Research · August 2026
+
+ {title}
+
+
+ {description} We connected one Executor workspace — 31 integrations — to Claude Code,
+ Cursor's CLI, Codex, and two versions of opencode, twice each: once plain, once with
+ Executor's ?search_tools=true option, which adds one
+ minimal search_<integration> tool per connected
+ integration. The difference between those two sessions is exactly what 31 extra tool
+ definitions cost, and where they end up.
+
+
+
+
+ {/* ─── THE THREE REALITIES ─── */}
+
+
+ What each harness shows the model
+
Five harnesses, three different realities
+
+ "The model sees your tools" hides three different mechanisms. Every card below is one
+ harness's view of the same server, drawn from the captured request that actually went
+ to the model. Solid blocks are in the context window on every turn; dashed blocks are
+ fetched on demand; crossed-out blocks never reach the model at all.
+
+
+
+ {/* Inliners */}
+
+
+
+ Claude Code · opencode
+ family: inline — every definition, every turn
+
+ +3,399–3,464 tokens
+
+
+
+
the model's context window
+
system prompt
+
+ 38 tool definitions — full JSON schema each
+ name + description + parameters for every tool, inlined as API function tools
+
+
conversation
+
+
+
+
+
+ tools[7] of 38 — as sent to the model
+
+
{`{
+ "name": "mcp__executor__search_mcp_linear_app",
+ "description": "Search this integration's tools;
+ empty query lists all. Run results with execute.",
+ "input_schema": {
+ "type": "object",
+ "properties": { "query": { "type": "string" } }
+ }
+}`}
+
+
+ To search Linear’s tools
+ one call — search_mcp_linear_app({"{ query }"}),
+ the definition is already in context.
+
+ catalog: bare tool names, grouped by server
+ ≈5.5 tokens per tool — the name is all the model gets
+
+
+ descriptions & schemas — fetched on demand via GetDynamicTools
+
+
conversation
+
+
+
+
+
+ the model's catalog view
+
+
{`executor: execute · skills · resume ·
+ search_axiom_mcp · search_github_rest ·
+ search_google_gmail · search_mcp_linear_app · …
+
+(full definition available via GetDynamicTools —
+ not fetched unless the model asks)`}
+
+
+ To search Linear’s tools
+ the name is visible; call it through
+ CallDynamicTool("search_mcp_linear_app", …),
+ optionally fetching the schema first.
+
+ To search Linear’s tools
+ the model must call exec and write code —
+ await tools.mcp__executor__search_mcp_linear_app(…) —
+ discovering what exists only from inside the sandbox.
+
+
+
+
+
+
+
+
+ {/* ─── COST ─── */}
+
+
+ Measured, not estimated
+
What 31 tool definitions cost, by harness
+
+ Δ prompt tokens between the plain and ?search_tools=true sessions,
+ as reported by the provider's usage accounting (input + cache write + cache read). Same
+ workspace, same prompt; only the endpoint query differs.
+
+ Cursor's model traffic terminates at its backend, so its pair comes from Cursor's own
+ per-request usage meter rather than a captured prompt. JSON tool definitions tokenize at
+ roughly 2.4 characters per token — schema punctuation is token-dense.
+
+
+
+
+ {/* ─── TAKEAWAYS ─── */}
+
+
+ If you ship an MCP server
+
Design for the weakest channel
+
+
+ 01
+
The name is the only universal channel
+
+ Every harness that shows tools at all shows the name. Descriptions reach only the
+ inliners; schemas effectively reach only the inliners. If information must arrive,
+ put it in the name.
+
+
+
+ 02
+
Schema tricks don't travel
+
+ An enum of options inside a parameter looks clever and costs less — and is invisible
+ in Cursor and Codex, the two harnesses where you'd need it most.
+
+
+
+ 03
+
Keep definitions small anyway
+
+ Where tools are inlined, every byte is paid on every turn, multiplied by tool count.
+ One shared description sentence and a single parameter cut this surface by more than half.
+
+
+
+ 04
+
Measure with provider usage, not estimates
+
+ Client-side token meters and character counts both mislead. Run the same session
+ twice and diff what the provider bills — that number is the truth.
+
+ Nothing about your tools reaches the prompt. They exist only as callables inside
+ Codex's code sandbox — the model discovers them by writing code.
+
the model's context window
@@ -422,51 +426,75 @@ const max = Math.max(...costs.map((c) => c.delta));
padding: 0;
overflow: hidden;
}
- .persp-head {
+ /* window titlebar — the card IS the harness's window */
+ .persp-bar {
display: flex;
- align-items: baseline;
- justify-content: space-between;
- gap: 16px;
- padding: 18px 24px;
+ align-items: center;
+ gap: 10px;
+ padding: 12px 18px;
border-bottom: 1px solid var(--color-rule);
+ background: var(--color-surface-2);
+ }
+ .persp-dot {
+ width: 9px;
+ height: 9px;
+ border-radius: 9999px;
+ background: var(--color-rule-strong);
+ flex-shrink: 0;
+ }
+ .persp-dot + .persp-dot {
+ margin-left: -3px;
+ }
+ .persp-icons {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ flex-shrink: 0;
+ margin-left: 8px;
+ }
+ .persp-icons img {
+ display: block;
+ height: 18px;
+ width: 18px;
}
.persp-name {
font-weight: 600;
- font-size: 17px;
+ font-size: 15px;
letter-spacing: -0.01em;
color: var(--color-ink);
+ white-space: nowrap;
}
- .persp-family {
- display: block;
- margin-top: 3px;
+ .persp-ver {
font-family: var(--font-mono);
font-size: 11px;
- letter-spacing: 0.08em;
- text-transform: uppercase;
color: var(--color-ink-3);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ min-width: 0;
}
.persp-cost {
font-family: var(--font-mono);
- font-size: 13px;
+ font-size: 12.5px;
color: var(--color-ink);
white-space: nowrap;
+ margin-left: auto;
}
- .persp-id {
- display: flex;
- align-items: center;
- gap: 12px;
- min-width: 0;
+ @media (max-width: 640px) {
+ .persp-ver {
+ display: none;
+ }
}
- .persp-icons {
- display: flex;
- align-items: center;
- gap: 8px;
- flex-shrink: 0;
+ .persp-lede {
+ padding: 16px 24px 0;
+ font-size: 14.5px;
+ line-height: 1.6;
+ color: var(--color-ink-2);
+ max-width: 72ch;
}
- .persp-icons img {
- display: block;
- height: 20px;
- width: 20px;
+ /* one window chrome per card: inner excerpts keep a filename bar, no dots */
+ .persp-detail .code-dot {
+ display: none;
}
.persp-body {
display: grid;
From cd450ba67ff43633756bfe9aa73d3fa5647db3b7 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Thu, 27 Aug 2026 16:09:46 -0700
Subject: [PATCH 4/5] Rework research page around a tabbed context-window
viewer
---
.../pages/research/mcp-tool-visibility.astro | 588 ++++++++----------
1 file changed, 267 insertions(+), 321 deletions(-)
diff --git a/apps/marketing/src/pages/research/mcp-tool-visibility.astro b/apps/marketing/src/pages/research/mcp-tool-visibility.astro
index 86e601ab6..d69834a75 100644
--- a/apps/marketing/src/pages/research/mcp-tool-visibility.astro
+++ b/apps/marketing/src/pages/research/mcp-tool-visibility.astro
@@ -8,7 +8,7 @@ import opencodeLogo from "../../assets/logos/opencode.svg?url";
const GH = "https://github.com/UsefulSoftwareCo/executor";
const title = "How agents actually see your MCP tools";
const description =
- "The same MCP server was connected to five coding agents and the model-bound traffic was measured. Each harness shows the model something different: full definitions, bare names, or nothing at all.";
+ "The same MCP server, connected to five coding agents, with the model-bound traffic measured. Each harness shows the model something different: full definitions, bare names, or nothing at all.";
// Δ prompt tokens for the 31 per-integration search tools, provider-reported.
const costs = [
@@ -63,229 +63,141 @@ const max = Math.max(...costs.map((c) => c.delta));
{title}
- {description} We connected one Executor workspace — 31 integrations — to Claude Code,
- Cursor's CLI, Codex, and two versions of opencode, twice each: once plain, once with
- Executor's ?search_tools=true option, which adds one
- minimal search_<integration> tool per connected
- integration. The difference between those two sessions is exactly what 31 extra tool
- definitions cost, and where they end up.
+ One Executor workspace — 31 integrations — connected to each harness twice: plain, and
+ with ?search_tools=true, which adds one minimal
+ search_<integration> tool per integration. The
+ difference between the two captured sessions is what 31 tool definitions cost, and
+ where they end up.
- {/* ─── THE THREE REALITIES ─── */}
+ {/* ─── CONTEXT VIEWER ─── */}
- What each harness shows the model
-
Five harnesses, three different realities
-
- "The model sees your tools" hides three different mechanisms. Every card below is one
- harness's view of the same server, drawn from the captured request that actually went
- to the model. Solid blocks are in the context window on every turn; dashed blocks are
- fetched on demand; crossed-out blocks never reach the model at all.
-
- catalog: bare tool names, grouped by server
- ≈5.5 tokens per tool — the name is all the model gets
-
-
- descriptions & schemas — fetched on demand via GetDynamicTools
-
-
conversation
-
-
-
-
-
- the model's catalog view
-
-
{`executor: execute · skills · resume ·
- search_axiom_mcp · search_github_rest ·
- search_google_gmail · search_mcp_linear_app · …
-
-(full definition available via GetDynamicTools —
- not fetched unless the model asks)`}
-
-
- To search Linear’s tools
- the name is visible; call it through
- CallDynamicTool("search_mcp_linear_app", …),
- optionally fetching the schema first.
-
- Nothing about your tools reaches the prompt. They exist only as callables inside
- Codex's code sandbox — the model discovers them by writing code.
-
-
-
-
the model's context window
-
system prompt
-
9 native tools — exec, collaboration, …
-
- your MCP tools
- carried only in request metadata the model never reads; prompts were byte-identical with 7 vs 38 tools
-
- To search Linear’s tools
- the model must call exec and write code —
- await tools.mcp__executor__search_mcp_linear_app(…) —
- discovering what exists only from inside the sandbox.
-
-
+ request metadata — never rendered into the prompt; callable only from code inside exec