MCP servers
Configure command and Streamable HTTP MCP servers and enable them on an Agent.
Configure a command or Streamable HTTP server, then select its resource ID on an Agent. Creating a server file registers it; selecting its ID on an Agent or in defaults enables it. Use Command reference for Harness UI shell commands. MCP command transports launch external programs.
Copy a JSON configuration
Create mcp/servers.json beside your selected root configuration. Common client-style mcpServers objects work directly:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/workspace"]
},
"docs": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer example-token",
"X-Workspace": "${WORKSPACE_ID}"
}
}
}
}Replace the illustrative endpoint, path, and token. Enable these entries with mcp_servers: [mcp-filesystem, mcp-docs] on an Agent or under root defaults. Creating the JSON file alone does not enable them.
A mcpServers file can define several servers. Its names normalize to mcp- IDs: My_Server becomes mcp-my-server. Duplicate IDs across files reject the configuration, so check the normalized IDs before adding a second file.
For a command entry, use command, optional args, and optional env. For a remote entry, use url and optional headers. Optional type accepts stdio for commands and http or streamable-http for remote endpoints. Remote connections use Streamable HTTP, not legacy SSE. OAuth login, JSONC comments, trailing commas, disabled, and unrelated client-specific fields are not supported. mcpServers is a common client convention, not a universal MCP protocol configuration standard.
Both .yaml and .json can also contain the single-resource format shown below; both accept the mcpServers wrapper. Existing YAML files continue to work without migration.
Literal values and environment references
Command env (or canonical transport.environment) and remote headers accept:
| Value | Behavior |
|---|---|
"example-token" | A literal string, including ordinary non-secret settings |
"${API_TOKEN}" | Read an environment variable when a Run starts |
"Bearer ${API_TOKEN}" | Substitute ${NAME} occurrences in a string |
{"env": "API_TOKEN"} | Existing explicit environment reference; works in YAML and JSON |
Empty literal strings and whitespace are preserved. References require non-empty variables in the Harness UI process environment; exporting in another shell does not change an already-running process. Expansion is one pass, only for ${NAME} with a valid environment-variable name; there is no shell execution or default-value syntax. These substitutions apply only to environment/header values, not commands, arguments, or URLs.
Direct token configuration is supported; environment references are optional. Keep credential-bearing source files private and out of version control. Harness UI does not copy MCP source text or literal environment/header values into config show, accepted generations, or Run compositions: it retains source locations and digests, then reads values at Run startup. A captured Run requires that literal-bearing source file to remain present and byte-identical until client construction. Editing it is supported for newly captured Runs, but an older captured Run or child continuation may fail with mcp_source_changed; use current configuration for a new Run. Already constructed ordinary clients keep their Run-local values. Host-owned connections outlive a Run but retain an exact effective binding; later operations and SDK follow-up rounds cannot use a retired binding. Environment references can rotate without editing the source file.
After editing, run a13n-harness-ui config validate and start a new conversation (a new root Thread) if you changed default MCP selections. Validation does not connect to servers or verify credentials.
Command transport
Create mcp/github.yaml:
schema_version: "1"
kind: mcp_server
id: mcp-github
name: GitHub
transport:
command: npx
arguments: ["-y", "@modelcontextprotocol/server-github"]
environment:
GITHUB_TOKEN:
env: GITHUB_TOKENThis illustrative server requires its executable/package and access token to be available. Harness UI does not validate external service entitlement by making a test call during file parsing. Review the command and package before enabling it.
Remote transport
Create mcp/docs.yaml using your actual MCP endpoint:
schema_version: "1"
kind: mcp_server
id: mcp-docs
name: Documentation service
transport:
url: https://mcp.example.com/mcp
headers:
Authorization:
env: DOCS_MCP_AUTHORIZATIONThe environment variable contains the entire header value expected by the server, including any authentication scheme. Alternatively, use "Bearer ${DOCS_MCP_TOKEN}" to interpolate only the token. URLs must be credential-free HTTPS; plain HTTP is allowed only for a literal loopback host with no configured headers. Authenticated redirects cannot weaken transport or leak headers to another origin.
Enable a server
In an Agent file:
mcp_servers: [mcp-github, mcp-docs]Or in root YAML:
defaults:
mcp_servers: [mcp-github]An Agent's mcp_servers: null inherits the root defaults; [] explicitly selects none. Existing conversations retain exact sticky selections. By default, clients/processes are constructed fresh for logical Runs. Root lifetime policy can retain selected clients, and enabled MCP Apps automatically retain theirs. Neither client state nor pending MCP input is serialized into continuations. Selecting the Sandbox Environment mode does not sandbox an external MCP command or remote service.
Connection lifetime and protocol
To preserve a server's process-local state between Runs, configure the root document:
mcp:
host_owned_servers: [mcp-docs]
protocol_overrides:
mcp-docs: autohost_owned_servers defaults to []. It changes lifetime only: the server must still be selected on the Agent or Thread, and existing tool filters and permissions still apply. Each owning Thread/server keeps its own entered client for an exact transport, credential and protocol binding. Child Threads do not borrow their parent's client. There is no idle or browser-viewer timeout; Host shutdown, explicit close, Thread disposal or binding retirement ends the connection generation (that entered client). Clients and generation IDs are process-local, not durable MCP sessions. A disconnected connection generation does not silently reconnect or replay business calls; activating an MCP App can explicitly establish a replacement.
protocol_overrides defaults to {}. Each configured server ID accepts auto, legacy, or 2026-07-28; an omitted ID uses auto. The override controls the upstream SDK's Core negotiation, independently of connection lifetime and the MCP Apps UI wire protocol. All policy IDs must name existing MCP resources. Existing YAML/JSON and saved recipes without these fields retain Run-local lifetime and automatic negotiation.
Human input from MCP servers
The TUI and WebUI present supported MCP form and URL requests while the original operation remains active. These are not ask_user_question, deferred continuation decisions, or another Run. A conversation can see requests from its child Threads, with the source Thread/server and available Run, tool-call or App View attribution.
Forms support primitive string, numeric and boolean fields, single-select enums and multi-select string enums, including titled options. The Host validates answers against the original schema. Unsupported schemas, remote references and declared sensitive fields are rejected; never enter passwords, tokens or other secrets into MCP forms. URL requests show the destination and require an explicit browser action outside the App iframe; complete the external flow before confirming.
Choose accept, decline or cancel within five minutes. Cancellation, expiration, connection retirement or process shutdown makes the live request unavailable. Responses bypass the business dispatch lane so a waiting call cannot deadlock. An identical duplicate response reconciles the same request; a conflicting response is rejected. If delivery is uncertain, retry only the exact submitted answer or inspect current state. Acceptance acknowledges input delivery, not completion or absence of remote effects. Headless embedding advertises no input handler unless explicitly enabled; see embedding.
MCP field reference
The shared fields are schema_version: "1", kind: mcp_server, unique mcp- id, and name. transport accepts exactly one form:
| Form | Fields |
|---|---|
| Command | Required command; arguments defaults to []; environment defaults to {}, values are strings or {env: VARIABLE} |
| Remote | Required url; headers defaults to {}, values are strings or {env: VARIABLE} |