Give an MCP server a secret

This page is for an MCP server that agents start in the sandbox, as a local process listed in a harness profile's mcpServers. For a remote server reached over HTTP, name it on an integration instead, so its calls get approvals, hooks and the run's journal: see Call a remote MCP server.

The example uses a placeholder server, acme-mcp, that reads its key from ACME_API_KEY and calls api.acme.example; put your own server, variable and host in their place. You need:

  • Terraform set up with the Spotlight provider, using a token that has the configure and secrets scopes. See Manage Spotlight with Terraform.
  • A host to take the runs. Runs that use secrets go only to hosts, never to runners a push pool provisions. See Install a host.
  • The API's host, the header or query parameter it reads the key from, and any prefix, such as Bearer .

Keep the key as a secret bound to the API's host

variable "acme_api_key" {
  type      = string
  sensitive = true
}

data "spotlight_workspace" "acme" {
  slug = "acme"
}

resource "spotlight_secret" "acme_api_key" {
  workspace_id = data.spotlight_workspace.acme.id
  name         = "ACME_API_KEY"
  bindings = [
    { host = "api.acme.example", carrier = "header", name = "authorization", prefix = "Bearer " },
  ]
  value_wo         = var.acme_api_key
  value_wo_version = 1
}

If the API reads the key from a query parameter, as the Google Maps APIs read key, bind it there instead:

bindings = [
  { host = "maps.googleapis.com", carrier = "query", name = "key" },
]

Name the secret in the server's environment

In a harness profile, set the environment variable the server reads to secret.<NAME>:

data "spotlight_team" "core" {
  workspace_id = data.spotlight_workspace.acme.id
  key          = "core"
}

resource "spotlight_harness_profile" "acme" {
  team_id = data.spotlight_team.core.id
  key     = "acme"
  mcp_servers = [
    {
      name    = "acme"
      command = "npx"
      args    = ["-y", "acme-mcp"]
      env     = { ACME_API_KEY = "secret.${spotlight_secret.acme_api_key.name}" }
    },
  ]
}

The secret must exist before the profile names it, and it cannot be deleted while the profile names it.

Run a station with the profile

Set the station's profile to the profile's key:

station {
  key       = "implementation"
  name      = "Implementation"
  harness   = "claude-code"
  profile   = spotlight_harness_profile.acme.key
  passes_to = ["code-review"]
}

Run terraform apply. On the next run at that station, the server's calls to api.acme.example succeed. Inside the sandbox, ACME_API_KEY holds only the stand-in spotlight-egress:ACME_API_KEY. The server must send it in the authorization header as the whole value, or right after the binding's exact prefix (Bearer spotlight-egress:ACME_API_KEY), and egress puts the real key in its place on the way out. For a secret bound to a query parameter, the server must send the stand-in as that parameter's whole value, or right after the prefix, as in ?key=spotlight-egress:MAPS_KEY for a secret named MAPS_KEY. A server that encodes the key, as HTTP Basic does, or sends it somewhere else, gets nothing. Other requests to api.acme.example from the sandbox go out without the key.

If the host sets SPOTLIGHT_EGRESS_ALLOW, the list must admit the API's host, or egress refuses the server's requests to it. A server that pins certificates cannot reach a host egress adds a secret for.

On this page