# QC Database - MCP Server Specification

> A guide for building Model Context Protocol (MCP) servers, AI agents, and other
> headless integrations on top of the QC Database public API.

This document explains what QC Database is, how its data is organized, how to
authenticate, and how the API should be used to do real quality-control work -
most importantly, how to assemble a complete **turnover package**.

It is intentionally a high-level guide. It references API endpoints by path and
scope, but it does **not** restate every request and response field. For the
authoritative, always-current contract (parameters, schemas, examples) use the
public, interactive API documentation:

- **Swagger UI:** [`/api/docs/`](/api/docs/)
- **ReDoc:** [`/api/redoc/`](/api/redoc/)
- **OpenAPI schema (JSON):** [`/api/schema/`](/api/schema/)

These pages are public and require no authentication, so an MCP server can read
them directly to discover the live surface.

---

## 1. What QC Database Is

QC Database is a multi-tenant SaaS platform for construction quality control. It
helps contractors collect, organize, and turn over the quality records that
every project requires - mapping inspection requirements and action items
directly onto construction drawings.

The platform's purpose is to produce a **complete, accurate, fully traceable
turnover package**: every weld, flange, and heat number mapped by a human, with
every supporting record (WPS, WPQ, NDE report, MTR, PWHT report, inspection
form, etc.) attached in its original form. AI is used to get there faster, but
every step is auditable and traceable back to a source document.

An MCP server's job, in this domain, is to act as an intelligent assistant that
helps move work toward that complete turnover package - uploading records,
extracting structured data, creating and fulfilling references, and reporting on
what is still missing.

---

## 2. Data Model: First-Class Items

QC Database is organized as a strict hierarchy. Everything is **tenant-scoped**,
and almost everything below the project level is **project-scoped** and gated by
**project membership**. Understanding this hierarchy is the key to using the API
correctly.

```
Tenant (organization)
+-- Project
    +-- Job (a work order / sub-scope of the project)
    |   +-- Package (a collection of work, e.g. a Turnover package)
    |       +-- ITP Line Item (a required inspection/test step)
    |       +-- Drawing (isometric sheet; usually under a package, may be "unsorted")
    |           +-- Map Item (a weld, flange, fitting... always pinned to a drawing)
    +-- Large Format Drawing (plan / elevation / P&ID / overview)
    +-- Line Spec (a line specification and its requirements)
    +-- Document (an uploaded record: MTR, NDE report, certificate...)
    +-- Form / Form Submission (a digital inspection form)
    +-- Note (an observation, optionally pinned to a project URL)
```

Each item below is a **first-class resource** with its own API surface.

### Tenant
The organization (company) that owns everything else. A user may belong to more
than one tenant. **A token is always pinned to exactly one tenant** (see OAuth
below), so every call you make operates inside that one organization. List the
tenants the current user belongs to via `GET /api/tenants/` (`tenants:read`).
There is intentionally no write access to tenants over the API.

### Project
The top-level container for actual quality work. Most other resources are
addressed as children of a project (e.g. `/api/.../projects/<project_id>/...`).
Use `/api/projects/` (`projects:read` / `projects:write`) to list, create, and
update projects, and `/api/projects/<id>/users/` (`projects:read`) to list active
members for assignee pickers.

### Job
A work order or sub-section of a project - a way to slice a project's scope into
manageable units. Managed under `/api/jobs/` (`jobs:read` / `jobs:write`).

### Package
A collection of work and the central organizing unit for turnover. A package
belongs to a **job**, and most of a project's drawings and ITP line items live
under a package (drawings may also be left "unsorted"). Packages have
a type (for example a Hydro Test package, a Weld Map package, or a **Turnover**
package). A turnover package is the final compilation of all records required to
hand a system over to the client. Managed under `/api/packages/`
(`packages:read` / `packages:write`).

### ITP Line Item
An entry in an **Inspection and Test Plan** - a specific, required inspection or
test step within a package (e.g. "Visual Weld Inspection", "Hydro Test"). These
are the checklist of activities that must be completed and accepted. Managed
under `/api/packages/projects/<project_id>/itp-line-items/`
(`packages:read` / `packages:write`), with explicit
`mark-complete/` and `mark-accepted/` actions. Completion and acceptance are
buy-off steps performed by people, not AI.

### Drawing
An isometric sheet uploaded to a project (P&IDs are handled as **Large Format
Drawings**, not here). A drawing typically belongs to a **package**, but may be
left **"unsorted"** at the project level. Drawings carry
AI-**extracted data** (drawing number, revision, fluid service, bill of
materials) and are the canvas onto which **map items** are placed. Managed under
`/api/drawings/` (`drawings:read` / `drawings:write`); upload to a project via
`POST /api/drawings/projects/<project_id>/upload/` and export a rendered PDF via
`GET /api/drawings/projects/<project_id>/drawing/<id>/export/`.

### Large Format Drawing (LFD)
A high-level, large-format sheet - plans, elevations, **P&IDs**, and overview
drawings. LFDs capture spatial and equipment-level data (tagged equipment,
instrumentation, areas/buildings) and show where a package's scope lives in the
plant. **Packages can be called out on an LFD as markups**, which makes it the
natural place to visualize P&ID and plan-view package scope. Upload via
`POST /api/drawings/projects/<project_id>/large-format/upload/`
and export via `.../large-format/<id>/export/` (both under `drawings:write` /
`drawings:read`).

### Form / Form Submission
A digital inspection form (e.g. a hydro test report) filled out in the app. A
**Form Submission** is one completed instance of a form schema, with revision
tracking. Create, fill, and complete submissions under
`/api/forms/projects/<project_id>/submissions/` (`forms:read` / `forms:write`),
including a `complete/` action. Completed forms are evidence that can satisfy
references (see below).

### Document
Any uploaded record that proves quality or completion - MTRs, NDE reports,
certificates, procedures, and so on - stored in its original PDF/image form.
Documents also carry AI-extracted data driven by a per-folder schema. That
extraction can run **in the cloud** (QC Database extracts against the folder
schema after upload), or you can **bring your own AI**: extract with the
tenant's in-house model and submit the structured JSON yourself when uploading
via the API or an MCP server (upload with `do_not_extract=true`, then write the
fields back - see Bring Your Own AI in Section 5). Managed
under `/api/documents/` (`documents:read` / `documents:write`); upload to a
project via `POST /api/documents/projects/<project_id>/upload/`. A document
folder can also publish a **fillable PDF template** that you fill and submit
back as a document - see Section 7.

### Note
An observation, action item, or issue. A note is pinned to a **URL within a
project** (the page it was raised on), or left unpinned - notes are **not** map
items and are **not** attached to drawings. Notes can be public or private, can
@mention and assign members, and can be resolved. Create and read via
`/api/notes/projects/<project_id>/...`
(`notes:read` / `notes:write`), including `mine/`, `feed/`, and a `resolve/`
action. A private note is only readable/resolvable by its author or a mentioned
member.

### Map Item
A point or region placed on a **drawing** - a single weld, flange, fitting, or
other inspected feature. A map item is **always pinned to a drawing** (a regular
drawing - never a Large Format Drawing). Each map item follows a **map item
schema** (see Extraction Schemas) that defines its custom fields, and each is a
candidate **source** for references. Text fields can embed **list-item pills**
(see List-Item Pills) to reference controlled-vocabulary values. Managed under
`/api/mapping/items/` (`mapping:read` / `mapping:write`); schemas live under
`/api/mapping/schemata/`. Completion and acceptance are buy-off steps with
explicit `mark-complete/` and `mark-accepted/` actions (mirroring ITP line
items): they stamp the acting user and time **server-side** and cannot be set
by writing the `status` field directly. `mark-accepted` requires the item to be
completed first, and both are blocked while the item has open reference
requests.

### Line Spec
A **line specification** - the piping class / material spec that governs a line -
together with its **requirements**. A line spec's requirements should be
**populated from the extracted JSON of referenced documents** (the spec sheets
and material standards a line cites), so the requirements stay grounded in real
source data. Those requirements then **inform the ITP line items** - whether
AI-generated or written by a person - that must be completed and accepted to
satisfy the spec. Managed under `/api/line-specs/`
(`linespecs:read` / `linespecs:write`).

### Supporting resources
- **Photos** - attach images to objects like map items:
  `/api/photos/projects/<project_id>/<object_type>/<object_id>/`
  (`photos:read` / `photos:write`).
- **Shippers** - read-only shipping/receiving data:
  `/api/shippers/projects/<project_id>/...` (`shippers:read`; no write scope).

### Deep Links (`web_url`)
Most resources include a `web_url` - an **absolute link to that item's page in
the human web app** - so an MCP server can show the user a clickable link back
into QC Database instead of a bare UUID. It appears on projects, jobs, packages,
ITP line items, drawings, large format drawings, documents, form submissions,
and list items. Listing endpoints that represent a whole page also return a
**top-level** `web_url` for that page:

- the folder-scoped document listing
  (`GET /api/documents/?project=<id>&folder=<id>`) adds `web_url` alongside the
  paginated `results` (present only when both `project` and `folder` are given);
- the list-item listing (`GET /api/lists/projects/<id>/<list_id>/items/`)
  returns `{ "web_url": <list page>, "items": [...] }`;
- the notes listings (`GET /api/notes/projects/<id>/feed/` and `.../mine/`)
  return `{ "web_url": <notes page>, "notes": [...] }`.

`web_url` is built from the **host of the incoming request**, so the links point
back through whatever domain the client connected on (this keeps them correct
under future per-tenant subdomains). It is `null` when no page applies (for
example a tenant- or global-scoped document with no project). Treat it as a
display convenience - it is never required as input.

### List-Item Pills (controlled vocabulary)
Lists hold a project's controlled vocabulary - welders, weld types, materials,
pipe sizes, equipment, and similar - and a **list item** is one entry in such a
list. Read them via `/api/lists/projects/<project_id>/` and
`/api/lists/projects/<project_id>/<list_id>/items/` (`lists:read`; there is no
write scope - list items are managed in the app). Every returned item carries a
ready-to-embed `pseudo_code` token, for example
`[[item_name:John Smith|item_id:<uuid>|project_id:<uuid>]]`. Drop that token
verbatim into a map item field value (or any other text field that renders
references) and QC Database shows it as a live, linked **pill** instead of free
text, keeping the value tied to the canonical list entry. Always prefer
inserting a list item's `pseudo_code` over typing its name by hand.

---

## 3. Authentication: The OAuth2 Flow

QC Database is an **OAuth2 authorization server and resource server** built for
headless and MCP clients. It is *not* itself an MCP server - it is the system an
MCP server connects to on a user's behalf. Authentication is standards-based
(OAuth 2.0 Authorization Code + PKCE) and every token is **pinned to a single
tenant**.

### 3.1 Discovery

Start by reading the standard metadata documents. They list every endpoint and
the supported scopes, and are CORS-enabled for browser clients:

- **Authorization server metadata (RFC 8414):**
  `/.well-known/oauth-authorization-server`
- **Protected resource metadata (RFC 9728):**
  `/.well-known/oauth-protected-resource`

### 3.2 Dynamic Client Registration (RFC 7591)

Register a client programmatically at `POST /oauth/register/`. The server issues
a **public, PKCE-required** authorization-code client (no client secret).

- You may request a `scope` string at registration. This becomes a **permanent
  scope ceiling** for the client - the client can never obtain a token broader
  than what it registered for, even if the user has wider permissions. If you
  omit it, the client defaults to the read-only scopes.
- Redirect URIs are strictly validated: no fragments, no wildcards, no
  whitespace/control characters, and must be `https`, loopback `http`
  (`127.0.0.1`/`localhost`), or a private custom scheme. Registration is rate
  limited.

Register for the **narrowest** set of scopes your integration actually needs.

### 3.3 Authorization (with tenant selection)

Send the user to `GET /oauth/authorize/` with the standard Authorization Code +
PKCE parameters (`response_type=code`, `client_id`, `redirect_uri`,
`code_challenge`, `code_challenge_method=S256`, `scope`, `state`).

The user is shown a branded consent screen where they **must pick a tenant** from
the organizations they belong to. That choice **pins** the resulting grant - and
every token derived from it - to that one tenant. This is the mechanism that
guarantees one connected app can only ever see one workspace's data.

### 3.4 Token exchange and refresh

Exchange the authorization code at `POST /oauth/token/`
(`grant_type=authorization_code`) with your PKCE `code_verifier`. You receive an
access token and a refresh token, both carrying the pinned tenant.

Refresh with `grant_type=refresh_token` at the same endpoint. Refresh rotation is
on, and the new token is **re-pinned to the same tenant** as the old one. The
tenant pin can never drift.

### 3.5 Calling the API

Send the access token as a bearer header:

```
Authorization: Bearer <access_token>
```

The bearer authenticator runs first and **fails closed**: a present-but-invalid
or under-scoped token is rejected (it cannot be masked by a session cookie). On a
valid token the server sets the request's tenant from the token's pin, so you
never pass a tenant id yourself.

Effective access is the **intersection** of four things:

```
user's permissions  AND  granted scopes  AND  pinned tenant  AND  client scope ceiling
```

Scopes only ever *narrow* access. A token can never grant something the user
lacks, reach outside the pinned tenant, or exceed the client's registered ceiling.

**Access errors.** Project-scoped endpoints enforce **project membership** on
every request, not just tenant scoping. If you are authenticated but not a
member of the project you address, the server returns **403**; a project (or
other resource) id that does not exist inside your pinned tenant returns
**404**. On writes, supplying a related id from another project or tenant - for
example a drawing, job, line spec, schema, or assignee that does not belong to
the target project - is rejected with **400** and a field error. Handle all
three so an agent fails clearly instead of silently writing into the wrong
place.

### 3.6 Scopes

Scopes are `area:read` or `area:write`. Safe methods (`GET`/`HEAD`/`OPTIONS`)
require `:read`; all other methods require `:write`. The area is derived from the
first path segment after `/api/`. Unmapped areas are denied (fail-closed).

| Area | Read scope | Write scope |
| --- | --- | --- |
| Tenants | `tenants:read` | _(none - read-only)_ |
| Projects | `projects:read` | `projects:write` |
| Jobs | `jobs:read` | `jobs:write` |
| Packages / ITP | `packages:read` | `packages:write` |
| Drawings & Large Format | `drawings:read` | `drawings:write` |
| Mapping (map items & schemas) | `mapping:read` | `mapping:write` |
| Documents | `documents:read` | `documents:write` |
| Forms | `forms:read` | `forms:write` |
| Notes | `notes:read` | `notes:write` |
| References | `references:read` | `references:write` |
| Photos | `photos:read` | `photos:write` |
| Line specs | `linespecs:read` | `linespecs:write` |
| Shippers | `shippers:read` | _(none - read-only)_ |

> The user can review and revoke any connected app at any time. Authorize and
> revoke actions are audited. Design your MCP server to handle a revoked or
> expired token gracefully (re-run the authorization flow).

---

## 4. Session Configuration: Set the Project Once

Almost every endpoint in this API is **project-scoped** - the project id is part
of the URL path (`/api/.../projects/<project_id>/...`). Forcing a human, or an
LLM acting on their behalf, to repeat the project id on every single call is
tedious and error-prone, and it invites mistakes like writing evidence into the
wrong project.

**Strong recommendation: an MCP server for QC Database should expose a `config`
(or "set project") tool that pins the active project once per user session.**
This is the project-level analogue of tenant pinning. The OAuth token already
pins the **tenant** for the whole session - the user chose it on the consent
screen. A `config` tool extends that idea one level down: the user picks the
**project** once, the server remembers it for that user's session, and every
later tool call defaults to it. The user works "inside" a project the same way
they already work inside a tenant.

### What the config tool should do
- Offer the user the projects available in the pinned tenant (from
  `GET /api/projects/`) and let them choose one.
- Store the chosen `project_id` as **per-user, per-session** state inside the MCP
  server. Never hard-code it, and never share it between users.
- Inject that stored `project_id` automatically into every project-scoped call,
  so other tools - upload a document, list reference requests, create a map item,
  and so on - do not require the caller to pass it each time.
- Let the user view and change the active project at any time, and re-confirm it
  when a session is resumed.

### Treat it as mandatory for everyday use
Because the large majority of endpoints require a project id, a normal
daily-driver MCP server should treat "a project is configured" as a hard
**precondition**. If no project has been set, the server should refuse
project-scoped tools and prompt the user to run `config` first, rather than
guessing a project or failing deep inside an operation. This single rule
eliminates the most common class of mistakes for day-to-day users and is the
strongly recommended default.

### Reasonable exceptions
A few specialized servers legitimately operate above the project level and should
*not* force a single project. A **tenant-admin** server, for example, may list or
compare many projects, audit members, or report across an entire tenant; for
those, treat the project as an optional filter rather than a required pin. For
anything aimed at a normal daily user, project configuration should be required.

---

## 5. Extraction Schemas

Several resource types carry **extracted data** - structured JSON populated from
an uploaded file. An **extraction schema** is the template that defines which
fields the AI should populate. Understanding schemas lets an MCP server either
consume the data QC Database extracted, or **bring its own AI** and supply the
structured data directly.

There are three file-extraction surfaces and one annotation schema:

### Drawings (fixed schema)
Standard drawings (isometrics) use a built-in schema targeting
construction metadata - drawing number, revision, fluid service - plus a detailed
**bill of materials (BOM)**. Discover the active shape via
`GET /api/projects/<project_id>/schemas/drawing/`.

### Large Format Drawings (fixed schema)
LFDs (plans, elevations, P&IDs, overviews) use a built-in schema focused on
spatial/equipment data - tagged equipment, instrumentation, areas/buildings.
Discover it via `GET /api/projects/<project_id>/schemas/large-format-drawing/`.

### Documents (custom, per-folder schema)
Document extraction is **configurable per document folder**. Each folder defines
its own `extraction_schema` (for example, an MTR folder might extract heat
number, material grade, and certificate number). Fields are declared with a
simple `"type - description"` convention. Discover available document schemas via
`GET /api/projects/<project_id>/schemas/documents/`.

### Map Item Schemas
Map items follow a **map item schema** (`/api/mapping/schemata/`) whose
`schema_definition` declares the custom fields for that kind of item (e.g. a Weld
schema with a "Weld ID" field). Schemas can be project-scoped or promoted to the
tenant for reuse, and can drive automatic, per-schema numbering of map items.
Discover usable schemas via `GET /api/projects/<project_id>/schemas/map-items/`.

### Bring Your Own AI
The upload endpoints for documents, drawings, and large format drawings accept a
`do_not_extract` flag. When set, QC Database stores the file and leaves the
record ready with **empty** extracted data and **no** server-side AI run. This is
the recommended path for an MCP server that runs its own extraction: upload with
`do_not_extract=true`, run your model against the schema you discovered, then
write the structured fields back via the resource's update endpoint.

---

## 6. References & Reference Requests - the Heart of Turnover

This is the most important workflow to get right. **References are how a turnover
package becomes complete.** An MCP server that understands references can
meaningfully accelerate a project; one that ignores them only moves files around.

### 6.1 What a reference is

An **Item Reference** links a **source** (the thing that needs proof) to a
**target** (the proof itself):

- **Sources** - the work that must be backed by evidence:
  - a **map item** (e.g. a specific weld or flange on a drawing),
  - a **list item** (e.g. a welder or a heat number on a project list),
  - an **ITP line item** (a required inspection/test activity).
- **Targets** - the evidence that satisfies it:
  - a **document** (e.g. an MTR, NDE report, or certificate),
  - a **form submission** (e.g. a completed hydro test report).

Each reference is one link in a **chain of evidence**. "Forward" references go
from a source to its proof; the same link is visible in reverse from the target,
so a document can show every item it satisfies. A turnover package is *complete*
when every required source item has its supporting references in place.

List and create references under
`/api/references/projects/<project_id>/<source_type>/<source_id>/`
(`references:read` / `references:write`), where `source_type` is one of
`map_item`, `list_item`, or `itp_line_item`. When creating a reference, the
create payload's `target_type` is either `document` or `form` (a form
submission).

### 6.2 What a reference request is

A **Reference Request** is a tracked placeholder for a reference that *should*
exist but doesn't yet - a to-do that says "this item still needs this proof."

- It names what is required: a `reference_type` of either a **document** (tied to
  a document folder) or a **custom form** (tied to a form schema), and the
  `item_type` it applies to (`map_item`, `itp_item`, or `list_item`).
- It carries a `description`, an optional **assignee**, and a `status` of `open`,
  `fulfilled`, or `cancelled`.
- It is **fulfilled** when a matching item reference is created; the request then
  links to the fulfilling reference and flips to `fulfilled`.

Manage requests under
`/api/references/projects/<project_id>/reference-requests/`
(`references:read` / `references:write`).

### 6.3 Why this matters for turnover

Reference requests turn "what's missing?" into a concrete, assignable,
trackable list. The set of **open** reference requests on a project *is* the
punch list standing between the team and a complete turnover package. QC Database
can even **auto-fulfill** requests: when a new document is uploaded or a form is
completed, it compares the new evidence against matching open requests for
supported item types (map items and ITP items) and, on a match, creates the
reference and closes the request automatically. Requests for other item types
(such as list items) may need a reference created manually.

An MCP server should treat reference requests as its primary "definition of
done." Querying open requests, helping satisfy them, and confirming they close
out is the single highest-value thing an integration can do here.

---

## 7. Fillable PDF Templates

Some document folders publish a **fillable PDF template** - a blank PDF form
(a Receiving and Inspection Report (RIR), an inspection checklist, a test
report) that field teams fill out and file as a document. The API exposes the
full fill-and-file loop so a headless or AI client can complete these forms end
to end. All four endpoints live under `/api/documents/` and need
`documents:read` (or `documents:write` to submit), plus membership of the
project.

1. **List templates.**
   `GET /api/documents/projects/<project_id>/fillable-templates/`
   returns the folders that have a template attached, each with the template's
   name and URLs for its fields, blank PDF, and submit endpoint.

2. **Read the fields.**
   `GET /api/documents/projects/<project_id>/fillable-templates/<folder_id>/`
   returns the template's form fields (each `name` plus a `type` of `text`,
   `checkbox`, `radio`, or `dropdown`) and its **autofill** map: the fields the
   folder is configured to pre-fill, each with its `source` (`project.name`,
   `project.code`, `project.client_name`, `project.description`,
   `current_date`, or `current_datetime`) and the resolved `value` for this
   project. Fill those fields with the supplied values and let a human or AI
   complete the rest.

3. **Download the blank PDF.**
   `GET /api/documents/projects/<project_id>/fillable-templates/<folder_id>/download/`
   streams the blank template bytes so you can fill it client-side (or with your
   own PDF tooling) and, ideally, flatten it.

4. **Submit the filled PDF.**
   `POST /api/documents/projects/<project_id>/fillable-templates/<folder_id>/submit/`
   (multipart) stores the filled PDF as a document in the template's folder.
   Send the file as `filled_pdf`, optionally a `file_name` (re-using a name
   creates a new **version** of that document), and `do_not_extract=true` to
   skip server-side AI extraction when you ran your own. Otherwise the folder's
   extraction schema runs in the background, just like a normal upload (see
   Extraction Schemas).

**PDF fields are not the same as extracted JSON fields.** The form fields on the
PDF and the folder's extraction schema are configured independently, so they may
not line up one-to-one - the admin decides which data they want captured as
structured JSON, which can be a subset, a superset, or a reshaping of what the
PDF itself contains. A truly "bring-your-own-AI" MCP server must therefore do
**two** things: upload the **filled PDF** (the human-readable record) *and*
submit the **extracted JSON from that filled PDF** as a separate object (the
document's structured `extracted_data`). Filling the PDF alone does not populate
the structured fields, and supplying JSON alone leaves no signed record.

This is the backbone of AI-assisted form filling: read the fields, fetch the
autofill values, complete the form, and file it as traceable evidence - which
in turn can satisfy a reference request (see References).

---

## 8. Recommended MCP Server Capabilities

This document does **not** prescribe how to build an MCP server. But a genuinely
useful one for QC Database should expose tools (resources/actions) that cover the
following. Map each capability to the endpoints and scopes above, and confirm the
exact contract in [`/api/docs/`](/api/docs/).

**Session configuration (start here)**
- A `config` / "set project" tool that pins the active project once per user
  session (see Section 4). For a normal daily-driver server, treat this as a
  required precondition for every project-scoped tool.
- A way to view and change the active project, and to confirm it on resume.

**Orientation & discovery**
- Resolve the connected tenant and list its projects.
- List members of a project (for assigning requests).
- Discover extraction schemas for drawings, large format drawings, documents,
  and map items.
- List the project's controlled-vocabulary lists and their items, each with an
  embeddable `pseudo_code` pill (`lists:read`).

**Ingestion**
- Upload documents, drawings, and large format drawings to a project - with the
  option to use `do_not_extract` and run the client's own AI.
- Complete fillable PDF templates: list them, read a template's fields and
  autofill values, download the blank PDF, and submit the filled copy (a re-used
  file name creates a new version; `do_not_extract` is supported).
- Attach photos to items.

**Structured data**
- Read extracted data from drawings, LFDs, and documents.
- Write structured fields back (especially after bring-your-own-AI extraction).
- Create and read map items against the correct schema. When a field maps to a
  controlled list, write the list item's `pseudo_code` pill rather than free
  text so the value stays linked to the canonical entry.

**Quality workflow**
- Create, fill, and complete form submissions.
- Create and read notes; assign via @mention; resolve them.
- List ITP line items and map items and report their completion/acceptance
  status. Buy-off (`mark-complete` / `mark-accepted` on both ITP line items and
  map items) records the acting user and time server-side; surface it as a
  human-confirmed action rather than performing it automatically.

**Turnover (highest value)**
- List **open reference requests** for a project ("what's missing").
- Create item references to satisfy requests (link a source to its proof).
- Create new reference requests when a gap is identified, with an assignee.
- Report turnover readiness: open vs. fulfilled requests, items lacking proof,
  ITP items not yet complete or accepted.

**Good citizenship**
- Request the narrowest scopes needed.
- Handle revoked/expired tokens by re-authorizing.
- Completion, acceptance, and sign-off are **the user's** actions. An MCP server
  **may** mark items complete or accepted on a user's behalf, but only with
  **clear, explicit communication that the user is responsible for every action
  the MCP server takes for them** - including buy-offs. Prepare and report by
  default, and never buy off work silently or without the user's informed
  go-ahead.

---

## 9. An Ideal Workflow

A representative end-to-end flow an MCP server might orchestrate, from connection
to a more complete turnover package:

1. **Connect.** Register a client (`/oauth/register/`), run the
   Authorization Code + PKCE flow (`/oauth/authorize/` -> `/oauth/token/`); the
   user picks the tenant on the consent screen, pinning the token.
2. **Set the project.** Read `/api/tenants/` and `/api/projects/`, let the user
   pick the project to work in, and pin it once via the server's `config` tool
   (see Section 4) so every later call defaults to it. For a daily-driver server,
   require this before any project-scoped tool runs.
3. **Orient.** Read the project's members, the relevant extraction schemas, and
   the project's controlled-vocabulary lists - so you can reference list values
   by their `pseudo_code` pill (see Section 2).
4. **Ingest source work.** Upload drawings and large format drawings; create or
   read the map items that represent the welds, flanges, and fittings to be
   inspected. When a map item field maps to a controlled list (welder, weld
   type, material), embed that list item's `pseudo_code` pill instead of typing
   the value, so it stays linked to the canonical list entry.
5. **Find the gaps.** List the project's **open reference requests** - the
   evidence still owed for those items and ITP activities.
6. **Provide the evidence.** Upload the missing documents (MTRs, NDE reports,
   certificates), complete the required forms, or fill a folder's **fillable PDF
   template** (read its fields and project-resolved autofill values, fill it,
   and submit it back as a versioned document - see Section 7). Use
   `do_not_extract` plus your own extraction when you want to control the AI;
   otherwise let QC Database extract against the folder schema.
7. **Close the loop.** Create the item references that link each new document or
   form back to its source item - or let auto-fulfillment match them - so each
   open request flips to `fulfilled`.
8. **Report readiness.** Summarize what remains: open reference requests by
   assignee, source items still lacking proof, and ITP line items not yet
   completed or accepted. This is the live picture of how close the project is to
   a complete, traceable turnover package.
9. **Audit on request.** An MCP server and its AI agent make a valuable
   **auditing assistant** - cross-checking extracted data against source
   documents, finding references that don't match their proof, spotting missing
   or inconsistent records, and verifying line-spec requirements are met. Perform
   any audit the user asks for and report the findings for their review.

Throughout, every artifact stays in its original form and every AI step remains
auditable - the system always "shows its math." An MCP server should preserve
that principle: accelerate the paperwork, but keep humans in command of buy-off
and accountability.

---

## 10. Where to Go Next

- **Interactive API reference:** [`/api/docs/`](/api/docs/) (Swagger),
  [`/api/redoc/`](/api/redoc/) (ReDoc)
- **Machine-readable schema:** [`/api/schema/`](/api/schema/)
- **OAuth discovery:** `/.well-known/oauth-authorization-server`,
  `/.well-known/oauth-protected-resource`
- **Dynamic client registration:** `POST /oauth/register/`

Use the endpoint paths and scopes in this guide as a map, and the API
documentation as the authoritative, always-current detail.
