Skip to content

MCP integrations

LatchAI is an MCP client. Servers are declared in mcp.json in your LatchAI home, connected with the official SDK, and their tools register into the same tool registry as the built-ins, namespaced <server>_<tool>. Agents and chat see one tool surface; they do not know or care which tools came from where.

Connected MCP servers and their tools

Built-ins and MCP tools in one registry.

Edit mcp.json by hand, or use Settings → MCP Servers, which validates what you type, writes the same file, and reconnects the pool in place. Either way the shape is one object keyed by server name:

{ "servers": { "<name>": { /* … */ } } }

A local process, spawned and spoken to over stdio:

{
"servers": {
"fs": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "${workspace}"]
}
}
}

${workspace} expands to the absolute workspace directory in stdio arguments — which is how you scope a filesystem server to the same tree LatchAI’s own tools are confined to.

env adds environment variables for the child process. A server is stdio unless you say otherwise, so command alone is enough.

A remote Streamable-HTTP endpoint:

{
"servers": {
"gateway": {
"transport": "http",
"url": "https://mcp-gateway.example.com/mcp",
"headers": { "Authorization": "Bearer {{secret:GATEWAY_SK}}" }
}
}
}

{{secret:NAME}} references resolve from the secret store in both stdio environment values and HTTP header values, so a token never sits in mcp.json. A reference whose secret isn’t set fails the connection with a message that names it — secret not set: GATEWAY_SK — add it in Secrets, then re-save — rather than sending the literal placeholder as a credential and collecting 401s.

Declaring transport: "http" is optional when you give a url; a config with only a URL is treated as HTTP.

Field Applies to Meaning
transport both "stdio" or "http". Inferred from command / url when omitted
command, args, env stdio The process to spawn, its arguments (${workspace} expands), and its environment
url http The Streamable-HTTP endpoint
headers http Raw headers, with {{secret:NAME}} resolved
auth http "bearer", "headers" (the default) or "oauth"
enabled both false keeps the server declared but never connects it

auth: "bearer" is the paste-a-token path in Settings: LatchAI stores what you paste as the secret mcp.<server>.token and writes only { "Authorization": "Bearer {{secret:mcp.<server>.token}}" } into mcp.json. Clearing the token deletes the secret and the header together.

auth: "oauth" uses OAuth 2.1 with Dynamic Client Registration and PKCE. LatchAI registers itself with the server as a public client, and the redirect comes back to LatchAI’s own origin at /oauth/callback/<server> — no external callback host is involved.

The flow:

  1. LatchAI tries to connect. The server answers 401, so the SDK produces an authorization URL.
  2. That server’s status becomes needs authorization, and the URL is surfaced (LatchAI also tries to open it) so you can sign in.
  3. The redirect hits LatchAI’s callback route, which exchanges the code, stores the tokens, and reconnects — at which point the server’s tools register like any other’s.

Client registration and tokens live in <home>/runs/oauth/<server>.json, owner-readable only, and survive restarts; access tokens refresh automatically. Log out disconnects the server, deletes that file, and reconnects — which lands back in needs authorization with a fresh URL, so re-authorising as a different account is one button.

Connection is asynchronous and non-blocking: the daemon boots immediately and tools appear as servers come up. A server that is slow or down does not hold up the engine — it simply contributes no tools until it connects.

Each server reports a status you can read at a glance:

Status Meaning
connecting Handshake in progress
connected (12 tools) Registered, with the count
needs authorization OAuth, waiting for you
logged out Tokens deleted; reconnecting
disabled enabled: false — declared, not connected
failed: … The connection error, verbatim

Saving from Settings reloads the whole pool: every client is closed, its tools are unregistered, and everything reconnects from the file. That is what makes a config change take effect without restarting the daemon — and it means a save briefly removes every MCP tool from the registry.

npx tsx packages/engine/src/cli.ts tools lists the whole registry from the terminal, and /api/status reports the active provider alongside registered tools and per-server MCP health.

Tool results come back as their text parts joined together; a non-text part (an image, say) appears as a [type] placeholder, and a server-reported error becomes a failed tool call the model sees and can react to. Like every tool result in LatchAI, the payload is clipped at 8,000 characters before it reaches the model.

An agent’s tools frontmatter (or a workflow node’s config.tools) is an allowlist over the combined registry, so granting an agent one MCP server’s tools without granting it shell access is a one-line change:

---
name: jira-reporter
description: Reads Jira and writes a status note.
tools: [atlassian_searchJiraIssuesUsingJql, atlassian_getJiraIssue, write_file]
---
  1. Settings → MCP Servers → add, name it fs, command npx, args -y @modelcontextprotocol/server-filesystem ${workspace}. Save. The row should turn connected (N tools).

  2. Check the names it contributed — they will be fs_read_file, fs_list_directory and so on. /api/status or the CLI lists them.

  3. Use them from a workflow node like any built-in:

    {
    "id": "summarise",
    "type": "agent",
    "position": { "x": 320, "y": 160 },
    "config": {
    "prompt": "List everything under out/ and summarise what changed today.",
    "tools": ["fs_list_directory", "fs_read_file"]
    }
    }

Every call shows up as a tool.call / tool.result pair in the events panel and the Run Monitor, exactly like a built-in.