@reposkein/mcp 0.4.0 → 0.5.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 +3 -0
- package/binary-digests.json +4 -4
- package/dist/cli/view.js +27 -6
- package/dist/cli/view.js.map +1 -1
- package/dist/index.d.ts +8 -56
- package/dist/index.js +38 -402
- package/dist/index.js.map +1 -1
- package/dist/indexer/indexLock.d.ts +13 -0
- package/dist/indexer/indexLock.js +66 -0
- package/dist/indexer/indexLock.js.map +1 -0
- package/dist/indexer/runIndexer.d.ts +8 -0
- package/dist/indexer/runIndexer.js +12 -0
- package/dist/indexer/runIndexer.js.map +1 -1
- package/dist/serve/serve.d.ts +95 -0
- package/dist/serve/serve.js +581 -0
- package/dist/serve/serve.js.map +1 -0
- package/dist/serve/tokens.d.ts +49 -0
- package/dist/serve/tokens.js +108 -0
- package/dist/serve/tokens.js.map +1 -0
- package/dist/serve/watch.d.ts +73 -0
- package/dist/serve/watch.js +135 -0
- package/dist/serve/watch.js.map +1 -0
- package/dist/server/createMcpServer.d.ts +129 -0
- package/dist/server/createMcpServer.js +464 -0
- package/dist/server/createMcpServer.js.map +1 -0
- package/dist/store/JsonlGraphStore.d.ts +5 -1
- package/dist/store/JsonlGraphStore.js +6 -2
- package/dist/store/JsonlGraphStore.js.map +1 -1
- package/dist/store/teamConfig.d.ts +10 -6
- package/dist/store/teamConfig.js +23 -17
- package/dist/store/teamConfig.js.map +1 -1
- package/dist/tools/recordDecision.d.ts +3 -0
- package/dist/tools/recordDecision.js +1 -1
- package/dist/tools/recordDecision.js.map +1 -1
- package/dist/tools/writeSemanticSummary.d.ts +4 -1
- package/dist/tools/writeSemanticSummary.js +5 -2
- package/dist/tools/writeSemanticSummary.js.map +1 -1
- package/dist/viz/assets/index-BnqIVYFE.css +1 -0
- package/dist/viz/assets/index-IhtszTp0.js +5 -0
- package/dist/viz/assets/{r3f-BZY3QR6_.js → r3f-Bn9htB92.js} +1 -1
- package/dist/viz/assets/{tanstack-CjXr-L-s.js → tanstack-CSzx6NDA.js} +7 -7
- package/dist/viz/index.html +4 -4
- package/package.json +1 -1
- package/dist/viz/assets/index-CxqeGuRS.css +0 -1
- package/dist/viz/assets/index-DiNZyrXx.js +0 -9
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import { makeViewHandler, vizDistDir } from "../cli/view.js";
|
|
9
|
+
import { createMcpServer } from "../server/createMcpServer.js";
|
|
10
|
+
import { agentSlug } from "../store/sidecar.js";
|
|
11
|
+
import { resolveSessionId } from "../store/sessionLog.js";
|
|
12
|
+
import { bearerFromAuthHeader, loadServeTokens, matchServeToken, } from "./tokens.js";
|
|
13
|
+
import { startHeadWatcher } from "./watch.js";
|
|
14
|
+
/** The one path the MCP Streamable HTTP transport answers on. Everything else
|
|
15
|
+
* is the viewer + `/api/*`, served by view.ts's own handler. */
|
|
16
|
+
export const MCP_PATH = "/mcp";
|
|
17
|
+
/** Cookie the viewer gets after a successful `?token=` handshake, so a browser
|
|
18
|
+
* can keep loading `/api/*` without the secret in every URL. */
|
|
19
|
+
const TOKEN_COOKIE = "reposkein_token";
|
|
20
|
+
/** Cap on a single JSON-RPC POST body. Tool arguments are prose and node ids;
|
|
21
|
+
* 4 MB is orders of magnitude past anything legitimate. */
|
|
22
|
+
const MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
23
|
+
/** Cap on concurrent MCP sessions. A leaked token shouldn't be able to make
|
|
24
|
+
* the process allocate servers without bound; an operator hitting this has a
|
|
25
|
+
* problem worth an error, not silent growth. */
|
|
26
|
+
const MAX_SESSIONS = 64;
|
|
27
|
+
/** How long a session may sit untouched before it is reaped.
|
|
28
|
+
*
|
|
29
|
+
* A crashed or network-partitioned client never sends the DELETE that closes
|
|
30
|
+
* its session, so without this every such client permanently consumes one of
|
|
31
|
+
* MAX_SESSIONS — and a long-lived server eventually wedges at the cap with
|
|
32
|
+
* nothing but corpses. Five minutes is far longer than any tool call and far
|
|
33
|
+
* shorter than a working day, so a live-but-idle agent that comes back gets a
|
|
34
|
+
* clean `404 session_not_found` and re-initializes. */
|
|
35
|
+
const SESSION_IDLE_MS = 5 * 60 * 1000;
|
|
36
|
+
/** Default bind host. NOT 0.0.0.0: even in the mode that exists to be
|
|
37
|
+
* reachable, exposure is an explicit `--host` decision by the operator. */
|
|
38
|
+
export const DEFAULT_SERVE_HOST = "127.0.0.1";
|
|
39
|
+
export const DEFAULT_SERVE_PORT = 4318;
|
|
40
|
+
export const DEFAULT_WATCH_INTERVAL_MS = 30_000;
|
|
41
|
+
export function parseServeArgs(argv) {
|
|
42
|
+
let port = DEFAULT_SERVE_PORT;
|
|
43
|
+
let host = DEFAULT_SERVE_HOST;
|
|
44
|
+
let watchIntervalMs = DEFAULT_WATCH_INTERVAL_MS;
|
|
45
|
+
let http = false;
|
|
46
|
+
const positional = [];
|
|
47
|
+
const intArg = (raw, fallback) => {
|
|
48
|
+
const n = parseInt(raw ?? "", 10);
|
|
49
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
50
|
+
};
|
|
51
|
+
for (let i = 0; i < argv.length; i++) {
|
|
52
|
+
const a = argv[i];
|
|
53
|
+
if (a === "--http")
|
|
54
|
+
http = true;
|
|
55
|
+
else if (a === "--port")
|
|
56
|
+
port = intArg(argv[++i], DEFAULT_SERVE_PORT);
|
|
57
|
+
else if (a.startsWith("--port="))
|
|
58
|
+
port = intArg(a.slice(7), DEFAULT_SERVE_PORT);
|
|
59
|
+
else if (a === "--host")
|
|
60
|
+
host = argv[++i] ?? DEFAULT_SERVE_HOST;
|
|
61
|
+
else if (a.startsWith("--host="))
|
|
62
|
+
host = a.slice(7);
|
|
63
|
+
// Seconds on the CLI (an operator thinks in seconds), ms internally.
|
|
64
|
+
else if (a === "--watch-interval")
|
|
65
|
+
watchIntervalMs = intArg(argv[++i], 30) * 1000;
|
|
66
|
+
else if (a.startsWith("--watch-interval="))
|
|
67
|
+
watchIntervalMs = intArg(a.slice(17), 30) * 1000;
|
|
68
|
+
else if (!a.startsWith("-"))
|
|
69
|
+
positional.push(a);
|
|
70
|
+
}
|
|
71
|
+
const repoPath = positional[0] ?? process.env.REPOSKEIN_REPO_PATH ?? ".";
|
|
72
|
+
return { repoPath, opts: { port, host, watchIntervalMs }, http };
|
|
73
|
+
}
|
|
74
|
+
function sendJson(res, status, body, headers = {}) {
|
|
75
|
+
const text = JSON.stringify(body);
|
|
76
|
+
res.writeHead(status, {
|
|
77
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
78
|
+
"Cache-Control": "no-store",
|
|
79
|
+
...headers,
|
|
80
|
+
});
|
|
81
|
+
res.end(text);
|
|
82
|
+
}
|
|
83
|
+
/** Reads a request body as JSON, capped. Resolves `{ ok: false }` on a bad
|
|
84
|
+
* parse or an oversized body — never throws, never buffers past the cap.
|
|
85
|
+
*
|
|
86
|
+
* Deliberately does NOT destroy the request itself: on oversize it stops
|
|
87
|
+
* accumulating and resolves, leaving the caller to write its 400 first and
|
|
88
|
+
* destroy afterwards. Destroying here raced the response and turned a clean
|
|
89
|
+
* "body too large" into an unexplained connection reset. */
|
|
90
|
+
function readJsonBody(req) {
|
|
91
|
+
return new Promise((resolvePromise) => {
|
|
92
|
+
const chunks = [];
|
|
93
|
+
let size = 0;
|
|
94
|
+
let settled = false;
|
|
95
|
+
const finish = (r) => {
|
|
96
|
+
if (settled)
|
|
97
|
+
return;
|
|
98
|
+
settled = true;
|
|
99
|
+
resolvePromise(r);
|
|
100
|
+
};
|
|
101
|
+
req.on("data", (chunk) => {
|
|
102
|
+
if (settled)
|
|
103
|
+
return; // over cap already; drain without buffering
|
|
104
|
+
size += chunk.length;
|
|
105
|
+
if (size > MAX_BODY_BYTES) {
|
|
106
|
+
chunks.length = 0;
|
|
107
|
+
finish({
|
|
108
|
+
ok: false,
|
|
109
|
+
error: `request body exceeds ${MAX_BODY_BYTES} bytes`,
|
|
110
|
+
oversize: true,
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
chunks.push(chunk);
|
|
115
|
+
});
|
|
116
|
+
req.on("end", () => {
|
|
117
|
+
if (chunks.length === 0)
|
|
118
|
+
return finish({ ok: true, value: undefined });
|
|
119
|
+
try {
|
|
120
|
+
finish({ ok: true, value: JSON.parse(Buffer.concat(chunks).toString("utf8")) });
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
finish({ ok: false, error: "request body is not valid JSON" });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
req.on("error", () => finish({ ok: false, error: "request stream error" }));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function cookieValue(header, name) {
|
|
130
|
+
if (!header)
|
|
131
|
+
return null;
|
|
132
|
+
for (const part of header.split(";")) {
|
|
133
|
+
const eq = part.indexOf("=");
|
|
134
|
+
if (eq === -1)
|
|
135
|
+
continue;
|
|
136
|
+
if (part.slice(0, eq).trim() !== name)
|
|
137
|
+
continue;
|
|
138
|
+
try {
|
|
139
|
+
return decodeURIComponent(part.slice(eq + 1).trim()) || null;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
/** Builds the one request handler that answers both the MCP endpoint and the
|
|
148
|
+
* viewer/`/api/*` surface (REP-17 requirement: one process, one graph).
|
|
149
|
+
*
|
|
150
|
+
* URL layout:
|
|
151
|
+
* POST/GET/DELETE `/mcp` — MCP Streamable HTTP transport
|
|
152
|
+
* GET `/api/graph`, `/api/jsonl/*`, `/api/source`, `/api/temporal`
|
|
153
|
+
* — byte-identical to `reposkein-mcp view`, because
|
|
154
|
+
* it IS view.ts's handler, not a copy
|
|
155
|
+
* GET everything else — the prebuilt viewer SPA
|
|
156
|
+
*
|
|
157
|
+
* Auth applies to every route. `/mcp` accepts ONLY
|
|
158
|
+
* `Authorization: Bearer <token>`; the viewer routes additionally accept
|
|
159
|
+
* `?token=` (which is immediately traded for an HttpOnly cookie and
|
|
160
|
+
* redirected away, so it appears once) because a static SPA in a browser
|
|
161
|
+
* cannot attach an Authorization header to its own page load. */
|
|
162
|
+
export function createServeApp(opts) {
|
|
163
|
+
const log = opts.log ?? ((m) => void process.stderr.write(`${m}\n`));
|
|
164
|
+
const factory = opts.viewHandlerFactory ?? makeViewHandler;
|
|
165
|
+
const repoPath = resolve(opts.repoPath);
|
|
166
|
+
const maxSessions = opts.maxSessions ?? MAX_SESSIONS;
|
|
167
|
+
const idleMs = opts.idleMs ?? SESSION_IDLE_MS;
|
|
168
|
+
const now = opts.now ?? Date.now;
|
|
169
|
+
// One session-log id per PROCESS (honoring REPOSKEIN_SESSION_ID), suffixed
|
|
170
|
+
// per token below. Computed once here rather than per connection: a
|
|
171
|
+
// reconnecting agent should append to the same file it was writing before,
|
|
172
|
+
// and `reposkein-mcp stats` groups by that id.
|
|
173
|
+
const logIdBase = resolveSessionId(process.env);
|
|
174
|
+
let viewHandler = factory(repoPath, opts.repoId);
|
|
175
|
+
const sessions = new Map();
|
|
176
|
+
// Slots claimed by an in-progress `initialize` that has not yet registered
|
|
177
|
+
// its session. The cap check and this increment are one synchronous pair,
|
|
178
|
+
// which is what closes the check-then-set race: without it, N concurrent
|
|
179
|
+
// initializes all saw `sessions.size` from before any of them had connected
|
|
180
|
+
// and every one of them was admitted.
|
|
181
|
+
let reservedSlots = 0;
|
|
182
|
+
/** Closes idle, quiescent sessions. See `ServeApp.sweepIdleSessions`. */
|
|
183
|
+
function sweepIdleSessions() {
|
|
184
|
+
const cutoff = now() - idleMs;
|
|
185
|
+
let closed = 0;
|
|
186
|
+
for (const [id, s] of [...sessions]) {
|
|
187
|
+
if (s.inFlight > 0)
|
|
188
|
+
continue; // never kill a live request
|
|
189
|
+
if (s.lastSeen > cutoff)
|
|
190
|
+
continue;
|
|
191
|
+
sessions.delete(id);
|
|
192
|
+
closed++;
|
|
193
|
+
log(`reposkein serve: reaped idle mcp session for "${s.tokenName}"`);
|
|
194
|
+
void s.transport.close().catch(() => undefined);
|
|
195
|
+
void s.server.close().catch(() => undefined);
|
|
196
|
+
}
|
|
197
|
+
return closed;
|
|
198
|
+
}
|
|
199
|
+
const unauthorized = (res, detail) => sendJson(res, 401, { error: "unauthorized", detail }, { "WWW-Authenticate": `Bearer realm="reposkein"` });
|
|
200
|
+
/** Authenticates one request. `allowCookieAndQuery` is false for `/mcp`. */
|
|
201
|
+
function authenticate(req, url, allowCookieAndQuery) {
|
|
202
|
+
const header = bearerFromAuthHeader(req.headers.authorization);
|
|
203
|
+
const hit = matchServeToken(opts.tokens, header);
|
|
204
|
+
if (hit || !allowCookieAndQuery)
|
|
205
|
+
return { token: hit, fromQuery: false };
|
|
206
|
+
const fromQuery = url.searchParams.get("token");
|
|
207
|
+
const q = matchServeToken(opts.tokens, fromQuery);
|
|
208
|
+
if (q)
|
|
209
|
+
return { token: q, fromQuery: true };
|
|
210
|
+
const cookie = cookieValue(req.headers.cookie, TOKEN_COOKIE);
|
|
211
|
+
return { token: matchServeToken(opts.tokens, cookie), fromQuery: false };
|
|
212
|
+
}
|
|
213
|
+
async function handleMcp(req, res, token) {
|
|
214
|
+
// No standalone notification stream. This server never sends anything the
|
|
215
|
+
// client didn't ask for (every tool is request/response, `enableJsonResponse`
|
|
216
|
+
// is on), so a GET would open a socket that stays open for the connection's
|
|
217
|
+
// whole life and produces nothing. Worse, it never completes — which meant
|
|
218
|
+
// it pinned `inFlight` above zero and made the idle reaper a no-op for any
|
|
219
|
+
// client that opened one. The spec allows 405 here; clients treat the
|
|
220
|
+
// stream as optional and carry on.
|
|
221
|
+
if (req.method === "GET") {
|
|
222
|
+
sendJson(res, 405, {
|
|
223
|
+
error: "sse_stream_not_supported",
|
|
224
|
+
detail: "This server answers MCP over POST only (JSON responses, no server-initiated " +
|
|
225
|
+
"notifications). Use POST for requests and DELETE to end a session.",
|
|
226
|
+
}, { Allow: "POST, DELETE" });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const rawSessionId = req.headers["mcp-session-id"];
|
|
230
|
+
const sessionId = Array.isArray(rawSessionId) ? rawSessionId[0] : rawSessionId;
|
|
231
|
+
if (sessionId) {
|
|
232
|
+
const existing = sessions.get(sessionId);
|
|
233
|
+
if (!existing) {
|
|
234
|
+
sendJson(res, 404, {
|
|
235
|
+
error: "session_not_found",
|
|
236
|
+
detail: "This Mcp-Session-Id is unknown (expired after 5 minutes idle, closed, or the " +
|
|
237
|
+
"server restarted). Re-run initialize to open a new session.",
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (existing.tokenName !== token.name) {
|
|
242
|
+
sendJson(res, 403, {
|
|
243
|
+
error: "session_token_mismatch",
|
|
244
|
+
detail: "This MCP session belongs to a different token. Start a new session " +
|
|
245
|
+
"(omit Mcp-Session-Id) instead of reusing another caller's.",
|
|
246
|
+
});
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// Claim the session for the duration of this request: `lastSeen` keeps
|
|
250
|
+
// the reaper away from an active client, `inFlight` keeps it away from
|
|
251
|
+
// an active REQUEST even if that request outlives the idle window.
|
|
252
|
+
existing.lastSeen = now();
|
|
253
|
+
existing.inFlight++;
|
|
254
|
+
try {
|
|
255
|
+
const body = req.method === "POST" ? await readJsonBody(req) : { ok: true, value: undefined };
|
|
256
|
+
if (!body.ok) {
|
|
257
|
+
sendJson(res, 400, { error: "bad_request", detail: body.error });
|
|
258
|
+
if (body.oversize)
|
|
259
|
+
req.destroy();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (req.method === "POST" && body.value === undefined) {
|
|
263
|
+
sendJson(res, 400, {
|
|
264
|
+
error: "bad_request",
|
|
265
|
+
detail: "Empty POST body. Send a JSON-RPC request, notification, or batch.",
|
|
266
|
+
});
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
await existing.transport.handleRequest(req, res, body.value);
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
existing.inFlight--;
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (req.method !== "POST") {
|
|
277
|
+
sendJson(res, 400, {
|
|
278
|
+
error: "bad_request",
|
|
279
|
+
detail: "Mcp-Session-Id is required for DELETE on /mcp.",
|
|
280
|
+
});
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const body = await readJsonBody(req);
|
|
284
|
+
if (!body.ok) {
|
|
285
|
+
sendJson(res, 400, { error: "bad_request", detail: body.error });
|
|
286
|
+
if (body.oversize)
|
|
287
|
+
req.destroy();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (!isInitializeRequest(body.value)) {
|
|
291
|
+
sendJson(res, 400, {
|
|
292
|
+
error: "bad_request",
|
|
293
|
+
detail: "No valid Mcp-Session-Id and this is not an initialize request. " +
|
|
294
|
+
"Open a session with initialize first.",
|
|
295
|
+
});
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
// Free anything the network already abandoned before deciding we're full.
|
|
299
|
+
sweepIdleSessions();
|
|
300
|
+
if (sessions.size + reservedSlots >= maxSessions) {
|
|
301
|
+
sendJson(res, 503, {
|
|
302
|
+
error: "too_many_sessions",
|
|
303
|
+
detail: `This server holds the maximum of ${maxSessions} MCP sessions. Retry later.`,
|
|
304
|
+
});
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
// Reserved synchronously, in the same tick as the check above.
|
|
308
|
+
reservedSlots++;
|
|
309
|
+
// Set by `onsessioninitialized` the moment the session is registered with
|
|
310
|
+
// `inFlight: 1` (this request). Held out here, not read back off the
|
|
311
|
+
// transport, so the `finally` can release the count on EVERY exit path.
|
|
312
|
+
// If anything after registration throws — the SDK `await`s our callback
|
|
313
|
+
// and does not guard it, and the response write can fail — an inline
|
|
314
|
+
// decrement is skipped, and a session stuck at `inFlight: 1` is one the
|
|
315
|
+
// reaper will never touch: a slot leaked until restart.
|
|
316
|
+
let registeredId;
|
|
317
|
+
try {
|
|
318
|
+
// A NEW connection: its own McpServer, its own RepoSession. Nothing in
|
|
319
|
+
// here is shared with any other session, which is what makes one
|
|
320
|
+
// caller's `select_repo` invisible to everybody else.
|
|
321
|
+
const server = createMcpServer({
|
|
322
|
+
cwd: repoPath,
|
|
323
|
+
envRepoPath: repoPath,
|
|
324
|
+
// One log file per token per process, so an operator can see who did
|
|
325
|
+
// what without every connection sharing one file.
|
|
326
|
+
sessionId: `${logIdBase}-${agentSlug(token.name)}`,
|
|
327
|
+
identity: token.name,
|
|
328
|
+
capabilities: { write: token.write },
|
|
329
|
+
...(opts.ensureGraph ? { ensureGraph: opts.ensureGraph } : {}),
|
|
330
|
+
});
|
|
331
|
+
const transport = new StreamableHTTPServerTransport({
|
|
332
|
+
sessionIdGenerator: () => randomUUID(),
|
|
333
|
+
// Plain JSON responses rather than an SSE stream per request: this
|
|
334
|
+
// server has no server-initiated notifications to push, and a
|
|
335
|
+
// request/response shape keeps sockets short-lived (and shutdown
|
|
336
|
+
// instant) on a long-running shared process.
|
|
337
|
+
enableJsonResponse: true,
|
|
338
|
+
onsessioninitialized: (id) => {
|
|
339
|
+
registeredId = id;
|
|
340
|
+
sessions.set(id, {
|
|
341
|
+
transport,
|
|
342
|
+
server,
|
|
343
|
+
tokenName: token.name,
|
|
344
|
+
lastSeen: now(),
|
|
345
|
+
// The initialize request itself is in flight right now.
|
|
346
|
+
inFlight: 1,
|
|
347
|
+
});
|
|
348
|
+
log(`reposkein serve: mcp session opened for "${token.name}" (write=${token.write})`);
|
|
349
|
+
},
|
|
350
|
+
onsessionclosed: (id) => {
|
|
351
|
+
sessions.delete(id);
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
transport.onclose = () => {
|
|
355
|
+
const id = transport.sessionId;
|
|
356
|
+
if (id)
|
|
357
|
+
sessions.delete(id);
|
|
358
|
+
void server.close().catch(() => {
|
|
359
|
+
/* shutting down */
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
await server.connect(transport);
|
|
363
|
+
await transport.handleRequest(req, res, body.value);
|
|
364
|
+
}
|
|
365
|
+
finally {
|
|
366
|
+
// Mirrors the existing-session branch: this request's own `inFlight`
|
|
367
|
+
// claim is released HERE, not inline after `handleRequest`, so no exit
|
|
368
|
+
// path can skip it.
|
|
369
|
+
//
|
|
370
|
+
// Defense in depth as of SDK 1.30: the Node wrapper runs the transport
|
|
371
|
+
// inside `@hono/node-server`'s request listener, which catches handler
|
|
372
|
+
// errors and answers 500 itself — so a throw after registration does not
|
|
373
|
+
// currently reject `handleRequest`. That is a library implementation
|
|
374
|
+
// detail we don't control, and it is the only thing that was keeping an
|
|
375
|
+
// inline decrement correct. Structurally, nothing may sit between the
|
|
376
|
+
// request completing and the claim being released.
|
|
377
|
+
if (registeredId !== undefined) {
|
|
378
|
+
const created = sessions.get(registeredId);
|
|
379
|
+
if (created)
|
|
380
|
+
created.inFlight--;
|
|
381
|
+
}
|
|
382
|
+
reservedSlots--;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const handler = (req, res) => {
|
|
386
|
+
const rawUrl = req.url ?? "/";
|
|
387
|
+
// The base is a placeholder — only pathname/searchParams are used.
|
|
388
|
+
const url = new URL(rawUrl, "http://localhost");
|
|
389
|
+
const isMcp = url.pathname === MCP_PATH;
|
|
390
|
+
const { token, fromQuery } = authenticate(req, url, !isMcp);
|
|
391
|
+
if (!token) {
|
|
392
|
+
log(`reposkein serve: 401 ${req.method ?? "?"} ${url.pathname} ` +
|
|
393
|
+
`(${req.headers.authorization ? "token rejected" : "no bearer token"})`);
|
|
394
|
+
unauthorized(res, isMcp
|
|
395
|
+
? "Send `Authorization: Bearer <token>`. Ask the operator for a token; see docs/REMOTE.md."
|
|
396
|
+
: "Send `Authorization: Bearer <token>`, or open this page as `?token=<token>` once.");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (isMcp) {
|
|
400
|
+
handleMcp(req, res, token).catch((err) => {
|
|
401
|
+
log(`reposkein serve: mcp request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
402
|
+
if (!res.headersSent) {
|
|
403
|
+
sendJson(res, 500, { error: "internal_error", detail: "See the server log." });
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
res.end();
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
// A browser authenticated by `?token=`: trade it for an HttpOnly cookie
|
|
412
|
+
// and redirect to the clean URL, so the secret shows up in exactly one
|
|
413
|
+
// request line instead of every one.
|
|
414
|
+
if (fromQuery) {
|
|
415
|
+
url.searchParams.delete("token");
|
|
416
|
+
const q = url.searchParams.toString();
|
|
417
|
+
// `Secure` whenever the request reached us over TLS. We terminate plain
|
|
418
|
+
// HTTP ourselves, so the only evidence is the proxy's forwarded scheme —
|
|
419
|
+
// set it when that says https so the cookie can't leak back over a
|
|
420
|
+
// downgraded connection, and omit it otherwise (a `Secure` cookie on a
|
|
421
|
+
// genuinely plain-HTTP deployment is simply discarded by the browser,
|
|
422
|
+
// which would break the handshake this exists to provide).
|
|
423
|
+
const forwardedProto = req.headers["x-forwarded-proto"];
|
|
424
|
+
const rawProto = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
|
|
425
|
+
const proto = (rawProto ?? "").split(",")[0].trim().toLowerCase();
|
|
426
|
+
const secure = proto === "https" ? "; Secure" : "";
|
|
427
|
+
res.writeHead(302, {
|
|
428
|
+
Location: url.pathname + (q ? `?${q}` : ""),
|
|
429
|
+
"Set-Cookie": `${TOKEN_COOKIE}=${encodeURIComponent(token.token)}; Path=/; HttpOnly; SameSite=Strict${secure}`,
|
|
430
|
+
"Cache-Control": "no-store",
|
|
431
|
+
});
|
|
432
|
+
res.end();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
viewHandler(req, res);
|
|
436
|
+
};
|
|
437
|
+
return {
|
|
438
|
+
handler,
|
|
439
|
+
refresh: () => {
|
|
440
|
+
viewHandler = factory(repoPath, opts.repoId);
|
|
441
|
+
},
|
|
442
|
+
sweepIdleSessions,
|
|
443
|
+
close: async () => {
|
|
444
|
+
const open = [...sessions.values()];
|
|
445
|
+
sessions.clear();
|
|
446
|
+
for (const s of open) {
|
|
447
|
+
try {
|
|
448
|
+
await s.transport.close();
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
/* best-effort */
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
await s.server.close();
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
/* best-effort */
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
sessionCount: () => sessions.size,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
/** `git status --porcelain` for the served checkout, or null when git is
|
|
465
|
+
* unavailable / the path isn't a repo. Never throws. */
|
|
466
|
+
export function gitPorcelainStatus(repoPath) {
|
|
467
|
+
try {
|
|
468
|
+
const out = execFileSync("git", ["-C", repoPath, "status", "--porcelain"], {
|
|
469
|
+
encoding: "utf8",
|
|
470
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
471
|
+
});
|
|
472
|
+
return out.split("\n").filter((l) => l.trim() !== "");
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/** The startup warning for serving a checkout with local modifications, or
|
|
479
|
+
* null when the tree is clean (or git can't tell us).
|
|
480
|
+
*
|
|
481
|
+
* Serving a dirty checkout is legal and sometimes exactly what an operator
|
|
482
|
+
* wants, so this WARNS and never refuses. But it is worth saying loudly:
|
|
483
|
+
* `serve`'s own re-index writes into `.reposkein/` (the committed summary
|
|
484
|
+
* shards absorb every `local/summaries-*.jsonl` sidecar), so a checkout that
|
|
485
|
+
* someone also works in will accumulate diffs that look like the server's
|
|
486
|
+
* fault — and a re-index landing on top of half-finished local edits indexes
|
|
487
|
+
* those edits. A dedicated deploy clone has neither problem. */
|
|
488
|
+
export function dirtyCheckoutWarning(repoPath, statusFn = gitPorcelainStatus) {
|
|
489
|
+
const lines = statusFn(repoPath);
|
|
490
|
+
if (!lines || lines.length === 0)
|
|
491
|
+
return null;
|
|
492
|
+
const sample = lines.slice(0, 5).map((l) => ` ${l}`);
|
|
493
|
+
const more = lines.length > sample.length ? `\n … and ${lines.length - sample.length} more` : "";
|
|
494
|
+
return (`reposkein serve: WARNING — the served checkout has ${lines.length} uncommitted ` +
|
|
495
|
+
`change(s):\n${sample.join("\n")}${more}\n` +
|
|
496
|
+
" Serving it anyway. Two things to know:\n" +
|
|
497
|
+
" - re-indexing writes into .reposkein/ (committed summary shards absorb the local\n" +
|
|
498
|
+
" sidecars), so this working tree will keep accumulating diffs.\n" +
|
|
499
|
+
" - a re-index picks up whatever is in the tree, including half-finished edits.\n" +
|
|
500
|
+
" Recommended: serve a DEDICATED DEPLOY CLONE that only ever fast-forwards, not a\n" +
|
|
501
|
+
" working copy someone edits. See docs/REMOTE.md.");
|
|
502
|
+
}
|
|
503
|
+
/** `reposkein-mcp serve --http [path] [--port N] [--host H] [--watch-interval S]`.
|
|
504
|
+
*
|
|
505
|
+
* Strictly optional (REP-17 / adr:2026-08-21-optional-shared-remote-mcp-server-…):
|
|
506
|
+
* stdio is still the default transport and nothing binds a socket unless this
|
|
507
|
+
* runs. Refuses to start without at least one configured token — an
|
|
508
|
+
* unauthenticated remote MCP server would hand write access to the network. */
|
|
509
|
+
export async function runServe(repoPath, repoId, opts) {
|
|
510
|
+
const nodesPath = join(repoPath, ".reposkein", "nodes.jsonl");
|
|
511
|
+
if (!existsSync(nodesPath)) {
|
|
512
|
+
console.error(`reposkein: no .reposkein/nodes.jsonl at ${repoPath}.\n` +
|
|
513
|
+
" Build the graph first: `reposkein-mcp index` (or `reposkein-mcp init`).");
|
|
514
|
+
return 1;
|
|
515
|
+
}
|
|
516
|
+
const loaded = loadServeTokens(repoPath, process.env);
|
|
517
|
+
for (const e of loaded.errors)
|
|
518
|
+
console.error(`reposkein serve: ${e}`);
|
|
519
|
+
if (loaded.tokens.length === 0) {
|
|
520
|
+
console.error("reposkein serve: refusing to start with no tokens.\n" +
|
|
521
|
+
" Set REPOSKEIN_SERVE_TOKENS='name:secret[:write]' (comma-separated), or add\n" +
|
|
522
|
+
" [serve] tokens = \"name:secret\" to .reposkein/config.toml (private deployments only —\n" +
|
|
523
|
+
" config.toml is committed). See docs/REMOTE.md.");
|
|
524
|
+
return 1;
|
|
525
|
+
}
|
|
526
|
+
const vizMissing = !existsSync(join(vizDistDir(), "index.html"));
|
|
527
|
+
if (vizMissing) {
|
|
528
|
+
console.error(`reposkein serve: the viewer bundle is missing (${vizDistDir()}); /api/* still works, ` +
|
|
529
|
+
"but the SPA will 404. Rebuild the package (`npm run build` in mcp/) to serve it.");
|
|
530
|
+
}
|
|
531
|
+
// Warn, never refuse: see `dirtyCheckoutWarning`.
|
|
532
|
+
const dirty = dirtyCheckoutWarning(repoPath);
|
|
533
|
+
if (dirty)
|
|
534
|
+
console.error(dirty);
|
|
535
|
+
const app = createServeApp({ repoPath, repoId, tokens: loaded.tokens });
|
|
536
|
+
const watcher = startHeadWatcher(repoPath, repoId, {
|
|
537
|
+
intervalMs: opts.watchIntervalMs,
|
|
538
|
+
onReindexed: () => app.refresh(),
|
|
539
|
+
});
|
|
540
|
+
// Catch up once at startup: the checkout may have moved while nothing was
|
|
541
|
+
// watching it. Same idempotent check as every tick, so an up-to-date repo
|
|
542
|
+
// does no work.
|
|
543
|
+
await watcher.tick();
|
|
544
|
+
const server = createServer(app.handler);
|
|
545
|
+
return await new Promise((resolvePromise) => {
|
|
546
|
+
let settled = false;
|
|
547
|
+
const settle = (code) => {
|
|
548
|
+
if (settled)
|
|
549
|
+
return;
|
|
550
|
+
settled = true;
|
|
551
|
+
resolvePromise(code);
|
|
552
|
+
};
|
|
553
|
+
server.on("error", (err) => {
|
|
554
|
+
console.error(`reposkein serve: server error: ${err.message}`);
|
|
555
|
+
watcher.stop();
|
|
556
|
+
settle(1);
|
|
557
|
+
});
|
|
558
|
+
server.listen(opts.port, opts.host, () => {
|
|
559
|
+
const addr = server.address();
|
|
560
|
+
const port = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
561
|
+
const names = loaded.tokens
|
|
562
|
+
.map((t) => `${t.name}${t.write ? " (write)" : ""}`)
|
|
563
|
+
.join(", ");
|
|
564
|
+
console.error(`reposkein serve: ${repoId} on http://${opts.host}:${port}\n` +
|
|
565
|
+
` MCP endpoint: http://${opts.host}:${port}${MCP_PATH}\n` +
|
|
566
|
+
` viewer + /api/: http://${opts.host}:${port}/\n` +
|
|
567
|
+
` tokens (${loaded.source}): ${names}\n` +
|
|
568
|
+
` watch: ${opts.watchIntervalMs > 0 ? `${opts.watchIntervalMs / 1000}s HEAD poll` : "disabled"}\n` +
|
|
569
|
+
" plain HTTP — terminate TLS in front of this. Ctrl-C to stop.");
|
|
570
|
+
});
|
|
571
|
+
const shutdown = () => {
|
|
572
|
+
watcher.stop();
|
|
573
|
+
void app.close().finally(() => {
|
|
574
|
+
server.close(() => settle(0));
|
|
575
|
+
});
|
|
576
|
+
};
|
|
577
|
+
process.on("SIGINT", shutdown);
|
|
578
|
+
process.on("SIGTERM", shutdown);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/serve/serve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** One bearer credential a `serve --http` operator handed out (REP-17).
|
|
2
|
+
*
|
|
3
|
+
* `name` is NOT a secret: it is the connection's writer identity, landing in
|
|
4
|
+
* `summary_by` / `decided_by` and selecting the per-agent sidecar file. It is
|
|
5
|
+
* the thing that gets logged. `token` is the secret and is never logged,
|
|
6
|
+
* printed, or echoed into a tool result. */
|
|
7
|
+
export interface ServeToken {
|
|
8
|
+
name: string;
|
|
9
|
+
token: string;
|
|
10
|
+
/** True only for entries declared `name:token:write`. */
|
|
11
|
+
write: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface ParsedTokens {
|
|
14
|
+
tokens: ServeToken[];
|
|
15
|
+
/** Human-readable reasons entries were dropped. NEVER contains a token
|
|
16
|
+
* value — an operator pastes these into a bug report. */
|
|
17
|
+
errors: string[];
|
|
18
|
+
}
|
|
19
|
+
/** Parses a token list: `name:token[:write]` entries separated by commas,
|
|
20
|
+
* whitespace, or newlines.
|
|
21
|
+
*
|
|
22
|
+
* Chosen over JSON because it has to survive being pasted into a shell
|
|
23
|
+
* export, a systemd `Environment=` line, and a TOML string without escaping.
|
|
24
|
+
* Anything unparseable is DROPPED with a reason rather than silently
|
|
25
|
+
* becoming a valid credential — a malformed entry must never widen access.
|
|
26
|
+
* Capability is opt-in: only the literal third field `write` grants it. */
|
|
27
|
+
export declare function parseServeTokens(spec: string): ParsedTokens;
|
|
28
|
+
export interface LoadedTokens extends ParsedTokens {
|
|
29
|
+
/** Where the accepted entries came from, for the startup banner. */
|
|
30
|
+
source: "env" | "config" | "none";
|
|
31
|
+
}
|
|
32
|
+
/** Resolves the token list for `serve`: `REPOSKEIN_SERVE_TOKENS` wins over
|
|
33
|
+
* `[serve] tokens` in `.reposkein/config.toml`.
|
|
34
|
+
*
|
|
35
|
+
* Env-over-config because the config file is COMMITTED — an operator who
|
|
36
|
+
* puts real secrets there has leaked them to everyone with repo access, so
|
|
37
|
+
* the documented path is the env var and config is the convenience for a
|
|
38
|
+
* private deployment. Only one source is consulted (no union): merging them
|
|
39
|
+
* would make "why does this old token still work?" unanswerable. */
|
|
40
|
+
export declare function loadServeTokens(repoPath: string, env?: NodeJS.ProcessEnv): LoadedTokens;
|
|
41
|
+
/** Extracts the credential from an `Authorization: Bearer <token>` header.
|
|
42
|
+
* Returns null for a missing, non-Bearer, or empty-value header. */
|
|
43
|
+
export declare function bearerFromAuthHeader(header: string | string[] | undefined): string | null;
|
|
44
|
+
/** Finds the token a presented credential matches, or null.
|
|
45
|
+
*
|
|
46
|
+
* Scans the WHOLE list with no early exit so the response time doesn't
|
|
47
|
+
* reveal which position matched (or how many tokens the operator
|
|
48
|
+
* configured). */
|
|
49
|
+
export declare function matchServeToken(tokens: readonly ServeToken[], presented: string | null): ServeToken | null;
|