version90 DEVELOPERS

MCP server

One URL. Your contracts, agent-ready.

version90 ships a remote MCP server — nothing to install or host. Point any MCP-capable agent (Claude Code, claude.ai, Cursor, …) at the endpoint and it gets thirteen tools over your organization's contracts — eleven read-only, two that write (filing a contract, choosing an organization), none that delete or edit. Stateless Streamable HTTP: every request self-contained, plain JSON responses, batch requests accepted. Tool results are returned as structured content and MCP text content containing JSON with the exact shapes documented below.

Endpoint: https://api.version90.com/api/v1/mcp
Auth: OAuth 2.0 — you sign in with your version90 account; the client runs the flow. No token to paste.
Protocol: MCP 2025-06-18 · capabilities: tools

Claude Code
claude mcp add version90 https://api.version90.com/api/v1/mcp --transport http
# On first use the client opens a browser to sign in with version90 (Clerk).
# No API key: the MCP OAuth flow issues and refreshes the token for you.
Cursor / generic mcp.json
{
  "mcpServers": {
    "version90": {
      "url": "https://api.version90.com/api/v1/mcp"
    }
  }
}

Just the URL — no headers block. The client discovers how to authenticate and prompts you to sign in the first time.

Authentication (OAuth)

The server is an OAuth 2.0 Resource Server; version90's identity provider (Clerk) is the authorization server. You never mint or paste a key — any spec-compliant MCP client runs the flow for you:

  1. The client calls the endpoint with no token and gets 401 with a WWW-Authenticate header pointing at the server's protected-resource metadata.
  2. It reads /.well-known/oauth-protected-resource/api/v1/mcp, which names the authorization server.
  3. It registers itself (Dynamic Client Registration) and opens a browser for you to sign in and consent.
  4. It retries with the issued access token, and refreshes it silently thereafter.

Only OAuth access tokens issued by version90's authorization server are accepted; a token that names a different audience is refused, and app session tokens and REST API tokens never work here. Access follows your membership: the agent acts as you, with your role, and can never edit a document.

Which organization. Tools act on one organization you're an active member of. With a single organization that's automatic. With several, the tools ask the agent to call list_organizations and select_organization first — the choice is remembered. With none yet, every tool replies with a link to finish setting up your organization in the app. Removing someone from an organization ends their MCP access to it on the next request.

Tool reference

Search contract archive

search_archive read-only #

Search executed agreements on extracted facts. All filters optional and AND-combined; query is a substring match over title, counterparty, summary and filename.

arguments
{
  "query": "renewal",              // optional free text
  "counterparty": "Meridian",      // optional, normalized exact match
  "agreement_type": "MSA",         // optional, substring
  "date_from": "2024-01-01",       // optional, effective_date >=
  "date_to": "2026-12-31",         // optional, effective_date <=
  "limit": 25,                     // optional, max 100
  "offset": 0                      // optional, for paging
}
returns (JSON in text content)
{
  "count": 2,
  "results": [{
    "id": "9d2f4c1a-…",
    "title": "Master Services Agreement",
    "filename": "Meridian_MSA_signed.pdf",
    "counterparty": "Meridian Robotics, Inc.",
    "agreement_type": "Master Services Agreement",
    "effective_date": "2024-09-02",     // or null
    "expiration_date": "2026-09-01",    // or null
    "governing_law": "Delaware",
    "status": "ready",                  // received | analyzing | ready | failed
    "summary": "Two-year MSA with auto-renewal…"
  }]
}

Get archived contract

get_archived_contract read-only #

Everything extracted from one document — the payload for answering detailed questions with citations.

arguments
{ "archive_id": "9d2f4c1a-…" }   // required
returns (JSON in text content)
{
  …all search_archive fields, plus:
  "executed_date": "2024-08-28",
  "key_terms":   [ { "field": "payment_terms", "value": "Net 60" } ],
  "key_clauses": [ { "clause_type": "limitation_of_liability",
                     "title": "Limitation of Liability",
                     "snippet": "…liable for all damages arising…" } ],
  "risks":       [ { "title": "Liability is uncapped",
                     "description": "No aggregate cap…",
                     "severity": "high" } ],
  "error": null
}

Upload contract to archive

upload_to_archive writes · non-destructive #

File an executed contract (PDF or DOCX, base64-encoded). Returns immediately; extraction runs in the background — fetch with get_archived_contract after a few seconds. Contracts signed through version90 e-sign file themselves into the archive on completion — no need to re-upload those.

arguments
{
  "filename": "signed_nda.pdf",           // required
  "content_base64": "JVBERi0xLjcK…"       // required
}
returns (JSON in text content)
{
  "id": "7c8d9e0f-…",
  "status": "received",
  "message": "Stored. Extraction runs in the background — fetch it with get_archived_contract in a few seconds."
}

List active negotiations

list_contracts read-only #

Active negotiations with lifecycle status and whose turn it is.

arguments
{
  "query": "Meridian",     // optional, substring on title/counterparty
  "status": "with_counterparty",  // optional lifecycle status
  "limit": 25,             // optional, max 100
  "offset": 0              // optional, for paging
}
returns (JSON in text content)
{
  "count": 1,
  "results": [{
    "id": "1f7a9e42-…",
    "title": "MSA — Meridian Robotics",
    "counterparty": "Meridian Robotics, Inc.",
    "status": "with_counterparty",
    "turn": "counterparty",          // "internal" = waiting on your team
    "updated_at": "2026-07-11T09:41:37+00:00"
  }]
}

Get negotiation details

get_contract read-only #

One negotiation in depth: the immutable version chain, open AI findings, and — once sent for signature — the signing state (signer progress, cc'd watchers). Read-only — no editing tool exists over MCP.

arguments
{ "workspace_id": "1f7a9e42-…" }   // required
returns (JSON in text content)
{
  "id": "1f7a9e42-…",
  "title": "MSA — Meridian Robotics",
  "counterparty": "Meridian Robotics, Inc.",
  "status": "with_counterparty",
  "turn": "counterparty",
  "versions": [{
    "id": "e8d1b2c3-…",
    "filename": "MSA_Meridian_v3.docx",
    "source": "counterparty_return",   // upload | internal_edit | …
    "created_at": "2026-07-11T09:41:35+00:00"
  }],
  "open_findings": [{
    "title": "Liability is uncapped",
    "type": "nonstandard",
    "severity_note": "Your standard caps liability at 12 months of fees…"
  }],
  "signature": {                       // null until sent for signature
    "status": "partially_signed",      // pending | sent | partially_signed |
                                       // completed | declined | voided | failed
    "provider": "native",              // native | dropbox_sign
    "signers": [{ "name": "Dana Ferro", "email": "dana@meridianrobotics.com",
                  "status": "signed", "signed_at": "2026-07-12T15:02:11+00:00" }],
    "cc_emails": ["legal@meridianrobotics.com"],  // watchers, notified each step
    "completed_at": null
  }
}

Search playbook

search_playbook read-only #

The organization's Playbook — approved clause language, standard documents (baselines), and knowledge sources the AI cites. The tool for quoting the org's own preferred terms: 'what is our standard limitation of liability language?'. Approved clauses only; candidates never surface.

arguments
{
  "query": "liability",    // optional free text over clause names/types/language
  "limit": 25,             // optional, max 100
  "offset": 0              // optional, for paging
}
returns (JSON in text content)
{
  "clauses": [{
    "id": "4b5c6d7e-…",
    "name": "Limitation of liability",
    "clause_type": "limitation_of_liability",
    "intent": "Cap aggregate liability at 12 months of fees.",
    "language": "IN NO EVENT SHALL EITHER PARTY'S AGGREGATE LIABILITY EXCEED…"
  }],
  "standards": [ { "id": "8e9f0a1b-…", "name": "Standard MSA" } ],
  "knowledge": [ { "id": "2c3d4e5f-…", "name": "Security policy" } ]
}

Get comparison context

get_comparison_context read-only #

Resolve the exact baseline for a document version (asset override before workspace pin) and retrieve its matching saved review and evidence. Does not run analysis or change the pin.

arguments
{ "version_id": "…", "baseline_version_id": "…", "offset": 0, "limit": 50 }
// version_id required; explicit baseline override optional; findings limit max 100
returns (JSON in text content)
{ "workspace_id": "…", "version_id": "…", "baseline_scope": "asset",
  "baseline": { "id": "…", "version_id": "…", "name": "Standard MSA" },
  "saved_review": { "id": "…", "status": "partial", "mode": "full", "summary": "…" },
  "findings": [{ "current_evidence": "…", "baseline_evidence": "…",
                 "evidence_verified": true, "state": "open", "suggested_language": "…" }],
  "next_offset": null }
// baseline / saved_review can be null; empty findings do not prove compliance

Get standard document

get_baseline read-only #

Read an exact historical baseline version or the active version of a standard, with paginated source text and provenance.

arguments
{ "baseline_version_id": "…", "offset": 0, "limit": 12000 }
// alternatively supply baseline_id for its active version; limit max 20000 characters
returns (JSON in text content)
{ "id": "…", "name": "Standard MSA", "version_id": "…",
  "state": "ready", "retired": false, "is_active_version": false,
  "text": "…", "text_available": true, "offset": 0, "end_offset": 12000,
  "total_characters": 17000, "next_offset": 12000 }

Get policy document

get_knowledge read-only #

Read a policy source's active version and its organization/category scope.

arguments
{ "knowledge_id": "…", "offset": 0, "limit": 12000 }
returns (JSON in text content)
{ "id": "…", "name": "Liability policy", "version_id": "…",
  "scope": "organization", "category_id": null, "state": "ready",
  "text": "…", "text_available": true, "offset": 0, "end_offset": 400,
  "total_characters": 400, "next_offset": null }

Get document text

get_document_text read-only #

Read an exact negotiation version or archived agreement as paginated source text. No paid OCR/model call; scans without readable text return a limitation.

arguments
{ "source_kind": "version", "source_id": "…", "offset": 0, "limit": 12000 }
// source_kind: version | archive; source_id required; limit max 20000 characters
returns (JSON in text content)
{ "source_kind": "version", "id": "…", "filename": "draft.docx",
  "text": "…", "text_available": true, "offset": 0, "end_offset": 12000,
  "total_characters": 17000, "next_offset": 12000 }

Get plan usage

get_usage_meter read-only #

This month's plan usage — lets an agent warn before running out.

arguments
{}
returns (JSON in text content)
{
  "contracts_used": 6,   "contracts_included": 10,
  "ai_credits_used": 84, "ai_credits_included": 300,
  "archive_used": 41,    "archive_capacity": 100
}

List organizations

list_organizations read-only #

The organizations you belong to, and which one the other tools act on. With no organization yet, includes the link to finish setup in the app.

arguments
{}
returns (JSON in text content)
{
  "organizations": [{
    "id": "5a6b7c8d-…",
    "name": "Meridian Legal Ops",
    "role": "admin",                 // owner | admin | member | …
    "current": true                  // the org the other tools act on
  }],
  "onboarding_url": "https://app.version90.com/"   // only when you have none
}

Select organization

select_organization writes · non-destructive #

Choose the organization the other tools act on; remembered for future sessions. Only organizations you're an active member of can be chosen. Idempotent — selecting the same one again changes nothing.

arguments
{ "organization_id": "5a6b7c8d-…" }   // required, an id from list_organizations
returns (JSON in text content)
{
  "organization": {
    "id": "5a6b7c8d-…",
    "name": "Meridian Legal Ops",
    "role": "admin",
    "current": true
  }
}

Note what's absent: there is no editing tool. That's not a permission setting — the capability doesn't exist over MCP, so no prompt injection, agent bug, or over-eager automation can redline a contract. Humans edit; agents read, file, and answer. Tool errors come back as isError: true with a message in the text content — the request itself still returns 200.

On the wire

tools/call — raw JSON-RPC (what your MCP client does for you)
curl -X POST https://api.version90.com/api/v1/mcp \
  -H "Authorization: Bearer $OAUTH_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "tools/call",
    "params": { "name": "search_archive",
                "arguments": { "counterparty": "Meridian" } }
  }'
# → { "jsonrpc":"2.0", "id":1, "result": {
#      "content": [{ "type":"text", "text":"{\"count\":2,\"results\":[…]}" }],
#      "isError": false } }

Prompts that just work

  • "Which of our agreements auto-renew in the next 90 days, and what notice does each require?"
  • "Summarize every contract we have with Meridian Robotics, newest first."
  • "File the attached signed NDA into the archive and tell me what it says about data retention."
  • "Do any of our archived contracts have uncapped liability? List them with the clause."
  • "What's our standard limitation of liability language, and does the Meridian draft match it?"
  • "What's waiting on us right now across active negotiations, and where is the Meridian signing at?"