PAKT MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@PAKT MCP Serverlist my contracts and flag high-risk clauses"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
PAKT
PAKT reads contracts - most of them in Azerbaijani, as PDFs or scans - and tells you where they hurt. It splits a contract into clauses and scores each one from our side of the deal as high, medium or low risk, with who the clause favours and why. Then it writes an overall verdict: the top risks and the asymmetries between the parties, which you can also listen to. For an amendment it finds every conflict with the base contract: what it contradicts, modifies, adds, or changes in our favour.
Ingestion: PDF text extraction, and OCR for scanned pages and images, in Azerbaijani, Russian and English. The text is then split into numbered clauses.
Analysis: LangChain chains over four LLM providers with automatic fallback. Every result records which provider, model and prompt version produced it.
Web app: companies, contracts and amendments in Azerbaijani, English and Russian, in a day and a night theme. It has a public landing page at
/, and the app is under/app/.Claude Desktop: an MCP server lets Claude list, read, analyse and compare your contracts.
Contract text, explanations and verdicts stay in the document's language; only the interface is translated.
Architecture
flowchart LR
browser[Browser] -->|HTTPS| funnel[Tailscale Funnel<br>:443]
claude[Claude Desktop<br>+ MCP server] -->|HTTPS| funnel
funnel -->|127.0.0.1:8000| api
subgraph server[This server - Docker Compose]
api[FastAPI<br>API + web UI + background jobs]
api --> db[(PostgreSQL)]
api --> files[(uploads/)]
n8n[n8n<br>internal only]
end
api -->|LCEL, with_fallbacks| llm[Groq → Gemini → Cerebras → NVIDIA]
api -->|verdict audio| tts[fish.audio]One FastAPI process serves the API and the static web UI, and runs the background jobs:
Upload. A document is stored under
uploads/<user>/and the request returns 202.Extraction. pypdf reads the text layer; pages without one go through pytesseract OCR (
aze+rus+eng). The text is split into clauses, or an amendment into items with the clause numbers they name.Analysis.
Clauses: scored in a batch through one LangChain LCEL chain per provider, joined with
with_fallbacks(). The chains pace themselves under Groq's free-tier token limit and retry what failed everywhere.Verdict: written from all the clauses, and streamed to the page over SSE.
Amendments: checked item by item against the base contract.
Status. The pages poll until each stage is done or failed. A restart fails whatever was left processing, so nothing hangs forever.
Every contract endpoint is scoped to the signed-in user (JWT). The MCP server is a client of the same HTTP API, so it sees only what its account sees.
Where | What |
| HTTP endpoints |
| extraction, clause splitting, the pipeline, storage, security |
| the LLM chains and their versioned prompt files |
| the web UI (vanilla HTML, CSS and JS) and the landing page |
| the MCP server for Claude Desktop |
| Alembic migrations, one per schema change |
| the evaluation script and its results |
| the demo seed and the MCP token |
| pytest; fake LLM providers, never a real API |
Related MCP server: ClauseLens MCP
Running it locally
Requirements: Docker with the Compose plugin.
Create your env file and fill in the secrets:
cp .env.example .envSet
POSTGRES_PASSWORD(and the same password insideDATABASE_URL),JWT_SECRET_KEYandN8N_ENCRYPTION_KEY. None of them has a default: Docker Compose refuses to start whilePOSTGRES_PASSWORDorN8N_ENCRYPTION_KEYis empty, and the API refuses to start unlessJWT_SECRET_KEYis at least 32 characters. Add at least one LLM provider key. Ifid -u/id -garen't1000, setHOST_UID/HOST_GIDtoo.Start the development stack and create the tables:
docker compose up -d --build docker compose exec api alembic upgrade headOpen:
The landing page: http://localhost:8000/ (the app is under
/app/)API docs: http://localhost:8000/docs
Health check: http://localhost:8000/health
The development api container mounts the source and reloads on change. Host ports are
set in .env (API_PORT, POSTGRES_PORT, N8N_PORT), on 127.0.0.1 only.
Everyday commands
docker compose logs -f api # follow API logs
docker compose exec api alembic revision --autogenerate -m "describe change"
docker compose exec api alembic upgrade head
docker compose exec api pytest # run tests (uses a separate pakt_test database)
docker compose exec api python -m eval.run_eval --provider all groq gemini nvidia
# score the LLM analysis against samples/expected.yaml
# (real API calls; results go to eval/results/)
docker compose exec api python -m scripts.seed_demo --email you@example.com
# demo companies and contracts, no LLM calls
docker compose down # stop (add -v to also wipe the database and n8n data)To add a dependency, run uv add <package> on the host (install uv from
https://docs.astral.sh/uv/), then rebuild with docker compose up -d --build api.
Deploying on this server (Tailscale Funnel)
Production runs docker-compose.prod.yml, which differs from the development setup:
Restarts: every service restarts itself, after a crash and after a reboot.
API: it runs from its image, with no source mount and no reload. Its entrypoint runs the migrations before the server starts.
Ports: only the API is published, on
127.0.0.1:8000. Postgres and n8n have no published port at all.Exposure: Tailscale Funnel puts that one port on the internet at
https://<machine>.<tailnet>.ts.net- herehttps://monoblock.tail1957ed.ts.net.
Both compose files use the same project name, database and uploads/, one at a time.
1. Start the production stack
# in .env: the Funnel URL, the only origin CORS allows
PUBLIC_BASE_URL=https://monoblock.tail1957ed.ts.net
docker compose -f docker-compose.prod.yml up -d --build
docker compose -f docker-compose.prod.yml ps # all three up; only api has a port
curl -s http://127.0.0.1:8000/health # {"status":"ok","database":"ok"}2. In the Tailscale admin console (once)
At https://login.tailscale.com/admin:
DNS: keep MagicDNS on, and under HTTPS Certificates click Enable HTTPS. Funnel serves a certificate for the machine's
ts.netname.Access controls: allow Funnel. In the policy file, add a
nodeAttrsentry granting thefunnelattribute, and save:"nodeAttrs": [ { "target": ["autogroup:member"], "attr": ["funnel"] } ]To allow only this machine, target its tag or its address instead of every member. If this is missing,
tailscale funnelprints a link to the page that adds it.
3. Turn on Funnel, on the server
sudo tailscale set --operator=$USER # once: lets your user run serve and funnel
tailscale funnel --bg 8000 # https://monoblock.tail1957ed.ts.net → 127.0.0.1:8000
tailscale funnel status--bg keeps the Funnel running in tailscaled, across reboots. It forwards port 443 to
the API only: Postgres and n8n aren't published, so nothing can forward to them. To
take the site off the internet:
tailscale funnel resetCheck from outside the tailnet, for example a phone on mobile data. The landing page
should open, and curl -I https://monoblock.tail1957ed.ts.net/ should show the
security headers.
After a reboot
Docker is enabled at boot (
systemctl is-enabled docker), and so is tailscaled, which brings the Funnel back.The containers restart themselves (
restart: unless-stopped).The API migrates on start, and marks any document a previous run left processing as failed. Its page offers to upload it again or re-run the analysis.
Operating it
docker compose -f docker-compose.prod.yml logs -f api # logs (rotated, 5 × 10 MB per service)
git pull && docker compose -f docker-compose.prod.yml up -d --build # update; migrations run on start
docker compose -f docker-compose.prod.yml exec postgres \
sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > "pakt-$(date +%F).sql" # back up the databasen8n has no published port. To open its editor for a while, forward it to the server's localhost, and stop with Ctrl-C:
docker run --rm -p 127.0.0.1:5678:5678 --network pakt_default alpine/socat \
tcp-listen:5678,fork,reuseaddr tcp-connect:n8n:5678Then open http://localhost:5678 on the server, or ssh -L 5678:127.0.0.1:5678 <server>
from your computer. To go back to development, run docker compose up -d --build.
What is in place before exposing it
Access: every contract, company and amendment endpoint is scoped to the signed-in user.
Rate limits: per client address, 10 sign-in attempts per 5 minutes and 5 registrations per hour. Beyond them the API answers 429 with
Retry-After. The limits are set in.env.Upload size: 20 MB per file, 21 MB per request. A larger body gets 413 before it is read.
CORS: only
PUBLIC_BASE_URLis allowed.Headers: every response carries a content security policy (the app itself plus the Google Fonts hosts),
nosniff,X-Frame-Options: DENY, a referrer policy, a permissions policy and COOP. HSTS is added over HTTPS.Network: only the API is published, on 127.0.0.1. Postgres and n8n are reachable from the compose network only.
Secrets: they live in
.env, which is never committed. The logs carry counts, provider names and error types. A scan of the API's logs found none of the secret values from.env, and no tokens.
Evaluation
eval/run_eval.py runs the real pipeline - extraction, the clause chain, the conflict
chain - on the samples in samples/ and scores it against samples/expected.yaml:
30 clauses: 19 in the lease and 11 in the scanned IT services contract. 13 of them are expected red.
The sample amendment, whose six expected conflicts are checked.
Measured on 2026-09-11, prompt set v1 (eval/results/2026-09-11_comparison.md):
Run | Model | Risk accuracy | Red recall | False alarms | Favours accuracy | Conflicts found | Extra | Failed calls | Avg latency | Wall time |
Full chain | Groq → Gemini → NVIDIA | 27/30 (90%) | 11/13 (85%) | 0 | 28/30 (93%) | 5/6 | 1 | 0 | 1477 ms | 198 s |
Groq | openai/gpt-oss-120b | 24/30 (80%) | 10/13 (77%) | 0 | 25/30 (83%) | 5/6 | 1 | 2 | 679 ms | 193 s |
NVIDIA | deepseek-ai/deepseek-v4-flash-0731 | 24/30 (80%) | 11/13 (85%) | 0 | 23/30 (77%) | 4/6 | 0 | 5 | 2894 ms | 547 s |
Gemini | gemini-3.5-flash | 0/30 | 0/13 | 0 | 0/30 | 0/6 | 0 | 36 | - | 249 s |
Cerebras | not configured (the free tier had no model access) |
How the columns are counted:
Risk accuracy: a clause counts as right when its level matches, and where yellow is expected, red also counts.
Red recall: expected-red clauses found red.
False alarms: red given where green was expected.
Conflicts found: expected conflicts matched by item, clause and type. Extra is conflicts beyond them.
The full chain beats every provider alone: a call that fails or returns malformed JSON falls through to the next provider (Groq answered 23 clauses, NVIDIA 7). No clause was left unscored. Every Gemini call failed during that run, so it contributed nothing. Wall times are dominated by free-tier rate limits.
Using PAKT from Claude Desktop (MCP)
app/mcp_server.py makes PAKT an MCP server with four tools:
list_contracts- your contracts, filtered by company or statusget_contract- one contract's overview: details, verdict, and every clause's riskanalyze_contract- upload a document (new contract or existing one) and run the pipelinecompare_amendment- an amendment's conflicts with its base contract, uploading it first if asked
It calls the PAKT API as your account, so it sees exactly what you see in the web app.
Write a token for your account into
.env. It is never printed, and is valid for 90 days unless you pass another--days:# production docker compose -f docker-compose.prod.yml run --rm -v "$PWD/.env:/app/.env" api \ python -m scripts.mcp_token --email you@example.com --days 90 # development docker compose exec api python -m scripts.mcp_token --email you@example.com --days 90The computer running Claude Desktop needs uv and a checkout of this repository whose
.envhasPAKT_API_URLandPAKT_API_TOKEN:On this server: that is the
.envfrom step 1, andPAKT_API_URLdefaults tohttp://127.0.0.1:8000.On another machine: set
PAKT_API_URL=https://monoblock.tail1957ed.ts.net, and copy thePAKT_API_TOKENline over from the server's.env.
In Claude Desktop open Settings → Developer → Edit Config, which opens
claude_desktop_config.json(~/Library/Application Support/Claude/on macOS,%APPDATA%\Claude\on Windows), and add:{ "mcpServers": { "pakt": { "command": "uv", "args": ["run", "--directory", "/absolute/path/to/pakt", "python", "-m", "app.mcp_server"] } } }On Windows, write the path as
C:\\Users\\you\\pakt. If Claude Desktop can't finduv, put its full path (which uv) incommand. The first start takes a minute while uv installs the dependencies.Restart Claude Desktop. PAKT's tools appear in the tools menu; ask, for example, "Which of my contracts have red clauses?" or "Compare the amendments of contract 14 with the contract."
File paths you give analyze_contract and compare_amendment are paths on the
computer running Claude Desktop. The token acts as your account until it expires:
writing a new one doesn't withdraw the old one, but changing JWT_SECRET_KEY
withdraws every token at once.
Articles of law
Under a clause's explanation, the text view shows the articles of law the clause relates
to ("Əlaqəli qanun maddəsi"): the article's number and title, the start of its official
text, and a link to the source. The links come from keyword rules in code
(app/services/law_links.py), with no LLM call:
a penalty clause (dəbbə) links to the penalty articles
a termination clause (xitam) links to the termination article
lease, services and obligation clauses link to theirs
A clause may link to none. The links are stored when a contract's clauses are made, and made again for every clause whenever the articles are loaded.
The articles are in
app/data/law/civil_code.yaml: number, title, topics, source address and text. All 10 articles have their official text. PAKT never writes article text: it is pasted into each article'stextfield by hand. An article whose text is empty stays linked to its clauses but isn't shown until its text is filled in.Loading them, and linking every clause again, runs on each start of the production API, or by hand:
docker compose exec api python -m scripts.law_articlesA monthly sync is written but not running:
n8n/workflows/law-articles-sync.json(import it in n8n) would update the text of the articles in the table, in place.It ships without a source URL, because it needs a permitted source. e-qanun.az serves its documents only to its own pages and answers 403 to direct requests.
Permission first: use it only with written permission from e-qanun.az (the Ministry of Justice) or with a licensed legal database. Never work around their check.
Future work
An httpOnly cookie for the JWT. The web UI keeps the token in
localStorage, where any script running on the page could read it. An httpOnly, Secure, SameSite cookie set by the API would keep it out of scripts' reach, at the cost of CSRF protection.Paid LLM tiers for speed. On the free tiers, Groq's 8000 tokens a minute paces the clause analysis, so a 19-clause lease takes minutes. With paid limits the batch could run at full concurrency and finish in seconds.
Per-clause progress. The processing panel shows the stages: upload, extraction, analysis. The pipeline already counts "clause 7 of 19" as results come in; streaming that to the page would show how far along it is.
Law database integration. Clauses already link to articles of law by keyword rules. Next steps:
a permitted sync source to keep the articles' texts current
cover the laws on leases and services beyond the Civil Code
say in the explanation where a clause departs from the article it links to
A summary endpoint for the context column. The wide-screen context column fetches the clauses of every analysed contract, one request each. A single endpoint with each contract's risk counts and riskiest clauses would keep that to one request as the number of contracts grows.
Available Tools
4 toolsanalyze_contractA
Upload a contract document and run PAKT's pipeline on it: text extraction (OCR for scans), clause splitting, the risk of every clause and the verdict. Give contract_id to replace an existing contract's document, or company, title, contract_number, start_date and our_role to create a new contract. Waits for the analysis - usually a minute or two - and returns the same overview as get_contract.
| Name | Required | Description | Default |
|---|---|---|---|
| title | No | For a new contract: its title. | |
| company | No | For a new contract: its company, by name (it must exist in PAKT). | |
| our_role | No | The party we are, as the contract names it, e.g. İcarəçi or Sifarişçi. Risk is scored from this side. | |
| file_path | Yes | The contract document on this computer: PDF, PNG or JPEG, at most 20 MB. | |
| start_date | No | For a new contract: its start date, YYYY-MM-DD. | |
| contract_id | No | An existing contract whose document this replaces. | |
| wait_seconds | No | How long to wait for the analysis. | |
| contract_number | No | For a new contract: its number. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden and does disclose useful behavior: OCR for scans, clause splitting, per-clause risk, a synchronous wait of about one to two minutes, and output equivalence to get_contract. However, it does not state whether replacing a document is reversible, what permissions are required, or what happens on failure, which are important for a mutating, long-running tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the core action and pipeline, then the two usage modes, then timing and return behavior. No filler; every sentence carries useful information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex, synchronous, mutating tool with an output schema present, the description covers the pipeline, timing, and create-vs-replace modes. It omits some operational details such as failure behavior and prerequisite validation, but those gaps are relatively small given the output schema and detailed parameter schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already explains each parameter. The description adds useful grouping by distinguishing the contract_id replacement mode from the new-contract creation fields, but it does not add syntax, constraints, or semantics beyond what the schema already provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource: uploading a contract and running PAKT's analysis pipeline. It names the processing steps and tells the agent the return is equivalent to get_contract, making its purpose clear relative to at least one sibling.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly explains the two usage modes: pass contract_id to replace an existing document, or pass company/title/contract_number/start_date/our_role to create a new contract. It gives clear context but does not explicitly state when-not to use this tool or name alternative siblings like compare_amendment.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
compare_amendmentA
The conflicts between a contract's amendments and the contract itself: for each amendment item, the base clauses it contradicts, modifies, adds or changes in our favour (benefits), how serious each is (red, yellow, green) and why - with counts of conflicts against us and in our favour. Give file_path and signed_date to upload a new amendment and wait for its analysis; amendment_id for one amendment; neither for all of the contract's amendments.
| Name | Required | Description | Default |
|---|---|---|---|
| title | No | With file_path: its title (the file name by default). | |
| file_path | No | A new amendment to upload first: PDF, PNG or JPEG on this computer. | |
| contract_id | Yes | The base contract's id. | |
| signed_date | No | With file_path: the date the amendment was signed, YYYY-MM-DD. | |
| amendment_id | No | Only this amendment. | |
| wait_seconds | No | How long to wait for the analysis. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden. It does disclose that uploading triggers an analysis that must be waited on and returns conflict counts by severity, but it omits permissions/auth requirements, whether uploading an amendment persists or modifies data, and any rate or failure behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The output content is front-loaded in the first sentence and the parameter modes follow. It is dense but every clause carries information; the second sentence is long but functional. No filler sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Six parameters with one required and an output schema that already covers return values, so the description need not detail results. It correctly covers the main invocation modes, but slightly over-describes outputs it didn't need to and leaves wait_seconds/analysis-timing behavior to the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. The description goes beyond the per-parameter schema text by tying file_path, signed_date and amendment_id into mutually exclusive modes ('neither for all of the contract's amendments'), which adds real meaning about how the parameters combine. It does not, however, explain wait_seconds semantics beyond what the schema already says.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb+resource: it compares a contract's amendments against the base contract and returns conflicts categorized by base clause, direction (against us / in our favour) and severity (red/yellow/green). It is clear what the tool does, but it never mentions the sibling analyze_contract, so differentiation from neighbors is left to inference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit operational routing: 'Give file_path and signed_date to upload a new amendment and wait for its analysis; amendment_id for one amendment; neither for all of the contract's amendments.' That is clear guidance on invocation modes, though it offers no when-to-use-vs-analyze_contract guidance or prerequisite/exclusion conditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_contractC
One contract's overview: its details, the verdict (summary, top risks, asymmetries), how many clauses are red, yellow and green, every clause's risk level with who it favours and why, and its amendments with their conflict counts.
| Name | Required | Description | Default |
|---|---|---|---|
| contract_id | Yes | The contract's id, from list_contracts. | |
| include_text | No | Also return each clause's full text. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral burden, yet it only enumerates returned fields and says nothing about whether this is a safe read, whether it is expensive, what authorization it needs, or how large the payload can get. The scoping to a single contract is implied but never stated as a behavioral constraint.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single dense sentence front-loads the resource ('One contract's overview') before listing contents. Nothing is wasted, though the long comma/chained enumeration makes it slightly harder to scan than a two-sentence split would be.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so the heavy focus on returned fields is partly redundant, and the description leaves usage and sibling selection entirely unaddressed for a tool whose siblings overlap in purpose. It is adequate — the agent knows what it gets back — but incomplete on when to prefer it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% with only two parameters, so contract_id's provenance ('from list_contracts') and include_text's effect ('also return each clause's full text') are already documented in the schema. The description adds nothing about either parameter, which is acceptable at full coverage but not above baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names the specific resource ('One contract's overview') and enumerates exactly what comes back: details, verdict components, clause color counts, per-clause risk level, and amendments with conflict counts. It is clear this fetches a single contract, distinguishing it implicitly from list_contracts, but it never explicitly contrasts itself with siblings like analyze_contract or compare_amendment.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no statement of when to call this versus analyze_contract or list_contracts, nor any prerequisite or exclusion. The only procedural hint is buried in the schema (contract_id 'from list_contracts'), not the description. An agent must infer usage entirely from the sibling names.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_contractsA
List the account's contracts: company, status, dates, our role, and whether the document is in and analysed. Use get_contract for one contract's risks.
| Name | Required | Description | Default |
|---|---|---|---|
| status | No | Only contracts in this status. | |
| company | No | Only this company's contracts: part of its name, in any case. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden, and it discloses almost nothing beyond the fact that this is a read/list operation. There is no mention of ordering, pagination, result limits, or any permission requirements, which matters for an account-wide listing.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the action and scope, and the routing hint comes last. The enumeration of returned fields is slightly redundant given an output schema exists, but it costs little.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, and both optional filters are covered by the schema. The remaining gap is operational behavior (ordering, pagination/limits) that an agent listing an account's contracts would reasonably want.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both filter parameters (status, company) are already fully documented in the schema; the description only alludes to them in passing. Per the baseline rule, a 3 is appropriate when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the account's contracts') plus the scope of the listing (account-wide, with the fields it surfaces). It also names the sibling it is not, so an agent can separate it from get_contract without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes single-contract risk lookups to get_contract, giving a clear use/use-other split. It stops short of covering the other siblings (analyze_contract, compare_amendment) or stating when a listing is the wrong choice.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.0- First observed
analyze_contract - First observed
compare_amendment - First observed
get_contract - First observed
list_contracts
TDQS
Scored across 4 tools
list_contracts, get_contract, analyze_contract, and compare_amendment each target a distinct action and resource. The only mild overlap is that analyze_contract returns 'the same overview as get_contract,' which could blur read vs. upload boundaries, but their primary intents remain clear.
All four tools follow a strict verb_noun snake_case pattern: list_contracts, get_contract, analyze_contract, compare_amendment. Consistent and predictable throughout.
Four tools is a tight, focused set for a contract-risk analysis server. It sits at the low end of the ideal range but each tool earns its place and none is redundant.
Core workflows (list, read overview, upload/analyze, compare amendments with create-or-replace logic) are covered. Gaps remain around deleting contracts, updating metadata, and standalone amendment listing, but these are minor workarounds rather than dead ends.
Maintenance
Related MCP Connectors
- platform7nOAuthtech.p7n
Connect Claude to your Platform7n workspaces — chat, links, and tasks. One-click OAuth.
ContractOracle - 10 contract analysis tools: clause extraction, redlines, DORA mappings.
- n3tz AtlasOAuthai.n3tz
Governed company knowledge over MCP. Access by contract for authorized tenants.
AI Visibility and Content Intelligence tools for Claude and MCP-compatible agents.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables users to create, manage, and track electronic signing requests and templates through Claude Desktop and other MCP-compatible clients. Provides multi-tenant authentication with customer API keys for secure document workflow operations.66 npm-
- AlicenseNot gradedqualityDmaintenanceEnables Claude or Cursor to act as a contract analyst by providing tools for document fetching, clause segmentation, span verification, and risk taxonomy lookup, with no server-side LLM calls or document retention.MIT

BitsBound MCP Serverofficial
AlicenseAqualityDmaintenanceEnables AI-powered contract analysis with partner-level redlines and real OOXML Track Changes for Claude Desktop and Claude.ai.12207 npmMIT- AlicenseAqualityCmaintenanceMCP server that gives Claude safety-gated access to SpotDraft contracts, enabling querying and managing contracts, templates, counterparties, obligations, and analytics through natural language.19MIT