Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
16 changes: 16 additions & 0 deletions docs/deployment/set-up-mcp-bridge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,17 @@ The `env.missing` field is an array of recorded host variable names that are cur
An existing valid provider can remain ready when that host variable is unset because OpenShell retains the credential.
The JSON value `support.mode: "bridge"` identifies the agent's config-adapter capability, not a host-side traffic bridge.

Provider presence, attachment, and credential-key checks read OpenShell metadata only; they cannot prove that OpenShell actually rewrites the `openshell:resolve:env:KEY` placeholder when a request leaves the sandbox.
`mcp status <server>` therefore also runs a differential wire-level credential-resolution probe by default: it sends the same idempotent MCP `initialize` request twice from inside the sandbox, wrapped in the adapter runtime so the generated `protocol: mcp` policy applies — once with the placeholder authorization header exactly as agent traffic carries it, and once with a deliberately-unresolvable literal control bearer that no gateway rewrite can touch.
A working rewrite makes the two requests reach the endpoint with different bearers; a dead rewrite forwards both literally.
Only a placeholder HTTP 2xx paired with a rejected control verifies resolution — an accepted request proves a valid credential was on the wire.
Every non-2xx placeholder outcome is inconclusive with the hypotheses named: identical rejections cannot separate "placeholder forwarded verbatim" from "resolved but expired or revoked credential" (or, for HTTP 400, endpoint request validation), and differing rejections cannot either, because an endpoint may reject two different literal bearer strings differently — the identical-rejection warning tells you to verify the stored credential first and, if it is valid, to treat the result as the host not rewriting placeholders.
The verdict appears as `provider.credentialResolution` in JSON (`ok` true or null with the probe and control `httpStatus`/`controlHttpStatus` and a `detail`) and as a `credential resolution:` line in text output.
The probe never captures or prints endpoint response bodies — classification uses HTTP status and curl exit codes only — and it refuses to run against a persisted URL that no longer satisfies the current authenticated-endpoint boundary, so a legacy, private-alias, or plain-HTTP URL is never sent a header the gateway could rewrite into a live credential.
`mcp add` runs the same probe once after the add commits and prints a warning on failure without failing the add.
Pass `--no-probe` to skip the probe, or `--probe` to force it for every server in the multi-server status form; the bare `mcp list` and `mcp status` forms never probe so they stay fast.
Endpoints that accept both probes without enforcing authentication, differing non-auth rejections, endpoint outages, policy denials, timeouts, and unreachable sandboxes all report `ok: null` with the evidence in `detail`, so an endpoint problem is never blamed on the host's credential rewrite.

### Rotate a Credential

Export the replacement value under the same host environment name used by `mcp add`, then restart that managed server:
Expand Down Expand Up @@ -259,6 +270,11 @@ For immediate revocation, revoke the upstream credential first, then run `$$nemo

## Troubleshooting

If `mcp status <server>` warns that credential resolution could not be verified because the placeholder probe and the unresolvable control probe were rejected identically, first confirm the stored credential is valid (rotate it with `mcp restart` if in doubt).
If the credential is confirmed valid — for example the same credential works end-to-end on another host — the identical rejection means the OpenShell gateway on this host is not rewriting the `openshell:resolve:env:KEY` placeholder on egress, and every agent request to that MCP server receives the same authentication failure even while provider presence, attachment, credential readiness, and adapter registration all report healthy.
That is a host-side OpenShell defect rather than a NemoClaw registration problem: verify the OpenShell installation on the host (tracked upstream as OpenShell issue 2161).
A `credential resolution: unknown` verdict with an endpoint or policy detail means the probe could not reach a judgment; fix the reported endpoint or policy condition and rerun `mcp status <server>`.

If `restart` reports a missing provider and the original credential is not registered in OpenShell, export the same variable name used during `add` and retry.

If `status` reports an incomplete add transaction, rerun the original `mcp add` command with the same URL and environment-variable name.
Expand Down
9 changes: 8 additions & 1 deletion docs/reference/commands-nemohermes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,7 @@ NemoClaw generates a narrow `protocol: mcp` policy for the destination, literal
OpenShell `0.0.72` evaluates that policy before replacing the attached provider placeholder in the allowed request header.
Static provider placeholders are sandbox-scoped rather than endpoint-exclusive, so do not grant broader inspected-HTTP routes to the same adapter runtime and credential key.
The sandbox client connects directly through OpenShell's existing egress path, and NemoClaw does not run a host-side MCP data-plane bridge, proxy, relay, or listener.
After the add commits, NemoClaw sends one wire-level MCP `initialize` from inside the sandbox with the placeholder header to verify that OpenShell resolves the credential on egress; a resolution failure prints a warning without failing the add, and `--no-probe` skips this check.
For full setup details, see [Set Up MCP Servers](../manage-sandboxes/set-up-mcp-servers).

Hermes MCP add, restart, and remove mutate managed config and are refused while shields are up.
Expand All @@ -1274,14 +1275,20 @@ unset GITHUB_MCP_TOKEN
Inspect MCP server state for one server or for all configured servers.
Status includes OpenShell provider presence and credential-key shape, provider attachment, generated policy content match, adapter registration, current host-variable availability, and the selected agent's MCP support mode.
While a managed provider is attached, text and JSON status warn that its credential is sandbox-scoped until OpenShell supports endpoint-exclusive binding plus Host, scheme, and query enforcement.
When a single server is named, status also runs a differential wire-level credential-resolution probe: the same MCP `initialize` is sent from inside the sandbox once with the `openshell:resolve:env:KEY` placeholder header and once with a deliberately-unresolvable control bearer, and only the two HTTP status codes are compared — response bodies are never captured or printed.
A `verified` verdict requires the placeholder request to be accepted (HTTP 2xx) while the control is rejected — the only outcome that proves a valid credential was on the wire.
Identical rejections for both requests raise a warning that names the hypotheses — the placeholder forwarded verbatim, an expired or revoked credential that resolved correctly, or (for HTTP 400) endpoint request validation — and tells you to verify the stored credential first; if it is valid, the host is not rewriting placeholders and agent runtimes receive the same auth failure and skip the server.
Every other outcome — differing rejections (an endpoint may reject two different literal bearers differently), endpoints that skip authentication, endpoint outages, policy denials, and unreachable sandboxes — reports as `unknown` rather than blaming the credential rewrite, and a persisted URL that fails the current authenticated-endpoint boundary is never probed.

```bash
nemohermes my-assistant mcp status [server] [--json]
nemohermes my-assistant mcp status [server] [--json] [--probe|--no-probe]
```

| Flag | Description |
|------|-------------|
| `--json` | Emit status as JSON without credential values |
| `--probe` | Force the wire-level credential-resolution probe for every listed server |
| `--no-probe` | Skip the probe; it defaults on only when a single server is named |

### `nemohermes <name> mcp restart`

Expand Down
9 changes: 8 additions & 1 deletion docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,7 @@ NemoClaw generates a narrow `protocol: mcp` policy for the destination, literal
OpenShell `0.0.72` evaluates that policy before replacing the attached provider placeholder in the allowed request header.
Static provider placeholders are sandbox-scoped rather than endpoint-exclusive, so do not grant broader inspected-HTTP routes to the same adapter runtime and credential key.
The sandbox client connects directly through OpenShell's existing egress path, and NemoClaw does not run a host-side MCP data-plane bridge, proxy, relay, or listener.
After the add commits, NemoClaw sends one wire-level MCP `initialize` from inside the sandbox with the placeholder header to verify that OpenShell resolves the credential on egress; a resolution failure prints a warning without failing the add, and `--no-probe` skips this check.
For full setup details, see [Set Up MCP Servers](../manage-sandboxes/set-up-mcp-servers).

<AgentOnly variant="hermes">
Expand All @@ -1596,14 +1597,20 @@ unset GITHUB_MCP_TOKEN
Inspect MCP server state for one server or for all configured servers.
Status includes OpenShell provider presence and credential-key shape, provider attachment, generated policy content match, adapter registration, current host-variable availability, and the selected agent's MCP support mode.
While a managed provider is attached, text and JSON status warn that its credential is sandbox-scoped until OpenShell supports endpoint-exclusive binding plus Host, scheme, and query enforcement.
When a single server is named, status also runs a differential wire-level credential-resolution probe: the same MCP `initialize` is sent from inside the sandbox once with the `openshell:resolve:env:KEY` placeholder header and once with a deliberately-unresolvable control bearer, and only the two HTTP status codes are compared — response bodies are never captured or printed.
A `verified` verdict requires the placeholder request to be accepted (HTTP 2xx) while the control is rejected — the only outcome that proves a valid credential was on the wire.
Identical rejections for both requests raise a warning that names the hypotheses — the placeholder forwarded verbatim, an expired or revoked credential that resolved correctly, or (for HTTP 400) endpoint request validation — and tells you to verify the stored credential first; if it is valid, the host is not rewriting placeholders and agent runtimes receive the same auth failure and skip the server.
Every other outcome — differing rejections (an endpoint may reject two different literal bearers differently), endpoints that skip authentication, endpoint outages, policy denials, and unreachable sandboxes — reports as `unknown` rather than blaming the credential rewrite, and a persisted URL that fails the current authenticated-endpoint boundary is never probed.

```bash
$$nemoclaw my-assistant mcp status [server] [--json]
$$nemoclaw my-assistant mcp status [server] [--json] [--probe|--no-probe]
```

| Flag | Description |
|------|-------------|
| `--json` | Emit status as JSON without credential values |
| `--probe` | Force the wire-level credential-resolution probe for every listed server |
| `--no-probe` | Skip the probe; it defaults on only when a single server is named |

### `$$nemoclaw <name> mcp restart`

Expand Down
11 changes: 11 additions & 0 deletions src/lib/actions/sandbox/mcp-bridge-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ export interface McpBridgeStatus {
attached: boolean | null;
credentialReady: boolean | null;
detail?: string;
/**
* Wire-level placeholder-resolution probe outcome (#6379). Present only
* when the probe was requested for this entry; `ok: null` with a detail
* means the probe ran or was skipped without a verdict.
*/
credentialResolution?: {
ok: boolean | null;
httpStatus?: number;
controlHttpStatus?: number;
detail?: string;
};
};
policy: {
name?: string;
Expand Down
12 changes: 12 additions & 0 deletions src/lib/actions/sandbox/mcp-bridge-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ export function renderMcpBridgeStatus(
console.log(
` env: ${status.env.ready ? "ready" : status.env.missing.length > 0 ? `missing ${status.env.missing.join(", ")}` : "not ready"}`,
);
const resolution = status.provider.credentialResolution;
if (resolution) {
console.log(
` credential resolution: ${
resolution.ok === true
? `verified (HTTP ${resolution.httpStatus})`
: resolution.ok === false
? `FAILED (HTTP ${resolution.httpStatus})`
: `unknown${resolution.detail ? ` (${resolution.detail})` : ""}`
}`,
);
}
for (const warning of status.warnings) console.log(` warning: ${warning}`);
}
console.log("");
Expand Down
Loading
Loading