Documentation
¶
Overview ¶
Package mcpserver serves agenttool.Tool values over MCP: the serve side of MCP and the inverse of mcpclient.
The native contract is the centre and MCP is an edge: a tool's name, description and Parameters become the MCP tool definition, and the SDK's handler decodes the call, runs Execute and maps the output back to MCP content. The official Go SDK owns transports (stdio, streamable HTTP) and protocol negotiation.
server, err := mcpserver.NewServer("files", "1.0", readFile, writeFile)
...
err = server.Run(ctx, &sdk.StdioTransport{})
No policy runs here. A loop's hooks around tool calls belong to whoever hosts the loop, and this package hosts only tools; a caller who wants a policy applies it to the tools before serving them. What a policy decides on crosses, though: a tool's facts and replay claims are answered by FactsMethod, and what MCP has no field for, those claims' presence, read-only, sequential, barrier, resource and flow, is in each definition's _meta under ToolMetaKey.
One server serves many clients and one Tool value serves them all, so the call's context carries the session it arrived on: a tool that owns anything per client, a working directory, a container or a shell, keys it on SessionFrom.
Index ¶
- Constants
- func AddTools(s *sdk.Server, tools ...agenttool.Tool) error
- func ContentOf(out openresponses.FunctionCallOutputData) []sdk.Content
- func ContextWithSession(ctx context.Context, session *sdk.ServerSession) context.Context
- func Definition(tl agenttool.Tool) *sdk.Tool
- func Handler(tl agenttool.Tool) (sdk.ToolHandler, error)
- func NewServer(name, version string, tools ...agenttool.Tool) (*sdk.Server, error)
- func SessionFrom(ctx context.Context) (*sdk.ServerSession, bool)
- func SessionID(ctx context.Context) string
- type Options
Constants ¶
const DefaultAbandonAfter = 30 * time.Minute
DefaultAbandonAfter is how long a call waits for the client to come back with the answers to its questions before it is stopped; see Options.AbandonAfter. The client comes back once the user has answered, so this is human time.
const DefaultMaxRequests = 10
DefaultMaxRequests is how many requests the client makes of one call at most; see Options.MaxRequests. It is the Go SDK's limit: its client gives up on a call whose tenth request brings back a question rather than a result.
const FactsCapability = "io.github.christopherdavenport.agenttool/facts"
FactsCapability is the experimental capability under which a server that answers FactsMethod says so, at initialize and in discovery, so that a client knows to ask. Its value is an empty object. It is mcpclient.FactsCapability byte for byte.
const FactsMethod = "execution/facts"
FactsMethod is the MCP method that carries the facts claim (agenttool.Factual) and the replay claim (agenttool.Replayable) of the tools a server serves. It is mcpclient.FactsMethod byte for byte, and a test holds the two together.
It is a method of its own rather than a reserved tool name, since a tool could reach the model's tool list if a client failed to hide it and a method cannot. Its params are a list of calls, a model response's calls together, so a response of five calls costs one round trip rather than five:
{"calls": [{"name": "bash", "arguments": {"command": "cat .env > out/x"}}]}
Its result has one answer per call, in the order asked:
{"results": [{"facts": {"calls": [{"tool": "read", "args": {"path": ".env"}},
{"tool": "write", "args": {"path": "out/x"}}],
"rewrite": {...}},
"replay": "unknown"}]}
An answer carries facts when the tool makes the claim, with calls null for the call itself and an empty list for a call that amounts to nothing the tool can state, and rewrite absent for none; error in facts' place when the claim failed or no tool has the name; neither when the tool makes no claim. Replay is "safe" or "unknown", the tool's answer for the call, and is there for every tool the server serves. A tool's "keyed" answer is sent as "unknown", since MCP carries no idempotency key and a call this package serves has none to deduplicate on.
Answering never acts: the handler asks each tool's claims and never runs Execute.
const RecordMetaKey = "io.github.christopherdavenport.agenttool/record"
RecordMetaKey is the _meta key under which a result's record is served; see Handler. It is mcpclient.RecordMetaKey byte for byte, spelt out here so this module builds against the mcpclient release that predates it, and a test holds the two together.
const ToolMetaKey = "io.github.christopherdavenport.agenttool/tool"
ToolMetaKey is the _meta key of a served tool's definition under which the tool's properties cross in the listing, as facts about the tool rather than as MCP annotations, which are hints a policy must not use alone:
{"facts": true, "replay": true, "readOnly": true, "sequential": true, "barrier": true, "resource": "shell:session"}
A field that would be false or empty is left out, and a tool that has none of them carries no entry. facts says the tool makes the facts claim (agenttool.IsFactual), so a client that trusts the server asks FactsMethod about its calls and skips the request for one that does not; replay says the tool answers agenttool.Replayable, which every tool agenttool.New and agenttool.NewFunc build does, so it says the server will answer and not that the answer is other than unknown. readOnly is the tool's agenttool.Annotations ReadOnly, sequential agenttool.IsSequential and resource agenttool.ResourceOf. barrier is agenttool.IsBarrier, and comes with sequential: a client that predates barriers reads the tool as sequential and runs its batch one call at a time, which is stricter, never looser; a client that reads barrier takes the tool as a barrier alone. It is mcpclient.ToolMetaKey byte for byte.
Variables ¶
This section is empty.
Functions ¶
func AddTools ¶
AddTools registers tools on an SDK server. Each tool's Parameters is the MCP input schema verbatim, so no reflection happens in the SDK; a nil Parameters becomes an empty object schema. The handler validates the arguments against that schema, as MCP servers are expected to, so a missing required property or a wrong type is refused before the tool runs. A schema the validator cannot parse or resolve is a registration error, "mcpserver: tool NAME: schema: ...", reported before any tool is added, as agenttool.New panics for the same mistake; the validator handles JSON Schema draft-07 and 2020-12, however the $schema URI is spelt. A schema of another dialect is not refused, since it may be sound: the tool is served without the check, and Options.Logger is told. The handler then runs Execute with the raw arguments and maps the result with ContentOf; a returned error, a validation failure included, sets isError with the message as text so the calling model can see it and retry.
The call's context carries the MCP session it arrived on, which SessionFrom returns, so one Tool value can serve two clients without sharing what belongs to each. It carries the agenttool.Call too, as the executor's does, so a tool finds its call with agenttool.CallFrom on either side of the boundary.
A record crosses. A result whose Details implements agenttool.Recordable is served with its agenttool.Record under RecordMetaKey in the result's _meta, the data as a JSON string so it arrives byte for byte, and mcpclient makes it the Details on its side, so the container that served a call or the directory it spilled to reaches a session recorder through a server as it would in process. A Details that says it is recordable and cannot be recorded, an empty namespace or a value that does not marshal, does not fail the call, since the side effect has happened and a failure would invite the model to retry it: the output is served and the error text takes the record's place under the same key, where a subscriber reading the SDK result finds it.
A record a tool writes while it runs, with agenttool.WriteRecord, reaches whatever agenttool.ContextWithRecorder installed on the context the call runs under, which the handler leaves in place. The go-sdk derives each call's context from the one the session was opened with: over stdio and in memory that is the context the host passed to Run or Connect, and over streamable HTTP it is the initialize request's context in stateful mode and each request's own in stateless mode, both of which the SDK says middleware may add values to, so an HTTP host installs the recorder in middleware on the handler. That is how the SDK behaves at v1.8.0 and not a documented guarantee; a test here pins it for the in-memory transport, and the HTTP routes are described from the SDK's source rather than pinned. A host that wants no dependence on any of it wraps its tools with agenttool.Wrap and installs the recorder on the context there.
A tool asks the user a question through the agenttool.Elicitor the handler puts on the call's context when the client offers elicitation, so a tool that asks in process asks the same way served. From protocol 2026-07-28 a server may not send elicitation/create while it serves a call: the question goes back as the call's input request, under a request state that names the waiting call, and the answer arrives with the client's next request, which resumes it. The tool runs on its own goroutine meanwhile, on a context detached from the first request and carrying its values, and is cancelled when the client cancels the request it is waiting on, when the session it came on ends, or when the client does not come back within Options.AbandonAfter; over stateless HTTP a session lasts one request, so only the first and the last apply. A client that gives up and answers the open questions cancel, as mcpclient does, ends the wait at once, and the tool hears that nobody chose. The state is random and names a call only this process holds, so a server behind a load balancer routes a client's requests to one process. The Go SDK's client makes ten requests of a call at most, Options.MaxRequests, so a tool asks in nine rounds at most, and a tenth question is answered with an error at once; questions it asks together go in one. A client before 2026-07-28 is asked with elicitation/create while the call runs. A form's answer is checked against its schema, a question of a mode the client does not offer is answered agenttool.ActionCancel, and a client that offers no elicitation leaves the context as the host set it.
When the request carries a progress token, Call.OnUpdate forwards each update as a progress notification whose message is the update's text. An update whose Details is an agenttool.ProgressInfo supplies the notification's progress, total and message, so a tool that mcpclient consumed and this package serves again keeps its numbers; otherwise updates are numbered in order with no total.
The tools' claims are served too. The server answers FactsMethod, a model response's calls together, with each call's facts claim (agenttool.Factual) and replay claim (agenttool.Replayable), and advertises FactsCapability so that a client knows to ask. Answering asks the claims and never runs a tool. The facts are asked under a context that carries the session, as a call's does, so a tool that keys its state on SessionFrom answers for the client that asked. A server that has tools added by several calls answers for all of them, the one added last for a name that was added twice.
func ContentOf ¶
func ContentOf(out openresponses.FunctionCallOutputData) []sdk.Content
ContentOf maps a function call output to MCP content, as RFC 0001's MCP content mapping states. Text becomes one text block. Parts map by type: textual parts to text blocks, an input_image with a data URL to an image block and one with a plain URL to a resource link, an input_file with data to audio or an embedded blob resource and one with a URL to a resource link. Anything else is emitted as its JSON.
func ContextWithSession ¶ added in v0.0.6
ContextWithSession returns ctx carrying the MCP session a call arrived on. Handler does this for every call, so a tool reaches it with SessionFrom; a host that runs a tool outside a server, in a test or a direct call, can install one the same way.
func Definition ¶
Definition builds the MCP tool definition for tl. A tool that carries agenttool.Annotations serves them as MCP tool annotations, each hint stated rather than left to MCP's defaults, so a tool consumed by mcpclient and served again here keeps the hints it arrived with. A tool that carries none serves none, which MCP reads as open-world. A tool whose agenttool.Flow reads untrusted content or sends is served open-world whatever its annotations say, so a client that reads only MCP's hints is never looser than one that reads the flow.
The tool's properties MCP has no field for, whether it makes the facts and replay claims, read-only, sequential, barrier, resource and flow, go in the definition's _meta under ToolMetaKey, so that a client reads them as the server's statement about the tool rather than as hints, and asks FactsMethod only about the tools that claim. A tool with none of them carries no entry. A server that adds a tool with Definition and Handler rather than AddTools marks it but neither answers FactsMethod nor advertises it, so a client asks nothing.
func Handler ¶
func Handler(tl agenttool.Tool) (sdk.ToolHandler, error)
Handler builds the SDK handler that runs tl. It fails when the tool's schema cannot be resolved for validation.
func NewServer ¶
NewServer builds an SDK server named name that serves tools. It fails as AddTools does.
func SessionFrom ¶ added in v0.0.6
func SessionFrom(ctx context.Context) (*sdk.ServerSession, bool)
SessionFrom returns the MCP session of the call on ctx. One Tool value serves every client that connects, so a tool whose behaviour belongs to a client, one holding a working directory, a container or a shell, keys its state on the session: the pointer is unique to the connection and is what a host maps to whatever it owns per client. It reports false outside an MCP call, where a tool that needs a session must say so rather than share one.
func SessionID ¶ added in v0.0.6
SessionID returns the MCP session ID of the call on ctx, and "" when there is no session or the transport has none. Only a transport that negotiates session IDs, streamable HTTP, sets one; over stdio the session is the process and the ID is empty, so a host that keys state on a client uses SessionFrom and reads the ID for logs and records.
Types ¶
type Options ¶ added in v0.0.11
type Options struct {
// AbandonAfter is how long a call whose question is with the client
// waits for the answer before its tool is stopped, zero meaning
// [DefaultAbandonAfter]. A call whose session ends is stopped then,
// and one whose client answers its questions cancel when it gives
// up, as mcpclient does, hears that at once. Over stateless HTTP,
// where each request is a session of its own, this is the only
// thing that stops a call whose client gave up without doing so.
AbandonAfter time.Duration
// MaxRequests is how many requests the client makes of one call at
// most, zero meaning [DefaultMaxRequests], and negative no limit. A
// question the tool asks on the last is answered with an error at
// once rather than sent to a client that would give up on the call
// instead of answering.
MaxRequests int
// Instructions is the server's guidance for whoever uses it, given
// to the client at initialize: what the server is for and how its
// tools go together. The contract has no field for it, since a tool
// set has no description, so it is the host's to give when it builds
// the server; the client's host decides whether the model reads it.
// [Options.NewServer] serves it; a host that builds its own
// [sdk.Server] sets it in the server's options instead.
Instructions string
// Logger is told of a tool whose arguments go unvalidated, because
// its schema names a dialect the validator does not handle, and is
// the SDK server's logger under [Options.NewServer]. Nil is
// [slog.Default].
Logger *slog.Logger
}
Options tunes how tools are served. The zero value is the default, and the package's functions of the same names use it.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
stdio
command
Command stdio serves three agenttool.New tools over MCP on stdin/stdout, so any MCP client can call Go tools written against the agenttool contract.
|
Command stdio serves three agenttool.New tools over MCP on stdin/stdout, so any MCP client can call Go tools written against the agenttool contract. |