mcp

package
v0.6.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 21 Imported by: 0

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

View Source
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.

View Source
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.

View Source
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.

View Source
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

func IsToolName(name string) bool

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

func NamespacedName(server, tool string) string

NamespacedName builds the model-facing tool name for a server's tool.

func SanitizeSchema

func SanitizeSchema(schema map[string]any) map[string]any

SanitizeSchema returns a sanitized copy of schema, leaving the input untouched. A nil or empty schema yields {"type":"object"}.

func SetLinePrompter

func SetLinePrompter(fn func(prompt string) (string, bool))

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

type Logger interface {
	Debug(msg string)
	Warn(msg string)
}

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

func (t *MCPTool) Call(ctx context.Context, args json.RawMessage) (any, error)

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

func (t *MCPTool) Check(ctx context.Context, args json.RawMessage) error

Check enforces the confirmation policy. There is no workspace path check: these tools run remotely and have their own trust model.

func (*MCPTool) Description

func (t *MCPTool) Description() string

Description returns the server's tool description.

func (*MCPTool) JSONSchema

func (t *MCPTool) JSONSchema() map[string]any

JSONSchema returns the server's input schema (optionally sanitized), passed through to the model as the function parameter schema.

func (*MCPTool) Name

func (t *MCPTool) Name() string

Name returns the namespaced, model-facing tool name.

func (*MCPTool) PromptCategory

func (t *MCPTool) PromptCategory() string

func (*MCPTool) PromptOrder

func (t *MCPTool) PromptOrder() int

func (*MCPTool) PromptSection

func (t *MCPTool) PromptSection() string

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 (t *MCPTool) PromptTemplateName() string

func (*MCPTool) SelfTimeout

func (t *MCPTool) SelfTimeout() bool

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) Close

func (m *Manager) Close() error

Close shuts down every connected server. Idempotent.

func (*Manager) Connect

func (m *Manager) Connect(ctx context.Context) error

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

func (m *Manager) ConnectCost() (time.Duration, string)

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

func (m *Manager) Defer(names map[string]bool)

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

func (m *Manager) PromptSection() string

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

func (m *Manager) ServerUsable(name string) bool

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

func (m *Manager) SetApprovals(a Approvals)

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

func (m *Manager) Summary() string

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

func (m *Manager) Tools() []tools.Tool

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

type ServerStatus struct {
	Name      string
	Transport string
	Connected bool
	ToolCount int
	Err       string
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL