Deploy MCP on Customer Infrastructure

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
/mcpand WSS/bridgeURLs; - exact browser-origin allowlists;
- authentication and authorization for MCP and session issue/revoke requests;
- a shared
SelfHostedSessionStorewhen 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
- Embed the Agent Console with the self-hosted session client and attachment lifecycle.
- Choose a deployment recipe for individual, small-business, or multi-tenant operation.
- Review MCP security before approving production origins, identity hooks, and retention policy.