v1.4.0 - Tool names and descriptions
Per-tool name and description overrides
Some MCP servers ship tool names and descriptions that models handle badly. On an
open-source server you can rebuild it; on a closed-source or paid HTTP endpoint you
can't. 1.4.0 lets an operator relabel any tool in place — rename it, rewrite its
description, or both — applied to every surface the server exposes, with no upstream
rebuild.
Existing installs are unaffected until you use it: the new column is nullable, so
servers with no policy serve their tools exactly as the upstream declares them.
Per-tool overrides (#113, closes #112)
A new tool_overrides map on the Server row — upstream tool name →
{name?, description?} — with both fields optional and independent.
- A renamed tool answers to its new name only, exactly as if the upstream had been
rebuilt. No alias and no two names for one tool, so clients pointed at the old name
must be updated (called out in the UI and README). - Keyed by the upstream name, which is the stable identity: an override survives
being renamed again, and a rename never orphans its own description. The bridge
stamps a renamed tool's pre-rename name into_meta, the discovery probe lifts it to
upstream_name, and the UI keys rows off that rather than reversing the rename map —
an exposed name isn't unique. - The labels change and nothing else. Schema,
_meta,icons,execution(MCP
task support) and dispatch remain the upstream's, including open-ended input schemas
and the argument forwarding that goes with them. - Hiding wins over renaming, and hiding also frees a tool's name so another tool
may be renamed onto it. - A rename never takes a name that's already answered to. If a live tool holds the
target the rename goes inert and both tools stay reachable under their own names; the
rest of the policy still applies. Symmetrically, a rename whose source has vanished
upstream doesn't reserve its target. - Every surface at once: MCP
tools/listandtools/call, the REST routes (a
renamed tool is served at its new path segment and appears that way in the generated
openapi.json), and the group hub.
One mechanism, not two
Rather than add a second per-tool rewriting path beside the disabled_tools
middleware from #105, both controls are now applied by a single FastMCP
ToolTransform in the bridge, which deletes the hand-rolled
DisabledToolsMiddleware. Hiding is unchanged in behaviour: a disabled tool drops out
of tools/list and is refused on call with the identical Unknown tool error.
The transform copies the upstream tool with new labels instead of rebuilding it —
a rebuild drops icons and execution, replaces rather than merges _meta, and
regenerates the input schema and the argument-forwarding closure from it. It also owns
name resolution, so list_tools and get_tool can never disagree about which tools
exist, and strips the reserved identity key from every upstream tool so an upstream
can't forge an identity the UI would key policy off.
Persistence and validation
Overrides are part of the launch spec (config_hash), so a change restarts the bridge
and persists as ordinary desired state. Values are trimmed, blank fields and no-op
entries are dropped, and keys are sorted, so re-submitting the same policy in a
different order doesn't bounce the bridge.
Rename targets are validated (≤64 chars of [A-Za-z0-9_.-], bare dot-segments
refused) because the name has to survive as a REST path segment and a model-facing
function name. Colliding renames and rename chains are refused at write time, while a
rename involving a hidden tool is allowed because the bridge applies exactly that.
Unknown override fields are rejected at the HTTP boundary, so a typo can't read as a
saved override that does nothing. Sizes are bounded per description and across the
whole map, and both write paths refuse a config too large to hand the bridge in one
environment variable — checked before committing, so an accepted write can never take
a running endpoint offline.
API and UI
PATCH /api/servers/<id>
{"tool_overrides": {"do_thing": {"name": "run_report", "description": "Runs the report."}}}
Send {} to restore every tool's upstream labels. Both fields are optional on the way
in and on the way out: an override of one field is stored and echoed carrying only that
field. Accepted on create, echoed on GET, and carried through clone.
In the UI, each tool row on the server detail page gains Name and Description
fields, staged alongside the existing enable/disable switches and saved by the same
single Apply — one PATCH, one bridge restart for the whole batch. A name collision
is flagged as an early signal but doesn't block the write, since the bridge decides
name ownership against the live tool list. A row for a name that isn't in the live list
— a hidden tool or a stale override key — stays editable but offers no playground.
Fixes and maintenance
- Tool names are matched exactly.
disabled_toolsandtool_overrideskeys are no
longer trimmed on write. The key is the upstream tool's identity, so rewriting it
meant a policy could be saved against a tool that doesn't exist; a tool whose real
name carries whitespace can now be hidden or relabelled. - Dependency bumps (#111).
postcss8.5.16 → 8.5.25 andundici7.28.0 → 7.29.0
in the frontend dev toolchain.
Upgrade notes
- No migration.
tool_overridesis added via the existing forward-only
ADD COLUMNpath and is nullable, so legacy rows read as{}— no behaviour change
for servers that don't use it. - No new environment variables. Overrides are per-server config managed in the
UI/API. - Renaming is client-visible. A renamed tool stops answering to its upstream name,
so any client, prompt, or script referencing the old name needs updating. - Exported
.mcpbbundles are unaffected — per-tool policy is a property of this
instance's proxying, not of the packaged server.
Full Changelog: v1.3.1...v1.4.0
License
MIT © pacnpal