Skip to content

fix(t3team): t3team_task_list empty schema dropped every host MCP tool - #209

Merged
johnnyelwailer merged 1 commit into
mainfrom
fix/mcp-task-list-empty-schema
Sep 12, 2026
Merged

johnnyelwailer merged 1 commit into
mainfrom
fix/mcp-task-list-empty-schema

Conversation

@johnnyelwailer

Copy link
Copy Markdown
Owner

Problem

Every Nexplore/Pi agent on the current desktop build silently kept only Pi's built-in tools. All 22 t3team_* and all 14 preview_* tools were gone, with no error in the UI or in any log.

Root cause

t3team_task_list (added in #206) was declared with parameters: Schema.Struct({}). An empty TS object type means "any non-null", so effect renders it as:

{"anyOf":[{"type":"object"},{"type":"array"}]}

The MCP SDK validates the whole tools/list response — inputSchema: z.object({ type: z.literal('object'), … }) in types.js:1240. One malformed tool therefore fails the entire list: client.listTools() throws, and the Nexplore bridge's catch returns an empty tool array.

Proven:

strict ListToolsResultSchema.safeParse({tools:[{name:"t3team_task_list",
  inputSchema:{anyOf:[{type:"object"},{type:"array"}]}}]}).success  →  false

Fix

Omit parameters, which picks up Tool.EmptyParams → {"type":"object","additionalProperties":false}. This is the same fix t3team_recipe_list already carries, with the comment explaining exactly this trap sitting 350 lines below.

Why the guard did not stop it

t3team-mcpToolInputSchema.test.ts covers every exported tool generically and was already red on main — #206 merged with it failing. It is green again:

Test Files  1 passed (1)
     Tests  25 passed (25)

All 24 toolkit schemas now render as top-level {"type":"object"} (verified by rendering each one).

Written by Claude Opus 5 in Claude Code.

`t3team_task_list` was declared with `parameters: Schema.Struct({})`. An empty
TS object type means "any non-null", so effect renders it as
`{anyOf:[{type:"object"},{type:"array"}]}`.

The MCP SDK validates the WHOLE `tools/list` response against a schema that
requires a literal `{type:"object"}` per tool, so that one tool failed the
entire list: `client.listTools()` threw, and every Nexplore/Pi agent silently
kept zero `t3team_*` and zero `preview_*` tools. No error surfaced anywhere.

Omitting `parameters` picks up `Tool.EmptyParams`, which renders as
`{"type":"object","additionalProperties":false}` — the same fix
`t3team_recipe_list` already carries.

`t3team-mcpToolInputSchema.test.ts` was already red for this on main; it is
green again (25/25).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XS labels Sep 12, 2026
@johnnyelwailer
johnnyelwailer marked this pull request as ready for review September 12, 2026 07:50
@johnnyelwailer
johnnyelwailer merged commit 6e4ea73 into main Sep 12, 2026
9 of 16 checks passed
@johnnyelwailer
johnnyelwailer deleted the fix/mcp-task-list-empty-schema branch September 12, 2026 07:50
johnnyelwailer added a commit that referenced this pull request Sep 12, 2026
#209 removed all 22 `t3team_*` AND all 14 `preview_*` tools from every
Nexplore agent, because MCP clients validate the whole `tools/list` response —
one bad schema drops every tool the server serves, across toolkits.

The guard only imported `t3team/tools.ts`, so the preview toolkit was
collateral damage with no guard of its own. It now covers both modules,
still asserted over the whole module so a tool added later is included
without anyone remembering.

40 tests pass (25 t3team + 15 preview). All 14 preview tools already render
a valid top-level {"type":"object"} today — this keeps it that way.

Co-authored-by: Phil J <philip.jonientz@nexplore.ch>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
johnnyelwailer pushed a commit that referenced this pull request Oct 3, 2026
…n V2

- The active-workflow dock returns to the composer's leading slot, fed
  by the workflow step artifacts and the workflowRunStatus fact, and
  scrolls the timeline to a run's card.
- The offline outbox moves from timeline rows (upstream has no queued
  rows) to the composer dock next to QueuedRunsControl; ChatView's
  queuedExtensions prop becomes composerBannerLeading.
- GHE #209's sender-side label now reads t3_thread_send calls
  (threadId argument) instead of the removed t3team_send_message.
- ADOPT: the actor-message card is deleted; inter-agent messages render
  through upstream's agent attribution and mailbox notification rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDL9mrxHBk7QdmE7MfFPfN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant