Skip to content

Shared deployment checklist

One page for the operator standing up mcp-devtools as a shared service: several people or agents, one gateway, vendor credentials the callers do not hold themselves. Every row is a control the threat model or the OWASP MCP Top 10 assessment relies on, with the setting or Kubernetes object that turns it on, what it defaults to, what a shared install needs, and the test or procedure that verifies it. Each row points at the runbook that explains it rather than repeating it.

The workstation posture (one operator, MCP_AUTH_MODE=off, loopback) needs none of this: the loopback bind is the trust boundary there, and the binary refuses to leave it without inbound authentication.

Control Setting or object Default Required for shared Verified by
Inbound authentication MCP_AUTH_MODE, MCP_OIDC_PROFILE, MCP_OIDC_ISSUER, MCP_OIDC_AUDIENCE, MCP_PUBLIC_URL (identity provider runbook) off; a non-loopback bind is refused oidc (or okta) with the issuer, audience and public URL set. Every caller presents a validated bearer token; the principal in the journal is a token claim, never a client-reported name auth_mode_tests, token_validator_tests::the_negative_matrix, startup gate validate_startup_security (src/server/http.rs)
Policy and default deny MCP_POLICY_FILE, MCP_POLICY_PUBLIC_KEY, MCP_REVOCATION_FILE, MCP_AUTHORIZATION_GENERATION_FILE (revocation runbook, admin API runbook) none; required once auth is on A signed document whose rules name exactly the reads each group needs and the writes it may make (request_risk: write is never implied). Unclassified calls are always denied. Start from deploy/policies/*.yaml policy_signing_tests, policy_engine_tests, authorization_generation_tests, prompt_injection_tests (a denied follow-up call sends nothing)
Credential selection per principal MCP_CREDENTIAL_SELECTION (per-user credential selection) unset: every caller acts with the shared credential user_then_shared or user_required for vendors where the caller’s own identity must act upstream; otherwise accept that the journal, not the vendor, attributes the call credential_selection_tests, credential_cache_partition_tests
Outbound network posture Helm networkPolicy.enabled and networkPolicy.egress.allow[].cidr (chart README) false: a non-enforcing CNI silently ignores the object, so it is opt-in true on a CNI that enforces NetworkPolicy, with cidr narrowed from 0.0.0.0/0 to the identity provider, the SIEM and the vendor hosts. This is the only control on where git and the control-plane fetches may connect; the binary does not range-check destinations operations_tests::helm_network_policy_allowlists_egress_and_blocks_the_metadata_endpoint; the chart’s NOTES.txt warns at install when auth is on and the policy is off
Inbound WAF Helm waf.enabled; the proxy service in deploy/compose; deploy/k8s/waf.yaml (WAF rules) on in every shipped deployment path; the .deb/.rpm packages and the bare image ship no proxy Left on, with the server reachable only through it: Helm networkPolicy.enabled=true admits only the WAF pods to the roles. Tune a false positive with a route- and argument-scoped exemption, never by disabling a rule globally. Running the packages or the bare image as a shared service needs an equivalent edge WAF scripts/ci/waf-smoke.sh, scripts/ci/compose-smoke.sh (CI image job); operations_tests::every_shipped_proxy_runs_the_waf, ::every_deployment_path_pins_the_same_waf_image
Journal checkpoints leave the host MCP_AUDIT_JOURNAL_DIR, MCP_AUDIT_SIGNING_KEY, MCP_AUDIT_CHECKPOINT_RECORDS, MCP_AUDIT_CHECKPOINT_SECONDS, MCP_AUDIT_CHECKPOINT_EXPORT_DIR (threat model §6, release operations runbook) journal required once auth is on; export directory unset An export directory shipped off the gateway host on a schedule, and a copy of the audit public key kept where the host cannot alter it. Evidence that stays on a compromised host proves nothing against that host audit_checkpoint_tests::the_verifier_catches_edits_forgeries_wrong_keys_and_a_truncated_tail; mcp-devtools audit verify --checkpoints <dir> against the off-host copy
SIEM forwarding MCP_AUDIT_FORWARD_URL and its TLS and token settings, on the control (or all) role (SIEM forwarding runbook) unset A receiver the gateway operator does not administer, over TLS with the CA pinned, so a second copy of every intent and outcome exists in real time audit_forwarding_tests; CI siem job against a live sink
Clone availability Transport-specific inventory bb_clone is stdio-only and unavailable over HTTP No Git binary, SSH credentials or clone volume is needed for HTTP. Direct CLI cloning remains a local operator command http_transport_tests::modern_tools_list_is_stateless_deterministic_and_cacheable, ::http_refuses_bb_clone_before_creating_a_destination; tool_surface_parity_tests::stdio_advertises_bb_clone_when_bitbucket_is_enabled
Per-principal rate limit MCP_RATE_LIMIT_PER_PRINCIPAL, MCP_RATE_LIMIT_BURST (configuration) 0: off A sustained rate and burst sized to the agents’ real traffic. The bucket is per process (CF-16), so it bounds one replica, not the fleet usage_metrics_tests::a_principal_over_its_rate_limit_gets_429_with_retry_after_and_the_metric_counts_it, ::rollup_and_rate_limit_misconfiguration_refuses_startup
Control plane stays private MCP_BIND_ADDR and MCP_AUTH_MODE on the control role; Helm ingress routes /admin/ and /console to the control service (admin API runbook) deploy/k8s/control.yaml binds 127.0.0.1 with auth off: health only, admin disabled. The chart’s control role uses the same OIDC boundary as the gateway Either keep the raw manifest’s loopback bind (no admin) or enable OIDC on the control role and expose /admin/ only through the ingress rules the chart renders. Never a non-loopback bind with auth off, which the binary refuses operations_tests::helm_routes_console_and_custom_callback_to_control, ::kubernetes_waf_routes_administration_and_browser_to_control; startup_security_tests::non_loopback_bind_without_auth_is_refused_with_a_clear_reason

What this page does not cover: vendor-side permissions (the credential should hold the least the policy ever grants), identity-provider hygiene, and the agent runtime’s own handling of retrieved content. Those belong to the vendor owner, the identity team and the client owner respectively; the OWASP assessment says which is which. For an AI governance audit, the governance framework map maps these controls to the NIST AI RMF, ISO/IEC 42001 and the EU AI Act.

The controls above assume named people behind them. Assign each role to a person and a deputy before the service is shared, and record the names where the on-call rota lives; the incident response runbook refers to these roles by name.

Role Does Holds What the gateway enforces Keep apart from
Caller (person or agent) Calls tools and, where the inference path is on, models A token with mcp:tools (MCP_REQUIRED_SCOPE) or mcp:inference, and the groups policy rules name mcp:tools reaches no administrative route; policy decides every call and model request Every administrative role, on the same token. An agent’s token never carries mcp:admin
Policy author Writes rules (console editor, policy explain, drafts from activity) and submits a signed generation as a proposal mcp:admin The submission is verified and held as an admin_proposal; nothing changes until a second administrator approves (MCP_ADMIN_APPROVALS=required) Policy approver for the same change
Policy key holder Signs policies and revocation lists, seals generations, runs the offline revoke path The policy private key, kept off the gateways; the gateways hold only MCP_POLICY_PUBLIC_KEY A document, list or manifest with a signature the public key does not verify is refused. A signed generation installed as files takes effect without approval: that is the emergency path, and the journal shows it as a generation change with no admin_mutation before it Journal custodian, who reviews those direct installs
Policy approver Reviews a pending generation’s digest and approves or rejects it mcp:admin, as a person Approver’s subject must differ from the proposer’s, in the same tenant, with a token newer than MCP_WRITE_MAX_TOKEN_AGE_SECONDS; a principal marked as a machine is refused (MCP_ADMIN_PRINCIPALS=human-or-unknown) Policy author for the same change
Call approver Approves or rejects held tool calls, held prompts and budget holds mcp:admin, as a person Same separation and freshness rules, per held call (MCP_CALL_APPROVALS=required); the approval is bound to the exact call’s hold key and spent once The caller whose call is held
Revoker Stops a subject, a token or everyone; closes a session; purges an artifact The policy key for revoke (as key holder); mcp:admin for admin sessions remove and admin artifacts remove Revocation is a signed generation and takes effect within one poll on /mcp and /v1/*; session close and artifact purge are one-person by design and journaled Nothing: speed matters here. The journal makes every act reviewable
Journal custodian Keeps the off-host checkpoint copy and the audit public key, runs audit verify and the exports, owns the SIEM relationship Read access to the journal and its off-host copies; the audit public key Checkpoints are signed with MCP_AUDIT_SIGNING_KEY on the gateway; the verifier detects edits, truncation and missing checkpoints against a copy the gateway cannot change Whoever administers the gateway host, so evidence survives a compromised host
Secret owner Issues, scopes and rotates the vendor and model-provider credentials the gateway acts with Write access to the secret source (vault://, awssm://, azkv://, file://) Credentials never pass through the admin API (CF-36); a new version is read at the next refresh and its version is recorded on each call Callers, who never hold the shared credentials

What the gateway separates is narrower than this table: callers from administrators (scope), the key holder from everyone else (possession of the private key), and one approver from the proposer (subject). Policy author, policy approver, call approver and the online revoker all hold mcp:admin, and the gateway does not tell them apart: any administrator may approve any proposal in the tenant except their own. Keeping those roles with different people is the organization’s assignment, made where the identity provider grants mcp:admin. The identity provider must also issue one subject per person, since two identities of one person pass the subject check. admin_api_tests::auth_role_and_json_contracts locks the scope boundary, unified_call_approvals_tests::self_approval_other_tenants_rejection_and_stale_tokens_are_refused the approval separation, and admin_principal_kind_tests::default_policy_refuses_machines_at_admin_and_nowhere_else the refusal of machine administrators.

deploy/aws/fargate.yaml (ECS on Fargate) and deploy/azure/main.bicep (Container Apps) ship the rows above as fixed values; where a row is a parameter, this is its name. The cloud deployment plan records what each platform cannot express and the cloud marketplace runbook covers listing them. operations_tests::aws_fargate_template_preserves_the_deployment_constraints and ::azure_container_apps_template_preserves_the_deployment_constraints parse both offline on every PR; deploy/<cloud>/smoke.sh is the live proof.

Control AWS (deploy/aws) Azure (deploy/azure)
Inbound authentication OidcProfile, OidcIssuer, OidcAudience, PublicUrl; MCP_AUTH_MODE=oidc fixed oidcProfile, oidcIssuer, oidcAudience, publicUrl; MCP_AUTH_MODE=oidc fixed
Policy and default deny PolicyPublicKey; the signed documents under s3://<ConfigBucket>/policies/, copied by the init container policyPublicKey; the signed documents on the policies share, mounted read-only
Credential selection per principal mcp.env in the bucket (MCP_CREDENTIAL_SELECTION) vendorEnvironment
Outbound network posture TaskSecurityGroup: TCP 443, NFS, the SIEM port; no all-traffic rule. CreateVpcEndpoints keeps Secrets Manager, Logs and ECR inside the VPC The environment NSG: 169.254.169.254/32 denied first, then DNS, Storage, HTTPS and the SIEM port; everything else denied
Inbound WAF The proxy sidecar is the only load balancer target; ProxyImage is the shared pin The proxy sidecar is the ingress target; proxyImage is the shared pin. The Application Gateway’s WAF observes in detection mode in front
Journal checkpoints leave the host MCP_AUDIT_CHECKPOINT_EXPORT_DIR=/checkpoints on EFS; CheckpointShipping copies to CheckpointBucket (Object Lock, CheckpointLockMode, CheckpointRetentionDays) on CheckpointSyncSchedule /checkpoints on NFS; the checkpoint-sync job copies to the checkpoints container (immutability policy, checkpointRetentionDays, lockCheckpointPolicy) on checkpointSyncCron
SIEM forwarding SiemForwardUrl, SiemEgressPort; CA and client certificate under policies/, named in mcp.env siemForwardUrl, siemEgressPort; CA and client certificate on the policies share, named in vendorEnvironment
Clone root Historical /tmp/clones setting; unused by HTTP Historical /tmp/clones setting; unused by HTTP
Per-principal rate limit mcp.env vendorEnvironment
Control plane stays private --role all behind the WAF; /admin/ and /console are reached only through the load balancer under OIDC --role all behind the WAF; the environment is internal and the Application Gateway is the only way in
One writer per journal DesiredCount: 1, stop-then-start deployment minReplicas: 1, maxReplicas: 1, single revision mode, dedicated profile
Secrets never in the template VendorSecretArns names what the task role may read; AuditSigningKeySecretArn is injected by ECS into the init container only keyVaultName with Key Vault Secrets User for the identity; auditSigningKeySecretName by Key Vault reference; the SMB key read by listKeys() at deployment