Inspiration

Everyone has signed a contract they didn't fully read. The current answer is to paste it into a chat window, which gets you advice about the document: a paragraph you then retype into the file yourself. We wanted the agent working inside the contract, with one hard rule: it can suggest anything and change nothing.

What it does

Redline is a contract review workspace for humans and their agents. The agent reads the agreement clause by clause, flags risks with severity and plain-English consequences, compares terms against a market-standard playbook, and drafts replacement language.

Every change it suggests arrives as a redline — old text struck through, proposed text in green, reasoning attached, that a human accepts or rejects. Accepting is the only action that alters the contract, and no agent can take it.

Why this is a strong fit for WebMCP

Contract review is stateful, multi-step, and unforgiving of hallucination, exactly where screen-scraping agents fail and where a typed tool surface shines.

Three things WebMCP makes possible that a chat upload cannot:

  1. The output is document state, not prose. A suggestion is an object anchored to clause 6 with an accept button, an author, and a status, not a sentence to transcribe.
  2. The document is live. Tools read the contract as it currently stands, so "re-check the clauses I just changed" works. An upload is a snapshot that goes stale immediately.
  3. The safety is structural. Any AI with write access is safe only while it behaves. Here there is no mutation tool registered, and the server refuses any write tagged as an agent. "Please don't edit without asking" versus "there is no API to edit" is the whole difference.

Typed schemas help too: flag_risky_clause requires a clause id that exists, so the agent cannot cite clause 27 of a 13-clause contract the way chat routinely does.

How it creates a better user experience

You stop being a transcription layer between a chat window and a document. The agent does the reading, the comparing, and the drafting; you do the deciding, on a screen that shows exactly what would change, why, and who suggested it. Rejections carry reasons the agent can read back, so the review gets sharper as it goes.

And because the tool surface is public, you bring your own agent, the one that knows your last eight contracts, your rates, and your risk tolerance. A document dropped into a fresh chat window knows nothing about the person dropping it.

What people and agents can do together that was difficult before

Work on the same legal document simultaneously, with a guarantee specifying who can change it. Before WebMCP, an agent either had no access (advice you retype) or full access (write permission you have to trust). Redline is the third option: full analytical reach, zero write capability, enforced by which tools exist rather than by how well the model behaves.

The phase model makes that dynamic. In analysis, the agent has 8 tools and literally cannot propose. In redlining, it has 12. Once the contract is executed, it has 5, all read-only — a signed contract cannot be marked up, and the agent's own capabilities reflect that the moment the phase changes.

How we implemented WebMCP

Twelve tools registered through document.modelContext.registerTool (with a navigator.modelContext fallback for older Chrome previews), unregistered via AbortSignal whenever the review phase changes:

document.modelContext.registerTool({
  name: "propose_edit",
  description:
    "Propose replacement text for a clause. This does NOT change the contract — it " +
    "creates a redline the human accepts or rejects.",
  inputSchema: {
    type: "object",
    properties: {
      clauseId: { type: "string" },
      proposedText: { type: "string" },
      rationale: { type: "string", maxLength: 400 },
    },
    required: ["clauseId", "proposedText", "rationale"],
    additionalProperties: false,
  },
  execute: async (input) => {
    const { proposalId } = await api.propose(reviewId, { type: "replace", ...input, via: "agent" });
    return `Proposal ${proposalId} created. Status: pending_human_approval — the contract ` +
           `is unchanged until a human accepts it. Use list_proposals for the outcome.`;
  },
}, { signal: phaseController.signal });

Two design decisions did most of the work:

  • Errors are written to be recovered from. "Error: no clause c27. Valid ids are: c1, c2, …" tells the agent how to fix its own mistake instead of leaving it to guess.
  • Descriptions are prompts. Each says when to call the tool and what to read first, which is why agents chain them sensibly without being told the order.

Stack: Express + vanilla JS, server-sent events for live sync across everyone reviewing, Upstash Redis in production so reviews survive serverless cold starts.

Challenges we ran into

  • Making the guarantee real rather than rhetorical. It isn't enough to omit an accept tool; the server also rejects any resolve request tagged via: "agent", and phase changes are human-only. Defense in depth, so the claim survives a hostile reading.
  • Writing tool descriptions models actually follow. Early versions had agents proposing edits to clauses they hadn't read. Explicitly saying so in the description fixed it.
  • The WebMCP surface is still moving between document.modelContext and the earlier navigator.modelContext, so registration goes through a small adapter.

What we learned

Capability design is the safety story. Every guardrail you can move from the prompt into the tool surface is a guarantee instead of a hope — and phase-scoped registration means the answer to "what can this agent do right now?" is a list you can print, not a judgment call.

What's next for Redline

Upload your own contract; a shared playbook per organization so proposals match house style; multi-party review where the other side's agent negotiates back; and export to tracked-changes DOCX so the redlines land in the tools lawyers already use.

Built With

Share this project:

Updates

Submission history