Skip to content

Support appending custom instructions to MCP server instructions (remote proxy, dual-era) #5774

Description

@jerm-dro

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) initializeInitializeResult.instructions
Modern (2026-07-28) server/discoverDiscoverResult.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 initialize JSON result and append to .instructions.
    Natural seam is the ResponseProcessor interface via ModifyResponse
    (pkg/transport/proxy/transparent/response_processor.go); the streamable path
    currently uses a NoOpResponseProcessor that must be replaced with a real one.
  • Modern: intercept the server/discover result 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 instructions served 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:

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    No fields configured for Story 🗺️.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions