Documentation
¶
Index ¶
- 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) Close()
- func (m *Manager) GetThreadTools(threadID string) []MCPTool
- func (m *Manager) GetTools() []MCPTool
- func (m *Manager) ServerStarted(id string) bool
- 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 SecretResolver
Constants ¶
This section is empty.
Variables ¶
This section is empty.
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
- "yaml" — KEY: VALUE mapping
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"`
// 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|yaml. Only consulted when
// SecretRef is set.
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
Client *client.Client
Tools []MCPTool
// 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) Close ¶
func (m *Manager) Close()
Close disconnects all MCP servers (project + thread scope).
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) 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) 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 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.