purecontext-mcp 1.1.1 → 1.1.2
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/package.json +1 -1
- package/docs/dev/API_STABILITY.md +0 -319
- package/docs/dev/DECISIONS.md +0 -22
- package/docs/dev/DOCUMENTATION_PLAN.md +0 -113
- package/docs/dev/PHASE10_TASKS.md +0 -476
- package/docs/dev/PHASE11_TASKS.md +0 -385
- package/docs/dev/PHASE12_TASKS.md +0 -335
- package/docs/dev/PHASE13_TASKS.md +0 -381
- package/docs/dev/PHASE14_TASKS.md +0 -371
- package/docs/dev/PHASE15_TASKS.md +0 -256
- package/docs/dev/PHASE16_TASKS.md +0 -314
- package/docs/dev/PHASE17_TASKS.md +0 -321
- package/docs/dev/PHASE18_TASKS.md +0 -345
- package/docs/dev/PHASE19_TASKS.md +0 -261
- package/docs/dev/PHASE1_TASKS.md +0 -443
- package/docs/dev/PHASE20_TASKS.md +0 -280
- package/docs/dev/PHASE21_TASKS.md +0 -355
- package/docs/dev/PHASE22_TASKS.md +0 -371
- package/docs/dev/PHASE23_TASKS.md +0 -274
- package/docs/dev/PHASE24_TASKS.md +0 -326
- package/docs/dev/PHASE25_TASKS.md +0 -452
- package/docs/dev/PHASE26_TASKS.md +0 -253
- package/docs/dev/PHASE27_TASKS.md +0 -410
- package/docs/dev/PHASE2_TASKS.md +0 -328
- package/docs/dev/PHASE3_TASKS.md +0 -571
- package/docs/dev/PHASE4_TASKS.md +0 -531
- package/docs/dev/PHASE5_TASKS.md +0 -835
- package/docs/dev/PHASE6_TASKS.md +0 -347
- package/docs/dev/PHASE7_TASKS.md +0 -257
- package/docs/dev/PHASE8_TASKS.md +0 -299
- package/docs/dev/PHASE9_TASKS.md +0 -320
- package/docs/dev/PureContext_MCP_PRD_v1.0.docx +0 -0
- package/docs/dev/SELF_HOSTING.md +0 -142
- package/docs/dev/TEAM_SETUP.md +0 -316
- package/docs/dev/TELEMETRY.md +0 -99
- package/docs/dev/feature-analysis.md +0 -305
- package/docs/dev/phase-1-notes.md +0 -3
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
# Phase 18 — Team & Cloud Features (Monetization Foundation)
|
|
2
|
-
|
|
3
|
-
**Goal**: Extend PureContext from a single-user local tool into a team-capable server with shared indexes, API key authentication, and workspace management — the technical foundation for a paid team/cloud tier.
|
|
4
|
-
|
|
5
|
-
**Scope rationale**: PureContext currently runs on each developer's machine with a local SQLite index. Teams working on the same codebase re-index independently, wasting compute and getting out of sync. A shared index server means: one machine indexes the repo, all team members query it. This is the natural team feature and the natural monetization lever — the server is where access control, billing, and enterprise features live. Phases 16–17 establish a solid open-source single-user product. Phase 18 adds the team layer on top, entirely through the HTTP transport that already exists in `src/server/http-server.ts`.
|
|
6
|
-
|
|
7
|
-
**Architecture shift:**
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
Phase 1–17 (local mode): Phase 18 (server mode):
|
|
11
|
-
AI Agent AI Agent (each team member)
|
|
12
|
-
↓ stdio ↓ HTTP + API key
|
|
13
|
-
MCP Server (local) MCP Server (shared, always-on)
|
|
14
|
-
↓ ↓
|
|
15
|
-
Local SQLite index Shared SQLite index(es)
|
|
16
|
-
↓
|
|
17
|
-
Workspace + API key DB
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Both modes continue to work. `stdio` mode remains the default for individual use. `--server` flag starts the HTTP mode.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Task 127: API Key Authentication
|
|
25
|
-
|
|
26
|
-
Add API key authentication to the HTTP transport so the shared server can authenticate multiple team members.
|
|
27
|
-
|
|
28
|
-
**Current state**: The HTTP server in `src/server/http-server.ts` has no authentication. Any client with network access can call any endpoint.
|
|
29
|
-
|
|
30
|
-
**Deliverables:**
|
|
31
|
-
|
|
32
|
-
- `src/core/db/api-keys.ts` — already exists (from Phase 12). Review and extend:
|
|
33
|
-
- `createApiKey(db, workspaceId, label, permissions)` — generates a `pctx_` prefixed key (e.g., `pctx_abc123def456`).
|
|
34
|
-
- `validateApiKey(db, key)` — returns `{ workspaceId, permissions }` or null.
|
|
35
|
-
- `revokeApiKey(db, keyId)`.
|
|
36
|
-
- `listApiKeys(db, workspaceId)` — returns keys with labels and last-used timestamps (never the key value itself after creation).
|
|
37
|
-
- Add `last_used_at` column to `api_keys` table (migration: `ALTER TABLE api_keys ADD COLUMN last_used_at INTEGER`).
|
|
38
|
-
- Key format: `pctx_` + 32 random hex chars. Store only the SHA-256 hash in DB (never the plaintext).
|
|
39
|
-
|
|
40
|
-
- `src/server/http-server.ts`:
|
|
41
|
-
- Add authentication middleware:
|
|
42
|
-
```typescript
|
|
43
|
-
function requireApiKey(req, res, next) {
|
|
44
|
-
const key = req.headers['authorization']?.replace('Bearer ', '');
|
|
45
|
-
if (!key) return res.status(401).json({ error: 'API key required. Pass as Authorization: Bearer pctx_...' });
|
|
46
|
-
const auth = validateApiKey(db, key);
|
|
47
|
-
if (!auth) return res.status(403).json({ error: 'Invalid or revoked API key.' });
|
|
48
|
-
req.auth = auth;
|
|
49
|
-
updateLastUsed(db, key);
|
|
50
|
-
next();
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
- Apply to all `/api/*` routes.
|
|
54
|
-
- Exempt: `GET /health`, `GET /` (UI), static assets.
|
|
55
|
-
- Rate limit per API key (re-use Phase 12 rate limiter, key = API key hash).
|
|
56
|
-
|
|
57
|
-
- `src/server/admin-api.ts` — already exists (from Phase 12). Extend:
|
|
58
|
-
- `POST /admin/keys` — create API key. Requires admin key (separate `PCTX_ADMIN_KEY` env var).
|
|
59
|
-
- `DELETE /admin/keys/:id` — revoke key. Admin only.
|
|
60
|
-
- `GET /admin/keys` — list keys (without values). Admin only.
|
|
61
|
-
- `GET /admin/keys/:id/usage` — last used, request count. Admin only.
|
|
62
|
-
|
|
63
|
-
- `src/config/keys-cli.ts` — already exists. Extend:
|
|
64
|
-
- `purecontext-mcp keys create --label "Alice's machine"` — creates and prints a key once.
|
|
65
|
-
- `purecontext-mcp keys list` — lists keys with labels and last-used.
|
|
66
|
-
- `purecontext-mcp keys revoke <keyId>` — revokes a key.
|
|
67
|
-
- These commands connect to the local server (or the configured remote URL) using the admin key.
|
|
68
|
-
|
|
69
|
-
- `src/config/config-schema.ts`:
|
|
70
|
-
- Add `server.requireAuth: boolean` (default: `false` for local stdio mode, `true` for `--server` mode).
|
|
71
|
-
- Add `server.adminKey: string` — read from env `PCTX_ADMIN_KEY` first, then config (never commit to source).
|
|
72
|
-
|
|
73
|
-
**Key technical notes:**
|
|
74
|
-
- In `stdio` mode (default), authentication is disabled entirely — the local user doesn't need API keys to use their own machine.
|
|
75
|
-
- In `--server` mode, authentication is on by default. The first-run message explains how to create a key.
|
|
76
|
-
- The admin key is separate from user API keys. It should only be set via environment variable in production (`PCTX_ADMIN_KEY=...`), never in config.json (which might be committed).
|
|
77
|
-
- Store only SHA-256(key) in the database. The plaintext key is shown once on creation and never again.
|
|
78
|
-
- `pctx_` prefix makes keys easily identifiable in logs and grep.
|
|
79
|
-
|
|
80
|
-
**Tests:**
|
|
81
|
-
|
|
82
|
-
- `test/server/api-auth.test.ts`:
|
|
83
|
-
- Request without key → 401 with helpful message.
|
|
84
|
-
- Request with invalid key → 403.
|
|
85
|
-
- Request with valid key → 200, `req.auth` populated.
|
|
86
|
-
- Revoked key → 403.
|
|
87
|
-
- `last_used_at` updated on use.
|
|
88
|
-
- Admin endpoints reject non-admin keys.
|
|
89
|
-
- `/health` endpoint accessible without auth.
|
|
90
|
-
- Rate limiter applies per API key (not global).
|
|
91
|
-
|
|
92
|
-
- `test/cli/keys-cli.test.ts`:
|
|
93
|
-
- `keys create` prints key once in `pctx_` format.
|
|
94
|
-
- `keys list` shows labels and masked keys.
|
|
95
|
-
- `keys revoke` removes key from DB.
|
|
96
|
-
|
|
97
|
-
**Verify:** Start server with `--server`. Attempt `curl localhost:3000/api/repos` without auth — get 401. Create a key with `keys create`. Use it in `curl -H "Authorization: Bearer pctx_..."`. Get 200.
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Task 128: Workspace Management
|
|
102
|
-
|
|
103
|
-
Add workspace support so multiple teams or projects can share one server with isolated indexes.
|
|
104
|
-
|
|
105
|
-
**Current state**: The multi-tenant framework exists from Phase 12 (`src/core/db/tenants.ts`). Phase 18 surfaces it to users via the CLI and makes it the organizational unit for billing/access control.
|
|
106
|
-
|
|
107
|
-
**Deliverables:**
|
|
108
|
-
|
|
109
|
-
- `src/core/db/tenants.ts` — review and extend:
|
|
110
|
-
- `createWorkspace(db, name, ownerId)` → `workspaceId`.
|
|
111
|
-
- `listWorkspaces(db)` → all workspaces with member count and repo count.
|
|
112
|
-
- `addMember(db, workspaceId, userId, role)` — roles: `owner`, `admin`, `member`, `readonly`.
|
|
113
|
-
- `getWorkspaceForKey(db, apiKeyHash)` — used in auth middleware to scope queries.
|
|
114
|
-
- Every indexed repo is associated with a workspace: `repos.workspace_id` column.
|
|
115
|
-
|
|
116
|
-
- `src/core/db/schema.ts`:
|
|
117
|
-
- Migration: add `workspace_id TEXT` column to `repos` table.
|
|
118
|
-
- Add `workspaces` table: `id TEXT PK, name TEXT, created_at INTEGER, plan TEXT`.
|
|
119
|
-
- `plan` values: `'free'` (up to 3 repos, 10k files each), `'team'` (unlimited), `'enterprise'` (unlimited + support).
|
|
120
|
-
- Default workspace: on first run, create a `default` workspace. All existing repos migrate to it.
|
|
121
|
-
|
|
122
|
-
- `src/server/tools/index-repo.ts`:
|
|
123
|
-
- `index_folder` tool: accept optional `workspaceId` parameter. If omitted, use the workspace associated with the API key.
|
|
124
|
-
- All symbol queries are automatically scoped to `req.auth.workspaceId`.
|
|
125
|
-
|
|
126
|
-
- `src/server/tools/list-repos.ts`:
|
|
127
|
-
- Return only repos in the caller's workspace.
|
|
128
|
-
- Add `workspaceId` field to each repo in the response.
|
|
129
|
-
|
|
130
|
-
- `src/server/admin-api.ts`:
|
|
131
|
-
- `POST /admin/workspaces` — create workspace.
|
|
132
|
-
- `GET /admin/workspaces` — list all workspaces with repo and member counts.
|
|
133
|
-
- `POST /admin/workspaces/:id/members` — add member with role.
|
|
134
|
-
- `DELETE /admin/workspaces/:id/members/:userId` — remove member.
|
|
135
|
-
- `GET /admin/workspaces/:id/usage` — file count, symbol count, API calls this month.
|
|
136
|
-
|
|
137
|
-
- `src/config/cli.ts`:
|
|
138
|
-
- `purecontext-mcp workspaces list` — show all workspaces.
|
|
139
|
-
- `purecontext-mcp workspaces create --name "My Team"` — create workspace, print ID.
|
|
140
|
-
|
|
141
|
-
- Plan limits enforcement (minimal version):
|
|
142
|
-
- `free` plan: if workspace has ≥ 3 repos, `index_folder` returns a `LimitError` with message: "Free plan is limited to 3 repos. Upgrade to Team at purecontext.dev/pricing."
|
|
143
|
-
- File count limit per repo: `free` = 10k, `team` = unlimited.
|
|
144
|
-
- Limits are checked in `index-manager.ts` before indexing starts.
|
|
145
|
-
- The `plan` field can be set via admin API (manually, for now — no payment integration in Phase 18).
|
|
146
|
-
|
|
147
|
-
**Key technical notes:**
|
|
148
|
-
- "Workspace" is the billing unit. One workspace = one paying customer.
|
|
149
|
-
- Workspace isolation is enforced at the query level: `WHERE workspace_id = ?` on all symbol/repo queries. A team member cannot query another workspace's repos, even with a valid key.
|
|
150
|
-
- The default workspace on a local install has `plan: 'team'` (unlimited) so single users are never impacted by limits.
|
|
151
|
-
- Plan limits are intentionally simple in Phase 18 — the goal is to have the infrastructure in place, not to build a full billing system.
|
|
152
|
-
|
|
153
|
-
**Tests:**
|
|
154
|
-
|
|
155
|
-
- `test/server/workspace-isolation.test.ts`:
|
|
156
|
-
- Two workspaces, two API keys. Index a repo in workspace A. Verify workspace B's key cannot see it in `list_repos`.
|
|
157
|
-
- Query from workspace B returns empty, not an error.
|
|
158
|
-
- Admin can see all workspaces.
|
|
159
|
-
|
|
160
|
-
- `test/core/workspace-limits.test.ts`:
|
|
161
|
-
- `free` plan: index 3 repos (succeeds). Index 4th repo (fails with LimitError, correct message).
|
|
162
|
-
- `team` plan: index 10 repos (all succeed).
|
|
163
|
-
- File limit: `free` workspace with 10001 files returns LimitError before indexing starts.
|
|
164
|
-
|
|
165
|
-
**Verify:** Create two workspaces with separate API keys. Index different repos in each. Confirm each key only sees its own repos. Confirm admin sees both.
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## Task 129: Server Mode — `--server` Flag and Docker Support
|
|
170
|
-
|
|
171
|
-
Make it easy to deploy PureContext as an always-on shared server for a team, including a Docker image.
|
|
172
|
-
|
|
173
|
-
**Deliverables:**
|
|
174
|
-
|
|
175
|
-
- `src/index.ts`:
|
|
176
|
-
- Add `--server` flag: starts the HTTP server instead of the stdio MCP server.
|
|
177
|
-
- `--server --port 3000 --host 0.0.0.0` — bind options.
|
|
178
|
-
- `--server` implies `requireAuth: true` by default (overridable with `--no-auth` for local testing).
|
|
179
|
-
- Print on startup:
|
|
180
|
-
```
|
|
181
|
-
PureContext MCP Server v1.0.0
|
|
182
|
-
Listening on http://0.0.0.0:3000
|
|
183
|
-
Auth: enabled (set PCTX_ADMIN_KEY to manage API keys)
|
|
184
|
-
Workspaces: 1 (default)
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
- `Dockerfile`:
|
|
188
|
-
```dockerfile
|
|
189
|
-
FROM node:20-slim
|
|
190
|
-
WORKDIR /app
|
|
191
|
-
COPY package*.json ./
|
|
192
|
-
RUN npm ci --omit=dev
|
|
193
|
-
COPY dist/ ./dist/
|
|
194
|
-
COPY grammars/ ./grammars/
|
|
195
|
-
COPY src/ui/dist/ ./src/ui/dist/
|
|
196
|
-
EXPOSE 3000
|
|
197
|
-
ENV PCTX_DATA_DIR=/data
|
|
198
|
-
VOLUME ["/data"]
|
|
199
|
-
CMD ["node", "dist/index.js", "--server", "--host", "0.0.0.0"]
|
|
200
|
-
```
|
|
201
|
-
- Data volume at `/data` holds indexes and config (maps to `~/.purecontext` behavior).
|
|
202
|
-
- `PCTX_DATA_DIR` env var overrides the default `~/.purecontext` path.
|
|
203
|
-
|
|
204
|
-
- `docker-compose.yml` (example, not production):
|
|
205
|
-
```yaml
|
|
206
|
-
version: '3.8'
|
|
207
|
-
services:
|
|
208
|
-
purecontext:
|
|
209
|
-
image: purecontext/purecontext-mcp:latest
|
|
210
|
-
ports: ["3000:3000"]
|
|
211
|
-
volumes: ["./data:/data"]
|
|
212
|
-
environment:
|
|
213
|
-
PCTX_ADMIN_KEY: "change-me-before-deploying"
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
- `src/config/config-loader.ts`:
|
|
217
|
-
- Support `PCTX_DATA_DIR` environment variable as the data directory (overrides `~/.purecontext`).
|
|
218
|
-
- Support `PCTX_ADMIN_KEY`, `PCTX_PORT`, `PCTX_HOST` environment variables.
|
|
219
|
-
- Priority: env var > config file > default.
|
|
220
|
-
|
|
221
|
-
- `.github/workflows/docker.yml`:
|
|
222
|
-
- On release tag push: build Docker image and push to `ghcr.io/org/purecontext-mcp:latest` and `ghcr.io/org/purecontext-mcp:1.0.0`.
|
|
223
|
-
- Also push to Docker Hub if `DOCKERHUB_TOKEN` secret is set.
|
|
224
|
-
|
|
225
|
-
- `docs/SELF_HOSTING.md`:
|
|
226
|
-
- Step-by-step: Docker Compose setup, create admin key, create workspace, create user keys, connect agents.
|
|
227
|
-
- Environment variable reference.
|
|
228
|
-
- How to update: pull new image, restart container (SQLite DB is in the volume, persists across updates).
|
|
229
|
-
|
|
230
|
-
**Key technical notes:**
|
|
231
|
-
- The Docker image must use the prebuilt `better-sqlite3` binary from Phase 16 — no compilation at container startup.
|
|
232
|
-
- `PCTX_DATA_DIR` makes the container stateless (state lives in the volume). This is critical for restarts and updates without data loss.
|
|
233
|
-
- `--no-auth` is intentionally not documented prominently — it's only for local development.
|
|
234
|
-
- The Docker image should be as small as possible. `node:20-slim` base with `--omit=dev` keeps it under 300MB.
|
|
235
|
-
|
|
236
|
-
**Tests:**
|
|
237
|
-
|
|
238
|
-
- `test/server/server-mode.test.ts`:
|
|
239
|
-
- Start server with `--server --port 0` (random port). Assert `/health` returns 200.
|
|
240
|
-
- Assert auth is required (no key → 401).
|
|
241
|
-
- Assert `PCTX_DATA_DIR` redirects index storage.
|
|
242
|
-
- Assert `PCTX_ADMIN_KEY` enables admin endpoints.
|
|
243
|
-
|
|
244
|
-
- `test/config/env-vars.test.ts`:
|
|
245
|
-
- `PCTX_PORT=4000` → server binds to 4000.
|
|
246
|
-
- `PCTX_DATA_DIR=/tmp/test` → config and indexes read from `/tmp/test`.
|
|
247
|
-
- Env vars override config file values.
|
|
248
|
-
|
|
249
|
-
**Verify:** `docker build -t purecontext-test .`. `docker run -e PCTX_ADMIN_KEY=testkey -p 3000:3000 purecontext-test`. `curl localhost:3000/health` → 200. `curl -H "Authorization: Bearer testkey" localhost:3000/admin/workspaces` → JSON list.
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
253
|
-
## Task 130: MCP-over-HTTP Transport
|
|
254
|
-
|
|
255
|
-
Expose the full MCP tool set over HTTP (not just the REST API wrappers) so agents can connect to the shared server using the standard MCP HTTP+SSE transport.
|
|
256
|
-
|
|
257
|
-
**Current state**: The HTTP server exposes REST endpoints that wrap MCP tools (for the Web UI). But AI agents using MCP expect the MCP protocol, not REST. Currently the only MCP transport is stdio (local). Adding MCP-over-HTTP means agents can connect to a remote shared server using the same MCP protocol they use locally.
|
|
258
|
-
|
|
259
|
-
**MCP HTTP+SSE transport** (as specified by the MCP protocol):
|
|
260
|
-
- Client opens `GET /mcp/sse` → server-sent events stream for server→client messages.
|
|
261
|
-
- Client sends tool calls to `POST /mcp/message` → synchronous JSON responses.
|
|
262
|
-
- Authentication: API key in `Authorization: Bearer` header on both.
|
|
263
|
-
|
|
264
|
-
**Deliverables:**
|
|
265
|
-
|
|
266
|
-
- `src/server/transport.ts`:
|
|
267
|
-
- Implement `HttpSseTransport` class alongside the existing `StdioTransport`.
|
|
268
|
-
- `HttpSseTransport` handles:
|
|
269
|
-
- `GET /mcp/sse`: opens SSE stream, registers session.
|
|
270
|
-
- `POST /mcp/message`: receives tool call JSON, routes to MCP server, returns result.
|
|
271
|
-
- Session cleanup on disconnect.
|
|
272
|
-
- Authentication: delegate to the same `requireApiKey` middleware from Task 127.
|
|
273
|
-
|
|
274
|
-
- `src/server/mcp-server.ts`:
|
|
275
|
-
- Make the MCP server transport-agnostic: accept either `StdioTransport` or `HttpSseTransport`.
|
|
276
|
-
- No tool changes needed — all 12 tools work through both transports.
|
|
277
|
-
|
|
278
|
-
- `src/index.ts`:
|
|
279
|
-
- In `--server` mode: use `HttpSseTransport` instead of `StdioTransport`.
|
|
280
|
-
- In stdio mode (default): use `StdioTransport` as before.
|
|
281
|
-
|
|
282
|
-
- Claude Code integration for remote server:
|
|
283
|
-
- Document in README how to connect Claude Code to a remote PureContext server:
|
|
284
|
-
```bash
|
|
285
|
-
claude mcp add purecontext-remote \
|
|
286
|
-
--transport http \
|
|
287
|
-
--url https://purecontext.mycompany.com/mcp/sse \
|
|
288
|
-
--header "Authorization: Bearer pctx_yourkey"
|
|
289
|
-
```
|
|
290
|
-
- This is the team use case: one server URL shared across all team members, each with their own API key.
|
|
291
|
-
|
|
292
|
-
- `docs/TEAM_SETUP.md`:
|
|
293
|
-
- Full walkthrough: deploy server → create workspace → each developer gets their own API key → add to Claude Code.
|
|
294
|
-
- Troubleshooting: connection timeouts, auth errors, proxy configuration.
|
|
295
|
-
|
|
296
|
-
**Key technical notes:**
|
|
297
|
-
- The MCP HTTP+SSE transport is specified by the MCP protocol spec — use `@modelcontextprotocol/sdk`'s HTTP transport implementation if it exists, otherwise implement per the spec.
|
|
298
|
-
- Each SSE connection is one agent session. Multiple agents can connect simultaneously (important for team use).
|
|
299
|
-
- The workspace scoping from Task 128 applies: each API key's tool calls are scoped to its workspace. The agent doesn't need to know about workspaces — scoping is transparent.
|
|
300
|
-
- SSE connections should have a keepalive ping every 30 seconds to prevent proxy/load balancer timeouts.
|
|
301
|
-
|
|
302
|
-
**Tests:**
|
|
303
|
-
|
|
304
|
-
- `test/server/mcp-http.test.ts`:
|
|
305
|
-
- Open SSE connection. Send `list_repos` tool call via POST. Assert result received via SSE.
|
|
306
|
-
- Two concurrent SSE connections. Assert they don't interfere.
|
|
307
|
-
- Disconnected client: assert session cleaned up.
|
|
308
|
-
- Unauthenticated SSE connection: assert 401.
|
|
309
|
-
- Tool scoping: two API keys in different workspaces each see only their own repos via the same MCP endpoint.
|
|
310
|
-
|
|
311
|
-
**Verify:** Configure Claude Code with `--transport http` pointing to local test server. Run `list_repos` from Claude Code. Assert repos appear. Run `index_folder` on a remote path. Assert it indexes and symbols are searchable.
|
|
312
|
-
|
|
313
|
-
---
|
|
314
|
-
|
|
315
|
-
## Order of Execution
|
|
316
|
-
|
|
317
|
-
```
|
|
318
|
-
Task 127: API key authentication ████████░░ Foundation (all others depend on it)
|
|
319
|
-
Task 128: Workspace management ████████░░ After 127 (needs auth for scoping)
|
|
320
|
-
Task 129: --server flag + Docker ██████░░░░ After 127 (needs auth to be meaningful)
|
|
321
|
-
Task 130: MCP-over-HTTP transport █████████░ After 127 + 129 (needs server mode)
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
**Critical path:** 127 → 128 → 129 → 130.
|
|
325
|
-
|
|
326
|
-
Tasks 128 and 129 have partial independence (Docker doesn't strictly need workspaces), but workspace scoping should be tested in the Docker environment so it's cleaner to do 128 first.
|
|
327
|
-
|
|
328
|
-
---
|
|
329
|
-
|
|
330
|
-
## Phase 18 Completion
|
|
331
|
-
|
|
332
|
-
**Before Phase 18:**
|
|
333
|
-
- Local only: each developer maintains their own index.
|
|
334
|
-
- No authentication: any client with network access can call the HTTP API.
|
|
335
|
-
- No team isolation: impossible to share one server across teams.
|
|
336
|
-
- No deployment path: no Docker, no self-hosting docs.
|
|
337
|
-
|
|
338
|
-
**After Phase 18:**
|
|
339
|
-
- Shared server: one always-on server, multiple team members querying it with individual API keys.
|
|
340
|
-
- Workspace isolation: teams are isolated at the query level.
|
|
341
|
-
- Docker image: `docker run purecontext/purecontext-mcp` starts a production server in minutes.
|
|
342
|
-
- MCP-over-HTTP: agents connect to the remote server using the standard MCP protocol.
|
|
343
|
-
- Monetization foundation: `plan` field on workspaces, limits enforced, admin API for manual plan management. Payment integration (Stripe etc.) is out of scope for Phase 18 — that's Phase 19.
|
|
344
|
-
|
|
345
|
-
**This phase draws the line between free (individual stdio) and paid (team server).** The individual stdio use case remains completely free and unchanged. The team server is what you charge for.
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
# Phase 19 — Missing Core Tools
|
|
2
|
-
|
|
3
|
-
**Goal**: Close the four most significant tool-level gaps versus jCodeMunch: batch symbol retrieval, raw file content access, identifier-level reference search, and cache invalidation.
|
|
4
|
-
|
|
5
|
-
**Scope rationale**: These are foundational capabilities. AI agents regularly need to fetch a known list of symbols in one round-trip, retrieve arbitrary file content, find all *usages* of a symbol (not just imports), and force a clean re-index. None of these are hard to build and all unblock real workflows that currently require awkward workarounds.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Task 131: `find_references` Tool
|
|
10
|
-
|
|
11
|
-
Find every file that *uses* (calls, references) a given identifier across the indexed repo — distinct from `find_importers`, which works at the import-statement level.
|
|
12
|
-
|
|
13
|
-
**Current state**: `find_importers` traverses the `dep_edges` table to find files that import a given file. There is no tool that finds files containing actual *usage* of a named identifier (function call, type reference, variable access).
|
|
14
|
-
|
|
15
|
-
**Deliverables**:
|
|
16
|
-
|
|
17
|
-
- `src/server/tools/find-references.ts` — new MCP tool
|
|
18
|
-
```typescript
|
|
19
|
-
interface FindReferencesInput {
|
|
20
|
-
repoId: string;
|
|
21
|
-
identifier: string; // Symbol name to search for
|
|
22
|
-
kind?: SymbolKind; // Optional: narrow to a specific kind
|
|
23
|
-
includeDefinition?: boolean; // Default false — exclude the file that defines it
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface ReferenceLocation {
|
|
27
|
-
filePath: string;
|
|
28
|
-
lineNumber: number;
|
|
29
|
-
lineContent: string; // Trimmed line showing the reference in context
|
|
30
|
-
symbolContext?: string; // Name of the enclosing symbol, if detectable
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface FindReferencesOutput {
|
|
34
|
-
identifier: string;
|
|
35
|
-
totalFiles: number;
|
|
36
|
-
references: ReferenceLocation[];
|
|
37
|
-
_meta: ResponseMeta;
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
- Implementation strategy: use the existing `search_text` infrastructure to scan cached file content for word-boundary matches of the identifier, then cross-reference against the `symbols` table to determine line numbers and enclosing symbols
|
|
41
|
-
- Word-boundary matching: `\bidentifier\b` regex to avoid false positives (e.g. `setUser` not matching `setUserName`)
|
|
42
|
-
- When `includeDefinition: false`, exclude files where the identifier is a top-level symbol definition in `symbols` table
|
|
43
|
-
- Register tool in `src/server/mcp-server.ts`
|
|
44
|
-
|
|
45
|
-
**Key technical notes**:
|
|
46
|
-
- This is a text-search over cached file content — not AST-aware. AST-aware reference tracking is out of scope (requires full name resolution)
|
|
47
|
-
- The distinction from `find_importers`: that tool uses the dependency graph (import statements); this tool scans actual content for identifier occurrences
|
|
48
|
-
- `symbolContext` can be derived by finding which symbol's byte range contains the matched line
|
|
49
|
-
- Respect the same file filter / exclusion list as other tools (node_modules, generated files)
|
|
50
|
-
- Performance: for large repos use the FTS5 `files_fts` table if available; fall back to scanning `file_store` rows
|
|
51
|
-
|
|
52
|
-
**Tests**: `test/server/find-references.test.ts`
|
|
53
|
-
- Reference found in multiple files
|
|
54
|
-
- Definition file excluded when `includeDefinition: false`
|
|
55
|
-
- Word-boundary matching (no false positives on partial names)
|
|
56
|
-
- `kind` filter narrows results
|
|
57
|
-
- Returns `_meta` with token savings
|
|
58
|
-
|
|
59
|
-
**Verify**:
|
|
60
|
-
```bash
|
|
61
|
-
# After indexing a project
|
|
62
|
-
npx purecontext-mcp # then via MCP client:
|
|
63
|
-
# find_references { repoId: "...", identifier: "parseSymbol", includeDefinition: false }
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## Task 132: `get_file_content` Tool
|
|
69
|
-
|
|
70
|
-
Retrieve raw cached file content for any indexed file, with optional line-range slicing.
|
|
71
|
-
|
|
72
|
-
**Current state**: `get_symbol_source` retrieves the source of a specific symbol by byte offsets. There is no way for an agent to retrieve an arbitrary file's content or a line range within it — agents are forced to use `get_symbol_source` repeatedly or request entire outlines.
|
|
73
|
-
|
|
74
|
-
**Deliverables**:
|
|
75
|
-
|
|
76
|
-
- `src/server/tools/get-file-content.ts` — new MCP tool
|
|
77
|
-
```typescript
|
|
78
|
-
interface GetFileContentInput {
|
|
79
|
-
repoId: string;
|
|
80
|
-
filePath: string; // Relative to repo root
|
|
81
|
-
startLine?: number; // 1-based, inclusive. Omit for start of file
|
|
82
|
-
endLine?: number; // 1-based, inclusive. Omit for end of file
|
|
83
|
-
withLineNumbers?: boolean; // Default true — prefix each line with its number
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface GetFileContentOutput {
|
|
87
|
-
filePath: string;
|
|
88
|
-
totalLines: number;
|
|
89
|
-
returnedLines: number;
|
|
90
|
-
content: string;
|
|
91
|
-
_meta: ResponseMeta;
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
- Read from `file_store` (the existing cached content table in `src/core/db/file-store.ts`)
|
|
95
|
-
- Apply line slicing after splitting on `\n`
|
|
96
|
-
- When `withLineNumbers: true`, prefix lines: ` 42 │ const x = ...`
|
|
97
|
-
- If the file is not in the cache (not yet indexed), return a clear error: `FileNotCachedError`
|
|
98
|
-
- Register tool in `src/server/mcp-server.ts`
|
|
99
|
-
|
|
100
|
-
**Key technical notes**:
|
|
101
|
-
- Do not read from disk — always serve from the SQLite `file_store` cache. This guarantees consistency with the indexed symbol offsets
|
|
102
|
-
- Impose a max return size: 500 lines per call (configurable via `MAX_FILE_CONTENT_LINES` env var). If range exceeds limit, return first N lines and indicate truncation in `_meta`
|
|
103
|
-
- `filePath` must be validated against `path.normalize` to prevent path traversal — reject any path containing `..`
|
|
104
|
-
- Token savings calculation: compare returned content size vs full file size
|
|
105
|
-
|
|
106
|
-
**Tests**: `test/server/get-file-content.test.ts`
|
|
107
|
-
- Full file retrieval
|
|
108
|
-
- Line range slicing (start/end combinations)
|
|
109
|
-
- `withLineNumbers` formatting
|
|
110
|
-
- Path traversal rejection
|
|
111
|
-
- Max lines truncation with indicator
|
|
112
|
-
- File not cached → `FileNotCachedError`
|
|
113
|
-
|
|
114
|
-
**Verify**:
|
|
115
|
-
```bash
|
|
116
|
-
# get_file_content { repoId: "...", filePath: "src/core/types.ts", startLine: 1, endLine: 50 }
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## Task 133: `get_symbols` Batch Retrieval Tool
|
|
122
|
-
|
|
123
|
-
Fetch full source for a list of known symbol IDs in a single MCP call.
|
|
124
|
-
|
|
125
|
-
**Current state**: `get_symbol_source` retrieves one symbol per call. Agents that have identified a set of relevant symbols (e.g. from `search_symbols` results) must make N sequential calls. `search_symbols` returns metadata but not source.
|
|
126
|
-
|
|
127
|
-
**Deliverables**:
|
|
128
|
-
|
|
129
|
-
- `src/server/tools/get-symbols.ts` — new MCP tool
|
|
130
|
-
```typescript
|
|
131
|
-
interface GetSymbolsInput {
|
|
132
|
-
repoId: string;
|
|
133
|
-
symbolIds: string[]; // Up to 50 symbol IDs per call
|
|
134
|
-
contextLines?: number; // Optional: N lines of surrounding context per symbol
|
|
135
|
-
verify?: boolean; // Check content hash against disk before returning
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
interface SymbolResult {
|
|
139
|
-
symbolId: string;
|
|
140
|
-
name: string;
|
|
141
|
-
kind: SymbolKind;
|
|
142
|
-
filePath: string;
|
|
143
|
-
source: string;
|
|
144
|
-
signature: string;
|
|
145
|
-
summary: string;
|
|
146
|
-
hashDrift?: boolean; // Only present when verify: true
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
interface GetSymbolsOutput {
|
|
150
|
-
found: SymbolResult[];
|
|
151
|
-
notFound: string[]; // Symbol IDs that didn't exist
|
|
152
|
-
_meta: ResponseMeta;
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
- Batch-fetch from `symbol_store` using `WHERE id IN (...)` — single SQL query
|
|
156
|
-
- Retrieve source for each via byte offsets from `file_store` (same logic as `get_symbol_source`)
|
|
157
|
-
- `contextLines`: expand byte range by N lines before/after the symbol boundaries
|
|
158
|
-
- `verify`: re-hash the file from disk and compare against `files.content_hash`; set `hashDrift: true` if mismatch
|
|
159
|
-
- Cap at 50 symbol IDs per call; return `TooManySymbolsError` if exceeded
|
|
160
|
-
- Register tool in `src/server/mcp-server.ts`
|
|
161
|
-
|
|
162
|
-
**Key technical notes**:
|
|
163
|
-
- Single SQL `IN` query is more efficient than N individual lookups — this is the main value proposition
|
|
164
|
-
- For `contextLines`, convert byte offsets to line numbers, then expand, then re-convert to byte range. Store line-indexed views in memory rather than re-scanning the full file
|
|
165
|
-
- `verify` is expensive (disk read per unique file) — only do it when explicitly requested
|
|
166
|
-
- Token savings: sum of all symbol source sizes returned
|
|
167
|
-
|
|
168
|
-
**Tests**: `test/server/get-symbols.test.ts`
|
|
169
|
-
- Batch fetch of 3 known symbols
|
|
170
|
-
- Mixed found/not-found IDs
|
|
171
|
-
- `contextLines` expansion
|
|
172
|
-
- `verify` with matching hash
|
|
173
|
-
- `verify` with hash drift detection
|
|
174
|
-
- Exceeding 50 IDs → `TooManySymbolsError`
|
|
175
|
-
|
|
176
|
-
**Verify**:
|
|
177
|
-
```bash
|
|
178
|
-
# get_symbols { repoId: "...", symbolIds: ["abc123", "def456", "ghi789"] }
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
---
|
|
182
|
-
|
|
183
|
-
## Task 134: `invalidate_cache` Tool
|
|
184
|
-
|
|
185
|
-
Delete a repo's index from the database, forcing a full re-index on the next `index_folder` or `index_repo` call.
|
|
186
|
-
|
|
187
|
-
**Current state**: There is no way for users or agents to clear a stale or corrupted index without manually deleting the SQLite file. This is a common need when the codebase has been heavily restructured, when the index gets corrupted, or when testing.
|
|
188
|
-
|
|
189
|
-
**Deliverables**:
|
|
190
|
-
|
|
191
|
-
- `src/server/tools/invalidate-cache.ts` — new MCP tool
|
|
192
|
-
```typescript
|
|
193
|
-
interface InvalidateCacheInput {
|
|
194
|
-
repoId?: string; // Invalidate a specific repo by ID
|
|
195
|
-
repoPath?: string; // Or resolve by path (converted to repoId internally)
|
|
196
|
-
all?: boolean; // Nuke all indexed repos (requires explicit confirmation)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
interface InvalidateCacheOutput {
|
|
200
|
-
deleted: {
|
|
201
|
-
repoId: string;
|
|
202
|
-
repoPath: string;
|
|
203
|
-
symbolsDeleted: number;
|
|
204
|
-
filesDeleted: number;
|
|
205
|
-
}[];
|
|
206
|
-
_meta: ResponseMeta;
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
- Exactly one of `repoId`, `repoPath`, or `all: true` must be provided
|
|
210
|
-
- Deletion sequence (all in a single SQLite transaction):
|
|
211
|
-
1. Delete from `dep_edges` where `source_file` in repo
|
|
212
|
-
2. Delete from `symbols` where `repo_id = ?`
|
|
213
|
-
3. Delete from `files` where `repo_id = ?`
|
|
214
|
-
4. Delete from `repos` where `id = ?`
|
|
215
|
-
- Return counts of deleted rows for confirmation
|
|
216
|
-
- When `all: true`, iterate over all repos and delete each
|
|
217
|
-
- Add `invalidateCache(repoId: string): InvalidateResult` to `src/core/db/symbol-store.ts`
|
|
218
|
-
- Register tool in `src/server/mcp-server.ts`
|
|
219
|
-
|
|
220
|
-
**Key technical notes**:
|
|
221
|
-
- Use a single transaction to ensure atomicity — partial deletion leaves the DB in a valid but inconsistent state
|
|
222
|
-
- After invalidation the HNSW semantic index (if present) must also be cleared for that repo — call the semantic index cleanup hook
|
|
223
|
-
- Log the operation at `info` level: repo ID, path, rows deleted
|
|
224
|
-
- Do NOT delete the SQLite file itself — just the rows. This avoids re-creating the schema and is safer if the DB is shared
|
|
225
|
-
|
|
226
|
-
**Tests**: `test/server/invalidate-cache.test.ts`
|
|
227
|
-
- Invalidate by `repoId`
|
|
228
|
-
- Invalidate by `repoPath`
|
|
229
|
-
- `all: true` clears all repos
|
|
230
|
-
- Missing both `repoId` and `repoPath` → validation error
|
|
231
|
-
- Verify rows are gone after invalidation
|
|
232
|
-
- Subsequent `index_folder` re-indexes successfully
|
|
233
|
-
|
|
234
|
-
**Verify**:
|
|
235
|
-
```bash
|
|
236
|
-
# invalidate_cache { repoId: "abc123def456" }
|
|
237
|
-
# Then: index_folder { path: "/path/to/repo" } # Should re-index from scratch
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
---
|
|
241
|
-
|
|
242
|
-
## Order of Execution
|
|
243
|
-
|
|
244
|
-
Tasks are independent and can be parallelized:
|
|
245
|
-
|
|
246
|
-
```
|
|
247
|
-
131 (find_references) ──┐
|
|
248
|
-
132 (get_file_content) ──┤──▶ All can land in any order
|
|
249
|
-
133 (get_symbols) ──┤
|
|
250
|
-
134 (invalidate_cache) ──┘
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
`get_symbols` (133) reuses logic from `get_symbol_source` — implement that first if doing sequentially.
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
## Phase 19 Completion
|
|
258
|
-
|
|
259
|
-
**Before**: 18 tools, no identifier-level reference search, no raw file retrieval, no batch symbol fetch, no cache invalidation.
|
|
260
|
-
|
|
261
|
-
**After**: 22 tools. Agents can fetch a known symbol list in one call, retrieve file content by line range, find all usages of any identifier, and force a clean re-index — closing the four largest day-to-day workflow gaps vs jCodeMunch.
|