MCP tool surface optimization

July 4, 2026 · View on GitHub

Status: Shipped (standard profile default)
Audience: Product + backend
Related: MVP.md (current tool table), MCP_INTEGRATION_STRATEGY.md, AGENT_HARNESS.md

Agent Deck MCP tools are registered in tiers (packages/backend/src/mcp-tools/). Default profile standard exposes ~16 tools (down from ~31), including create_deck.

As-built (2026-07)

ProfileToolsUse
runtime~9Bind, read deck, playbooks, proxy — minimal context
standard~16Default — runtime + deck editing + create_deck
legacy~30+ deprecated aliases (add_*_to_bound_deck, list_playbooks, …)

New tools: manage_deck_card, list_collection, create_deck (always on standard). Enriched: get_bound_deck includes display_summary. Harness: uses get_bound_deck instead of list_bound_deck_services / list_playbooks.

Not on MCP: delete_service, delete_playbook, import/export — CLI / dashboard (rare). No extended profile or env gate for rare ops.

Dynamic lazy loading — blocked by hosts, not by us

MCP defines notifications/tools/list_changed so a server can add tools mid-session and the client re-fetches tools/list. Agent Deck could implement that (register rare tools on demand, emit the notification). Do not ship it yet:

HostMid-session list_changed
CursorIgnored — tool list frozen until reconnect / manual MCP refresh (forum)
Claude CodeIgnored for interactive sessions — ToolSearch index not refreshed (#66084, #4118)

So dynamic lazy loading is not “only implementation cost.” Building it now would advertise tools the agent still cannot call until restart — worse UX than CLI.

When hosts honor list_changed: add rare tools (delete, import/export) behind a small always-on entrypoint that registers them and notifies — no env profile.

Until then:

SurfaceOperations
MCP (always)Bind, read, edit cards, register/update, create_deck, proxy
CLI / dashboardDelete cards, import/export decks or cards, secrets

Product policy: who handles what

Operation classSurfaceRationale
Secrets — API key values, OAuth tokensDashboard / CLI onlyNever MCP; human-in-the-loop trust boundary
OAuth browser consentDashboard onlyRequires user click
Runtime — bind, read deck, proxy, playbooksMCP defaultEvery turn
Editing — link cards, register/update, create_deckMCP defaultAgents own deck setup; users should not drag cards in UI
Rare ops — delete card, import/exportCLI / dashboardInfrequent; keep MCP catalog small. Future: dynamic lazy tools if hosts support it
Dashboard UIVisual paritySame REST APIs; optional for humans who prefer cards

CLI rare ops (shipped) — not MCP

agent-deck service list|delete <id>
agent-deck playbook list|delete <id>
agent-deck deck list|delete <id>

Delete service blocks when playbooks depend on it (same as dashboard). Import/export is CLI + dashboard — PRD_EXPORT_IMPORT.md.

If hosts later support dynamic tool loading, rare ops can also appear as on-demand MCP tools without an env profile.


Executive summary

QuestionRecommendation
Link/unlink cards on a deckOne toolmanage_deck_card with action: link | unlink | reorder, card_type, card_id
Create/update/delete in collectionKeep type-specific tools (or at most one manage_collection_item per verb, not one mega-tool) — schemas diverge too much
List bound vs collectionCollapse readsget_bound_deck returns card summaries; one list_collection with optional card_type filter
Target tool count12–15 tools in the default agent context (down from ~31)
Deprecated aliasesRemove *_active_deck_* in the next breaking MCP release; document restart requirement (AGENT_HARNESS.md)

Principle: Optimize for the runtime path (bind → use deck → call proxied tools → read playbooks). Treat deck editing as a secondary, still-agent-accessible but narrower surface.

Growing the server without bloating every session

Use tiered registration, not one mega-tool and not an env profile for rare ops:

  1. runtime — what 90% of agent turns need (optional minimal profile).
  2. standard — default: editing + create_deck.
  3. legacy — deprecated aliases only while hosts refresh cached tool lists.
  4. Rare ops — CLI / dashboard today; dynamic lazy MCP tools later if hosts support tool-on-demand.

Implementation: packages/backend/src/mcp-tools/profile.ts + register.ts. Secrets never get a tier — permanent dashboard/CLI boundary.


Research: MCP tool design best practices

Sources reviewed:

Findings that apply to Agent Deck

  1. Tool budget. Selection accuracy degrades non-linearly past ~10–15 tools; sharp cliff past ~20–30. GitHub Copilot cut 40 → 13 tools with measurable benchmark gains. Block’s Linear MCP went 30+ → 2.

  2. Expose capabilities, not endpoints. REST-shaped 1:1 tool mapping (“add X”, “remove X”, “list X” for every resource) inflates the catalog and burns context every turn (~500–1,000 tokens per tool in many hosts).

  3. Outcome-oriented tools. High-frequency workflows deserve dedicated tools; rare admin belongs behind fewer entry points, dashboard, or lazy-loaded tiers.

  4. Descriptions are UX. Unclear purpose in tool metadata causes wrong selection even when the tool set is sized correctly. Names should be verb-first, constraints explicit, errors actionable.

  5. Hub / aggregator pattern. When a server must expose many capabilities, use scoped exposure (proxy filter, tool search, or session phase) rather than static merge of the full catalog (arXiv §IV-D Proxy Aggregator).

  6. Agent Deck is a meta-hub. Unlike a single-vendor MCP (Linear, GitHub), we manage decks and proxy to N downstream MCPs. The optimization target is our tool surface; downstream service tools stay on those servers (already accessed via call_service_tool).


Current state (as-built)

Implementation: packages/backend/src/mcp-server.ts

GroupToolsCount
Session / deck bindingbind_workspace, switch_bound_deck, get_session_binding, get_decks, get_bound_deck, get_active_deck ⚠️, create_deck7
Bound deck listslist_bound_deck_services, list_bound_deck_credentials, list_playbooks, `list_active_deck_*$ ⚠️ \times 25
\text{Collection} \text{lists}$list_collection_services, list_collection_credentials, list_collection_playbooks`3
Service CRUD + deck linkregister_service, update_service, delete_service, add_service_to_bound_deck, remove_service_from_bound_deck, update_service_tool_settings6
Credential deck linkadd_credential_to_bound_deck, remove_credential_from_bound_deck2
Playbook CRUD + deck linkregister_playbook, update_playbook, delete_playbook, add_playbook_to_bound_deck, remove_playbook_from_bound_deck5
Proxylist_service_tools, call_service_tool2
Total~31 (27 unique + 4 deprecated)

Pain points

IssueImpact
Over budget~31 tools loaded every turn in Cursor/Claude; competes with host tools and other MCP servers
Redundant readsget_bound_deck + three list_bound_deck_* overlap
Symmetric link/unlink sprawlSix tools (add_* / remove_* × three card types) for the same junction-table operation
Deprecated aliasesFour extra tools until hosts refresh
REST mirroringCollection CRUD mirrors /api/* one-to-one instead of user outcomes

What works well (keep)

  • bind_workspace + get_session_binding — clear session model; powers status line and harness opener
  • call_service_tool — correct “macro” pattern; hides downstream MCP wiring
  • Separate playbook read/write — harness depends on get_playbook / update_playbook; high-frequency for Module 3
  • Credentials: link-only via MCP — secrets stay dashboard/CLI; good security boundary

Three card types share the same deck junction semantics:

Card typeLink APIUnlink APICollection survives unlink?
MCP servicePOST /api/decks/:id/servicesDELETE …/servicesYes
API keyPOST /api/decks/:id/credentialsDELETE …/credentialsYes
PlaybookPOST /api/decks/:id/playbooksDELETE …/playbooksYes

Optional position is identical for all three.

Option A — Per card type (current)

add_service_to_bound_deck
remove_service_from_bound_deck
add_credential_to_bound_deck
remove_credential_from_bound_deck
add_playbook_to_bound_deck
remove_playbook_from_bound_deck
ProsCons
Very explicit names; easy grep in logsSix tools for one concept
Short, focused descriptionsModel must choose among near-duplicates
Matches REST mental model for dashboard devsPoor fit for “fewer tools” research

Option B — One unified deck-card tool

manage_deck_card
  action: link | unlink | reorder
  card_type: service | credential | playbook
  card_id: string
  position?: number
ProsCons
Six tools → oneSlightly longer schema
One description explains “link existing collection card to bound deck”Playbook unlink dependency warnings must be in unified errors
Aligns with junction-table implementationLess keyword match for “add slack service” (mitigate in description + examples)
  • Unified: link / unlink / reorder on bound deck (manage_deck_card)
  • Type-specific: create/update/delete in collection and type-specific settings (register_service, update_service_tool_settings, register_playbook, …)

Why hybrid: Link/unlink is structurally identical across types. Create/update is not — services carry OAuth/local-mcp fields, playbooks carry markdown body and dependency detection, credentials cannot set secret values via MCP.

flowchart TB
  subgraph runtime["Tier 1 — Runtime (~8 tools)"]
    bind["bind_workspace"]
    session["get_session_binding"]
    deck["get_bound_deck"]
    pb["get_playbook"]
    proxy["call_service_tool"]
  end

  subgraph edit["Tier 2 — Deck editing"]
    link["manage_deck_card"]
    coll["list_collection"]
    create["create_deck"]
    reg_svc["register_service"]
    reg_pb["register_playbook / update_playbook"]
  end

  subgraph rare["Tier 3 — CLI / dashboard"]
    del["delete card"]
    migrate["import / export"]
  end

  bind --> deck
  deck --> proxy
  create --> bind
  coll --> link
  link --> deck

Proposed target tool surface (~14 tools)

Tier 1 — Runtime (always advertised)

ToolPurposeReplaces / notes
bind_workspaceBind session to workspace + deckKeep; optional deckId on re-bind could subsume switch_bound_deck
get_session_bindingWorkspace, effective deck, display_summaryKeep
get_decksList deck ids/names for bindKeep (small; needed before bind)
get_bound_deckFull bound deck snapshot: services, credential metadata, playbook summariesReplaces get_bound_deck + list_bound_deck_* + list_playbooks
get_playbookFull playbook body when task matches triggersKeep
update_playbookSelf-improvement loop in harnessKeep
list_service_toolsDiscover tools on one bound serviceKeep; consider lazy tier
call_service_toolProxy to bound MCP servicesKeep — core “macro”

Tier 2 — Deck & collection editing

ToolPurposeReplaces / notes
manage_deck_cardlink / unlink / reorder by card_type + card_idReplaces six add_* / remove_* tools
list_collectionAll collection cards; optional card_type filterReplaces three list_collection_*
register_serviceCreate MCP card; optional auto-linkKeep; document add_to_bound_deck: false
update_serviceMetadata updateKeep
update_service_tool_settingsEnable/disable proxied toolsKeep — not expressible as generic link
register_playbookCreate playbook + auto-linkKeep
create_deckNew deckMCP standard — agents need this for setup

Tier 3 — Not MCP (CLI / dashboard)

OperationSurface
Delete service / playbook / deckDashboard today; CLI when useful
Import / export deck or cardsCLI + dashboard — PRD_EXPORT_IMPORT.md

create_deck is MCP standard (Tier 2), not rare-tier.

Remove (breaking, next major)

ToolReplacement
get_active_deckget_bound_deck
list_active_deck_servicesget_bound_deck
list_active_deck_credentialsget_bound_deck
`add_*_to_bound_deck$ ( \times 3)$manage_deck_card` action=link
`remove_*_from_bound_deck$ ( \times 3)$manage_deck_card` action=unlink
list_bound_deck_servicesget_bound_deck
list_bound_deck_credentialsget_bound_deck
list_playbooksget_bound_deck.playbooks summaries
list_collection_services etc.list_collection

Net: ~31 → ~16 advertised tools on standard (−48%).


manage_deck_card — proposed contract

// Illustrative schema — not shipped
{
  action: "link" | "unlink" | "reorder",
  card_type: "service" | "credential" | "playbook",
  card_id: string,          // service_id | credential_id | playbook_id
  position?: number         // link + reorder only
}

Description (agent-facing):

Link, unlink, or reorder a card on the bound deck. Card must already exist in the collection (list_collection). Does not create cards or store API key secrets. On unlink, collection card remains; playbook unlink may warn if other playbooks depend on the card.

Error affordances (recognition over recall):

  • NOT_ON_BOUND_DECK — suggest list_collection + link
  • PLAYBOOK_DEPENDENCY — list dependent playbook ids (matches dashboard 409 behavior)
  • NOT_BOUND — call bind_workspace first

Read path consolidation: get_bound_deck

Today agents call get_bound_deck then list_bound_deck_services for capability rescue. Proposed single response shape:

{
  "id": "…",
  "name": "dev",
  "services": [{ "id", "name", "type", "health", "…" }],
  "credentials": [{ "id", "label", "env_name", "…" }],
  "playbooks": [{ "id", "title", "triggers" }],
  "display_summary": "◆ dev · 2 MCP · 1 keys · 3 playbooks · ⌘moss"
}

Harness update: “before declining tools, call get_bound_deck (or list_bound_deck_services during transition)” → single tool.


Alternative considered: one tool for everything

manage_agent_deck
  domain: session | deck | collection | proxy
  action: …

Rejected for v1 optimization:

  • Becomes a mini-DSL the model must learn
  • Harder to validate; errors less actionable
  • Hides security boundaries (credential secrets, OAuth)
  • Does not match host caching — one giant schema change invalidates all tool descriptors

If tool count grows again (export/import, deck templates, multi-workspace), revisit scoped tiers or MCP tool search rather than a single mega-tool.


Migration plan

PhaseWorkUser impact
1. AdditiveShip manage_deck_card, enriched get_bound_deck, list_collection; mark old tools deprecated in descriptionNone if old tools remain
2. Harness + docsUpdate agent-harness.ts, MVP.md tool table, AGENT_HARNESS.mdRe-run agent-deck setup
3. BreakingRemove deprecated tools; major version bump (@agent-deck/cli / backend)Restart Cursor/Claude to refresh MCP cache
4. MeasureLog tool invocation counts; track wrong-tool / NOT_BOUND errorsIterate descriptions

Compatibility shim (optional): deprecated add_service_to_bound_deck delegates to manage_deck_card internally for one release.


Success criteria

MetricTarget
Advertised tool count≤ 16 (standard)
Harness hot path toolsbind → get_bound_deck → call_service_tool / get_playbook (≤ 4 calls)
Deprecated aliases0 after breaking release
Agent task completionQualitative: fewer “wrong tool” failures in dogfood sessions
Context costRoughly 50% reduction in Agent Deck tool-definition tokens

Open questions

  1. switch_bound_deck vs bind_workspace — merge parameters or keep for monorepo clarity (MONOREPO_SCOPE.md)?
  2. Dynamic lazy loading — revisit only after Cursor and Claude Code honor notifications/tools/list_changed mid-session (currently they do not).
  3. Credential create via MCP — stay dashboard/CLI-only permanently (yes for secrets)?

References