Skip to main content

Deploy MCP on Customer Infrastructure

TradeScript Agent Console showing the session state issued by a customer-operated MCP gateway
The browser surface remains the same when the customer replaces loopback with its authenticated HTTPS and WSS gateway.

The self-hosted gateway is licensed software that your organization builds, deploys, authenticates, monitors, and stores. TradeScript does not receive MCP traffic, model traffic, pairing credentials, or session logs.

Production contract

A production gateway needs:

  • customer-owned HTTPS /mcp and WSS /bridge URLs;
  • exact browser-origin allowlists;
  • authentication and authorization for MCP and session issue/revoke requests;
  • a shared SelfHostedSessionStore when more than one process can serve traffic;
  • tenant and principal binding on every session;
  • one-time pairing codes, browser-token expiry, and immediate revocation;
  • atomic browser-token rotation that invalidates the prior token before the replacement is returned;
  • customer-owned audit, metrics, retention, and incident handling; and
  • health, readiness, graceful shutdown, rate, payload, and timeout controls.

Pairing proves possession of a short-lived code for one browser session. It is not a login system and must not replace the customer's identity boundary.

The canonical rotation endpoint is POST /sessions/rotate. The authenticated principal supplies the session ID and current browser token; the store receives only their hashes and atomically replaces the bound hash. A successful response returns the replacement token once. Repeating with the old token, rotating an expired session, or rotating a revoked session fails closed.

Programmatic server

import { startSelfHostedTradeScriptGateway } from '@tradescript/chart-mcp/server/hosted'

const gateway = await startSelfHostedTradeScriptGateway({
mode: 'production',
publicMcpUrl: 'https://agents.customer.example/mcp',
publicBridgeUrl: 'wss://agents.customer.example/bridge',
allowedOrigins: ['https://terminal.customer.example'],
sessionStore,
rateLimitRequest: customerSharedRateLimiter.check,
authenticateRequest,
authorizeRequest,
audit(event) {
customerAuditSink.write(event)
},
replica: {
mode: 'multiple',
replicaId: process.env.POD_NAME,
router: customerSharedBridgeRouter,
},
})

process.once('SIGTERM', () => void gateway.close())

authorizeRequest receives sanitized MCP identity facts in context.mcp: the Streamable HTTP session id, gateway-generated consumer id, JSON-RPC method, and tool name when available. After pairing, context.session also contains the bound tenant/principal session. Tool arguments and pairing/browser credentials are never projected into the authorization callback. The session store must atomically accept a pairing-code hash once and retain a hashed consumed marker until expiry so replay returns PAIRING_CODE_CONSUMED across replicas.

Packaged production launcher

If the customer platform prefers a packaged process instead of owning the small Node launcher, create a local ESM module:

export async function createTradeScriptSelfHostedGatewayOptions() {
return {
mode: 'production',
publicMcpUrl: 'https://agents.customer.example/mcp',
publicBridgeUrl: 'wss://agents.customer.example/bridge',
allowedOrigins: ['https://terminal.customer.example'],
sessionStore,
rateLimitRequest,
authenticateRequest,
authorizeRequest,
audit,
}
}

Then run:

TRADESCRIPT_MCP_GATEWAY_MODULE=./customer-gateway.mjs \
tradescript-chart-mcp-hosted

The module path must be a local path or file: URL. The launcher never fetches code or configuration from TradeScript. It accepts production options only and delegates startup to the same fail-closed gateway validation as the programmatic API. It has no anonymous demo or development-mode fallback. Relative paths resolve from the process working directory; container deployments should use an absolute mounted path.

The rate-limit callback runs before authentication for protected session and MCP requests and browser-bridge connections. Back it with customer-owned state shared by every replica, or enforce the equivalent policy at the customer edge. Authentication callbacks return the customer's principal and tenant facts. Authorization callbacks decide whether that principal may issue, revoke, pair, or use a session. The gateway never infers identity from an Origin header.

Development mode

The included in-memory store is intentionally limited to explicit development mode and one process. It is useful for local integration tests, not production tenancy or restart survival.

Production mode rejects a missing rate-limit authority, missing authentication or audit callbacks, an in-memory store, insecure public URLs, empty origins, and unbounded limits.

Deployment examples

The package repository includes customer-build Docker, Compose, and Kubernetes examples. They contain placeholders rather than TradeScript domains, secrets, registries, or identity providers. Build the licensed package into your own image, terminate TLS at your edge, inject configuration through your secret manager, and connect the store and audit implementations used by your platform.

For high availability, every gateway replica must share the same store and a SelfHostedBridgeRouter whose deployment is shared. The router registers each replica's live browser bridges and delivers an MCP operation arriving on one replica to a browser connected to another. Production multiple-replica startup fails closed when either shared authority is absent. Readiness must fail while either dependency is unavailable.

Upgrade and shutdown

Stop accepting new sessions, mark readiness false, allow bounded in-flight MCP requests to finish, close browser bridges, then terminate. Do not silently move an active browser token or pairing code to a different session identity.

After an upgrade, pair a fresh test session and verify context, paginated control discovery, one read, one chart write, revocation, and a denied paper-trade call before enabling customer traffic.

Next steps