@jmtrin/kevin-mcp 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -240
- package/dist/server.js +2 -13
- package/dist/tools/read.js +0 -56
- package/dist/tools/write.js +2 -14
- package/package.json +33 -35
package/README.md
CHANGED
|
@@ -1,240 +1,242 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
# ⚡ Kevin MCP — Bridge
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
╔══════════════════════════════════════════════╗
|
|
7
|
-
║ @jmtrin/kevin-mcp
|
|
8
|
-
║ MCP Bridge for Kevin ║
|
|
9
|
-
║ stdio · zero network · WAL ║
|
|
10
|
-
╚══════════════════════════════════════════════╝
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
### Same SQLite, every harness — Kevin via Model Context Protocol
|
|
14
|
-
|
|
15
|
-
**Kevin MCP exposes the local `kevin.db` to Claude Code, Codex, Cursor, Windsurf, Gemini CLI and Opencode through one stdio server.**
|
|
16
|
-
|
|
17
|
-

|
|
19
|
-

|
|
20
|
-

|
|
21
|
-

|
|
22
|
-
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
|
-
> **No new database. No HTTP. One WAL file, two processes.**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
- [
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
- [
|
|
42
|
-
- [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
npx -y @jmtrin/kevin-mcp
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
|
96
|
-
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
|
114
|
-
|
|
115
|
-
| `
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
"
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
|
160
|
-
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
- **
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
src/
|
|
190
|
-
src/
|
|
191
|
-
src/
|
|
192
|
-
src/
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
|
204
|
-
|
|
205
|
-
| `
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
npm
|
|
228
|
-
|
|
229
|
-
npm
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ⚡ Kevin MCP — Bridge
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
╔══════════════════════════════════════════════╗
|
|
7
|
+
║ @jmtrin/kevin-mcp 2.0.0 ║
|
|
8
|
+
║ MCP Bridge for Kevin ║
|
|
9
|
+
║ stdio · zero network · WAL ║
|
|
10
|
+
╚══════════════════════════════════════════════╝
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Same SQLite, every harness — Kevin via Model Context Protocol
|
|
14
|
+
|
|
15
|
+
**Kevin MCP exposes the local `kevin.db` to Claude Code, Codex, Cursor, Windsurf, Gemini CLI and Opencode through one stdio server.**
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+

|
|
19
|
+

|
|
20
|
+

|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
> **No new database. No HTTP. One WAL file, two processes.**
|
|
26
|
+
|
|
27
|
+
> **Commonwealth (2.0.0):** same bridge, same WAL, now with `kevin_sources` provenance and sharded OKF v3 support (`014`) — reader walks `knowledge/` shards, writer gated by `okf_write_version`.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 📖 Contents
|
|
32
|
+
|
|
33
|
+
- [Why a Bridge](#-why-a-bridge)
|
|
34
|
+
- [Quick start](#-quick-start)
|
|
35
|
+
- [Tools (11)](#-tools-11)
|
|
36
|
+
- [Reads vs writes — gates](#-reads-vs-writes--gates)
|
|
37
|
+
- [Identity & repo scope](#-identity--repo-scope)
|
|
38
|
+
- [Provenance](#-provenance)
|
|
39
|
+
- [Harnesses](#-harnesses)
|
|
40
|
+
- [Architecture](#-architecture)
|
|
41
|
+
- [Configuration](#%EF%B8%8F-configuration)
|
|
42
|
+
- [Latency budgets](#-latency-budgets)
|
|
43
|
+
- [Development](#-development)
|
|
44
|
+
- [License](#-license)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 🤔 Why a Bridge
|
|
49
|
+
|
|
50
|
+
OpenCode plugin memory is excellent inside OpenCode. The bridge makes that same ranked recall available to **any MCP host** without a second sync, a server, or a cloud hop.
|
|
51
|
+
|
|
52
|
+
| Before | After |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `kevin.db` only reachable from plugin | `kevin.db` shared via WAL + `busy_timeout=5000` |
|
|
55
|
+
| one host | 6 harnesses (Claude Code / Codex / Cursor / Windsurf / Gemini CLI / Opencode) |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🚀 Quick start
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx -y @jmtrin/kevin-mcp # ro by default, stderr ready line
|
|
63
|
+
npx -y @jmtrin/kevin-mcp --repo 2114ad162af50a25 # pinned repo_id
|
|
64
|
+
npx -y @jmtrin/kevin-mcp --help
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Claude Code (`~/.claude.json`)
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"kevin-mcp": { "command": "npx", "args": ["-y", "@jmtrin/kevin-mcp"] }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Codex / Cursor / Windsurf / Gemini CLI
|
|
78
|
+
|
|
79
|
+
See `docs/harnesses/{codex,cursor,windsurf,gemini-cli,opencode}.md` for exact JSON/TOML snippets, verification (`npx @jmtrin/kevin-mcp --version`), troubleshooting and uninstall.
|
|
80
|
+
|
|
81
|
+
### How to verify inside a host
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
what does Kevin remember about <topic>?
|
|
85
|
+
→ recall returns results + provenance {repo_id, identity_source, channel:"mcp"}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 🧰 Tools (11)
|
|
91
|
+
|
|
92
|
+
<details open>
|
|
93
|
+
<summary><b>Always-on reads (7) + ping</b></summary>
|
|
94
|
+
|
|
95
|
+
| Tool | What it does | Ledger |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `ping` | Liveness probe | — |
|
|
98
|
+
| `query` | Slim search | — |
|
|
99
|
+
| `get` | Fetch one memory | — |
|
|
100
|
+
| `recall` | Ranked recall (BM25 × origin × recency → token budget) | `channel='mcp'` injection row, tokens `ceil(chars/4)` |
|
|
101
|
+
| `why` | Failure→fix trace via `kevinWhy` | — |
|
|
102
|
+
| `status` | `{repo_id, resolved_repo_id, requested_repo_id, gates, counters{requests,reads,writes_accepted/refused}, perf}` | — |
|
|
103
|
+
| `trace` | Dry-run injection preview, zero side effects | — |
|
|
104
|
+
| `feedback` | Rate a memory `useful/wrong/outdated/ignore` | — |
|
|
105
|
+
|
|
106
|
+
Every read returns `provenance: {repo_id, identity_source, channel:"mcp"}` plus confidence/evidence when the underlying row carries them.
|
|
107
|
+
|
|
108
|
+
</details>
|
|
109
|
+
|
|
110
|
+
<details>
|
|
111
|
+
<summary><b>Gated writes (3) — disabled by default</b></summary>
|
|
112
|
+
|
|
113
|
+
| Tool | Gate | On refuse |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `save` | `mcp_write_enabled==='1'` | `{error:"disabled", hint:"set mcp_write_enabled=1"}` + `mcp_writes_refused++` |
|
|
116
|
+
| `approve` | `mcp_write_enabled==='1'` **and** `mcp_approve_enabled==='1'` | same (`disabled`) |
|
|
117
|
+
| `share` | double-gate as `approve` (and existing `share_requires_approval` chain) | `disabled` → `missing_approval_chain` → `executed` |
|
|
118
|
+
|
|
119
|
+
On success `mcp_writes_accepted++`. All writes are repo-scoped and funnel through core's single write path.
|
|
120
|
+
|
|
121
|
+
</details>
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 🔐 Identity & repo scope
|
|
126
|
+
|
|
127
|
+
Repository identity resolves **once** at server init, in order:
|
|
128
|
+
|
|
129
|
+
`--repo <id>` (`KEVIN_REPO`) → `kevin_settings.mcp_repo_override` (`setting:mcp_repo_override`) → `RepoIdentity.resolve` (declared → remote hash → path).
|
|
130
|
+
|
|
131
|
+
Every tool carries an optional `repo_id` arg; mismatch returns:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{ "error": "repo_mismatch", "expected": "<server repo_id>", "got": "<caller repo_id>" }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Declared ids must be 16-char lowercase hex (`/^[0-9a-f]{16}$/`).
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 🧾 Provenance
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"provenance": {
|
|
146
|
+
"repo_id": "2114ad162af50a25",
|
|
147
|
+
"identity_source": "setting:mcp_repo_override",
|
|
148
|
+
"channel": "mcp"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
When the memory row has confidence/evidence, those fields surface as `confidence`, `evidence_count`, `last_verified_at`.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 🔌 Harnesses
|
|
158
|
+
|
|
159
|
+
| Harness | Recipe | Tested-on |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| Claude Code | `docs/harnesses/claude-code.md` | claude-code v1.0.0 |
|
|
162
|
+
| Codex | `docs/harnesses/codex.md` | codex v0.5.0 |
|
|
163
|
+
| Cursor | `docs/harnesses/cursor.md` | cursor v1.2.0 |
|
|
164
|
+
| Windsurf | `docs/harnesses/windsurf.md` | windsurf v1.0 |
|
|
165
|
+
| Gemini CLI | `docs/harnesses/gemini-cli.md` | gemini-cli v0.4.0 |
|
|
166
|
+
| Opencode | `docs/harnesses/opencode.md` | opencode v1.18.0 |
|
|
167
|
+
|
|
168
|
+
Each recipe is a tested JSON/TOML block (parsed by `tests/unit/docs_config_lint.test.ts`), verification command, troubleshooting and uninstall note. Demo: `docs/demo-cross-harness.md` (10-min `▶` script: opencode `save` → Claude Code `recall`).
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## ⚙️ Architecture
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
plugin (opencode) ─┐
|
|
176
|
+
├─► kevin.db (WAL, busy_timeout=5000) ◄─ kevin-mcp (stdio, SDK 1.30.0)
|
|
177
|
+
MCP host (any) ────┘ │ ▲ │
|
|
178
|
+
│ └─ kevin_audit mcp block ─┘
|
|
179
|
+
└── perf_samples (mcp.read/write)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- **Zero network** — forbidden list enforced: `node:http/https/net/dgram, fetch, XMLHttpRequest, SSETransport, HttpTransport, child_process/spawn`; only `node:fs/path/os/sqlite`.
|
|
183
|
+
- **Logs on stderr only** — `stderr ready repo=... mode=ro|rw db=...`; stdout is MCP JSON only.
|
|
184
|
+
- **Lifecycle** — `resolveEnv → Store(busy_timeout) → Migrate(014) → Metrics+Perf → registry`; SIGINT/SIGTERM flush metrics + perf (every 100 req or signal).
|
|
185
|
+
|
|
186
|
+
Package layout:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
src/server.ts lifecycle, registry, ready line, signals
|
|
190
|
+
src/identity.ts resolution + mismatch guard
|
|
191
|
+
src/tools/read.ts 7 reads + ping + provenance helper
|
|
192
|
+
src/tools/write.ts 3 gated writes
|
|
193
|
+
src/provenance.ts block builder
|
|
194
|
+
src/perf-mcp.ts mcp.read/write budgets wrapper
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## ⚙️ Configuration
|
|
200
|
+
|
|
201
|
+
Text settings via `kevin_config` (compare `=== "1"`):
|
|
202
|
+
|
|
203
|
+
| Key | Default | Purpose |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `mcp_write_enabled` | `'0'` | Gate for MCP `save` |
|
|
206
|
+
| `mcp_approve_enabled` | `'0'` | Double-gate for MCP `approve`/`share` |
|
|
207
|
+
| `mcp_repo_override` | `''` | Override RepoIdentity (hex-16) |
|
|
208
|
+
|
|
209
|
+
Plus the 40 core/plugin keys (`C-04` since `2.0.0` → golden 43 settings: `+sources_enabled`, `+source_*×3`, `+okf_write_version`, `-import_host_memory`). `kevin_audit` block `mcp` appears only on schema `013`+; pre-013 omitted (`partial:true`). Channel split counters in `kevin_metrics` + `kevin_injections.channel`.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## ⏱️ Latency budgets
|
|
214
|
+
|
|
215
|
+
| Scope | p95 | max |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| `mcp.read` | 25 ms | 100 ms |
|
|
218
|
+
| `mcp.write` | 50 ms | 250 ms |
|
|
219
|
+
|
|
220
|
+
Measured on reference laptop: recall p50 ≈ 0.2ms. Persists to `perf_samples` every 100 requests or SIGINT; `bench:check` enforces.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 🛠️ Development
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npm install -w @jmtrin/kevin-mcp
|
|
228
|
+
npm run build -w @jmtrin/kevin-mcp
|
|
229
|
+
npm run typecheck -w @jmtrin/kevin-mcp
|
|
230
|
+
npx vitest run packages/mcp/tests/purity_scan.test.ts
|
|
231
|
+
npm pack --dry-run -w @jmtrin/kevin-mcp
|
|
232
|
+
# boot smoke (no stdout, stderr ready):
|
|
233
|
+
node packages/mcp/dist/server.js --help
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Monorepo publish order: `core → tui → plugin → mcp` (exact `2.0.0` pin), see `docs/DISTRIBUTION.md`.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 📄 License
|
|
241
|
+
|
|
242
|
+
MIT — see `LICENSE`. Kevin is built by [jmtrin](https://github.com/jmtrin); bug reports and PRs welcome at the [issue tracker](https://github.com/jmtrin/opencode-kevin/issues).
|
package/dist/server.js
CHANGED
|
@@ -12,7 +12,7 @@ import { resolveEnv } from "@jmtrin/kevin-core";
|
|
|
12
12
|
import { resolveMcpIdentity, readSettingOverride } from "./identity.js";
|
|
13
13
|
import { createReadTools } from "./tools/read.js";
|
|
14
14
|
import { createWriteTools } from "./tools/write.js";
|
|
15
|
-
const KEVIN_VERSION = "1.
|
|
15
|
+
const KEVIN_VERSION = "1.5.0";
|
|
16
16
|
const { values } = parseArgs({
|
|
17
17
|
options: {
|
|
18
18
|
help: { type: "boolean", default: false },
|
|
@@ -132,18 +132,7 @@ const registry = [
|
|
|
132
132
|
];
|
|
133
133
|
// Register with MCP server
|
|
134
134
|
for (const tool of registry) {
|
|
135
|
-
|
|
136
|
-
// For compatibility, we register with empty zod object via SDK's internal conversion
|
|
137
|
-
// Use any to bypass typing
|
|
138
|
-
server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, async (args) => {
|
|
139
|
-
// Wrap already does metrics; but ping needs manual wrap? Use tool.handler directly with wrapper already applied for read/write tools.
|
|
140
|
-
// For ping, apply wrap
|
|
141
|
-
if (tool.name === "ping") {
|
|
142
|
-
const w = wrap("mcp.read", tool.handler);
|
|
143
|
-
return await w(args);
|
|
144
|
-
}
|
|
145
|
-
return await tool.handler(args).then((r) => ({ content: [{ type: "text", text: JSON.stringify(r) }] }));
|
|
146
|
-
});
|
|
135
|
+
server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, wrap("mcp.read", tool.handler));
|
|
147
136
|
}
|
|
148
137
|
const transport = new StdioServerTransport();
|
|
149
138
|
await server.connect(transport);
|
package/dist/tools/read.js
CHANGED
|
@@ -20,14 +20,6 @@ export function createReadTools(opts) {
|
|
|
20
20
|
const mismatch = assertScope(identity.repoId, args.repo_id);
|
|
21
21
|
if (mismatch)
|
|
22
22
|
return mismatch;
|
|
23
|
-
try {
|
|
24
|
-
metrics.incr("mcp_requests_total", 1);
|
|
25
|
-
}
|
|
26
|
-
catch { }
|
|
27
|
-
try {
|
|
28
|
-
metrics.incr("mcp_reads_served", 1);
|
|
29
|
-
}
|
|
30
|
-
catch { }
|
|
31
23
|
const svc = memService();
|
|
32
24
|
const memories = svc.query({
|
|
33
25
|
text: args.query,
|
|
@@ -50,14 +42,6 @@ export function createReadTools(opts) {
|
|
|
50
42
|
const mismatch = assertScope(identity.repoId, args.repo_id);
|
|
51
43
|
if (mismatch)
|
|
52
44
|
return mismatch;
|
|
53
|
-
try {
|
|
54
|
-
metrics.incr("mcp_requests_total", 1);
|
|
55
|
-
}
|
|
56
|
-
catch { }
|
|
57
|
-
try {
|
|
58
|
-
metrics.incr("mcp_reads_served", 1);
|
|
59
|
-
}
|
|
60
|
-
catch { }
|
|
61
45
|
const svc = memService();
|
|
62
46
|
const mem = svc.getById(args.id);
|
|
63
47
|
if (!mem)
|
|
@@ -73,14 +57,6 @@ export function createReadTools(opts) {
|
|
|
73
57
|
const mismatch = assertScope(identity.repoId, args.repo_id);
|
|
74
58
|
if (mismatch)
|
|
75
59
|
return mismatch;
|
|
76
|
-
try {
|
|
77
|
-
metrics.incr("mcp_requests_total", 1);
|
|
78
|
-
}
|
|
79
|
-
catch { }
|
|
80
|
-
try {
|
|
81
|
-
metrics.incr("mcp_reads_served", 1);
|
|
82
|
-
}
|
|
83
|
-
catch { }
|
|
84
60
|
const svc = memService();
|
|
85
61
|
const memories = svc.getRelevant({
|
|
86
62
|
query: args.query,
|
|
@@ -108,14 +84,6 @@ export function createReadTools(opts) {
|
|
|
108
84
|
const mismatch = assertScope(identity.repoId, args.repo_id);
|
|
109
85
|
if (mismatch)
|
|
110
86
|
return mismatch;
|
|
111
|
-
try {
|
|
112
|
-
metrics.incr("mcp_requests_total", 1);
|
|
113
|
-
}
|
|
114
|
-
catch { }
|
|
115
|
-
try {
|
|
116
|
-
metrics.incr("mcp_reads_served", 1);
|
|
117
|
-
}
|
|
118
|
-
catch { }
|
|
119
87
|
const res = kevinWhy(store, args.query);
|
|
120
88
|
if (!res)
|
|
121
89
|
return { error: "not_found", query: args.query, provenance: buildProvenance(identity) };
|
|
@@ -128,14 +96,6 @@ export function createReadTools(opts) {
|
|
|
128
96
|
inputSchema: {},
|
|
129
97
|
handler: async (args) => {
|
|
130
98
|
// status bypasses mismatch but reports both
|
|
131
|
-
try {
|
|
132
|
-
metrics.incr("mcp_requests_total", 1);
|
|
133
|
-
}
|
|
134
|
-
catch { }
|
|
135
|
-
try {
|
|
136
|
-
metrics.incr("mcp_reads_served", 1);
|
|
137
|
-
}
|
|
138
|
-
catch { }
|
|
139
99
|
const requested = args.repo_id ?? null;
|
|
140
100
|
const mismatch = requested ? assertScope(identity.repoId, requested) : null;
|
|
141
101
|
const memCount = store.prepare("SELECT COUNT(*) as c FROM memories").get().c;
|
|
@@ -174,14 +134,6 @@ export function createReadTools(opts) {
|
|
|
174
134
|
const mismatch = assertScope(identity.repoId, args.repo_id);
|
|
175
135
|
if (mismatch)
|
|
176
136
|
return mismatch;
|
|
177
|
-
try {
|
|
178
|
-
metrics.incr("mcp_requests_total", 1);
|
|
179
|
-
}
|
|
180
|
-
catch { }
|
|
181
|
-
try {
|
|
182
|
-
metrics.incr("mcp_reads_served", 1);
|
|
183
|
-
}
|
|
184
|
-
catch { }
|
|
185
137
|
const svc = memService();
|
|
186
138
|
const memories = svc.getRelevant({ query: args.query, maxTokens: 2500, scope: "all" });
|
|
187
139
|
return { dry_run: true, results: memories.map((m) => ({ id: m.id, type: m.type, scope: m.scope })), provenance: buildProvenance(identity) };
|
|
@@ -195,14 +147,6 @@ export function createReadTools(opts) {
|
|
|
195
147
|
const mismatch = assertScope(identity.repoId, args.repo_id);
|
|
196
148
|
if (mismatch)
|
|
197
149
|
return mismatch;
|
|
198
|
-
try {
|
|
199
|
-
metrics.incr("mcp_requests_total", 1);
|
|
200
|
-
}
|
|
201
|
-
catch { }
|
|
202
|
-
try {
|
|
203
|
-
metrics.incr("mcp_reads_served", 1);
|
|
204
|
-
}
|
|
205
|
-
catch { }
|
|
206
150
|
const fb = new Feedback(store, metrics);
|
|
207
151
|
try {
|
|
208
152
|
const id = fb.record({ memoryId: args.id, verdict: args.verdict, sessionId: `mcp:${Date.now()}` });
|
package/dist/tools/write.js
CHANGED
|
@@ -31,10 +31,6 @@ export function createWriteTools(opts) {
|
|
|
31
31
|
const mismatch = assertScope(identity.repoId, args.repo_id);
|
|
32
32
|
if (mismatch)
|
|
33
33
|
return mismatch;
|
|
34
|
-
try {
|
|
35
|
-
metrics.incr("mcp_requests_total", 1);
|
|
36
|
-
}
|
|
37
|
-
catch { }
|
|
38
34
|
if (!isWriteEnabled()) {
|
|
39
35
|
try {
|
|
40
36
|
metrics.incr("mcp_writes_refused", 1);
|
|
@@ -44,7 +40,7 @@ export function createWriteTools(opts) {
|
|
|
44
40
|
}
|
|
45
41
|
const svc = new MemoryService(store, metrics, identity.repoId);
|
|
46
42
|
try {
|
|
47
|
-
const
|
|
43
|
+
const id = svc.save({
|
|
48
44
|
type: args.type,
|
|
49
45
|
content: args.content,
|
|
50
46
|
scope: args.scope ?? "project",
|
|
@@ -53,7 +49,7 @@ export function createWriteTools(opts) {
|
|
|
53
49
|
metrics.incr("mcp_writes_accepted", 1);
|
|
54
50
|
}
|
|
55
51
|
catch { }
|
|
56
|
-
return { id
|
|
52
|
+
return { id, provenance: buildProvenance(identity) };
|
|
57
53
|
}
|
|
58
54
|
catch (e) {
|
|
59
55
|
try {
|
|
@@ -72,10 +68,6 @@ export function createWriteTools(opts) {
|
|
|
72
68
|
const mismatch = assertScope(identity.repoId, args.repo_id);
|
|
73
69
|
if (mismatch)
|
|
74
70
|
return mismatch;
|
|
75
|
-
try {
|
|
76
|
-
metrics.incr("mcp_requests_total", 1);
|
|
77
|
-
}
|
|
78
|
-
catch { }
|
|
79
71
|
if (!isWriteEnabled() || !isApproveEnabled()) {
|
|
80
72
|
try {
|
|
81
73
|
metrics.incr("mcp_writes_refused", 1);
|
|
@@ -111,10 +103,6 @@ export function createWriteTools(opts) {
|
|
|
111
103
|
const mismatch = assertScope(identity.repoId, args.repo_id);
|
|
112
104
|
if (mismatch)
|
|
113
105
|
return mismatch;
|
|
114
|
-
try {
|
|
115
|
-
metrics.incr("mcp_requests_total", 1);
|
|
116
|
-
}
|
|
117
|
-
catch { }
|
|
118
106
|
if (!isWriteEnabled() || !isApproveEnabled()) {
|
|
119
107
|
try {
|
|
120
108
|
metrics.incr("mcp_writes_refused", 1);
|
package/package.json
CHANGED
|
@@ -1,37 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
36
|
-
"license": "MIT"
|
|
2
|
+
"name": "@jmtrin/kevin-mcp",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Kevin MCP — stdio bridge for any MCP harness (Bridge)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kevin-mcp": "dist/server.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/server.js",
|
|
10
|
+
"types": "dist/server.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/server.d.ts",
|
|
14
|
+
"import": "./dist/server.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": ["dist"],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@jmtrin/kevin-core": "2.0.0",
|
|
25
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
26
|
+
"zod": "^3.25.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22.5.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT"
|
|
37
35
|
}
|