Target: https://mcp.cometchat.com (subdomain to confirm with DevOps).
docker build -t cometchat-mcp:0.1.4 .
docker run --rm -p 3000:3000 \
-v $(pwd)/data:/app/data \
-e NODE_ENV=production \
-e ALLOWED_HOSTS=mcp.cometchat.com \
cometchat-mcp:0.1.4
The image bundles bundles/, skills/, and the compiled server. The SQLite
index is not bundled — mount it at /app/data or build it inside the
container before launch (see "Index build" below). The server still boots
without the index; /health will report degraded and the search tool will
return backend_unavailable per call until the index is in place.
The image ships without the SQLite index. Build the index against the latest cometchat/docs clone, then mount or COPY it into the container.
git clone --depth 1 https://github.com/cometchat/docs.git /tmp/cometchat-docs
DOCS_REPO=/tmp/cometchat-docs INDEX_PATH=./data/index.sqlite npm run build:index
The provided GitHub Actions workflow (.github/workflows/ci.yml) already does this and uploads the index as an artifact — wire that artifact to your deployment job.
- Daily cron rebuild of the index against
cometchat/docs@main. - Optional: GitHub webhook on
pushtocometchat/docstriggers a rebuild + roll.
- Public HTTPS endpoint terminated at the load balancer or CDN.
- Anthropic outbound traffic originates from
160.79.104.0/21. Allowlist explicitly if a WAF is in front. - No inbound auth — docs are public. Per Anthropic's
noneauth-type spec, accept any incoming connection on/mcp. - DNS rebinding protection is on by default. Set
ALLOWED_HOSTSto the production hostname(s) the server is reachable at (comma-separated, including ports if non-standard, e.g.mcp.cometchat.com,mcp.cometchat.com:443). SetDNS_REBINDING_PROTECTION=falseonly if you have an upstream that already validates theHostheader. - Browser-based tooling (MCP Inspector, in-tab clients) needs CORS. Set
ALLOWED_ORIGINSto the comma-separated origins you want to permit, e.g.ALLOWED_ORIGINS=https://inspector.modelcontextprotocol.io. With it unset, the server reflects the request'sOriginonly when DNS-rebinding protection allows it; for a tighter posture in production, always set this explicitly.
| Env var | Default | Notes |
|---|---|---|
ALLOWED_HOSTS |
<HOST>:<PORT>,localhost:<PORT>,127.0.0.1:<PORT> |
Comma-separated Host allowlist. |
ALLOWED_ORIGINS |
unset | Comma-separated CORS origin allowlist. |
DNS_REBINDING_PROTECTION |
true |
Set false to disable. |
NODE_ENV |
development |
production enables strict bundle loading (any malformed bundle aborts boot rather than being skipped). |
RATE_LIMIT_ENABLED |
true |
Per-IP rate limit on /mcp. Set false for dev or behind an upstream limiter. |
RATE_LIMIT_MAX |
120 |
Max requests per window per IP. |
RATE_LIMIT_WINDOW_MS |
60000 |
Window in ms. |
The limiter reads the client IP from X-Forwarded-For (first hop) when present, otherwise from the socket. In a multi-replica deploy each replica keeps its own counter — for global limits, terminate at the LB or front the replicas with a shared limiter.
GET /health→200 { status: "ok", indexReady: true, indexAgeSeconds: N, bundles: 10, sessions: N }when the SQLite index exists and at least one bundle loaded.GET /health→503 { status: "degraded", indexReady: false, indexAgeSeconds: null, ... }when the index is missing or the bundle store is empty. Use this for readiness gating: a container without an index should be marked not-ready so the load balancer doesn't route to it.indexAgeSecondsis the file mtime of the SQLite index. The daily-rebuild cadence above means a healthy production should always be under ~26 hours (~94000s). Alert above that.
The server is stateful per the MCP Streamable HTTP spec. Each client gets its own mcp-session-id allocated on initialize, kept alive across subsequent POST/GET/DELETE on /mcp. Sessions are in-memory and process-local, so:
- For multi-replica deploys, configure the load balancer with session affinity on the
mcp-session-idheader (or sticky cookies seeded from it). Otherwise round-robin will break sessions mid-handshake. - Rolling restarts terminate active sessions; the client (Claude) reconnects with a fresh
initialize.
Structured JSON via pino. Log per tool invocation:
tool(name)duration_msstatus(success/ error code)query_length/path_length/bundle(for the relevant tool)
Aggregate at the platform level (Datadog, CloudWatch, etc.) and watch for:
- p95 tool latency > 1.5 s.
backend_unavailablerate > 1% of calls.- Sustained
unknown_bundlecalls — signal that we're missing a popular scenario.
The image is stateless. Roll back by pointing the load balancer at the previous tag. The index is a build artifact; an old image plus the current index works as long as schema didn't change.
Once mcp.cometchat.com is accepted into Anthropic's directory:
- Update
mcp-server.mdxincometchat/docsto point users at the new connector. - Add an HTTP 301 from
cometchat.com/docs/mcp→mcp.cometchat.com/mcp(or simply leave both up for one release; do not register two CometChat listings in the directory).