Documentation
¶
Overview ¶
Package mcp is a Model Context Protocol server: the transport that lets an LLM client ask Hecate questions.
It is strictly a transport over pkg/ops. Any rule that appears here is a rule in the wrong place — the CLI, the API and this server must not be able to disagree about what "eligible" means (D32).
Two protocol eras ¶
MCP changed shape in revision 2026-07-28. Earlier revisions ("legacy") open with an `initialize` handshake that negotiates a version for the session. From 2026-07-28 ("modern") there is no handshake at all: every request carries its protocol version in `_meta`, `server/discover` replaces `initialize`, and a version the server does not support is refused per request.
This server speaks both, because neither alone is enough. A modern-only server is unusable by the many clients still on a legacy revision — the specification's own compatibility matrix says that combination simply fails — and a legacy-only server is one that has to be rewritten the first time a client updates.
Index ¶
Constants ¶
const ( // VersionModern is the stateless revision: per-request metadata, // server/discover, no handshake. VersionModern = "2026-07-28" // VersionLegacy is the newest handshake-based revision. VersionLegacy = "2025-11-25" )
Protocol revisions this server speaks, newest first.
const ActorPrefix = "mcp:"
ActorPrefix marks an action as having come through this server.
A promotion an agent performed on someone's behalf is not the same event as one that person performed, and the trail should say which. The configured actor is who authorised the server to act; the prefix is how it acted.
const ApproveIsNotAvailable = "approval is a segregation-of-duties control and is not exposed over MCP"
ApproveIsNotAvailable records why there is no `approve` tool, so that the absence reads as a decision rather than an oversight to be helpfully fixed.
Approval is a segregation-of-duties control: Fides' change gate withholds approval until a human who is not the committer has signed off, and a Gate's requireApproval exists to make a person look at a Bundle before it moves. An agent that can approve can be one of the two required eyes — and one operator driving an agent can then be both. The control would still appear in every audit trail while having stopped meaning anything, which is worse than not having it: an absent control is visibly absent.
This is not a setting. A flag to enable it would be a flag to make the guarantee untrue, and the value of a guarantee is that it holds without anyone having to check how the server was started. Approval stays with the CLI, the API and the Fides UI, where a person is the one acting.
Variables ¶
This section is empty.
Functions ¶
func SupportedVersions ¶
func SupportedVersions() []string
SupportedVersions is what server/discover advertises.
Types ¶
type Server ¶
type Server struct {
Name string
Version string
// Instructions is natural-language guidance a client shows the model about
// what this server is for. Worth writing well: it is the only place to say
// "read before you act" to something that will otherwise guess.
Instructions string
// contains filtered or unexported fields
}
Server answers MCP requests over a byte stream.
func (*Server) Handle ¶
Handle processes one message and returns the reply, or nil for a notification.
func (*Server) MustRegister ¶
MustRegister panics on failure, for wiring at startup.
func (*Server) Register ¶
Register adds a tool. Names must be unique, and a duplicate is a programming error rather than something to resolve at runtime.
func (*Server) Serve ¶
Serve reads newline-delimited JSON-RPC from r and writes replies to w.
Nothing but protocol messages may go to w: a stray print to stdout corrupts the stream and the client's failure will point anywhere but here. Logging belongs on stderr, which the specification reserves for exactly that.
type Tool ¶
type Tool struct {
// Name is what the model calls. The specification restricts these to
// letters, digits, underscore, hyphen and dot.
Name string `json:"name"`
// Title is the human-readable name a client shows.
Title string `json:"title,omitempty"`
// Description is what the model reads to decide whether to call it.
Description string `json:"description"`
// InputSchema is required and must be a JSON Schema object — never null,
// even for a tool that takes nothing.
InputSchema json.RawMessage `json:"inputSchema"`
// OutputSchema describes structuredContent. Optional, and binding: a
// server that publishes one must produce results that conform.
OutputSchema json.RawMessage `json:"outputSchema,omitempty"`
// Handler performs the call. It returns the value to put in
// structuredContent, and a human-readable rendering for content.
//
// An error it returns is a *tool execution* error — reported in the result
// with isError so the model can read it and correct itself, rather than as
// a JSON-RPC error which clients are told is less recoverable.
Handler func(ctx context.Context, args json.RawMessage) (structured any, text string, err error) `json:"-"`
}
Tool is one tool this server exposes.
func ReadTools ¶
ReadTools are the tools that only look.
Every one is a marshalling shim over pkg/ops. That is the whole design: a question answered here rather than there would be a second answer to it, and the operator would eventually be told two different things by two tools reading the same cluster (D32).
Writes are deliberately absent. They are gated on an explicit opt-in and carry a question this layer cannot answer by itself — whether an agent may satisfy a four-eyes approval — so they are tracked separately.
func WriteTools ¶
WriteTools are the tools that change something.
There are two, and there will not be a third by accident: `approve` is absent, and absent by construction rather than by configuration. See ApproveIsNotAvailable.
They enforce nothing themselves. Every rule — eligibility, upstream clearance, promotion windows, one crossing at a time — is pkg/ops', which is the same code the CLI and the controller go through. An MCP client is a client, not a bypass.