@sentropic/h2a 0.97.2 → 0.97.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/dist/bin.js +13 -0
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +94 -37
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +2 -0
- package/dist/mcp.js.map +1 -1
- package/dist/runtime/identity/bindings.d.ts +33 -0
- package/dist/runtime/identity/bindings.d.ts.map +1 -1
- package/dist/runtime/identity/bindings.js +65 -2
- package/dist/runtime/identity/bindings.js.map +1 -1
- package/dist/runtime/identity/index.d.ts +4 -4
- package/dist/runtime/identity/index.d.ts.map +1 -1
- package/dist/runtime/identity/index.js +2 -2
- package/dist/runtime/identity/index.js.map +1 -1
- package/dist/runtime/identity/live.d.ts +63 -0
- package/dist/runtime/identity/live.d.ts.map +1 -1
- package/dist/runtime/identity/live.js +346 -65
- package/dist/runtime/identity/live.js.map +1 -1
- package/dist/runtime/identity/worker.d.ts +19 -0
- package/dist/runtime/identity/worker.d.ts.map +1 -0
- package/dist/runtime/identity/worker.js +189 -0
- package/dist/runtime/identity/worker.js.map +1 -0
- package/dist/runtime/local-files/locks.d.ts +34 -0
- package/dist/runtime/local-files/locks.d.ts.map +1 -1
- package/dist/runtime/local-files/locks.js +55 -2
- package/dist/runtime/local-files/locks.js.map +1 -1
- package/dist/runtime/local-files/store.d.ts +21 -0
- package/dist/runtime/local-files/store.d.ts.map +1 -1
- package/dist/runtime/local-files/store.js +79 -10
- package/dist/runtime/local-files/store.js.map +1 -1
- package/dist/runtime/mcp/discovery-pagination.d.ts +73 -0
- package/dist/runtime/mcp/discovery-pagination.d.ts.map +1 -0
- package/dist/runtime/mcp/discovery-pagination.js +446 -0
- package/dist/runtime/mcp/discovery-pagination.js.map +1 -0
- package/dist/runtime/mcp/frame-budget.d.ts +149 -0
- package/dist/runtime/mcp/frame-budget.d.ts.map +1 -0
- package/dist/runtime/mcp/frame-budget.js +275 -0
- package/dist/runtime/mcp/frame-budget.js.map +1 -0
- package/dist/runtime/mcp/handlers.d.ts +14 -3
- package/dist/runtime/mcp/handlers.d.ts.map +1 -1
- package/dist/runtime/mcp/handlers.js +22 -1
- package/dist/runtime/mcp/handlers.js.map +1 -1
- package/dist/runtime/mcp/identity-state.d.ts +131 -0
- package/dist/runtime/mcp/identity-state.d.ts.map +1 -0
- package/dist/runtime/mcp/identity-state.js +288 -0
- package/dist/runtime/mcp/identity-state.js.map +1 -0
- package/dist/runtime/mcp/index.d.ts +2 -0
- package/dist/runtime/mcp/index.d.ts.map +1 -1
- package/dist/runtime/mcp/index.js +2 -0
- package/dist/runtime/mcp/index.js.map +1 -1
- package/dist/runtime/mcp/notifications.d.ts +13 -1
- package/dist/runtime/mcp/notifications.d.ts.map +1 -1
- package/dist/runtime/mcp/notifications.js +36 -20
- package/dist/runtime/mcp/notifications.js.map +1 -1
- package/dist/runtime/mcp/payload-store.d.ts +67 -0
- package/dist/runtime/mcp/payload-store.d.ts.map +1 -0
- package/dist/runtime/mcp/payload-store.js +242 -0
- package/dist/runtime/mcp/payload-store.js.map +1 -0
- package/dist/runtime/mcp/phase-trace.d.ts +98 -0
- package/dist/runtime/mcp/phase-trace.d.ts.map +1 -0
- package/dist/runtime/mcp/phase-trace.js +179 -0
- package/dist/runtime/mcp/phase-trace.js.map +1 -0
- package/dist/runtime/mcp/server.d.ts +36 -1
- package/dist/runtime/mcp/server.d.ts.map +1 -1
- package/dist/runtime/mcp/server.js +162 -5
- package/dist/runtime/mcp/server.js.map +1 -1
- package/dist/runtime/mcp/sessions.d.ts +12 -2
- package/dist/runtime/mcp/sessions.d.ts.map +1 -1
- package/dist/runtime/mcp/sessions.js +15 -3
- package/dist/runtime/mcp/sessions.js.map +1 -1
- package/dist/runtime/mcp/stdio.d.ts +21 -0
- package/dist/runtime/mcp/stdio.d.ts.map +1 -1
- package/dist/runtime/mcp/stdio.js +378 -98
- package/dist/runtime/mcp/stdio.js.map +1 -1
- package/dist/runtime/mcp/tools.d.ts.map +1 -1
- package/dist/runtime/mcp/tools.js +47 -4
- package/dist/runtime/mcp/tools.js.map +1 -1
- package/dist/runtime/mcp-central.d.ts.map +1 -1
- package/dist/runtime/mcp-central.js +17 -7
- package/dist/runtime/mcp-central.js.map +1 -1
- package/dist/runtime/mcp-http/hosted-mcp-server.d.ts.map +1 -1
- package/dist/runtime/mcp-http/hosted-mcp-server.js +16 -6
- package/dist/runtime/mcp-http/hosted-mcp-server.js.map +1 -1
- package/dist/runtime/mcp-http/readonly-allowlist.d.ts.map +1 -1
- package/dist/runtime/mcp-http/readonly-allowlist.js +6 -0
- package/dist/runtime/mcp-http/readonly-allowlist.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/runtime/mcp/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,gBAAgB,EAGjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/runtime/mcp/notifications.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable recovery store for oversize MCP outputs (lot L1).
|
|
3
|
+
*
|
|
4
|
+
* When a legitimate response or notification exceeds the frame budget
|
|
5
|
+
* (`frame-budget.ts`), the INTACT original bytes are persisted here BEFORE the
|
|
6
|
+
* bounded error/notice is emitted, and the client recovers them in budget-sized
|
|
7
|
+
* chunks through the `h2a_read_payload` tool. The store never holds tool
|
|
8
|
+
* arguments or a private key — only output already destined for THIS client —
|
|
9
|
+
* and it never re-executes an effectful tool: it hands back stored bytes.
|
|
10
|
+
*
|
|
11
|
+
* Security properties (DEC-116 lineage):
|
|
12
|
+
* - Root/tenant confinement: one store per store-root; a ref is a 256-bit
|
|
13
|
+
* random capability whose file name IS the ref, so there is no free path, no
|
|
14
|
+
* `../`, and no cross-tenant lookup. Symlinks are refused on read.
|
|
15
|
+
* - Atomic publication: content is staged to a temp file (0600) and renamed.
|
|
16
|
+
* - Integrity: every payload carries a SHA-256 the reader verifies after
|
|
17
|
+
* reassembly.
|
|
18
|
+
* - Durability across restart: refs survive; nothing is purged at boot. Only a
|
|
19
|
+
* dedicated maintenance sweep removes EXPIRED refs, never live data.
|
|
20
|
+
*/
|
|
21
|
+
/** Announced recovery window: a ref is readable for 24h after it is written. */
|
|
22
|
+
export declare const PAYLOAD_TTL_MS: number;
|
|
23
|
+
/** Proposed per-root ceiling (measured against the store dir before a write). */
|
|
24
|
+
export declare const PAYLOAD_ROOT_QUOTA_BYTES: number;
|
|
25
|
+
/** Hard maximum a single read may return (also bounded again by the frame budget). */
|
|
26
|
+
export declare const PAYLOAD_MAX_READ_BYTES = 65536;
|
|
27
|
+
export interface PayloadRecoveryRef {
|
|
28
|
+
/** Opaque 256-bit capability. */
|
|
29
|
+
readonly ref: string;
|
|
30
|
+
readonly sha256: string;
|
|
31
|
+
readonly totalBytes: number;
|
|
32
|
+
readonly expiresAt: string;
|
|
33
|
+
}
|
|
34
|
+
export type PayloadReadError = "payload_not_found" | "payload_expired" | "invalid_offset" | "payload_corrupt";
|
|
35
|
+
export interface PayloadReadChunk {
|
|
36
|
+
readonly ref: string;
|
|
37
|
+
readonly encoding: "base64";
|
|
38
|
+
readonly data: string;
|
|
39
|
+
readonly offset: number;
|
|
40
|
+
readonly nextOffset: number | null;
|
|
41
|
+
readonly totalBytes: number;
|
|
42
|
+
readonly sha256: string;
|
|
43
|
+
readonly expiresAt: string;
|
|
44
|
+
}
|
|
45
|
+
export declare class PayloadStoreError extends Error {
|
|
46
|
+
readonly reason: "recovery_unavailable";
|
|
47
|
+
readonly cause: string;
|
|
48
|
+
constructor(reason: "recovery_unavailable", cause: string, message: string);
|
|
49
|
+
}
|
|
50
|
+
export interface PayloadStore {
|
|
51
|
+
/** The scope (store-root) every ref of this store is bound to. */
|
|
52
|
+
readonly scope: string;
|
|
53
|
+
/** Persist output bytes and return the recovery ref, or throw PayloadStoreError. */
|
|
54
|
+
persistOutput(bytes: Buffer): PayloadRecoveryRef;
|
|
55
|
+
/** Read a budget-sized chunk of a ref, or a typed read error. */
|
|
56
|
+
readPayload(ref: string, offset: number, maxBytes: number): PayloadReadChunk | {
|
|
57
|
+
error: PayloadReadError;
|
|
58
|
+
};
|
|
59
|
+
/** Remove ONLY expired refs (dedicated maintenance; never touches live data). */
|
|
60
|
+
sweepExpired(now?: number): number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A payload store confined to `<root>/mcp-payloads/`. `scope` defaults to the
|
|
64
|
+
* root, so two roots (two tenants) never share a namespace.
|
|
65
|
+
*/
|
|
66
|
+
export declare function createPayloadStore(root: string, scope?: string): PayloadStore;
|
|
67
|
+
//# sourceMappingURL=payload-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload-store.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp/payload-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoBH,gFAAgF;AAChF,eAAO,MAAM,cAAc,QAAsB,CAAC;AAElD,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,QAAoB,CAAC;AAE1D,sFAAsF;AACtF,eAAO,MAAM,sBAAsB,QAAQ,CAAC;AAI5C,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAWD,MAAM,MAAM,gBAAgB,GACxB,mBAAmB,GACnB,iBAAiB,GACjB,gBAAgB,GAChB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAExC,QAAQ,CAAC,MAAM,EAAE,sBAAsB;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM;gBADb,MAAM,EAAE,sBAAsB,EAC9B,KAAK,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAAC;IACjD,iEAAiE;IACjE,WAAW,CACT,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,gBAAgB,GAAG;QAAE,KAAK,EAAE,gBAAgB,CAAA;KAAE,CAAC;IAClD,iFAAiF;IACjF,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACpC;AAqBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAa,GAAG,YAAY,CAwLnF"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable recovery store for oversize MCP outputs (lot L1).
|
|
3
|
+
*
|
|
4
|
+
* When a legitimate response or notification exceeds the frame budget
|
|
5
|
+
* (`frame-budget.ts`), the INTACT original bytes are persisted here BEFORE the
|
|
6
|
+
* bounded error/notice is emitted, and the client recovers them in budget-sized
|
|
7
|
+
* chunks through the `h2a_read_payload` tool. The store never holds tool
|
|
8
|
+
* arguments or a private key — only output already destined for THIS client —
|
|
9
|
+
* and it never re-executes an effectful tool: it hands back stored bytes.
|
|
10
|
+
*
|
|
11
|
+
* Security properties (DEC-116 lineage):
|
|
12
|
+
* - Root/tenant confinement: one store per store-root; a ref is a 256-bit
|
|
13
|
+
* random capability whose file name IS the ref, so there is no free path, no
|
|
14
|
+
* `../`, and no cross-tenant lookup. Symlinks are refused on read.
|
|
15
|
+
* - Atomic publication: content is staged to a temp file (0600) and renamed.
|
|
16
|
+
* - Integrity: every payload carries a SHA-256 the reader verifies after
|
|
17
|
+
* reassembly.
|
|
18
|
+
* - Durability across restart: refs survive; nothing is purged at boot. Only a
|
|
19
|
+
* dedicated maintenance sweep removes EXPIRED refs, never live data.
|
|
20
|
+
*/
|
|
21
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
22
|
+
import { chmodSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
/** Announced recovery window: a ref is readable for 24h after it is written. */
|
|
25
|
+
export const PAYLOAD_TTL_MS = 24 * 60 * 60 * 1000;
|
|
26
|
+
/** Proposed per-root ceiling (measured against the store dir before a write). */
|
|
27
|
+
export const PAYLOAD_ROOT_QUOTA_BYTES = 128 * 1024 * 1024;
|
|
28
|
+
/** Hard maximum a single read may return (also bounded again by the frame budget). */
|
|
29
|
+
export const PAYLOAD_MAX_READ_BYTES = 65536;
|
|
30
|
+
const REF_RE = /^[0-9a-f]{64}$/;
|
|
31
|
+
export class PayloadStoreError extends Error {
|
|
32
|
+
reason;
|
|
33
|
+
cause;
|
|
34
|
+
constructor(reason, cause, message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.reason = reason;
|
|
37
|
+
this.cause = cause;
|
|
38
|
+
this.name = "PayloadStoreError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function binPath(dir, ref) {
|
|
42
|
+
return join(dir, `${ref}.bin`);
|
|
43
|
+
}
|
|
44
|
+
function metaPath(dir, ref) {
|
|
45
|
+
return join(dir, `${ref}.json`);
|
|
46
|
+
}
|
|
47
|
+
function dirBytes(dir) {
|
|
48
|
+
let total = 0;
|
|
49
|
+
for (const entry of readdirSync(dir)) {
|
|
50
|
+
try {
|
|
51
|
+
total += statSync(join(dir, entry)).size;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* a racing unlink is not a measurement failure */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return total;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A payload store confined to `<root>/mcp-payloads/`. `scope` defaults to the
|
|
61
|
+
* root, so two roots (two tenants) never share a namespace.
|
|
62
|
+
*/
|
|
63
|
+
export function createPayloadStore(root, scope = root) {
|
|
64
|
+
const dir = join(root, "mcp-payloads");
|
|
65
|
+
function ensureDir() {
|
|
66
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
67
|
+
try {
|
|
68
|
+
chmodSync(dir, 0o700);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* best effort; a pre-existing dir may be owned differently in tests */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function sweepExpired(now = Date.now()) {
|
|
75
|
+
if (!existsSync(dir))
|
|
76
|
+
return 0;
|
|
77
|
+
let removed = 0;
|
|
78
|
+
for (const entry of readdirSync(dir)) {
|
|
79
|
+
if (!entry.endsWith(".json"))
|
|
80
|
+
continue;
|
|
81
|
+
const ref = entry.slice(0, -".json".length);
|
|
82
|
+
if (!REF_RE.test(ref))
|
|
83
|
+
continue;
|
|
84
|
+
try {
|
|
85
|
+
const meta = JSON.parse(readFileSync(join(dir, entry), "utf8"));
|
|
86
|
+
if (Date.parse(meta.expiresAt) <= now) {
|
|
87
|
+
try {
|
|
88
|
+
unlinkSync(metaPath(dir, ref));
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* already gone */
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
unlinkSync(binPath(dir, ref));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* already gone */
|
|
98
|
+
}
|
|
99
|
+
removed += 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* a corrupt meta is left for a human sweep, never live source data */
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return removed;
|
|
107
|
+
}
|
|
108
|
+
function persistOutput(bytes) {
|
|
109
|
+
try {
|
|
110
|
+
ensureDir();
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
throw new PayloadStoreError("recovery_unavailable", err.code ?? "mkdir_failed", `cannot create payload store directory: ${err.message}`);
|
|
114
|
+
}
|
|
115
|
+
// Reclaim expired space before enforcing the quota; never evict live data.
|
|
116
|
+
let used = 0;
|
|
117
|
+
try {
|
|
118
|
+
sweepExpired();
|
|
119
|
+
used = dirBytes(dir);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
used = 0;
|
|
123
|
+
}
|
|
124
|
+
if (used + bytes.length > PAYLOAD_ROOT_QUOTA_BYTES) {
|
|
125
|
+
throw new PayloadStoreError("recovery_unavailable", "quota_exceeded", `payload store quota exceeded for ${dir}`);
|
|
126
|
+
}
|
|
127
|
+
const ref = randomBytes(32).toString("hex");
|
|
128
|
+
const sha256 = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
const meta = {
|
|
131
|
+
sha256,
|
|
132
|
+
totalBytes: bytes.length,
|
|
133
|
+
createdAt: new Date(now).toISOString(),
|
|
134
|
+
expiresAt: new Date(now + PAYLOAD_TTL_MS).toISOString(),
|
|
135
|
+
scope
|
|
136
|
+
};
|
|
137
|
+
const tmpBin = binPath(dir, `.${ref}.tmp`);
|
|
138
|
+
const tmpMeta = metaPath(dir, `.${ref}.tmp`);
|
|
139
|
+
try {
|
|
140
|
+
writeFileSync(tmpBin, bytes, { mode: 0o600, flag: "wx" });
|
|
141
|
+
writeFileSync(tmpMeta, `${JSON.stringify(meta)}\n`, { mode: 0o600, flag: "wx" });
|
|
142
|
+
renameSync(tmpBin, binPath(dir, ref));
|
|
143
|
+
renameSync(tmpMeta, metaPath(dir, ref));
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
for (const p of [tmpBin, tmpMeta, binPath(dir, ref), metaPath(dir, ref)]) {
|
|
147
|
+
try {
|
|
148
|
+
unlinkSync(p);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
/* best effort cleanup of a half-written record */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
throw new PayloadStoreError("recovery_unavailable", err.code ?? "write_failed", `cannot persist payload: ${err.message}`);
|
|
155
|
+
}
|
|
156
|
+
return { ref, sha256, totalBytes: bytes.length, expiresAt: meta.expiresAt };
|
|
157
|
+
}
|
|
158
|
+
function readMeta(ref) {
|
|
159
|
+
const mp = metaPath(dir, ref);
|
|
160
|
+
let st;
|
|
161
|
+
try {
|
|
162
|
+
st = lstatSync(mp);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
if (!st.isFile())
|
|
168
|
+
return undefined; // refuse a symlink/dir masquerading as a ref
|
|
169
|
+
try {
|
|
170
|
+
return JSON.parse(readFileSync(mp, "utf8"));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function readPayload(ref, offset, maxBytes) {
|
|
177
|
+
if (typeof ref !== "string" || !REF_RE.test(ref)) {
|
|
178
|
+
return { error: "payload_not_found" };
|
|
179
|
+
}
|
|
180
|
+
const meta = readMeta(ref);
|
|
181
|
+
if (!meta || meta.scope !== scope)
|
|
182
|
+
return { error: "payload_not_found" };
|
|
183
|
+
if (Date.parse(meta.expiresAt) <= Date.now())
|
|
184
|
+
return { error: "payload_expired" };
|
|
185
|
+
if (!Number.isInteger(offset) || offset < 0 || offset > meta.totalBytes) {
|
|
186
|
+
return { error: "invalid_offset" };
|
|
187
|
+
}
|
|
188
|
+
const bp = binPath(dir, ref);
|
|
189
|
+
let bst;
|
|
190
|
+
try {
|
|
191
|
+
bst = lstatSync(bp);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return { error: "payload_not_found" };
|
|
195
|
+
}
|
|
196
|
+
if (!bst.isFile() || bst.size !== meta.totalBytes)
|
|
197
|
+
return { error: "payload_corrupt" };
|
|
198
|
+
const cap = Math.max(1, Math.min(Number.isInteger(maxBytes) && maxBytes > 0 ? maxBytes : PAYLOAD_MAX_READ_BYTES, PAYLOAD_MAX_READ_BYTES));
|
|
199
|
+
const remaining = meta.totalBytes - offset;
|
|
200
|
+
const chunkLen = Math.min(cap, remaining);
|
|
201
|
+
const buf = Buffer.allocUnsafe(chunkLen);
|
|
202
|
+
let fd;
|
|
203
|
+
try {
|
|
204
|
+
fd = openSync(bp, "r");
|
|
205
|
+
let read = 0;
|
|
206
|
+
while (read < chunkLen) {
|
|
207
|
+
const n = readSync(fd, buf, read, chunkLen - read, offset + read);
|
|
208
|
+
if (n === 0)
|
|
209
|
+
break;
|
|
210
|
+
read += n;
|
|
211
|
+
}
|
|
212
|
+
if (read !== chunkLen)
|
|
213
|
+
return { error: "payload_corrupt" };
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return { error: "payload_corrupt" };
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
if (fd !== undefined) {
|
|
220
|
+
try {
|
|
221
|
+
closeSync(fd);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
/* fd closed on error already */
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const nextOffset = offset + chunkLen;
|
|
229
|
+
return {
|
|
230
|
+
ref,
|
|
231
|
+
encoding: "base64",
|
|
232
|
+
data: buf.toString("base64"),
|
|
233
|
+
offset,
|
|
234
|
+
nextOffset: nextOffset >= meta.totalBytes ? null : nextOffset,
|
|
235
|
+
totalBytes: meta.totalBytes,
|
|
236
|
+
sha256: meta.sha256,
|
|
237
|
+
expiresAt: meta.expiresAt
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return { scope, persistOutput, readPayload, sweepExpired };
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=payload-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload-store.js","sourceRoot":"","sources":["../../../src/runtime/mcp/payload-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EACL,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,UAAU,EACV,QAAQ,EACR,UAAU,EACV,aAAa,EACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAElD,iFAAiF;AACjF,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAE1D,sFAAsF;AACtF,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,CAAC;AAE5C,MAAM,MAAM,GAAG,gBAAgB,CAAC;AAoChC,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAE/B;IACA;IAFX,YACW,MAA8B,EAC9B,KAAa,EACtB,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAJN,WAAM,GAAN,MAAM,CAAwB;QAC9B,UAAK,GAAL,KAAK,CAAQ;QAItB,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAiBD,SAAS,OAAO,CAAC,GAAW,EAAE,GAAW;IACvC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,CAAC;AACjC,CAAC;AACD,SAAS,QAAQ,CAAC,GAAW,EAAE,GAAW;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,kDAAkD;QACpD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,QAAgB,IAAI;IACnE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAEvC,SAAS,SAAS;QAChB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC;YACH,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;QACzE,CAAC;IACH,CAAC;IAED,SAAS,YAAY,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;QAC5C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC;QAC/B,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,SAAS;YACvC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,SAAS;YAChC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,CAAgB,CAAC;gBAC/E,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,EAAE,CAAC;oBACtC,IAAI,CAAC;wBACH,UAAU,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;oBACjC,CAAC;oBAAC,MAAM,CAAC;wBACP,kBAAkB;oBACpB,CAAC;oBACD,IAAI,CAAC;wBACH,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;oBAChC,CAAC;oBAAC,MAAM,CAAC;wBACP,kBAAkB;oBACpB,CAAC;oBACD,OAAO,IAAI,CAAC,CAAC;gBACf,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,sEAAsE;YACxE,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,SAAS,aAAa,CAAC,KAAa;QAClC,IAAI,CAAC;YACH,SAAS,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,iBAAiB,CACzB,sBAAsB,EACrB,GAA6B,CAAC,IAAI,IAAI,cAAc,EACrD,0CAA2C,GAAa,CAAC,OAAO,EAAE,CACnE,CAAC;QACJ,CAAC;QACD,2EAA2E;QAC3E,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,CAAC;YACH,YAAY,EAAE,CAAC;YACf,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,CAAC,CAAC;QACX,CAAC;QACD,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,wBAAwB,EAAE,CAAC;YACnD,MAAM,IAAI,iBAAiB,CACzB,sBAAsB,EACtB,gBAAgB,EAChB,oCAAoC,GAAG,EAAE,CAC1C,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,IAAI,GAAgB;YACxB,MAAM;YACN,UAAU,EAAE,KAAK,CAAC,MAAM;YACxB,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE;YACtC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,cAAc,CAAC,CAAC,WAAW,EAAE;YACvD,KAAK;SACN,CAAC;QACF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;QAC7C,IAAI,CAAC;YACH,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D,aAAa,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACjF,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;YACtC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,KAAK,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;gBACzE,IAAI,CAAC;oBACH,UAAU,CAAC,CAAC,CAAC,CAAC;gBAChB,CAAC;gBAAC,MAAM,CAAC;oBACP,kDAAkD;gBACpD,CAAC;YACH,CAAC;YACD,MAAM,IAAI,iBAAiB,CACzB,sBAAsB,EACrB,GAA6B,CAAC,IAAI,IAAI,cAAc,EACrD,2BAA4B,GAAa,CAAC,OAAO,EAAE,CACpD,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;IAC9E,CAAC;IAED,SAAS,QAAQ,CAAC,GAAW;QAC3B,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC9B,IAAI,EAAE,CAAC;QACP,IAAI,CAAC;YACH,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE;YAAE,OAAO,SAAS,CAAC,CAAC,6CAA6C;QACjF,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,EAAE,MAAM,CAAC,CAAgB,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,SAAS,WAAW,CAClB,GAAW,EACX,MAAc,EACd,QAAgB;QAEhB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACjD,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;QACxC,CAAC;QACD,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK;YAAE,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;QACzE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE;YAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAClF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YACxE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QACrC,CAAC;QACD,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC7B,IAAI,GAAG,CAAC;QACR,IAAI,CAAC;YACH,GAAG,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;QACxC,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,UAAU;YAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAEvF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAClB,CAAC,EACD,IAAI,CAAC,GAAG,CACN,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,sBAAsB,EAC9E,sBAAsB,CACvB,CACF,CAAC;QACF,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,EAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,EAAE,GAAG,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YACvB,IAAI,IAAI,GAAG,CAAC,CAAC;YACb,OAAO,IAAI,GAAG,QAAQ,EAAE,CAAC;gBACvB,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;gBAClE,IAAI,CAAC,KAAK,CAAC;oBAAE,MAAM;gBACnB,IAAI,IAAI,CAAC,CAAC;YACZ,CAAC;YACD,IAAI,IAAI,KAAK,QAAQ;gBAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QACtC,CAAC;gBAAS,CAAC;YACT,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;gBACrB,IAAI,CAAC;oBACH,SAAS,CAAC,EAAE,CAAC,CAAC;gBAChB,CAAC;gBAAC,MAAM,CAAC;oBACP,gCAAgC;gBAClC,CAAC;YACH,CAAC;QACH,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;QACrC,OAAO;YACL,GAAG;YACH,QAAQ,EAAE,QAAQ;YAClB,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC5B,MAAM;YACN,UAAU,EAAE,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU;YAC7D,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-phase MCP boot/serve tracing (lot L0 — measurement & diagnosis only).
|
|
3
|
+
*
|
|
4
|
+
* Emits correlated, closed-field JSONL spans to an injected stderr writer so a
|
|
5
|
+
* host or an out-of-process measurement probe can reconstruct the whole
|
|
6
|
+
* `mcp-serve` lifecycle — process start, identity resolution, lock waits,
|
|
7
|
+
* registry reads, transport `initialize` / `tools/list` / first tool — WITHOUT
|
|
8
|
+
* pulling in the CLI or the heavy runtime. It has three properties the rest of
|
|
9
|
+
* the fix relies on:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Passive.** A measurement callback can never fail the MCP protocol:
|
|
12
|
+
* every emit is wrapped, a writer throw is swallowed, and the trace has no
|
|
13
|
+
* side effect on the JSON-RPC stream (it only ever writes to stderr).
|
|
14
|
+
* 2. **Confidential.** Fields are a fixed whitelist and every string is
|
|
15
|
+
* sanitized + length-capped, so no PEM, token, lock payload or binding
|
|
16
|
+
* content can ever reach an event — even if a caller passes it by mistake.
|
|
17
|
+
* 3. **Monotonic + correlated.** Every event carries `attemptId` (a locally
|
|
18
|
+
* minted UUID — a child process cannot inherit an identity from the MCP
|
|
19
|
+
* client), `pid`, a per-trace `seq`, and a monotonic `monotonicMs`. Two
|
|
20
|
+
* processes are correlated by matching events, never by subtracting their
|
|
21
|
+
* independent monotonic clocks.
|
|
22
|
+
*
|
|
23
|
+
* `H2A_MCP_TRACE=1` turns on the detailed spans (`begin`/`end` of internal
|
|
24
|
+
* phases). Lifecycle milestones and failure diagnostics stay visible without
|
|
25
|
+
* the flag.
|
|
26
|
+
*/
|
|
27
|
+
/** Log line prefix; every trace event is `${TRACE_LINE_PREFIX}${json}\n` on stderr. */
|
|
28
|
+
export declare const TRACE_LINE_PREFIX = "h2a.mcp.phase ";
|
|
29
|
+
/** Env flag that turns on detailed (`begin`/`end`) spans. */
|
|
30
|
+
export declare const TRACE_DETAIL_ENV = "H2A_MCP_TRACE";
|
|
31
|
+
export type McpTraceRole = "server" | "identity-child";
|
|
32
|
+
export type McpTraceEventKind = "begin" | "end" | "error" | "sample";
|
|
33
|
+
/** The wire event. `v:1` is the schema version; keep additions backward-safe. */
|
|
34
|
+
export interface McpTraceEvent {
|
|
35
|
+
readonly v: 1;
|
|
36
|
+
readonly attemptId: string;
|
|
37
|
+
readonly pid: number;
|
|
38
|
+
readonly role: McpTraceRole;
|
|
39
|
+
readonly seq: number;
|
|
40
|
+
readonly monotonicMs: number;
|
|
41
|
+
readonly phase: string;
|
|
42
|
+
readonly event: McpTraceEventKind;
|
|
43
|
+
readonly durationMs?: number;
|
|
44
|
+
readonly requestId?: string | number;
|
|
45
|
+
readonly method?: string;
|
|
46
|
+
readonly tool?: string;
|
|
47
|
+
readonly bytes?: number;
|
|
48
|
+
readonly waitMs?: number;
|
|
49
|
+
readonly holdMs?: number;
|
|
50
|
+
readonly code?: string | number;
|
|
51
|
+
}
|
|
52
|
+
/** The only fields a caller may attach. Everything else is dropped. */
|
|
53
|
+
export interface McpTraceFields {
|
|
54
|
+
readonly requestId?: string | number;
|
|
55
|
+
readonly method?: string;
|
|
56
|
+
readonly tool?: string;
|
|
57
|
+
readonly bytes?: number;
|
|
58
|
+
readonly waitMs?: number;
|
|
59
|
+
readonly holdMs?: number;
|
|
60
|
+
readonly code?: string | number;
|
|
61
|
+
readonly durationMs?: number;
|
|
62
|
+
}
|
|
63
|
+
export interface McpTraceOptions {
|
|
64
|
+
readonly role?: McpTraceRole;
|
|
65
|
+
/** Locally minted UUID; never taken from the MCP client. */
|
|
66
|
+
readonly attemptId?: string;
|
|
67
|
+
readonly pid?: number;
|
|
68
|
+
/** Injected sink; defaults to a swallow-on-throw `process.stderr` writer. */
|
|
69
|
+
readonly writer?: (line: string) => void;
|
|
70
|
+
/** Monotonic clock; defaults to `performance.now`. */
|
|
71
|
+
readonly now?: () => number;
|
|
72
|
+
/** Force detailed spans on/off; defaults to `env[H2A_MCP_TRACE] === "1"`. */
|
|
73
|
+
readonly detailed?: boolean;
|
|
74
|
+
readonly env?: Record<string, string | undefined>;
|
|
75
|
+
}
|
|
76
|
+
export interface McpTrace {
|
|
77
|
+
readonly attemptId: string;
|
|
78
|
+
readonly role: McpTraceRole;
|
|
79
|
+
readonly pid: number;
|
|
80
|
+
readonly detailed: boolean;
|
|
81
|
+
/** A lifecycle milestone (`sample`). Always emitted — this is a diagnostic. */
|
|
82
|
+
phase(name: string, fields?: McpTraceFields): void;
|
|
83
|
+
/** Start of a detailed span. Emitted only when `detailed`. */
|
|
84
|
+
begin(name: string, fields?: McpTraceFields): void;
|
|
85
|
+
/** End of a detailed span. Emitted only when `detailed`. */
|
|
86
|
+
end(name: string, fields?: McpTraceFields): void;
|
|
87
|
+
/** A failure. Always emitted, flag or not. */
|
|
88
|
+
error(name: string, fields?: McpTraceFields): void;
|
|
89
|
+
/** Wrap `fn`: `begin`/`end` when detailed, an `error` event on throw always. */
|
|
90
|
+
span<T>(name: string, fn: () => T, fields?: McpTraceFields): T;
|
|
91
|
+
/** A correlated sub-trace (same `attemptId`) for a child role. */
|
|
92
|
+
child(role: McpTraceRole, options?: Pick<McpTraceOptions, "pid" | "writer">): McpTrace;
|
|
93
|
+
}
|
|
94
|
+
/** Create a fresh trace. `attemptId` defaults to a locally minted UUID. */
|
|
95
|
+
export declare function createMcpTrace(options?: McpTraceOptions): McpTrace;
|
|
96
|
+
export declare function setActiveMcpTrace(trace: McpTrace | undefined): void;
|
|
97
|
+
export declare function getActiveMcpTrace(): McpTrace | undefined;
|
|
98
|
+
//# sourceMappingURL=phase-trace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase-trace.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp/phase-trace.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-phase MCP boot/serve tracing (lot L0 — measurement & diagnosis only).
|
|
3
|
+
*
|
|
4
|
+
* Emits correlated, closed-field JSONL spans to an injected stderr writer so a
|
|
5
|
+
* host or an out-of-process measurement probe can reconstruct the whole
|
|
6
|
+
* `mcp-serve` lifecycle — process start, identity resolution, lock waits,
|
|
7
|
+
* registry reads, transport `initialize` / `tools/list` / first tool — WITHOUT
|
|
8
|
+
* pulling in the CLI or the heavy runtime. It has three properties the rest of
|
|
9
|
+
* the fix relies on:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Passive.** A measurement callback can never fail the MCP protocol:
|
|
12
|
+
* every emit is wrapped, a writer throw is swallowed, and the trace has no
|
|
13
|
+
* side effect on the JSON-RPC stream (it only ever writes to stderr).
|
|
14
|
+
* 2. **Confidential.** Fields are a fixed whitelist and every string is
|
|
15
|
+
* sanitized + length-capped, so no PEM, token, lock payload or binding
|
|
16
|
+
* content can ever reach an event — even if a caller passes it by mistake.
|
|
17
|
+
* 3. **Monotonic + correlated.** Every event carries `attemptId` (a locally
|
|
18
|
+
* minted UUID — a child process cannot inherit an identity from the MCP
|
|
19
|
+
* client), `pid`, a per-trace `seq`, and a monotonic `monotonicMs`. Two
|
|
20
|
+
* processes are correlated by matching events, never by subtracting their
|
|
21
|
+
* independent monotonic clocks.
|
|
22
|
+
*
|
|
23
|
+
* `H2A_MCP_TRACE=1` turns on the detailed spans (`begin`/`end` of internal
|
|
24
|
+
* phases). Lifecycle milestones and failure diagnostics stay visible without
|
|
25
|
+
* the flag.
|
|
26
|
+
*/
|
|
27
|
+
import { performance } from "node:perf_hooks";
|
|
28
|
+
import { randomUUID } from "node:crypto";
|
|
29
|
+
/** Log line prefix; every trace event is `${TRACE_LINE_PREFIX}${json}\n` on stderr. */
|
|
30
|
+
export const TRACE_LINE_PREFIX = "h2a.mcp.phase ";
|
|
31
|
+
/** Env flag that turns on detailed (`begin`/`end`) spans. */
|
|
32
|
+
export const TRACE_DETAIL_ENV = "H2A_MCP_TRACE";
|
|
33
|
+
const MAX_STR = 200;
|
|
34
|
+
/** Strip control chars / newlines and cap length so a JSONL line stays one line. */
|
|
35
|
+
function safeStr(value) {
|
|
36
|
+
if (value === undefined || value === null)
|
|
37
|
+
return undefined;
|
|
38
|
+
const raw = typeof value === "string" ? value : String(value);
|
|
39
|
+
// eslint-disable-next-line no-control-regex
|
|
40
|
+
const cleaned = raw.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").trim();
|
|
41
|
+
if (cleaned.length === 0)
|
|
42
|
+
return undefined;
|
|
43
|
+
return cleaned.length > MAX_STR ? cleaned.slice(0, MAX_STR) : cleaned;
|
|
44
|
+
}
|
|
45
|
+
function safeNum(value) {
|
|
46
|
+
if (typeof value !== "number")
|
|
47
|
+
return undefined;
|
|
48
|
+
return Number.isFinite(value) ? value : undefined;
|
|
49
|
+
}
|
|
50
|
+
function safeId(value) {
|
|
51
|
+
if (typeof value === "number")
|
|
52
|
+
return Number.isFinite(value) ? value : undefined;
|
|
53
|
+
return safeStr(value);
|
|
54
|
+
}
|
|
55
|
+
/** Default writer: never throws into the caller (a broken stderr is not fatal). */
|
|
56
|
+
function defaultWriter(line) {
|
|
57
|
+
try {
|
|
58
|
+
process.stderr.write(line);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* a measurement sink must never break the protocol */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
class Trace {
|
|
65
|
+
attemptId;
|
|
66
|
+
role;
|
|
67
|
+
pid;
|
|
68
|
+
detailed;
|
|
69
|
+
writer;
|
|
70
|
+
now;
|
|
71
|
+
env;
|
|
72
|
+
seq = 0;
|
|
73
|
+
constructor(options) {
|
|
74
|
+
this.env = options.env ?? process.env;
|
|
75
|
+
this.role = options.role ?? "server";
|
|
76
|
+
this.attemptId = options.attemptId ?? randomUUID();
|
|
77
|
+
this.pid = options.pid ?? process.pid;
|
|
78
|
+
this.writer = options.writer ?? defaultWriter;
|
|
79
|
+
this.now = options.now ?? (() => performance.now());
|
|
80
|
+
this.detailed =
|
|
81
|
+
options.detailed ?? this.env[TRACE_DETAIL_ENV] === "1";
|
|
82
|
+
}
|
|
83
|
+
emit(phase, kind, fields) {
|
|
84
|
+
try {
|
|
85
|
+
const event = {
|
|
86
|
+
v: 1,
|
|
87
|
+
attemptId: this.attemptId,
|
|
88
|
+
pid: this.pid,
|
|
89
|
+
role: this.role,
|
|
90
|
+
seq: this.seq++,
|
|
91
|
+
monotonicMs: Math.round(this.now() * 1000) / 1000,
|
|
92
|
+
phase: safeStr(phase) ?? "unknown",
|
|
93
|
+
event: kind,
|
|
94
|
+
...withField("durationMs", safeNum(fields?.durationMs)),
|
|
95
|
+
...withField("requestId", safeId(fields?.requestId)),
|
|
96
|
+
...withField("method", safeStr(fields?.method)),
|
|
97
|
+
...withField("tool", safeStr(fields?.tool)),
|
|
98
|
+
...withField("bytes", safeNum(fields?.bytes)),
|
|
99
|
+
...withField("waitMs", safeNum(fields?.waitMs)),
|
|
100
|
+
...withField("holdMs", safeNum(fields?.holdMs)),
|
|
101
|
+
...withField("code", safeId(fields?.code))
|
|
102
|
+
};
|
|
103
|
+
this.writer(`${TRACE_LINE_PREFIX}${JSON.stringify(event)}\n`);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
/* the trace is best-effort; it never disturbs the caller */
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
phase(name, fields) {
|
|
110
|
+
this.emit(name, "sample", fields);
|
|
111
|
+
}
|
|
112
|
+
begin(name, fields) {
|
|
113
|
+
if (this.detailed)
|
|
114
|
+
this.emit(name, "begin", fields);
|
|
115
|
+
}
|
|
116
|
+
end(name, fields) {
|
|
117
|
+
if (this.detailed)
|
|
118
|
+
this.emit(name, "end", fields);
|
|
119
|
+
}
|
|
120
|
+
error(name, fields) {
|
|
121
|
+
this.emit(name, "error", fields);
|
|
122
|
+
}
|
|
123
|
+
span(name, fn, fields) {
|
|
124
|
+
const start = this.now();
|
|
125
|
+
if (this.detailed)
|
|
126
|
+
this.emit(name, "begin", fields);
|
|
127
|
+
try {
|
|
128
|
+
const out = fn();
|
|
129
|
+
if (this.detailed) {
|
|
130
|
+
this.emit(name, "end", { ...fields, durationMs: round3(this.now() - start) });
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
this.emit(name, "error", {
|
|
136
|
+
...fields,
|
|
137
|
+
durationMs: round3(this.now() - start),
|
|
138
|
+
code: err instanceof Error ? err.name : "error"
|
|
139
|
+
});
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
child(role, options) {
|
|
144
|
+
return new Trace({
|
|
145
|
+
role,
|
|
146
|
+
attemptId: this.attemptId,
|
|
147
|
+
detailed: this.detailed,
|
|
148
|
+
now: this.now,
|
|
149
|
+
env: this.env,
|
|
150
|
+
...(options?.pid !== undefined ? { pid: options.pid } : {}),
|
|
151
|
+
...(options?.writer !== undefined ? { writer: options.writer } : {})
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function withField(key, value) {
|
|
156
|
+
return value === undefined ? {} : { [key]: value };
|
|
157
|
+
}
|
|
158
|
+
function round3(ms) {
|
|
159
|
+
return Math.round(ms * 1000) / 1000;
|
|
160
|
+
}
|
|
161
|
+
/** Create a fresh trace. `attemptId` defaults to a locally minted UUID. */
|
|
162
|
+
export function createMcpTrace(options = {}) {
|
|
163
|
+
return new Trace(options);
|
|
164
|
+
}
|
|
165
|
+
// --- Ambient, process-wide active trace ------------------------------------
|
|
166
|
+
//
|
|
167
|
+
// The boot path threads through many modules (bin → cli → identity → store →
|
|
168
|
+
// stdio). Rather than plumb a trace object through every signature, the entry
|
|
169
|
+
// point installs ONE active trace and the deep modules reach it via
|
|
170
|
+
// `getActiveMcpTrace()`. When none is installed (a normal CLI verb, or a unit
|
|
171
|
+
// test), every deep call is a no-op via optional chaining.
|
|
172
|
+
let active;
|
|
173
|
+
export function setActiveMcpTrace(trace) {
|
|
174
|
+
active = trace;
|
|
175
|
+
}
|
|
176
|
+
export function getActiveMcpTrace() {
|
|
177
|
+
return active;
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=phase-trace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase-trace.js","sourceRoot":"","sources":["../../../src/runtime/mcp/phase-trace.ts"],"names":[],"mappings":"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"}
|