mmmcp

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 23 Imported by: 0

README

mmmcp

mmmcp icon

mmmcp (pronounced “mmm-c-p”) combines multiple MCP servers behind one MCP endpoint. Components may be remote Streamable HTTP servers or local commands that speak MCP over stdio. The root Go package is embeddable; cmd/mmmcp is the production command.

Configuration

Configuration is one strict YAML document. Unknown fields, missing referenced environment variables, duplicate component names, invalid overrides, failed component discovery, and final identity collisions stop startup.

name: company-mcp
version: 1.0.0
listen: 127.0.0.1:8080
idleTimeout: 30s
servers:
  - name: github
    prefix: gh
    url: https://example.invalid/mcp
    headers:
      Authorization: Bearer ${GITHUB_TOKEN}
    passthroughHeaders:
      - X-Request-ID
      - X-Tenant
    tools:
      - name: search_code
        overrideName: search
        overrideDescription: Search configured repositories.
        enabled: true

  - name: local-files
    command: /usr/local/bin/files-mcp
    args: ["--root", "${PROJECT_ROOT}"]
    workingDirectory: ${PROJECT_ROOT}
    env:
      PROJECT_MODE: ${PROJECT_MODE}
    resources:
      - uri: file:///private-notes
        enabled: false

${NAME} interpolation is supported in component URLs and headers, commands, arguments, working directories, and explicit environment values. Missing variables expand to an empty string. $$ emits a literal dollar sign. The name, version, and listen fields, prefixes, override metadata, and component names are not interpolated. When exactly one component is configured, the frontend reports that component server's MCP name and version. With multiple components, the optional top-level name and version are reported, defaulting to mmmcp and dev when omitted. For remote components, passthroughHeaders copies the named headers from each incoming HTTP request to the downstream MCP request. Explicit values in headers take precedence when both settings name the same header.

With one component and no explicit prefix, tool names, prompt names, resource URIs, and resource templates are exposed without namespace wrapping (after any configured overrides). With multiple components, tool and prompt identities are exposed as <prefix>__<local-name>, while resource URIs and templates use mmmcp+<prefix>:<original-or-overridden-uri>. An explicit prefix is always honored; otherwise each component name is sanitized deterministically when namespacing is required. Overrides identify the original component-local feature, apply before the namespace, and default to enabled. Only an explicit enabled: false hides a feature. Supported MCP resource URI fields are rewritten on routed results; arbitrary tool JSON is not inspected.

Running

Build and run the HTTP frontend:

go build -o mmmcp ./cmd/mmmcp
./mmmcp -config ./mmmcp.yaml -transport http

The HTTP frontend defaults to 127.0.0.1:8080 when neither YAML, environment, nor flags provide an address. It serves MCP at /; a reverse proxy may mount the handler at another path.

GET /healthz is a dependency-free liveness probe. GET /readyz checks the default catalog and pings the default event store with a short timeout. A failed catalog refresh reports degraded with HTTP 200 while the last-known-good catalog remains usable; an unavailable catalog or store returns HTTP 503. Both endpoints support HEAD, disable caching, and expose stable reason codes rather than dependency errors or configuration values. Request-scoped configurations and DSNs are intentionally outside the global readiness check.

Run one persistent frontend session over stdin/stdout:

./mmmcp -config ./mmmcp.yaml -transport stdio

Flags take precedence over environment variables, which take precedence over YAML values:

Flag Environment Purpose
-config MMMCP_CONFIG YAML path; defaults to mmmcp.yaml
-transport MMMCP_TRANSPORT http or stdio; defaults to http
-listen MMMCP_LISTEN HTTP address override
-dsn MMMCP_DSN storage DSN; an explicit empty value selects default SQLite

SIGINT and SIGTERM stop accepting HTTP traffic, drain the HTTP server, close frontend and downstream MCP sessions, terminate owned command components, and close database pools. Logs do not print configuration values or DSNs.

Session Model

Current MCP 2026-07-28 HTTP requests are stateless. Every routed operation opens a one-off downstream client session; command components therefore start and stop one process per operation. Modern interactive exchanges retain MCP multi-round-trip input requests, responses, and request state.

Legacy Streamable HTTP and stdio frontends are stateful. Each frontend session, complete configuration fingerprint, and component receives an isolated downstream session. Command processes start lazily, remain active while calls or frontend streams are active, retire after idleTimeout (30 seconds by default), and restart on the next operation. Sampling, elicitation, roots, progress, logging, resource updates, and list changes stay bound to the originating frontend session.

Command children never inherit the complete host environment. The baseline is limited to PATH, PWD, TMPDIR, TMP, TEMP, LANG, LC_ALL, LC_CTYPE, and TZ; Windows also includes SystemRoot, WINDIR, ComSpec, and PATHEXT. Explicit component env entries are appended last and win on duplicate names. There is no shell expansion or OS-level sandboxing.

Library

cfg, err := config.LoadFile("mmmcp.yaml", config.LoadOptions{LookupEnv: os.LookupEnv})
if err != nil {
    return err
}

composite, err := mmmcp.New(ctx, cfg, mmmcp.Options{Logger: slog.Default()})
if err != nil {
    return err
}
defer composite.Close()

http.Handle("/mcp", composite.HTTPHandler())

ContextWithConfig attaches a complete immutable configuration snapshot. It replaces the default configuration for that request or stdio process session; components, credentials, overrides, and timeouts are never merged. Storage is selected independently with Options.DSN or ContextWithDSN.

request = request.WithContext(mmmcp.ContextWithConfig(request.Context(), tenantConfig))

Use Composite.RunStdio(ctx) to serve stdin/stdout. A configuration attached to that context applies to the complete stdio frontend session.

Storage

Set storage with Options.DSN (or -dsn/MMMCP_DSN in the CLI). An empty DSN selects a local SQLite database. PostgreSQL URLs and keyword DSNs, MySQL driver DSNs and mysql:// URLs, SQLite file: and sqlite:// URLs, and plain SQLite paths are supported. The selected database is opened, pinged, and migrated before serving. Stateful SSE event streams remain bound to the store on which they opened even if a later request selects another configuration.

Examples:

postgres://user:password@db.example/mmmcp?sslmode=require
host=db.example dbname=mmmcp user=mmmcp sslmode=require
user:password@tcp(db.example:3306)/mmmcp?parseTime=true
mysql://user:password@db.example:3306/mmmcp?parseTime=true
file:/var/lib/mmmcp/mmmcp.db
./mmmcp.db

Container

docker build -t mmmcp .
docker run --rm -p 127.0.0.1:8080:8080 \
  -v "$PWD/mmmcp.yaml:/etc/mmmcp/config.yaml:ro" \
  -v mmmcp-data:/var/lib/mmmcp \
  mmmcp -config /etc/mmmcp/config.yaml -listen 0.0.0.0:8080

The runtime user is non-root. /var/lib/mmmcp is the writable persistence volume and contains the default mmmcp.db. The image contains CA certificates and the mmmcp binary only in addition to the minimal Alpine runtime. Build a derivative image when command-backed MCP component binaries are needed:

FROM ghcr.io/obot-platform/mmmcp:latest
COPY --chown=mmmcp:mmmcp files-mcp /usr/local/bin/files-mcp

Tagged releases publish Linux, macOS, and Windows archives, checksums, and a Linux amd64/arm64 image at ghcr.io/obot-platform/mmmcp.

Current Limitations

  • mmmcp doesn't properly propogate OAuth
  • Windows support is untested

Documentation

Overview

Package mmmcp combines configured MCP components behind one server.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConfigFromContext

func ConfigFromContext(ctx context.Context) (*config.Config, bool)

ConfigFromContext returns the complete configuration snapshot attached to ctx.

func ContextWithConfig

func ContextWithConfig(ctx context.Context, cfg *config.Config) context.Context

ContextWithConfig attaches a complete configuration snapshot to ctx.

func ContextWithDSN

func ContextWithDSN(ctx context.Context, dsn string) context.Context

ContextWithDSN selects event storage for work performed with ctx. An empty DSN explicitly selects SQLite.

func DSNFromContext

func DSNFromContext(ctx context.Context) (string, bool)

DSNFromContext returns the event storage DSN attached to ctx.

Types

type AuthorizationError

type AuthorizationError = component.AuthorizationError

AuthorizationError reports an authorization challenge returned by an HTTP component.

type Composite

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

Composite is a reusable composite MCP server.

func New

func New(ctx context.Context, cfg *config.Config, opts Options) (*Composite, error)

New discovers the configured components and creates a composite server.

func (*Composite) Close

func (c *Composite) Close() error

Close prevents new requests and releases component resources.

func (*Composite) HTTPHandler

func (c *Composite) HTTPHandler() http.Handler

HTTPHandler returns the Streamable HTTP MCP, health, and readiness handler.

func (*Composite) Refresh

func (c *Composite) Refresh(ctx context.Context) error

Refresh recompiles the default configuration's immutable catalog.

func (*Composite) RunStdio

func (c *Composite) RunStdio(ctx context.Context) error

RunStdio serves one persistent MCP frontend session over stdin and stdout. A configuration attached to ctx is authoritative for the entire session.

type Options

type Options struct {
	Logger     *slog.Logger
	HTTPClient *nethttp.Client
	// OAuth supplies a component-specific OAuth handler for downstream HTTP clients.
	OAuth http.OAuthHandlerProvider
	// DSN selects the default event storage. An empty DSN selects SQLite.
	DSN string
	// LookupEnv supplies the allow-listed baseline for command components.
	LookupEnv func(string) (string, bool)
	// CommandTerminateDuration controls each SDK command shutdown stage.
	CommandTerminateDuration time.Duration
	// PageSize limits each composite feature-list page. Zero returns a complete family.
	PageSize int
	// Storage controls SQL connection pools, data location, and event retention.
	Storage storage.Options
}

Options configures the composite runtime.

Directories

Path Synopsis
Package catalog compiles immutable composite feature snapshots and routes.
Package catalog compiles immutable composite feature snapshots and routes.
cmd
mmmcp command
Package component defines the downstream MCP boundary used by the catalog and composite server.
Package component defines the downstream MCP boundary used by the catalog and composite server.
http
Package http implements Streamable HTTP component clients.
Package http implements Streamable HTTP component clients.
stdio
Package stdio implements isolated command-backed MCP components.
Package stdio implements isolated command-backed MCP components.
Package namespace constructs collision-safe MCP identities.
Package namespace constructs collision-safe MCP identities.
scripts
imagecheck command
Package storage provides persistent state used by the composite server.
Package storage provides persistent state used by the composite server.
migrations
Package migrations embeds and applies storage schema migrations.
Package migrations embeds and applies storage schema migrations.
Package testserver provides reusable HTTP MCP component fixtures.
Package testserver provides reusable HTTP MCP component fixtures.
stdiohelper command

Jump to

Keyboard shortcuts

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