kojee-mcp 0.5.2 → 0.5.4
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 +117 -0
- package/dist/chunk-2MIISF2W.js +35 -0
- package/dist/chunk-36L3GCU3.js +213 -0
- package/dist/{chunk-YEC7IHIG.js → chunk-62KH6VNQ.js} +49 -381
- package/dist/{chunk-ZW4SW7LJ.js → chunk-64EOLZNI.js} +14 -5
- package/dist/chunk-HIZ4NDWN.js +141 -0
- package/dist/chunk-LDZXU3DW.js +170 -0
- package/dist/chunk-OSKHA5DS.js +185 -0
- package/dist/{chunk-C6GZ2L2W.js → chunk-X672ZN7V.js} +5 -2
- package/dist/{chunk-WBMX4CHB.js → chunk-YVUXQ4Z2.js} +4 -32
- package/dist/cli.js +37 -9
- package/dist/{codex-stop-hook-JOTBCS5K.js → codex-stop-hook-SWA53ECG.js} +1 -1
- package/dist/control-token-TYDAL477.js +35 -0
- package/dist/{doctor-TSHOMT5X.js → doctor-TXWMMSRC.js} +2 -2
- package/dist/{doctor-codex-BMI5JOO6.js → doctor-codex-3A7KYOVX.js} +10 -3
- package/dist/{hook-server-QF5JVUHV.js → hook-server-NDJSV22J.js} +85 -0
- package/dist/index.js +6 -3
- package/dist/send-cli-7QJ36YY7.js +72 -0
- package/dist/{stop-hook-SEPWWETV.js → stop-hook-GO363SMD.js} +1 -1
- package/dist/{tail-stream-BYKO4DW6.js → tail-stream-U436QL2X.js} +2 -1
- package/dist/webhook-config-UKUSI2FE.js +20 -0
- package/dist/{webhook-sink-7OYZBWXA.js → webhook-sink-GCLL2S6S.js} +12 -3
- package/dist/{wizard-7KHD5JT4.js → wizard-Z5JA3YPV.js} +63 -26
- package/package.json +1 -1
- package/dist/chunk-F7L25L2J.js +0 -60
- package/dist/webhook-config-5TLLX7RA.js +0 -10
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
startEventStream
|
|
3
|
+
} from "./chunk-YVUXQ4Z2.js";
|
|
1
4
|
import {
|
|
2
5
|
deriveDiscoveryKey,
|
|
3
6
|
findClaudeAncestorPid
|
|
@@ -6,74 +9,26 @@ import {
|
|
|
6
9
|
buildCatchUpNote,
|
|
7
10
|
buildMonitorSpawn,
|
|
8
11
|
buildReplyRecipe
|
|
9
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-X672ZN7V.js";
|
|
10
13
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
GatewayClient,
|
|
15
|
+
generateES256KeyPair,
|
|
16
|
+
loadKeystore,
|
|
17
|
+
saveKeystore
|
|
18
|
+
} from "./chunk-36L3GCU3.js";
|
|
15
19
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "./chunk-BLEGIR35.js";
|
|
20
|
+
translateToolCallResult
|
|
21
|
+
} from "./chunk-LDZXU3DW.js";
|
|
19
22
|
|
|
20
23
|
// src/index.ts
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
+
import fs2 from "fs";
|
|
25
|
+
import os from "os";
|
|
26
|
+
import path2 from "path";
|
|
24
27
|
|
|
25
28
|
// src/auth/auth-module.ts
|
|
26
29
|
import { calculateJwkThumbprint } from "jose";
|
|
27
30
|
import crypto from "crypto";
|
|
28
31
|
|
|
29
|
-
// src/auth/keystore.ts
|
|
30
|
-
import { importJWK, exportJWK, generateKeyPair } from "jose";
|
|
31
|
-
import fs from "fs";
|
|
32
|
-
import os from "os";
|
|
33
|
-
import path from "path";
|
|
34
|
-
var DEFAULT_PATH = path.join(os.homedir(), ".kojee", "keypair.json");
|
|
35
|
-
async function loadKeystore(keystorePath = DEFAULT_PATH, expectedBrokerUrl) {
|
|
36
|
-
if (!fs.existsSync(keystorePath)) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
const raw = fs.readFileSync(keystorePath, "utf-8");
|
|
40
|
-
const data = JSON.parse(raw);
|
|
41
|
-
if (expectedBrokerUrl && data.broker_url !== expectedBrokerUrl) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
const privateKey = await importJWK(data.private_key_jwk, "ES256");
|
|
45
|
-
return {
|
|
46
|
-
privateKey,
|
|
47
|
-
publicJwk: data.public_jwk,
|
|
48
|
-
kid: data.kid,
|
|
49
|
-
data
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
async function saveKeystore(privateKey, publicJwk, kid, brokerUrl, keystorePath = DEFAULT_PATH) {
|
|
53
|
-
const dir = path.dirname(keystorePath);
|
|
54
|
-
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
55
|
-
secureDir(dir);
|
|
56
|
-
const privateJwk = await exportJWK(privateKey);
|
|
57
|
-
const data = {
|
|
58
|
-
private_key_jwk: privateJwk,
|
|
59
|
-
kid,
|
|
60
|
-
broker_url: brokerUrl,
|
|
61
|
-
public_jwk: publicJwk,
|
|
62
|
-
enrolled_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
63
|
-
};
|
|
64
|
-
fs.writeFileSync(keystorePath, JSON.stringify(data, null, 2), {
|
|
65
|
-
mode: 384
|
|
66
|
-
});
|
|
67
|
-
secureFile(keystorePath);
|
|
68
|
-
}
|
|
69
|
-
async function generateES256KeyPair() {
|
|
70
|
-
const { privateKey, publicKey } = await generateKeyPair("ES256");
|
|
71
|
-
const publicJwk = await exportJWK(publicKey);
|
|
72
|
-
publicJwk.kty = "EC";
|
|
73
|
-
publicJwk.crv = "P-256";
|
|
74
|
-
return { privateKey, publicJwk };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
32
|
// src/auth/registration.ts
|
|
78
33
|
async function registerKey(brokerUrl, token, publicJwk) {
|
|
79
34
|
const url = `${brokerUrl}/api/v1/bots/keys/register/`;
|
|
@@ -203,317 +158,6 @@ var AuthModule = class {
|
|
|
203
158
|
}
|
|
204
159
|
};
|
|
205
160
|
|
|
206
|
-
// src/gateway-client.ts
|
|
207
|
-
import crypto2 from "crypto";
|
|
208
|
-
|
|
209
|
-
// src/error-translator.ts
|
|
210
|
-
function translateGovernanceResult(result) {
|
|
211
|
-
const governance = result._meta?.governance;
|
|
212
|
-
if (!governance) return result;
|
|
213
|
-
if (governance.decision === "deny") {
|
|
214
|
-
return formatDenied(governance);
|
|
215
|
-
}
|
|
216
|
-
if (governance.decision === "require_approval") {
|
|
217
|
-
return formatApprovalRequired(governance);
|
|
218
|
-
}
|
|
219
|
-
return result;
|
|
220
|
-
}
|
|
221
|
-
function formatApprovalRequired(governance) {
|
|
222
|
-
const rules = formatRules(governance.triggered_guardrails);
|
|
223
|
-
const text = `APPROVAL REQUIRED: This action needs user approval before executing. Approval ID: ${governance.approval_id}. Expires: ${governance.expires_at ?? "unknown"}. Triggered rules: ${rules}. The user has been notified. Call kojee_check_approval with this approval_id to check the status.`;
|
|
224
|
-
return {
|
|
225
|
-
content: [{ type: "text", text }],
|
|
226
|
-
isError: true
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
function formatDenied(governance) {
|
|
230
|
-
const rules = formatRules(governance.triggered_guardrails);
|
|
231
|
-
const text = `DENIED: This action was blocked by governance policy. Triggered rules: ${rules}. This cannot proceed \u2014 modify your request or ask the user to adjust governance rules.`;
|
|
232
|
-
return {
|
|
233
|
-
content: [{ type: "text", text }],
|
|
234
|
-
isError: true
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
function translateHttpError(status, errorCode, trigger) {
|
|
238
|
-
if (status === 401) {
|
|
239
|
-
if (errorCode === "use_dpop_nonce") {
|
|
240
|
-
return null;
|
|
241
|
-
}
|
|
242
|
-
if (errorCode === "invalid_dpop_proof") {
|
|
243
|
-
return makeError(
|
|
244
|
-
"Authentication failed. The proxy will attempt to re-enroll. If this persists, regenerate your gateway token."
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
if (errorCode === "key_enrollment_required") {
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
return makeError(
|
|
251
|
-
"Gateway token is invalid or expired. Generate a new one."
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
if (status === 403 && errorCode === "step_up_required") {
|
|
255
|
-
const reason = trigger ? ` (reason: ${trigger})` : "";
|
|
256
|
-
return makeError(
|
|
257
|
-
`Device re-authorization required${reason}. This action can't proceed until the user re-authorizes this device in the Kojee dashboard.`
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
if (status === 429) {
|
|
261
|
-
return makeError(
|
|
262
|
-
"Rate limit exceeded. Wait before making more requests."
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
if (status >= 500) {
|
|
266
|
-
return makeError(
|
|
267
|
-
"Kojee gateway encountered an error. Try again."
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
var TANDEM_ERROR_MESSAGES = {
|
|
273
|
-
[-32003]: () => "This Tandem is hardened to owner-only membership; you can't join.",
|
|
274
|
-
[-32004]: () => "You aren't a member of that Tandem. Use tandem_join(join_link) first.",
|
|
275
|
-
[-32006]: (data) => {
|
|
276
|
-
const retry = data?.["retry_after_seconds"] ?? "a moment";
|
|
277
|
-
return `Rate limit hit on this Tandem. Retry after ${retry} seconds.`;
|
|
278
|
-
},
|
|
279
|
-
[-32007]: (data) => {
|
|
280
|
-
const rule = data?.["rule"] ?? "policy";
|
|
281
|
-
return `Message rejected by Tandem policy (${rule}).`;
|
|
282
|
-
},
|
|
283
|
-
[-32011]: (data) => {
|
|
284
|
-
const id = data?.["tandem_id"] ?? "unknown";
|
|
285
|
-
return `Tandem ${id} doesn't exist or isn't visible to you.`;
|
|
286
|
-
},
|
|
287
|
-
[-32015]: (data) => {
|
|
288
|
-
const candidates = data?.["candidates"] ?? [];
|
|
289
|
-
return `An @-mention matched multiple members and is ambiguous. Retry with explicit mentions[]. Candidates: ${candidates.join(", ")}.`;
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
function translateJsonRpcError(error) {
|
|
293
|
-
const msg = error.message ?? "";
|
|
294
|
-
const msgLower = msg.toLowerCase();
|
|
295
|
-
const tandemMessage = TANDEM_ERROR_MESSAGES[error.code];
|
|
296
|
-
if (tandemMessage) {
|
|
297
|
-
return {
|
|
298
|
-
content: [
|
|
299
|
-
{
|
|
300
|
-
type: "text",
|
|
301
|
-
text: tandemMessage(error.data)
|
|
302
|
-
}
|
|
303
|
-
],
|
|
304
|
-
isError: true
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
switch (error.code) {
|
|
308
|
-
case -32601:
|
|
309
|
-
return makeError(
|
|
310
|
-
`Tool not available. It may have been removed or is not connected. Check your connected services in the Kojee dashboard.`
|
|
311
|
-
);
|
|
312
|
-
case -32602:
|
|
313
|
-
return makeError(msg || "Invalid parameters for this tool call.");
|
|
314
|
-
case -32603: {
|
|
315
|
-
if (msgLower.includes("multiple accounts connected")) {
|
|
316
|
-
return makeError(msg);
|
|
317
|
-
}
|
|
318
|
-
if (msgLower.includes("not connected")) {
|
|
319
|
-
return makeError(
|
|
320
|
-
"The service is not connected. Connect it in the Kojee dashboard."
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
if (msgLower.includes("scope") && msgLower.includes("access")) {
|
|
324
|
-
return makeError(
|
|
325
|
-
"Token doesn't have access to this tool. Update scopes in the Kojee dashboard."
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
if (msgLower.includes("invalid_grant") || msgLower.includes("token refresh failed") || msgLower.includes("re-authorization") || msgLower.includes("reauthorization")) {
|
|
329
|
-
const serviceMatch = msg.match(
|
|
330
|
-
/(?:for|connected for)\s+(\w[\w-]*)/i
|
|
331
|
-
);
|
|
332
|
-
const service = serviceMatch ? serviceMatch[1] : "service";
|
|
333
|
-
return makeError(
|
|
334
|
-
`The ${service} connection needs re-authorization. Ask the user to reconnect it in the Kojee dashboard.`
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
return makeError(msg || "An internal error occurred on the gateway.");
|
|
338
|
-
}
|
|
339
|
-
case -32600:
|
|
340
|
-
return makeError(
|
|
341
|
-
"Unexpected response from gateway. This may be a temporary issue."
|
|
342
|
-
);
|
|
343
|
-
case -32e3:
|
|
344
|
-
return makeError(
|
|
345
|
-
"Rate limit exceeded. Wait before making more requests."
|
|
346
|
-
);
|
|
347
|
-
default:
|
|
348
|
-
return makeError(msg || "An unknown error occurred.");
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
function translateNetworkError(_error) {
|
|
352
|
-
return makeError(
|
|
353
|
-
"Cannot reach Kojee gateway. Check your connection."
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
function translateToolCallResult(result) {
|
|
357
|
-
if (result._meta?.governance) {
|
|
358
|
-
return translateGovernanceResult(result);
|
|
359
|
-
}
|
|
360
|
-
return result;
|
|
361
|
-
}
|
|
362
|
-
function formatRules(guardrails) {
|
|
363
|
-
if (!guardrails || guardrails.length === 0) return "unknown";
|
|
364
|
-
return guardrails.join(", ");
|
|
365
|
-
}
|
|
366
|
-
function makeError(text) {
|
|
367
|
-
return {
|
|
368
|
-
content: [{ type: "text", text }],
|
|
369
|
-
isError: true
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// src/gateway-client.ts
|
|
374
|
-
var GatewayClient = class {
|
|
375
|
-
constructor(brokerUrl, token, privateKey, kid, sessionId) {
|
|
376
|
-
this.brokerUrl = brokerUrl;
|
|
377
|
-
this.token = token;
|
|
378
|
-
this.privateKey = privateKey;
|
|
379
|
-
this.kid = kid;
|
|
380
|
-
this.endpoint = `${brokerUrl}/mcp/messages/${sessionId}/`;
|
|
381
|
-
}
|
|
382
|
-
brokerUrl;
|
|
383
|
-
token;
|
|
384
|
-
privateKey;
|
|
385
|
-
kid;
|
|
386
|
-
currentNonce;
|
|
387
|
-
requestCounter = 0;
|
|
388
|
-
endpoint;
|
|
389
|
-
/**
|
|
390
|
-
* Expose the DPoP signing key so peer modules sharing auth state
|
|
391
|
-
* (e.g. tandem/event-stream.ts) can sign their own requests.
|
|
392
|
-
*/
|
|
393
|
-
getPrivateKey() {
|
|
394
|
-
return this.privateKey;
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Expose the bot_key_id (kid) for DPoP proof headers. Paired with
|
|
398
|
-
* getPrivateKey() so peer modules can construct proofs without
|
|
399
|
-
* threading the key material through their own constructors.
|
|
400
|
-
*/
|
|
401
|
-
getKid() {
|
|
402
|
-
return this.kid;
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* Derive a deterministic session ID from the gateway token.
|
|
406
|
-
* session_id = sha256(token + "proxy").slice(0, 16)
|
|
407
|
-
*/
|
|
408
|
-
static deriveSessionId(token) {
|
|
409
|
-
const hash = crypto2.createHash("sha256").update(token + "proxy").digest("hex");
|
|
410
|
-
return hash.slice(0, 16);
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Send a JSON-RPC 2.0 request to the gateway, handling DPoP auth and
|
|
414
|
-
* nonce retry transparently. A 403 `step_up_required` (deprecated feature,
|
|
415
|
-
* owner ruling 2026-06-10) is no longer polled — it surfaces immediately as
|
|
416
|
-
* a structured tool error via translateHttpError.
|
|
417
|
-
*
|
|
418
|
-
* `signal` (ROUND-3 MAJOR A) is a REAL AbortSignal threaded into the
|
|
419
|
-
* underlying `fetch` option — NOT placed inside `params`/`arguments`. A
|
|
420
|
-
* caller with a per-call timeout budget (e.g. resubscribeMemberships) passes
|
|
421
|
-
* its controller's signal here so a hung backend aborts at the budget instead
|
|
422
|
-
* of hanging forever. Putting the signal in `arguments` (the round-2 bug) both
|
|
423
|
-
* left fetch un-aborted AND serialized a junk `{}` onto the wire body.
|
|
424
|
-
*/
|
|
425
|
-
async sendRpc(method, params = {}, signal) {
|
|
426
|
-
const rpcRequest = {
|
|
427
|
-
jsonrpc: "2.0",
|
|
428
|
-
id: ++this.requestCounter,
|
|
429
|
-
method,
|
|
430
|
-
params
|
|
431
|
-
};
|
|
432
|
-
return this.executeWithRetries(rpcRequest, signal);
|
|
433
|
-
}
|
|
434
|
-
async executeWithRetries(rpcRequest, signal) {
|
|
435
|
-
let response;
|
|
436
|
-
try {
|
|
437
|
-
response = await this.sendHttpRequest(rpcRequest, signal);
|
|
438
|
-
} catch (err) {
|
|
439
|
-
return translateNetworkError(err);
|
|
440
|
-
}
|
|
441
|
-
this.trackNonce(response);
|
|
442
|
-
if (response.status === 401) {
|
|
443
|
-
const body = await this.tryParseErrorBody(response);
|
|
444
|
-
if (body?.error === "use_dpop_nonce") {
|
|
445
|
-
console.error("[gateway] Nonce expired, retrying with fresh nonce...");
|
|
446
|
-
try {
|
|
447
|
-
response = await this.sendHttpRequest(rpcRequest, signal);
|
|
448
|
-
} catch (err) {
|
|
449
|
-
return translateNetworkError(err);
|
|
450
|
-
}
|
|
451
|
-
this.trackNonce(response);
|
|
452
|
-
} else {
|
|
453
|
-
const translated = translateHttpError(401, body?.error);
|
|
454
|
-
if (translated) return translated;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
if (response.status === 403) {
|
|
458
|
-
const body = await this.tryParseErrorBody(response);
|
|
459
|
-
const translated = translateHttpError(403, body?.error, body?.trigger);
|
|
460
|
-
if (translated) return translated;
|
|
461
|
-
}
|
|
462
|
-
if (!response.ok) {
|
|
463
|
-
const body = await this.tryParseErrorBody(response);
|
|
464
|
-
const translated = translateHttpError(response.status, body?.error);
|
|
465
|
-
if (translated) return translated;
|
|
466
|
-
return {
|
|
467
|
-
content: [{ type: "text", text: `Gateway error: ${response.status}` }],
|
|
468
|
-
isError: true
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
const rpcResponse = await response.json();
|
|
472
|
-
if (rpcResponse.error) {
|
|
473
|
-
return translateJsonRpcError(rpcResponse.error);
|
|
474
|
-
}
|
|
475
|
-
const result = rpcResponse.result;
|
|
476
|
-
return result ?? { content: [{ type: "text", text: "No result" }] };
|
|
477
|
-
}
|
|
478
|
-
async sendHttpRequest(rpcRequest, signal) {
|
|
479
|
-
const proof = await createDPoPProof(
|
|
480
|
-
this.privateKey,
|
|
481
|
-
this.kid,
|
|
482
|
-
"POST",
|
|
483
|
-
this.endpoint,
|
|
484
|
-
this.currentNonce,
|
|
485
|
-
this.token
|
|
486
|
-
);
|
|
487
|
-
return fetch(this.endpoint, {
|
|
488
|
-
method: "POST",
|
|
489
|
-
headers: {
|
|
490
|
-
"Content-Type": "application/json",
|
|
491
|
-
Authorization: `DPoP ${this.token}`,
|
|
492
|
-
DPoP: proof,
|
|
493
|
-
"Mcp-Session-Id": MCP_SESSION_ID
|
|
494
|
-
},
|
|
495
|
-
body: JSON.stringify(rpcRequest),
|
|
496
|
-
// ROUND-3 MAJOR A: the caller's AbortSignal rides HERE (a real fetch
|
|
497
|
-
// option), never inside the JSON-RPC body. `undefined` is a valid value
|
|
498
|
-
// for the fetch `signal` option (no abort wired).
|
|
499
|
-
...signal ? { signal } : {}
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
trackNonce(response) {
|
|
503
|
-
const nonce = response.headers.get("DPoP-Nonce");
|
|
504
|
-
if (nonce) {
|
|
505
|
-
this.currentNonce = nonce;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
async tryParseErrorBody(response) {
|
|
509
|
-
try {
|
|
510
|
-
return await response.json();
|
|
511
|
-
} catch {
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
};
|
|
516
|
-
|
|
517
161
|
// src/tool-registry.ts
|
|
518
162
|
var ToolRegistry = class {
|
|
519
163
|
constructor(gateway) {
|
|
@@ -590,15 +234,15 @@ import {
|
|
|
590
234
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
591
235
|
|
|
592
236
|
// src/version.ts
|
|
593
|
-
import
|
|
594
|
-
import
|
|
237
|
+
import fs from "fs";
|
|
238
|
+
import path from "path";
|
|
595
239
|
import { fileURLToPath } from "url";
|
|
596
240
|
var FALLBACK_VERSION = "0.0.0-unknown";
|
|
597
241
|
function resolveVersion() {
|
|
598
242
|
try {
|
|
599
|
-
const here =
|
|
243
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
600
244
|
const parsed = JSON.parse(
|
|
601
|
-
|
|
245
|
+
fs.readFileSync(path.join(here, "..", "package.json"), "utf8")
|
|
602
246
|
);
|
|
603
247
|
return typeof parsed?.version === "string" && parsed.version ? parsed.version : FALLBACK_VERSION;
|
|
604
248
|
} catch (err) {
|
|
@@ -751,7 +395,7 @@ var unknownAdapter = {
|
|
|
751
395
|
};
|
|
752
396
|
|
|
753
397
|
// src/index.ts
|
|
754
|
-
var DEFAULT_KEYSTORE_PATH =
|
|
398
|
+
var DEFAULT_KEYSTORE_PATH = path2.join(os.homedir(), ".kojee", "keypair.json");
|
|
755
399
|
function isDPoPEnrollmentError(err) {
|
|
756
400
|
const msg = String(err?.message ?? err ?? "").toLowerCase();
|
|
757
401
|
if (msg.includes("invalid or expired") && msg.includes("token")) return false;
|
|
@@ -804,7 +448,7 @@ async function startProxy(config) {
|
|
|
804
448
|
let server;
|
|
805
449
|
if (adapter.supportsChannels) {
|
|
806
450
|
const { EventQueue } = await import("./event-queue-5YVJFR3E.js");
|
|
807
|
-
const { startHookServer } = await import("./hook-server-
|
|
451
|
+
const { startHookServer } = await import("./hook-server-NDJSV22J.js");
|
|
808
452
|
const {
|
|
809
453
|
writeDiscoveryByKey,
|
|
810
454
|
cleanupDiscoveryByKey,
|
|
@@ -812,8 +456,8 @@ async function startProxy(config) {
|
|
|
812
456
|
} = await import("./session-discovery-FNMJGFPM.js");
|
|
813
457
|
const { startEventLog, sweepStaleEventLogs } = await import("./event-log-RSTM4PLL.js");
|
|
814
458
|
const { resubscribeMemberships } = await import("./resubscribe-SLZNA76S.js");
|
|
815
|
-
const { resolveWebhookConfig } = await import("./webhook-config-
|
|
816
|
-
const { createWebhookSink } = await import("./webhook-sink-
|
|
459
|
+
const { resolveWebhookConfig } = await import("./webhook-config-UKUSI2FE.js");
|
|
460
|
+
const { createWebhookSink } = await import("./webhook-sink-GCLL2S6S.js");
|
|
817
461
|
sweepStaleDiscovery();
|
|
818
462
|
sweepStaleEventLogs();
|
|
819
463
|
const ccPid = await findClaudeAncestorPid();
|
|
@@ -826,6 +470,11 @@ async function startProxy(config) {
|
|
|
826
470
|
void eventLog.appendStatus(`status=webhook error="${webhookResolution.error}"`).catch(() => {
|
|
827
471
|
});
|
|
828
472
|
}
|
|
473
|
+
if (webhookResolution.warning) {
|
|
474
|
+
console.error(`[kojee-mcp] webhook sink WARNING: ${webhookResolution.warning}`);
|
|
475
|
+
void eventLog.appendStatus(`status=webhook warning="${webhookResolution.warning}"`).catch(() => {
|
|
476
|
+
});
|
|
477
|
+
}
|
|
829
478
|
const webhookSink = webhookResolution.enabled && webhookResolution.config ? createWebhookSink(webhookResolution.config, {
|
|
830
479
|
// Route delivery/failure observability to the STATUS sink.
|
|
831
480
|
log: (line) => {
|
|
@@ -839,12 +488,23 @@ async function startProxy(config) {
|
|
|
839
488
|
});
|
|
840
489
|
}
|
|
841
490
|
server = createMcpServer(registry, adapter, tandemMembershipCount, eventLog.path);
|
|
491
|
+
const { issueControlToken, controlTokenPath } = await import("./control-token-TYDAL477.js");
|
|
492
|
+
let controlToken = null;
|
|
493
|
+
try {
|
|
494
|
+
controlToken = issueControlToken();
|
|
495
|
+
} catch (err) {
|
|
496
|
+
console.error(
|
|
497
|
+
"[kojee-mcp] control token write failed \u2014 POST /send disabled:",
|
|
498
|
+
err.message
|
|
499
|
+
);
|
|
500
|
+
}
|
|
842
501
|
const queue = new EventQueue();
|
|
843
502
|
let streamHandle = null;
|
|
844
503
|
const hookServer = await startHookServer({
|
|
845
504
|
port: 0,
|
|
846
505
|
queue,
|
|
847
506
|
adapter,
|
|
507
|
+
...controlToken !== null ? { send: { gateway, authToken: controlToken } } : {},
|
|
848
508
|
getStreamState: () => streamHandle ? streamHandle.getState() : {
|
|
849
509
|
connected: false,
|
|
850
510
|
connectedSince: null,
|
|
@@ -869,6 +529,9 @@ async function startProxy(config) {
|
|
|
869
529
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
870
530
|
brokerUrl: config.url,
|
|
871
531
|
eventLogPath: eventLog.path,
|
|
532
|
+
// Advertise where the POST /send bearer lives so local consumers
|
|
533
|
+
// (native gateway plugins) can find it without guessing ~/.kojee.
|
|
534
|
+
...controlToken !== null ? { controlTokenPath: controlTokenPath() } : {},
|
|
872
535
|
// Stamp the auth mode so `kojee-mcp doctor` renders the pairing check
|
|
873
536
|
// honestly: a token-mode box has no ~/.kojee/config.json by design and
|
|
874
537
|
// must not hard-fail on "paired config: MISSING". Defaults to "paired"
|
|
@@ -934,8 +597,8 @@ async function startProxy(config) {
|
|
|
934
597
|
});
|
|
935
598
|
} else if (needsWebhookEventStream()) {
|
|
936
599
|
const { startEventLog, sweepStaleEventLogs } = await import("./event-log-RSTM4PLL.js");
|
|
937
|
-
const { resolveWebhookConfig } = await import("./webhook-config-
|
|
938
|
-
const { createWebhookSink } = await import("./webhook-sink-
|
|
600
|
+
const { resolveWebhookConfig } = await import("./webhook-config-UKUSI2FE.js");
|
|
601
|
+
const { createWebhookSink } = await import("./webhook-sink-GCLL2S6S.js");
|
|
939
602
|
const { resubscribeMemberships } = await import("./resubscribe-SLZNA76S.js");
|
|
940
603
|
sweepStaleEventLogs();
|
|
941
604
|
const ccPid = await findClaudeAncestorPid();
|
|
@@ -948,6 +611,11 @@ async function startProxy(config) {
|
|
|
948
611
|
void eventLog.appendStatus(`status=webhook error="${webhookResolution.error}"`).catch(() => {
|
|
949
612
|
});
|
|
950
613
|
}
|
|
614
|
+
if (webhookResolution.warning) {
|
|
615
|
+
console.error(`[kojee-mcp] webhook sink WARNING: ${webhookResolution.warning}`);
|
|
616
|
+
void eventLog.appendStatus(`status=webhook warning="${webhookResolution.warning}"`).catch(() => {
|
|
617
|
+
});
|
|
618
|
+
}
|
|
951
619
|
const webhookSink = webhookResolution.enabled && webhookResolution.config ? createWebhookSink(webhookResolution.config, {
|
|
952
620
|
log: (line) => {
|
|
953
621
|
void eventLog.appendStatus(`status=webhook ${line}`).catch(() => {
|
|
@@ -1021,7 +689,7 @@ async function enrollAndDiscover(config, keystorePath, isRetry = false) {
|
|
|
1021
689
|
"[kojee-mcp] Auth failed, attempting recovery with fresh enrollment..."
|
|
1022
690
|
);
|
|
1023
691
|
try {
|
|
1024
|
-
if (
|
|
692
|
+
if (fs2.existsSync(keystorePath)) fs2.unlinkSync(keystorePath);
|
|
1025
693
|
} catch (unlinkErr) {
|
|
1026
694
|
console.error("[kojee-mcp] Could not remove stale keystore:", unlinkErr);
|
|
1027
695
|
}
|
|
@@ -24,7 +24,9 @@ function buildCodexMcpServerTable(opts) {
|
|
|
24
24
|
"[mcp_servers.kojee.env]",
|
|
25
25
|
'KOJEE_RUNTIME = "codex"',
|
|
26
26
|
`KOJEE_WEBHOOK_URL = "${escapeTomlString(opts.webhookUrl)}"`,
|
|
27
|
-
`KOJEE_WEBHOOK_SECRET = "${escapeTomlString(opts.webhookSecret)}"
|
|
27
|
+
`KOJEE_WEBHOOK_SECRET = "${escapeTomlString(opts.webhookSecret)}"`,
|
|
28
|
+
// Signature emission overrides (0.5.3) — emitted only when configured.
|
|
29
|
+
...(opts.signatureEnv ?? []).map(([k, v]) => `${k} = "${escapeTomlString(v)}"`)
|
|
28
30
|
].join("\n");
|
|
29
31
|
}
|
|
30
32
|
function buildCodexStopHookBlock() {
|
|
@@ -48,7 +50,7 @@ function writeCodexConfig(inputs) {
|
|
|
48
50
|
toml = fs.readFileSync(configPath, "utf8");
|
|
49
51
|
} catch {
|
|
50
52
|
}
|
|
51
|
-
toml = upsertKojeeTomlTables(toml, inputs.webhookUrl, inputs.webhookSecret);
|
|
53
|
+
toml = upsertKojeeTomlTables(toml, inputs.webhookUrl, inputs.webhookSecret, inputs.signatureEnv ?? []);
|
|
52
54
|
writeFile600(configPath, toml);
|
|
53
55
|
const hooks = readJson(hooksPath);
|
|
54
56
|
hooks.hooks ??= {};
|
|
@@ -93,10 +95,14 @@ function removeCodexConfig(opts = {}) {
|
|
|
93
95
|
}
|
|
94
96
|
return result;
|
|
95
97
|
}
|
|
96
|
-
function upsertKojeeTomlTables(existing, webhookUrl, webhookSecret) {
|
|
98
|
+
function upsertKojeeTomlTables(existing, webhookUrl, webhookSecret, signatureEnv = []) {
|
|
97
99
|
const parsed = extractKojeeBlock(existing);
|
|
98
100
|
if (!parsed) {
|
|
99
|
-
const block = buildCodexMcpServerTable({
|
|
101
|
+
const block = buildCodexMcpServerTable({
|
|
102
|
+
webhookUrl,
|
|
103
|
+
webhookSecret,
|
|
104
|
+
...signatureEnv.length > 0 ? { signatureEnv } : {}
|
|
105
|
+
});
|
|
100
106
|
const base2 = existing.replace(/\s*$/, "");
|
|
101
107
|
return base2.length === 0 ? block + "\n" : base2 + "\n\n" + block + "\n";
|
|
102
108
|
}
|
|
@@ -107,7 +113,10 @@ function upsertKojeeTomlTables(existing, webhookUrl, webhookSecret) {
|
|
|
107
113
|
const envKeys = upsertKeyLines(parsed.envKeys, [
|
|
108
114
|
["KOJEE_RUNTIME", '"codex"'],
|
|
109
115
|
["KOJEE_WEBHOOK_URL", `"${escapeTomlString(webhookUrl)}"`],
|
|
110
|
-
["KOJEE_WEBHOOK_SECRET", `"${escapeTomlString(webhookSecret)}"`]
|
|
116
|
+
["KOJEE_WEBHOOK_SECRET", `"${escapeTomlString(webhookSecret)}"`],
|
|
117
|
+
// Signature emission overrides (0.5.3) — owned only when configured this
|
|
118
|
+
// run; an operator's existing signature lines are otherwise preserved.
|
|
119
|
+
...signatureEnv.map(([k, v]) => [k, `"${escapeTomlString(v)}"`])
|
|
111
120
|
]);
|
|
112
121
|
const merged = [
|
|
113
122
|
KOJEE_TABLE_HEADER,
|