Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Jobs:
# rust-check — cargo fmt/check/clippy/test (no DB needed)
# dashboard — Next.js type-check + lint + build
# mcp — MCP server typecheck + unit tests + tsc (RFC-090)
# docker — Dockerfile builds (buildx + GHA layer cache, no push)
# migrations-check — apply migrations to Postgres 16, sqlx prepare --check
# compose-smoke — default compose stack end-to-end smoke
Expand Down Expand Up @@ -185,6 +186,39 @@ jobs:
# Point at localhost during CI; no real backend needed for static build
NEXT_PUBLIC_API_URL: "http://localhost:3001"

# ── MCP server (RFC-090) ──────────────────────────────────
mcp:
name: MCP — typecheck / test
needs: changes
if: ${{ needs.changes.outputs.heavy == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: mcp
steps:
- uses: actions/checkout@v4

- name: Setup Node 22
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
cache-dependency-path: mcp/package-lock.json

- name: npm install
run: npm install

- name: TypeScript type-check
run: npm run typecheck

- name: Unit tests
run: npm test

- name: Build
run: npm run build

# ── Docker build smoke-test ───────────────────────────────
docker:
name: Docker — build smoke-test
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ target/
.env.local

# Node / Next.js
mcp/node_modules/
mcp/dist/
dashboard/node_modules/
dashboard/.next/
dashboard/.next-verify*/
Expand All @@ -30,6 +32,7 @@ dashboard/tsconfig.tsbuildinfo
dashboard/public/llms.txt
dashboard/public/llm-integration.md
dashboard/public/llms-full.txt
dashboard/public/mcp-reference.md
# Added by code-review-graph
.code-review-graph/

Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ from real samples, deploys it, wires your producer to `/v1/ingest`, and verifies
with a dry run before anything is written. Machine index:
[`/llms.txt`](https://app.datacontractgate.com/llms.txt).

MCP (Cursor, Claude Desktop, Windsurf, Copilot, Codex):

```json
{
"mcpServers": {
"contractgate": {
"command": "npx",
"args": ["-y", "@contractgate/mcp-server"],
"env": {
"CONTRACTGATE_API_KEY": "${CONTRACTGATE_API_KEY}"
}
}
}
}
```

Tools and auth: [`docs/mcp-reference.md`](docs/mcp-reference.md).

---

## Try it in 10 minutes (Self-Hosted Free)
Expand Down
12 changes: 12 additions & 0 deletions dashboard/app/docs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ const DOCS = [
pills: ["copy-paste", "curl + TS + Python", "dry-run verified", "no signup to read"],
cta: "Open the raw playbook →",
},
{
external: true,
href: "/mcp-reference.md",
icon: "🔌",
title: "MCP server",
badge: "RFC-090",
badgeColor: "text-cyan-400 bg-cyan-900/30 border-cyan-700/40",
description:
"Official Model Context Protocol server. Add npx -y @contractgate/mcp-server to Cursor, Claude Desktop, Windsurf, or Copilot and the agent gets typed tools for infer, dry-run, deploy, and quarantine — no curl.",
pills: ["stdio", "npx", "API-key auth", "dry-run default"],
cta: "Open the MCP reference →",
},
{
external: false,
href: "/docs/python-sdk",
Expand Down
1 change: 1 addition & 0 deletions dashboard/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const metadata: Metadata = {
"text/markdown": [
{ url: "/llms.txt", title: "LLM Index" },
{ url: "/llms-full.txt", title: "Full LLM Documentation" },
{ url: "/mcp-reference.md", title: "MCP Server" },
],
},
},
Expand Down
2 changes: 1 addition & 1 deletion dashboard/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const nextConfig: NextConfig = {
async headers() {
return [
{
source: "/:path(llms.txt|llms-full.txt|llm-integration.md)",
source: "/:path(llms.txt|llms-full.txt|llm-integration.md|mcp-reference.md)",
headers: [
{ key: "Content-Type", value: "text/plain; charset=utf-8" },
{ key: "Cache-Control", value: "public, max-age=300" },
Expand Down
1 change: 1 addition & 0 deletions dashboard/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const PUBLIC_ROUTES = [
// but a future matcher edit shouldn't silently re-gate them.
"/llms.txt",
"/llm-integration.md",
"/mcp-reference.md",
];

function isPublic(pathname: string) {
Expand Down
4 changes: 3 additions & 1 deletion dashboard/scripts/sync-llm-docs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* RFC-089 — copy the agent-facing docs into public/ so they are served raw at
* https://app.datacontractgate.com/llms.txt
* https://app.datacontractgate.com/llm-integration.md
* https://app.datacontractgate.com/mcp-reference.md
* https://app.datacontractgate.com/llms-full.txt
*
* Canonical source is docs/ at the repo root. The copies in public/ are
Expand All @@ -23,12 +24,13 @@ const here = dirname(fileURLToPath(import.meta.url));
const docsDir = join(here, "..", "..", "docs");
const publicDir = join(here, "..", "public");

const FILES = ["llms.txt", "llm-integration.md"];
const FILES = ["llms.txt", "llm-integration.md", "mcp-reference.md"];

// Order matches the "Reference" section of llms.txt. Playbook first so agents
// hit the executable flow before the deep reference material.
const FULL_BUNDLE = [
"llm-integration.md",
"mcp-reference.md",
"v1-ingest-reference.md",
"deploy-contract-reference.md",
"csv-inference-reference.md",
Expand Down
3 changes: 3 additions & 0 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ signed off (may be planning docs or UI-only); **Draft** = under review;
| 084 | [Slack Lead-Intake Bot](rfcs/084-slack-lead-bot.md) | Shipped | `nightly-maintenance-2026-07-15-rfc084-slack-bot` |
| 085 | [Org Admin / Team Management](rfcs/085-org-admin-team-management.md) | Shipped | `nightly-maintenance-2026-07-16-rfc085-team-admin` |
| 089 | [LLM-Pasteable Onboarding (`/llms.txt` + agent playbook)](rfcs/089-llm-agent-onboarding.md) | Shipped | `nightly-maintenance-2026-07-22-bot-signup-cleanup` |
| 090 | [Official MCP Server (stdio)](rfcs/090-mcp-server.md) | Accepted | `nightly-maintenance-2026-08-23-rfc090` |

---

Expand All @@ -114,6 +115,8 @@ signed off (may be planning docs or UI-only); **Draft** = under review;

---

*2026-08-23 — RFC-090: official stdio MCP server at `mcp/` (`npx -y @contractgate/mcp-server`). Tools wrap existing gateway routes (infer, dry-run ingest / playground, deploy, quarantine, list). No engine changes. Reference: `docs/mcp-reference.md`, served raw at `/mcp-reference.md`.

*2026-08-13 — RFC-089 shipped: `docs/llm-integration.md` (agent-executable integration playbook) and `docs/llms.txt`, copied into `dashboard/public/` by a prebuild step and served raw at `app.datacontractgate.com/llm-integration.md` + `/llms.txt`; `datacontractgate.com` 307s to both, and the marketing site carries a copy-the-prompt block. Drift gate `tests/llm_docs_test.rs` asserts every endpoint the playbook cites exists in the router and that its example contract compiles. Same branch also replaced the CI migration file-count sentinel with a filename-contract check (contiguous NNN_ prefixes, no duplicates) plus `.github/workflows/migration-drift.yml`, which compares the prod ledger to `supabase/migrations/` daily — the check that would have caught 033/034 being applied-but-untracked. Migration 035 is held (listed in `supabase/unapplied-migrations.txt`) pending a re-audit against current signups.*

*Last updated: 2026-07-16 — RFC-078/079/080 implemented on `feature-RFCs_deferred`: RFC-079 unifies contract inference on the Rust engine (Generate-from-Sample now routes through `POST /contracts/infer`, nested objects infer correctly, JS inferrer removed); RFC-080 adds Visual Builder nested-object support; RFC-078 adds the cross-surface walkthrough spine + API/CSV/Kafka/Kinesis walkthroughs with cg-validated runnable examples. RFC-077 RAG profile moved to Accepted — contract, examples, and reference doc are shipped and engine-validated, but GA promotion is deferred until a RAG prospect is active.*
Expand Down
4 changes: 4 additions & 0 deletions docs/agent-rules/AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ a producer must satisfy a YAML contract in the `contracts/` directory.
Never edit a deployed version in place — bump `version:` in the YAML and
redeploy.

If the ContractGate MCP server is connected, prefer `infer_contract`,
`validate_events`, `deploy_contract`, and `get_quarantine` over constructing
shell pipelines. Setup: <https://app.datacontractgate.com/mcp-reference.md>.

Full playbook: <https://app.datacontractgate.com/llm-integration.md>.
8 changes: 5 additions & 3 deletions docs/agent-rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ presets carry the same core rule — pick the one your team uses.
| Claude Code, Codex, others | `AGENT.md` | Paste into your project's agent instructions file |

Every preset points the agent at the canonical playbook:
<https://app.datacontractgate.com/llm-integration.md>. Nothing here duplicates
that content — the presets are short by design so they can be dropped into
a repo without becoming a maintenance burden.
<https://app.datacontractgate.com/llm-integration.md>. If the host has the
ContractGate MCP server connected, the presets tell the agent to use those
tools instead of curl. Nothing here duplicates the playbook — the presets
are short by design so they can be dropped into a repo without becoming a
maintenance burden.
2 changes: 2 additions & 0 deletions docs/agent-rules/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ one of these:
contracts/<name>.yaml --json`. Never edit a deployed version in place — bump
`version:` in the YAML and redeploy.

If the ContractGate MCP server is connected, prefer its tools over curl.
MCP: <https://app.datacontractgate.com/mcp-reference.md>.
Full playbook: <https://app.datacontractgate.com/llm-integration.md>.
5 changes: 3 additions & 2 deletions docs/agent-rules/cursor.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ loosening the contract.

Use the CLI: `contractgate deploy-contract contracts/<name>.yaml --json`.
Never edit a deployed version in place — bump `version:` in the YAML and
redeploy. See <https://app.datacontractgate.com/llm-integration.md> for the
full playbook.
redeploy. If the ContractGate MCP server is connected, prefer its tools
over curl. See <https://app.datacontractgate.com/llm-integration.md> and
<https://app.datacontractgate.com/mcp-reference.md>.
6 changes: 4 additions & 2 deletions docs/agent-rules/windsurf.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@ loosening the contract.
## Deploying a contract change

Use `contractgate deploy-contract contracts/<name>.yaml --json`. Never edit a
deployed version in place — bump `version:` in the YAML and redeploy. Full
playbook: <https://app.datacontractgate.com/llm-integration.md>.
deployed version in place — bump `version:` in the YAML and redeploy. If the
ContractGate MCP server is connected, prefer its tools over curl. Full
playbook: <https://app.datacontractgate.com/llm-integration.md>. MCP:
<https://app.datacontractgate.com/mcp-reference.md>.
5 changes: 5 additions & 0 deletions docs/llm-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ gateway — verified with a dry run before anything writes.

Every request below sends the key as the `X-Api-Key` header.

If the ContractGate MCP server is connected, prefer its tools
(`infer_contract`, `validate_events`, `deploy_contract`, `get_quarantine`,
`list_contracts`) over constructing `curl` calls. Setup:
<https://app.datacontractgate.com/mcp-reference.md>.

---

## §1 — Find the event shape in the user's repo
Expand Down
1 change: 1 addition & 0 deletions docs/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Source: https://github.com/nightmoose/contractgate
## Start here

- [Integration playbook for coding agents](https://app.datacontractgate.com/llm-integration.md): Paste this into Claude, Cursor, or Codex. End-to-end, executable: get a key, infer a contract from real samples, write and deploy the contract YAML, wire the producer to POST /v1/ingest, verify with a dry run.
- [MCP server](https://app.datacontractgate.com/mcp-reference.md): Official stdio MCP package (`npx -y @contractgate/mcp-server`) — infer, dry-run, deploy, and list quarantine as typed tools.
- [Full documentation bundle](https://app.datacontractgate.com/llms-full.txt): One-shot ingestion for large-context agents — the playbook and every reference doc below, concatenated. Skip the per-link round-trips.

## Reference
Expand Down
139 changes: 139 additions & 0 deletions docs/mcp-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# ContractGate MCP Server

**RFC-090.** Official Model Context Protocol server for Cursor, Claude Desktop,
Windsurf, VS Code Copilot, Codex, and any other MCP host.

The server is a thin stdio client of the existing gateway. It does not run
validation itself. Auth is the same API key the CLI and playbook already use.

## Install

Add this to the host's MCP config (`~/.cursor/mcp.json`, Claude Desktop
`claude_desktop_config.json`, etc.).

Once `@contractgate/mcp-server` is on npm:

```json
{
"mcpServers": {
"contractgate": {
"command": "npx",
"args": ["-y", "@contractgate/mcp-server"],
"env": {
"CONTRACTGATE_API_KEY": "${CONTRACTGATE_API_KEY}"
}
}
}
}
```

Until then, from a clone (`cd mcp && npm install && npm run build`):

```json
{
"mcpServers": {
"contractgate": {
"command": "node",
"args": ["<repo>/mcp/dist/index.js"],
"env": {
"CONTRACTGATE_API_KEY": "${CONTRACTGATE_API_KEY}"
}
}
}
}
```

Restart the host after editing the config.

## Environment

| Variable | Required | Default |
|---|---|---|
| `CONTRACTGATE_API_KEY` | yes | — |
| `CONTRACTGATE_BASE_URL` | no | `https://app.datacontractgate.com` |

Never put the raw key in the config file. Reference the environment variable
the way your host supports (`${CONTRACTGATE_API_KEY}` in Cursor; Claude Desktop
reads the process environment).

Get a key at <https://app.datacontractgate.com/account>.

## Tools

### `infer_contract`

`POST /contracts/infer`. Draft YAML from real sample events. Does not persist.

| Argument | Type | Required |
|---|---|---|
| `name` | string | yes |
| `samples` | object[] | yes, ≥1 |
| `description` | string | no |

Write the returned `yaml_content` to `contracts/<name>.yaml` and review it
before deploying. Inference is a starting point.

### `validate_events`

Validate events against a deployed contract or against in-flight YAML.

| Argument | Type | Required |
|---|---|---|
| `events` | object[] | yes, ≥1 |
| `contract_id` | uuid | exactly one of `contract_id` / `yaml_content` |
| `yaml_content` | string | exactly one of `contract_id` / `yaml_content` |
| `dry_run` | boolean | no, default `true` |

- `contract_id` → `POST /v1/ingest/{contract_id}`. Default `dry_run=true` (no
audit row, no quarantine, no metered usage). Set `dry_run=false` only after a
dry run has passed.
- `yaml_content` → `POST /playground/validate` per event. Never persists.
`dry_run` is ignored.

`200` / `207` / `422` all return the body. Read `results[].violations` —
entries may include `received`, `expected`, and `suggestion` so you can fix
the producer or the YAML without guessing.

### `deploy_contract`

`POST /contracts/deploy`. Finds-or-creates the contract by `name`, inserts the
YAML as `stable`, deprecates prior stable versions. Refused while quarantine
is pending. `409` if that `(name, version)` already exists — bump `version:`
in the YAML and retry.

| Argument | Type | Required |
|---|---|---|
| `name` | string | yes |
| `yaml_content` | string | yes |
| `source` | string | no |
| `deployed_by` | string | no (defaults to `mcp`) |

Save the returned `contract_id`. It is not a secret.

### `get_quarantine`

`GET /quarantine`. Source quarantine rows for the caller's org, newest first.

| Argument | Type | Required |
|---|---|---|
| `contract_id` | uuid | no |
| `limit` | int | no, default 100, max 500 |
| `offset` | int | no |

### `list_contracts`

`GET /contracts`. Identities the key can see.

## Prompt

`integrate-contractgate` — loads the agent playbook URL
(<https://app.datacontractgate.com/llm-integration.md>) as the instruction to
follow. Use it when wiring ContractGate into a repo for the first time.

## What this server will not do

- Live ingest by default (`validate_events` defaults to dry-run).
- Kafka / Kinesis / billing / collaborator management.
- Invent contract fields that were not in the samples.

Full executable flow without MCP: <https://app.datacontractgate.com/llm-integration.md>.
Loading