# Connecting MCP clients

> How MCP clients reach a FrontMCP server: by URL over Streamable HTTP, by starting it over stdio, or over a Unix socket; the commands that serve stdio; publishing a server to npm for npx; client snippets from the CLI; and what the MCP Bundle target produces.

Source: https://frontmcp.dev/reference/deployment/mcp-clients

An MCP client reaches a server in one of two ways. It connects to a URL, for a server that's already running somewhere: a deployed one, or `frontmcp dev` on your machine. Or it starts the server itself, as a child process, and talks to it over stdin and stdout. FrontMCP serves both from the same build; what matters is the URL's path, the command, and keeping everything but protocol messages off stdout. This page covers each, publishing a server to npm so clients can start it with `npx`, the snippet the CLI prints for a client's settings, and the MCP Bundle target.

```json
{ "mcpServers": { "help-desk": { "url": "https://desk.example.com/mcp" } } }
```

```json
{ "mcpServers": { "help-desk": { "command": "/opt/help-desk/dist/node/help-desk", "args": ["--stdio"] } } }
```

---

## Reference

### Ways to connect

| Client connects | Server runs as | Clients per server | Page |
| --- | --- | --- | --- |
| To a URL, over Streamable HTTP | FrontMCP's Node server, a serverless function or a Worker | Many | [Over HTTP](#over-http) |
| By starting a command, over stdio | A process the client starts and stops | One | [Over stdio](#over-stdio) |
| To a Unix socket | FrontMCP's Node server on a socket file | Many, on the same machine | [Over a Unix socket](#over-a-unix-socket) |
| In the same process | [`connect()`](https://frontmcp.dev/reference/sdk/connect) or [`createDirect()`](https://frontmcp.dev/reference/sdk/create) | Your code | |

Most clients keep their servers in a JSON file with an `mcpServers` object, one entry per server; the field that says "this entry is a URL" differs between clients (`type`, `transport`, or none), so check your client's documentation for it. The shapes on this page are the server's side of the contract.

### Over HTTP

The URL is the server's public address plus its MCP endpoint, `http.entryPath`:

| The server | Its URL |
| --- | --- |
| `frontmcp dev` in a project from `frontmcp create` | `http://localhost:3000/mcp`: the port and path of `transport.http` in `frontmcp.config` |
| The `node` build, started with its script `dist/node/<name>` or with `node dist/node/<name>.bundle.js`, which a Docker image's `CMD` runs | `http.entryPath`, else `transport.http.path` (`/mcp` in a project from `frontmcp create`). Changed in 1.9 for `node dist/node/<name>.bundle.js`: it served `/`. |
| A `vercel`, `lambda`, `distributed` or `cloudflare` build | `http.entryPath`, else `transport.http.path` (changed in 1.8.7 for the first three: they served `/`) |
| A server started without the `frontmcp` command, like `tsx src/main.ts` | `/` of its address, unless `http.entryPath` is set: see [the endpoint path](https://frontmcp.dev/reference/deployment/production-build#the-endpoint-path) |

What a client sends and gets:

- **Clients on MCP 2026-07-28** send a `POST` for every call, with the method and name repeated in headers, and keep no session. See [request headers](https://frontmcp.dev/reference/sdk/create-fetch-handler#request-headers).
- **Older clients** start with `initialize`, get an `Mcp-Session-Id`, and send it with every request. A server that runs as several instances needs [Redis](https://frontmcp.dev/reference/deployment/redis) for them.
- **With `auth`**, a request without credentials gets `401` and a `WWW-Authenticate` header. For `static` auth, give the client the key as a header, `Authorization: Bearer <key>`; for `local`, `remote` and `transparent`, a client that supports MCP's OAuth follows the header to the sign-in: see [the flow a client follows](https://frontmcp.dev/reference/auth/local#the-flow-a-client-follows).
- **Clients that run in a web page** need [CORS](https://frontmcp.dev/reference/sdk/create-fetch-handler#letting-web-pages-connect).

### Over stdio

The client starts the command with its arguments and environment, writes JSON-RPC messages to its stdin, one per line, and reads answers from its stdout. So stdout must carry nothing else.

| Command | Serves stdio |
| --- | --- |
| `dist/node/<name> --stdio`, the `node` build's start script | Yes. It sets `FRONTMCP_STDIO=1` and runs the bundle. |
| `node dist/node/<name>.bundle.js --stdio`, or `FRONTMCP_STDIO=1 node dist/node/<name>.bundle.js` | Yes. (Changed in 1.9: before, the bundle ignored `--stdio`, started the HTTP server and wrote its log to stdout, which the client couldn't parse.) |
| `dist/cli/<name> --stdio`, or `dist/cli/<name> serve --stdio`, from `--target cli` | Yes. |
| `npx -y <package> --stdio`, for a server [published to npm](#publishing-a-server-to-npm) | Yes. Without `--stdio`, the program prints its help and exits. |
| An entry file that calls [`FrontMcpInstance.runStdio()`](https://frontmcp.dev/reference/sdk/frontmcp-instance#serving-over-stdio) | Yes. |

In stdio mode no port is opened, so several copies can run at once. Logs go to stderr, and to a file in `~/.frontmcp/logs/` ([where the lines go](https://frontmcp.dev/reference/server/logging#where-the-lines-go)); anything your own code prints with `console.log` before the server starts still reaches stdout. Configuration comes from the environment the client gives the process, through its `env` field: the same variables as any deployment, like secrets and `NODE_ENV`. Each client starts its own process, so nothing is shared between clients, and the process ends when the client closes it.

### Over a Unix socket

A server on a socket ([Serving on a Unix socket](https://frontmcp.dev/reference/deployment/node#serving-on-a-unix-socket)) speaks the same HTTP as on a port, to anyone who can open the file. A client needs to support sockets to use it; `curl --unix-socket` can, for testing:

```bash
curl --unix-socket ./help-desk.sock http://localhost/healthz
```

The host name in the URL doesn't matter on a socket; the path does.

### `frontmcp eject-mcp-config <client>`

Prints the `mcpServers` entry for a client, built from `clients.<client>` in [`frontmcp.config`](https://frontmcp.dev/reference/server/config-files#fields). `frontmcp eject-mcp-config --help` lists the clients it knows; `-o <file>` writes the snippet to a file instead. It creates the file's missing folders, and when the file already holds a client config it merges the entry into it and keeps the rest (`Wrote … (merged into existing config)`); a file that isn't valid JSON is refused and left alone (`Existing client config is not valid JSON, refusing to overwrite it`). Changed in 1.8.7: `-o` didn't create folders and overwrote an existing file.

| `clients.<client>` | Prints |
| --- | --- |
| `{ transport: "http", url? }` | `{ "url": …, "transport": "http" }`. Without `url`: `http://<transport.http.host, or 127.0.0.1>:<transport.http.port><transport.http.path, or /mcp>`. |
| `{ transport: "stdio", command?, args?, env? }` | `{ "command": …, "args": […], "env": {…} }`, with `npx -y <name>` when `command` and `args` are missing. `env` is `env.shared` and `env.ship` from `frontmcp.config`, then the client's own `env`, which wins (since 1.9). |

The entry's key is `clients.<client>.name`, else the config's `name`. With `env: { shared: { DESK_REGION: "eu-west" }, ship: { NODE_ENV: "production" } }` and a client entry with `env: { DESK_REGION: "us-east" }`, the snippet's `env` was `{ "DESK_REGION": "us-east", "NODE_ENV": "production" }`. A client missing from `clients` fails with `frontmcp.config has no clients.<client> entry`. Projects from `frontmcp create` have one HTTP client entry.

### MCP Bundles

`frontmcp build --target mcpb` writes `dist/mcpb/<name>-<version>.mcpb`, a ZIP archive for desktop clients that install MCP Bundles: a `manifest.json` (MCPB 0.3) with the name, version, the tools found in your server, and `server.mcp_config: { command: "node", args: ["${__dirname}/server/index.js"], env: { FRONTMCP_STDIO: "1" } }`, plus `server/index.js`, your code with FrontMCP's packages bundled into it (about 10 MB, so a small server's archive is about 2 MB). The archive needs no `node_modules`: extracted into an empty folder, `FRONTMCP_STDIO=1 node server/index.js` answered `initialize` and a `tools/call` on stdin, wrote nothing else to stdout, and logged to stderr. `frontmcp mcpb validate <file>` checks an archive, and now fails one whose `server/index.js` still `require()`s `@frontmcp/sdk` or `reflect-metadata` ([`validate`](#installing-from-an-mcp-bundle) below). The build is deterministic, and prints the archive's `sha256`.

The archive has no widget files: `frontmcp build --target mcpb` doesn't copy them, though `node`, `cli` and the serverless targets do, so a tool whose widget is a `file` has no file to read in the extracted archive.

> **Note**
In 1.8.6 `server/index.js` was the `node` target's bundle: your code only, importing `@frontmcp/sdk` and `reflect-metadata`, which the archive doesn't contain, so it failed with `Cannot find module 'reflect-metadata'`. With them it started FrontMCP's HTTP server and wrote its log to stdout, because `mcp_config` didn't set `FRONTMCP_STDIO`, so a client that talked to it over stdio couldn't use it, and `validate` still reported `archive is valid`.

---

## Usage

The Playground is itself an MCP client, but it can't show another client's settings, so this section uses code blocks. Each server command was run with FrontMCP 1.9.2, with a small script playing the client; no MCP desktop or editor client was used.

### Connecting to `frontmcp dev`

```bash
npx frontmcp dev
```

```text
[dev] listening on port: 3000
[dev] MCP endpoint path: /mcp
```

```json
{ "mcpServers": { "help-desk": { "url": "http://localhost:3000/mcp" } } }
```

`frontmcp dev` restarts the server when you change a file. Built for production, the same server answers at `/mcp` too, started with the `node` build's script or with `node dist/node/<name>.bundle.js`.

### Connecting to a deployed server with a key

For a server with `auth: { mode: "static", tokens: [process.env.DESK_API_KEY] }`, the client sends the key with every request:

```json
{
  "mcpServers": {
    "help-desk": {
      "url": "https://desk.example.com/mcp",
      "headers": { "Authorization": "Bearer ${DESK_API_KEY}" }
    }
  }
}
```

Whether a client supports `headers`, and how it fills in `${…}`, is up to the client. Without the header, the server answers `401` with `WWW-Authenticate: Bearer realm="mcp"`.

### Starting the server over stdio

Build with the `node` target, and point the client at the start script:

```json
{
  "mcpServers": {
    "help-desk": {
      "command": "/opt/help-desk/dist/node/help-desk",
      "args": ["--stdio"],
      "env": { "DESK_API_URL": "https://api.desk.example.com" }
    }
  }
}
```

A client that sends `initialize` and then `tools/call` on stdin gets two JSON lines back on stdout, and nothing else; the log goes to stderr. `node dist/node/help-desk.bundle.js --stdio` does the same. The start script needs the project's `node_modules` next to `dist/`, as any `node` build does. For a server your users install from npm, the `cli` target makes a program they can run with `--stdio`:

```bash
npx frontmcp build --target cli --js
./dist/cli/help-desk --stdio
```

[Publishing a server to npm](#publishing-a-server-to-npm) turns that program into a package.

### Publishing a server to npm

Most stdio servers reach their users as npm packages that the client starts with `npx`. A FrontMCP server does too, as the `cli` target's JavaScript bundle. Build it:

```bash
npx frontmcp build --target cli --js
```

`dist/cli/` then holds the program, `help-desk-cli.bundle.js`, which starts with `#!/usr/bin/env node`; your server, `help-desk.bundle.js`, which the program loads from its own folder; and `bin-meta.json` and `_skills/`, which the program's [`install` command](https://frontmcp.dev/reference/cli#installing-into-claude-code-and-codex) reads. Ship the whole folder. Both bundles `require()` `@frontmcp/sdk` and `reflect-metadata` when they run, so those two are the package's `dependencies`; the CLI, TypeScript and the test packages are only for building, so they go in `devDependencies`:

```json package.json
{
  "name": "help-desk-mcp",
  "version": "0.1.1",
  "description": "Search and read help desk tickets from an MCP client",
  "bin": { "help-desk": "dist/cli/help-desk-cli.bundle.js" },
  "files": ["dist/cli"],
  "scripts": {
    "build": "frontmcp build --target cli --js",
    "prepublishOnly": "npm run build"
  },
  "dependencies": {
    "@frontmcp/sdk": "1.9.4",
    "reflect-metadata": "^0.2.2"
  },
  "devDependencies": {
    "frontmcp": "1.9.4",
    "typescript": "^5.5.3"
  }
}
```

- **`private`:** a project from `frontmcp create` has `"private": true`, and `npm publish` refuses it with `EPRIVATE` until you remove it.
- **`bin`:** `npx -y help-desk-mcp` runs the package's only command, whatever its name. Name it after `name` in `frontmcp.config` anyway: the program's `install -p claude` writes an entry that runs `help-desk serve --stdio`, which needs a `help-desk` command on the `PATH`.
- **`files`:** a package without `help-desk.bundle.js` fails at start with `Cannot find module '…/help-desk.bundle.js'`, and one without `bin-meta.json` can't run `install -p`.
- **`version`:** without `version` in `frontmcp.config`, the build and `--version` take the package's.

Try the package before you publish it, from its tarball:

```bash
npm pack
npx -y ./help-desk-mcp-0.1.1.tgz --version     # 0.1.1
npm publish
```

The client's entry starts it with `npx`, and gives it its settings in `env`, which the tools read as `process.env.DESK_API_URL`:

```json
{
  "mcpServers": {
    "help-desk": {
      "command": "npx",
      "args": ["-y", "help-desk-mcp", "--stdio"],
      "env": { "DESK_API_URL": "https://api.desk.example.com" }
    }
  }
}
```

Keep `--stdio` in `args`: without it the program prints its help and exits. The first start downloads the package and its dependencies, 127 packages for this server, and later starts use npx's cache. [`frontmcp eject-mcp-config`](#frontmcp-eject-mcp-config-client) prints this entry when `clients.<client>` is `{ transport: "stdio", command: "npx", args: ["-y", "help-desk-mcp", "--stdio"], env }`; its default, `npx -y <name>`, uses `frontmcp.config`'s `name`, which isn't the package's here, and has no `--stdio`.

People who install the package for good, with `npm install -g help-desk-mcp`, get a `help-desk` command, and `help-desk install -p claude` writes a Claude Code plugin for it, with the server's skills: see [Installing into Claude Code and Codex](https://frontmcp.dev/reference/cli#installing-into-claude-code-and-codex).

Every step here was run with FrontMCP 1.9.4 against a local npm registry (Verdaccio), not the public one: `npx -y help-desk-mcp --stdio` answered `initialize`, `tools/list` and `tools/call` on stdout, with `DESK_API_URL` from `env` reaching the tool, and its log on stderr.

### Trying a server by hand

An MCP 2026-07-28 call is one `POST`, so `curl` can check a URL before you give it to a client:

```bash
curl -s https://desk.example.com/mcp \
  -H 'content-type: application/json' \
  -H 'mcp-protocol-version: 2026-07-28' \
  -H 'mcp-method: tools/call' \
  -H 'mcp-name: get_ticket' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_ticket","arguments":{"id":"T-1"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}'
```

```json
{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"{\"id\":\"T-1\",\"title\":\"Cannot log in\",\"status\":\"open\"}"}],"structuredContent":{"id":"T-1","title":"Cannot log in","status":"open"},"resultType":"complete","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"help-desk","version":"1.0.0"}}}}
```

`/healthz` answers even where the MCP endpoint needs a token, so check it first to tell a network problem from an auth one.

### Printing a client's snippet

With the `clients` entry `frontmcp create` wrote, `{ transport: "http", url: "http://127.0.0.1:3000/mcp" }`:

```bash
npx frontmcp eject-mcp-config <client>
```

```json
{
  "mcpServers": {
    "help-desk": {
      "url": "http://127.0.0.1:3000/mcp",
      "transport": "http"
    }
  }
}
```

### Installing from an MCP Bundle

```bash
npx frontmcp build --target mcpb
npx frontmcp mcpb validate dist/mcpb/help-desk-0.1.0.mcpb
```

```text
[build:mcpb] extracted: 5 tools, 0 resources, 0 prompts
[build:mcpb] server bundle (runtime inlined): 11.0 MB
[build:mcpb] wrote manifest.json
[build:mcpb] dist/mcpb/help-desk-0.1.0.mcpb (2.1 MB)
[mcpb:validate] archive is valid
```

Without `version` in `frontmcp.config`, the archive takes the one in `package.json`. An archive whose `server/index.js` needs packages it doesn't contain fails the check, with a line per package, and the command exits with `1`:

```text
[mcpb:validate] server/index.js requires "@frontmcp/sdk" but the archive has no node_modules — the server cannot start. Rebuild with `frontmcp build --target mcpb` so runtime packages are bundled
```

`--sea` adds a single-executable binary, which hosts of an operating system run only when the archive has every architecture of it, added with `--merge-from`: see [the `mcpb` target](https://frontmcp.dev/reference/cli#the-mcpb-target). The name, icon, links and settings a host shows come from the `mcpb` deployment in `frontmcp.config`, or from `package.json`: see [Bundle metadata and user settings](https://frontmcp.dev/reference/cli#bundle-metadata-and-user-settings), which also covers `--icon` and `--stage-only`.

---

## Troubleshooting

### The client gets `404` at `/mcp`

The server's endpoint is `/`: it was started without the `frontmcp` command, which is what passes `transport.http.path` on, or it's a `node` build of 1.8.7 or earlier started with `node dist/node/<name>.bundle.js`. Set `http.entryPath` in `@FrontMcp`, or connect to the root. See [the endpoint path](https://frontmcp.dev/reference/deployment/production-build#the-endpoint-path).

### A stdio client says it can't parse the server's output

The command starts the HTTP server instead of stdio, and its log reaches stdout: typically `node dist/node/<name>.bundle.js --stdio` with a bundle built by FrontMCP 1.8.7 or earlier. Build again, use the start script with `--stdio`, or set `FRONTMCP_STDIO=1`. Also check that none of your code prints to stdout before the server starts.

### A server started with `npx` exits at once

The client's `args` have no `--stdio`, so the program prints its help and exits instead of serving. Use `["-y", "<package>", "--stdio"]`. An entry from `eject-mcp-config` without `command` and `args` is `npx -y <name>` with the config's `name`, which may be another package on npm, and has no `--stdio`: give the client `command` and `args` in `frontmcp.config` ([Publishing a server to npm](#publishing-a-server-to-npm)).

### `Cannot find module '…/help-desk.bundle.js'`

The published program can't find your server, which it loads from its own folder: the package has `help-desk-cli.bundle.js` without `help-desk.bundle.js`. Publish the whole of `dist/cli/`, with `"files": ["dist/cli"]`. `Cannot find module 'reflect-metadata'` means the package's `dependencies` leave out `reflect-metadata` and `@frontmcp/sdk`.

### `Error: unsupported flag '--port' on the server runner.`

The `node` build's start script only takes `--stdio`, `--help`, `--version` and `--print-manifest`, and exits with code 2 on any other flag. Set the port with `PORT`. The `cli` target's `serve` command does take `--port`.

### `frontmcp.config has no clients.<client> entry`

`eject-mcp-config` only prints what `frontmcp.config` describes. Add the client under `clients`, with its `transport`.

### `Cannot find module 'reflect-metadata'` from an MCP Bundle

The archive was built by FrontMCP 1.8.6 or earlier, and doesn't contain FrontMCP's packages. Rebuild with 1.9.3. See [MCP Bundles](#mcp-bundles).

### `Header mismatch: the mcp-protocol-version header is required`

The request has a 2026-07-28 body without the headers that go with it; clients built for 2026-07-28 send them. See [request headers](https://frontmcp.dev/reference/sdk/create-fetch-handler#request-headers).

### `401 Unauthorized` from a client that can't sign in

The server uses an auth mode that needs OAuth, and the client doesn't support it, or has no key for `static` auth. Give the client a key in its `headers`, or use a client that supports MCP's OAuth. See [Auth modes](https://frontmcp.dev/reference/auth/modes).
