MCPDBWizard

Documentation  ·  Getting started

Launching on Docker

This page is an outline. What is here is accurate, but it is not yet the whole story — each section ends with a note on what is still to be written. For anything it does not answer, DEPLOYMENT.md and USING-MCP.md in the repository are the complete references.

The container holds the web console and every MCP server it generates. One image, one port to publish, one volume to keep.

The minimum

This is the whole command. The Oracle settings are not optional extras to add later — without them the console starts, serves the Users and Runtime pages, and refuses every Design page, because there is no connect form and nowhere else for them to come from.

docker run -d --name mcpdbwizard \
  -p 8080:8080 \
  -v mcpdbwizard-data:/data \
  -e MCPDBWIZARD_ORACLE_HOST=db.example.com \
  -e MCPDBWIZARD_ORACLE_PORT=1521 \
  -e MCPDBWIZARD_ORACLE_SID=/FREEPDB1 \
  -e MCPDBWIZARD_ORACLE_USER=appuser \
  -e DB_PASS=secret \
  ghcr.io/srmadscience/mcpdbwizard:2.0.32

Then open http://localhost:8080. There is no default password — the container generates one for this installation and writes it to initial-admin-password in the config directory, and the sign-in page names that exact path. Read it, sign in as admin, and choose your own; the file is deleted the moment you do.

docker exec mcpdbwizard cat /data/initial-admin-password

Set ADMIN_INITIAL_PASSWORD if you would rather choose it yourself — you are still made to change it at first sign-in.

MCPDBWIZARD_ORACLE_PORT may be omitted — it defaults to 1521. The other four are required, and the container warns about each one it is missing on its own line of docker logs at start-up.

A leading / on the SID selects the service-name form, which is what a pluggable database like FREEPDB1 needs. Without it you get the older SID form and a connection failure that looks like a wrong hostname. See Connecting to Oracle.

Passing values already in your shell

-e NAME with no =value copies the variable through from the environment running docker, which keeps the secret off the command line and out of your shell history:

export DB_PASS=secret
docker run -d --name mcpdbwizard \
  -p 8080:8080 -v mcpdbwizard-data:/data \
  -e MCPDBWIZARD_ORACLE_HOST -e MCPDBWIZARD_ORACLE_PORT \
  -e MCPDBWIZARD_ORACLE_SID -e MCPDBWIZARD_ORACLE_USER -e DB_PASS \
  ghcr.io/srmadscience/mcpdbwizard:2.0.32

Better still, use DB_PASS_FILE and a mounted secret — see Connecting to Oracle.

:latest does not refresh itself

docker run does not pull an image the machine already has. If a box pulled :latest once, the daemon keeps serving that copy under the same name and never contacts the registry again — so a docker run … :latest months later silently starts the old build. Nothing in the output says so; you get a container id either way.

That is not a bug in Docker, it is what run has always done, and it catches people precisely because :latest reads like a promise of freshness.

docker rm -f mcpdbwizard
docker pull ghcr.io/srmadscience/mcpdbwizard:latest    # the step that is easy to skip
docker run -d --name mcpdbwizard ... ghcr.io/srmadscience/mcpdbwizard:latest

docker run --pull always does the same thing in one command.

To see which build a machine actually has, compare the digest — the tag cannot tell you:

docker image inspect ghcr.io/srmadscience/mcpdbwizard:latest \
    --format '{{index .RepoDigests 0}}'
docker buildx imagetools inspect ghcr.io/srmadscience/mcpdbwizard:latest | head -2

The first is what you are running, the second is what the registry holds. Different digests mean a stale local copy.

From 2.0.8 there is an easier answer: the console shows its own version on the login screen and in the banner, so a browser settles it without a shell. Before 2.0.8 there is nothing on screen to ask, which is why the digest comparison is here.

Pinning the version tag avoids the whole question. :2.0.8 cannot go stale, because a version tag never moves — which is the real reason to pin one in anything that matters, over and above latest moving under you.

Checking what you pulled

The image carries a build record and a bill of materials, readable straight off the registry:

docker buildx imagetools inspect ghcr.io/srmadscience/mcpdbwizard:2.0.32 \
    --format '{{ json .SBOM }}'

That answers “what is in it” without pulling. It is not a signature and does not identify the publisher — see Verifying the image for the commands and, more importantly, for what none of it proves.

Publishing the metrics port

Two settings have to agree, and both default to off. Publishing -p 9464:9464 on its own gets you a refused connection, which is the commonest way this goes wrong.

WhereDefaultWithout it
PROMETHEUS_SERVER=YESthe config — Design → Service OptionsNO for a console-created configno metrics code is emitted at all
MCPDBWIZARD_RUNTIME_METRICS_PORT_RANGEthe containeremptyno server is given a port, so nothing binds
docker run -d --name mcpdbwizard \
  -p 8080:8080 -p 9464-9469:9464-9469 \
  -e MCPDBWIZARD_RUNTIME_METRICS_PORT_RANGE=9464-9469 \
  ... ghcr.io/srmadscience/mcpdbwizard:2.0.32

Before 2.0.10 there was a third: MCP_METRICS_HOST

Add -e MCP_METRICS_HOST=0.0.0.0 on 2.0.9 and earlier. Without it the scrape listener bound 127.0.0.1 inside the container, and a published port forwards to the container’s bridge address — so the port was published, the container was healthy, MCP_METRICS_PORT was in the child’s environment, and every scrape was refused with nothing in any log to say why.

From 2.0.10 the console sets the bind address for the servers it launches. Binding every interface inside a container is not an exposure: reaching it still takes the published port, which is your own deliberate act, and you have already opted in by setting the port range. Setting MCP_METRICS_HOST yourself still overrides it — and still logs the exposure warning, because then the network policy is yours to write.

A range, not one port: the console runs several servers at once and each needs its own. The first to start gets 9464.

PROMETHEUS_SERVER is a generation-time flag, so turning it on means regenerating that config — setting it changes nothing until you do. See Setting up metrics and Grafana.

Which one is biting:

docker exec mcpdbwizard sh -c 'ps -ef | grep [D]aoFactoryMcpServer' | grep -o MCP_METRICS_PORT
docker exec mcpdbwizard curl -sf -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9464/metrics

No MCP_METRICS_PORT in the child’s environment is the second row; reachable inside the container but not outside is the third; neither, on a config that publishes tools, is the first.

Why only 8080 is published

Generated servers bind 8090–8109 on loopback and are meant to be reached through the proxy on 8080, which is the only component that knows who is calling. Publishing them directly bypasses the accounts, the access grid and the per-caller limits.

If you do it anyway, the server refuses to start unless it was generated with bearer tokens or OAuth: exposure is the act that puts it on a network, while authentication is opt-in.

The /data volume

Configs, accounts, access grants, each runtime’s workspace and (if you enable it) the audit spool all live here. Keep it. Replacing the image is then routine; losing the volume is losing your curation.

Set a memory limit

Without a cgroup limit, the container’s memory metric is a percentage of the whole host — Docker does not hide the host’s RAM from a container. A limit creates real cgroup accounting, and the metric’s basis label flips from host to cgroup.

    deploy:
      resources:
        limits:
          memory: 4g

That limit is shared by the web app, the forked generator and one MCP server per running config, each of which is given its heap explicitly — JAVA_OPTS for the web app (default -Xmx1g), mcpdbwizard.runtime.java-options for each server (default -Xmx512m). Budget limit >= (web heap + ~300m) + servers * (server heap + ~200m), the trailing term being the non-heap footprint -Xmx does not cover; at the defaults 4 GB holds the web app and about four concurrent servers.

The image also sets JAVA_TOOL_OPTIONS, for an unrelated XML parser setting that Logback needs. Overriding that variable replaces it, so carry the existing value through if you add to it.

To write. A worked docker-compose.yml; Kubernetes notes; the published image name and tag policy, once there is a registry to name; upgrade and rollback steps.