MCP Servers
Ask for MCP servers per turn. agentshim installs them the way the provider expects, and restores whatever it changed when the turn ends, including when the turn fails.
from agentshim import CliAgent, StdioMcpServer, TurnRequest
servers = [
StdioMcpServer(
name="issues",
command="python",
args=["-m", "board.mcp", "issues.json"],
env={"BOARD_TOKEN": "..."},
)
]
agent = CliAgent("claude")
agent.start_session(cwd="/workspace").turn(
TurnRequest(prompt="File a bug for the failing test.", mcp_servers=servers)
)
StdioMcpServer(..., tool_timeout_s=120.0) and
HttpMcpServer(..., tool_timeout_s=120.0) request a per-server tool timeout.
The value is optional and must be positive and finite. Codex applies
it to that server with an invocation-scoped --config override named
mcp_servers.<name>.tool_timeout_sec, so the user's
~/.codex/config.toml is never edited. Providers that cannot express a
per-server tool timeout raise ProviderCapabilityError when one is requested.
startup_timeout_s similarly bounds server initialization and the initial
tool listing. Codex receives it through the invocation-scoped
mcp_servers.<name>.startup_timeout_sec override, without editing user config.
AgentShim also marks each per-turn Codex server as required, so Codex waits for
its configured startup timeout instead of applying the shared optional-server
catalog grace period.
HttpMcpServer(name=..., url=..., headers=..., transport=...) describes a
remote server. transport is "http" (streamable HTTP, the default) or
"sse"; they are different wire protocols, so a CLI told the wrong one
connects and then fails. What each provider does with it:
| provider | transport="http" |
transport="sse" |
|---|---|---|
| claude | {"type": "http", "url": ...} |
{"type": "sse", "url": ...} |
| copilot | {"type": "http", "url": ...} |
{"type": "sse", "url": ...} |
| gemini | {"httpUrl": ...} |
{"url": ...} |
| opencode | {"type": "remote", "url": ...} |
same |
| codex | mcp_servers.<name>.url=... |
same |
opencode has one remote server type and codex's --config override carries
only the address, so neither can be told which transport to use; both work it
out from the endpoint. Codex additionally cannot carry HTTP headers, and
raises ProviderCapabilityError rather than dropping them.
How installation works
ProviderProfile.mcp declares the mechanism:
CONFIG_FILE: the servers are merged into the provider's JSON config in the workspace (.mcp.jsonfor Claude Code). The original bytes are kept and written back on restore. If the file changed during the turn, only the entries agentshim added are removed: an edit the agent made to one of them, a server it added, and unrelated top-level keys all survive. Deleting the file during the turn is treated as a workspace edit and is not undone.CLI_FLAGS: the servers become flags appended to argv; nothing outlives the process.NONE: asking for servers raisesProviderCapabilityError.
A CONFIG_FILE provider needs a cwd, since that is where the config lives.
A config that is a symlink is followed to the file it names, so the link
survives the turn.
Doing it yourself
install_config_file(target, server_key=..., servers=..., defaults=...)
returns an installation whose restore() is idempotent, if you need the same
merge semantics outside a turn. restore() never raises: if the file is no
longer readable as JSON it writes the original bytes back verbatim and returns
a note saying so.
Concurrent installs into one workspace from separate processes are not
supported. The merge is read-modify-write on a plain JSON file with no lock,
so two agents installing into the same .mcp.json at the same time will race,
and one restore can undo the other's entries. Give each concurrent turn its
own workspace, or its own TurnRequest.mcp_workspace.