@frihet/mcp-server 1.14.3 → 1.14.5

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.
Files changed (33) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/intelligence-duplicate-invoice.test.d.ts +14 -0
  3. package/dist/__tests__/intelligence-duplicate-invoice.test.d.ts.map +1 -0
  4. package/dist/__tests__/intelligence-duplicate-invoice.test.js +108 -0
  5. package/dist/__tests__/intelligence-duplicate-invoice.test.js.map +1 -0
  6. package/dist/__tests__/observability-redaction.test.d.ts +13 -0
  7. package/dist/__tests__/observability-redaction.test.d.ts.map +1 -0
  8. package/dist/__tests__/observability-redaction.test.js +94 -0
  9. package/dist/__tests__/observability-redaction.test.js.map +1 -0
  10. package/dist/__tests__/openai-profile.test.js +33 -0
  11. package/dist/__tests__/openai-profile.test.js.map +1 -1
  12. package/dist/observability.d.ts +60 -0
  13. package/dist/observability.d.ts.map +1 -1
  14. package/dist/observability.js +94 -69
  15. package/dist/observability.js.map +1 -1
  16. package/dist/openai-profile.d.ts.map +1 -1
  17. package/dist/openai-profile.js +76 -40
  18. package/dist/openai-profile.js.map +1 -1
  19. package/dist/redaction.d.ts +39 -0
  20. package/dist/redaction.d.ts.map +1 -0
  21. package/dist/redaction.js +91 -0
  22. package/dist/redaction.js.map +1 -0
  23. package/dist/tools/intelligence.d.ts.map +1 -1
  24. package/dist/tools/intelligence.js +36 -9
  25. package/dist/tools/intelligence.js.map +1 -1
  26. package/dist/tools/invoices.d.ts.map +1 -1
  27. package/dist/tools/invoices.js +73 -2
  28. package/dist/tools/invoices.js.map +1 -1
  29. package/dist/tools/quotes.d.ts.map +1 -1
  30. package/dist/tools/quotes.js +48 -0
  31. package/dist/tools/quotes.js.map +1 -1
  32. package/package.json +2 -2
  33. package/scripts/audit-mcp-refs.mjs +1 -0
package/README.md CHANGED
@@ -39,7 +39,7 @@
39
39
  | **ChatGPT Apps** | Coming soon | [chatgpt.com](https://chatgpt.com) |
40
40
  | **Anthropic Claude Directory** | Coming soon | [claude.ai/settings/connectors](https://claude.ai/settings/connectors) |
41
41
 
42
- > **Tool count:** npm `latest` (1.14.3) ships all 157 tools, same as the remote endpoint (`mcp.frihet.io`).
42
+ > **Tool count:** npm `latest` (1.14.5) ships all 157 tools, same as the remote endpoint (`mcp.frihet.io`).
43
43
 
44
44
  ---
45
45
 
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Regression test for duplicate_invoice (intelligence.ts).
3
+ *
4
+ * Bug: duplicate_invoice fetched the FULL raw stored invoice (getInvoice returns
5
+ * the entire Firestore doc) and spread it into createInvoice, only blacklisting
6
+ * 6 fields. The create endpoint validates against a Zod `.strict()` schema that
7
+ * REJECTS unknown keys → HTTP 400, so any paid/sent/cancelled/e-invoiced invoice
8
+ * failed to duplicate. Fix: allowlist-PICK only the writable create fields.
9
+ *
10
+ * This test asserts the body sent to createInvoice contains ONLY allowlisted
11
+ * fields (no payments/verifactu/operationType/documentNumber/total/…).
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=intelligence-duplicate-invoice.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intelligence-duplicate-invoice.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/intelligence-duplicate-invoice.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Regression test for duplicate_invoice (intelligence.ts).
3
+ *
4
+ * Bug: duplicate_invoice fetched the FULL raw stored invoice (getInvoice returns
5
+ * the entire Firestore doc) and spread it into createInvoice, only blacklisting
6
+ * 6 fields. The create endpoint validates against a Zod `.strict()` schema that
7
+ * REJECTS unknown keys → HTTP 400, so any paid/sent/cancelled/e-invoiced invoice
8
+ * failed to duplicate. Fix: allowlist-PICK only the writable create fields.
9
+ *
10
+ * This test asserts the body sent to createInvoice contains ONLY allowlisted
11
+ * fields (no payments/verifactu/operationType/documentNumber/total/…).
12
+ */
13
+ import { test, describe } from "node:test";
14
+ import assert from "node:assert/strict";
15
+ class StubMcpServer {
16
+ tools = new Map();
17
+ registerTool(name, config, handler) {
18
+ this.tools.set(name, { name, config, handler });
19
+ }
20
+ }
21
+ // A realistic FULL stored invoice doc as getInvoice returns it: writable fields
22
+ // PLUS many stored-only fields the strict create schema rejects.
23
+ const STORED_INVOICE = {
24
+ id: "inv_123",
25
+ documentNumber: "2026-0042",
26
+ status: "paid",
27
+ total: 1210,
28
+ createdAt: "2026-05-01T10:00:00Z",
29
+ updatedAt: "2026-05-02T10:00:00Z",
30
+ // ── writable (must be copied) ──
31
+ clientName: "Acme Corp",
32
+ clientId: "cli_9",
33
+ clientAddress: "Gran Via 1, Madrid",
34
+ clientTaxId: "B12345678",
35
+ items: [{ description: "Consulting", quantity: 10, unitPrice: 100 }],
36
+ dueDate: "2026-06-01",
37
+ notes: "Thanks",
38
+ taxRate: 21,
39
+ irpfRate: 15,
40
+ equivalenceSurchargeRate: 0,
41
+ clientLocation: "peninsula",
42
+ prepayment: 0,
43
+ seriesId: "A",
44
+ // ── stored-only (must NOT be copied — would 400 the strict create schema) ──
45
+ payments: [{ amount: 1210, date: "2026-05-10" }],
46
+ amountPaid: 1210,
47
+ verifactu: { hash: "abc", chained: true },
48
+ eInvoice: { format: "facturae" },
49
+ operationType: "S1",
50
+ poNumber: "PO-77",
51
+ discountRate: 5,
52
+ createdBy: "user_x",
53
+ sentTo: "billing@acme.com",
54
+ sentAt: "2026-05-02T10:00:00Z",
55
+ cancelledAt: null,
56
+ attachments: ["a.pdf"],
57
+ };
58
+ const REJECTED_FIELDS = [
59
+ "payments", "amountPaid", "verifactu", "eInvoice", "operationType",
60
+ "poNumber", "discountRate", "createdBy", "sentTo", "sentAt",
61
+ "cancelledAt", "attachments", "documentNumber", "total", "createdAt", "updatedAt", "id",
62
+ ];
63
+ const COPYABLE_FIELDS = [
64
+ "clientName", "clientId", "clientAddress", "clientTaxId", "items",
65
+ "notes", "taxRate", "irpfRate", "equivalenceSurchargeRate",
66
+ "clientLocation", "prepayment", "seriesId",
67
+ ];
68
+ async function makeServerCapturing(captured) {
69
+ const server = new StubMcpServer();
70
+ const client = {
71
+ getInvoice: async (_id) => ({ ...STORED_INVOICE }),
72
+ createInvoice: async (data) => {
73
+ captured.body = data;
74
+ return { id: "inv_dup", status: "draft", ...data };
75
+ },
76
+ };
77
+ const { registerIntelligenceTools } = await import("../tools/intelligence.js");
78
+ registerIntelligenceTools(server, client);
79
+ return server;
80
+ }
81
+ describe("duplicate_invoice — allowlist pick", () => {
82
+ test("sends ONLY writable fields to createInvoice (no strict-schema rejects)", async () => {
83
+ const captured = {};
84
+ const server = await makeServerCapturing(captured);
85
+ const tool = server.tools.get("duplicate_invoice");
86
+ assert.ok(tool, "duplicate_invoice should be registered");
87
+ const result = await tool.handler({ id: "inv_123" });
88
+ assert.notEqual(result.isError, true, "duplicate should not error");
89
+ assert.ok(captured.body, "createInvoice should have been called");
90
+ for (const f of REJECTED_FIELDS) {
91
+ assert.equal(f in captured.body, false, `create body must NOT carry stored-only field "${f}"`);
92
+ }
93
+ for (const f of COPYABLE_FIELDS) {
94
+ assert.equal(f in captured.body, true, `create body must carry writable field "${f}"`);
95
+ }
96
+ assert.equal(captured.body.status, "draft", "status forced to draft");
97
+ assert.equal(typeof captured.body.issueDate, "string", "issueDate set");
98
+ });
99
+ test("honors newIssueDate + newDueDate overrides", async () => {
100
+ const captured = {};
101
+ const server = await makeServerCapturing(captured);
102
+ const tool = server.tools.get("duplicate_invoice");
103
+ await tool.handler({ id: "inv_123", newIssueDate: "2026-07-01", newDueDate: "2026-08-01" });
104
+ assert.equal(captured.body.issueDate, "2026-07-01");
105
+ assert.equal(captured.body.dueDate, "2026-08-01");
106
+ });
107
+ });
108
+ //# sourceMappingURL=intelligence-duplicate-invoice.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intelligence-duplicate-invoice.test.js","sourceRoot":"","sources":["../../src/__tests__/intelligence-duplicate-invoice.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Trust test — no government ID, credential, or banking identifier may ever
3
+ * reach the external Langfuse observability service.
4
+ *
5
+ * Regression guard for the P0 finding (Codex audit 2026-06-22): the Langfuse
6
+ * tracer captured the RAW tool output BEFORE the OpenAI profile redaction
7
+ * wrapper ran, so taxId/secret/IBAN leaked into traces in every profile mode.
8
+ * The fix moved redaction INTO buildTracePayload, so the trace copy is redacted
9
+ * unconditionally. This test fails if any sensitive value survives into the
10
+ * serialized ingestion batch.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=observability-redaction.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observability-redaction.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/observability-redaction.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Trust test — no government ID, credential, or banking identifier may ever
3
+ * reach the external Langfuse observability service.
4
+ *
5
+ * Regression guard for the P0 finding (Codex audit 2026-06-22): the Langfuse
6
+ * tracer captured the RAW tool output BEFORE the OpenAI profile redaction
7
+ * wrapper ran, so taxId/secret/IBAN leaked into traces in every profile mode.
8
+ * The fix moved redaction INTO buildTracePayload, so the trace copy is redacted
9
+ * unconditionally. This test fails if any sensitive value survives into the
10
+ * serialized ingestion batch.
11
+ */
12
+ import { describe, test } from "node:test";
13
+ import assert from "node:assert/strict";
14
+ import { buildTracePayload } from "../observability.js";
15
+ import { redactClone, SENSITIVE_FIELD_NAMES } from "../redaction.js";
16
+ const SECRETS = {
17
+ taxId: "B12345678",
18
+ nif: "12345678Z",
19
+ secret: "whsec_live_DEADBEEFCAFE",
20
+ iban: "ES9121000418450200051332",
21
+ passport: "PAX9988776",
22
+ apiKey: "fri_live_TOPSECRET",
23
+ accessToken: "ya29.A0ARrdaM-TOKEN",
24
+ password: "hunter2",
25
+ };
26
+ const SECRET_VALUES = Object.values(SECRETS);
27
+ /** A realistic MCP tool result: structuredContent + a serialized JSON text block. */
28
+ function mcpResultWithSecrets() {
29
+ return {
30
+ content: [{ type: "text", text: JSON.stringify({ id: "cli_1", ...SECRETS }) }],
31
+ structuredContent: {
32
+ client: { id: "cli_1", name: "Acme", ...SECRETS },
33
+ webhooks: [{ id: "wh_1", url: "https://x.test", secret: SECRETS.secret }],
34
+ },
35
+ };
36
+ }
37
+ function assertNoSecrets(serialized) {
38
+ for (const v of SECRET_VALUES) {
39
+ assert.ok(!serialized.includes(v), `sensitive value leaked into trace payload: ${v}`);
40
+ }
41
+ }
42
+ describe("observability redaction", () => {
43
+ test("buildTracePayload strips PII/credentials from output (success path)", () => {
44
+ const now = new Date("2026-06-22T10:00:00.000Z");
45
+ const batch = buildTracePayload({
46
+ toolName: "get_client",
47
+ input: { id: "cli_1", taxId: SECRETS.taxId },
48
+ output: mcpResultWithSecrets(),
49
+ isError: false,
50
+ startTime: now,
51
+ endTime: now,
52
+ traceId: "trace_1",
53
+ spanId: "span_1",
54
+ stub: null,
55
+ });
56
+ assertNoSecrets(JSON.stringify(batch));
57
+ });
58
+ test("buildTracePayload strips PII from input args even on the error path", () => {
59
+ const now = new Date("2026-06-22T10:00:00.000Z");
60
+ const batch = buildTracePayload({
61
+ toolName: "create_client",
62
+ input: { name: "Acme", ...SECRETS },
63
+ output: undefined,
64
+ isError: true,
65
+ // an error message that echoes the request body must not leak either
66
+ errorMessage: `Backend rejected payload: {"taxId":"${SECRETS.taxId}","secret":"${SECRETS.secret}"}`,
67
+ startTime: now,
68
+ endTime: now,
69
+ traceId: "trace_2",
70
+ spanId: "span_2",
71
+ stub: null,
72
+ });
73
+ assertNoSecrets(JSON.stringify(batch));
74
+ });
75
+ test("redactClone never mutates its argument", () => {
76
+ const original = mcpResultWithSecrets();
77
+ const before = JSON.stringify(original);
78
+ redactClone(original);
79
+ assert.equal(JSON.stringify(original), before, "redactClone must not mutate input");
80
+ });
81
+ test("redactClone preserves non-sensitive fields", () => {
82
+ const cloned = redactClone({ id: "cli_1", name: "Acme", total: 100, taxId: SECRETS.taxId });
83
+ assert.equal(cloned.id, "cli_1");
84
+ assert.equal(cloned.name, "Acme");
85
+ assert.equal(cloned.total, 100);
86
+ assert.notEqual(cloned.taxId, SECRETS.taxId);
87
+ });
88
+ test("the sensitive field set covers the critical Trust fields", () => {
89
+ for (const f of ["taxId", "secret", "iban", "apiKey", "accessToken", "password"]) {
90
+ assert.ok(SENSITIVE_FIELD_NAMES.includes(f), `missing critical sensitive field: ${f}`);
91
+ }
92
+ });
93
+ });
94
+ //# sourceMappingURL=observability-redaction.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observability-redaction.test.js","sourceRoot":"","sources":["../../src/__tests__/observability-redaction.test.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,7 @@
9
9
  import { describe, test } from "node:test";
10
10
  import assert from "node:assert/strict";
11
11
  import { applyOpenAIProfile } from "../openai-profile.js";
12
+ import { SENSITIVE_FIELD_NAMES } from "../redaction.js";
12
13
  import { registerAllTools } from "../tools/register-all.js";
13
14
  import { registerAllPrompts } from "../prompts/register-all.js";
14
15
  class StubMcpServer {
@@ -128,5 +129,37 @@ describe("OpenAI profile", () => {
128
129
  assert.equal(serialized.includes("secret"), false);
129
130
  assert.equal(serialized.includes("should-not-leak"), false);
130
131
  });
132
+ test("no reviewed tool DECLARES a sensitive field in its outputSchema", () => {
133
+ const server = makeOpenAIServer();
134
+ // Recursively collect every object key declared by a Zod output schema.
135
+ const collectKeys = (schema, acc) => {
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
+ const def = schema?._def;
138
+ const typeName = def?.typeName;
139
+ if (typeName === "ZodObject") {
140
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
+ for (const [key, value] of Object.entries(schema.shape)) {
142
+ acc.add(key);
143
+ collectKeys(value, acc);
144
+ }
145
+ }
146
+ else if (typeName === "ZodArray") {
147
+ collectKeys(def.type, acc);
148
+ }
149
+ else if (typeName === "ZodOptional" || typeName === "ZodNullable") {
150
+ collectKeys(def.innerType, acc);
151
+ }
152
+ return acc;
153
+ };
154
+ for (const tool of server.tools.values()) {
155
+ const out = tool.config.outputSchema;
156
+ if (!out)
157
+ continue;
158
+ const keys = collectKeys(out, new Set());
159
+ for (const field of SENSITIVE_FIELD_NAMES) {
160
+ assert.equal(keys.has(field), false, `${tool.name} outputSchema must not declare sensitive field "${field}"`);
161
+ }
162
+ }
163
+ });
131
164
  });
132
165
  //# sourceMappingURL=openai-profile.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"openai-profile.test.js","sourceRoot":"","sources":["../../src/__tests__/openai-profile.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openai-profile.test.js","sourceRoot":"","sources":["../../src/__tests__/openai-profile.test.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,36 @@ export declare function initLangfuse(config: {
27
27
  secretKey?: string;
28
28
  baseUrl?: string;
29
29
  }): void;
30
+ interface LangfuseSpanBody {
31
+ id: string;
32
+ traceId: string;
33
+ name: string;
34
+ startTime: string;
35
+ endTime: string;
36
+ input?: unknown;
37
+ output?: unknown;
38
+ metadata?: Record<string, unknown>;
39
+ level?: "DEFAULT" | "DEBUG" | "WARNING" | "ERROR";
40
+ statusMessage?: string;
41
+ }
42
+ interface LangfuseTraceBody {
43
+ id: string;
44
+ name: string;
45
+ timestamp: string;
46
+ input?: unknown;
47
+ output?: unknown;
48
+ metadata?: Record<string, unknown>;
49
+ tags?: string[];
50
+ userId?: string;
51
+ }
52
+ interface IngestionBatch {
53
+ batch: Array<{
54
+ type: string;
55
+ id: string;
56
+ timestamp: string;
57
+ body: LangfuseTraceBody | LangfuseSpanBody;
58
+ }>;
59
+ }
30
60
  interface StubMarker {
31
61
  plannedEndpoint?: string;
32
62
  note?: string;
@@ -61,6 +91,36 @@ interface TraceContext {
61
91
  * Call once from server init; applies to all subsequent traces.
62
92
  */
63
93
  export declare function setTraceContext(ctx: TraceContext): void;
94
+ interface TracePayloadParams {
95
+ toolName: string;
96
+ /** Raw tool input args (redacted before serialization). */
97
+ input: unknown;
98
+ /** Raw tool output (redacted before serialization); ignored when isError. */
99
+ output: unknown;
100
+ isError: boolean;
101
+ errorMessage?: string;
102
+ startTime: Date;
103
+ endTime: Date;
104
+ traceId: string;
105
+ spanId: string;
106
+ /** Already-hashed user id, or undefined. */
107
+ userIdHashed?: string;
108
+ clientName?: string;
109
+ mcpVersion?: string;
110
+ /** Fabricated-stub marker, or null on a genuine result. */
111
+ stub: StubMarker | null;
112
+ }
113
+ /**
114
+ * Builds the Langfuse trace+span ingestion batch for a single tool call.
115
+ *
116
+ * CRITICAL (Trust): `input` and `output` are passed through {@link redactClone}
117
+ * before they enter the payload, so government IDs (taxId/NIF/CIF), banking
118
+ * identifiers (IBAN), webhook signing secrets, and auth tokens NEVER reach the
119
+ * external Langfuse service — in ANY profile mode. The tool-call output handed
120
+ * to the user is redacted later (OpenAI mode only); this redacts the trace copy
121
+ * unconditionally. Exported so a test can assert no sensitive value survives.
122
+ */
123
+ export declare function buildTracePayload(p: TracePayloadParams): IngestionBatch;
64
124
  /**
65
125
  * Wraps a tool handler fn and sends a Langfuse trace+span for the call.
66
126
  *
@@ -1 +1 @@
1
- {"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAkCH;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAQP;AAgED,UAAU,UAAU;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CA0BpE;AAkCD,UAAU,YAAY;IACpB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAEvD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAuIZ"}
1
+ {"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,7 @@
18
18
  *
19
19
  * Docs: https://langfuse.com/docs/api/reference/overview
20
20
  */
21
+ import { redactClone, redactText } from "./redaction.js";
21
22
  function getConfig() {
22
23
  let publicKey;
23
24
  let secretKey;
@@ -147,6 +148,83 @@ let _sessionContext = {};
147
148
  export function setTraceContext(ctx) {
148
149
  _sessionContext = { ..._sessionContext, ...ctx };
149
150
  }
151
+ /**
152
+ * Builds the Langfuse trace+span ingestion batch for a single tool call.
153
+ *
154
+ * CRITICAL (Trust): `input` and `output` are passed through {@link redactClone}
155
+ * before they enter the payload, so government IDs (taxId/NIF/CIF), banking
156
+ * identifiers (IBAN), webhook signing secrets, and auth tokens NEVER reach the
157
+ * external Langfuse service — in ANY profile mode. The tool-call output handed
158
+ * to the user is redacted later (OpenAI mode only); this redacts the trace copy
159
+ * unconditionally. Exported so a test can assert no sensitive value survives.
160
+ */
161
+ export function buildTracePayload(p) {
162
+ const { toolName, input, output, isError, errorMessage, startTime, endTime, traceId, spanId, userIdHashed, clientName, mcpVersion, stub, } = p;
163
+ const stubbed = stub !== null;
164
+ // success: false whenever the call threw OR returned a fabricated stub body.
165
+ const success = !isError && !stubbed;
166
+ const stubNote = stub?.note ??
167
+ (stub?.plannedEndpoint
168
+ ? `Backend endpoint not yet available: ${stub.plannedEndpoint}`
169
+ : undefined);
170
+ // ── Redact BEFORE the payload is built (never mutates the live response) ──
171
+ const safeArgs = redactClone(input);
172
+ const safeError = errorMessage ? redactText(errorMessage) : errorMessage;
173
+ const safeOutput = isError ? { error: safeError } : redactClone(output);
174
+ const traceBody = {
175
+ id: traceId,
176
+ name: "mcp_request",
177
+ timestamp: startTime.toISOString(),
178
+ input: { tool: toolName, args: safeArgs },
179
+ output: safeOutput,
180
+ metadata: {
181
+ tool: toolName,
182
+ clientName,
183
+ mcpVersion,
184
+ // FALSE on a thrown error AND on a fabricated-stub fallback.
185
+ success,
186
+ ...(stubbed
187
+ ? {
188
+ stub: true,
189
+ ...(stub?.plannedEndpoint ? { plannedEndpoint: stub.plannedEndpoint } : {}),
190
+ ...(stubNote ? { note: stubNote } : {}),
191
+ }
192
+ : {}),
193
+ },
194
+ tags: [`mcp.tool.${toolName}`, ...(stubbed ? ["mcp.stub"] : [])],
195
+ ...(userIdHashed ? { userId: userIdHashed } : {}),
196
+ };
197
+ const spanBody = {
198
+ id: spanId,
199
+ traceId,
200
+ name: `tool.${toolName}`,
201
+ startTime: startTime.toISOString(),
202
+ endTime: endTime.toISOString(),
203
+ input: safeArgs,
204
+ output: safeOutput,
205
+ metadata: {
206
+ durationMs: endTime.getTime() - startTime.getTime(),
207
+ clientName,
208
+ mcpVersion,
209
+ ...(stubbed ? { stub: true } : {}),
210
+ },
211
+ // ERROR on a thrown error; WARNING on a fabricated-stub fallback (the call
212
+ // "succeeded" mechanically but returned no real backend data); DEFAULT only
213
+ // on a genuine success.
214
+ level: isError ? "ERROR" : stubbed ? "WARNING" : "DEFAULT",
215
+ ...(isError && safeError
216
+ ? { statusMessage: safeError }
217
+ : stubbed && stubNote
218
+ ? { statusMessage: stubNote }
219
+ : {}),
220
+ };
221
+ return {
222
+ batch: [
223
+ { type: "trace-create", id: newId(), timestamp: startTime.toISOString(), body: traceBody },
224
+ { type: "span-create", id: newId(), timestamp: startTime.toISOString(), body: spanBody },
225
+ ],
226
+ };
227
+ }
150
228
  /**
151
229
  * Wraps a tool handler fn and sends a Langfuse trace+span for the call.
152
230
  *
@@ -193,82 +271,29 @@ export async function traceMCPTool(toolName, input, fn) {
193
271
  // success. This is the central fix: it covers every stub branch regardless of
194
272
  // which tool produced it, because the stub path never reaches the catch above.
195
273
  const stub = isError ? null : inspectStubMarker(output);
196
- const stubbed = stub !== null;
197
- // success: false whenever the call threw OR returned a fabricated stub body.
198
- const success = !isError && !stubbed;
199
- const stubNote = stub?.note ??
200
- (stub?.plannedEndpoint
201
- ? `Backend endpoint not yet available: ${stub.plannedEndpoint}`
202
- : undefined);
203
274
  // Fire-and-forget — build and send async, never awaited
204
275
  void (async () => {
205
276
  try {
206
277
  // Hash PII fields
207
278
  const userIdRaw = _sessionContext.userId;
208
279
  const userIdHashed = userIdRaw ? await hashPii(userIdRaw) : undefined;
209
- const traceBody = {
210
- id: traceId,
211
- name: "mcp_request",
212
- timestamp: startTime.toISOString(),
213
- input: { tool: toolName, args: input },
214
- output: isError ? { error: errorMessage } : output,
215
- metadata: {
216
- tool: toolName,
217
- clientName: _sessionContext.clientName,
218
- mcpVersion: _sessionContext.mcpVersion,
219
- // FALSE on a thrown error AND on a fabricated-stub fallback.
220
- success,
221
- ...(stubbed
222
- ? {
223
- stub: true,
224
- ...(stub?.plannedEndpoint ? { plannedEndpoint: stub.plannedEndpoint } : {}),
225
- ...(stubNote ? { note: stubNote } : {}),
226
- }
227
- : {}),
228
- },
229
- tags: [`mcp.tool.${toolName}`, ...(stubbed ? ["mcp.stub"] : [])],
230
- ...(userIdHashed ? { userId: userIdHashed } : {}),
231
- };
232
- const spanBody = {
233
- id: spanId,
234
- traceId,
235
- name: `tool.${toolName}`,
236
- startTime: startTime.toISOString(),
237
- endTime: endTime.toISOString(),
280
+ // buildTracePayload redacts input/output (taxId/secret/IBAN/tokens) so
281
+ // the external Langfuse service never stores cleartext PII or credentials.
282
+ const batch = buildTracePayload({
283
+ toolName,
238
284
  input,
239
- output: isError ? { error: errorMessage } : output,
240
- metadata: {
241
- durationMs: endTime.getTime() - startTime.getTime(),
242
- clientName: _sessionContext.clientName,
243
- mcpVersion: _sessionContext.mcpVersion,
244
- ...(stubbed ? { stub: true } : {}),
245
- },
246
- // ERROR on a thrown error; WARNING on a fabricated-stub fallback (the
247
- // call "succeeded" mechanically but returned no real backend data);
248
- // DEFAULT only on a genuine success.
249
- level: isError ? "ERROR" : stubbed ? "WARNING" : "DEFAULT",
250
- ...(isError && errorMessage
251
- ? { statusMessage: errorMessage }
252
- : stubbed && stubNote
253
- ? { statusMessage: stubNote }
254
- : {}),
255
- };
256
- const batch = {
257
- batch: [
258
- {
259
- type: "trace-create",
260
- id: newId(),
261
- timestamp: startTime.toISOString(),
262
- body: traceBody,
263
- },
264
- {
265
- type: "span-create",
266
- id: newId(),
267
- timestamp: startTime.toISOString(),
268
- body: spanBody,
269
- },
270
- ],
271
- };
285
+ output,
286
+ isError,
287
+ errorMessage,
288
+ startTime,
289
+ endTime,
290
+ traceId,
291
+ spanId,
292
+ userIdHashed,
293
+ clientName: _sessionContext.clientName,
294
+ mcpVersion: _sessionContext.mcpVersion,
295
+ stub,
296
+ });
272
297
  await sendBatch(config, batch);
273
298
  }
274
299
  catch (langfuseErr) {