Model Context Protocol (MCP) in Node Wire¶
Node Wire integrates with the Model Context Protocol to allow AI agents (like Claude or custom LLM orchestrators) to discover and use connectors as tools.
For pre-built per-connector Docker images and ToolHive registration, see packaging.md. For generating a custom standalone MCP host for a connector with nw-mcp-builder, see mcp-servers.md.
For outbound OAuth when connecting to remote authorized MCP servers over HTTP, see mcp-client-oauth.md.
Transport Modes¶
Switch between transports using the NW_MCP_TRANSPORT environment variable.
1. stdio (Default)¶
Communicates via standard I/O. Best for local development and subprocess-based clients. Bash (Linux/macOS):
# Using uv
NW_MCP_TRANSPORT=stdio uv run python -m agents.mcp_entrypoint
# Using python
NW_MCP_TRANSPORT=stdio python -m agents.mcp_entrypoint
PowerShell (Windows):
# Using uv
$env:NW_MCP_TRANSPORT="stdio"; uv run python -m agents.mcp_entrypoint
# Using python
$env:NW_MCP_TRANSPORT="stdio"; python -m agents.mcp_entrypoint
2. streamable-http¶
Native HTTP MCP server using SSE (Server-Sent Events). Bash (Linux/macOS):
# Using uv
NW_MCP_TRANSPORT=streamable-http NW_MCP_HOST=127.0.0.1 NW_MCP_PORT=8081 NW_MCP_PATH=/mcp uv run python -m agents.mcp_entrypoint
# Using python
NW_MCP_TRANSPORT=streamable-http NW_MCP_HOST=127.0.0.1 NW_MCP_PORT=8081 NW_MCP_PATH=/mcp python -m agents.mcp_entrypoint
PowerShell (Windows):
# Using uv
$env:NW_MCP_TRANSPORT="streamable-http"; $env:NW_MCP_HOST="127.0.0.1"; $env:NW_MCP_PORT="8081"; $env:NW_MCP_PATH="/mcp"; uv run python -m agents.mcp_entrypoint
# Using python
$env:NW_MCP_TRANSPORT="streamable-http"; $env:NW_MCP_HOST="127.0.0.1"; $env:NW_MCP_PORT="8081"; $env:NW_MCP_PATH="/mcp"; python -m agents.mcp_entrypoint
Streaming Features¶
- Configurable Buffering (
NW_STREAM_BUFFER_MS): When streaming, output can be buffered to reduce event spam. Set to the duration in milliseconds (e.g.,2000for a 2-second batching window). Default is0(no buffering). - Completion Signals: The core runtime emits structured "done" signals (
stream_completion_log) via Python logging when streaming ends, allowing package consumers to easily detect when a stream finishes.
Testing with MCP Inspector¶
The MCP Inspector is the best way to validate your MCP tools locally.
Testing stdio¶
npx @modelcontextprotocol/inspector uv run python -m agents.mcp_entrypoint
# Using python
npx @modelcontextprotocol/inspector python -m agents.mcp_entrypoint
Testing streamable-http¶
- Start the server (as shown above).
- Run the inspector:
- In the UI, select Streamable HTTP and connect to
http://127.0.0.1:8081/mcp.
Deployment Modes¶
Node Wire supports two ways to expose tools via MCP:
1. Combined MCP Server¶
All connectors enabled for MCP in config/connectors.yaml are exposed from a single process.
2. Individual MCP Servers¶
Each connector runs as its own independent MCP server (often in a dedicated Docker container). This is preferred for modular, scalable deployments.
- Pre-built per-connector Docker images: packaging.md
- Generate a custom standalone MCP host with nw-mcp-builder: mcp-servers.md
Multi-tenancy¶
When NW_MULTITENANCY_ENABLED=true, MCP loads config/tenants.yaml (or NW_TENANTS_PATH) and exposes nw_list_tenants, nw_select_tenant, nw_list_configs, and nw_select_config. nw_select_config's selection applies to every connector on that MCP process by default, but each connector tool also accepts an optional per-call config_name argument that outranks the shared selection for that one call. tenant_id is never accepted as a tool argument — tenant is always resolved from the session/request, never from tool call arguments.
Tenant pin precedence differs by transport:
- stdio: the NW_TENANT_ID env pin is the default until nw_select_tenant is called, after which the selection overrides it for the rest of the session.
- streamable-http: the live per-request X-Tenant-ID header (or JWT tenant claim) always wins, on every request — a prior nw_select_tenant call in one session can never shadow another concurrent session's request-level tenant. A tenant claim in the JWT that disagrees with the caller-supplied header/session tenant is rejected with a TenantIdentityMismatchError (403 TENANT_IDENTITY_MISMATCH), not silently overridden.
Session vs instance pin: nw_select_tenant sets the MCP session overlay (which tenant/config names bindings pass into factory.get). The factory then sets _tenant_id on the connector instance; run() uses that pin when tenant_id is omitted and returns TENANT_MISMATCH if a caller passes a different id.
Full guide: Multi-tenancy (MCP)
FHIR Tool Arguments (Cerner / Epic)¶
Tool names follow the pattern fhir_cerner_<action> and fhir_epic_<action> (e.g. fhir_cerner_read_patient). The MCP server normalizes common LLM aliases (e.g., patientId → resource_id).
| Action | When to use | Example arguments |
|---|---|---|
read_patient |
You have a Patient ID | {"resource_id": "12724066"} |
search_patients |
No ID, or name-based search | {"given_name": "Nancy", "family_name": "Smart"} |
search_encounter |
Find medical visits | {"patient_id": "12724066"} |
Connector Manifests¶
Each connector defines a manifest that MCP uses to understand available tools.
- Tool names follow the pattern: <connector_id>_<action> with dots in the action replaced by underscores (e.g., google_drive_files_list). Legacy dotted names (google_drive.files.list) still work on tools/call.
- The runtime handles argument normalization, so LLM-friendly aliases often work automatically.