🔌

Remora

GoMCPYAML

Remora is my config-driven MCP server — a single Go binary that turns a YAML file into a set of tools Claude can call, with no Go written per integration.

Every service I add is one I eventually want Claude to use, and a bespoke MCP server each time is the same boilerplate: register a tool, validate arguments, translate them into a request, call the backend. Remora lets me describe that mapping once — a tool’s name, its inputs, and which endpoint answers it — and dispatch to REST, GraphQL, or ConnectRPC backends. It serves over stdio, Streamable HTTP, or SSE, applies per-tool auth from the environment, and ships a validate subcommand so a bad config fails in CI rather than at runtime.

Remora is my config-driven MCP (Model Context Protocol) server — a single Go binary that turns a YAML file into a set of tools Claude can call, without writing a line of Go for each new integration.

Why I Built It

Every self-hosted service I add to my platform is a service I eventually want Claude to be able to use — search my index, look up a conversation, kick off a job. Writing a bespoke MCP server per service means the same boilerplate every time: register a tool, validate its arguments, translate them into a request, call the backend, shovel the response back. Remora exists so I describe that mapping once, in YAML — a tool’s name, its inputs, and which backend endpoint answers it — and get an MCP tool for free. Add a new backend method to expose to Claude, and it’s a few lines of config, not a new Go package.

The name fits: a remora attaches itself to something bigger and rides along, picking up whatever surfaces. That’s what this does with the rest of my platform — it doesn’t own any data itself, it just exposes what’s already there.

What It Does

  • Reads a YAML config file and registers each entry under tools: as an MCP tool, with typed, optionally-required input parameters.
  • Supports an include directive so tool definitions can be split across files (e.g. tools/shrike.yaml, tools/grey-seal.yaml) and merged at load time instead of living in one giant config.
  • Serves over stdio (the default — spawned as a subprocess by Claude Desktop or Claude Code), Streamable HTTP (transport: http, for an HTTP-reachable deployment — the current MCP remote transport), or SSE (transport: sse, legacy), selected via transport in the config.
  • Dispatches each tool call to a rest, graphql, or grpc backend declared per tool. “grpc” backends are actually plain JSON over HTTP to a ConnectRPC service (Content-Type: application/json, Connect-Protocol-Version: 1) — no generated stubs or .proto files needed on Remora’s side.
  • Maps MCP tool arguments into the backend request via request_mapping: each field is either a literal or a $.input.<field> expression evaluated with gjson.
  • Applies per-tool auth — bearer token, API key header, or basic auth — sourced from an environment variable at call time.
  • Ships a remora validate subcommand that checks a config file (duplicate tool names, missing descriptions, missing required backend fields, unknown transport/backend types) without starting the server, so a bad config fails fast in CI rather than at runtime.

Tech Stack

  • Language: Go
  • MCP protocol: mark3labs/mcp-go
  • Config: YAML (gopkg.in/yaml.v3)
  • Request mapping: tidwall/gjson
  • CLI: spf13/cobra

For a deeper look at how the pieces fit together, see Architecture.

Documentation