forgejo-mcp: trim tool-schema surface to the ~15 actually-used RPCs #81

Closed
opened 2026-04-19 08:42:14 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As the operator, I want the forgejo-mcp server registered by
agent containers to expose only the ~15 RPCs our skills actually
call, so that the tool-schema bloat in the SDK's system prompt
drops by ~20-25k tokens and agents stop hitting Prompt is too long
on their first turn.

What happened (concrete)

Boss (claude-opus-4-7, 200k context) died on the first turn of
task 695b934e-6db9-4b35-990c-924b8b39e717 on #79 with:

Claude Code returned an error result: Prompt is too long

Zero events in the task record — the API request never even left
the SDK. The prompt body is tiny (~1.5KB skill template + 5KB issue
body). What's eating the 200k:

  1. SDK system prompt — baseline.
  2. MCP tool schemas — ~65 tools from forgejo-mcp, serialised
    verbatim into the system prompt at startup.
    Each tool carries
    a JSONSchema for its arguments; the aggregate is 25-30k tokens.
  3. (On designer/design-reviewer) penpot-mcp adds another 10-15 tools.
  4. Claude Code v2.1.111 introduced a ~14% context bloat regression
    (ref anthropics/claude-code#49593).
    Container is on 2.1.112, so we inherit that.

Why this matters

The SDK does not lazy-load MCP tool schemas — it ships every
registered tool's full schema on every turn. The allowedTools
option only gates invocation, not schema inclusion.
SDK issue #138
documents the same failure mode on first turn; closed as "not planned".
Claude Code #20421
asks for dynamic MCP loading — exists in the CLI, not the SDK.

Translation: we can't trim this from the SDK caller side. We have to
cut the surface in the forgejo-mcp server.

What our skills actually use

Grepping skills/, src/webhook-*.ts, and the test suite, the only
mcp__forgejo__* names we invoke are:

  • Read: get_issue_by_index, list_issue_comments,
    list_repo_issues, list_repo_labels, list_repo_pull_requests,
    get_pull_request_by_index, list_pull_request_files,
    list_pull_review_comments, get_pull_review,
    list_workflow_runs, get_workflow_run, get_file_content.
  • Write: create_issue_comment, update_issue,
    add_issue_labels, create_pull_request,
    create_review_requests, submit_pull_review, merge_pull_request.

That's ~19 tools, not 65. Everything else (org/team admin, user
management, notifications, repo creation, fork, dispatch_workflow,
interactive review states, file CRUD, etc.) is dead surface.

Acceptance criteria

Server-side tool filtering

  • forgejo-mcp gains an env-var allowlist — e.g.
    FORGEJO_MCP_TOOLS="get_issue_by_index,list_issue_comments,..."
    — and only registers those names at startup.
  • If unset, keep today's behaviour (full surface) so we don't
    break unpatched installs.
  • Patch lives in our patches/ directory and rebases cleanly
    on upstream v2.17.0 (current patched version).

Wiring

  • agent-runner.ts buildMcpSetup sets FORGEJO_MCP_TOOLS
    from a single canonical list (probably a const FORGEJO_TOOLS_ALLOWLIST in the same file).
  • allowedTools in the SDK query() call derives from the
    same list so the two stay in sync.

Smoke

  • scripts/smoke-creds.sh asserts that an agent's
    claude mcp__forgejo__* list (or equivalent) exposes exactly
    the allowlisted count — fails loud if bigger.
  • Manual: re-dispatch #79 to boss; task makes it past turn 1
    (the current failure signature) and runs to completion under
    200k context.

Out of scope

  • Patching the SDK / claude-code to lazy-load MCP tool schemas.
    Upstream's call; not our codebase.
  • Dropping penpot-mcp's surface — small today (~15 tools), covered
    by #73/#74 adding more. Revisit if this recurs on designers.

References

Dependencies

  • Blocked by: nothing.
  • Blocks: reliable boss/dev agent runs on Opus-200k without the
    [1m] fallback.
  • Branch off: main.
## User story As the **operator**, I want the `forgejo-mcp` server registered by agent containers to expose only the ~15 RPCs our skills actually call, so that the tool-schema bloat in the SDK's system prompt drops by ~20-25k tokens and agents stop hitting `Prompt is too long` on their first turn. ## What happened (concrete) Boss (`claude-opus-4-7`, 200k context) died on the **first turn** of task `695b934e-6db9-4b35-990c-924b8b39e717` on #79 with: ``` Claude Code returned an error result: Prompt is too long ``` Zero events in the task record — the API request never even left the SDK. The prompt body is tiny (~1.5KB skill template + 5KB issue body). What's eating the 200k: 1. SDK system prompt — baseline. 2. **MCP tool schemas — ~65 tools from `forgejo-mcp`, serialised verbatim into the system prompt at startup.** Each tool carries a JSONSchema for its arguments; the aggregate is 25-30k tokens. 3. (On designer/design-reviewer) penpot-mcp adds another 10-15 tools. 4. Claude Code v2.1.111 introduced a ~14% context bloat regression (ref [anthropics/claude-code#49593](https://github.com/anthropics/claude-code/issues/49593)). Container is on 2.1.112, so we inherit that. ## Why this matters The SDK does not lazy-load MCP tool schemas — it ships every registered tool's full schema on every turn. The `allowedTools` option only gates *invocation*, not schema inclusion. [SDK issue #138](https://github.com/anthropics/claude-agent-sdk-typescript/issues/138) documents the same failure mode on first turn; closed as "not planned". [Claude Code #20421](https://github.com/anthropics/claude-code/issues/20421) asks for dynamic MCP loading — exists in the CLI, not the SDK. Translation: we can't trim this from the SDK caller side. We have to cut the surface *in the `forgejo-mcp` server*. ## What our skills actually use Grepping `skills/`, `src/webhook-*.ts`, and the test suite, the only `mcp__forgejo__*` names we invoke are: - **Read**: `get_issue_by_index`, `list_issue_comments`, `list_repo_issues`, `list_repo_labels`, `list_repo_pull_requests`, `get_pull_request_by_index`, `list_pull_request_files`, `list_pull_review_comments`, `get_pull_review`, `list_workflow_runs`, `get_workflow_run`, `get_file_content`. - **Write**: `create_issue_comment`, `update_issue`, `add_issue_labels`, `create_pull_request`, `create_review_requests`, `submit_pull_review`, `merge_pull_request`. That's ~19 tools, not 65. Everything else (org/team admin, user management, notifications, repo creation, fork, dispatch_workflow, interactive review states, file CRUD, etc.) is dead surface. ## Acceptance criteria ### Server-side tool filtering - [ ] `forgejo-mcp` gains an env-var allowlist — e.g. `FORGEJO_MCP_TOOLS="get_issue_by_index,list_issue_comments,..."` — and only registers those names at startup. - [ ] If unset, keep today's behaviour (full surface) so we don't break unpatched installs. - [ ] Patch lives in our `patches/` directory and rebases cleanly on upstream `v2.17.0` (current patched version). ### Wiring - [ ] `agent-runner.ts` `buildMcpSetup` sets `FORGEJO_MCP_TOOLS` from a single canonical list (probably a `const FORGEJO_TOOLS_ALLOWLIST` in the same file). - [ ] `allowedTools` in the SDK `query()` call derives from the same list so the two stay in sync. ### Smoke - [ ] `scripts/smoke-creds.sh` asserts that an agent's `claude mcp__forgejo__* list` (or equivalent) exposes exactly the allowlisted count — fails loud if bigger. - [ ] Manual: re-dispatch #79 to boss; task makes it past turn 1 (the current failure signature) and runs to completion under 200k context. ## Out of scope - Patching the SDK / claude-code to lazy-load MCP tool schemas. Upstream's call; not our codebase. - Dropping penpot-mcp's surface — small today (~15 tools), covered by #73/#74 adding more. Revisit if this recurs on designers. ## References - Failure: task `695b934e-6db9-4b35-990c-924b8b39e717` on #79. - [anthropics/claude-agent-sdk-typescript#138](https://github.com/anthropics/claude-agent-sdk-typescript/issues/138) — same first-turn failure mode. - [anthropics/claude-code#49593](https://github.com/anthropics/claude-code/issues/49593) — 2.1.111 context bloat regression. - [anthropics/claude-code#20421](https://github.com/anthropics/claude-code/issues/20421) — dynamic MCP loading not in SDK. - `patches/forgejo-mcp/` — where the patch goes. ## Dependencies - **Blocked by:** nothing. - **Blocks:** reliable boss/dev agent runs on Opus-200k without the `[1m]` fallback. - **Branch off:** `main`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#81
No description provided.