Qcdatabase Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Qcdatabase Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A small program that lets an AI assistant (like Claude) do your everyday QC Database work for you — set the project you're working on, upload records, read what the AI pulled off a document, and find and close out the items still needed for a complete, traceable turnover package.
It runs on your own computer. Your AI assistant talks to it; it talks to QCDatabase.AI on your behalf, using your own login.
Built on the official Model Context Protocol Python library and the QC Database MCP specification — published at qcdatabase.ai/mcp_server_spec.md and kept in this repo as mcp_server_spec.md.
Once it's connected, you can talk to it in plain language. For example:
across your project data, ranked by relevance.
QC Database user manual, so you can learn the app without leaving your assistant.
recorded under your name — that's your call, not the AI's).
You need Python 3.10 or newer on your computer.
python.org/downloads. During the python.org install, tick "Add Python to PATH."
brew install python or get it fromTo check it's installed, open a terminal (PowerShell on Windows, Terminal on Mac) and run:
python --versionYou should see Python 3.10 or higher. (On some Macs the command is python3.)
From a terminal, in this folder:
pip install ".[runtime]"That's it. This installs the server and a command called qcdatabase-mcp.
Ifpipisn't found, trypython -m pip install ".[runtime]"(orpython3 -m pip install ".[runtime]").
Claude Desktop launches this server for you and shows its tools in your chats. Set it up once:
If you don't have it, download it from claude.ai/download and sign in.
In Claude Desktop:
(Windows: menu ☰ → File → Settings; Mac: Claude → Settings).
This opens (and, if needed, creates) a file named claude_desktop_config.json. You can also open it directly:
%APPDATA%\Claude\claude_desktop_config.json~/Library/Application Support/Claude/claude_desktop_config.jsonPut this in the file. If the file already has other servers, just add the "qcdatabase" block inside the existing "mcpServers" section.
Windows:
{
"mcpServers": {
"qcdatabase": {
"command": "python",
"args": ["-m", "qcdatabase_mcp"]
}
}
}Mac (Python is usually python3 there):
{
"mcpServers": {
"qcdatabase": {
"command": "python3",
"args": ["-m", "qcdatabase_mcp"]
}
}
}Save the file.
If Claude can't start it (you see "python not found" or the server fails), use the full path to Python instead of just"python". Find it by runningwhere python(Windows) orwhich python3(Mac) in a terminal, then paste that path as the"command". For example on Windows:
>
``json { "mcpServers": { "qcdatabase": { "command": "C:\\Users\\you\\AppData\\Local\\Programs\\Python\\Python312\\python.exe", "args": ["-m", "qcdatabase_mcp"] } } } ``>
(Use double backslashes \\ in Windows paths inside JSON.)Fully quit and reopen the app (on Windows, also close it from the system tray; on Mac, Quit with Cmd-Q — closing the window isn't enough). The new server only loads on a fresh start.
Start a new chat and look for the tools/connector indicator (a slider or tool icon near the message box). Click it and you should see qcdatabase with its tools listed. If you don't, see Troubleshooting.
Then ask Claude to "connect to QC Database" to sign in — see below.
logintool). Your web browser opens; log in and pick which company workspace you want to connect. That choice locks the connection to that one organization. You only do this once — it's remembered.
projects first. Almost every action happens "inside" a project, so the server asks you to choose one before it will do project work. You can switch projects any time.
turnover, create and close reference requests, add notes and photos, and so on.
login, logout, auth_status, whoamilist_tenants, list_projects, set_project,get_active_project, list_project_members, list_lists, list_list_items, list_map_item_schemas, get_map_item_schema, list_document_folders, list_form_schemas
create_list_item, update_list_item,delete_list_item (a soft delete — the entry stops appearing in list reads but stays on record)
update_project, list_jobs, create_job,update_job, list_packages, create_package, update_package, list_line_specs, create_line_spec, update_line_spec (updates change only the fields you pass; name a field in clear to empty it)
upload_document, upload_document_version,download_document, upload_drawing, upload_large_format_drawing, upload_drawing_to_package, upload_drawing_version, upload_large_format_drawing_version, attach_photo
list_drawings (filter by type, status, job, package, orupdated_since to see only drawings changed since a date-time), get_drawing (incl. pixel width/height — the HTML5-canvas coordinate space for map items), export_drawing, export_large_format_drawing, update_drawing (identifying metadata), move_drawing_to_package (existing drawing and destination in the active project; only changes package assignment, without uploading or duplicating)
list_fillable_templates, get_fillable_template,download_fillable_template, submit_fillable_template
list_documents (filter by folder, status, job, package,tags and metadata values), get_document_history, delete_document, bulk_delete_documents, list_deleted_documents, restore_document, bulk_restore_documents (deletes are soft: the document is archived and can be restored). download_document is the document export
get_document, set_document_extracted_data,list_map_items, get_map_item, create_map_item (place welds/flanges by pixel coordinates), update_map_item (rename, move, notes, assignee, working status), bulk_create_map_items, bulk_update_map_items (batch up to 500 items on one drawing/schema). Always call `list_map_item_schemas` first — see the CAD/PCF/.weldb note below
inspect_zipmap,upload_zipmap — see Zipmaps
list_repair_codes, add_map_item_repairlist_form_submissions, create_form_submission,get_form_submission, update_form_submission, complete_form_submission, create_note, list_notes, resolve_note
list_photos, attach_photolist_itp_line_items, get_itp_line_item,create_itp_line_item, update_itp_line_item, mark_map_item_complete, mark_map_item_accepted, mark_itp_complete, mark_itp_accepted
list_lock_types(read only), list_locks, get_lock, add_lock (place a hold point — fit-up, tack-up, weld-cleanliness, FME, final-closure, boiler-tube FME sponge-in/sponge-out, etc. — on a map item or ITP line item so it can't be turned in until inspected), unlock_lock, assign_lock, delete_lock (withdraws a hold placed in error — a soft delete: kept in the audit trail, no longer holding the item; use unlock_lock for a hold that was actually satisfied). A lock is a construction quality gate, not a security control; place or clear one only at the explicit request of its owner/author or an authorized inspector. *Creating or editing lock types* (the named hold definitions, and who may place/clear each) is intentionally not** exposed here — that permission-sensitive setup is done in the web app's Project Admin, and the assistant will point you there
list_reference_requests,create_reference_request, list_references, create_reference, turnover_report
list_shippers, get_shipper, list_shipper_line_items,list_shipper_types, list_shipper_locations, create_shipper, update_shipper, set_shipper_status, add_shipper_line_items, update_shipper_line_item, remove_shipper_line_item (a soft delete), delete_shipper (project admins only; a soft delete). Any status other than draft locks a shipper; unlocking, receiving and checklists stay in the web app. Needs the shippers:write scope: connections approved before it was added must reconnect (stdio: run login again) to get it
semantic_search — natural-languagesearch across the project's documents, drawings, large-format drawings, jobs, packages, list items, map items, form submissions, notes, and shippers
search_user_manual (ask "how do I…?" questions andread the product help articles), list_user_manual (browse the help contents)
create_training,list_trainings, get_training, list_training_ideas, get_training_idea, get_training_policy (admins), upload_training_policy, get_training_authoring_guide, get_training_template, validate_training_bundle (free), upload_training_version (free; stores a private version only you can play), submit_training_review ($25 security review, needs confirm_charge), get_training_review and download_training_version (reviewed versions only, for co-developers). The local server zips a bundle folder itself and sends it straight to redline.school; on the hosted server, send a zip with begin_upload first. File contents never pass through the AI. These tools take an explicit scope rather than the active project. Connections approved before Trainings existed must re-approve to get trainings:read/trainings:write. Publishing, delisting and reassigning owners stay in the QC Database web app.
assign_training and invite_users (admins; bothpreview until confirm=True, at most 200 people), list_assignments (the Training Matrix: completed / due / overdue / expired, and blocked / grace for required trainings), list_permission_requirements (which permissions need a current training; set only in the web app) and get_my_trainings (any member, one call: required and completed trainings, results, links, and gated permissions). Gated actions (accepting map items or ITP rows, lock actions, gated forms) can fail with training_required; the error names the training, its expiry and the link to take it.
get_training_leaderboard (any member who can playthe game: ranked best scores by person, user type, project role or project, with your own rank) and get_engagement_report (admins: replays, voluntary plays, time trained, multiplayer, score improvement and trainings nobody replays, for the workspace, a project or one training).
start_crew_session (a publishedmultiplayer training; returns the join code, join link and QR link the crew scans to play together, optionally with an in-app invite to a project), list_crew_sessions, get_crew_session (who joined, teams, results as games finish), end_crew_session and invite_project_to_crew_session. Starting, ending and inviting preview first and need confirm=True.
list_build_requests, get_build_request and, for PQSstaff, quote_build_request (price, $0 allowed, and description; previews first; emails both sides). A PQS Training Builder sees the builds assigned to them and builds each with the usual training tools (reviews included, no charge); a customer's admins see their requests and quotes. Requesting a quote, accepting the bid, accepting the training and change orders happen on the training page.
list_crews (admins); assign a training to a crew withassign_training and {type: 'crew', crew_id}. Crews are edited in the web app.
list_forum_boards,list_forum_posts (filter by board, search, questions), get_forum_post, list_forum_replies, create_forum_post and reply_to_forum_post (optional image; on the hosted server, send it with begin_upload first). Posts are public to every company and are badged "via MCP" by QC Database, so the assistant shows you the text before posting. Connections approved before the Forum API existed must re-approve to get forum:read/forum:write. Moderation stays in the web app.
generate_qr_codeMap-item type definitions are read-only in MCP: discovery is available, but no create/edit/promote operation is exposed. Record edits and imports bind to existing schema IDs; archive schemas are not installed as new types.
List and document type creation/editing are permitted, but blocked by missing API write contracts. Document folder moves and package tags placed on LFDs are also API-blocked; moving a drawing to a package is not an LFD package tag. See the API handoff for the exact gaps. Hosted production needs a republish and refreshed client tool discovery before new tools appear; development verification alone does not update production.
This server is a natural partner to the systems that already describe your geometry — CAD/CAE exports, PCF piping files, and .weldb boilermaker replacement-panel files. Those sources carry most of what a good map item needs, so the assistant can turn them into accurate weld maps — if it follows two habits:
list_map_item_schemas beforecreate_map_item / bulk_create_map_items. That picks the right schema and reveals its exact fields, so source values land where they belong instead of being guessed or free-texted:
material, weight/sch**.
rectangular position on the drawing — enough to build a weld map automatically.
data-quality lever. A PCF pipe weld is a single point weld (one x/y). A .weldb weld has a rectangular position — give the second point (x_position_2/y_position_2) as the opposite corner so the extent is preserved. Collapsing a rectangular weld to a bare point (or spreading a point weld into a box) silently corrupts the map. Fetching the schema up front is what makes this distinction obvious before any items are placed.
A [zipmap](https://github.com/ProcessQualitySolutions/zipmaps) is a plain zip that packages one drawing (a PNG, optionally the source single-page PDF) together with every map item already placed on it and the JSON Schemas that describe those item types. It is the "bring your own AI" hand-off: your model, CAD export, or takeoff tool produces the map; QC Database ingests it.
upload_zipmap replaces the old three-step dance (upload the drawing → wait → bulk-create items against it) with one transactional request. The drawing, all of its map items across every schema, and its extracted data are created together — or nothing is. No server-side AI runs on it; the map is yours.
Ask for it in plain language: "upload this zipmap into the Unit 2 hydro package." Behind that, the assistant does three things first:
many items of each type, the data fields those items use, and which types are not yet bound to a QC Database map item schema.
locally (weld, heat); QC Database identifies schemas by id. Any unbound type must be paired with the schema it belongs on (schema_ids={"weld": "<id>"}), and comparing the type's fields against the schema's fields is how that match is confirmed. Producers can skip this by writing the id into the archive's schemata/<type>.schema.json up front.
requires a package id. Pick (or create_package) the scope package first.
Coordinates travel as pixels of the zipmap's PNG (top-left origin, y down) — exactly the space QC Database maps in — so nothing is rescaled or flipped. If the project uses per-schema auto-numbering, its labels win over the zipmap's item ids and the result reports each new item against the id it came from.
Re-sending a corrected map? mode="replace" first soft-deletes any live drawing in that package with the same drawing number (retained in the audit trail, gone from drawing lists, and its map items go with it) instead of leaving a duplicate. The default, mode="append", always creates a new drawing.
Everything that can be checked locally is checked before the upload — a missing PNG, a type with no schema id, a coordinate outside the drawing, PDF-space coordinates that would misplace every item — so a bad map fails in a second with a list of what to fix, not after a multi-megabyte round trip.
Zipmaps are read from your own computer, so this works with the local (stdio) server. A hosted server never touches your disk (see Filesystem safety).
Marking work complete or accepted is a buy-off — the system records it under your name and the current time. The assistant can do these for you, but every one of those tools tells you plainly that you are responsible for the action. The server never signs anything off silently. Treat those as your decisions, made with your go-ahead.
https://qcdatabase.ai.session is pinned to one project — so the assistant can't reach into a different workspace or project by accident.
actions are out of scope by design.
Your login is stored on your own computer in a per-user folder:
%LOCALAPPDATA%\qcdatabase-mcp\store.json~/Library/Application Support/qcdatabase-mcp/store.json~/.config/qcdatabase-mcp/store.jsonRun the logout tool (or delete that file) to forget the login.
This is a hard rule the server must always uphold — for its own safety and for anyone contributing to this open-source repo:
the server's whole installation — its own source, its dependencies (site-packages), the virtualenv, or (from a checkout) the repo root — whatever path it is given, so the assistant can never edit the server's or a dependency's code (no self-modification). Downloads also refuse to overwrite an existing file; choose a new path.
tool can read or write the per-user config directory, so a token can never be exfiltrated or overwritten through a tool, and it can never be committed to the repo (it lives outside any checkout; store.json is also .gitignored).
only in stdio mode — the upload tools read a file you point them at, and the download/export tools write to a path you choose. In hosted mode the server refuses all local filesystem access, because the disk is the server's, not yours (uploads/downloads there would need a client-side file channel instead).
These rules are enforced in code by _guard_local_path in server.py.
and reopened it.
server from loading. Paste it into a JSON checker if unsure.
"command" to the full path to Python (seethe install note above).
%APPDATA%\Claude\logs\, Mac ~/Library/Logs/Claude/.
the sign-in in your browser.
quality-hold locks). If you first signed in with an older version, your saved app registration predates that feature's permission. Delete your `store.json` (see Privacy & safety** for its location) and sign in again — that re-registers the app with the current permissions.
open it manually to finish, then sign in again.
local web address on port 8765. If another program is using it, set a different free port before launching the app by adding an environment variable QCDB_CALLBACK_PORT (for example 8766), then sign in again.
(Windows), or use python3 in the config.
pip install -e '.[runtime]' # editable install
python -m qcdatabase_mcp # run the stdio server directly
pip install -e '.[dev]' # editable install + test deps
pytest # run the test suiteLayout:
src/qcdatabase_mcp/
__main__.py # entry point + CLI (chooses stdio vs. hosted HTTP)
server.py # FastMCP server + all tools + local filesystem guard
hosted.py # multi-user hosting: OAuth resource server, token verify, sessions
client.py # HTTP client: bearer auth, auto-refresh, errors, pagination, path safety
auth.py # OAuth2 (PKCE + dynamic client registration) login & refresh (stdio)
config.py # local token / project storage (stdio)
tests/
test_security.py # guard, token verifier, path safety, pagination, refresh, ...
mcp_server_spec.md # the API + behaviour spec this server implements
CLAUDE.md # contributor invariants (filesystem safety, credential handling)Environment variables (stdio mode):
QCDB_CALLBACK_PORT — local OAuth callback port (default 8765).QCDB_CONFIG_DIR — override where tokens/settings are stored.mcp.qcdatabase.ai)By default the server speaks MCP over stdio — one local user, sign-in via the login tool. Pass --http to run the multi-user hosted server instead, which serves the Streamable HTTP transport at /mcp, plus a public home page at / (connection instructions for humans) and a health check at /health (for load balancers and uptime monitors).
Try it on your own machine (creates a .venv, installs, runs on http://127.0.0.1:8000):
./scripts/run-local.sh # Mac/Linux
.\scripts\run-local.ps1 # Windows (PowerShell)
scripts\run-local.bat # Windows (double-clickable / cmd)Then open <http://127.0.0.1:8000/> in a browser.
Deploy it on a server so that git pull + restart is a complete deploy — the full walkthrough (systemd unit, nginx/Caddy configs, verification checklist, troubleshooting) is in deploy/README.md:
cp deploy/example.env .env # set QCDB_MCP_RESOURCE_URL=https://mcp.example.com
./scripts/run-server.sh # binds 127.0.0.1:8000; put a TLS proxy in frontBehind nginx you MUST forward the original Host header (proxy_set_header Host $host;) — the provideddeploy/nginx.confdoes. Without it, the DNS-rebinding protection rejects every authenticated request with421, which looks like "OAuth works but no tool call ever does". Caddy (deploy/Caddyfile) does the right thing by default.
In hosted mode the server is an OAuth 2.0 resource server (per the MCP authorization spec) — there are no shared secrets and nothing is stored on disk:
the /.well-known/oauth-protected-resource document this server publishes) and sends that user's access token as Authorization: Bearer … on every request.
/api/whoami/, identifies the user,and acts as them. Unauthenticated requests get 401 with a WWW-Authenticate header pointing clients at the authorization server, so sign-in is automatic.
globally-unique identity (tenant + user id, never the raw token), so many people share one deployment without ever seeing each other's work. Verification fails closed — a token that doesn't resolve to a user is rejected. (State resets on restart — users just re-run set_project.)
Host/Origin of every request(forged Host → 421, forged Origin → 403).
The login / logout tools become no-ops in hosted mode (sign-in is the client's job); everything else works identically to stdio.
Hosted-mode configuration (flags override env):
QCDB_MCP_HTTP=1 — same as --http.--host / QCDB_MCP_HOST, --port / QCDB_MCP_PORT — bind (default127.0.0.1:8000).
--resource-url / QCDB_MCP_RESOURCE_URL — this server's public URL and OAuthresource id; required when binding a non-loopback host.
--issuer-url / QCDB_MCP_ISSUER_URL — the OAuth authorization server (defaulthttps://qcdatabase.ai).
QCDB_MCP_ALLOWED_HOSTS / QCDB_MCP_ALLOWED_ORIGINS — extra allow-list entries(comma-separated), e.g. when fronted by additional hostnames. The allowed origins are also the only origins that get CORS headers on /mcp, so add a browser-based client's origin here to let it connect.
QCDB_MCP_SCOPES — scopes advertised as scopes_supported in theprotected-resource document and as scope="..." in the 401 challenge (space- or comma-separated). Defaults to the same everyday read + write set the stdio login uses. MCP clients such as Claude only request advertised scopes, so leaving this empty would give them read-only access. Advertised, not required: a token approved with fewer scopes still connects. Every value must be a scope QCDatabase.AI grants.
QCDB_MCP_TOKEN_CACHE_TTL — seconds a verified token is trusted beforere-checking /api/whoami/ (default 60). This is also the window in which a token revoked upstream keeps working here; lower it to shrink that window.
QCDB_MCP_TOKEN_NEG_CACHE_TTL — seconds a failed verification is remembered(default 5), which blunts garbage-token amplification against the API.
QCDB_MCP_STATELESS — serve each request without a server-held MCP session(default 1). Leave on: stateful sessions break on restarts, redeploys, and multi-replica setups ("session not found"). Set 0 only if you need server-initiated messages within a session.
QCDB_MCP_JSON_RESPONSE — answer POSTs with plain JSON instead of an SSEstream (default 1). Leave on: buffering proxies (nginx's default) stall SSE and the client times out; none of this server's tools stream partial results.
TLS. Terminate TLS at a reverse proxy in front of the server (never expose plain HTTP publicly) and forward the real Host header.
Resource indicator (kept open). This server accepts any valid QCDatabase- issued token and takes its resource id from QCDB_MCP_RESOURCE_URL, so anyone can fork it and host on their own domain without special AS configuration. For strict RFC 8707 audience binding, an operator may configure their authorization server to issue tokens bound to their MCP URL — but it is not required.
Scaling across replicas. The only shared state is each user's pinned project — small, non-secret, and disposable (lost pins just mean re-running set_project). Pick whichever fits your deployment:
self-hosters.
user to the same replica; still in-memory, but a replica restart drops its users' pins.
QCDB_MCP_REDIS_URL and install the extra(pip install 'qcdatabase-mcp[redis]'). All replicas share state and it survives restarts. Pins expire after QCDB_MCP_SESSION_TTL seconds idle (default 8h). To plug in a different backend, implement the two-method store in hosted.py (get_active_project / set_active_project) and return it from build_session_store.
(The token-verification cache is intentionally per-replica — each rebuilds it from /api/whoami/, so it needs no sharing.)
Licensed under the MIT License (see LICENSE).
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.