mcp

module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: MIT

README

MCP Server

coverage

A configurable MCP (Model Context Protocol) server that exposes various tools through the Model Context Protocol. It supports multiple transports (stdio, SSE, streamable HTTP) and is configured declaratively from a YAML or JSON file using a sources: pipeline.

Features

  • Multiple transport support — stdio, SSE, and streamable HTTP
  • YAML and JSON configuration — flexible config file formats
  • Sources pipeline — each source is a named connection that exposes one or more MCP tools, dispatched and registered at startup
  • Tool naming and filtering — per-source tools.prefix, tools.remove, tools.enable_only, and tools.read_only adjust the exposed tool set
  • Built-in source types — HTTP, PostgreSQL, proxy, GitLab, Yandex Tracker/Wiki, English validation, GitHub, Websearch, Woodpecker, Shell, FS, Sequential Thinking, Temporal (30 tools — workflow/activity/schedule lifecycle, query/signal, and batch operations)

Installation

go install go.amidman.dev/mcp/cmd/mcp@latest

Usage

mcp --config config.yaml [--transport stdio|sse|streamable] [--addr :8080]

Flags:

Flag Description Default
--config Path to config file (required)
--transport Transport type stdio
--addr Listen address (SSE/streamable) :8080

Configuration

Configuration is written in YAML or JSON. A config file has two parts: a set of top-level identity fields, and a sources: map that names each connection.

Top-level identity
name: my-mcp-server
title: "My MCP Server"
version: "1.0.0"

name and version default to mcp and 1.0.0 when omitted; title is optional.

Sources

A source is a named connection that exposes one or more MCP tools. Each key under sources: is the source name you choose; the value carries the type, a connect: map (type-specific), and an optional tools: block.

sources:
  forgejo:
    type: proxy
    connect:
      command: forgejo-mcp-server
    tools:
      prefix: forgejo
      remove:
        - "branch_protection"

The server connects each source at startup (via the per-type Connect implementation), applies the tools: adjustments, and registers the surviving tools.

Top-level before section

Some setups need operator-controlled shell wiring (typically kubectl port-forward to expose cluster-internal services) to be in place before any source connects. The optional top-level before: list declares those commands. Each entry is a long-lived sh -c invocation spawned in parallel; the server start proceeds to source.Apply only after every declared healthcheck has finished (passed, failed, or timed out).

before:
  - command: "kubectl port-forward svc/postgres-primary 5432:5432 -n primary"
    healthcheck:
      tcp: "127.0.0.1:5432"
      interval: 500ms
      timeout: 10s
  - command: "kubectl port-forward svc/postgres-events 5433:5432 -n events"
    healthcheck:
      tcp: "127.0.0.1:5433"

before fields:

Field Description Required
command Shell command string passed to sh -c Yes
healthcheck Optional readiness probe (see below) No

healthcheck fields:

Field Description Required Default
tcp host:port for the TCP-readiness probe Yes
interval Per-attempt deadline and retry delay No 1s
timeout Overall healthcheck budget No 30s

Failure semantics. Spawn failures, non-zero exits, and timed-out healthchecks are all logged at ERROR. Nothing aborts the server: even when every healthcheck times out, source.Apply still runs through the common path, so any source whose dependencies happen to be reachable connects normally.

Lifetime and cleanup. The spawned sh -c processes live for the entire server lifetime. When the server receives SIGINT or SIGTERM, the signal.NotifyContext cancellation propagates through exec.CommandContext and kills every child.

Parallel-by-default. All commands and all healthchecks run in parallel — shell.RunBefore's wall time is bounded by the slowest healthcheck, never the sum.

A before: entry without healthcheck is fine: the spawned command is fire-and-forget, and RunBefore does not block on it (the entry's purpose is operator boot wiring, not readiness gating).

Restart policy

Long-lived before-commands like kubectl port-forward can die during the server's lifetime — the upstream pod restarts, the network blips, the port-forward session drops. By default, a single failure ends the entry for the rest of the server's lifetime. The optional restart: block on each entry changes that: a non-signal-error exit triggers an automatic respawn of the same command.

before:
  # Most common pattern: keep the tunnel alive forever, no cap.
  - command: "kubectl port-forward svc/postgres-primary 5432:5432 -n primary"
    healthcheck:
      tcp: "127.0.0.1:5432"
    # restart omitted → MaxAttempts=0 → restart forever, 5s delay
  - command: "kubectl port-forward svc/postgres-events 5433:5432 -n events"
    healthcheck:
      tcp: "127.0.0.1:5433"
    restart:
      delay: 2s               # explicit delay; still no cap
  - command: "some-flaky-tunnel"
    restart:
      max_attempts: 5         # give up after 5 total attempts
      delay: 10s

restart fields:

Field Description Required Default
max_attempts Total spawn attempts including the first; 0 (the default) means unbounded No 0
delay Wait between attempts; honors server-ctx cancellation (SIGINT/SIGTERM) No 5s

max_attempts semantics:

Value Meaning
restart: omitted No policy. Spawn exactly once, current behavior.
restart: {} (empty) or max_attempts: 0 Restart forever until ctx cancels or clean exit.
max_attempts: 1 Run once; never restart.
max_attempts: N (N ≥ 2) Up to N total spawn attempts, then ERROR "restart budget exhausted".

Restart triggers (only on these conditions):

  • The spawn exited non-zero and was not terminated by a signal
  • The server context has not been cancelled

Restart does NOT trigger on:

  • Clean exit (exit 0) — the operator can wrap the command in while true; do ...; done if they want infinite lifetime
  • Signal-terminated exit — that's the server's SIGINT/SIGTERM cleanup path
  • Server context already cancelled — function returns immediately, no respawn

Healthcheck is not re-run on restart. The healthcheck is a one-shot startup readiness gate; restart is a separate "keep alive" concern. Re-verifying readiness post-restart can be added as a follow-up feature.

Logging. Each respawn logs at INFO with index, command, attempt, delay. Bounded exhaustion (when max_attempts is hit) logs at ERROR with attempts, max_attempts, and the last error.

Tool naming (tools.prefix)

Every tool a source exposes is prefixed before registration. The effective prefix is tools.prefix exactly as set: there is no source-name fallback. Omit tools.prefix to leave tool names unchanged; set it explicitly to namespace tools across sources or to shorten long upstream names.

The prefix is concatenated to the tool's base name without a separator, so include any desired separator (e.g. _) in the prefix itself.

For example, an english source named grammar exposes a base tool validate_english; with no tools.prefix it stays validate_english. Setting tools.prefix: en_ yields en_validate_english.

Duplicate tool names are a config error

Tool names are unique across the server. If two sources (or two tools within one source) end up with the same name, source.Apply returns an error like:

source "users": duplicate tool name "http" (already registered by source "weather")

and the server fails to start with no tools registered. The MCP Go SDK silently replaces a tool of the same name on AddTool; the apply step surfaces this as a hard config error so the conflict is visible at startup, not at the first call to the wrong tool. To resolve, either set distinct tools.prefix values on the colliding sources or remove one of them.

Tool filtering (tools.remove)

tools.remove is a list of regex patterns. Each pattern is matched against the tool's final, prefixed name; matching tools are dropped. Patterns are applied after prefixing, so author them against the public-facing names. A pattern that fails to compile is ignored.

sources:
  forgejo:
    type: proxy
    connect:
      command: forgejo-mcp-server
    tools:
      remove:
        - "^forgejo_branch_protection"
Tool whitelisting (tools.enable_only)

tools.enable_only is the whitelist mirror of tools.remove: a list of regex patterns matched against the tool's final, prefixed name. Tools matching any pattern are kept; tools that match none are dropped. The intended use is to expose a small, explicit set of upstream tools when the default tool set is too broad to safely hand to the LLM.

tools.enable_only and tools.remove are mutually exclusive on the same source. Setting both causes LoadSources to return an error of the form source "<name>": tools.remove and tools.enable_only are mutually exclusive and the server fails to start — the two fields have opposite intent (drop matching vs keep matching only), and combining them produces ambiguous behavior on tools that match only one of the two pattern sets. See Tool filtering (tools.remove) for the corresponding blacklist contract.

As with tools.remove, a pattern that fails to compile is ignored (not fatal) — bad regex does not abort server startup.

Middleware ordering. The full chain is applyReadOnly → applyPrefix → applyRemove → applyEnableOnly, so enable_only operates on the post-prefix, post-remove name. Author patterns against the public-facing prefixed names (the same convention as tools.remove):

sources:
  forgejo:
    type: proxy
    connect:
      command: forgejo-mcp-server
    tools:
      prefix: forgejo_
      enable_only:
        - "^forgejo_(repo|issue|pull_request)_"
        # every other forgejo_* tool is dropped

A tool whose embedded *mcp.Tool is nil has no public name to whitelist against and is dropped (the opposite of tools.remove, which keeps such entries because "no match" means "keep" in a remove-only world).

Read-only filtering (tools.read_only)

When tools.read_only is true, the source exposes only tools whose embedded *mcp.Tool.Annotations.ReadOnlyHint == true. The flag is consumed by the per-type implementer: each Connect sets ReadOnlyHint on the tools it returns (the english, sequentialthinking, fs, and HTTP-GET sources mark all their tools read-only; the http, postgres, shell, and tracker sources mix read-only and mutating tools).

The filter runs before tools.prefix and tools.remove, so a remove pattern written against the prefixed name only sees the read-only survivors.

If the filter yields zero tools, the apply succeeds (no tools are registered for that source) and a warning is logged identifying the source. This is a likely configuration mistake — the user asked for read-only tools but the per-type Connect did not produce any — so it surfaces in logs without aborting server startup.

A tool whose embedded *mcp.Tool.Annotations is nil is treated as "unknown" and dropped under read_only: true. The implementer signals "I don't know whether this tool is read-only" by leaving Annotations nil; the dispatcher honors that signal.

sources:
  gitlab_readonly:
    type: gitlab
    connect:
      token: ${GITLAB_TOKEN}
    tools:
      read_only: true
Full example
name: my-mcp-server
title: "My MCP Server"
version: "1.0.0"

before:
  - command: "kubectl port-forward svc/postgres-primary 5432:5432 -n primary"
    healthcheck:
      tcp: "127.0.0.1:5432"
      interval: 500ms
      timeout: 10s

sources:
  maindb:
    type: postgres
    connect:
      datasource: "postgres://user:pass@localhost:5432/mydb?sslmode=disable"

  github:
    type: github
    connect:
      token: "ghp_xxxxxxxxxxxx"
      # host: github.com (default; or "github.example.com" for GitHub Enterprise)
      # toolsets: [repos, issues, pull_requests, ...] (optional; default is all)
      # read_only: false (optional; restrict to read-only tools)

  # Whitelist a narrow slice of upstream tools via enable_only:
  # every tracker_* tool NOT matching the patterns below is dropped.
  work_focused:
    type: tracker
    connect:
      token: "your-oauth-token"
      org_id: "your-org-id"
    tools:
      prefix: tracker_
      enable_only:
        - "^tracker_(get_issues|get_issue|search_issues)$"

  remote:
    type: proxy
    connect:
      url: https://mcp.example.com/mcp
      headers:
        Authorization: "Bearer token123"
      transport: sse

  work:
    type: tracker
    connect:
      token: "your-oauth-token"
      org_id: "your-org-id"

  checker:
    type: english

  myproject:
    type: gitlab
    connect:
      token: "glpat-xxxxxxxxxxxx"

  search:
    type: websearch
    connect:
      brave_api_key_env: BRAVE_API_KEY
      max_results: 10
      timeout: 10s

  weather:
    type: http
    connect:
      url: https://api.weather.example.com/v1/forecast
      method: GET
      description: Get weather forecast for a location
      headers:
        Accept: application/json

  ci:
    type: woodpecker
    connect:
      token: "ci_xxxxxxxxxxxx"
      api_url: "https://ci.example.com/api"
    tools:
      prefix: wp_

  ops:
    type: shell
    connect:
      working_dir: /home/user/project
      shell: /bin/zsh
      shell_flags: ["-lic"]
      env:
        HOME: /home/user

  project_files:
    type: fs
    connect:
      allowed_paths:
        - /home/user/project

  thinker:
    type: sequentialthinking

  workflows:
    type: temporal
    connect:
      host: my-cluster.tmprl.cloud:7233
      namespace: production
      api_key: "${TEMPORAL_API_KEY}"
    tools:
      prefix: t1

Source Types

Type Description
HTTP Forwards requests to HTTP endpoints with configurable method, headers, and body.
PostgreSQL Database introspection tools — list schemas, tables, execute read-only queries, and get detailed table info with recursive FK traversal.
Proxy Proxies tools from another MCP server (HTTP or stdio). Supports custom environment variables for child processes.
Tracker Yandex Tracker and Wiki API tools — CRUD on issues and comments, wiki pages, and retrospective reports.
GitLab GitLab API tools — fetch merge request discussions (comments, review threads, system notes) and commits.
GitHub Official GitHub MCP server embedded in-process (single binary, no external github-mcp-server needed).
English English grammar and spelling validation via LanguageTool API.
Websearch Web search (Brave, DuckDuckGo), news search, image search, URL content extraction, and provider listing.
Woodpecker Woodpecker CI/CD management — list repositories, inspect and manage pipeline runs, and read step logs directly via MCP.
Shell Executes shell commands via sh -c and returns stdout, stderr, and exit code. A drop-in replacement for the host's built-in shell tool.
FS Path-confined file operations (read, write, edit, list, move, copy, delete, search, stat) gated by an operator allowlist.
Sequential Thinking Dynamic, reflective problem-solving tool — break problems into steps, revise earlier thoughts, and branch into alternative reasoning paths. Go port of @modelcontextprotocol/server-sequential-thinking.
Temporal Temporal workflow orchestration — 30 tools covering workflow lifecycle, standalone activity lifecycle, schedule management, query/signal, and batch operations (signal/cancel/terminate with bounded concurrency). Supports mTLS and API-key auth for Temporal Cloud.
HTTP Source

Forwards MCP tool calls as HTTP requests to any HTTP endpoint. Supports all standard HTTP methods, configurable headers, query parameters, JSON bodies, and form data. The source exposes a single tool named http; with no tools.prefix override it stays http.

connect fields:

Field Description Required
url Target HTTP endpoint URL Yes
method HTTP method (GET, POST, PUT, DELETE, etc.) Yes
description Tool description exposed to the LLM No
headers Default HTTP headers sent with every request No

Example:

sources:
  weather:
    type: http
    connect:
      url: https://api.weather.example.com/v1/forecast
      method: GET
      description: Get weather forecast for a location
      headers:
        Accept: application/json
        X-API-Key: "your-api-key"
PostgreSQL Source

Database introspection tools for PostgreSQL. Connects using the pgx driver and exposes tools for listing schemas/tables, executing read-only SQL queries, and fetching detailed table information with recursive foreign key traversal. The connection is opened (and validated) at startup.

connect fields:

Field Description Required
datasource PostgreSQL connection string (pgx) Yes

Example:

sources:
  mydb:
    type: postgres
    connect:
      datasource: "postgres://user:pass@localhost:5432/mydb?sslmode=disable"

Exposed tools (base names; the tools.prefix, when set, is prepended at registration):

Base tool Description
list_schemas List all schemas in the database
list_tables List all tables in a specific schema
execute_query Execute a read-only SQL query with optional prepared-statement parameters
get_table_info Get comprehensive table info: columns, indexes, constraints, comments, and recursive FK relationships
Proxy Source

The proxy source forwards tools from another MCP server. It supports two connection modes:

  • HTTP mode — connect to a remote MCP server via streamable HTTP (default) or SSE transport.
  • Command (stdio) mode — launch a local MCP server binary and communicate over stdio.

connect fields:

Field Description Required (one of)
url Remote MCP server URL (HTTP mode) url or command
headers HTTP headers sent with every request (HTTP mode) No
transport HTTP transport: streamable (default) or sse No
command Path to local MCP server binary (stdio mode) url or command
args Arguments passed to the command No
env Additional environment variables for the child process No

The env map is merged with the parent process environment, so the child process inherits all existing env vars plus the ones you specify. Upstream tool names are preserved verbatim; only the source prefix is prepended.

HTTP mode example:

sources:
  remote:
    type: proxy
    connect:
      url: https://mcp.example.com/mcp
      headers:
        Authorization: "Bearer token123"
      transport: streamable  # "streamable" (default) or "sse"

Command (stdio) mode example with environment variables — for users who prefer to run their own github-mcp-server binary:

sources:
  github:
    type: proxy
    connect:
      command: github-mcp-server
      args:
        - stdio
      env:
        GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_xxxxxxxxxxxx"

The native type: github source is the recommended path (see GitHub Source below); the proxy example above is a fallback for users who want to pin their own github-mcp-server version.

GitHub Source

The GitHub source embeds the official github.com/github/github-mcp-server module in-process. There is no separate github-mcp-server binary to install — the upstream's tool inventory is loaded into the parent server at startup, and tool calls are forwarded through an in-memory MCP transport.

The upstream's module is pinned to v1.3.1-0.20260617160418-4f73cfd1db14 (commit 4f73cfd1... from 2026-06-17). To upgrade, bump the version in go.mod.

connect fields:

Field Description Required Default
token GitHub personal access token (ghp_..., fine-grained, or GitHub App) Yes
host GitHub host (e.g. github.com or github.example.com for Enterprise) No github.com
toolsets List of upstream toolset names to enable (e.g. repos, issues) No All toolsets
read_only Restrict the upstream to read-only tools No false

Basic example (GitHub.com, all toolsets, read+write):

sources:
  github:
    type: github
    connect:
      token: "ghp_xxxxxxxxxxxx"

GitHub Enterprise example (specific host, restricted toolset, read-only):

sources:
  github:
    type: github
    connect:
      token: "ghp_xxxxxxxxxxxx"
      host: "github.example.com"
      toolsets:
        - repos
        - pull_requests
      read_only: true

The upstream's tool names (e.g. create_or_update_file, search_repositories, get_me) are passed through unchanged. Set tools.prefix in the source config to namespace them — for example, tools: { prefix: "github_" } renames get_me to github_get_me.

Tracker Source

Yandex Tracker and Wiki API tools — search issues, get details and comments, create and update issues and comments, read wiki pages, get issue links, and generate retrospective reports.

Authentication: Two options are supported:

  1. Direct OAuth token — set token and org_id.
  2. OAuth device flow — set client_id, client_secret, and org_id. On startup, the source guides you through the Yandex OAuth device authorization flow automatically.

connect fields:

Field Description Required
token OAuth token for Yandex Tracker Yes* (or OAuth flow)
client_id OAuth application client ID (for device flow) Yes* (or direct token)
client_secret OAuth application client secret (for device flow) Yes* (with client_id)
org_id Organization ID for Yandex Tracker Yes
base_url Tracker API base URL No
wiki_base_url Wiki API base URL No
oauth_token_url OAuth token endpoint URL No
oauth_device_url OAuth device code endpoint URL No
cloud_org Use X-Cloud-Org-ID header instead of X-Org-ID No

Direct token example:

sources:
  work:
    type: tracker
    connect:
      token: "your-oauth-token"
      org_id: "your-org-id"

OAuth device flow example:

sources:
  work:
    type: tracker
    connect:
      client_id: "your-client-id"
      client_secret: "your-client-secret"
      org_id: "your-org-id"

Exposed tools (base names; the tools.prefix, when set, is prepended at registration):

Base tool Description
search_issues Search issues by queue, filter, or query language
get_issue Get detailed issue info by key or ID
get_links Get all links (relations) for an issue
get_comments Get all comments for an issue
get_fields Get available Tracker issue fields (for building search queries)
get_wiki_page Get a Wiki page by slug or numeric ID
get_wiki_subpages List subpages of a Wiki page
list_queues List available queues
my_report Generate a structured retrospective report for a user in a given time period
create_issue Create a new issue (task, bug, epic, etc.) in a queue
update_issue Update an existing issue — only provided fields are changed
create_comment Create a new comment on an issue
update_comment Update the text of an existing comment
GitLab Source

GitLab API tools — fetch merge request discussions (comments, review threads, system notes) and commits. Accepts full MR URLs and supports self-hosted GitLab instances.

connect fields:

Field Description Required
token GitLab private token or personal access token Yes
base_url GitLab API base URL (optional, defaults to gitlab.com) No

GitLab.com example:

sources:
  myproject:
    type: gitlab
    connect:
      token: "glpat-xxxxxxxxxxxx"

Self-hosted GitLab example:

sources:
  selfhosted:
    type: gitlab
    connect:
      token: "glpat-xxxxxxxxxxxx"
      base_url: "https://gitlab.example.com"

Exposed tools (base names; the tools.prefix, when set, is prepended at registration):

Base tool Description
get_mr_discussions Fetch all discussion threads (comments, review threads, system notes) for a merge request by full URL
get_mr_commits Fetch all commits in a merge request by full URL
English Source

English grammar and spelling validation via the LanguageTool API. Strips code blocks and URLs before checking, filters style suggestions, and returns structured errors with corrections.

connect fields:

Field Description Default
language Language code for validation en-US
api_url LanguageTool API base URL https://api.languagetool.org/v2

Example (use defaults):

sources:
  checker:
    type: english

With a custom LanguageTool server:

sources:
  checker:
    type: english
    connect:
      api_url: "http://localhost:8010/v2"
      language: "en-GB"

Exposed tool (base name; the tools.prefix, when set, is prepended at registration):

Base tool Description
validate_english Validate English text for grammar, spelling, punctuation, and vocabulary
Websearch Source

Web search and URL content extraction powered by Brave Search and DuckDuckGo. Supports web, news, and image search, as well as fetching and extracting readable content from URLs.

The Brave API key is resolved from an environment variable at startup. If brave_api_key_env is set, the source validates that the variable exists and fails fast if it doesn't. If unset, it defaults to BRAVE_API_KEY and reads whatever value is present (empty is allowed — DuckDuckGo works without a key).

connect fields:

Field Description Default
brave_api_key_env Environment variable name holding the Brave API key BRAVE_API_KEY
max_results Default number of results per search 10
timeout HTTP client timeout as a duration string (e.g. 10s) 10s

Example:

sources:
  search:
    type: websearch
    connect:
      brave_api_key_env: BRAVE_API_KEY
      max_results: 10
      timeout: 10s

Exposed tools (base names; the tools.prefix, when set, is prepended at registration):

Base tool Description
web_search Search the web — supports provider selection, pagination, domain filters
news_search Search for news articles with freshness and country filters
image_search Search for images with safe-search controls
fetch_url Fetch a URL and extract readable text, links, and metadata
list_providers List available search providers and the current default
Woodpecker Source

Woodpecker CI/CD management — list repositories, inspect and manage pipeline runs, and read step logs directly via MCP (no copy-pasting CI output into the session).

connect fields:

Field Description Required Default
token Woodpecker personal access token Yes
api_url Woodpecker API base URL (e.g. https://ci.example.com/api) Yes

There is no default for api_url — the source refuses to start without it, so the user is always explicit about which Woodpecker instance the tools will talk to.

Example:

sources:
  ci:
    type: woodpecker
    connect:
      token: "ci_xxxxxxxxxxxx"
      api_url: "https://ci.example.com/api"

Headline use case — investigate a failed pipeline without copy-pasting logs:

sources:
  ci:
    type: woodpecker
    connect:
      token: "ci_xxxxxxxxxxxx"
      api_url: "https://ci.example.com/api"
    tools:
      prefix: "wp_"   # optional; renames list_repos → wp_list_repos, etc.

The model can then walk wp_list_reposwp_list_pipelines({status: "failure"})wp_get_pipelinewp_get_step_logs to surface the failure logs directly, then either wp_restart_pipeline (if flaky) or report back to the user.

Exposed tools (base names; the tools.prefix, when set, is prepended at registration):

Base tool Description Read-only
list_repos List repositories the token can see; discover repo_id from full_name yes
list_pipelines List pipelines for a repository; filter by status, branch, event yes
get_pipeline Get one pipeline including workflows[].children[] (steps) yes
get_step_logs Fetch decoded log entries for a step — UTF-8 text + kind per entry yes
restart_pipeline Restart an existing pipeline; optional event and deploy_to overrides no
launch_pipeline Trigger a new manual pipeline; optional branch and variables no
cancel_pipeline Cancel a running pipeline; idempotent no
Shell Source

The source exposes one tool:

Base tool Description
run_command Execute a shell command string and return its captured output

connect fields:

Field Description Required Default
working_dir Working directory for every invocation Yes
timeout Per-call timeout applied when the per-call timeout is absent No 30s
max_output_bytes Cap on combined stdout+stderr bytes per invocation No 1048576
shell Absolute path to the shell binary No /bin/sh
shell_flags Argv flags inserted between shell and command (e.g. ["-lic"]) No ["-c"]
env Explicit env var map passed to every invocation No {}

The argv is always [shell, shell_flags..., command]. The default shell_flags: ["-c"] is the POSIX command-string convention; operators who want rc-file sourcing override it (see examples below).

Example — strict default (no rc sourcing):

sources:
  shell:
    type: shell
    connect:
      working_dir: /home/user
      timeout: 30s
      max_output_bytes: 1048576
      env:
        LANG: C.UTF-8
        PATH: /usr/local/bin:/usr/bin:/bin
        HOME: /home/user

Example — zsh with .zshrc sourced (login + interactive + command; sources .zprofile then .zshrc):

sources:
  shell:
    type: shell
    connect:
      shell: /bin/zsh
      shell_flags: ["-lic"]
      working_dir: /home/user
      env:
        HOME: /home/user

Example — bash with .bash_profile sourced:

connect:
  shell: /bin/bash
  shell_flags: ["-l"]

Per-call overrides (directory is required; timeout and env are optional):

Field Required Description
directory Yes Absolute path inside connect.working_dir to chdir to before running the command. Must exist and be a directory. Required on every call — empty or omitted values are rejected with a tool error. Non-absolute paths, paths outside working_dir, missing paths, and paths reached through a symlink that points outside working_dir are rejected with a tool error.
timeout No Override the source's per-call timeout (e.g. '5m', '30s')
env No Map of per-call env overrides; merged on top of connect.env, per-call values win on key collision

Security model. This source can execute arbitrary code. The MVP trusts the operator (same trust model as the postgres source for SQL) and the LLM (same trust model as the host's built-in shell tool). The guardrails narrow the blast radius but do not eliminate it:

  • The command runs as <shell> [shell_flags...] <command> — the meta-server does not parse the flags, it passes them through to exec.CommandContext as-is. With the default shell_flags: ["-c"], sh itself performs all shell syntax the LLM writes. With shell_flags: ["-lic"], the shell sources rc files first, which can change which executables and aliases are available.
  • The per-call directory is confined to connect.working_dir via os.Root. The meta-server opens an os.Root at the canonical working directory once at startup, and every directory request is validated against it: missing or empty values, paths outside the root, non-absolute paths, paths the LLM does not have permission to traverse, and paths reached only via a symlink that points outside the root are rejected with a tool error before the child is spawned. The field is also declared as required in the input schema so the MCP host enforces its presence before arguments reach the handler. The validation is backed by openat2(RESOLVE_BENEATH) on Linux (and equivalents on macOS / Windows) so symlink attacks cannot escape. os.Root does not protect against traversal across filesystem boundaries (bind mounts, /proc), access to Unix device files, or .. across mount boundaries — operators who mount attacker-controlled content inside working_dir have already crossed the trust boundary.
  • connect.env is the only source of env vars passed to the child. The child process does not inherit the parent process's environment. Operators who want PATH must declare it. (Note: when shell_flags enables rc-file sourcing, the rc files can set additional env vars; this is the documented behavior of the chosen shell.)
  • No stdin is opened. The child process's stdin is /dev/null.
  • No PTY; output is plain pipes.
  • A hard timeout kills the child on expiry; the result reports exit_code: -1 and a wrapped error.
  • Output is capped per connect.max_output_bytes; past the cap, output is dropped and truncated: true is set.
  • Non-UTF-8 output is base64-encoded and prefixed with b64: (same convention as the woodpecker source's step logs).
  • The tool advertises DestructiveHint=true and ReadOnlyHint=false so hosts that gate on these hints treat shell correctly as write/destructive.

Output schema:

{
  "stdout":      "captured stdout (UTF-8 text, or 'b64:<...>' for binary)",
  "stderr":      "captured stderr (UTF-8 text, or 'b64:<...>' for binary)",
  "exit_code":   0,
  "duration_ms": 42,
  "truncated":   false
}

exit_code is -1 when the process was killed by a signal or by the timeout (and a tool error is returned alongside).

FS Source

Exposes twelve file-operation tools gated by an operator-configured allowed_paths allowlist. Every tool argument that names a path is routed through a resolver that rejects .. traversal, symlink escapes, and any path that does not normalize to a child of one of the configured roots. The LLM cannot escape the allowlist regardless of how it phrases a path.

The source exposes the following tools (with no tools.prefix override):

Base tool Description
list_allowed_directories Return the operator-configured allowlist
read_file Read a file (UTF-8 text or b64:<base64> for binary)
write_file Create or overwrite a file; encoding: "base64" for binary
edit_file Find/replace edit, rejected unless old_text occurs exactly once
create_directory mkdir -p semantics
list_directory List immediate children
directory_tree Recursive JSON tree (depth bounded by max_depth, default 8)
move_file Rename or move within the allowlist
copy_file Copy a regular file (directories refused)
delete_file Remove a file or empty directory (non-empty directories refused)
search_files Recursive glob-style match within a root
get_file_info Stat a path (size, mode, mtime, is_dir)
grep Regex content search (literal prefilter, .gitignore honoring, default-ignore list)

connect fields:

Field Description Required Default
allowed_paths Absolute paths the LLM is permitted to read, write, or delete Yes
max_read_bytes Hard cap on file size returned by read_file No 1048576
max_write_bytes Hard cap on size of content written by write_file / edit_file No 10485760
follow_symlinks Follow symlinks during path resolution (default: deny) No false

Example:

sources:
  work:
    type: fs
    connect:
      allowed_paths:
        - /home/user/projects
        - /tmp/work
      max_read_bytes: 524288
      max_write_bytes: 5242880

Security note: the operator's allowlist is the contract. The source never executes subprocesses, never reads /proc, never touches the network; the only side effects are file mutations, all gated by allowed_paths. Use follow_symlinks: false (the default) to reject any symlink under the allowlist that points outside; permissive mode follows symlinks but rejects any post-follow real path that exits the allowlist.

Sequential Thinking Source

Exposes a single sequentialthinking tool for dynamic, reflective problem-solving. The LLM breaks a problem into numbered thoughts, can revise earlier thoughts (re-running a step with is_revision: true), and can branch into alternative reasoning paths (using branch_from_thought + branch_id). The meta-server keeps the in-memory thought history per source instance and returns the current thought_number, total_thoughts, next_thought_needed, the sorted set of active branch IDs, and the running history length on every call.

The source exposes one tool:

Base tool Description
sequentialthinking Record one step of a multi-step reasoning process; supports revisions and branches. Read-only, idempotent.

This is a Go port of the upstream @modelcontextprotocol/server-sequential-thinking (MIT, modelcontextprotocol org). The tool name, input/output schema, and annotations match upstream 1:1 so existing client code, docs, and prompts keep working.

connect fields:

Field Description Required Default
disable_thought_logging Suppress the per-thought operator log line No false

Example:

sources:
  think:
    type: sequentialthinking
    connect:
      disable_thought_logging: false

Input schema (mirrors upstream's Zod schema):

Field Type Required Description
thought string Yes The current thinking step
next_thought_needed boolean Yes Whether another thought step is needed
thought_number integer Yes Current thought number (≥ 1)
total_thoughts integer Yes Estimated total thoughts needed (≥ 1); the tool bumps this up if thought_number ever exceeds it
is_revision boolean No Whether this revises previous thinking
revises_thought integer No Which thought number is being reconsidered (≥ 1)
branch_from_thought integer No Branching point thought number (≥ 1)
branch_id string No Branch identifier
needs_more_thoughts boolean No If more thoughts are needed

Output schema:

Field Type Description
thought_number integer The (possibly bumped) thought number for this call
total_thoughts integer The (possibly bumped) total thoughts estimate
next_thought_needed boolean Echoed from the input
branches array of strings Sorted set of branch IDs currently in the map
thought_history_length integer Number of thoughts recorded so far (including this one)

State and concurrency. Each sequentialthinking source owns its own thought history (one instance per Connect call). State is in-memory and mutex-protected so concurrent tool calls on the same source see consistent history. Thought history is reset on meta-server restart; persistence is a future enhancement.

The tool advertises ReadOnlyHint=true, DestructiveHint=false, IdempotentHint=true, OpenWorldHint=false — matching upstream's annotations.

Temporal Source

Exposes 30 tools for Temporal workflow orchestration, mapped onto the upstream Python temporal-mcp. The source uses go.temporal.io/sdk/client directly and connects to a Temporal frontend (local dev server, Temporal Cloud, or self-hosted cluster).

The 30 tools split into 5 feature groups:

Group Count Lifecycle
*_workflow 8 start_workflow, cancel_workflow, terminate_workflow, get_workflow_result, describe_workflow, list_workflows, get_workflow_history, continue_as_new (signal-based; pair with a workflow that calls workflow.NewContinueAsNewError).
*_activity 8 start_activity, execute_activity, get_activity_result, describe_activity, list_activities, count_activities, cancel_activity, terminate_activity (Experimental standalone activities on a task queue).
*_schedule 7 create_schedule, list_schedules, pause_schedule, unpause_schedule, delete_schedule, trigger_schedule, describe_schedule
query_/signal_ 2 query_workflow (read-only), signal_workflow (mutating, NOT idempotent on the receiver side)
batch_* 5 batch_signal, batch_cancel, batch_terminate, batch_cancel_activities, batch_terminate_activities (visibility-query fan-out with bounded concurrency via errgroup.Group; default 50, cap 100).

The source uses client.NewLazyClient under the hood — the binary never crashes on startup when Temporal is offline. RPC failures surface only when a tool is actually invoked; tools/list always succeeds.

connect fields:

Field Description Required Default
host host:port of the Temporal frontend No localhost:7233
namespace Temporal namespace No default
tls_enabled Tri-state: true force-on, false force-off, absent → auto-detect from host/api_key No auto-detect
tls_client_cert_path Path to mTLS client cert (for Temporal Cloud / self-hosted with mTLS) No
tls_client_key_path Path to mTLS client private key No
api_key Temporal Cloud API key (forces TLS on automatically) No

Examples:

sources:
  workflows:
    type: temporal
    connect:
      host: localhost:7233
      namespace: default
    tools:
      prefix: t_
sources:
  workflows:
    type: temporal
    connect:
      host: my-cluster.tmprl.cloud:7233
      namespace: production
      api_key: "${TEMPORAL_API_KEY}"
      tls_enabled: true
    tools:
      prefix: t1

mTLS example:

sources:
  workflows:
    type: temporal
    connect:
      host: my-cluster.tmprl.cloud:7233
      namespace: production
      tls_client_cert_path: /etc/temporal/cert.pem
      tls_client_key_path: /etc/temporal/key.pem
    tools:
      prefix: t1

The full input/output JSON Schemas for all 30 tools live in temporal/schemas/*.json (embedded via //go:embed in the per-feature handler files). cmd/testdata/sources_with_temporal.{yaml,json} provides a complete configuration example that cmd/binary_integration_test.go::TestBinary_TemporalSource_RegistersAll30Tools exercises end-to-end.

Environment Variables

Variable Description Default
LOG_LEVEL Log level (debug, info, warn, error, disabled) info
LOG_JSON Enable JSON log output (true/false) false
LOG_ADD_SOURCE Add source file/line to logs (true/false) false

License

MIT License — see LICENSE for details.

Publishing a release

This module is served from a release-only public mirror. To publish a new release:

  1. Tag the source commit: git tag v0.x.y
  2. Push the tag: git push origin v0.x.y
  3. Run ./mirror/mirror.sh <public-mirror-url> to rebuild the public mirror from scratch.

The mirror script auto-discovers all semver v* tags from this repo and creates one commit per tag in the public mirror, with the excluded paths stripped. Authentication is the caller's responsibility — pass an SSH URL or an HTTPS URL with embedded credentials.

Directories

Path Synopsis
Package decode provides shared helpers for decoding the per-source `connect:` map (the free-form map each per-type Connect function receives as its second argument).
Package decode provides shared helpers for decoding the per-source `connect:` map (the free-form map each per-type Connect function receives as its second argument).
Package english implements the English grammar/spelling validation MCP tool.
Package english implements the English grammar/spelling validation MCP tool.
langtoolapi
Package langtoolapi provides primitives to interact with the openapi HTTP API.
Package langtoolapi provides primitives to interact with the openapi HTTP API.
Package fs implements an MCP source that exposes a path-confined set of file-operation tools to the LLM.
Package fs implements an MCP source that exposes a path-confined set of file-operation tools to the LLM.
Package github implements an MCP tool source that exposes the official GitHub MCP server (github.com/github/github-mcp-server) as a first-class `type: github` source.
Package github implements an MCP tool source that exposes the official GitHub MCP server (github.com/github/github-mcp-server) as a first-class `type: github` source.
Package gitlab implements MCP tools for the GitLab API.
Package gitlab implements MCP tools for the GitLab API.
Package http implements a single-tool HTTP source.
Package http implements a single-tool HTTP source.
Package postgres implements a source of MCP tools for PostgreSQL database introspection.
Package postgres implements a source of MCP tools for PostgreSQL database introspection.
Package proxy implements an MCP tool source that proxies tools from a remote or local MCP server.
Package proxy implements an MCP tool source that proxies tools from a remote or local MCP server.
scripts
fix-untyped-nil command
fix-untyped-nil/fixer
Package fixer is the testable core of scripts/fix-untyped-nil.
Package fixer is the testable core of scripts/fix-untyped-nil.
Package sequentialthinking implements an MCP source that exposes a dynamic, reflective problem-solving tool.
Package sequentialthinking implements an MCP source that exposes a dynamic, reflective problem-solving tool.
Package shell implements an MCP source that executes shell commands.
Package shell implements an MCP source that executes shell commands.
Package source defines the top-level source configuration consumed by the MCP server and the dispatcher that wires per-type Connect implementations into the server.
Package source defines the top-level source configuration consumed by the MCP server and the dispatcher that wires per-type Connect implementations into the server.
Package temporal implements the Temporal workflow orchestration source for the MCP server.
Package temporal implements the Temporal workflow orchestration source for the MCP server.
Package tool defines the shared data types used by source-level tool declarations, the per-type Connect functions, and the source dispatcher middlewares.
Package tool defines the shared data types used by source-level tool declarations, the per-type Connect functions, and the source dispatcher middlewares.
Package tracker implements MCP tools for Yandex Tracker + Wiki.
Package tracker implements MCP tools for Yandex Tracker + Wiki.
Package websearch provides foundation utilities for the web search MCP tool, including an LRU+TTL cache, result-ID minting, cursor encoding, and HTTP fetch with retry.
Package websearch provides foundation utilities for the web search MCP tool, including an LRU+TTL cache, result-ID minting, cursor encoding, and HTTP fetch with retry.
Package woodpecker implements the Woodpecker CI/CD source for the MCP server.
Package woodpecker implements the Woodpecker CI/CD source for the MCP server.
Package woodpeckerapi provides primitives to interact with the openapi HTTP API.
Package woodpeckerapi provides primitives to interact with the openapi HTTP API.

Jump to

Keyboard shortcuts

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