Skip to content

MCP servers

An MCP server is a program or service that offers an agent extra tools over the Model Context Protocol, such as a code-analysis service that answers questions about a repository. Good Grief lets an organisation give its agents tools like these, under the same rules as everything else a flight does:

  • Root names the servers, once, for the whole organisation.
  • A loop says which of their tools its agent may call.
  • The runner starts or connects to those servers on its own machine, and resolves any credential they need there.

The control plane holds the definitions and passes them to the runner with the flight. It never holds a server’s credential.

Servers are defined under mcp-servers: in root, the organisation’s base rules in the airspace. Only root may define them. A work kind, a narrowing or any other airspace document that declares mcp-servers: is rejected, with a message saying that only root may.

Each entry is a key, which names the server, and the server’s definition. A server is reached in one of two ways.

At an address, for a server that runs as a service:

mcp-servers:
analysis:
type: http
url: "https://mcp.example.com/mcp"
headers:
Authorization: "Bearer ${credential:local:analysis/example}"
READ_ONLY: "true"

From a command, for a server the runner starts as a program on the machine:

mcp-servers:
analysis-local:
command: example-mcp-server
args: ["--read-only"]
env:
EXAMPLE_TOKEN: "${credential:local:analysis/example}"
Field What it says
type http or sse for a server at an address. stdio, or nothing, for one started from a command.
url The server’s address.
headers Settings sent to a server at an address, such as an authorisation header.
command The program to start.
args Arguments for the program.
env Environment variables for the program.

No other field is accepted.

  • A key is letters, digits, - and _, because it becomes the prefix of every tool name the server offers. Two servers may not share a key.
  • The key gg is reserved. gg serves its own tools under that name, and a server with the same key would hide them.
  • A server has exactly one of command or url. A server started from a command takes env, not headers. A server at an address takes headers, not env or args.
  • A credential is named, never written. A value may include ${credential:<name>}, where the name is the one gg credential list shows, such as local:analysis/example. No other ${…} form is substituted.
  • A credential may not appear in args. Arguments are visible on the command line to every user on the machine. Put it in env instead.
  • A setting whose name suggests a secret must use a reference. A setting whose name contains authorization, token, secret, password, passwd or apikey (also api_key or api-key), and whose value contains no ${credential:…} reference, is rejected. This catches a secret pasted into the document by mistake.

Defining a new server, or changing an existing one in any way, gives agents something they could not reach before. Applying either change opens a gate on root, so a person approves it before it takes effect. Removing a server applies at once. Airspace documents are changed with gg airspace pull, gg airspace diff and gg airspace apply; Change the airspace covers the steps.

Defining a server gives no agent access to it. A loop in a work kind allows tools under mcp:, beside its moves::

loops:
- id: investigate
executor: frontier
moves: [read, search]
mcp:
analysis:
allow:
- "*"
  • Each entry under mcp: is a server key from root, and allow: lists the tools the agent may call.
  • "*" allows every tool the server offers, and must appear on its own.
  • Otherwise, list tool names as the server gives them, such as search_issues. A name contains letters, digits, -, _ and ..
  • allow: is required and may not be empty. A server may appear only once in a loop.

A loop that names a server root does not define is rejected when its work kind is combined with root, with a message naming the loop and the server. Correct the name, or define the server in root.

Allowing more is also a widening that opens a gate: naming another server, adding a tool, or changing a list of tools to "*".

Moves bound what an agent may do with gg’s own tools and with the repository, such as read, edit and run-tests. They do not apply to an organisation’s MCP servers: for those, the allow: list is the whole of what is allowed. A loop with the move anything runs with no allowed-tool list at all, so allow: does not narrow what it may call.

See The envelope: loops for the rest of the loop.

When a runner takes a flight, the lease carries the loop’s mcp: entries and the definitions of the servers those entries name, and nothing else from mcp-servers:. The credential references travel; the credentials do not.

Before it starts the agent, the runner:

  1. Resolves each credential reference on its own machine, from gg’s credential store. Credentials explains how a machine comes to hold one.
  2. Writes the server configuration to a file only the runner’s user can read. The resolved values go into that file, never onto the agent’s command line, where other users on the machine could see them. The runner deletes the file as soon as the agent reports that its servers have started, and again when the run ends.
  3. Starts the agent with only these servers. Any MCP servers configured for the machine’s own user are not loaded, so a flight never runs with tools the envelope did not name.
  4. Lists the allowed tools. Each allowed tool becomes an entry on the agent’s allowed-tool list, as mcp__<key>__<tool>, or mcp__<key> for "*".

The same steps apply when a person flies a flight by hand.

Situation What happens
The runner cannot resolve a credential a server needs. The flight is refused before the agent starts, and nothing is spent. The message names the server and the credential, and says to register it on the machine with gg credential add or to send it there.
A server does not start or cannot be reached. The runner reads the agent’s own report of which servers connected, and refuses the flight if any did not.
A server’s key is the same as one gg already uses on that flight. The runner refuses the flight rather than start two servers under one name.
A loop names a server root does not define. The work kind cannot be combined with root, so no flight runs under it.

In each case the flight is not run with fewer tools than the envelope says. A missing server stops the flight, so the agent never works without a tool it was meant to have.

  • Servers defined anywhere but root. A work kind, a repository narrowing or a single flight cannot add a server.
  • A machine’s own servers. MCP servers configured for the user on a runner’s machine are never loaded for a flight.
  • Moves for external tools. Access to an organisation’s server is controlled by allow: alone.
  • Watches. The agent that runs a watch’s sweep is not given the organisation’s MCP servers.
  • Substitution other than credentials. ${credential:<name>} is the only reference a value may contain.
  • Managing servers from the command line. No gg command edits mcp-servers:; it is changed like any other part of root.

gg’s own tool servers, such as the one that answers an agent’s questions about its flight, are separate from these. They are not declared in the airspace, and moves still govern them.