Support appending custom instructions to MCP server instructions (remote proxy, dual-era)
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 2.2k
- Forks
- 300
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 184
Description
Summary
Why: There is a small but critical amount of additional context that would
benefit every MCP server we front, not just any one server (e.g. BigQuery).
Today we have no way to inject it. We want to append a configured custom
instruction string on top of each server's default instructions, so the
server's own guidance still comes through and our context is added on top.
Append, not override — the default server instructions (if any) must be preserved.
What: Add support for appending custom instructions to the instructions field
ToolHive serves to MCP clients on the remote proxy (streamable HTTP), for both
the legacy (2025-11-25 and earlier) and modern (2026-07-28 stateless) protocol
revisions.
Background: where instructions lives per era
Today nothing in ToolHive touches instructions — the field is proxied through
untouched (.Instructions appears nowhere in pkg//cmd/). The field survives the
stateless spec migration but changes delivery vehicle:
| Era | Message carrying instructions |
|---|---|
| Legacy (≤ 2025-11-25) | initialize → InitializeResult.instructions |
| Modern (2026-07-28) | server/discover → DiscoverResult.instructions |
Same field name, same semantics (optional natural-language LLM guidance). The append
operation is conceptually identical in both eras; only the intercepted message differs.
Scope
Remote proxy (streamable HTTP):
- Legacy: intercept the
initializeJSON result and append to.instructions.
Natural seam is theResponseProcessorinterface viaModifyResponse
(pkg/transport/proxy/transparent/response_processor.go); the streamable path
currently uses aNoOpResponseProcessorthat must be replaced with a real one. - Modern: intercept the
server/discoverresult and append to.instructions. - Append is literal here — single upstream server, so there is a real "default"
to append to.
Acceptance criteria
- A configured custom string is appended to
instructionsserved by the remote
proxy (streamable HTTP) for a legacy backend, preserving the backend's default. - Same for a modern (2026-07-28) backend via
server/discover. - No change for servers/clients that don't configure the feature (empty = passthrough).
- Unit tests on both proxy response processors.
Dependencies / sequencing
This builds on the protocol-currency work:
- #5743 (Epic: MCP protocol currency and 2026-07-28 stateless readiness)
- #5729 (go-sdk migration) + #5742/#5772 (regression gate)
- #5754 (adopt go-sdk v1.7 — stateless enablement)
- #5755 (stateless transport proxy design) — the modern-era proxy interception should
be co-designed here
Legacy side (initialize) has no hard blocker and could start at any time — the
instructions field and the ResponseProcessor/ModifyResponse seam already exist on
main, and the field is identical across mcp-go and go-sdk, so it survives the migration.
There's nonetheless benefit to waiting until #5754 lands and #5755's design is set:
- Build it once as a unified dual-era change rather than shipping a legacy-only
feature that only covers one era for months until the modern half catches up. - Avoid rework — #5729's import swap would sweep the processor from mcp-go → go-sdk, and
#5755 restructures the exact proxy code the legacy path lives near (initialize-sniffing,
session logic), so building against it prematurely risks churn.
The modern side only makes sense once server/discover and stateless traffic are handled
(#5754/#5755) and should be folded into those designs rather than built standalone.
Note: the config surface (how the custom string is specified and wired to the processor)
is currently unspecified and must be defined before implementation, whenever it starts.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read pkg/transport/proxy/transparent/response_processor.go and trace the streamable HTTP path that currently uses NoOpResponseProcessor. First resolve the unspecified configuration surface and the modern server/discover interception with the designs in #5754/#5755; done means both legacy initialize and modern server/discover append configured instructions, preserve defaults, passthrough when empty, and have unit tests for both processors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100