.agents/skills/optimize-integration/SKILL.md
Improve an existing Switchboard integration adapter's LLM usability — tool description enrichment, field compaction refinement, and response tuning. Use when: "optimize integration", "improve tool descriptions", "extend compaction", "make integration better for LLMs", after user story mapping, or when an LLM is making wrong tool choices or passing wrong IDs. Not for adding new integrations (use add-integration) or fixing bugs.
npx skillsauth add daltoniam/switchboard optimize-integrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Improve an existing adapter's LLM usability. Three phases, each independent — run whichever applies.
See AGENTS.md for interface contracts, project structure, and conventions.
Before optimizing, you need signal on what to optimize. Ideal inputs:
If none exist, start by mapping the top 10 user stories to tools — this reveals the high-value tools and the common mistakes.
Tool descriptions are the only context an LLM gets for tool selection. The goal is correct routing on first attempt.
Classify every tool into tiers based on user story coverage:
| Tier | Criteria | Description treatment | |------|----------|-----------------------| | 1 | Covers 80%+ of user stories (typically 3-5 tools) | Workflow routing: "Start here", "returns the data needed by X" | | 2 | Used in multi-step workflows but not entry points | Chaining hints: "Use before X to understand the schema" | | 3 | Primitives subsumed by higher-level tools | Prefer-over hints: "For full detail, prefer X" | | 4 | Identity/utility tools with clear names | Keep as-is |
For each tool, apply these patterns where applicable:
Keep descriptions to 1-3 sentences. No paragraphs.
Example — before:
"List issues for a repository"
Example — after:
"List issues for a repository. Returns open issues by default. For searching across repos, prefer search_issues."
Focus on parameters where the wrong value is a common mistake:
make ci passes (descriptions are data, not logic)search({"query": "<integration>"}) returns tools with enriched descriptionsExtend or tune compaction specs to reduce token waste. The add-integration skill covers initial compaction setup — this phase covers refinement of an existing spec set.
Handlers and compaction have distinct jobs. Mixing them causes specs and handlers to drift independently — field additions require changes in two places, and handler-level filtering is invisible to spec reviewers.
results + recordMap), resolve double-wrapped formats, tree-build from flat recordsA handler that selects specific fields during a merge loop duplicates what the compaction spec already declares. A handler that filters record types for "relevance" makes a context decision that belongs in compaction. Both cause the same failure: the compaction spec becomes an incomplete description of the tool's output shape, and changes to what the LLM sees require editing two files instead of one.
When list and get tools return the same record type but with different noise profiles, use shared field slices with list/single variance:
// List context: compact, just essentials for routing
var userFields = []string{"id", "name", "email"}
// Single-record context: more detail is acceptable (1 object, not N)
var singleUserFields = []string{"id", "name", "email", "profile_photo"}
How these get applied to specs depends on the adapter's compact_specs.go conventions. Check the existing adapter (or integrations/github/compact_specs.go as canonical reference).
The compaction engine supports both array-element whitelisting (items[].name) and nested object whitelisting (page.id, commit.message). When 2+ specs share a root segment, the engine groups them into a nested output object:
specs: ["page.id", "page.type", "page.properties"]
input: {"page": {"id":"x", "type":"page", "properties":{...}, "crdt_data":"noise", "version":42}}
output: {"page": {"id":"x", "type":"page", "properties":{...}}}
A single spec with a dot (e.g., user.login) stays flat — {"user.login": "alice"} — preserving backward compatibility. Grouping only activates at 2+ members.
TestFieldCompactionSpecs_NoOrphanSpecs passesTestFieldCompactionSpecs_NoMutationTools passes (if present)Adjust response size caps and HTTP client behavior for the adapter's actual response profile.
Only applies to raw HTTP adapters that read response bodies directly. SDK adapters handle this internally.
Measure the largest real response the adapter produces (use the heaviest list/search tool with maximum page size). Set the cap at ~2x the observed maximum.
const maxResponseSize = 512 << 10 // example: 512 KB for an adapter whose largest real response is ~230KB
http.Client.Timeout set (prevents hanging on slow APIs)io.LimitReader cap on response bodies (prevents unbounded reads)CheckRedirect blocks redirects (prevents token leaking on 3xx)make ci after each phaseThese optimizations are applied automatically at the MCP response boundary — no per-adapter work needed. Understanding them helps when debugging response shapes or writing scripts.
Arrays of 8+ objects in execute and search responses are reshaped from [{k:v},{k:v}] to {"columns":[...],"rows":[[...],...],"constants":{...}}. This eliminates per-record key repetition (28%+ savings at 8+ items).
"constants" map (e.g., filtered list where all items have state:"open")Params with identical name+description across 3+ tools on the search page are extracted to shared_parameters. Common params like owner/repo appear once instead of N times.
Scripts can project fields via third arg: api.call(tool, args, {fields: ["id","title"]}). Uses CompactAny under the hood. Use this in scripts that only need a few fields from large responses.
Use "-*_url" to exclude all fields matching a glob pattern. Valid in exclusion mode only. Caveat: catches future fields — prefer targeted exclusions when the field set is small. Invalid patterns rejected at parse time.
| Mistake | Failure it causes | Correct approach |
|---------|-------------------|-----------------|
| Handler-level field whitelist in merge/transform loops | Spec and handler drift independently — field additions require two-file edits, reviewers miss the handler's hidden filter | Handler passes all fields through; compaction spec is the single source of truth for what the LLM sees |
| Handler-level record filtering for context reduction (e.g., skipping "noisy" record types) | Filtering logic invisible to spec reviewers; no way to audit what the LLM never sees without reading handler code | Handler passes all records through; if a record type is pure noise, add a compaction spec that strips its fields down to just id and type |
| Long paragraph descriptions | LLM skips or misinterprets; wastes context | 1-3 sentences max |
| Describing implementation details (internal endpoint names, SDK methods) | Leaks internals that confuse LLMs into constructing raw API calls | Describe behavior and value to the caller |
| Compaction specs on mutation tools | Spec maintenance cost with no token savings (mutations return small confirmations) | No spec needed |
| Same field set for list and get tools | List context wastes tokens on fields only useful in single-record context (N x noise) | Use shared slices with list/single variance |
| Adding every field "just in case" | Every field costs tokens x N items — unjustified fields compound across pagination | Justify each field by the query it enables |
| Enriching Tier 4 tools that are already clear | Description churn with no routing improvement | Don't touch what doesn't need touching |
| Broad glob exclusions like -*_url | Silently excludes future upstream API fields that match the glob | Use targeted exclusions when field set is small and stable |
| Aliasing ToolDefinition.Parameters map in search | Progressive silent corruption — extractSharedParameters deletes from shared map | Always deep-copy the Parameters map when building searchToolInfo |
| Mutation handler hardcodes one parent type when API has polymorphic parents | 400/silent failure for other parent types (e.g., listAfter on a collection ID in Notion) | Branch on parent type at the op-building layer; verify API uses the same write mechanism for all parent types |
tools
Cross-model search quality benchmark for Switchboard's tool discovery. Dispatches identical search scenarios to opus, sonnet, and haiku in parallel, compiles a comparison table, and identifies optimization opportunities. Use when: "benchmark search", "test search quality", "run search benchmark", after changing scoring logic, synonyms, stop words, IDF, or tool descriptions, after adding new integrations, or when evaluating Phase 2 tag impact. Also use when the user mentions "search hit rate", "search recall", or "did search get better/worse". Not for full MCP smoke tests (use mcp-benchmark) or unit testing (use make test).
tools
Review a GitHub pull request for the Switchboard Go MCP server project. Enforces idiomatic Go, project conventions (hexagonal architecture, dispatch maps, port interfaces), test coverage, build/lint verification, and production readiness.
tools
Submit a PR review as inline GitHub comments on specific files and lines using the gh CLI.
tools
Live benchmark protocol for Switchboard's MCP server. Runs real tool-calling sequences against enabled integrations, tracks failure metrics, and identifies impediments to successful LLM tool usage. Use when: "benchmark", "test the MCP", "run user stories", "smoke test integrations", after adding/changing integrations or tools, after changing compaction specs or search logic, before releases. Not for unit testing (use make test) or load testing.