{"slug": "mcp-at-90-tools-the-catalog-breaks-before-the-server-does", "title": "MCP at 90+ tools: the catalog breaks before the server does", "summary": "A developer outlined a platform architecture for running Model Context Protocol servers at scale, arguing that catalogs of 90+ tools fail organizationally before they fail technically. The design treats the MCP server as a tool broker alongside an LLM gateway, sharing identity, metering and audit rules, and adds credential envelopes for stored third-party secrets plus PII masking on responses. Key recommendations include category-based tool naming as a contract, per-tool permission and audit units, tool-group grants to avoid burning 30k+ tokens of agent context on schemas, and OAuth 2.1 with PKCE, resource indicators and per-group scopes.", "body_md": "Exposing one internal tool over the Model Context Protocol is a weekend project. Exposing ninety of them to humans *and* agents, without turning the MCP endpoint into a credential dump and the audit log into a fire hazard, is a platform problem.\n\nThis post is the short version of how I'd design that platform. The one-line answer: **treat the MCP server as a tool broker in the same family as your LLM gateway.** Same identity rules (no static keys), same metering shape (every invocation is an event), same audit discipline. Plus two things the LLM path doesn't need: an envelope for stored third-party credentials, and a PII masking step for what flows back.\n\n## \n  \n  \n  1. Organize the catalog by category, not by tool\n\nAt 90+ tools, the first failures are organizational, not performance. A few rules keep the catalog sane:\n\n- \n**Naming is a contract:**`{category}.{verb}_{object}` , e.g.`issues.create_ticket` ,`mail.send_draft` ,`ci.get_pipeline_logs` . Renaming a tool is a breaking change with a deprecation window, not a silent edit.\n- \n**One tool = one unit of permission, audit, and test.** A \"do everything in the issue tracker\" tool is a permission you can't grant safely, an audit row you can't read, and a test you can't write. Split until each tool is all three.\n- \n**Version in the schema, not the name** (`issues.create_ticket@v2` ), and serve all live versions during the deprecation window.\n- \n**Grant tool groups, not the universe.** The server barely notices 94 schemas. The*client* does: an agent that loads every schema can burn 30k+ tokens of context on tool definitions alone. Clients request a group (`ci` ,`mail` ), and the group is the grant unit.\n\n## \n  \n  \n  2. Tool design patterns that keep agents (and auditors) sane\n\nA tool definition worth reviewing looks roughly like this:\n\nThe patterns behind it:\n\n- \n**Idempotency keys on retryable writes.** A retried create with the same key returns the same ticket, not a duplicate. Make it a registration-time lint for write tools.\n- \n**Errors are part of the contract.** Normalize upstream failures into a small taxonomy with a`retryable` flag (and`Retry-After` when you have it). Agents retry on the flag; humans read the code.\n- \n**Reads are cheap, writes are loud.** Writes need a minimum role and a scope (project, space, folder). The audit event for a write carries the full (masked) input; for a read, just the shape.\n- \n**Bound every list.** Default`limit` 50, server cap 200, cursor pagination. An unbounded list tool is a context-window bomb.\n- \n**Return what was asked, not what you know.** Enrichment (\"related tickets\") is a second tool the agent can choose to call. Tools that over-return are tools agents can't plan around.\n\n## \n  \n  \n  3. Auth: OAuth 2.1 + PKCE, with a few non-negotiables\n\nThe happy path is simple: the client runs the authorization-code flow with PKCE against your identity platform, gets a short-lived access token, and presents it on the MCP endpoint. The server verifies it, resolves the principal, and checks tool grants. What makes it actually safe:\n\n- \n**PKCE for every client type** , including IDEs and agent runtimes. MCP clients usually can't hold a secret.\n- \n**Resource indicators.** The token's audience is the MCP server. A valid token minted for some other service gets rejected.\n- \n**Dynamic client registration is not a grant.** DCR keeps \"bring your own agent runtime\" possible (redirect URI allowlist, auth method checks), but tool access still flows through an explicit grant.\n- \n**One token-time posture for the org.** For example: 15-minute access tokens for interactive clients, 1 hour for workloads, no refresh tokens for agents (they re-authenticate through their workload identity).\n- \n**Scopes per tool group** (`mcp:ci:read` ,`mcp:issues:write` ), so the consent screen is readable instead of a wall of 94 strings.\n- \n**The client's token never travels to the SaaS.** The broker calls downstream systems with its own credential and carries the principal in-band for audit and metering. Where a downstream system has a clean per-user delegation model, use that instead; it's the stronger design.\n\n## \n  \n  \n  4. Stored secrets: envelope encryption, and why it isn't the control\n\nThe broker has to store credentials for the systems behind it. Do it with a standard envelope:\n\n- \n**Per-category DEKs** keep the blast radius at one category. Per-user tokens get a per-user DEK under the category DEK.\n- \n**Rotation is cheap by design:** the KMS rotates the KEK; a scheduled job re-wraps DEKs. Neither touches the secret ciphertexts.\n- \n**But encryption at rest only answers \"a DB leak is not a credential leak.\"** The real control is that no role (except break-glass) can*read* a secret. The broker*uses* it in-process for one call, zeroes the buffer, and the audit event records the call and the principal, never the credential. A portal should show \"credential present, last rotated N days ago\", nothing more.\n\n## \n  \n  \n  5. Mask on the way back\n\nThe LLM gateway can stay payload-blind. The MCP path can't: the tool result *is* the product. So results pass through a PII masking step before they reach the client and before anything is stored, and the per-tool `pii` field in the catalog says which inputs and outputs need it. This is also why write audits store masked inputs, not raw ones.\n\n## \n  \n  \n  The checklist version\n\n- Broker, not proxy: no static keys, every invocation metered and audited\n- Category naming, one-tool-one-permission, schema versioning, group grants\n- Idempotency, error taxonomy, bounded lists, no over-returning\n- OAuth 2.1 + PKCE, audience-bound tokens, DCR ≠ grant, group scopes\n- Envelope encryption with per-category DEKs, secrets used and never read\n- PII masking on results and on stored audit inputs\n\nThis is a condensed version of Chapter 5 of my book **[The AI Gateway Playbook](https://leanpub.com/theaigatewayplaybook)**, which also covers the LLM gateway itself: model registry, keyless auth and RBAC, quotas and budgets, a RAG team assistant, and operations runbooks. Free sample on the Leanpub page.\n\n*Disclosure: this article and the book were drafted with AI assistance from my own experience designing and running this kind of platform. All examples are generic.*", "url": "https://wpnews.pro/news/mcp-at-90-tools-the-catalog-breaks-before-the-server-does", "canonical_source": "https://dev.to/zerokdevops/mcp-at-90-tools-the-catalog-breaks-before-the-server-does-3ibo", "published_at": "2026-10-04 20:08:21+00:00", "updated_at": "2026-10-04 20:12:43.185682+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-infrastructure", "developer-tools", "mlops"], "entities": ["Model Context Protocol", "MCP"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/mcp-at-90-tools-the-catalog-breaks-before-the-server-does", "markdown": "https://wpnews.pro/news/mcp-at-90-tools-the-catalog-breaks-before-the-server-does.md", "text": "https://wpnews.pro/news/mcp-at-90-tools-the-catalog-breaks-before-the-server-does.txt", "jsonld": "https://wpnews.pro/news/mcp-at-90-tools-the-catalog-breaks-before-the-server-does.jsonld"}}