Publish an MCP Server¶
This guide covers the user-facing path for getting an MCP server into MCP Runtime:
- write an
MCPServermanifest or.mcpmetadata - build the server image
- push the image to the platform registry
- deploy the server into the platform
- verify that the server is reachable and governed
Use this guide after Getting started once the platform stack is already installed.
Choose an authoring format¶
You can describe a server in two ways:
MCPServermanifest Best when you want direct control over the Kubernetes resource the operator will reconcile..mcpmetadata Best when you want a lighter authoring format andserver generate/server deployfrom.mcpmetadata.
The platform outcome is the same in both cases: the operator reconciles a server deployment, service, route, and optional governed request path.
Option A: write an MCPServer manifest¶
Start with a minimal manifest:
apiVersion: mcpruntime.org/v1alpha1
kind: MCPServer
metadata:
name: payments
namespace: mcp-servers
spec:
description: Payments MCP server for invoice lookup and refund workflows.
image: registry.example.com/payments
imageTag: v1.0.0
port: 8088
publicPathPrefix: payments
gateway:
enabled: true
What each field does¶
metadata.nameThe server name inside the platform. This is also the default public route prefix when you do not override it.metadata.namespaceUsuallymcp-serversfor a single-team setup. In a multi-team deployment, use the team's namespace, for examplemcp-team-acme; see Multi-team isolation.spec.teamIDStable platform team ID for the server owner. The platform API defaults this for team namespaces; hand-written YAML should set it explicitly.spec.descriptionA short platform-facing summary shown in the server catalog.spec.imageThe image repository to run.spec.imageTagThe image tag when the tag is not embedded directly inspec.image.spec.portThe port your MCP process listens on inside the container.spec.publicPathPrefixThe public route prefix.paymentsbecomes/payments/mcp.spec.gateway.enabledSends requests throughmcp-gatewayso policy, session checks, and request analytics run before tool calls.spec.analyticsAnalytics emission to the Sentinel stack is on by default whenever the operator has an ingest URL configured (viaMCP_SENTINEL_INGEST_URLorspec.analytics.ingestURL). Setspec.analytics.disabled: trueto opt this server out. Platform API deploys throughmcp-runtime server deploycreate a namespace-local ingest-key Secret and setspec.analytics.apiKeySecretRefautomatically when gateway analytics are enabled.
mcp-runtime server deploy and the platform API (POST /api/runtime/servers)
publish governed servers by default: when the request does not provide
spec.gateway, the platform writes spec.gateway.enabled: true. Hand-written
YAML must set that field explicitly if you want request analytics and governed
tool calls.
Common edits¶
- Add
spec.ingressHostfor host-based routing instead of path-based routing. - Add
spec.servicePortwhen you want a Service port other than80. - Add
spec.envVarsorspec.secretEnvVarsfor runtime configuration. - Add
spec.imagePullSecretsif your registry requires explicit pull credentials. - Add
spec.toolswith tool descriptions, trust levels, and side-effect classes so the platform catalog and policy engine mirror the tool summaries clients see fromtools/list. - Add
spec.auth,spec.policy,spec.session, orspec.rolloutwhen you want stricter governance or more delivery control.
Apply the manifest only from an admin/operator workstation. server apply
requires --use-kube, kubectl, and kubeconfig/RBAC access to the target
namespace. For normal platform workflows, use the platform-backed
server deploy flow in Option B instead.
./bin/mcp-runtime server apply --file payments.yaml --use-kube
./bin/mcp-runtime server status --use-kube # pod detail; omit --use-kube for platform API summary
Option B: initialize or write .mcp metadata¶
The metadata-driven server flow uses YAML files under .mcp. server deploy
publishes directly from that metadata, while server generate renders
MCPServer manifests when you need YAML for review or GitOps.
Start with server init when you do not already have metadata:
./bin/mcp-runtime server init payments --scope org --tool list_invoices --tool refund_invoice
./bin/mcp-runtime server init payments \
--scope org \
--tool list_invoices \
--tool-spec refund_invoice:high:destructive
This creates .mcp/servers.yaml with sane defaults. Re-run with --force to
replace the same server entry, or edit the generated file when tools need
different trust levels or side-effect values. --tool is shorthand for a
read-only, low-trust tool. Use --tool-spec name:low|medium|high:read|write|destructive
for per-tool metadata.
For grant manifests, access grant init --tool is shorthand for
name:allow:<--trust>. Use repeated --tool-rule name:allow|deny:low|medium|high
when a grant needs mixed per-tool decisions or trust levels. For explicit
session manifests, access session init supports --trust,
--expires-in, --expires-at, --revoked, and upstream-token secret flags.
mcp-runtime auth login --api-url https://platform.example.com
./bin/mcp-runtime access grant init payments-globex-cursor \
--namespace mcp-team-acme \
--server payments \
--team-id <globex-team-id> \
--agent-id cursor \
--tool list_invoices \
--tool-rule refund_invoice:allow:high \
--side-effect read \
--output grant.yaml
./bin/mcp-runtime access session init cursor-session \
--namespace mcp-team-acme \
--server payments \
--human-id <user-id> \
--agent-id cursor \
--trust medium \
--expires-in 1h \
--output session.yaml
./bin/mcp-runtime access grant apply --file grant.yaml
# Platform API session apply is admin-only — use adapter for agents:
# ./bin/mcp-runtime access session apply --file session.yaml
Adapter-driven agents should skip manual session apply; use
mcp-runtime adapter stdio --server payments --agent cursor --auto-refresh
after the grant exists. See Agent Adapters.
Example metadata:
version: v1
servers:
- name: payments
description: Payments MCP server for invoice lookup and refund workflows.
scope: org
image: registry.example.com/org/payments
imageTag: v1.0.0
route: /payments
port: 8088
replicas: 1
gateway:
enabled: true
tools:
- name: list_invoices
description: List invoices for a customer account.
requiredTrust: low
sideEffect: read
- name: refund_invoice
description: Issue a refund for an invoice.
requiredTrust: high
sideEffect: destructive
Metadata fields¶
nameThe server name.descriptionA short platform-facing summary shown in the server catalog.imageThe image repository.imageTagThe image tag.scopeOptional publish destination:tenant,org, orpublic.server initwritesscope: tenantby default so new metadata starts in the private team publish path.orgresolves to the org catalog namespace, andpublicresolves to the public catalog namespace.tenantselects one of the authenticated user's team namespaces when you publish through the platform API; when generating Kubernetes YAML directly, setnamespaceexplicitly for team deployments.routeThe public path prefix that will become the server ingress path.portThe container port.replicasThe desired replica count.namespaceThe target namespace.toolsTool inventory for the platform catalog and policy authoring. Include each tool's description when the MCP server SDK exposes one throughtools/list, and setsideEffecttoread,write, ordestructive. Tool side effects are required when a tool is listed.auth,policy,session, andgatewayGoverned request-path settings.server initwritesgateway.enabled: true, allow-list/deny policy, andsession.required: trueso public tool calls go through the adapter/session path by default. Use--policy-mode,--default-decision, or--session-required=falseto change those scaffolded values. Init omits platform-managed gateway wiring and auth/session header details unless you override them intentionally.
Metadata defaults¶
If fields are omitted, the loader applies defaults:
- image defaults toward the platform registry path
server initwritesscope: tenantunless you pass--scope orgor--scope publicscope: org/scope: publicprefix default image repositories withorg/orpublic/and default the generated namespace tomcp-servers-orgormcp-servers-public- tag defaults to
latest - route is normalized with a leading
/ - port defaults to
8088 - replicas default to
1 - namespace defaults to
mcp-servers server initomits platform-managed gateway wiring and auth/session header details; add those fields only when you intentionally need to override the platform/operator defaults
For multi-team deployments, set scope: tenant and deploy through
server deploy --scope tenant with platform credentials. The platform API
resolves the target team namespace and defaults team ownership metadata. The
namespace is the write boundary for the MCPServer, grants, sessions, and
secrets. server deploy creates by default; if a server with the same name
already exists, pass --update to redeploy it intentionally.
Deploy from metadata:
./bin/mcp-runtime server deploy payments --scope org --metadata-dir .mcp
# Redeploy an existing server after changing metadata or image tag.
./bin/mcp-runtime server deploy payments --scope org --metadata-dir .mcp --update
# Optional: render YAML for review/GitOps.
./bin/mcp-runtime server generate --metadata-dir .mcp --output manifests/
Build and push the server image¶
MCP Runtime supports two practical image flows. Keep these flows separate so tags stay consistent.
Flow A — metadata-driven build with the CLI¶
./bin/mcp-runtime server build image payments --tag v1.0.0 --platform linux/amd64
server build image builds the image, resolves the target registry host, tags the local image with that resolved reference, and rewrites matching .mcp metadata (image and imageTag). The command defaults Docker builds to linux/amd64, matching common amd64 Kubernetes nodes; set --platform or MCP_DOCKER_PLATFORM when your target nodes use another architecture. Registry resolution prefers explicit registry env, then the cluster's registry/registry Ingress host, before falling back to the registry Service address. When metadata sets scope: tenant, the build command uses platform credentials to resolve the same team repository prefix that registry push --scope tenant uses, so log in first or set MCP_PLATFORM_API_TOKEN with a saved or explicit MCP_PLATFORM_API_URL.
After this command, push the exact image reference produced by the build output (or read it from the rewritten metadata):
mcp-runtime auth login --api-url https://platform.example.com
./bin/mcp-runtime registry push --scope org --image <exact-image-ref-from-build>
registry push requires platform credentials from mcp-runtime auth login or
MCP_PLATFORM_API_TOKEN with a saved or explicit MCP_PLATFORM_API_URL;
unauthenticated pushes are
rejected before Docker or the in-cluster helper starts. <exact-image-ref-from-build>
may be a resolved public registry host such as registry.example.com/org/payments:v1.0.0,
or a registry Service address when no public registry Ingress is configured.
Use --scope public for public catalog images. Use --scope tenant for team
images; if the image name has no repository prefix, the CLI prefixes it with
the authenticated user's active team slug. Explicit repository prefixes for
tenant images must match one of the user's teams.
Then deploy from metadata:
./bin/mcp-runtime server deploy payments --scope org --metadata-dir .mcp
Flow B — manual Docker build, push, and direct platform deploy¶
Use this when you manage image tags directly and want the platform API to write
the MCPServer for you:
docker build -t payments:v1.0.0 .
mcp-runtime auth login --api-url https://platform.example.com
./bin/mcp-runtime registry push --scope public --image payments:v1.0.0
./bin/mcp-runtime server deploy payments --scope public --image payments --tag v1.0.0
Short names like payments:v1.0.0 are valid for registry push when that
exact local image tag exists. For server deploy, the platform API accepts
short names such as payments and resolves them to the configured registry and
scope prefix, for example <registry>/public/payments in public mode or
<registry>/<team-slug>/payments in tenant mode.
server deploy --scope public resolves the platform public catalog namespace;
--scope org resolves the org catalog namespace; --scope tenant uses the
authenticated user's team namespace unless --team or --namespace selects one
explicitly. server deploy uses the default public route /<name>/mcp and
passes that same value as MCP_PATH so the bundled Go, Python, and Rust
examples listen on the route the ingress exposes. The platform API and CLI
deploy flow also default spec.gateway.enabled: true, so published servers use
the governed gateway path unless you explicitly provide spec.gateway. When
you run server deploy from a directory with .mcp/*.yaml, the CLI copies the
matching server metadata into the request. If the metadata directory contains
exactly one server, it uses that server's inventory even when the deployed
runtime name is different; this keeps spec.tools side-effect metadata in sync
with governance policy.
Flow C — manual Docker build, push, and manifest apply (admin/GitOps)¶
Use this when you need full control of MCPServer fields and have
admin/operator Kubernetes access. For the normal tenant platform path, use
Flow A/B with server deploy instead of server apply --use-kube.
docker build -t payments:v1.0.0 .
mcp-runtime auth login --api-url https://platform.example.com
./bin/mcp-runtime registry push --scope tenant --image payments:v1.0.0
./bin/mcp-runtime server apply --file payments.yaml --use-kube
What happens after deploy¶
After the server description reaches the platform, the operator does the following:
- stores the
MCPServerresource in Kubernetes - resolves the final image reference
- creates or updates a
Deployment - creates or updates a
Service - creates or updates an
Ingress - renders gateway policy when governed access is enabled
- updates
MCPServer.statuswith readiness and progress
With the default path-based shape, the server becomes available at:
/{publicPathPrefix}/mcp
For the example above, that is:
/payments/mcp
Verify from the CLI¶
Check server state:
mcp-runtime auth login --api-url https://platform.example.com
./bin/mcp-runtime server status
./bin/mcp-runtime server get payments
./bin/mcp-runtime status
If the server uses governed access:
./bin/mcp-runtime server policy inspect payments
./bin/mcp-runtime sentinel status
If traffic is failing:
./bin/mcp-runtime server policy inspect payments
./bin/mcp-runtime status
# Admin/operator only
./bin/mcp-runtime sentinel logs gateway --follow
./bin/mcp-runtime server logs payments --follow --use-kube
Common failure points¶
Image built, but deploy still points at the wrong image¶
Check:
- the
spec.imageandspec.imageTagin your manifest - the metadata entry updated by
server build image - whether you pushed the exact same image reference (registry/repo/tag) that your metadata or manifest points to
Image pushed, but server never becomes ready¶
Check:
./bin/mcp-runtime server get <name>./bin/mcp-runtime server status./bin/mcp-runtime status
Most often this is an image reference, image-pull, or routing mismatch.
Route exists, but governed calls fail¶
Check:
./bin/mcp-runtime server policy inspect <name>- your grant and session objects
./bin/mcp-runtime sentinel logs gateway --follow
Event count is 0¶
Check:
kubectl get mcpserver <name> -n <namespace> -o yamlGatewayReady=TrueandPolicyReady=Trueon the MCPServer status- the server pod is
2/2and includes themcp-gatewaysidecar kubectl logs -n <namespace> <pod> -c mcp-gateway./bin/mcp-runtime sentinel status./bin/mcp-runtime sentinel logs ingest --follow./bin/mcp-runtime sentinel logs processor --follow
Request analytics only exist for traffic that flows through mcp-gateway.
The adapter is not required for analytics; it only helps clients that cannot
attach identity or session headers directly, or that want platform-issued
sessions. Hand-written YAML must include spec.gateway.enabled: true for
request analytics. If you apply raw YAML with kubectl apply or
server apply --use-kube instead of server deploy, also create a
namespace-local ingest-key Secret and set spec.analytics.apiKeySecretRef;
otherwise the gateway can reach ingest but events will be rejected with 401.
Analytics is on by default for gateway traffic when the operator has
MCP_SENTINEL_INGEST_URL; opt out with:
analytics:
disabled: true
Related docs¶
Next: Agent Adapters — connect your MCP client via the adapter proxy.