Skip to content

Run your own server

This guide is for running mcopenfoam yourself from a checkout of its source; most people use the hosted server instead.

With docker, uv and the source in ~/mcopenfoam-src:

Terminal window
docker pull opencfd/openfoam-default:2406
claude mcp add mcopenfoam -- uv run --directory ~/mcopenfoam-src mcopenfoam

The server runs over stdio with an in-memory queue and the worker in the same process. Jobs live under ~/mcopenfoam/jobs/, and case_path can be any directory on your machine. On a machine with fewer physical cores than a case’s ranks, set MCOPENFOAM_MPI_ARGS='["--use-hwthread-cpus"]'.

On a shared machine the same code runs as three containers: the MCP server over HTTP, a worker that launches the job containers, and Postgres for the queue and the instrumentation.

  1. Put the source on the host and make the env file.

    Terminal window
    cd ~/mcopenfoam-src/deploy && cp .env.example .env
  2. Edit .env. The values that matter:

    Variable Set it to
    MCOPENFOAM_HOST the host’s name; it is the queue name and appears in every event
    BIND_ADDR 127.0.0.1 or a LAN address; never a public one
    JOBS_HOST_DIR a directory on a large volume: a fine sweep is about 18 GB
    CASES_HOST_DIR where staged cases live; the server mounts it read-write
    HOST_UID, HOST_GID, DOCKER_GID your user and the docker group, so files stay yours
    RANK_BUDGET the most ranks one job may take
    MPI_ARGS e.g. ["--bind-to","core","--map-by","ppr:10:socket"] on a two-socket, 20-core host
    POSTGRES_PASSWORD anything long
    EMBEDDINGS_URL, EMBEDDINGS_API_KEY optional; turns on similar failures
  3. Build the ParaView image and start the stack.

    Terminal window
    make paraview
    make up

    make up builds the server image and the Postgres image (Alpine Postgres 17 with pgvector), starts all three, and prints their logs. The worker’s first log line names the host, the jobs directory and its host path.

  4. Reach it from your client. The server has no authentication of its own, so it stays off the public network. Tunnel to it:

    Terminal window
    ssh -f -N -L 8372:<host-lan-address>:8372 <host>
    claude mcp add --transport http mcopenfoam-remote http://127.0.0.1:8372/mcp
  5. Check it. server_info on the remote server reports the host, the image and whether it is present, the rank budget, the jobs directory as the host sees it, free disk, cases_writable, and the instrumentation backend.

deploy/compose.public.yml adds a second server container on the same queue, worker and jobs, with OAuth, published through caddy-docker-proxy at https://$PUBLIC_DOMAIN/mcp. The private server keeps serving trusted clients over the tunnel.

  1. Create an OAuth application at your OIDC identity provider: a confidential client with the redirect URI https://<your-domain>/auth/callback and the scopes openid, profile, email and offline_access. Bind it to the group of people who may use the server.

  2. Write deploy/.env.oauth, which stays out of git:

    Terminal window
    MCOPENFOAM_OIDC_CONFIG_URL=https://<idp>/application/o/<slug>/.well-known/openid-configuration
    MCOPENFOAM_OIDC_CLIENT_ID=<client id>
    MCOPENFOAM_OIDC_CLIENT_SECRET=<client secret>
  3. Turn it on in .env with PUBLIC=1 and PUBLIC_DOMAIN=<your-domain>, then make up.

Caddy sends /mcp, the OAuth endpoints and the job-files route to the public server, and everything else on that hostname to whatever else serves it, such as these docs. The sign-in state lives in Postgres, so restarts keep clients signed in.

Terminal window
cd ~/mcopenfoam-src && git pull
make -C deploy build
make -C deploy restart

A restart is safe while a job runs: its step keeps running in its container and the new worker re-attaches. Surviving restarts explains how.

Unpinned ranks migrate between cores and share hyperthread siblings with whatever else runs on the host. On one 20-core, two-socket machine this cost about 10% per iteration. --bind-to core --map-by ppr:10:socket puts ten ranks on each socket, one per physical core.