Skip to content

Client Configuration

The galois-edge MCP server speaks streamable-HTTP (transport revision 2025-03-26). Any MCP client that supports this transport works; this page collects copy-pasteable config for the common ones.

The endpoint pattern in every example below:

PathUse it for
http://<edge>:8767/mcp (tailnet IP or loopback)Direct dial. No per-call auth — tailnet membership is the boundary.
https://cloud.galoislabs.ai/mcp/<edge_id>Public-internet routing through the cloud relay with per-call JWT-scoped ACLs. Daemon side shipped; cloud side in PR review — rolling out.

For the relay path, the authorization_token is a short-lived JWT minted by the cloud against the requesting user’s edge ACL. Where the cloud-side termination isn’t yet in place, all examples below default to the tailnet-direct form.

Add the daemon to claude_desktop_config.json — macOS at ~/Library/Application Support/Claude/claude_desktop_config.json, Windows at %APPDATA%\Claude\claude_desktop_config.json.

{
"mcpServers": {
"galois-edge": {
"url": "http://lab-pi.tail-1234.ts.net:8767/mcp"
}
}
}

Restart Claude Desktop. The galois-edge tools appear in the tool picker; Claude will surface a confirmation prompt for any tool the daemon flagged with destructiveHint: true (anything whose underlying profile command is is_dangerous: true).

The Anthropic API’s MCP connector (header mcp-client-2025-11-20) speaks streamable-HTTP and is the canonical path for cloud-routed agents.

import anthropic
import os
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=2048,
messages=[{
"role": "user",
"content": "List instruments and run *IDN? on each one."
}],
mcp_servers=[{
"type": "url",
"url": "https://cloud.galoislabs.ai/mcp/<edge_id>",
"name": "galois-edge",
"authorization_token": os.environ["GALOIS_MCP_JWT"],
}],
extra_headers={"mcp-client-2025-11-20": "true"},
)
print(response.content)

The JWT in authorization_token is the per-call Galois-Caller-JWT documented in the MCP Server Reference. It’s minted by the cloud against the requesting user’s edge ACL. The daemon validates it against the cloud’s JWKS and enforces tools_allow / danger_allow per call.

The OpenAI Agents SDK supports MCP servers via its MCPServerStreamableHttp adapter.

from agents import Agent, Runner
from agents.mcp.server import MCPServerStreamableHttp
galois_mcp = MCPServerStreamableHttp(
name="galois-edge",
params={
"url": "http://lab-pi.tail-1234.ts.net:8767/mcp",
},
)
agent = Agent(
name="lab-assistant",
instructions=(
"You drive lab instruments through the galois-edge MCP server. "
"Always start with list_instruments and get_capabilities before "
"calling typed per-instrument tools."
),
mcp_servers=[galois_mcp],
)
result = Runner.run_sync(agent, "Read the voltage on the DMM.")
print(result.final_output)

For the cloud-relay path, pass the JWT as a header in params:

galois_mcp = MCPServerStreamableHttp(
name="galois-edge",
params={
"url": "https://cloud.galoislabs.ai/mcp/<edge_id>",
"headers": {"Authorization": f"Bearer {os.environ['GALOIS_MCP_JWT']}"},
},
)

Cursor reads MCP server config from ~/.cursor/mcp.json (global) or .cursor/mcp.json in a workspace. Streamable-HTTP support landed in Cursor 0.42+.

{
"mcpServers": {
"galois-edge": {
"url": "http://lab-pi.tail-1234.ts.net:8767/mcp"
}
}
}

Open the Cursor settings → MCP panel after editing the file; the server should show Connected with the tool count from the daemon. If it doesn’t, check that the URL is reachable from the machine running Cursor (curl http://lab-pi.tail-1234.ts.net:8767/mcp should return a 4xx with an MCP error body, not a connection failure).

LangGraph’s langchain-mcp-adapters exposes MCP tools as LangChain tools that any LangGraph agent can call.

from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_anthropic import ChatAnthropic
client = MultiServerMCPClient({
"galois-edge": {
"url": "http://lab-pi.tail-1234.ts.net:8767/mcp",
"transport": "streamable_http",
}
})
tools = await client.get_tools()
agent = create_react_agent(
ChatAnthropic(model="claude-opus-4-7"),
tools,
)
result = await agent.ainvoke({
"messages": [("user", "List instruments and read voltage on the DMM.")]
})

Tool names from the daemon (e.g. keithley_2400__source_voltage) become the LangChain tool names — same shape, same input schema.

LlamaIndex exposes MCP servers via BasicMCPClient and McpToolSpec.

from llama_index.tools.mcp import BasicMCPClient, McpToolSpec
client = BasicMCPClient("http://lab-pi.tail-1234.ts.net:8767/mcp")
tool_spec = McpToolSpec(client=client)
tools = await tool_spec.to_tool_list_async()
# Pass tools to FunctionAgent / ReActAgent / a workflow.

Before wiring an agent, confirm the daemon is reachable. The MCP Python SDK ships a streamable-HTTP client suitable for one-shot smoke tests:

import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
async with streamablehttp_client("http://lab-pi.tail-1234.ts.net:8767/mcp") as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print(f"Connected; {len(tools.tools)} tools available")
for t in tools.tools[:5]:
print(" -", t.name)
asyncio.run(main())

Or with curl:

Terminal window
curl -X POST http://lab-pi.tail-1234.ts.net:8767/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

A 200 response with a JSON-RPC result body means the listener is up; a connection failure means MCP_ENABLED=false, the wrong port, or a network path issue.