> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oktolabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Server configuration

> Tune the Pulse runtime: API and MCP ports, bind hosts, CORS, logging, and uvicorn workers. Single Python process, two listeners, defaults that work locally and in containers.

# Server configuration

`okto-pulse serve` is one Python process running two uvicorn listeners — the API + Web UI on `8100` and the MCP server on `8101` ([`80-pulse-feature-inventory.md:213–215`](https://github.com/OktoLabsAI/okto-pulse-core)). Configuration is read from environment variables and an optional `.env` file via `pydantic-settings`. Every field on `CoreSettings` accepts an env-var override using its uppercase field name ([`80-pulse-feature-inventory.md:178–180`](https://github.com/OktoLabsAI/okto-pulse-core)).

This page covers the runtime knobs you actually need to touch: ports, bind hosts, CORS, logging, and worker tuning. For the full env-var reference, see [Environment variables](/configuration/environment-variables). For where Pulse stores data, see [Storage paths](/configuration/storage-paths).

***

## Ports

Two listeners, two defaults.

| Listener     | Default port | Source                                                                                             |
| ------------ | ------------ | -------------------------------------------------------------------------------------------------- |
| API + Web UI | `8100`       | `DEFAULT_API_PORT` in `cli.py:20` ([inventory:213](https://github.com/OktoLabsAI/okto-pulse-core)) |
| MCP server   | `8101`       | `DEFAULT_MCP_PORT` in `cli.py:21` ([inventory:214](https://github.com/OktoLabsAI/okto-pulse-core)) |

### Override on the command line

```bash theme={null}
okto-pulse serve --api-port 9100 --mcp-port 9101 --accept-terms
```

`--api-port` writes `OKTO_PULSE_PORT` and `--mcp-port` writes `OKTO_PULSE_MCP_PORT` into the process environment **before** importing `okto_pulse.community.main` — that module evaluates `app = create_community_app()` at import time and bakes the port values into `/config.js` for the Web UI ([inventory:53–60](https://github.com/OktoLabsAI/okto-pulse-core)). Setting them later, after the import, is too late.

### Override via environment

```bash theme={null}
export OKTO_PULSE_PORT=9100
export OKTO_PULSE_MCP_PORT=9101
okto-pulse serve --accept-terms
```

```text theme={null}
Okto Pulse listening on:
  API + UI  http://127.0.0.1:9100
  MCP       http://127.0.0.1:9101/mcp
```

<Note>
  Both ports must be free on the bind host before `serve` starts. Run `okto-pulse status` to confirm the current binding ([inventory:63–80](https://github.com/OktoLabsAI/okto-pulse-core)).
</Note>

***

## Bind addresses

Pulse separates the two listeners' bind hosts on purpose: the API (port 8100) defaults to loopback because it serves the Web UI and admin endpoints, while the MCP server (port 8101) needs to be reachable from the agent.

| Field    | Default                                  | Env var    | Notes                                                                                                                                                              |
| -------- | ---------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `host`   | `127.0.0.1`                              | `HOST`     | API + Web UI bind. Loopback only by design ([`CoreSettings.host`](https://github.com/OktoLabsAI/okto-pulse-core), inventory:761)                                   |
| MCP host | `0.0.0.0` (Docker) / `127.0.0.1` (local) | `MCP_HOST` | Read at runtime in `community/main.py` (since v0.1.12) and in `core/mcp/server.py`. Docker images sed-patch the binding line so `MCP_HOST` takes effect there too. |

<Tabs>
  <Tab title="Local (default)">
    Both listeners bind `127.0.0.1`. Reachable only from the same machine. This is the safe default — your agent connects via `http://localhost:8101/mcp?api_key=...`.

    ```bash theme={null}
    okto-pulse serve --accept-terms
    ```

    ```text theme={null}
    INFO  uvicorn  Started server process [12345]
    INFO  uvicorn  Uvicorn running on http://127.0.0.1:8100
    INFO  okto_pulse.mcp  MCP listening on http://127.0.0.1:8101/mcp
    ```
  </Tab>

  <Tab title="Docker / container">
    Inside a container, `127.0.0.1` is unreachable from the host even with `-p 8101:8101`. You must bind `0.0.0.0` inside the container. The published port (`-p 127.0.0.1:8101:8101`) keeps the surface limited to your host machine.

    ```bash theme={null}
    docker run -d \
      -p 127.0.0.1:8100:8100 \
      -p 127.0.0.1:8101:8101 \
      -e HOST=0.0.0.0 \
      -e MCP_HOST=0.0.0.0 \
      -e DATA_DIR=/data \
      -e KG_BASE_DIR=/data \
      -v okto-pulse-data:/data \
      ghcr.io/oktolabsai/okto-pulse:latest
    ```

    See [Docker install](/install/docker) for the full container recipe.
  </Tab>

  <Tab title="LAN-exposed (advanced)">
    Exposing Pulse to your LAN means anyone on the network with the API key can reach the MCP server. Only do this for a trusted network and behind a firewall.

    ```bash theme={null}
    HOST=0.0.0.0 MCP_HOST=0.0.0.0 okto-pulse serve --accept-terms
    ```
  </Tab>
</Tabs>

<Warning>
  Never bind `0.0.0.0` on a public network without a firewall. The MCP API key is the only thing between an attacker and full board control. Use `127.0.0.1` for local work, a private network plus firewall for shared workstations, and a reverse proxy with TLS termination (Caddy, nginx, Traefik) if you must expose Pulse beyond the host.
</Warning>

***

## CORS

The API enforces CORS on the Web UI and any third-party browser client.

| Field          | Default (core)                                | Default (community) | Env var        |
| -------------- | --------------------------------------------- | ------------------- | -------------- |
| `cors_origins` | `http://localhost:5173,http://localhost:3000` | `*`                 | `CORS_ORIGINS` |

The community edition overrides the core default to `*` so the bundled Web UI works regardless of how you reach it ([inventory:765](https://github.com/OktoLabsAI/okto-pulse-core), inventory:191). Override per environment by setting `CORS_ORIGINS` to a comma-separated list:

```bash theme={null}
CORS_ORIGINS="http://localhost:5173,https://pulse.internal.example.com" \
  okto-pulse serve --accept-terms
```

```text theme={null}
INFO  okto_pulse.api  CORS allow_origins=['http://localhost:5173', 'https://pulse.internal.example.com']
```

<Note>
  CORS is enforced by the API, not the MCP server. MCP requests are server-to-server and not subject to CORS — the API key is the access control there.
</Note>

***

## Logging

Pulse uses Python's standard `logging` module. Logs stream to stdout/stderr by default.

### Log level

Set via `DEBUG=true` or by configuring logging directly with standard Python tooling. There is no dedicated `LOG_LEVEL` env var; `DEBUG` toggles `INFO` → `DEBUG` for `okto_pulse.*` loggers.

| Env var       | Default       | Effect                                                                                                                                                                                                   |
| ------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DEBUG`       | `false`       | When `true`, raises `okto_pulse.*` loggers from `INFO` to `DEBUG` and surfaces SQLAlchemy echo on the API. Source: `CoreSettings.debug` ([inventory:753](https://github.com/OktoLabsAI/okto-pulse-core)) |
| `ENVIRONMENT` | `development` | Free-form label, used in startup banners and exposed at `GET /health`. Source: `CoreSettings.environment` ([inventory:754](https://github.com/OktoLabsAI/okto-pulse-core))                               |

```bash theme={null}
DEBUG=true okto-pulse serve --accept-terms
```

```text theme={null}
DEBUG  okto_pulse.api  request POST /api/v1/cards
DEBUG  sqlalchemy.engine  BEGIN (implicit)
DEBUG  sqlalchemy.engine  SELECT cards.id, cards.title FROM cards WHERE ...
INFO   okto_pulse.mcp  tool call okto_pulse_create_card → ok (32ms)
```

### Log destinations

By default, logs go to:

* **stdout** for `INFO` and below
* **stderr** for `WARNING`, `ERROR`, `CRITICAL`
* **No file logging** — the process does not write a log file. Capture stdout/stderr if you need persistence.

For a containerized deployment, let the container runtime capture the streams:

```bash theme={null}
docker logs -f okto-pulse > pulse.log 2>&1
```

For a `systemd` unit, journald captures both streams automatically:

```bash theme={null}
journalctl -u okto-pulse.service -f
```

### MCP call tracing

Independent of the standard logger, Pulse can record every MCP tool call to a JSONL trace.

| Env var             | Default                     | Purpose                                                                                                                                             |
| ------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MCP_TRACE_ENABLED` | unset                       | Set to `1` (or `true`/`yes`) to enable tracing. Source: [`core/mcp/trace_middleware.py:49`](https://github.com/OktoLabsAI/okto-pulse-core)          |
| `MCP_TRACE_DIR`     | `${KG_BASE_DIR}/mcp_traces` | Output directory; one `session_*.jsonl` per MCP session. Source: [`core/mcp/trace_middleware.py:13`](https://github.com/OktoLabsAI/okto-pulse-core) |

```bash theme={null}
MCP_TRACE_ENABLED=1 okto-pulse serve --accept-terms
```

```text theme={null}
$ ls ~/.okto-pulse/mcp_traces/
session_2026-05-07T14-12-03_a3f9.jsonl
$ head -1 ~/.okto-pulse/mcp_traces/session_2026-05-07T14-12-03_a3f9.jsonl
{"ts":"2026-05-07T14:12:04Z","tool":"okto_pulse_get_active_board","args":{},"result_summary":"ok","ms":8}
```

Use traces to debug tool selection or to replay sessions against the MCP test harness. See [Troubleshooting](/concepts/sdlc-flow#mcp-tracing) for replay tooling.

***

## Worker tuning (uvicorn)

Pulse runs with a single uvicorn worker, by default and by design. `okto-pulse serve` exposes `--api-port`, `--mcp-port`, and `--accept-terms` only — there is no `--workers` flag ([`okto-pulse/src/okto_pulse/community/cli.py:223–276`](https://github.com/OktoLabsAI/okto-pulse) / [inventory:53–60](https://github.com/OktoLabsAI/okto-pulse-core)). The KG consolidation pipeline, the queue scheduler, and the embedded LadybugDB graph all assume one writer per process; spawning multiple uvicorn workers would create concurrent writers, lock contention, and split queue state.

<Warning>
  **Do not bypass the CLI to run Pulse with multiple uvicorn workers.** The single-worker model is required for KG correctness — concurrent writers will hit the LadybugDB advisory lock and dead-letter queue entries. Scale by running multiple Pulse instances on different ports against different data directories instead — one Pulse process per board cluster.
</Warning>

What you **can** tune:

| Concurrency surface                                                | Setting                           | Default | Range     |
| ------------------------------------------------------------------ | --------------------------------- | ------- | --------- |
| KG consolidation workers (asyncio tasks inside the single process) | `KG_QUEUE_MAX_CONCURRENT_WORKERS` | `4`     | 1–16      |
| KG queue poll interval                                             | `KG_QUEUE_MIN_INTERVAL_MS`        | `100`   | 0–1000 ms |
| LadybugDB connection pool                                          | `KG_CONNECTION_POOL_SIZE`         | `8`     | 1–32      |
| LadybugDB buffer pool (memory)                                     | `KG_KUZU_BUFFER_POOL_MB`          | `256`   | 16–512 MB |

Sources: `CoreSettings` fields `kg_queue_max_concurrent_workers`, `kg_queue_min_interval_ms`, `kg_connection_pool_size`, `kg_kuzu_buffer_pool_mb` ([inventory:780–784](https://github.com/OktoLabsAI/okto-pulse-core)).

<Note>
  The `KG_KUZU_*` and `kg_kuzu_*` field names are legacy — Pulse uses LadybugDB (the Kùzu-derived engine) under the hood since 2026-05-03. The variable names are kept stable for backward compatibility; the underlying engine is LadybugDB.
</Note>

`KG_QUEUE_*` and `KG_DECAY_TICK_*` fields are **hot-reloadable**. APScheduler re-reads them with a 5-second debounce and `kg_decay_tick_*` can be updated through `PUT /settings/runtime` without a restart ([inventory:790–793](https://github.com/OktoLabsAI/okto-pulse-core)).

***

## Reverse proxy notes

If you front Pulse with Caddy, nginx, or Traefik:

* Bind Pulse to `127.0.0.1` and let the proxy terminate TLS and forward.
* Set `MCP_HOST=127.0.0.1` to keep the MCP listener loopback-only behind the proxy.
* Forward `Host`, `X-Forwarded-For`, and `X-Forwarded-Proto` so the API renders absolute URLs correctly.
* WebSockets are not used by the API or MCP — plain HTTP/1.1 is sufficient. Streamable HTTP responses (MCP SSE) require the proxy to disable response buffering on `/mcp`.

```caddyfile theme={null}
pulse.internal.example.com {
    @mcp path /mcp*
    handle @mcp {
        reverse_proxy 127.0.0.1:8101 {
            flush_interval -1
        }
    }
    handle {
        reverse_proxy 127.0.0.1:8100
    }
}
```

***

## Verifying the running config

`okto-pulse status` reads the SQLite metadata and probes the configured ports:

```bash theme={null}
okto-pulse status
```

```text theme={null}
Okto Pulse status
─────────────────
Data dir:     /Users/you/.okto-pulse
Database:     /Users/you/.okto-pulse/data/pulse.db (412 KB)
Boards:       1   Cards: 23   Specs: 4   Agents: 2
API server:   running on 127.0.0.1:8100
MCP server:   running on 127.0.0.1:8101
```

Source: `cli.py:567–614` (`cmd_status`) ([inventory:67–78](https://github.com/OktoLabsAI/okto-pulse-core)).

The 216 MCP tools exposed by the running process are listed in the [MCP reference](/reference/mcp).

***

## Related

<CardGroup cols={2}>
  <Card title="Storage paths" icon="folder" href="/configuration/storage-paths">
    Where Pulse keeps its database, attachments, and per-board knowledge graphs.
  </Card>

  <Card title="Environment variables" icon="square-terminal" href="/configuration/environment-variables">
    Full reference of every `CoreSettings` field and its env-var name.
  </Card>

  <Card title="Docker install" icon="docker" href="/install/docker">
    Run Pulse in a container with the right `HOST` and `MCP_HOST` defaults.
  </Card>

  <Card title="MCP setup" icon="plug" href="/mcp-setup">
    Connect Claude Code, Cursor, Cline, Windsurf, or Codex to your board.
  </Card>
</CardGroup>
