{
  "markdown": "> WARNING! Our apologies, but some of the modifications we made to allow remote hosting of this MCP server have broken the stdio tools. We are working on a fix ASAP. Please check back here later.\n\n\n# QC Database MCP Server\n\nA small program that lets an AI assistant (like Claude) do your everyday\n**QC Database** work for you — set the project you're working on, upload records,\nread what the AI pulled off a document, and find and close out the items still\nneeded for a complete, traceable **turnover package**.\n\nIt runs **on your own computer**. Your AI assistant talks to it; it talks to\n[QCDatabase.AI](https://qcdatabase.ai) on your behalf, using your own login.\n\nBuilt on the official [Model Context Protocol](https://modelcontextprotocol.io)\nPython library and the QC Database MCP specification — published at\n[qcdatabase.ai/mcp_server_spec.md](https://qcdatabase.ai/mcp_server_spec.md) and\nkept in this repo as [`mcp_server_spec.md`](mcp_server_spec.md).\n\n---\n\n## What you can ask your assistant to do\n\nOnce it's connected, you can talk to it in plain language. For example:\n\n- *\"Connect to QC Database.\"* → signs you in (opens your browser once).\n- *\"Work on the Riverside Unit 2 project.\"* → pins that project for the session.\n- *\"Upload this MTR to the Mill Test Reports folder.\"*\n- *\"What's still missing for turnover on this project?\"*\n- *\"Show me the open reference requests assigned to me.\"*\n- *\"Find welds that failed X-ray near line 12.\"* → meaning-based (semantic) search\n  across your project data, ranked by relevance.\n- *\"How do I create a test package in QC Database?\"* → answers from the built-in\n  QC Database user manual, so you can learn the app without leaving your assistant.\n- *\"Attach this photo to that weld.\"*\n- *\"Mark this inspection complete.\"* (it will remind you that the sign-off is\n  recorded under your name — that's your call, not the AI's).\n\n---\n\n## Before you start\n\nYou need **Python 3.10 or newer** on your computer.\n\n- **Windows:** install from the Microsoft Store (search \"Python\") or from\n  [python.org/downloads](https://www.python.org/downloads/). During the\n  python.org install, tick **\"Add Python to PATH.\"**\n- **Mac:** `brew install python` or get it from\n  [python.org/downloads](https://www.python.org/downloads/).\n\nTo check it's installed, open a terminal (PowerShell on Windows, Terminal on Mac)\nand run:\n\n```\npython --version\n```\n\nYou should see `Python 3.10` or higher. (On some Macs the command is `python3`.)\n\n---\n\n## Install\n\nFrom a terminal, in this folder:\n\n```\npip install .\n```\n\nThat's it. This installs the server and a command called `qcdatabase-mcp`.\n\n> If `pip` isn't found, try `python -m pip install .` (or `python3 -m pip install .`).\n\n---\n\n## Install in Claude Desktop\n\nClaude Desktop launches this server for you and shows its tools in your chats.\nSet it up once:\n\n### 1. Install the Claude Desktop app\n\nIf you don't have it, download it from\n[claude.ai/download](https://claude.ai/download) and sign in.\n\n### 2. Open the config file\n\nIn Claude Desktop:\n\n1. Open **Settings**\n   (Windows: menu **☰ → File → Settings**; Mac: **Claude → Settings**).\n2. Go to the **Developer** tab.\n3. Click **Edit Config**.\n\nThis opens (and, if needed, creates) a file named `claude_desktop_config.json`.\nYou can also open it directly:\n\n- **Windows:** `%APPDATA%\\Claude\\claude_desktop_config.json`\n- **Mac:** `~/Library/Application Support/Claude/claude_desktop_config.json`\n\n### 3. Add the QC Database server\n\nPut this in the file. If the file already has other servers, just add the\n`\"qcdatabase\"` block inside the existing `\"mcpServers\"` section.\n\n**Windows:**\n\n```json\n{\n  \"mcpServers\": {\n    \"qcdatabase\": {\n      \"command\": \"python\",\n      \"args\": [\"-m\", \"qcdatabase_mcp\"]\n    }\n  }\n}\n```\n\n**Mac** (Python is usually `python3` there):\n\n```json\n{\n  \"mcpServers\": {\n    \"qcdatabase\": {\n      \"command\": \"python3\",\n      \"args\": [\"-m\", \"qcdatabase_mcp\"]\n    }\n  }\n}\n```\n\nSave the file.\n\n> **If Claude can't start it** (you see \"python not found\" or the server fails),\n> use the full path to Python instead of just `\"python\"`. Find it by running\n> `where python` (Windows) or `which python3` (Mac) in a terminal, then paste\n> that path as the `\"command\"`. For example on Windows:\n>\n> ```json\n> {\n>   \"mcpServers\": {\n>     \"qcdatabase\": {\n>       \"command\": \"C:\\\\Users\\\\you\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python312\\\\python.exe\",\n>       \"args\": [\"-m\", \"qcdatabase_mcp\"]\n>     }\n>   }\n> }\n> ```\n>\n> (Use double backslashes `\\\\` in Windows paths inside JSON.)\n\n### 4. Restart Claude Desktop\n\n**Fully quit and reopen** the app (on Windows, also close it from the system\ntray; on Mac, **Quit** with Cmd-Q — closing the window isn't enough). The new\nserver only loads on a fresh start.\n\n### 5. Confirm it loaded\n\nStart a new chat and look for the tools/connector indicator (a slider or\ntool icon near the message box). Click it and you should see **qcdatabase** with\nits tools listed. If you don't, see [Troubleshooting](#troubleshooting).\n\nThen ask Claude to **\"connect to QC Database\"** to sign in — see below.\n\n---\n\n## First time you use it\n\n1. **Sign in.** Ask the assistant to *connect to QC Database* (the `login`\n   tool). Your web browser opens; log in and **pick which company workspace**\n   you want to connect. That choice locks the connection to that one\n   organization. You only do this once — it's remembered.\n\n2. **Pick your project.** Ask it to *work on* a project by name, or list your\n   projects first. Almost every action happens \"inside\" a project, so the server\n   asks you to choose one before it will do project work. You can switch\n   projects any time.\n\n3. **Get to work.** Upload documents and drawings, check what's missing for\n   turnover, create and close reference requests, add notes and photos, and so\n   on.\n\n---\n\n## What it can do (tool groups)\n\n- **Session:** `login`, `logout`, `auth_status`, `whoami`\n- **Find your way around:** `list_tenants`, `list_projects`, `set_project`,\n  `get_active_project`, `list_project_members`, `list_lists`, `list_list_items`,\n  `list_map_item_schemas`, `get_map_item_schema`, `list_document_folders`,\n  `list_form_schemas`\n- **Controlled-vocabulary lists:** `create_list_item`, `update_list_item`,\n  `delete_list_item` (a soft delete — the entry stops appearing in list reads but\n  stays on record)\n- **Jobs & packages:** `list_jobs`, `create_job`, `list_packages`,\n  `create_package`, `list_line_specs`, `create_line_spec`\n- **Upload records:** `upload_document`, `upload_document_version`,\n  `download_document`, `upload_drawing`, `upload_large_format_drawing`,\n  `upload_drawing_to_package`, `upload_drawing_version`,\n  `upload_large_format_drawing_version`, `attach_photo`\n- **Drawings:** `list_drawings`, `get_drawing` (incl. pixel width/height — the\n  HTML5-canvas coordinate space for map items), `export_drawing`,\n  `export_large_format_drawing`\n- **Fillable PDF forms:** `list_fillable_templates`, `get_fillable_template`,\n  `download_fillable_template`, `submit_fillable_template`\n- **Structured data:** `get_document`, `set_document_extracted_data`,\n  `list_documents`, `list_map_items`, `get_map_item`, `create_map_item`\n  (place welds/flanges by pixel coordinates), `bulk_create_map_items`,\n  `bulk_update_map_items` (batch up to 500 items on one drawing/schema). **Always\n  call `list_map_item_schemas` first** — see the CAD/PCF/.weldb note below\n- **Zipmaps (a whole mapped drawing in one upload):** `inspect_zipmap`,\n  `upload_zipmap` — see [Zipmaps](#zipmaps-a-whole-mapped-drawing-in-one-upload)\n- **Repairs:** `list_repair_codes`, `add_map_item_repair`\n- **Inspection forms & notes:** `list_form_submissions`, `create_form_submission`,\n  `get_form_submission`, `update_form_submission`, `complete_form_submission`,\n  `create_note`, `list_notes`, `resolve_note`\n- **Photos:** `list_photos`, `attach_photo`\n- **ITP & sign-offs (your call):** `list_itp_line_items`, `get_itp_line_item`,\n  `create_itp_line_item`, `update_itp_line_item`, `mark_map_item_complete`,\n  `mark_map_item_accepted`, `mark_itp_complete`, `mark_itp_accepted`\n- **Quality-hold locks (witness/hold points — the user's call):** `list_lock_types`\n  (read only), `list_locks`, `get_lock`, `add_lock` (place a hold point — fit-up,\n  tack-up, weld-cleanliness, FME, final-closure, boiler-tube FME sponge-in/sponge-out,\n  etc. — on a map item or ITP line item so it can't be turned in until inspected),\n  `unlock_lock`, `assign_lock`, `delete_lock` (withdraws a hold placed in error — a\n  soft delete: kept in the audit trail, no longer holding the item; use `unlock_lock`\n  for a hold that was actually satisfied). A lock is a construction quality gate,\n  **not** a security control; place or clear one only at the explicit request of its\n  owner/author or an authorized inspector. **Creating or editing lock *types*** (the\n  named hold definitions, and who may place/clear each) is intentionally **not**\n  exposed here — that permission-sensitive setup is done in the web app's Project\n  Admin, and the assistant will point you there\n- **Turnover (the important part):** `list_reference_requests`,\n  `create_reference_request`, `list_references`, `create_reference`,\n  `turnover_report`\n- **Receiving:** `list_shippers`, `list_shipper_line_items`\n- **Semantic search (meaning-based, ranked):** `semantic_search` — natural-language\n  search across the project's documents, drawings, large-format drawings, jobs,\n  packages, list items, map items, form submissions, notes, and shippers\n- **How QC Database works:** `search_user_manual` (ask \"how do I…?\" questions and\n  read the product help articles), `list_user_manual` (browse the help contents)\n- **Utilities:** `generate_qr_code`\n\n---\n\n## A companion to CAD, PCF, and .weldb systems\n\nThis server is a natural partner to the systems that already describe your\ngeometry — CAD/CAE exports, **PCF** piping files, and **.weldb** boilermaker\nreplacement-panel files. Those sources carry most of what a good map item needs,\nso the assistant can turn them into accurate weld maps — **if** it follows two\nhabits:\n\n1. **Fetch the schema first.** Always call `list_map_item_schemas` *before*\n   `create_map_item` / `bulk_create_map_items`. That picks the right schema and\n   reveals its exact fields, so source values land where they belong instead of\n   being guessed or free-texted:\n   - **PCF** (piping) files carry most of what a pipe weld needs — **joint type,\n     material, weight/sch**.\n   - **.weldb** files carry **material, tube wall thickness**, and the weld's\n     **rectangular position** on the drawing — enough to build a weld map\n     automatically.\n2. **Match the source's geometry — point vs. rectangular welds.** This is the big\n   data-quality lever. A **PCF** pipe weld is a single **point** weld (one x/y). A\n   **.weldb** weld has a **rectangular** position — give the second point\n   (`x_position_2`/`y_position_2`) as the opposite corner so the extent is\n   preserved. Collapsing a rectangular weld to a bare point (or spreading a point\n   weld into a box) silently corrupts the map. Fetching the schema up front is\n   what makes this distinction obvious before any items are placed.\n\n---\n\n## Zipmaps: a whole mapped drawing in one upload\n\nA **[zipmap](https://github.com/ProcessQualitySolutions/zipmaps)** is a plain zip\nthat packages **one drawing** (a PNG, optionally the source single-page PDF)\ntogether with **every map item already placed on it** and the JSON Schemas that\ndescribe those item types. It is the \"bring your own AI\" hand-off: your model,\nCAD export, or takeoff tool produces the map; QC Database ingests it.\n\n`upload_zipmap` replaces the old three-step dance (upload the drawing → wait →\nbulk-create items against it) with **one transactional request**. The drawing, all\nof its map items across every schema, and its extracted data are created together\n— or nothing is. No server-side AI runs on it; the map is yours.\n\nAsk for it in plain language: *\"upload this zipmap into the Unit 2 hydro\npackage.\"* Behind that, the assistant does three things first:\n\n1. **`inspect_zipmap`** — looks inside without uploading: the drawing size, how\n   many items of each type, the data fields those items use, and which types are\n   **not yet bound** to a QC Database map item schema.\n2. **`list_map_item_schemas` / `get_map_item_schema`** — a zipmap names its types\n   locally (`weld`, `heat`); QC Database identifies schemas by id. Any unbound type\n   must be paired with the schema it belongs on (`schema_ids={\"weld\": \"<id>\"}`),\n   and comparing the type's fields against the schema's fields is how that match is\n   confirmed. Producers can skip this by writing the id into the archive's\n   `schemata/<type>.schema.json` up front.\n3. **`list_packages`** — the new drawing has to be filed somewhere, and the API\n   **requires** a package id. Pick (or `create_package`) the scope package first.\n\nCoordinates travel as **pixels of the zipmap's PNG** (top-left origin, y down) —\nexactly the space QC Database maps in — so nothing is rescaled or flipped. If the\nproject uses per-schema auto-numbering, its labels win over the zipmap's item ids\nand the result reports each new item against the id it came from.\n\nRe-sending a corrected map? `mode=\"replace\"` first **soft-deletes** any live\ndrawing in that package with the same drawing number (retained in the audit trail,\ngone from drawing lists, and its map items go with it) instead of leaving a\nduplicate. The default, `mode=\"append\"`, always creates a new drawing.\n\nEverything that can be checked locally is checked **before** the upload — a\nmissing PNG, a type with no schema id, a coordinate outside the drawing, PDF-space\ncoordinates that would misplace every item — so a bad map fails in a second with a\nlist of what to fix, not after a multi-megabyte round trip.\n\n> Zipmaps are read from your own computer, so this works with the local (stdio)\n> server. A hosted server never touches your disk (see\n> [Filesystem safety](#filesystem-safety-design-invariant)).\n\n---\n\n## A note on sign-offs\n\nMarking work **complete** or **accepted** is a buy-off — the system records it\nunder **your** name and the current time. The assistant *can* do these for you,\nbut every one of those tools tells you plainly that **you are responsible for the\naction**. The server never signs anything off silently. Treat those as your\ndecisions, made with your go-ahead.\n\n---\n\n## Privacy & safety\n\n- The server runs locally and only talks to `https://qcdatabase.ai`.\n- Your login is **pinned to one organization** (chosen at sign-in) and your\n  session is **pinned to one project** — so the assistant can't reach into a\n  different workspace or project by accident.\n- The server only requests the permissions an everyday user needs; admin\n  actions are out of scope by design.\n- You can disconnect any connected app at any time from your QC Database account.\n\nYour login is stored on your own computer in a per-user folder:\n\n- **Windows:** `%LOCALAPPDATA%\\qcdatabase-mcp\\store.json`\n- **Mac:** `~/Library/Application Support/qcdatabase-mcp/store.json`\n- **Linux:** `~/.config/qcdatabase-mcp/store.json`\n\nRun the `logout` tool (or delete that file) to forget the login.\n\n### Filesystem safety (design invariant)\n\nThis is a hard rule the server must always uphold — for its own safety and for\nanyone contributing to this open-source repo:\n\n- **The server never modifies its own files.** No tool can read from or write to\n  the server's whole installation — its own source, its dependencies\n  (`site-packages`), the virtualenv, or (from a checkout) the repo root — whatever\n  path it is given, so the assistant can never edit the server's or a\n  dependency's code (no self-modification). Downloads also refuse to **overwrite\n  an existing file**; choose a new path.\n- **The server never touches its credential store beyond the token flow.** No\n  tool can read or write the per-user config directory, so a token can never be\n  exfiltrated or overwritten through a tool, and it can never be committed to the\n  repo (it lives outside any checkout; `store.json` is also `.gitignore`d).\n- **The only local files a tool ever touches are the user's own documents**, and\n  only in **stdio** mode — the upload tools read a file you point them at, and the\n  download/export tools write to a path you choose. In **hosted** mode the server\n  refuses all local filesystem access, because the disk is the server's, not\n  yours (uploads/downloads there would need a client-side file channel instead).\n\nThese rules are enforced in code by `_guard_local_path` in `server.py`.\n\n---\n\n## Troubleshooting\n\n- **\"qcdatabase\" doesn't show up in Claude Desktop after restarting.**\n  1. Make sure you **fully quit** the app (system tray on Windows, Cmd-Q on Mac)\n     and reopened it.\n  2. Check the config file is valid JSON — a missing comma or brace stops every\n     server from loading. Paste it into a JSON checker if unsure.\n  3. If it still doesn't load, set `\"command\"` to the full path to Python (see\n     the install note above).\n  4. Look at the Claude Desktop logs for an error: Windows\n     `%APPDATA%\\Claude\\logs\\`, Mac `~/Library/Logs/Claude/`.\n- **\"Not logged in.\"** Ask the assistant to *connect to QC Database* and finish\n  the sign-in in your browser.\n- **\"No project is set.\"** Ask it to *work on* a project, or to *list projects*.\n- **A newer feature says \"Access denied (403)\" (e.g. the user manual, or the\n  quality-hold locks).** If you first signed in with an older version, your saved\n  app registration predates that feature's permission. Delete your `store.json`\n  (see **Privacy & safety** for its location) and sign in again — that\n  re-registers the app with the current permissions.\n- **The browser didn't open during login.** The assistant will show you a link —\n  open it manually to finish, then sign in again.\n- **The sign-in page can't connect / port already in use.** The login uses a\n  local web address on port `8765`. If another program is using it, set a\n  different free port before launching the app by adding an environment variable\n  `QCDB_CALLBACK_PORT` (for example `8766`), then sign in again.\n- **It can't find `python`.** Reinstall Python with \"Add to PATH\" ticked\n  (Windows), or use `python3` in the config.\n\n---\n\n## For developers\n\n```\npip install -e .            # editable install\npython -m qcdatabase_mcp    # run the stdio server directly\n\npip install -e '.[dev]'     # editable install + test deps\npytest                      # run the test suite\n```\n\nLayout:\n\n```\nsrc/qcdatabase_mcp/\n  __main__.py   # entry point + CLI (chooses stdio vs. hosted HTTP)\n  server.py     # FastMCP server + all tools + local filesystem guard\n  hosted.py     # multi-user hosting: OAuth resource server, token verify, sessions\n  client.py     # HTTP client: bearer auth, auto-refresh, errors, pagination, path safety\n  auth.py       # OAuth2 (PKCE + dynamic client registration) login & refresh (stdio)\n  config.py     # local token / project storage (stdio)\ntests/\n  test_security.py  # guard, token verifier, path safety, pagination, refresh, ...\nmcp_server_spec.md  # the API + behaviour spec this server implements\nCLAUDE.md           # contributor invariants (filesystem safety, credential handling)\n```\n\nEnvironment variables (stdio mode):\n\n- `QCDB_CALLBACK_PORT` — local OAuth callback port (default `8765`).\n- `QCDB_CONFIG_DIR` — override where tokens/settings are stored.\n\n### Hosting it for many users (`mcp.qcdatabase.ai`)\n\nBy default the server speaks MCP over **stdio** — one local user, sign-in via the\n`login` tool. Pass `--http` to run the **multi-user hosted** server instead, which\nserves the **Streamable HTTP** transport at `/mcp`, plus a public **home page**\nat `/` (connection instructions for humans) and a **health check** at `/health`\n(for load balancers and uptime monitors).\n\n**Try it on your own machine** (creates a `.venv`, installs, runs on\n`http://127.0.0.1:8000`):\n\n```\n./scripts/run-local.sh        # Mac/Linux\n.\\scripts\\run-local.ps1       # Windows (PowerShell)\nscripts\\run-local.bat         # Windows (double-clickable / cmd)\n```\n\nThen open <http://127.0.0.1:8000/> in a browser.\n\n**Deploy it on a server** so that `git pull` + restart is a complete deploy —\nthe full walkthrough (systemd unit, nginx/Caddy configs, verification\nchecklist, troubleshooting) is in [`deploy/README.md`](deploy/README.md):\n\n```\ncp deploy/example.env .env    # set QCDB_MCP_RESOURCE_URL=https://mcp.example.com\n./scripts/run-server.sh       # binds 127.0.0.1:8000; put a TLS proxy in front\n```\n\n> **Behind nginx you MUST forward the original Host header**\n> (`proxy_set_header Host $host;`) — the provided `deploy/nginx.conf` does.\n> Without it, the DNS-rebinding protection rejects every authenticated request\n> with `421`, which looks like \"OAuth works but no tool call ever does\". Caddy\n> (`deploy/Caddyfile`) does the right thing by default.\n\nIn hosted mode the server is an **OAuth 2.0 resource server** (per the MCP\nauthorization spec) — there are no shared secrets and nothing is stored on disk:\n\n- Each MCP client signs its **own user** in against QCDatabase.AI (discovered from\n  the `/.well-known/oauth-protected-resource` document this server publishes) and\n  sends that user's access token as `Authorization: Bearer …` on every request.\n- The server **verifies** the token against `/api/whoami/`, identifies the user,\n  and acts as them. Unauthenticated requests get `401` with a `WWW-Authenticate`\n  header pointing clients at the authorization server, so sign-in is automatic.\n- **Per-user session state** (the active project) is kept in memory keyed by a\n  globally-unique identity (tenant + user id, never the raw token), so many people\n  share one deployment without ever seeing each other's work. Verification fails\n  closed — a token that doesn't resolve to a user is rejected. (State resets on\n  restart — users just re-run `set_project`.)\n- **DNS-rebinding protection** validates the `Host`/`Origin` of every request\n  (forged `Host` → `421`, forged `Origin` → `403`).\n\nThe `login` / `logout` tools become no-ops in hosted mode (sign-in is the client's\njob); everything else works identically to stdio.\n\nHosted-mode configuration (flags override env):\n\n- `QCDB_MCP_HTTP=1` — same as `--http`.\n- `--host` / `QCDB_MCP_HOST`, `--port` / `QCDB_MCP_PORT` — bind (default\n  `127.0.0.1:8000`).\n- `--resource-url` / `QCDB_MCP_RESOURCE_URL` — this server's public URL and OAuth\n  resource id; **required** when binding a non-loopback host.\n- `--issuer-url` / `QCDB_MCP_ISSUER_URL` — the OAuth authorization server (default\n  `https://qcdatabase.ai`).\n- `QCDB_MCP_ALLOWED_HOSTS` / `QCDB_MCP_ALLOWED_ORIGINS` — extra allow-list entries\n  (comma-separated), e.g. when fronted by additional hostnames.\n- `QCDB_MCP_TOKEN_CACHE_TTL` — seconds a verified token is trusted before\n  re-checking `/api/whoami/` (default `60`). This is also the window in which a\n  token revoked upstream keeps working here; lower it to shrink that window.\n- `QCDB_MCP_TOKEN_NEG_CACHE_TTL` — seconds a *failed* verification is remembered\n  (default `5`), which blunts garbage-token amplification against the API.\n- `QCDB_MCP_STATELESS` — serve each request without a server-held MCP session\n  (default `1`). Leave on: stateful sessions break on restarts, redeploys, and\n  multi-replica setups (\"session not found\"). Set `0` only if you need\n  server-initiated messages within a session.\n- `QCDB_MCP_JSON_RESPONSE` — answer POSTs with plain JSON instead of an SSE\n  stream (default `1`). Leave on: buffering proxies (nginx's default) stall SSE\n  and the client times out; none of this server's tools stream partial results.\n\n**TLS.** Terminate TLS at a reverse proxy in front of the server (never expose\nplain HTTP publicly) and forward the real `Host` header.\n\n**Resource indicator (kept open).** This server accepts any valid QCDatabase-\nissued token and takes its resource id from `QCDB_MCP_RESOURCE_URL`, so anyone can\nfork it and host on their own domain without special AS configuration. For strict\nRFC 8707 audience binding, an operator *may* configure their authorization server\nto issue tokens bound to their MCP URL — but it is not required.\n\n**Scaling across replicas.** The only shared state is each user's pinned project —\nsmall, non-secret, and disposable (lost pins just mean re-running `set_project`).\nPick whichever fits your deployment:\n\n- **Single instance (default).** In-memory store, nothing to run. Best for most\n  self-hosters.\n- **Sticky sessions.** Several replicas behind a load balancer that routes each\n  user to the same replica; still in-memory, but a replica restart drops its\n  users' pins.\n- **Shared store (Redis).** Set `QCDB_MCP_REDIS_URL` and install the extra\n  (`pip install 'qcdatabase-mcp[redis]'`). All replicas share state and it\n  survives restarts. Pins expire after `QCDB_MCP_SESSION_TTL` seconds idle\n  (default 8h). To plug in a different backend, implement the two-method store in\n  `hosted.py` (`get_active_project` / `set_active_project`) and return it from\n  `build_session_store`.\n\n(The token-verification cache is intentionally per-replica — each rebuilds it from\n`/api/whoami/`, so it needs no sharing.)\n\nLicensed under the MIT License (see `LICENSE`).\n",
  "bytes": 25232,
  "sha": "a232012d789d1d30626df9bb968acdbe6972c1b0cb268a3f354aabbdaf8e4af3",
  "repo_slug": "processqualitysolutions/qcdatabase_mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_ai_qcdatabase_mcp_e3874a0f/readme"
}