mcphost

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package mcphost implements a generic, self-hosted MCP server that a consumer advertises to an ACP agent as a stdio MCP server. It exposes consumer-registered tools to the agent while running the MCP JSON-RPC state machine in the consumer's own process, reached over a per-process unix socket with per-session token authentication.

Transport design (deliberately minimal, stdlib-only, no MCP SDK):

  • The agent spawns a redirector subprocess over MCP **stdio** (newline-delimited JSON-RPC 2.0). Stdio needs no port and no capability negotiation, so it works on every deployment.
  • That subprocess is a **dumb redirector** (see RunRedir): it dials the host's unix socket, writes one newline-terminated preamble line `{"token":"<tok>"}`, then io.Copy's stdin↔socket in both directions. It has no MCP knowledge.
  • The Host owns the MCP state machine: it runs the loop once per accepted socket connection, after the preamble token is resolved to a session key. The session key is bound server-side from the token, so a client can never spoof which session it attaches to.

Security: each ACP session is minted a fresh random token bound to its session key in a registry. Only a same-uid process holding that token (delivered via the spawned subprocess's env) can attach, and only to the one session the token maps to. The socket lives in a private 0700 directory and the socket file itself is chmod'd 0600.

mcphost contains zero consumer-specific logic: tool names, schemas, and behaviour are all supplied by the consumer via Host.Tool.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MaybeRunRedir

func MaybeRunRedir(cfg RedirConfig) (handled bool, err error)

MaybeRunRedir inspects os.Args: if os.Args[1] matches the configured subcommand or one of its aliases, it runs the redirector and returns handled=true with the run result. Otherwise it returns handled=false and the consumer continues normal startup. Call it early in main.

func RunRedir

func RunRedir(cfg RedirConfig) error

RunRedir is the redirector entrypoint: a dumb pipe. It reads the socket path and token from the env the parent set, dials the socket, writes the preamble, and pumps stdin↔socket. It has no MCP knowledge.

Types

type Config

type Config struct {
	// BaseDir is the directory under which the private socket directory is
	// created. Defaults to $XDG_RUNTIME_DIR, then os.TempDir().
	BaseDir string
	// DirPrefix is the MkdirTemp prefix for the private socket directory.
	// Defaults to "mcphost-".
	DirPrefix string
	// SocketName is the socket file name inside the private directory.
	// Defaults to "mcp.sock".
	SocketName string

	// RedirCommand is the executable the agent spawns for the redirector.
	// Defaults to os.Executable().
	RedirCommand string
	// RedirSubcommand is the argv token the redirector binary dispatches
	// on (e.g. "mcp-serve"). Required.
	RedirSubcommand string

	// ServerName is the MCP server name advertised to the agent in the
	// session/new McpServer config (e.g. "poe"). Required.
	ServerName string
	// ServerInfoName is the serverInfo.name returned from initialize.
	// Defaults to ServerName.
	ServerInfoName string
	// ServerInfoVersion is the serverInfo.version returned from
	// initialize. Defaults to "1".
	ServerInfoVersion string

	// EnvSocket / EnvToken are the env var names carrying the socket path
	// and per-session token to the spawned redirector. Required.
	EnvSocket string
	EnvToken  string
}

Config configures a Host. Zero-value fields fall back to the documented defaults; RedirSubcommand, ServerName, EnvSocket and EnvToken are required.

type Handler

type Handler func(sessionKey string, args json.RawMessage) (string, error)

Handler implements one registered tool. It receives the session key resolved server-side from the connection's preamble token (never client-supplied, so it cannot be spoofed) plus the raw JSON arguments object from the tools/call request. It returns the success text shown to the agent, or an error whose message is surfaced as an MCP tool error (isError). Argument decoding and validation are the handler's responsibility.

type Host

type Host struct {
	// contains filtered or unexported fields
}

Host owns the unix socket, the token→sessionKey registry, the set of registered tools, and the accept loop that runs the MCP JSON-RPC loop over each accepted connection.

func New

func New(cfg Config) (*Host, error)

New creates a Host: it validates the config, creates a private 0700 directory, and computes the socket path. It does not bind the socket yet — register tools with Tool, then call Listen. Close removes the directory.

func (*Host) Close

func (h *Host) Close() error

Close stops accepting, waits for in-flight handlers, removes the socket file, and removes the private directory.

func (*Host) Listen

func (h *Host) Listen() error

Listen creates the unix socket (replacing any stale one), tightens it to 0600, and starts accepting connections. Call Close to stop and clean up.

func (*Host) ServerConfigForSession

func (h *Host) ServerConfigForSession(sessionKey string) []acp.McpServer

ServerConfigForSession mints a fresh token bound to sessionKey and returns the acp McpServer(s) to advertise for that session's session/new. The server is a Stdio McpServer whose Command is the configured redirector binary, Args the redirector subcommand, and Env carries the socket path and token. Any previous token for the same session key is invalidated.

func (*Host) SocketPath

func (h *Host) SocketPath() string

SocketPath returns the unix socket path (known before Listen).

func (*Host) Tool

func (h *Host) Tool(name, description string, inputSchema map[string]any, handler Handler)

Tool registers a tool by name with its JSON input schema and handler. Registration order is preserved in tools/list. Registering the same name twice replaces the earlier entry in place. Not safe to call concurrently with the accept loop; register all tools before Listen.

type RedirConfig

type RedirConfig struct {
	// Subcommand is the primary argv token the redirector dispatches on.
	Subcommand string
	// Aliases are additional accepted argv tokens (e.g. a deprecated
	// legacy name).
	Aliases []string
	// EnvSocket / EnvToken are the env var names carrying the socket path
	// and token.
	EnvSocket string
	EnvToken  string
}

RedirConfig configures the dumb redirector entrypoint. Subcommand and the env var names must match the Host that spawned it (they run in the same consumer binary).

Jump to

Keyboard shortcuts

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