Documentation
¶
Overview ¶
Package mcp implements a Model Context Protocol (MCP) client for kvit-coder.
MCP is an open client–server protocol (JSON-RPC 2.0) that lets an external process expose tools to an agent. This package connects to configured MCP servers (over stdio or HTTP), discovers the tools each one advertises, and wraps every remote tool in an adapter (MCPTool) that satisfies tools.Tool. Once registered, an MCP tool is indistinguishable from a built-in tool to the rest of the system: the registry's Specs(), the agent loop, loop detection, and the text-fallback parser all treat it uniformly.
The architectural boundary is the Client interface below: transports (stdio, http) implement it, and nothing outside this package depends on the concrete transport types or on a third-party SDK. This makes the protocol implementation swappable — an official Go SDK could back Client later without touching internal/tools or the agent loop.
Dependency decision: this package ships a small in-house JSON-RPC client rather than pulling in the official MCP Go SDK, to preserve kvit-coder's deliberately small dependency tree (no network fetch at build time, no large transitive deps). The stdio transport is a few hundred lines; the Client interface keeps the choice reversible.
Index ¶
- Constants
- func IsToolName(name string) bool
- func NamespacedName(server, tool string) string
- func SanitizeSchema(schema map[string]any) map[string]any
- func SetLinePrompter(fn func(prompt string) (string, bool))
- type Approvals
- type CallResult
- type Client
- type ContentBlock
- type FileApprovals
- type Logger
- type MCPTool
- func (t *MCPTool) Call(ctx context.Context, args json.RawMessage) (any, error)
- func (t *MCPTool) Check(ctx context.Context, args json.RawMessage) error
- func (t *MCPTool) Description() string
- func (t *MCPTool) JSONSchema() map[string]any
- func (t *MCPTool) Name() string
- func (t *MCPTool) PromptCategory() string
- func (t *MCPTool) PromptOrder() int
- func (t *MCPTool) PromptSection() string
- func (t *MCPTool) PromptTemplateName() string
- func (t *MCPTool) SelfTimeout() bool
- type Manager
- func (m *Manager) Close() error
- func (m *Manager) Connect(ctx context.Context) error
- func (m *Manager) ConnectCost() (time.Duration, string)
- func (m *Manager) Defer(names map[string]bool)
- func (m *Manager) PromptSection() string
- func (m *Manager) ServerUsable(name string) bool
- func (m *Manager) SetApprovals(a Approvals)
- func (m *Manager) Status() []ServerStatus
- func (m *Manager) Summary() string
- func (m *Manager) Tools() []tools.Tool
- func (m *Manager) ToolsForServers(ctx context.Context, names []string) ([]tools.Tool, []tools.GroupNote, error)
- type ServerStatus
- type ToolDescriptor
Constants ¶
const ( NamePrefix = "mcp" Separator = "." )
Namespacing: MCP tools are registered as "mcp" + Separator + server + Separator + tool, e.g. "mcp.filesystem.read_file". The dotted form matches the convention already used for Shell.advanced and is rendered correctly by the agent's generic tool-call display. If a target model rejects dots in function names, change Separator to "__" ("mcp__filesystem__read_file", the convention Claude Code uses) here — it is the single source of truth.
const ( ConfirmBlock = "block" ConfirmAskOnce = "ask_once" ConfirmAskAlways = "ask_always" ConfirmTrust = "trust" )
Confirmation policy. MCP tools run in a separate process and can touch anything that process can reach, so workspace path safety does not apply to them. The trust model is configured by mcp.confirm (overridable per server):
block — refuse every call (load a server for inspection only) ask_once — prompt once per distinct tool name, remember for the session ask_always — prompt on every call trust — never prompt
In a headless/benchmark run with no controlling terminal, ask_* falls back to block rather than hanging.
const PromptCategory = "mcp"
PromptCategory is the system-prompt category MCP tools are grouped under. It must match the entries added to internal/tools registry category machinery.
const ProtocolVersion = "2025-06-18"
Protocol version this client implements and offers during initialize. Servers negotiate down to a version they support; a different negotiated version is logged but not treated as fatal, since servers are generally backward compatible.
Variables ¶
This section is empty.
Functions ¶
func IsToolName ¶
IsToolName reports whether a tool name belongs to the MCP namespace. The agent loop uses this to exempt MCP tools from its blanket per-tool timeout.
func NamespacedName ¶
NamespacedName builds the model-facing tool name for a server's tool.
func SanitizeSchema ¶
SanitizeSchema returns a sanitized copy of schema, leaving the input untouched. A nil or empty schema yields {"type":"object"}.
func SetLinePrompter ¶
SetLinePrompter installs the function used to ask the person at the terminal a question. Passing nil restores reading the terminal directly.
Types ¶
type Approvals ¶
type Approvals interface {
// Approved reports whether this tool has already been allowed.
Approved(tool string) bool
// Approve records an allowance. An error is reported to the caller but
// never refuses the call that was just approved: failing to write the file
// should cost a repeated prompt next turn, not the work.
Approve(tool string) error
}
Approvals remembers which MCP tools the user has already said yes to under the ask_once policy.
Where this is kept matters more than it looks. The agent is one process per turn, so a map in memory is emptied every time you say anything, which turns ask_once into ask-once-per-instruction: a browser server would prompt again for every click of every turn. Keeping it in the session directory makes ask_once mean what it says, and matches where session-scoped shell grants already go (`<session>/permissions.json`).
type CallResult ¶
type CallResult struct {
Content []ContentBlock `json:"content"`
IsError bool `json:"isError"`
}
CallResult is the outcome of a tools/call.
type Client ¶
type Client interface {
// Initialize performs the initialize → notifications/initialized handshake.
Initialize(ctx context.Context) error
// Instructions returns the guidance the server sent with its initialize
// result, or "" when it sent none or Initialize has not run.
Instructions() string
// ListTools returns every tool the server advertises (handling pagination).
ListTools(ctx context.Context) ([]ToolDescriptor, error)
// CallTool invokes a tool by its raw (server-side) name with the given
// arguments (a JSON object), returning the typed result.
CallTool(ctx context.Context, name string, args map[string]any) (*CallResult, error)
// Close shuts the connection down and releases resources (reaps the
// subprocess for stdio, closes idle connections for HTTP).
Close() error
}
Client is one connection to a single MCP server. Implementations are the stdio and HTTP transports. Methods must be safe for the agent's single-threaded use; transports additionally run a background reader for notifications.
type ContentBlock ¶
type ContentBlock struct {
Type string `json:"type"` // "text" | "image" | "audio" | "resource" | "resource_link"
Text string `json:"text,omitempty"`
Data string `json:"data,omitempty"` // base64 for image/audio
MimeType string `json:"mimeType,omitempty"` // image/audio/resource
URI string `json:"uri,omitempty"` // resource_link
Resource map[string]any `json:"resource,omitempty"` // embedded resource (uri, mimeType, text/blob)
}
ContentBlock is one typed block of a tools/call result. Only the fields relevant to the block's Type are populated.
type FileApprovals ¶
type FileApprovals struct {
// contains filtered or unexported fields
}
FileApprovals persists approvals to a JSON file, normally `<session>/mcp-approvals.json`.
The file is re-read before each check rather than cached, because turns of one session can overlap and a second agent's approval should be honoured rather than prompted for again. Reads are cheap: the file holds a handful of tool names.
func OpenApprovals ¶
func OpenApprovals(path string) (*FileApprovals, error)
OpenApprovals returns a store backed by path. The file need not exist.
func (*FileApprovals) Approve ¶
func (a *FileApprovals) Approve(tool string) error
func (*FileApprovals) Approved ¶
func (a *FileApprovals) Approved(tool string) bool
func (*FileApprovals) Path ¶
func (a *FileApprovals) Path() string
Path is the file approvals are written to, so a person can see what they have agreed to and take it back by editing or deleting it.
type Logger ¶
Logger is the minimal logging surface the package needs, satisfied by the UI writer. Kept as a local interface to avoid importing internal/ui.
type MCPTool ¶
type MCPTool struct {
// contains filtered or unexported fields
}
MCPTool adapts a single tool advertised by an MCP server to the tools.Tool interface. The rest of kvit-coder treats it like any built-in tool; the adapter delegates execution to the server over the Client.
func (*MCPTool) Call ¶
Call issues tools/call with the raw tool name under the per-call deadline and maps the typed result back to something the model can read.
func (*MCPTool) Check ¶
Check enforces the confirmation policy. There is no workspace path check: these tools run remotely and have their own trust model.
func (*MCPTool) Description ¶
Description returns the server's tool description.
func (*MCPTool) JSONSchema ¶
JSONSchema returns the server's input schema (optionally sanitized), passed through to the model as the function parameter schema.
func (*MCPTool) PromptCategory ¶
func (*MCPTool) PromptOrder ¶
func (*MCPTool) PromptSection ¶
PromptSection documents the tool for the system prompt. It names the source server so the model knows the tool is external and may have side effects.
func (*MCPTool) PromptTemplateName ¶
func (*MCPTool) SelfTimeout ¶
SelfTimeout marks the tool as managing its own execution deadline, exempting it from the agent loop's blanket 15s per-tool timeout.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns the set of MCP server clients for a session. It connects them at startup, exposes their tools as adapters for the registry, and closes them on shutdown. A missing or failing server is logged and skipped, never fatal: the agent must still run with its built-in tools.
func NewManager ¶
func NewManager(cfg config.MCPConfig, workspaceRoot string, logger Logger, tempFileMgr *tools.TempFileManager) *Manager
NewManager creates a manager. workspaceRoot is the default working directory for stdio servers that do not set their own cwd. tempFileMgr may be nil (large MCP results then truncate inline instead of spilling to a temp file).
func (*Manager) Connect ¶
Connect dials every enabled server concurrently, each under its own startup timeout, running initialize → tools/list. Failures are logged and skipped; a summary error naming the failed servers is returned for the caller to warn on (it is informational, never fatal). A no-op when MCP is disabled.
func (*Manager) ConnectCost ¶
ConnectCost reports how long the last Connect took -- the wall clock of its slowest server, since they are dialed concurrently -- and which server that was. Zero when nothing was dialed.
func (*Manager) Defer ¶
Defer marks servers as belonging to a tool group. Connect skips them, and ToolsForServers builds their adapters on demand. Call it before Connect.
func (*Manager) PromptSection ¶ added in v0.6.1
PromptSection is the system-prompt section holding the instructions of the servers connected at startup, or "" when none sent any. A server whose tools are all filtered out is left out, since its instructions would describe tools the model cannot run.
func (*Manager) ServerUsable ¶
ServerUsable reports whether a server could be reached at all: MCP is on, the server is configured, and it is not switched off. cmd/kvit-coder asks before building a tool group, so a group left in the configuration with its server disabled is not registered as a tool that can only fail.
func (*Manager) SetApprovals ¶
SetApprovals decides where "ask_once" answers are remembered. Without it they live in process memory, which under one process per turn means they are forgotten between instructions. cmd/kvit-coder passes a store in the session directory.
func (*Manager) Status ¶
func (m *Manager) Status() []ServerStatus
Status returns a snapshot of connected servers for surfacing.
func (*Manager) Summary ¶
Summary returns a one-line description of connected servers and tool counts, or "" when nothing is connected. Suitable for a startup log line.
func (*Manager) Tools ¶
Tools returns one adapter per discovered tool, after applying each server's allow/deny filter. Order is deterministic (server index × 100 + tool index) so the system prompt and request body stay byte-stable.
func (*Manager) ToolsForServers ¶
func (m *Manager) ToolsForServers(ctx context.Context, names []string) ([]tools.Tool, []tools.GroupNote, error)
ToolsForServers returns adapters for the named servers' tools, in the order the names are given, with the instructions those servers sent (see instructions.go). Nothing is dialed here when the tool list is cached: the adapters describe themselves from the cache and dial on their first call.
A server that has never been reached and is not in the cache is dialed now, because there is no other way to learn what it offers.
type ServerStatus ¶
ServerStatus is a snapshot of one server for surfacing (e.g. a /mcp view).
type ToolDescriptor ¶
type ToolDescriptor struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema map[string]any `json:"inputSchema"`
}
ToolDescriptor describes one tool advertised by an MCP server (the shape of a tools/list entry). InputSchema is JSON Schema, passed through to the model almost verbatim.