# MCP servers

**An inbound MCP server gives a project's coders a tool the repository does not contain** — a search index, an internal service, a design system's API. You declare it once for the project, hlix admits it only if it passes a set of checks, and every task afterwards finds it in its checkout.

This is the **inbound** direction. The [official hlix MCP server](/mcp/) points the other way, letting a coding agent on your own machine drive hlix.

## Prerequisites

- A project in your Workspace, and `manage` access on it.
- Every credential the server needs already stored as a [project secret](/configure/secrets/). A server is refused if a key it references has no secret behind it.
- For a remote server, an `https` URL with no credentials, query string or fragment in it.

## Declare one

```bash
curl -sS -X POST "$HLIX_BASE_URL/v1/api/projects/$PROJECT_ID/resources/mcp" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
  -H 'content-type: application/json' \
  -d '{
    "name": "docs-search",
    "transport": "http",
    "url": "https://mcp.internal.example.com/",
    "headers": { "Authorization": "Bearer ${DOCS_MCP_TOKEN}" },
    "envKeys": ["DOCS_MCP_TOKEN"]
  }'
```

Expected result:

```json
{"mcpServer": {"name": "docs-search", "transport": "http", "envKeys": ["DOCS_MCP_TOKEN"]}}
```

`201`, echoing the name, the transport and the environment keys it references — never a value. The server is merged into the project's active [Environment](/configure/environments/), and the write is recorded in the [audit log](/security/audit-log/) as `environment.config.updated`.

`transport` is `stdio`, `http` or `sse`. A remote transport takes `url` and optional `headers`; `stdio` takes `command` and `args`.

## Credentials are references, never values

Anywhere a secret would go, hlix requires a placeholder naming a project secret — `${DOCS_MCP_TOKEN}` — and refuses a literal.

- Every referenced key must already resolve as a project secret. If one does not, the import is refused with the missing names, so a server can never be admitted in a state where it would fail at task time.
- A remote header must be a **complete** placeholder template, not a string with one embedded in it.
- A `stdio` argument that looks like a credential must be a complete placeholder too, and a high-confidence literal credential anywhere in the arguments is rejected outright.

The placeholders are resolved when a task starts, from that project's secrets, into the configuration the coder reads. The stored declaration never holds the value.

## What a `stdio` server may run

`command` must be one of exactly seven direct executables: `bunx`, `deno`, `node`, `npx`, `python`, `python3`, `uvx`. Nothing else is accepted, and a shell is not among them.

**No shell commands:** A `stdio` server is a process hlix starts in every task's sandbox. A shell command would turn the declaration into a script that the credential checks above cannot verify, so only a direct executable with checked arguments is accepted.

Remote transports get their own rules: `https` only, no username or password in the URL, no query string or fragment, and no host that resolves to a private or metadata address.

## Where it lands in a task

When a task starts, hlix resolves the placeholders and sends the server definitions to the task's sandbox. In the task's checkout they are **merged** into `.mcp.json` — anything the repository already declares there is preserved, so a project-level server adds to what the repository configures rather than replacing it.

Whether a coder then uses the tool is up to that coder: `.mcp.json` is the convention the external coders read. See [External coders](/coders/external-acp/).

**Every coder receives it:** Every coder, Hlix Code included, reads `.mcp.json` and the server list its session starts with. Either way the server is started and reached from **inside** the task's Coding Workspace, never from hlix's own infrastructure. A server that cannot be reached is skipped with a warning; the task still runs.

**Workspace-wide servers:** `/v1/api/mcp-servers` (**Settings → Agent tools** in the dashboard) records MCP servers for the whole Workspace. Every **enabled** one reaches every task in the Workspace, alongside the project's own. When both declare the same name, the project's Environment wins. A server's environment values and headers are write-only: a read lists each name with an empty value, and an edit that leaves a value empty keeps the saved one.

## If a server is refused or never appears

- **``{"error":"MCP server references missing project secrets: DOCS_MCP_TOKEN"}``, `400`.** Add the named [project secrets](/configure/secrets/) first, then import again.
- **`MCP stdio command must be a supported direct executable, never a shell`.** Use the interpreter or runner directly and pass the script as an argument.
- **`MCP stdio arguments must not contain literal credentials`.** Replace the literal with a `${KEY}` placeholder and add the key to `envKeys`.
- **`MCP stdio secret arguments must use a complete environment placeholder`.** The argument embeds a placeholder in a longer string. The whole argument must be the placeholder.
- **``MCP remote header Authorization must be a strict environment placeholder template``.** Same rule for headers: the value is a template referencing exactly one key.
- **`MCP remote server URL must use HTTPS`.** Plain HTTP is refused.
- **`MCP remote server URL must not contain credentials`.** No `user:pass@` in the URL — use a header referencing a secret.
- **`MCP remote server URL must not contain a query or fragment`.** Put per-request parameters in the request, not in the declared endpoint.
- **`MCP remote server URL must not target a private or metadata host`.** The host resolves to a private address. Expose the service publicly, or use a `stdio` server instead.
- **A task fails with ``MCP server … requires missing project secret …``.** The secret existed at import and does not now. Restore it, or re-import the server without that key.
- **The server is in the Environment and the coder ignores it.** hlix merges the declaration into `.mcp.json`; it does not force a coder to call it. Check that the task's coder reads that file.
- **A `403` on the import.** It needs `manage` access on the project.

## Where to go next

| If you want to… | Read |
| --- | --- |
| Store the credentials a server references | [Secrets](/configure/secrets/) |
| See the bundle a server is merged into | [Environments](/configure/environments/) |
| Import a server from an existing repository | [Import skills, agents & MCP](/cli/resources/) |
| Drive hlix from your own coding agent | [Official MCP server](/mcp/) |

## Next steps

[Import trust model](/security/import-trust-model/)
  [Secrets](/configure/secrets/)
  [Environments](/configure/environments/)
  [Official MCP server](/mcp/)