@theronap/cortex-mcp 0.9.112 → 0.9.114
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/lib/diagnose.mjs +20 -2
- package/lib/server.mjs +6 -2
- package/package.json +1 -1
package/lib/diagnose.mjs
CHANGED
|
@@ -238,11 +238,29 @@ export function classify(status, contentType, bodyText, requestId) {
|
|
|
238
238
|
let SESSION_KEY = null
|
|
239
239
|
export function setSessionKey(key) { SESSION_KEY = key || null }
|
|
240
240
|
|
|
241
|
+
// WHO IS CALLING — an identity INDEPENDENT of the session key, and it must stay independent.
|
|
242
|
+
//
|
|
243
|
+
// The server labels every page revision's `actor_source` from this header. It used to default to
|
|
244
|
+
// 'mcp' server-side with nothing setting it, so 'mcp' silently meant "unlabelled" and an ADR-0030
|
|
245
|
+
// scrub run against the HTTP API on 2026-08-26 wrote 340 revisions that read as MCP sessions with
|
|
246
|
+
// dropped attribution — which reopened gate 2 on 2026-08-27 against a cause that was never MCP.
|
|
247
|
+
//
|
|
248
|
+
// ⚠ Deliberately NOT derived from SESSION_KEY, and deliberately sent even when SESSION_KEY is null.
|
|
249
|
+
// The alarm's whole subject is `actor_source='mcp' AND session_key IS NULL`; if this header were
|
|
250
|
+
// conditional on the session, that pair could never occur and the detector would be structurally
|
|
251
|
+
// blind. A one-shot hook process legitimately has no session and IS still cortex-mcp — it should
|
|
252
|
+
// say so, and be visible as one, not disguise itself as a script.
|
|
253
|
+
let CLIENT_ID = 'cortex-mcp/unknown'
|
|
254
|
+
export function setClientVersion(version) {
|
|
255
|
+
CLIENT_ID = `cortex-mcp/${version || 'unknown'}`
|
|
256
|
+
}
|
|
257
|
+
|
|
241
258
|
export async function fetchCortex(url, opts = {}, { retries = 2, baseDelayMs = 400 } = {}) {
|
|
242
259
|
const { timeoutMs: optTimeout, signal: callerSignal, ...rest } = opts
|
|
260
|
+
const baseHeaders = { ...(rest.headers ?? {}), 'x-cortex-client': CLIENT_ID }
|
|
243
261
|
const fetchOpts = SESSION_KEY
|
|
244
|
-
? { ...rest, headers: { ...
|
|
245
|
-
: rest
|
|
262
|
+
? { ...rest, headers: { ...baseHeaders, 'x-cortex-session-key': SESSION_KEY } }
|
|
263
|
+
: { ...rest, headers: baseHeaders }
|
|
246
264
|
const timeoutMs = optTimeout ?? (Number(process.env.CORTEX_HTTP_TIMEOUT_MS) || 15_000)
|
|
247
265
|
let lastErr
|
|
248
266
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
package/lib/server.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { writeFileSync, mkdirSync } from 'fs'
|
|
|
5
5
|
import { homedir } from 'os'
|
|
6
6
|
import { join } from 'path'
|
|
7
7
|
import { createHash, randomUUID } from 'crypto'
|
|
8
|
-
import { fetchCortex, classify, resolveBase, resolveEnvToken, setSessionKey } from './diagnose.mjs'
|
|
8
|
+
import { fetchCortex, classify, resolveBase, resolveEnvToken, setSessionKey, setClientVersion } from './diagnose.mjs'
|
|
9
9
|
import { folderClaimLine } from './folder_claims.mjs'
|
|
10
10
|
import { resolveSessionKey, resolveLogSessionId } from './session_key.mjs'
|
|
11
11
|
import { runSendImessage } from './imessage_send.mjs'
|
|
@@ -104,6 +104,10 @@ export async function runServer(version) {
|
|
|
104
104
|
// resolve THIS session's brain pointer. Must be set BEFORE the first fetchCortex call below —
|
|
105
105
|
// otherwise the opening requests of a session would silently resolve by the account pointer.
|
|
106
106
|
setSessionKey(SESSION_KEY)
|
|
107
|
+
// Paired with setSessionKey and set at the same moment, but a SEPARATE signal on purpose: the
|
|
108
|
+
// server labels actor_source from this and watches for `mcp` + no session. Tying the two together
|
|
109
|
+
// would make that pair impossible and blind the detector. See actorSourceFrom in auth_cloud.ts.
|
|
110
|
+
setClientVersion(version)
|
|
107
111
|
async function pingSession() {
|
|
108
112
|
try {
|
|
109
113
|
await fetchCortex(`${BASE}/api/session-ping`, {
|
|
@@ -2542,7 +2546,7 @@ export async function runServer(version) {
|
|
|
2542
2546
|
description: 'Attach a pending record to the pages you judge correct — the point of the whole triage path. Use this when you have real context on what the work was; that judgment is better than any rule the webhook could run. Attachments are additive: existing deterministic homes (routing identifiers, your profile) stay. Pass park=true instead when you have looked and there is genuinely no good home — parking beats attaching to a page that merely shares a word.',
|
|
2543
2547
|
inputSchema: {
|
|
2544
2548
|
recordId: z.string().describe('record id from pending_records'),
|
|
2545
|
-
documentIds: z.array(z.string()).optional().describe('
|
|
2549
|
+
documentIds: z.array(z.string()).optional().describe('pages to attach. Accepts EITHER a brain_documents id (from a pending_records sweep) OR the `ref:` that read_page prints for a page — they are different columns and read_page only ever gives you the second, which is why this used to fail as no_documents on ids that looked perfectly valid. A ref resolves to that node\'s current page, accessible copy first. Anything that resolves to no page in this brain is named back to you rather than silently dropped.'),
|
|
2546
2550
|
reason: z.string().describe('why these pages — recorded with the attachment'),
|
|
2547
2551
|
park: z.boolean().optional().describe('no good home exists; leave it alone rather than guessing'),
|
|
2548
2552
|
tier: z.number().int().optional().describe('2 when this came from the cheap sweep rather than your own context'),
|
package/package.json
CHANGED