The Chalk MCP Gateway is a control plane for the external Model Context Protocol (MCP) servers your agents use. Instead of pointing each agent at a handful of MCP servers directly — each with its own URL, credentials, and trust assumptions — you register those servers once with the gateway. The gateway aggregates their tools, resources, and prompts behind a single connection, holds the credentials, enforces who may call what, and records every operation.

The MCP Gateway is distinct from the MCP Server. The MCP Server exposes Chalk itself to agents (run queries, execute ChalkSQL, inspect your deployment). The MCP Gateway sits in front of other MCP servers and governs your agents' access to them.


Deploying the gateway

The gateway runs as a service in your Chalk environment, alongside the engine. To deploy it, open the Cloud Resource Configuration pane, add MCP Gateway to a resource group from the Add Config menu, where it is listed under Advanced, set its resource request, and choose Save and Apply Service. See Resource Configuration for how services and resource groups work.

The gateway is unavailable in an environment until the service is added, so deploy it before registering backends.


Registering MCP servers

Register and manage backend servers from MCP gateway → MCP servers in the Chalk dashboard. Each server has:

FieldDescription
NameA unique name for the backend.
TransportHTTP for a remote server (set a URL), or stdio for a local process (set a command and args).
CredentialThe credential used to connect (see Credentials).

Once a server is registered and connected, the dashboard shows its connection status and lets you browse everything it exposes: its tools (with input schemas), resources (with URI, MIME type, and description), and prompts (with arguments). You can also call a tool directly from the UI to verify it works.


Credentials

Credentials are stored with the gateway, not with the agents, so secrets never have to be distributed to clients. The gateway supports several credential kinds to match how each backend authenticates:

KindUse for
Bearer tokenA server that accepts a static Authorization: Bearer token.
HeadersA server that needs one or more custom HTTP headers.
Environment variablesA stdio server that reads secrets from its environment.
OAuth (user)A server behind an OAuth 2.0 user flow (authorize/token URLs, client ID/secret, scopes).
OAuth (DCR)A server that supports OAuth Dynamic Client Registration (issuer + scopes).

For OAuth-backed servers, each user links their own account, so calls run with that user’s identity rather than a shared credential.


Connecting an agent

An environment reaches the gateway one of two ways, depending on how it is provisioned. Ask your Chalk contact which applies to yours.

ProvisioningEndpoint
Shared gatewayhttps://<shared-gateway-host>/mcpeg
Gateway deployed in your own environmenthttps://<environment-id>.<cluster-dns-zone>/mcp

Your environment id is the one chalk config reports. The DNS zone is the one your Chalk cluster runs under.

Authenticate with a service token, which you can mint with chalk token get, and name the environment on every request:

HeaderValue
AuthorizationBearer <CHALK_ACCESS_TOKEN>
X-Chalk-Env-IdYour environment id

Requests that go directly to an environment’s own gateway need one more header, X-Chalk-Deployment-Type: mcp-gateway, which selects the gateway among the services running in that cluster. Without it the request never reaches the gateway and returns a 404.

Confirm the gateway answers before you configure a client:

curl -sS https://<environment-id>.<cluster-dns-zone>/health \
  -H "X-Chalk-Env-Id: <environment-id>" \
  -H "X-Chalk-Deployment-Type: mcp-gateway"

A running gateway reports its status and the backends registered with it:

{"backends":["chalk"],"status":"ok"}

Authorization policies

Policies decide which users and agents may call which tools, and with which arguments, independent of the backend itself. You write them in Rego, Open Policy Agent’s policy language. A policy has a name, its Rego source, and an enabled flag, so you can keep a policy in place and turn it on or off without deleting it.

Beyond per-tool policies, you can scope an agent to a fixed set of allowed backends, so a given agent only reaches the servers it is meant to use.

Writing a policy

The gateway evaluates data.chalk.policy.deny. Write your rules in a chalk.policy package, returning a message for each reason a request should be refused:

package chalk.policy

# Refuse repository deletion for everyone except the platform owner.
deny contains msg if {
	input.request.method == "tools/call"
	input.gateway.upstream_tool == "delete_repo"
	input.auth.subject != "platform-owner@example.com"
	msg := sprintf("user %v is not permitted to call delete_repo", [input.auth.subject])
}

Policies are deny-only. A request proceeds when no deny rule matches, so a policy lists what to refuse rather than what to permit, and an allow rule has no effect. Each message a rule produces becomes one of the decision’s reasons, and those reasons appear in the audit log.

What a policy can read

Every evaluation receives one input document describing the request:

PathDescription
input.auth.subjectThe user making the request.
input.auth.scopesThe scopes that user holds.
input.gateway.backendThe registered backend the request is bound for.
input.gateway.upstream_toolThe tool name on that backend, for tool calls.
input.gateway.upstream_uriThe resource URI on that backend, for resource reads.
input.gateway.upstream_promptThe prompt name on that backend, for prompt gets.
input.request.methodtools/call, resources/read, or prompts/get.
input.request.params.argumentsThe call’s arguments, for tool calls and prompt gets.
input.schema_versionThe version of this document’s shape. Currently 1.

Policies cover resource reads and prompt gets as well as tool calls. Match on input.request.method when a rule should apply to only one of them.

Prefer the input.gateway fields over their equivalents under input.request.params. The gateway namespaces names and encodes URIs before passing them upstream, so a resource read arrives with input.gateway.upstream_uri set to bigquery://project/dataset/table while input.request.params.uri holds chalk-mcp://resource/v1/<backend>/bigquery%3A%2F%2F.... Matching the encoded form works but is easy to get wrong, and a rule that fails to match allows the request.

More examples

Restrict a tool by its arguments:

package chalk.policy

# The SQL tool may only run reads.
deny contains msg if {
	input.gateway.upstream_tool == "execute_sql_query"
	not startswith(lower(trim_space(input.request.params.arguments.query)), "select")
	msg := "execute_sql_query is limited to SELECT statements"
}

Require a scope for writes:

package chalk.policy

deny contains msg if {
	input.gateway.upstream_tool in {"create_issue", "update_issue"}
	not "write" in input.auth.scopes
	msg := sprintf("%v requires the write scope", [input.gateway.upstream_tool])
}

Protect a set of resources:

package chalk.policy

deny contains msg if {
	input.request.method == "resources/read"
	startswith(input.gateway.upstream_uri, "bigquery://project/dataset/private_")
	not endswith(input.auth.subject, "@example.com")
	msg := sprintf("user %v cannot read private tables", [input.auth.subject])
}

Simulating before you enable

Simulate a policy against a hypothetical request before turning it on. Supply a user, scopes, backend, tool, and arguments, and the gateway returns the decision it would reach along with its reasons. A policy that fails to deny something you expected usually has a field name wrong, since an unmatched rule fails silently.


Audit log

The gateway records every operation it handles: tool calls, resource reads, and prompt fetches. The recent audit feed shows, for each one:

ColumnDescription
TimeWhen the event was recorded.
AgentThe agent, or user, that made the request.
Operationtools/call, resources/read, or prompts/get.
TargetThe namespaced tool, resource URI, or namespaced prompt the operation addressed.
BackendThe registered server the request was bound for.
PolicyWhat your policy recommended: allow, deny, error, or NOT RUN.
OutcomeWhat the gateway did: allow, deny, or error.
Policy reasonThe messages your policy returned with a deny. Empty on an allow.
ArgumentsA preview of the call arguments, truncated when long.
DurationHow long the call took, in milliseconds.

Policy and outcome

Policy is what your policy recommended. Outcome is what the gateway did. A request can be allowed despite a deny recommendation, so the two are recorded separately.

Three combinations have specific meanings:

  • Policy deny, Outcome allow: the policy ran in audit mode, which records decisions without enforcing them.
  • Policy error, Outcome allow: the policy failed to evaluate. The gateway allows the request rather than blocking on a rule it cannot run.
  • Policy NOT RUN: no policy evaluated the request, which is the case when none is enabled.

See also

  • MCP Server: connect agents to your Chalk deployment over MCP.
  • Model Gateway: OpenAI-compatible LLM routing for your Chalk deployment.