mcpmgr

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RenderSecret

func RenderSecret(format string, kv map[string]string) ([]byte, error)

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

func ThreadServerID(integrationID, threadID string) string

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. 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
	// 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

func (m *Manager) GetThreadTools(threadID string) []MCPTool

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

func (m *Manager) GetTools() []MCPTool

GetTools returns all tools from all connected MCP servers.

func (*Manager) ServerReadyErr added in v0.4.0

func (m *Manager) ServerReadyErr(id string) error

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

func (m *Manager) ServerStarted(id string) bool

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

func (m *Manager) ServerStderr(id string) string

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

func (m *Manager) StartServer(ctx context.Context, cfg MCPConfig) error

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

func (m *Manager) StopServer(id string)

StopServer disconnects and kills an MCP server.

func (*Manager) StopThreadServers

func (m *Manager) StopThreadServers(threadID string)

StopThreadServers tears down every thread-scoped MCP server bound to threadID, across all integrations. Called on thread close.

func (*Manager) ThreadServerStarted

func (m *Manager) ThreadServerStarted(integrationID, threadID string) bool

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

type Notification struct {
	ServerID string
	Method   string
	Params   map[string]any
}

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

Jump to

Keyboard shortcuts

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