anthropics / anthropics/claude-code
[FEATURE] Expose the resolved MCP tool catalogue to plugins: add `inputSchema`/`outputSchema` to `$.tool.list()`
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
### Preflight Checklist
- [x] I have searched [existing requests](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20label%3Aenhancement) and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
### Problem Statement
### Use case
We build a security product that inventories and governs MCP servers on managed developer
endpoints. For each configured MCP server we need its **declared** tool surface — names,
descriptions, `inputSchema` — to inventory what an endpoint can reach, and to validate
`tools/call` arguments against the schema the server declared.
Scope: we are not asking for anything not already resident in the Claude Code process, and not
asking to read user conversation content.
### Why hooks are not sufficient
Hooks fire on `tools/call`, so we see only the tools someone happened to invoke, by name, with no
schema. A server can expose forty tools and we see the three that were used.
### Proposed Solution
### Asks, in priority order
1. **Add `inputSchema` and `outputSchema` to `$.tool.list()`.** The data is already resident; this
is a field addition, and the one change that unblocks schema validation.
2. **Ship `tengu_plugin_hooks_modules`** — or provide any supported way for a plugin to read the
resolved MCP tool catalogue. The flag is not in the served payload at all, so the seam is
unreachable, not merely disabled.
3. **Lift or make configurable the 300-char description truncation**, or return the untruncated
description on a catalogue-oriented call. Truncation that is fine for prompt-building is lossy
for inventory.
4. **Provide a non-interactive path.** Inventory collection is not an interactive activity;
requiring a bound interactive session rules out the scanning context entirely.
5. Lower priority: a stable, documented way to read the catalogue **without** parsing the private
transcript format.
### Alternative Solutions
### What we do today, and why we would rather not
Our fallback parses the session transcript (`~/.claude/projects//.jsonl`), whose
`deferred_tools_delta` attachment lists every MCP tool name on connect — complete, including
never-invoked tools, but names only, with `.` irreversibly sanitized to `_`. That is
unsatisfactory for both sides: it is a private format with no stability contract (its shape
changed between 2.1.246 and 2.1.247), and it means a security agent reads files containing **user
conversation content** just to obtain a tool list. A supported catalogue API would let us stop
touching those files — which we think is the strongest reason to do this.
### Priority
High - Significant impact on productivity
### Feature Category
MCP server integration
### Use Case Example
_No response_
### Additional Context
### What we measured (Claude Code 2.1.247)
- A plugin's `hooks/hooks.json` may declare `modules: ["./register.js"]`; Claude Code loads it
into a `node:vm` and passes a capability object of ~32 ops, including `$.tool.list()`.
- `$.tool.list()` returns exactly `{name, description, mcp}` — **no `inputSchema`, no
`outputSchema`** — and `description` is hard-truncated at 300 characters (a 616-char
description came back cut mid-word, no ellipsis).
- The seam is gated on the GrowthBook flag `tengu_plugin_hooks_modules`, which is **absent from
the served feature payload entirely** (556 features fetched) — not off for this user, but not
shipped to this client population.
- `$.tool.describe` is referenced in the bundle but is `undefined` at runtime.
- `$.tool.list()` refuses to run headlessly: *"not available in this mode: no interactive session
is bound in this process (a `-p` run or the SDK)"*.
- The schema **is** in the process: Claude Code's own `/mcp` panel renders both `Description:`
and `Parameters:` per tool. So omitting `inputSchema` from `$.tool.list()` is an API-shape
choice, not a data-availability limit.
Happy to test a build behind a flag.
#### Appendix — verbatim measurements (Claude Code 2.1.247)
| Observation | Detail |
|---|---|
| Plugin module seam | `hooks/hooks.json` → `modules: ["./register.js"]`, loaded into a `node:vm`, called with a capability object of ~32 ops including `$.tool.list()` |
| `$.tool.list()` shape | `{name, description, mcp}` — no `inputSchema`, no `outputSchema` |
| Description truncation | Hard cut at 300 chars; a 616-char description returned cut mid-word, no ellipsis |
| Gating flag | `tengu_plugin_hooks_modules` — absent from the served feature payload (556 features fetched); not present-and-false |
| `$.tool.describe` | Referenced in the bundle, `undefined` at runtime |
| Headless behaviour | `$.tool.list()`: *"not available in this mode: no interactive session is bound in this process (a `-p` run or the SDK)"* |
| Data is resident | `/mcp` panel renders `Description:` and `Parameters:` per tool |
| Transcript fallback | `~/.claude/projects//.jsonl`, `deferred_tools_delta` attachment, written on connect; complete tool-name list incl. never-invoked tools; names only; `.` → `_`, irreversible; shape changed between 2.1.246 and 2.1.247 |
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with hooks/hooks.json and its modules entry, then examine the plugin capability object around $.tool.list() and the reported interactive-session restriction. Done means plugins can access the resolved MCP catalogue with inputSchema and outputSchema through a supported path, with the requested truncation and non-interactive behavior addressed or explicitly scoped.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- api, devtools, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100