> ## Documentation Index
> Fetch the complete documentation index at: https://milford.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP

> Let external LLMs call your flows as MCP tools.

Milford can act as a Model Context Protocol (MCP) server. External LLM clients such as Claude Desktop, Claude Code or your own agents connect to it and call your flows as tools. The MCP server is a separate program from the HTTP server. Both sit on the same core engine and read the same config file, and you can run either or both.

## Expose flows as tools

Nothing is exposed by default. List the flows you want in `mcp.expose`. Each becomes one tool with the same name as the flow id.

```yaml theme={null}
flows:
  - { file: ./flows/classify-error.json }
  - { file: ./flows/home.json }
mcp:
  expose: [classify-error]   # home stays private
  transport: http
  host: 0.0.0.0
  port: 8090
  auth: { tokens: ["${MILFORD_MCP_TOKEN}"] }
```

The tool description and its arguments come from two optional fields of the flow:

```json theme={null}
{
  "id": "classify-error",
  "description": "Classify an application error as a defect, an infrastructure incident or a user exception.",
  "input": {
    "type": "object",
    "properties": {
      "service": { "type": "string" },
      "message": { "type": "string", "description": "The error message" },
      "stackTrace": { "type": "string" },
      "history": { "type": "array", "items": { "type": "string" } }
    },
    "required": ["service", "message"]
  },
  "nodes": [],
  "edges": []
}
```

The `input` schema tells the model what to send, and the server rejects calls that break it before the flow runs. Without an `input` schema, the tool accepts any object. Tool names may contain letters, digits, `_`, `-` and `.`, up to 64 characters, so an exposed flow id must fit that.

The tool result holds the flow's `output` node as structured content:

```json theme={null}
{ "ok": true, "runId": "5c1f...", "output": "category=infra-incident", "data": { "category": { "choice": "infra-incident", "confidence": 0.93 } } }
```

If a node fails, the result is marked as an error and lists `errors` such as `"fetch-history: HTTP 503"`, so the model can decide whether to retry.

## Run the server

<CodeGroup>
  ```bash npm theme={null}
  npx @milfordai/mcp milford.config.yaml
  ```

  ```bash pnpm theme={null}
  pnpm dlx @milfordai/mcp milford.config.yaml
  ```
</CodeGroup>

The transport is set with `mcp.transport`:

| Transport | Use it for | Notes |
| - | - | - |
| `stdio` (default) | A client that starts Milford as a local process, such as Claude Desktop. | Logs go to stderr because stdout carries the protocol. |
| `http` | Remote clients over Streamable HTTP. | Serves `/mcp`, and `/health` without a token. |

The HTTP transport requires `mcp.auth.tokens` unless `mcp.host` is `127.0.0.1`, `localhost` or `::1`. The server refuses to start otherwise. It also refuses to start when `mcp.expose` is empty or names an unknown flow.

<Warning>
  An LLM that reads untrusted text can be tricked into calling any tool it can see. Expose only flows that are safe to run on the caller's behalf, and keep flows that act on real devices or systems out of `mcp.expose`.
</Warning>

## Connect a client

<Tabs>
  <Tab title="Claude Code">
    ```bash theme={null}
    claude mcp add --transport http milford http://localhost:8090/mcp \
      --header "Authorization: Bearer $MILFORD_MCP_TOKEN"
    ```
  </Tab>

  <Tab title="Claude Desktop">
    Add a stdio server to `claude_desktop_config.json`:

    ```json theme={null}
    {
      "mcpServers": {
        "milford": {
          "command": "npx",
          "args": ["-y", "@milfordai/mcp", "/absolute/path/to/milford.config.yaml"],
          "env": { "ANTHROPIC_API_KEY": "sk-ant-..." }
        }
      }
    }
    ```
  </Tab>
</Tabs>

## Call other MCP servers

The `mcp` node calls a tool on another MCP server, so a flow can reach systems that already publish MCP tools. Declare each server once under `mcpServers`, then refer to it by id. Milford connects to remote servers over Streamable HTTP. It does not start local stdio servers.

```yaml theme={null}
mcpServers:
  - id: crm
    url: https://crm.example.com/mcp
    headers: { authorization: "Bearer ${CRM_TOKEN}" }
```

```json theme={null}
{ "id": "lookup", "type": "mcp", "config": { "server": "crm", "tool": "find_customer", "arguments": { "email": "{{input.email}}" } } }
```

`arguments` are templated like other node configs. The result's `output` is the tool's text, and `data` is its structured content, or the text parsed as JSON when it is JSON. A tool that reports an error fails the node, and `retry` and `timeoutMs` on the node apply as usual. The connection is opened on first use and dropped after an error, so the next call reconnects.

### Let a decision pick the tool

The `tool` can be a template, so a `decision` node can choose which tool runs. The node then needs an `allow` list, and Milford refuses to call any tool outside it. Loading the flow fails when a templated `tool` has no `allow` list.

```json theme={null}
{ "id": "route", "type": "decision", "config": { "provider": "classifier", "kind": "choice", "prompt": "Which system holds this?", "options": ["find_customer", "find_invoice"], "state": "{{input.question}}", "minConfidence": 0.6 } },
{ "id": "lookup", "type": "mcp", "config": { "server": "crm", "tool": "{{route}}", "allow": ["find_customer", "find_invoice"], "arguments": { "query": "{{input.question}}" } } }
```

The graph stays fixed. A decision chooses among tools that you list, and nothing loops or plans.

## Limits

* Tools only. Milford does not expose MCP resources or prompts, and the `mcp` node calls tools only.
* Runs use the shared `run.timeoutMs` and `run.maxConcurrentRuns` limits. Calls over the cap get an error result.
* Authentication is static bearer tokens. Put the server behind a gateway for OIDC or mutual TLS.
* MCP calls have no idempotency key.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.