Production-ready API for ReviewOps engineering standards. It stores versioned standards in PostgreSQL and returns the latest active rules for AI code review tooling.
- Node.js 22
- TypeScript
- Fastify
- PostgreSQL
- Prisma
- Vitest
Copy .env.example to .env and adjust values for your environment.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
required | PostgreSQL connection string used by Prisma |
HOST |
0.0.0.0 |
Bind host |
PORT |
3000 |
API port |
LOG_LEVEL |
info |
Fastify logger level |
STANDARDS_API_KEY |
unset | Optional write API key. Required for writes when set, and always required when NODE_ENV=production |
Do not commit real database credentials or secrets.
npm install
cp .env.example .env
docker compose up -d postgres
npx prisma migrate dev
npm run prisma:seed
npm run devThe API listens on http://localhost:3000.
Run the full app and database:
docker compose up --buildSeed data after the app/database are running:
docker compose exec app npm run prisma:seed:prodnpm run dev # start local HTTP dev server
npm run build # compile TypeScript
npm run lint # strict TypeScript check
npm test # run tests
npm run mcp:dev # start MCP stdio server (tsx, no build required)
npm run mcp:start # start compiled MCP stdio server
npm run mcp:http:dev # start MCP Streamable HTTP server (tsx, no build required)
npm run mcp:http:start # start compiled MCP Streamable HTTP server
npm run prisma:migrate # create/apply local migration
npm run prisma:deploy # apply migrations in deployed environments
npm run prisma:seed # load example standardsThe MCP server exposes read-only access to engineering standards over the Model Context Protocol stdio transport. It shares the same PostgreSQL database as the HTTP API.
# Development (no build step required)
DATABASE_URL=postgresql://user:password@localhost:5432/standards npm run mcp:dev
# Production (build first)
npm run build
DATABASE_URL=postgresql://user:password@localhost:5432/standards npm run mcp:startAdd the following to your MCP client configuration (for example, claude_desktop_config.json):
{
"mcpServers": {
"standards-api": {
"command": "npm",
"args": ["run", "mcp:start"],
"cwd": "/absolute/path/to/standards-api",
"env": {
"DATABASE_URL": "postgresql://user:password@localhost:5432/standards"
}
}
}
}| Tool | Description |
|---|---|
list_standards |
List standards with optional status, category, severity, owner, limit, offset filters |
get_standard |
Get the latest version of a single standard by rule_key |
latest_standards |
Return the latest active standards payload (same as GET /api/v1/standards/latest) |
applicable_standards |
Return active standards matching repo, team, language, framework, runtime, environment, and/or changed_paths |
| URI | Description |
|---|---|
standards://latest |
Latest active standards payload as JSON |
standards://rule/{rule_key} |
A single standard by rule key |
The Streamable HTTP server exposes the same tools and resources as the stdio server over the MCP Streamable HTTP transport. Remote MCP clients (such as Claude.ai) can connect to it over HTTPS via a reverse proxy.
| Variable | Default | Description |
|---|---|---|
MCP_API_KEY |
required | API key sent in x-api-key on every request. Unset = all requests rejected. |
MCP_HTTP_PORT |
3001 |
Port for the Streamable HTTP MCP server (avoids collision with Fastify on 3000). |
# Development (no build step required)
DATABASE_URL=postgresql://user:password@localhost:5432/standards \
MCP_API_KEY=secret \
npm run mcp:http:dev
# Production (build first)
npm run build
DATABASE_URL=postgresql://user:password@localhost:5432/standards \
MCP_API_KEY=secret \
npm run mcp:http:startRequests without a valid x-api-key header return 401. Deploy behind a TLS-terminating reverse proxy (nginx, Caddy, AWS ALB, etc.) before exposing to the public internet.
Add the following to your MCP client configuration to connect to a remotely hosted instance:
{
"mcpServers": {
"standards-api-remote": {
"type": "http",
"url": "https://your-host/mcp",
"headers": {
"x-api-key": "your-secret-key"
}
}
}
}Replace https://your-host/mcp with the public URL of your reverse proxy.
The Streamable HTTP server exposes the same four tools and two resources as the stdio server. See the MCP Server section above for the full list.
The service creates one table:
standards: versioned standards/rules with status, severity, category, applicability metadata, guidance, examples, owner, timestamps, and deprecation timestamp.
There is a unique constraint on (rule_key, version) to prevent duplicate versions and a partial unique index that allows only one active version for a given rule_key.
rule_keyis stable across versions.- Each database row represents one version of a rule.
- Draft rules are updated in place by
PUT /api/v1/standards/:ruleKey. - Updating an active or deprecated rule creates a new version.
- Creating a new active version automatically marks the previous active version for the same
rule_keyasdeprecatedand setsdeprecated_at. - Deprecated rules remain queryable with list filters such as
?status=deprecated, but they do not appear in/api/v1/standards/latestor/api/v1/standards/applicable.
GET /healthGET /api/v1/standardsGET /api/v1/standards/:ruleKeyGET /api/v1/standards/latestPOST /api/v1/standardsPUT /api/v1/standards/:ruleKeyGET /api/v1/standards/applicableGET /openapi.jsonGET /docs
GET /openapi.json includes detailed schemas for request/response payloads, validation constraints, and error responses.
GET /api/v1/standards defaults to active latest standards. Supported filters:
status:active,draft,deprecatedcategory:reliability,security,observability,performance,cost,maintainability,architecture,complianceseverity:critical,high,medium,low,infoownerlimitoffset
Validation notes:
limitmust be1.500offsetmust be>= 0- invalid filters return
400witherror.code = "validation_error"
GET /api/v1/standards/applicable accepts:
repoteamlanguageframeworkruntimeenvironmentchanged_paths, comma-separated file paths
Validation notes:
changed_pathsmust be a comma-separated list with no empty entries (for example,src/a.ts,infra/main.tf)- malformed
changed_pathsreturns400witherror.code = "validation_error"
GET /api/v1/standards/:ruleKey returns the latest version for the provided rule_key, or 404 if it does not exist.
Matching is deterministic:
- Only active rules are returned.
- Empty or missing
applies_tofields are global for that field. - If an
applies_tofield has values, the request must match one of those values. - Supported fields are
languages,frameworks,runtimes,file_patterns,teams,repos, andenvironments. changed_pathsis parsed as comma-separated file paths.file_patternsuse glob matching againstchanged_paths.- A rule with no file patterns can still match on repo, team, language, framework, runtime, or environment.
- Each returned applicable rule includes
match_reason.
/api/v1/standards/latest and /api/v1/standards/applicable return:
{
"standards_version": "2026-05-18T15:30:00.000Z-count-6",
"rules": []
}The current standards_version is generated from the latest updated_at timestamp among returned rules and the returned rule count.
Write endpoints are POST /api/v1/standards and PUT /api/v1/standards/:ruleKey.
- If
NODE_ENV=production, writes requirex-api-key. - If
STANDARDS_API_KEYis set in any environment, writes requirex-api-key. - In non-production, writes are allowed without a key only when
STANDARDS_API_KEYis unset. - Missing or invalid keys return a JSON
401response witherror.code = "unauthorized".
Write payload notes:
rule_keyformat for create:^[A-Z0-9]+(?:-[A-Z0-9]+)+-\d{3,}$(example:SRE-K8S-003)- for create,
rule_key,title,description,severity,category,applies_to,rule_text,review_guidance, andownerare required statusdefaults todraftandversiondefaults to1when omitted- if
status=deprecated,deprecated_atis required in create payloads PUT /api/v1/standards/:ruleKeyalways returns201; it updates drafts in place and creates a new version for active/deprecated rules
Error response shape for non-2xx responses:
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"details": {}
}
}Health:
curl http://localhost:3000/healthList active standards:
curl http://localhost:3000/api/v1/standardsGet a standard:
curl http://localhost:3000/api/v1/standards/SRE-K8S-003Create a standard:
curl -X POST http://localhost:3000/api/v1/standards \
-H 'content-type: application/json' \
-d '{
"rule_key": "PERF-API-010",
"title": "Pagination required for collection endpoints",
"description": "Collection endpoints must support bounded pagination.",
"status": "active",
"severity": "medium",
"category": "performance",
"applies_to": {
"languages": ["typescript"],
"file_patterns": ["src/**/*.ts"]
},
"rule_text": "Collection endpoints must enforce page size limits.",
"review_guidance": "Look for unbounded list queries and missing limit parameters.",
"owner": "api-platform",
"version": 1
}'Get ReviewOps applicable standards:
curl 'http://localhost:3000/api/v1/standards/applicable?repo=payments-api&team=platform&language=typescript&environment=production&changed_paths=src/client.ts,infra/main.tf'Get Kubernetes standards for a changed manifest:
curl 'http://localhost:3000/api/v1/standards/applicable?framework=kubernetes&runtime=container&environment=production&changed_paths=deploy/deployment.yaml'{
"standards_version": "2026-05-18T19:42:00.000Z-count-1",
"rules": [
{
"id": "c3c3d7a9-2e30-4c12-9f18-0bcb4b5f4b7a",
"rule_key": "REL-NET-004",
"title": "External calls must define timeouts/retries",
"description": "External network calls must bound latency and define retry behavior appropriate to idempotency.",
"status": "active",
"severity": "high",
"category": "reliability",
"applies_to": {
"languages": ["typescript", "javascript", "go", "python"],
"file_patterns": ["src/**/*.ts", "src/**/*.js", "src/**/*.go", "src/**/*.py"]
},
"rule_text": "Every external HTTP/RPC call must define a timeout and explicit retry policy.",
"review_guidance": "Inspect clients and SDK calls for configured timeouts, retry limits, and backoff.",
"good_example": "fetch(url, { signal: AbortSignal.timeout(3000) }) with bounded retry wrapper.",
"bad_example": "await fetch(url)",
"owner": "platform",
"version": 1,
"created_at": "2026-05-18T19:42:00.000Z",
"updated_at": "2026-05-18T19:42:00.000Z",
"deprecated_at": null,
"match_reason": "Matched language=typescript and Matched changed_paths=src/client.ts"
}
]
}