AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Contract Question Agent

mcp-mofuteq-contract-question-agent · by mofuteq

Contract clause verification-question generator using CUAD and typed LLM workflows.

No reviews yet
0 installs
24 views
0.0% view→install

Install

$ agentstack add mcp-mofuteq-contract-question-agent

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-mofuteq-contract-question-agent)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Contract Question Agent? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

contract-question-agent

contract-question-agent is a small open-source contract verification-question generator.

It is built for the moment when a contract clause feels risky, but the reviewer cannot yet say exactly why.

Instead of answering whether a clause is legal, enforceable, fair, risky, or safe to sign, it turns that vague concern into structured verification questions a human reviewer can raise before relying on the clause.

In that sense, it treats contract review as an information asymmetry problem: the drafter may understand the clause, edge cases, and business implications better than the counter-party reviewing it.

It also refuses to force generation when the input is outside the contract verification-question task.

Generated verification questions are prompts for further investigation. They must be reviewed with a qualified legal professional before anyone relies on them.

Thesis

Agent frameworks are useful, but they should not become the architecture.

The important part is not which framework owns the agent.

The important part is whether each responsibility has a visible boundary.

This repository is an experiment in separating orchestration, model execution, schema contracts, controlled context, skill constraints, reflection, and human-facing observability so that agent failures become debuggable state transitions instead of vague outputs.

In practice:

  • LangGraph owns workflow orchestration and routing.
  • Pydantic AI currently implements the structured model-execution boundary.
  • Schemas define what can cross each boundary.
  • MCP provides system-controlled candidate context, not autonomous tool use.
  • Skill files constrain what the model is trying to do.
  • Reflection checks whether the output follows that thesis.
  • AG-UI and Streamlit expose workflow state to a human.

The goal is not to make the agent more autonomous.

The goal is to make it easier to inspect, constrain, and repair.

Status

This repository is at a v1.0.0 release-candidate stage.

It includes:

  • CUAD data preparation
  • CLI generation
  • LangGraph workflow orchestration
  • two-step scope checking
  • Pydantic AI / OpenRouter structured model calls
  • MCP candidate review lenses
  • skill-thesis reflection
  • FastAPI and AG-UI event stream
  • Streamlit run viewer
  • synthetic in-scope and out-of-scope examples

Design principles

The design keeps the economic framing intentionally narrow:

  • Information asymmetry: the reviewer has less information than the

drafter, and targeted questions are a low-cost way to narrow that gap.

  • Screening and signaling: questions help a reviewer screen for

terms that warrant deeper attention and give the drafter a channel to signal intent.

  • Principal-agent problems: the reviewer's interests and the

drafter's interests are not always aligned, and explicit questions make that alignment testable.

  • Loss aversion: reviewers tend to weight downside outcomes heavily,

so surfacing unknowns up front reduces the cost of acting on them later.

Those ideas map directly to runtime boundaries:

| Principle | Runtime behavior | Where to see it | |---|---|---| | Do not answer legal questions | Outputs verification questions, not legal conclusions | VerificationQuestionOutput, examples/in_scope_non_compete.md | | Do not force generation | Out-of-scope input stops before generation and reflection | PRECHECK_INPUT, CLASSIFY_SCOPE, examples/out_of_scope_non_contract_text.md | | Keep workflow state visible | AG-UI emits node-level status events | Streamlit node timeline | | Keep responsibilities separate | LangGraph handles workflow; Pydantic AI is used as the current structured model-execution boundary | workflows/workflow.py, model_client/openrouter.py | | Keep external knowledge bounded | MCP provides candidate review lenses, not autonomous tool use | mcp/, selected_review_lenses |

Architecture and observability

LangGraph:
  workflow orchestration / state transitions / routing

PRECHECK_INPUT:
  deterministic cheap input validation before any LLM call

CLASSIFY_SCOPE:
  LLM-backed structured task-scope classification

Pydantic AI:
  node-internal structured model calls / classification / generation / reflection

MCP:
  deterministic candidate review lenses

FastAPI:
  thin HTTP boundary

AG-UI SSE:
  human-facing run event stream

Streamlit:
  minimal run/output viewer

Langfuse:
  optional tracing / Agent Graph / business-node spans / generation usage

CI:
  GitHub Actions with uv-managed Python 3.13.13

Layout

data/cuad/
  raw/            # downloaded CUAD payload (gitignored)
  processed/      # contracts.jsonl, labels.jsonl, clause_spans.jsonl
docs/
  data.md         # data provenance, license, limitations
examples/
  in_scope_non_compete.md
  out_of_scope_non_contract_text.md
src/contract_question_agent/
  cuad_downloader.py  # optional downloader
  cuad_loader.py      # parser + JSONL writer
  cli_generate_questions.py
  api/                # thin FastAPI HTTP adapter
  model_client/        # Pydantic AI structured model-calling path
  prompts/             # model instructions
  skills/              # contract verification-question skill thesis
  mcp/                 # candidate review-lens server
  workflows/
    workflow.py        # LangGraph orchestration / state transitions
    tracing.py         # optional Langfuse tracing helpers
    nodes/             # framework-independent business node transitions
viewer/
  streamlit_app.py
  sse_client.py
tests/
  test_cuad_downloader.py
  test_cuad_loader.py
  test_generate_questions_cli.py
  test_workflow.py
  test_pydantic_ai_client.py
  test_safety.py
  test_schemas.py

Quick start

This project uses uv and a pinned .python-version to make local development reproducible with Python 3.13.13. uv sync installs the matching interpreter (no prior install needed), creates .venv/, and resolves all dependencies from the committed uv.lock.

# Set up the environment (Python 3.13.13 + locked dependencies).
uv sync

# Run the test suite.
uv run pytest

# 1. Download CUAD_v1.json (Hugging Face is the default source).
uv run cuad-downloader --source huggingface
# Writes data/cuad/raw/CUAD_v1.json by default.

# 2. Process it into JSONL filtered to the evaluation clause types.
uv run cuad-loader \
  --input data/cuad/raw/CUAD_v1.json \
  --output-dir data/cuad/processed

# 3. Run the minimal E2E generator without network access.
uv run contract-question-generate \
  --input data/cuad/processed/clause_spans.jsonl \
  --clause-type "Non-Compete" \
  --limit 3 \
  --dry-run

Zenodo is also supported as an alternative source:

uv run cuad-downloader --source zenodo
# Writes data/cuad/raw/CUAD_v1.zip by default. The loader reads .zip
# directly, so you can pass it straight to --input without unzipping.

The downloader is optional — if you already have CUAD_v1.json (or the zip archive) on disk, place it under data/cuad/raw/ and skip step 1.

See [docs/data.md](docs/data.md) for licensing and attribution requirements.

Minimal E2E generation

Workflow

The generator runs a deliberately linear LangGraph workflow:

LOAD_CLAUSE_SPANS
-> FILTER_RECORDS
-> PRECHECK_INPUT
-> CLASSIFY_SCOPE
-> GENERATE_MINIMAL_QUESTIONS
-> REFLECT_AGAINST_SKILL_THESIS
-> SAFETY_CHECK
-> WRITE_OUTPUT

PRECHECK_INPUT is deterministic. CLASSIFY_SCOPE is LLM-backed and returns a structured task-scope classification through the model-client boundary. Out-of-scope inputs stop before generation and reflection.

CLI usage

Record loading and filtering are deterministic and use only CLI arguments:

uv run contract-question-generate \
  --input data/cuad/processed/clause_spans.jsonl \
  --clause-type "Non-Compete" \
  --contract-id SOME_CONTRACT_ID \
  --limit 3 \
  --offset 0 \
  --dry-run

For real model calls, set OPENROUTER_API_KEY and omit --dry-run. OPENROUTER_MODEL is optional; --model overrides the environment and default.

Recommended local setup:

cp .env.example .env
# Edit .env and set OPENROUTER_API_KEY.
# .env is gitignored and must not be committed.

Alternative one-shell setup:

export OPENROUTER_API_KEY="..."
export OPENROUTER_MODEL="google/gemini-3-flash-preview"

Then run:

uv run contract-question-generate \
  --input data/cuad/processed/clause_spans.jsonl \
  --clause-type "Non-Compete" \
  --limit 3

Model runtime

The default OpenRouter model is configured in src/contract_question_agent/model_client/openrouter.py and can be overridden with OPENROUTER_MODEL or --model. OPENROUTER_API_KEY is required for real model calls; OPENROUTER_MODEL is optional. Tests use fake clients and do not call the network.

Pydantic AI is used as the current structured model-execution boundary for node-internal model calls and Pydantic output validation. LangGraph handles workflow orchestration.

MAF was removed because the project does not need a universal Agent framework. The workflow boundary is implemented with LangGraph, while Pydantic AI is used only as a structured model-call component inside classification, generation, and reflection nodes.

The workflow calls model_client.classify_scope() once per record that passes deterministic precheck, then calls model_client.generate() once per classified in-scope clause span. If --limit 1 produces one output row but OpenRouter or provider logs show additional upstream requests, the extra request is usually classification, reflection, or provider structured-output behavior, not a duplicate LangGraph generation node.

Scope classification

Scope checking is split into two visible workflow responsibilities:

  • PRECHECK_INPUT is deterministic and cheap. It rejects clearly invalid input

such as empty text, missing clause_type, very short text, or obviously malformed text before any LLM call.

  • CLASSIFY_SCOPE is an LLM-backed structured classifier behind the existing

model_client boundary. It decides only whether the text is a reasonable input for generating contract verification questions for the given clause_type.

The scope classifier does not judge legality, enforceability, fairness, risk, validity, or whether anyone should sign. Out-of-scope inputs stop before generation and reflection, then still write normal metadata and an empty output file.

Outputs and metadata

Each run creates a fresh directory under data/cuad/runs// by default. The timestamp run id uses local time in YYYYMMDD-HHMMSS format. Inside the run directory:

verification_questions.jsonl
run_metadata.json
run.log

verification_questions.jsonl contains the structured outputs. run_metadata.json records the run settings, scope counts, scope reasons, and row-count metrics: rows_read, rows_filtered, rows_in_scope, rows_out_of_scope, rows_generated, scope_status_counts, out_of_scope_reasons, scope_results, safety_failed_count, and rows_written. run.log records the same safe lifecycle events and row counts without logging API keys, clause text, or model output.

Use --output-dir to change the parent directory, --run-id for deterministic or manual run names, and --verbose for DEBUG logs. The command fails if the run directory already exists, so previous runs are not silently overwritten.

FastAPI adapter

The API is a minimal HTTP boundary over the same workflow. It does not add legal-advice behavior, autonomous tool calling, persistence, auth, background jobs, WebSockets, or run history.

POST /runs accepts a single clause payload, writes a temporary one-row JSONL input internally, executes the existing LangGraph workflow synchronously, and returns workflow observability fields plus generated verification questions.

FastAPI writes local artifacts under data/cuad/api-runs/{run_id}/.

This adapter is intended for local development and internal E2E validation. Do not expose it publicly without path restrictions, authentication, and deployment hardening.

uv run uvicorn contract_question_agent.api.app:app --reload --reload-dir src

Dry-run example:

curl -X POST http://127.0.0.1:8000/runs \
  -H "content-type: application/json" \
  -d '{
    "contract_id": "demo-contract",
    "clause_type": "Non-Compete",
    "evidence_text": "Employee will not compete for one year after termination.",
    "dry_run": true
  }'

AG-UI run event stream

The AG-UI endpoint exposes a minimal human-facing event stream for observing a single workflow run.

It is intentionally not a chat UI. The endpoint streams run lifecycle events over Server-Sent Events:

  • RUN_STARTED
  • STEP_STARTED
  • STEP_FINISHED
  • STATE_SNAPSHOT
  • RUN_FINISHED
  • RUN_ERROR

The AG-UI stream emits node-level workflow status events so the viewer can show which workflow node is currently running, such as PRECHECK_INPUT, CLASSIFY_SCOPE, GENERATE_MINIMAL_QUESTIONS, REFLECT_AGAINST_SKILL_THESIS, and SAFETY_CHECK.

This is observability only. It does not add checkpointing, interrupts, resume support, persistence, auth, WebSockets, or run history.

The event snapshot removes raw evidence_text from generated question outputs. Local artifacts are still written under data/cuad/api-runs/{run_id}/.

curl -N -X POST http://127.0.0.1:8000/ag-ui/runs \
  -H "content-type: application/json" \
  -d '{
    "contract_id": "demo-contract",
    "clause_type": "Non-Compete",
    "evidence_text": "Employee will not compete for one year after termination.",
    "dry_run": true
  }'

Streamlit AG-UI run viewer

The Streamlit viewer is a minimal human-facing observability UI.

It does not call LangGraph directly. It calls the FastAPI AG-UI SSE endpoint:

POST /ag-ui/runs

FastAPI remains the interface boundary, and LangGraph remains the workflow owner. Streamlit does not directly call workflow internals, model clients, or MCP. No upload support, checkpointing, resume support, persistence, auth, WebSockets, or run history is added.

Start the FastAPI backend:

uv run uvicorn contract_question_agent.api.app:app --reload --reload-dir src

Start the Streamlit viewer:

uv run streamlit run viewer/streamlit_app.py

Or start both together:

./scripts/dev.sh

Optional ports:

FASTAPI_PORT=8001 STREAMLIT_PORT=8502 ./scripts/dev.sh

Open the Streamlit URL shown in the terminal.

The viewer submits a single clause, reads AG-UI-compatible lifecycle events, shows the current workflow node and node timeline, and renders the final safe state snapshot. It removes raw evidence_text from rendered backend snapshots. It includes a few built-in synthetic sample clauses for demos and smoke tests, while keeping the manual input path editable. The samples are not drawn from CUAD or real contracts, and upload support is intentionally out of scope for v1.0.0.

This is not a chat UI. It is a run/output viewer.

Example outputs

This repository includes two synthetic example outputs:

  • [examples/in_scope_non_compete.md](examples/inscopenon_compete.md)
  • a successful in-scope run for a synthetic Non-Compete clause
  • [examples/out_of_scope_non_contract_text.md](examples/outofscopenoncontract_text.md)
  • an out-of-scope run where non-contract text stops before generation

Both examples are synthetic and are not drawn from CUAD or real contracts.

v1.0.0 scope

Included:

  • CUAD data preparation
  • CLI generation
  • LangGraph workflow
  • two-step scope classification
  • MCP candidate review lenses
  • skill-thesis reflection
  • FastAPI / AG-UI / Streamlit viewer
  • example outputs

Not included:

  • upload support
  • auth
  • persistence
  • run history
  • checkpoint/resume
  • production deployment hardening
  • legal advice
  • legal enforceability judgment
  • signing recommendation

Non-goals

This project does not:

  • answer legal

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.