# Connecting MCP Servers

> Applies to: ChengOS v0.1.0+ ｜ Last verified: 2026-08-12 ｜ Source: `crates/cheng-nodes/src/nodes/builtin/tools/mcp_hub.rs`, `crates/cheng-mcp/src/lib.rs`, `crates/cheng-core/src/domain/mcp_server.rs`

ChengOS speaks MCP in both directions:

| Direction | What it means | Where |
|---|---|---|
| **Client** | Your agent calls tools from external MCP servers | `tools/mcp_hub` |
| **Server** | Your workflows are exposed as MCP tools to external AI clients | `cheng-mcp` |

This page is mostly about the client side, which is what "connecting an MCP server" usually
means.

## The MCP Hub node

`tools/mcp_hub` is one node that manages **several** MCP servers. Connect it to an agent's
`tools` port and the agent gains every tool those servers expose, subject to the visibility
rules below.

Its guiding principle is **config-first**: by default the node returns only server status. It
performs a request only when you explicitly discover or call. Dropping the node on the canvas
does not fire off connections.

## Transports

| Transport | Configure |
|---|---|
| `stdio` | `command` (e.g. `uvx`, `npx`) plus `args` and `env` — JSON-RPC over newline-delimited stdin/stdout to a child process |
| `streamable_http` | `url`, optional `authorization`, optional `headers` — JSON-RPC over HTTP POST |

The two are mutually exclusive: a server config with a `command` is stdio, one with a `url` is
HTTP.

Each server entry also has `enabled`, a per-server `timeout`, and a `long_running` flag.
`long_running` keeps a persistent session — a live child process for stdio, a live HTTP session
otherwise — instead of reconnecting per call. Use it for servers with expensive startup; the
node caches the session and reuses it.

## Credentials, not literals

Do not paste API keys into the server config. Connect a `utils/credential` node's `env_vars`
output to the hub's `env_vars` input, then reference values as `${env:KEY}`.

Placeholders expand in **every** string field — `url`, `authorization`, `headers`, `args`, and
`env`. Values from the credential node take precedence over the system process environment when
both define a key, so a workflow's own credentials cannot be shadowed by whatever happens to be
in the server's environment.

## Visibility control

Exposing every tool from every server to a model is usually wrong: it burns context and hands
the model destructive operations by default. The hub separates **visible** (the model can see
it) from **callable** (the model may invoke it).

The recommended structured form:

```json
{
  "servers": {
    "my-server": { "visible": true, "callable": true },
    "*":         { "visible": true, "callable": true }
  },
  "tools": {
    "my-server:search": { "visible": true,  "callable": true  },
    "my-server:delete": { "visible": false, "callable": false },
    "*":                { "visible": true,  "callable": true  }
  }
}
```

There is also a flat compatibility form keyed by `server:tool`. When both are present,
`visibility_config` wins over `tool_visibility`.

Matching runs most-specific first:

```text
1. server:tool     exact
2. server:*        server wildcard
3. *:tool          tool-name wildcard
4. *               global default
```

The pattern worth adopting: set `*` to visible-and-callable, then explicitly deny the
destructive tools by name. A tool that is `visible: false` does not consume context either.

## Calling tools

| Input | Meaning |
|---|---|
| `tool_name` | `server:tool` — or just `tool` when there is only one server |
| `arguments` | JSON object of parameters |
| `discover_only` | List every server's tools without calling anything |
| `timeout_ms` | Request timeout; 0 or empty means no limit |

`tool_name`, `arguments`, and `discover_only` are LLM-fillable. The server registry, visibility
config, and credentials are static canvas configuration the model never sees — which is what
keeps the model from editing its own permissions.

Output is a single `result` port carrying summary, per-server status, discovered tools, and any
tool-call output, so the node's shape does not change between configuration, discovery, and
call.

## Multimodal results

MCP tools can return more than text, and the hub preserves all of it in `content_parts`:

```json
{"type":"text","text":"..."}
{"type":"image","mimeType":"image/png","data":"base64..."}
{"type":"resource","resource":{"uri":"...","text":"...","mimeType":"..."}}
```

An MCP tool that renders a chart therefore reaches a vision-capable model as an image rather
than as a dropped attachment.

## Publishing workflows as MCP servers

The other direction. `cheng-mcp` provides two server modes:

| Mode | What it exposes | Endpoint |
|---|---|---|
| `ChengMcpServer` | Application-level workflow operations as tools | `/mcp`, enabled by `MCP_ENABLED=true` |
| `WorkflowMcpServer` | One published workflow's tools | Its own ephemeral port, e.g. `http://127.0.0.1:PORT/mcp` |

Both use Streamable HTTP. For stdio:

```sh
cargo run -p cheng-mcp -- --stdio
```

A published workflow gets an `McpServer` record binding it to a gateway port, an internal URL
reported by the node via heartbeat, and a public gateway URL used in the JSON manifest. The
publishing user owns the auth tokens.

## Troubleshooting

| Symptom | Look at |
|---|---|
| Server shows disconnected | For stdio, is the `command` on the server's `PATH`? For HTTP, is `url` reachable *from the backend*, not from your browser? |
| Tools do not appear | Run with `discover_only` first; if they appear there but not to the agent, it is a visibility rule |
| Auth failures | `${env:KEY}` unresolved — check the credential node is connected and the key name matches |
| Slow first call | The server has expensive startup; set `long_running` |
| Model calls a tool you did not want | Set `callable: false` for it — do not rely on prompt instructions |

## Next steps

- [Tool Discovery & Tool Hub](/docs/agent-tools-and-hub) — how the agent sees these tools alongside built-in ones.
- [ReAct Agent Guide](/docs/agent-react-guide) — the agent that calls them.
- [Skills System Overview](/docs/agent-skills-system) — the other way to package capability.
