JevAI-MCP
README.md
# JevAI-MCP
JevAI-MCP is an internal server that connects AI coding agents to the Jev
decision model. Jev answers structured questions fast and cheap: classify a
task, pick a strategy, rate complexity, decide whether to escalate. The server
exposes Jev through the Model Context Protocol (MCP) and records every
decision in a local dashboard.
The server ships two things in one container:
- an MCP server your agents connect to at `/mcp`
- a web dashboard at `/` that shows usage, agents, logs and settings
This project has no login. It is built for a private network. Do not expose it
directly to the public internet. See [Security](#security).
---
## Quickstart
1. Get a DefAPI key. Keys start with `dk-`. See https://defapi.org.
2. Copy the example environment file:
```bash
cp .env.example .env
```
3. Put your key into `.env`:
```
DEFAPI_API_KEY=dk-your-key-here
```
4. Run with Docker Compose:
```bash
docker compose up --build -d
```
5. Open the dashboard: http://localhost:8080
6. Open the **Integrations** page and download the ZIP for your agent. Each
ZIP contains ready-to-use configuration and routing instructions that
already contain the MCP URL you configured in Settings.
7. Copy the MCP endpoint into your agent configuration. For example:
```bash
# Codex
codex mcp add jevai --url http://localhost:3001/mcp
# Claude Code
claude mcp add --transport http jevai http://localhost:3001/mcp
```
8. Ask your agent to classify a task. It should call `jev_route_task`.
---
## What the server does
Agents call five MCP tools. The server builds the Jev questions, calls the
DefAPI gateway, applies the confidence thresholds, and stores the result.
| Tool | Purpose |
| --------------------- | ---------------------------------------------------------- |
| `jev_decide` | Answer one or more custom questions about one state |
| `jev_route_task` | Classify a coding task (bug, feature, refactor, docs, ...) |
| `jev_assess_complexity` | Rate a change on the five-level complexity scale |
| `jev_should_escalate` | Decide whether a task needs deeper reasoning or a human |
| `jev_choose_strategy` | Pick a strategy from your own list of options |
Jev calls happen at `POST {endpoint}/systemone`. See
[Jev integration](#jev-integration) below.
---
## Core principles
**Jev is an optimization, not a dependency.** If Jev, DefAPI, or this server
is unavailable, every tool returns a structured failure:
```json
{ "success": false, "error": { "type": "JEV_UNAVAILABLE", "retryable": true, "message": "..." } }
```
The agent instructions that ship with each integration tell agents to keep
working with their normal reasoning in that case. The health endpoint returns
`200` with `status: degraded`, never `503`, so a DefAPI outage does not stop
agents from working.
**Fail open.** The dashboard, health checks and the MCP endpoint keep serving
while the upstream is down.
**Least content stored.** By default the server stores metadata only: sizes,
counts, timing, caller, tool, status. The exact prompt content is not stored
until you turn full storage on in Settings → Privacy.
---
## Dashboard pages
| Page | Contents |
| -------------- | --------------------------------------------------------------- |
| Dashboard | Calls over time, success rate, latency, tokens, per-tool and per-agent bars |
| Requests | Every Jev call, with filters and a detail view |
| Agents | Which agents called the server, their most used tool, last seen |
| Logs | Application log stream, live tailing, text/JSON download |
| Integrations | Step-by-step setup and per-agent ZIP downloads |
| Settings | Jev connection, MCP, privacy and retention, URL for downloads |
| System | Version, runtime state, connection events, diagnostics ZIP |
The footer shows the running version, commit and build date.
---
## Configuration
Two layers exist. Environment variables win over values saved on the Settings
page. The Settings page marks each field that an environment variable locks.
All variables are listed in [.env.example](.env.example). Key ones:
| Variable | Default | Meaning |
| ----------------------- | --------------------------- | ---------------------------------------- |
| `DEFAPI_API_KEY` | - | DefAPI key. Store it here or on Settings |
| `DEFAPI_API_ENDPOINT` | `https://api.defapi.org/v1` | Gateway base URL |
| `JEV_MODEL` | `typesafe/jev-1.13` | Decision model |
| `PORT` | `8080` | HTTP: dashboard, API, `/mcp`, `/metrics` |
| `MCP_PORT` | `3001` | Second, MCP-only port |
| `MCP_ENABLED` | `true` | Turn the MCP endpoints on or off |
| `DATABASE_PATH` | `./data/jevai.db` | SQLite file. In Docker: `/data/jevai.db` |
| `DATA_RETENTION_DAYS` | `365` | Days to keep requests. `0` = never delete |
| `LOG_RETENTION_DAYS` | `14` | Days to keep log lines |
| `REQUEST_STATE_STORAGE` | `metadata_only` | `off`, `metadata_only` or `full` |
| `STORE_JEV_RESPONSES` | `true` | Store the answers Jev returns |
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error` |
| `PUBLIC_PROTOCOL` | `http` | Protocol written into downloads |
| `PUBLIC_HOSTNAME` | `localhost` | Hostname written into downloads |
| `PUBLIC_MCP_PORT` | `3001` | Port written into downloads |
Settings that you change without a restart take effect immediately. The server
picks up retention settings on an hourly run, and you can run retention by
hand on the System page.
---
## HTTP endpoints
Server root port: `PORT` (default `8080`).
| Method | Path | Purpose |
| ------ | ------------------------ | ----------------------------------------- |
| `POST` | `/mcp` | The MCP endpoint for agents |
| `GET` | `/health` | Health summary. `200` unless the database is down |
| `GET` | `/health/live` | Liveness probe |
| `GET` | `/health/ready` | Readiness probe (database) |
| `GET` | `/metrics` | Prometheus metrics |
| `GET` | `/version`, `/api/version` | Version and commit |
| `GET` | `/api/bootstrap` | One call with version, defaults, thresholds |
| `GET` | `/api/dashboard` | Dashboard payload for a time range |
| `GET` | `/api/requests` | Request list with filters |
| `GET` | `/api/logs` | Log list with filters |
| `GET` | `/api/agents` | Per-agent stats |
| `GET/PUT/POST/DELETE` | `/api/settings...` | Read and change settings |
| `GET` | `/api/integrations...` | Guides, file previews, ZIP downloads |
A second MCP server runs on `MCP_PORT` (default `3001`) with `/mcp` only.
Use one or the other depending on how you expose the service.
---
## Metric families
Named `jevai_*`. Labels stay low-cardinality on purpose: caller, tool, status,
route, error category. You will never see request ids or repository names as
metric labels. `GET /metrics` returns Prometheus text format.
Examples: `jevai_requests_total`, `jevai_requests_success_total`,
`jevai_request_duration_seconds`, `jevai_http_requests_total`,
`jevai_http_duration_seconds`, `jevai_active_clients`, `jevai_upstream_up`,
`jevai_database_size_bytes`.
---
## Jev integration
The server talks to the DefAPI gateway:
```
POST https://api.defapi.org/v1/systemone
Authorization: Bearer dk-...
Content-Type: application/json
{
"model": "typesafe/jev-1.13",
"state": "One paragraph of context the agent is working on.",
"questions": {
"subsystem": { "type": "choice", "instructions": "...", "criteria": { "application": null, "ingress": null } },
"risk": { "type": "score", "instructions": "...", "criteria": ["minimal", "low", "moderate", "high", "critical"] },
"urgent": { "type": "noul", "instructions": "..." }
}
}
```
Question types:
- `choice` - one option out of a fixed set
- `score` - an ordered scale (2 to 10 levels)
- `noul` - a yes/no call probability
Answers carry a `confidence`. The server maps it to a guidance level:
| Confidence | Guidance | What the agent should do |
| --------------- | ------------- | ------------------------------------ |
| >= 0.80 | `use` | Follow the answer |
| 0.55 to 0.79 | `guidance` | Treat it as advice |
| < 0.55 | `independent` | Decide on your own |
`noul` verdicts follow the same idea: `0.80` or higher means yes, `0.20` or
lower means no, and everything in between is `agent_decides`.
Transient failures (408, 425, 429, 500, 502, 503, 504, 529) retry with
exponential backoff and jitter, and they honor `Retry-After`. Validation,
authentication and bad-request errors do not retry.
---
## Agent setup
The Integrations page renders the same steps and the same ZIP files for all
five targets. The repository keeps plain copies under
[`integrations/`](integrations). Set `PUBLIC_HOSTNAME` and `PUBLIC_MCP_PORT`
so the generated configuration matches your deployment.
The `integrations/` copies use `http://localhost:3001`. Regenerate them after
guidance changes:
```bash
npm run generate:integrations
```
The ZIPs the server creates use the live URL you saved in Settings.
### Codex
Add the server to `~/.codex/config.toml`:
```toml
[mcp_servers.jevai]
url = "http://localhost:3001/mcp"
```
Or with the CLI: `codex mcp add jevai --url http://localhost:3001/mcp`.
Put the routing policy from `integrations/codex/jev-routing.md` into your
`AGENTS.md`.
### Claude Code
One project:
```json
{
"mcpServers": {
"jevai": { "type": "http", "url": "http://localhost:3001/mcp" }
}
}
```
`.mcp.json` needs the `"type": "http"` key, or Claude treats it as stdio.
CLI form: `claude mcp add --transport http jevai http://localhost:3001/mcp`.
Put the routing policy into `CLAUDE.md`.
### OpenCode
In `opencode.json`:
```json
{
"mcp": {
"jevai": { "type": "remote", "url": "http://localhost:3001/mcp", "enabled": true, "oauth": false }
}
}
```
### Gemini CLI
`gemini mcp add --transport http -s user jevai http://localhost:3001/mcp`.
In `~/.gemini/settings.json` the key is `httpUrl`, not `url`:
```json
{
"mcpServers": {
"jevai": { "httpUrl": "http://localhost:3001/mcp", "timeout": 30000 }
}
}
```
### Agents that send their own name
Set the header `X-Jev-Caller: <name>` on the MCP connection, or send
`metadata` with `caller`, `client_version`, `project`, `repository` and
`session_id` in tool calls. Agents you do not recognise show up under the
caller they send, and the Agents page marks them as custom.
---
## Development
```bash
npm install
cp .env.example .env # put DEFAPI_API_KEY in .env
npm run dev
```
`npm run dev` starts three things: an esbuild watcher for the server, Vite for
the dashboard on http://5173 (it proxies `/api`, `/mcp`, `/metrics` and
`/health` to the server on `:8080`), and the server itself.
Other commands:
```bash
npm test # vitest: node + web projects (77 backend + 8 dashboard tests)
npm run typecheck # tsc --noEmit for each workspace
npm run lint # eslint
npm run build # web bundle + server esbuild bundle
npm run clean # remove dist folders and local caches
npm run backup # online-safe SQLite backup into ./backups
npm run restore <path> # restore a backup after integrity checks
npm run helm:lint
```
Node 22 or newer is required (`better-sqlite3` prebuilds and the `node:`
imports). Tests: `npx vitest run --project node` runs only backend tests.
Real end-to-end calls to Jev (`packages/jev-client/src/real-api.test.ts`) are
skipped unless you export both `JEV_REAL_TESTS=1` and `DEFAPI_API_KEY`.
This keeps `npm test` offline and free by default.
---
## Deployment
### Docker
```bash
docker build -t jevai-mcp:1.0.0 .
docker run -p 8080:8080 -p 3001:3001 \
-e DEFAPI_API_KEY=dk-... \
-e PUBLIC_HOSTNAME=yourhost.internal \
-v jevai-data:/data \
jevai-mcp:1.0.0
```
The image runs as a non-root user with a read-only root filesystem and a
healthcheck. `/data` holds the SQLite database. A host volume is enough; the
database needs no external service.
### Kubernetes
```bash
kubectl create secret generic jevai-mcp-secrets --from-literal=DEFAPI_API_KEY=dk-...
kubectl apply -f kubernetes/
```
The plain manifests keep one replica with `Recreate` because SQLite runs on a
`ReadWriteOnce` volume. Probes point at `/health/live` and `/health/ready`.
### Helm
```bash
helm install jevai ./helm/jevai-mcp \
--set jevaiApiKey=dk-... \
--set integration.publicHostname=mcp.internal.example
```
`ingress.enabled` is `false` by default on purpose (no login). If you enable
it, put an authenticating proxy or VPN in front. The chart fails on
`replicaCount != 1` and on bad privacy settings. It sets a `checksum/config`
pod annotation so a ConfigMap change restarts the pod.
---
## Operations
**Back up.** Stop the container or use the online-safe script, then store the
file:
```bash
npm run backup /backups # uses better-sqlite3 backup API
DATABASE_PATH=/data/jevai.db ./scripts/backup.sh /backups
```
**Restore** takes the backup path and keeps the current database at
`<path>.before-restore`:
```bash
npm run restore /backups/jevai-20260926T101010Z.db
```
**Retention** deletes old rows on startup and every hour. The System page has
a "Run retention now" button and `POST /api/system/run-retention`.
**Diagnostics** bundles settings, health, connection history and logs into a
ZIP with redactions. The API key never appears in it. Download from the System
page or `GET /api/system/diagnostics`.
**API keys.** Keys go through `DEFAPI_API_KEY` (recommended) or the database
through Settings. In the database the key is hashed and shown masked
(`dk-****abcd`). A key from the environment cannot be removed on the Settings
page; the page tells you to unset the variable.
---
## Security
- **No login.** Internal tool only. Keep it inside the private network, a
VPN, or behind an identity-aware proxy. Both Docker and Helm keep it
`ClusterIP` / no ingress by default for this reason.
- **Secrets in transit and at rest.** TLS to DefAPI; no key in logs,
metrics, errors, ZIP downloads or the diagnostics bundle. Request
error messages are redacted before storage.
- **Headers.** The server sends `X-Content-Type-Options`,
`X-Frame-Options: DENY`, a strict `Content-Security-Policy`,
`Referrer-Policy` and `Permissions-Policy`, and blocks cross-origin
writes by comparing `Origin` to `Host`.
- **Input limits.** JSON bodies are size-limited, and metric labels are
bounded to low-cardinality values.
If you expose this anywhere users can reach, add an auth proxy and also set a
strong `PUBLIC_HOSTNAME` so the downloads do not invite credential-stuffing
against the wrong URL.
---
## Project layout
```
apps/server Hono HTTP + MCP server (API routes under src/routes)
apps/web React dashboard (pages under src/pages)
packages/jev-client The DefAPI HTTP client: retries, errors, connection state
packages/database SQLite access: repositories, migrations, retention, key store
packages/mcp-tools The five MCP tools: schema, question builders, fail-open
packages/shared Settings, types, wiring, time ranges, constants
integrations/ Plain copies of the files each agent ZIP contains
scripts/ dev, clean, backup, restore, guide generator
kubernetes/ Plain manifests (secret, pvc, deployment, service)
helm/jevai-mcp Helm chart with the same content plus ingress off by default
.github, .gitlab-ci.yml CI: lint, typecheck, test, build, docker, helm
```
## License
MIT. See [LICENSE](LICENSE).
## Related
- [Jev decision model](https://jev.sh) - the model behind the decisions
- [DefAPI gateway](https://defapi.org) - how you reach it
- [Model Context Protocol](https://modelcontextprotocol.io) - the protocol
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues