# @bellona/mcp-gateway

Bellona MCP gateway exposes the remote-control plane to local and cloud agents.
This package currently implements the Phase 180 stdio server and the first
gateway-backed tool surface:

- a stable Bellona MCP server identity
- a high-level `McpServer` factory
- injected-stream stdio transport construction for tests and local launchers
- an idempotent stdio server lifecycle handle
- gateway-backed MCP tools for `device.list`, `session.start`, `session.stop`,
  `blender.scene.info`, `blender.object.create_primitive`, `browser.navigate`,
  `browser.snapshot`, and `desktop.screenshot`
- gateway-backed MCP resources for active device capabilities, the active
  session timeline, recent artifacts, and the last command result
- optional or required MCP auth/session binding for remote/cloud clients through
  SDK `authInfo` and a gateway binding callback
- deterministic MCP composition rules for running Bellona beside Isis, Yemaya,
  and third-party MCP servers
- an Nx `test` target and a local `stdio` target

Tool, resource, and auth/session registration use protocol-owned schemas and
structural gateway interfaces so policy, approval, audit, and host routing
remain in the gateway runtime rather than inside MCP transport code.

## Local Stdio

Run the local server over process stdio:

```sh
pnpm nx stdio @bellona/mcp-gateway
```

Library callers can inject streams for tests or embedded launchers:

```ts
import { connectBellonaMcpGatewayStdioServer } from '@bellona/mcp-gateway';

const handle = await connectBellonaMcpGatewayStdioServer();

// Later, during shutdown:
await handle.close();
```

## Remote-Control Tools

Register tools by passing a gateway boundary when creating the server:

```ts
import { createBellonaMcpGatewayServer } from '@bellona/mcp-gateway';

const server = createBellonaMcpGatewayServer({
  remoteControlAuthSession: {
    mode: 'required',
    resource: 'https://gateway.example.com/mcp',
    gateway: {
      bindMcpClientSession: (request) =>
        app.mcpClientSessions.bindMcpClientSession(request),
    },
  },
  remoteControlGateway: {
    listDevices: (filter) => app.deviceRegistry.listDevices(filter),
    listSessions: () => app.sessionLifecycle.listSessions(),
    queryTimeline: (query) => app.timeline.queryTimeline(query),
    listArtifacts: (filter) => app.artifactRegistry.listArtifacts(filter),
    getClientEvents: (clientId) =>
      app.commandDispatcher.getClientEvents(clientId),
    startSession: (input) => app.sessionLifecycle.startSession(input),
    stopSession: (sessionId, input) =>
      app.sessionLifecycle.stopSession(sessionId, input),
    dispatchCommand: (command, options) =>
      app.commandDispatcher.dispatchCommand(command, options),
  },
});
```

Command tools build protocol-valid `RemoteCommandEnvelope` payloads and return
the gateway dispatch payload as MCP `structuredContent`. They do not bypass
gateway policy or approval checks; any rejection from the gateway is surfaced as
the MCP tool call failure.

When a command result includes a `RemoteCommandError`, the MCP response exposes
the protocol remediation twice: inside `result.error.remediation` for exact
protocol fidelity and at top level as `remediation` plus `suggestedNextCommand`
so agents can immediately plan the next command without rediscovering the
recovery path.

## Remote-Control Resources

The resource surface is fixed and JSON-only:

- `bellona://remote-control/devices/active/capabilities`
- `bellona://remote-control/sessions/active/timeline`
- `bellona://remote-control/artifacts/recent`
- `bellona://remote-control/commands/last-result`

Resources are read-only snapshots. The active-session resource selects the most
recent non-terminal session and reads the gateway timeline service when
available, falling back to lifecycle events if the structural gateway only
provides session state.

The last-command-result resource also promotes any command error remediation to
top-level `remediation` and `suggestedNextCommand` fields for agent recovery.

## Auth And Session Binding

For local stdio development, auth binding is disabled by default. Remote or
cloud transports should pass `remoteControlAuthSession.mode: "required"` and
validate SDK `authInfo` through the gateway. The binding enforces token expiry,
resource audience, required scopes, gateway-provided client ids, participant
ids, and allowed session ids. Authenticated client ids override tool-supplied
`clientId` values so callers cannot spoof another client stream.

## Cloud-Agent MVP Smoke

Run the deterministic Phase 180 cloud-agent MVP smoke:

```sh
pnpm nx cloud-agent:mvp-smoke @bellona/mcp-gateway
```

The smoke starts an authenticated MCP client against an in-memory Bellona MCP
gateway boundary and runs the required sequence: `device.list`, `session.start`,
`blender.scene.info`, `browser.navigate`, `desktop.screenshot`, and
`session.stop`. It prints a JSON evidence report with the selected device id,
session id, command ids, idempotency keys, screenshot artifact id, MCP
tool/resource surface, auth binding, and an explicit
`liveDeploymentExercised: false` flag. It does not contact Blender, Chrome,
desktop helpers, a Remote Host, Hetzner, or AWS directly.

## Agent Tool Selection

`selectBellonaAgentTool()` ranks candidate tools with the Phase 180 preference
order:

1. domain APIs
2. Blender, Unreal, or browser adapters
3. high-level browser agents
4. desktop fallback
5. human handoff

Desktop fallback is rejected when the intent disables it, blocked/offline
candidates are rejected before ranking, and a human handoff candidate remains
available by default. `createBellonaAgentToolCandidatesFromDevice()` converts
advertised `RemoteDevice` capabilities into policy candidates so cloud-agent
planning can use the same device capability surface exposed by the MCP gateway.

## Agent Preflight Checks

`preflightBellonaAgentRemoteTask()` evaluates the required checks before a cloud
or local agent starts remote work:

- device online state
- permission scopes and approval-gated permissions
- app or adapter availability inferred from the selected command/capability
- project-root grants, including write access, scopes, and expiry
- stream availability for evidence or observation requirements
- task budget estimates against declared limits
- approval service availability and granted approval state

The evaluator is side-effect-free and returns a structured decision with a
stable policy id, per-check status, blockers, warnings, and approval
requirements. It consumes protocol-owned `RemoteDevice` and
`RemoteStreamDescriptor` values so gateway planners can run preflight against
the same advertised capability surface used for MCP tools and resources.

## Agent Long-Running Jobs

`startBellonaAgentLongRunningJob()` and related helpers provide deterministic
agent-side job handling for remote renders, builds, file transfers, and imports.
The state machine covers:

- progress polling with adaptive next-poll timestamps
- cancellation requests with configured control commands
- checkpoint creation with resumable tokens and artifact references
- resume planning from the latest resumable checkpoint
- timeout escalation through warning, cancel, human handoff, and hard timeout

The helpers are pure TypeScript state transitions so cloud agents can persist
the returned job state in gateway storage, recover it after reconnect, and keep
all long-running work observable without coupling MCP transport code to a
specific Blender, Unreal, browser, or file-transfer adapter implementation.

## Agent Dry-Run Impact Summary

`createBellonaAgentDryRunImpactSummary()` generates a protocol-valid
`RemoteDryRunPlan` and human-readable impact summary before risky agent actions
reach dispatch. It covers Blender, Unreal, browser, desktop fallback, file, and
shell/process surfaces, and infers dry-run-required risk classes from command
risk, permission scopes, command terms, and affected resources.

The helper is side-effect-free. For risky work it returns the predicted changes,
cost and time estimates, reversibility assessment, affected surfaces, required
safeguards, warnings, and blockers. Critical shell, privileged, destructive,
financial browser, desktop, file, and project-write paths name the rollback or
manual remediation path and require the plan to be reviewed before execution.

`createBellonaAgentDryRunPlan()` exposes the per-namespace planners directly
when an agent needs the exact `RemoteDryRunPlan` object without the surrounding
impact decision. The planners are deterministic and side-effect-free for
Blender, Unreal, browser, desktop, file, and shell/process commands. They expand
the command arguments into concrete predicted changes, target paths or surfaces,
namespace-specific assumptions, warnings, rollback metadata, and protocol-valid
cost, time, and reversibility fields before any risky command reaches dispatch.

`estimateBellonaAgentDryRunCostAndTime()` populates `RemoteDryRunPlan` cost and
time estimates from historical execution telemetry when agents provide recent
command samples. The estimator matches by command, namespace, and risk class,
ignores failed samples, and fills token counts, compute units, TURN bandwidth,
render minutes, build minutes, duration confidence, and estimator metadata. If
no relevant telemetry exists, plans retain deterministic planner defaults and
mark the estimate source as `fallback-defaults`.

## MCP Composition Rules

`planBellonaMcpComposition()` builds a deterministic composition plan for a
client runtime that mounts the Bellona MCP Gateway beside other MCP servers such
as Isis, Yemaya, Playwright MCP, or vendor documentation servers.
`createBellonaMcpGatewayCompositionServerDescriptor()` provides the canonical
Bellona server descriptor from this package's remote-control tool and resource
surface.

The policy keeps Bellona remote-control tools on their unqualified names
whenever Bellona collides with another server. First-party servers are
discovered after Bellona and before third-party servers, with explicit priority
as a tie-breaker. A colliding non-winner stays callable through a stable
namespace prefix such as `isis.device.list` or `playwright.browser.navigate`;
non-colliding tools keep their original exposed name while still carrying a
`qualifiedName` for audit and routing.

Resources receive a gateway-owned qualified URI unless they already use the
server's URI scheme. Transport routes are isolated per server with a
`multiplexKey` of the form `mcp:<namespace>:<transport>:<server>` and an
explicit auth boundary: Bellona gateway session, first-party server session, or
third-party server-owned auth.

## Agent Mutation Locking

`createBellonaAgentMutationLockManager()` provides exclusive mutation locks for
multi-agent remote work. It prevents concurrent mutation of the same Blender
file, Unreal map, browser profile, project root, command queue, or approval
request by inferring lock resources from command metadata and explicit agent
inputs.

The in-memory manager supports preview, acquire, refresh, release, TTL expiry,
fencing tokens, reentrant owner refresh, and project-root overlap detection. It
is side-effect-free outside its lock table so gateway runtimes can back the same
resource records with process memory, Redis, SQL, or another durable store
without changing planner behavior.

## Agent Handoff Paths

`planBellonaAgentHandoffPath()` routes work between the cloud model, local
model, browser specialist, Blender specialist, Unreal specialist, desktop
fallback specialist, and human operator. The planner uses namespace, tool tier,
approval state, policy/preflight status, confidence, role availability, and
explicit operator targets to decide whether to stay, hand off, or block.

Each decision returns the selected route, per-step reasons, warnings, blockers,
and a protocol-valid `RemoteActorChain` so command, approval, audit, and
timeline records preserve the delegated path across orchestrators, specialists,
desktop fallback, and the operator.

## Agent Autonomous Stop Criteria

`evaluateBellonaAgentAutonomousStopCriteria()` decides when an agent must stop
without spending more turns or issuing more remote commands. It evaluates task
completion, approval denial, policy blocks, repeated failures, host busy
timeouts, lost streams, low confidence, and spent/projected budget overruns in a
deterministic priority order.

The decision returns every criterion evaluation, the primary stop reason, the
session stop reason, outcome classification, and next actions. Safety blockers
such as denied approval, policy blocks, and budget overruns take priority over a
task completion claim so agents preserve audit context and hand control back
instead of retrying blocked work.

## Agent Deep-Integration Evaluations

`createBellonaAgentDeepIntegrationEvaluationSuite()` defines paired evaluation
tasks comparing deep-integration agents against Computer Use-only baselines for
Blender, browser, desktop, and Unreal workflows. Each task has a typed adapter
variant and a Computer Use-only variant, with command constraints, evidence
requirements, score thresholds, step budgets, duration budgets, and acceptance
criteria.

`compareBellonaAgentDeepIntegrationEvaluationResults()` scores paired run
results and fails the gate when a pair is missing, a variant violates its tool
constraints, or the deep-integration run fails to beat the Computer Use-only
baseline on score, step count, and duration. The `agent:deep-integration-evals`
target validates and prints the catalog without exercising live remote hardware.

## Remote Mac No-Inbound Smoke

`runBellonaMcpRemoteMacNoInboundSmoke()` wraps the MCP cloud-agent smoke with
explicit topology assertions for the remote Mac path. It verifies that an MCP
client can authenticate, list devices, start a session, dispatch Blender,
browser, and desktop screenshot commands through the gateway, stop the session,
and receive gateway-owned artifact metadata while the controlled MacBook remains
outbound-only.

The `remote-mac:no-inbound-smoke` target prints a report with
`noInboundMacBookPorts: true`,
`hostConnectionDirection: "outbound-from-macbook"`, and
`liveDeploymentExercised: false`. It is a deterministic gateway/MCP smoke and
does not claim live hardware signoff.

## Agent Capability Memory

`createBellonaAgentCapabilityMemoryManager()` lets agents remember stable
device, adapter, app, and command capability observations. The memory manager
fingerprints capability reports, records stable observation counts, assigns
status-aware TTLs, supports forced probes and explicit invalidation, and returns
probe decisions that distinguish required probes from safe memory reuse.

Agents can call `rememberDevice()` after `device.list` or active capability
resource reads, then call `shouldProbe()` before probing a remote host again.
Unchanged hosts reuse memory until expiry, near-expiry entries can refresh in
the background, and host/version/capability changes require a fresh probe.

## Agent Visual Verification

`evaluateBellonaAgentVisualVerificationPolicy()` decides whether a remote action
needs visual verification before an agent can treat it as complete. High-impact
edits include mutating, destructive, privileged, desktop input, Blender/Unreal
edit, browser submit/upload, render, build, import, and other explicitly
evidence-required intents.

When verification is required, the policy passes only with before and after
visual evidence references, or with a documented reason that visual evidence is
impossible. The evaluator validates screenshot/video/render artifacts, evidence
phase, command/session consistency, and redaction failure so agents cannot close
high-impact work on missing, mismatched, or unusable evidence.

## Verification

```sh
pnpm nx lint @bellona/mcp-gateway
pnpm nx typecheck @bellona/mcp-gateway
pnpm nx test @bellona/mcp-gateway
pnpm nx build @bellona/mcp-gateway
pnpm nx cloud-agent:mvp-smoke @bellona/mcp-gateway
```
