README
¶
MCP Server
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, andtools.read_onlyadjust 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 inwhile true; do ...; doneif 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:
- Direct OAuth token — set
tokenandorg_id. - OAuth device flow — set
client_id,client_secret, andorg_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_repos → wp_list_pipelines({status: "failure"}) → wp_get_pipeline → wp_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 toexec.CommandContextas-is. With the defaultshell_flags: ["-c"],shitself performs all shell syntax the LLM writes. Withshell_flags: ["-lic"], the shell sources rc files first, which can change which executables and aliases are available. - The per-call
directoryis confined toconnect.working_dirviaos.Root. The meta-server opens anos.Rootat the canonical working directory once at startup, and everydirectoryrequest 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 byopenat2(RESOLVE_BENEATH)on Linux (and equivalents on macOS / Windows) so symlink attacks cannot escape.os.Rootdoes 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 insideworking_dirhave already crossed the trust boundary. connect.envis the only source of env vars passed to the child. The child process does not inherit the parent process's environment. Operators who wantPATHmust declare it. (Note: whenshell_flagsenables 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: -1and a wrapped error. - Output is capped per
connect.max_output_bytes; past the cap, output is dropped andtruncated: trueis 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=trueandReadOnlyHint=falseso 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:
- Tag the source commit:
git tag v0.x.y - Push the tag:
git push origin v0.x.y - 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. |