Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ __pycache__/
*.egg-info/
dist/
.env
config/tenants.yaml
config/tenants.yaml.tmp
config/playground_tenants.yaml
config/playground_tenants.yaml.tmp
.DS_Store
**/.DS_Store
.coverage
Expand Down Expand Up @@ -46,3 +50,7 @@ launch.json
# log
mcp_debug.log
.cursor/

# nw-mcp-builder
nw-mcp-builder/fixtures/
nw-mcp-builder/out/
11 changes: 11 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ copy sample.env .env
| `NW_JWT_ISSUER` | Expected JWT `iss` claim when any `*_JWT_SECRET` is set | _(required with JWT secret)_ |
| `NW_SMTP_ALLOWED_HOSTS` | Optional comma-separated SMTP relay hostnames permitted for `smtp.send_email` (recommended for production) | _(unset = env relay only)_ |

### Multi-tenancy

| Variable | Description | Default |
|----------|-------------|---------|
| `NW_MULTITENANCY_ENABLED` | When `true`, resolve tenant from header / `NW_TENANT_ID` / JWT and require a tenant (missing → error). When `false`, always `__default__`. | `false` |
| `NW_TENANT_ID` | **MCP stdio only** — pins the process to one tenant. Do not set on multi-tenant streamable-http (use `X-Tenant-ID` instead). Required when multitenancy is enabled for stdio. | _(unset)_ |
| `NW_TENANT_ID_HEADER` | HTTP/gRPC header name for tenant id (case-insensitive) | `X-Tenant-ID` |
| `NW_TENANTS_PATH` | Path to the YAML file that persists runtime named configs + tenant secret overlays (`config/tenants.yaml` by default; gitignored) | `config/tenants.yaml` |

Named-tenant secrets use `NW_{TENANT}_{CONNECTOR}_{CONFIG}_{KEY}` (one credential vault per named config). MCP transport details: [mcp-servers.md](mcp-servers.md#multi-tenancy-mcp).

---

## Configuration File (`config/connectors.yaml`)
Expand Down
40 changes: 34 additions & 6 deletions playground/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,49 @@ The Agentic Workflow panel displays the active transport as a pill:
Set the mode before starting the REST API:

```powershell
# Buffered stdio mode
# Playground agent: in-process MCP (recommended for local MT testing)
$env:MODE="API"
$env:NW_MCP_TRANSPORT="stdio"
# Leave TOOLHIVE_MCP_URL empty unless ToolHive / a separate MCP server is running
uv run node-wire
```

```powershell
# Streamable HTTP mode
# Streamable HTTP UI + remote MCP proxy (requires something listening on the URL)
$env:MODE="API"
$env:NW_MCP_TRANSPORT="streamable-http"
$env:NW_MCP_HOST="127.0.0.1"
$env:NW_MCP_PORT="8081"
$env:NW_MCP_PATH="/mcp"
$env:TOOLHIVE_MCP_URL="http://127.0.0.1:8081/mcp"
# In another terminal: start MCP — see docs/mcp.md
uv run node-wire
```

After changing `NW_MCP_TRANSPORT` / `TOOLHIVE_MCP_URL`, restart the backend and hard refresh the browser so the latest `app.js` and transport status are loaded.

**Modes (do not confuse):**

| Goal | What to run |
|------|-------------|
| Playground + connector scenarios + Agent (tenant-aware) | `MODE=API` → `http://127.0.0.1:8000/playground/` |
| Standalone MCP tools (Inspector / Claude) | `python -m agents.mcp_entrypoint` (`NW_MCP_TRANSPORT=stdio` or `streamable-http` on `:8081`) — see [docs/mcp.md](../docs/mcp.md) |
| Full binding as MCP process | `MODE=MCP` with same transport vars |

If `TOOLHIVE_MCP_URL` points at `:8081` but nothing is listening, Agent chat fails with `All connection attempts failed`. Clear the URL or start an MCP server; by default the playground falls back to **in-process** MCP when the proxy cannot list tools.

#### Multitenancy (`NW_MULTITENANCY_ENABLED`)

Defaults to off (legacy single-tenant). When enabled:

```powershell
$env:NW_MULTITENANCY_ENABLED="true"
uv run node-wire
```

After changing `NW_MCP_TRANSPORT`, restart the backend and hard refresh the browser so the latest `app.js` and transport status are loaded.
- **Tenant ID required**: connector/scenario calls without `X-Tenant-ID` return **400**. Explicit `__default__` is allowed.
- **Header**: Tenant dropdown (existing tenants) and Config dropdown appear when multitenancy is on. Header **Add config** is hidden; use **Add config** on each System Connector page.
- **Config dropdown**: lists configs for the **active connector** only under the selected tenant. Switching connectors clears a name that does not exist for the new connector.
- **Agentic Workflow**: sends the same `X-Tenant-ID` (and optional `config_name` query). Local agent MCP runs **in-process** against the playground factory. Streamable-HTTP / ToolHive proxy URLs receive `X-Tenant-ID` on each MCP HTTP request. (Agent Add-config UI is unchanged / out of scope for this flow.)
- **Per-connector Add config**: On a connector page, **Add config** opens a modal for that connector only (tenant free-text for new tenants, config name, default flag, and varying credentials). Each named config has its **own** credential vault (`NW_{TENANT}_{CONNECTOR}_{CONFIG}_{KEY}`). Shared host env (e.g. `EPIC_FHIR_BASE_URL`, `EPIC_TOKEN_URL`) is copied into that config’s secret overlay when omitted. Persist file: gitignored `config/tenants.yaml` (holds secrets — do not commit).
- **Tenant in logs**: When multitenancy is on, server INFO lines include `tenant_id` / `config_name` for Agent chat, connector scenarios, config mutations, REST connector calls, and MCP tool resolution. The playground Technical Audit panel also prints Tenant/Config for those actions.

#### Testing the MCP server with Inspector

Expand Down
Loading