Documentation
¶
Index ¶
- Variables
- func RenderSecret(format string, kv map[string]string) ([]byte, error)
- func ThreadServerID(integrationID, threadID string) string
- type MCPConfig
- type MCPServer
- type MCPTool
- type Manager
- func (m *Manager) CallTool(ctx context.Context, serverID string, toolName string, ...) (string, error)
- func (m *Manager) Capabilities(serverID string) map[string]any
- func (m *Manager) Close()
- func (m *Manager) GetPrompt(ctx context.Context, serverID, name string, args map[string]string) (string, []PromptMessage, error)
- func (m *Manager) GetThreadTools(threadID string) []MCPTool
- func (m *Manager) GetTools() []MCPTool
- func (m *Manager) ListPrompts(ctx context.Context, serverID string) ([]Prompt, error)
- func (m *Manager) ListResources(ctx context.Context, serverID string) ([]Resource, error)
- func (m *Manager) ReadResource(ctx context.Context, serverID, uri string) ([]ResourceContent, error)
- func (m *Manager) ServerInfo(serverID string) (ServerInfo, bool)
- func (m *Manager) ServerReady(id string) bool
- func (m *Manager) ServerReadyErr(id string) error
- func (m *Manager) ServerStarted(id string) bool
- func (m *Manager) ServerStderr(id string) string
- func (m *Manager) SetNotificationHandler(fn func(Notification))
- func (m *Manager) SetSecretResolver(resolver SecretResolver)
- func (m *Manager) StartServer(ctx context.Context, cfg MCPConfig) error
- func (m *Manager) StartThreadServer(ctx context.Context, integrationID, threadID string, cfg MCPConfig) error
- func (m *Manager) StopServer(id string)
- func (m *Manager) StopThreadServers(threadID string)
- func (m *Manager) ThreadServerStarted(integrationID, threadID string) bool
- type Notification
- type Prompt
- type PromptArgument
- type PromptMessage
- type Resource
- type ResourceContent
- type SecretResolver
- type ServerInfo
- type UnauthorizedError
Constants ¶
This section is empty.
Variables ¶
ErrUnauthorized is what errors.Is matches for an *UnauthorizedError: the server answered 401 and a refresh, if there was a hook to try one, did not help. It means "log in again", not "the server is broken".
Functions ¶
func RenderSecret ¶
RenderSecret serialises a flat key/value secret map into one of the supported on-disk formats. The rendered bytes are written to a per-spawn 0600 temp file whose path replaces the {{secret_file}} placeholder in an MCP server's Args/Env (see manager.spawn).
Supported formats:
- "env" — KEY=VALUE lines (gmcpail consumes this)
- "properties" — key=value lines (java .properties variant)
- "json" — a JSON object
There was a "yaml" format. It was removed with gopkg.in/yaml.v3, which was 11k LOC carried for that one branch and had no users — every config in the field renders env. A server that genuinely needs YAML can take "json" instead: YAML 1.2 is a superset of JSON, so a JSON object IS a YAML document, and any conforming parser reads it unchanged.
Keys are sorted so output is deterministic.
func ThreadServerID ¶
ThreadServerID is the convention for cfg.ID on thread-scoped servers — composite so the scheduler's resolveTool can recover the (integration, thread) pair from a single ServerID field and so tool names stay collision-free across threads.
Types ¶
type MCPConfig ¶
type MCPConfig struct {
ID string `json:"id"`
Name string `json:"name"`
Command string `json:"command"`
Args []string `json:"args"`
Env []string `json:"env"`
Timeout int `json:"timeout"` // seconds, default 30
Scope string `json:"scope"`
// URL selects the Streamable HTTP transport (http.go) instead of
// spawning Command. Headers go on every request — Authorization: Bearer
// <token> for a server that takes a static token.
URL string `json:"url,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
// Authorization, when set, is called before every HTTP request and its
// return value is sent as the Authorization header ("Bearer <token>"),
// replacing any in Headers; "" sends none. It is called often and from
// several goroutines: cache the token, and return quickly. An error
// fails the request. Not serialized — a host sets it in code.
Authorization func(ctx context.Context) (string, error) `json:"-"`
// response's WWW-Authenticate header (resource_metadata in it starts
// RFC 9728 discovery — see mcpauth). It should refresh or obtain the
// token that Authorization returns; the request is then retried once.
// A second 401, or no hook, fails with an *UnauthorizedError
// (errors.Is ErrUnauthorized). Concurrent 401s run it one at a time,
// and not at all when Authorization already returns a value different
// from the one that failed. Not serialized.
OnUnauthorized func(ctx context.Context, wwwAuthenticate string) error `json:"-"`
// SecretRef names a row in the secrets table whose plaintext is a
// JSON object (a flat KV map). When set, spawn() resolves it,
// renders it to SecretFormat, writes it to a per-server 0600 temp
// file, and substitutes the literal token {{secret_file}} in every
// Args and Env entry with that file's path. No secret value ever
// lands in Args/Env directly — only the file path. Carried in the
// integration's JSONB config (type=mcp); no schema column.
SecretRef string `json:"secret_ref"`
// SecretFormat selects the on-disk encoding of the rendered secret
// file: one of env|properties|json. Only consulted when SecretRef is
// set. A server wanting YAML takes json — YAML 1.2 is a superset of
// JSON.
SecretFormat string `json:"secret_format"`
}
MCPConfig holds the configuration for an MCP server connection.
Scope:
- "" — backwards-compat default, treated as "project"
- "project" — one shared instance per integration, spawned at daemon boot. Use when the MCP server is workspace- agnostic OR indexes the project's canonical repo_path.
- "thread" — one instance per (integration, thread) pair, spawned lazily when the thread's first dev session activates. The Args list may contain "{{worktree_root}}" placeholders that the scheduler substitutes with the thread's worktree directory at spawn time. Tears down on thread close. Use for per-workspace indexers (poly-lsp-mcp and friends) where each thread has its own worktree.
type MCPServer ¶
type MCPServer struct {
Config MCPConfig
Tools []MCPTool
// Capabilities is what the server declared in the handshake: "tools",
// "resources", "prompts", … — a client asks only for what is there.
Capabilities map[string]any
// Info is what the server said about itself in the handshake.
Info ServerInfo
// contains filtered or unexported fields
}
MCPServer wraps a connected MCP client.
type MCPTool ¶
type MCPTool struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema map[string]interface{} `json:"inputSchema"`
ServerID string `json:"server_id"`
}
MCPTool represents a tool discovered from an MCP server.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager manages MCP server connections, both project-scoped (one shared instance per integration) and thread-scoped (one instance per (integration, thread) pair). Thread-scoped servers are what makes a per-thread editor MCP work: each thread's poly-lsp-mcp indexes only that thread's worktree, so file operations stay single-threaded within a thread (the dev-lane lock already serializes Turns) while threads make progress independently.
Key conventions:
- servers[cfg.ID] — project-scoped, started at boot
- threadServers[integID][threadID] — thread-scoped, started lazily when the first dev session for the thread activates
func NewManager ¶
func NewManager() *Manager
func NewManagerWithSecrets ¶
func NewManagerWithSecrets(resolver SecretResolver) *Manager
NewManagerWithSecrets builds a Manager wired to a SecretResolver. MCP configs that carry a non-empty SecretRef will have the resolved secret rendered to a 0600 temp file and substituted into Args/Env via the {{secret_file}} placeholder at spawn time.
func (*Manager) CallTool ¶
func (m *Manager) CallTool(ctx context.Context, serverID string, toolName string, args map[string]interface{}) (string, error)
CallTool invokes a tool on the appropriate MCP server. ServerID may be a project-scoped id (one of m.servers' keys) OR a thread-scoped id produced by ThreadServerID(integID, threadID). Routes to whichever map holds it.
func (*Manager) Capabilities ¶ added in v0.7.0
Capabilities returns what a connected server declared in the handshake, or nil when it is not connected.
func (*Manager) Close ¶
func (m *Manager) Close()
Close disconnects all MCP servers (project + thread scope).
func (*Manager) GetPrompt ¶ added in v0.7.0
func (m *Manager) GetPrompt(ctx context.Context, serverID, name string, args map[string]string) (string, []PromptMessage, error)
GetPrompt renders one prompt with its arguments (prompts/get): the prompt's description and its messages.
func (*Manager) GetThreadTools ¶
GetThreadTools returns the tool set the scheduler should expose to developers on threadID: project-scoped tools (unchanged behaviour) PLUS any thread-scoped tools bound to this thread. Callers that don't care about thread scoping can continue using GetTools.
func (*Manager) ListPrompts ¶ added in v0.7.0
ListPrompts returns a server's prompts (prompts/list).
func (*Manager) ListResources ¶ added in v0.7.0
ListResources returns a server's resources (resources/list).
func (*Manager) ReadResource ¶ added in v0.7.0
func (m *Manager) ReadResource(ctx context.Context, serverID, uri string) ([]ResourceContent, error)
ReadResource returns the contents of one resource (resources/read).
func (*Manager) ServerInfo ¶ added in v0.11.0
func (m *Manager) ServerInfo(serverID string) (ServerInfo, bool)
ServerInfo returns what a connected server said about itself in the handshake; ok=false when it is not connected.
func (*Manager) ServerReady ¶ added in v0.7.0
ServerReady reports whether a project-scoped server finished tool discovery without error. A server with no tools at all (one that serves only resources or prompts) is ready once discovery returns empty.
func (*Manager) ServerReadyErr ¶ added in v0.4.0
ServerReadyErr reports why a project-scoped server discovered no tools, once discovery has finished. Returns nil while still discovering, when discovery succeeded, or when no such server is running — a caller that wants to distinguish those cases should pair it with ServerStarted.
This exists because tool discovery is async: StartServer returning nil only means the handshake worked, and a server that then fails ListTools otherwise shows up as a silent absence of tools.
func (*Manager) ServerStarted ¶
ServerStarted reports whether a project-scoped MCP server is already running for id (== cfg.ID). The Phase 3 decision executor uses this to spawn the write server (gmail-exec) on demand WITHOUT the idempotent-restart of StartServer tearing down a live process on every apply (which would kill in-flight IMAP/SMTP calls).
func (*Manager) ServerStderr ¶ added in v0.4.0
ServerStderr returns the tail of what a running server wrote to stderr, or "" when it has said nothing (or is not running). Useful for reporting a server that started but is misbehaving, where no error was ever returned.
func (*Manager) SetNotificationHandler ¶ added in v0.4.0
func (m *Manager) SetNotificationHandler(fn func(Notification))
SetNotificationHandler registers the sink for server→client notifications. Call before StartServer: handlers are attached at spawn, so a server already running keeps whatever sink it was spawned with.
func (*Manager) SetSecretResolver ¶
func (m *Manager) SetSecretResolver(resolver SecretResolver)
SetSecretResolver attaches (or replaces) the resolver after construction. Used when the Manager is built before the secrets store is available.
func (*Manager) StartServer ¶
StartServer spawns a project-scoped MCP server process and connects to it. Idempotent: calling twice for the same cfg.ID closes the previous instance.
func (*Manager) StartThreadServer ¶
func (m *Manager) StartThreadServer(ctx context.Context, integrationID, threadID string, cfg MCPConfig) error
StartThreadServer spawns a thread-scoped MCP server. integrationID identifies the project_integration the config came from; (integrationID, threadID) is the spawn key — a second call for the same pair restarts the server. Tool dispatch uses cfg.ID for routing, so callers should set cfg.ID = threadServerID(integID, threadID) before calling.
func (*Manager) StopServer ¶
StopServer disconnects and kills an MCP server.
func (*Manager) StopThreadServers ¶
StopThreadServers tears down every thread-scoped MCP server bound to threadID, across all integrations. Called on thread close.
func (*Manager) ThreadServerStarted ¶
ThreadServerStarted reports whether a thread-scoped MCP server is already running for (integrationID, threadID). The scheduler's per-Turn collectTools uses this to avoid re-spawning on every tool list.
type Notification ¶ added in v0.4.0
Notification is one server→client message, flattened to what a consumer acts on. The raw params are kept because MCP's notification methods are open-ended and this package should not become a bottleneck on which ones mean something.
func (Notification) Level ¶ added in v0.4.0
func (n Notification) Level() string
Level is the notifications/message severity, when the method carries one. Empty for methods that do not.
func (Notification) Text ¶ added in v0.4.0
func (n Notification) Text() string
Text renders the human-readable payload of a notifications/message, which is where servers put anything they want a person or a model to read. `data` is by spec any JSON value; a bare string is the common case and is returned as-is rather than re-encoded with quotes around it.
type Prompt ¶ added in v0.7.0
type Prompt struct {
Name string `json:"name"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Arguments []PromptArgument `json:"arguments,omitempty"`
}
Prompt is one entry of prompts/list.
type PromptArgument ¶ added in v0.7.0
type PromptMessage ¶ added in v0.7.0
PromptMessage is one message of a prompts/get reply. Content is one content block, kept raw for the same reason tool results are; Text renders it.
func (PromptMessage) Text ¶ added in v0.7.0
func (p PromptMessage) Text() string
type Resource ¶ added in v0.7.0
type Resource struct {
URI string `json:"uri"`
Name string `json:"name"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
MimeType string `json:"mimeType,omitempty"`
}
Resource is one entry of resources/list.
type ResourceContent ¶ added in v0.7.0
type ResourceContent struct {
URI string `json:"uri"`
MimeType string `json:"mimeType,omitempty"`
Text string `json:"text,omitempty"`
Blob string `json:"blob,omitempty"`
}
ResourceContent is one entry of a resources/read reply: Text for a text resource, Blob (base64) for a binary one.
type SecretResolver ¶
type SecretResolver interface {
Resolve(ctx context.Context, ref string) (map[string]string, error)
}
SecretResolver resolves a secret reference (a row key in the secrets table) into a flat key/value map. The concrete implementation lives server-side (internal/server/secret_resolver.go) and wraps the at-rest *secrets.Store; mcpmgr intentionally does NOT import internal/secrets so it stays free of the crypto/db dependency edge and unit-testable with a fake resolver.
type ServerInfo ¶ added in v0.11.0
type ServerInfo struct {
Name, Title, Version string
ProtocolVersion string
Instructions string
Raw json.RawMessage
}
ServerInfo is what a server said about itself in the initialize handshake: serverInfo's name, title (newer servers) and version, the negotiated protocol version, its instructions (what it is for and how to use it, when it says), and the whole result as sent, for fields beside the spec's.
type UnauthorizedError ¶ added in v0.8.0
type UnauthorizedError struct {
}
UnauthorizedError is a 401 that survived the refresh-and-retry. WWWAuthenticate is the challenge as the server sent it; an MCP server puts resource_metadata in it (RFC 9728), which is where OAuth discovery starts — see mcpauth.ParseWWWAuthenticate.
func (*UnauthorizedError) Error ¶ added in v0.8.0
func (e *UnauthorizedError) Error() string
func (*UnauthorizedError) Is ¶ added in v0.8.0
func (e *UnauthorizedError) Is(target error) bool
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package mcpauth has the discovery steps an MCP client needs before it can run OAuth against a protected server (MCP authorization, 2025-06-18 and later):
|
Package mcpauth has the discovery steps an MCP client needs before it can run OAuth against a protected server (MCP authorization, 2025-06-18 and later): |