@frihet/mcp-server 1.16.5 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -31
- package/dist/agent-onboarding.d.ts +98 -0
- package/dist/agent-onboarding.js +275 -0
- package/dist/api-origin.d.ts +9 -0
- package/dist/api-origin.js +41 -0
- package/dist/canary/mcp-harness.d.ts +77 -0
- package/dist/canary/mcp-harness.js +549 -0
- package/dist/capability-truth.d.ts +29 -0
- package/dist/capability-truth.js +254 -0
- package/dist/client-interface.d.ts +22 -14
- package/dist/client.d.ts +114 -18
- package/dist/client.js +520 -58
- package/dist/demo-client.d.ts +16 -13
- package/dist/demo-client.js +209 -29
- package/dist/demo-fixtures.d.ts +3 -3
- package/dist/demo-fixtures.js +7 -7
- package/dist/fiscal-aliases.d.ts +44 -0
- package/dist/fiscal-aliases.js +62 -0
- package/dist/index.js +41 -56
- package/dist/logger.d.ts +2 -2
- package/dist/logger.js +140 -8
- package/dist/observability.d.ts +18 -29
- package/dist/observability.js +192 -105
- package/dist/openai-profile.d.ts +27 -15
- package/dist/openai-profile.js +1191 -145
- package/dist/openai-review-contract.d.ts +49 -0
- package/dist/openai-review-contract.js +919 -0
- package/dist/openai-review-oauth.d.ts +98 -0
- package/dist/openai-review-oauth.js +136 -0
- package/dist/prompts/register-all.d.ts +2 -0
- package/dist/prompts/register-all.js +2 -0
- package/dist/public-capability-contract.d.ts +39 -0
- package/dist/public-capability-contract.js +186 -0
- package/dist/redaction.d.ts +10 -0
- package/dist/redaction.js +74 -0
- package/dist/resources/register-all.js +6 -12
- package/dist/server-composition.d.ts +16 -0
- package/dist/server-composition.js +44 -0
- package/dist/tool-exposure.d.ts +25 -22
- package/dist/tool-exposure.js +199 -77
- package/dist/tools/accountingClose.d.ts +14 -5
- package/dist/tools/accountingClose.js +41 -23
- package/dist/tools/backend-availability.d.ts +3 -3
- package/dist/tools/backend-availability.js +3 -3
- package/dist/tools/clients.js +9 -9
- package/dist/tools/crm.js +22 -21
- package/dist/tools/deposits.js +30 -11
- package/dist/tools/einvoice.d.ts +1 -1
- package/dist/tools/expenses.d.ts +16 -0
- package/dist/tools/expenses.js +47 -27
- package/dist/tools/hr.d.ts +2 -3
- package/dist/tools/hr.js +8 -9
- package/dist/tools/intelligence.js +95 -4
- package/dist/tools/invoices.js +150 -47
- package/dist/tools/payroll.d.ts +5 -8
- package/dist/tools/payroll.js +19 -32
- package/dist/tools/permissions.d.ts +4 -7
- package/dist/tools/permissions.js +11 -14
- package/dist/tools/products.js +6 -6
- package/dist/tools/quotes.js +109 -24
- package/dist/tools/shared.d.ts +1431 -75
- package/dist/tools/shared.js +386 -76
- package/dist/tools/team.js +18 -14
- package/dist/tools/vendors.js +9 -9
- package/dist/tools/webhooks.js +35 -30
- package/dist/types.d.ts +28 -4
- package/docs/agent-onboarding.json +381 -0
- package/package.json +32 -7
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
15
15
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
16
|
import { FrihetClient } from "./client.js";
|
|
17
17
|
import { DemoFrihetClient } from "./demo-client.js";
|
|
18
|
+
import { normalizePublicApiBaseUrl } from "./api-origin.js";
|
|
18
19
|
// Single source of truth for the server version: package.json (read at runtime
|
|
19
20
|
// from dist/../package.json). Hardcoding it here caused repeated version drift
|
|
20
21
|
// (server.json/index.ts/console out of sync → audit:mcp-refs gate failure).
|
|
@@ -28,11 +29,10 @@ const PKG_VERSION = (() => {
|
|
|
28
29
|
return "0.0.0";
|
|
29
30
|
}
|
|
30
31
|
})();
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import { resolveToolMode, applyToolExposureProfile, GROUPED_META_TOOL_COUNT } from "./tool-exposure.js";
|
|
32
|
+
import { OPENAI_ALLOWED_TOOL_COUNT, OPENAI_EXCLUDED_COUNT, OPENAI_EXCLUDED_RESOURCE_COUNT } from "./openai-profile.js";
|
|
33
|
+
import { resolveToolMode, GROUPED_META_TOOL_COUNT } from "./tool-exposure.js";
|
|
34
|
+
import { localMcpSurfaceComposition, registerMcpSurface, } from "./server-composition.js";
|
|
35
|
+
import { AGENT_SERVER_INSTRUCTIONS, API_KEY_LOCATION, } from "./agent-onboarding.js";
|
|
36
36
|
import { log } from "./logger.js";
|
|
37
37
|
import { registerShutdownHook } from "./metrics.js";
|
|
38
38
|
import { setTraceContext } from "./observability.js";
|
|
@@ -44,33 +44,40 @@ function main() {
|
|
|
44
44
|
const demoFlag = process.env.FRIHET_DEMO;
|
|
45
45
|
const demoMode = demoFlag === "1" || demoFlag === "true";
|
|
46
46
|
if (!demoMode && !apiKey) {
|
|
47
|
+
// Human-readable first, then one machine-readable line. An agent that only
|
|
48
|
+
// gets prose here has to guess; the JSON line gives it a code to branch on
|
|
49
|
+
// and the two recoveries that actually exist. /settings/api is the real,
|
|
50
|
+
// routable screen — there is no "Developers" section in the app.
|
|
47
51
|
console.error("Error: FRIHET_API_KEY environment variable is required.\n\n" +
|
|
48
52
|
"Get your API key:\n" +
|
|
49
53
|
" 1. Create a free account at https://app.frihet.io\n" +
|
|
50
|
-
" 2.
|
|
51
|
-
" 3. Create a key and add it to your MCP configuration\n\n" +
|
|
54
|
+
" 2. Open https://app.frihet.io/settings/api\n" +
|
|
55
|
+
" 3. Create a key (it starts with fri_) and add it to your MCP configuration\n\n" +
|
|
52
56
|
"Or try it instantly with no key: set FRIHET_DEMO=1 for example data.\n\n" +
|
|
53
57
|
"Documentation: https://docs.frihet.io/desarrolladores/mcp-server\n");
|
|
58
|
+
console.error(JSON.stringify({
|
|
59
|
+
error: {
|
|
60
|
+
code: "FRIHET_API_KEY_MISSING",
|
|
61
|
+
message: "FRIHET_API_KEY environment variable is required",
|
|
62
|
+
obtainAt: API_KEY_LOCATION,
|
|
63
|
+
recovery: [
|
|
64
|
+
{ action: "set_env", env: { FRIHET_API_KEY: "fri_<key>" } },
|
|
65
|
+
{ action: "run_without_credentials", env: { FRIHET_DEMO: "1" } },
|
|
66
|
+
],
|
|
67
|
+
docs: "https://docs.frihet.io/desarrolladores/mcp-server",
|
|
68
|
+
},
|
|
69
|
+
}));
|
|
54
70
|
process.exit(1);
|
|
55
71
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
const baseUrlRaw = process.env.FRIHET_API_URL;
|
|
73
|
+
let baseUrl;
|
|
74
|
+
if (!demoMode && baseUrlRaw !== undefined) {
|
|
59
75
|
try {
|
|
60
|
-
|
|
76
|
+
baseUrl = normalizePublicApiBaseUrl(baseUrlRaw);
|
|
61
77
|
}
|
|
62
|
-
catch {
|
|
63
|
-
console.error(`Error:
|
|
64
|
-
"It must be a
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
if (parsed.protocol !== "https:") {
|
|
68
|
-
console.error(`Error: FRIHET_API_URL must use https:// (got "${parsed.protocol}").\n`);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
if (!parsed.hostname.endsWith("frihet.io")) {
|
|
72
|
-
console.error(`Error: FRIHET_API_URL hostname must be under frihet.io (got "${parsed.hostname}").\n` +
|
|
73
|
-
"This prevents redirection to untrusted servers.\n");
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error(`Error: ${error instanceof Error ? error.message : "FRIHET_API_URL is invalid"}.\n` +
|
|
80
|
+
"It must be a canonical https:// URL with a trusted frihet.io hostname.\n");
|
|
74
81
|
process.exit(1);
|
|
75
82
|
}
|
|
76
83
|
}
|
|
@@ -80,69 +87,47 @@ function main() {
|
|
|
80
87
|
if (demoMode) {
|
|
81
88
|
console.error("[frihet-mcp] DEMO MODE — serving example fixtures, no real data, nothing persisted");
|
|
82
89
|
}
|
|
83
|
-
// Set
|
|
84
|
-
//
|
|
90
|
+
// Set the fixed protocol fact for Langfuse (reads LANGFUSE_* from process.env automatically).
|
|
91
|
+
// Client identity is deliberately omitted from telemetry.
|
|
85
92
|
setTraceContext({
|
|
86
93
|
mcpVersion: "mcp/1.0",
|
|
87
|
-
clientName: process.env.FRIHET_CLIENT_NAME,
|
|
88
94
|
});
|
|
89
95
|
const server = new McpServer({
|
|
90
96
|
name: "frihet-erp",
|
|
91
97
|
version: PKG_VERSION,
|
|
92
98
|
description: "AI-native MCP server for Frihet ERP — invoices, expenses, clients, products, quotes, webhooks, and deposits. " +
|
|
93
|
-
"Provides
|
|
94
|
-
"11 resources (7 static + 4
|
|
99
|
+
"Provides a catalogue of 157 canonical operations; fiscal aliases and optional grouped discovery names are reported separately. " +
|
|
100
|
+
"The local package serves 11 resources (7 static + 4 API-backed) and 10 workflow prompts " +
|
|
95
101
|
"with full Spanish tax compliance (IVA, IGIC, IPSI).",
|
|
102
|
+
}, {
|
|
103
|
+
// Delivered by `initialize` to every MCP client, which hands it to the model
|
|
104
|
+
// before the first tool call. This is the only onboarding channel that costs
|
|
105
|
+
// the operator zero configuration. Source: src/agent-onboarding.ts.
|
|
106
|
+
instructions: AGENT_SERVER_INSTRUCTIONS,
|
|
96
107
|
});
|
|
97
108
|
const openaiMode = process.env.FRIHET_OPENAI_MODE === "true";
|
|
98
109
|
const toolMode = resolveToolMode();
|
|
99
|
-
// PROFILE COMPOSITION ORDER (both interceptors wrap registerTool):
|
|
100
|
-
// 1. applyToolExposureProfile FIRST (innermost) — so the 3 discovery
|
|
101
|
-
// meta-tools register against the REAL server.registerTool and bypass the
|
|
102
|
-
// OpenAI allow-list gate. In allow-list mode it catalogs ONLY the reviewed
|
|
103
|
-
// tools, keeping the progressive-disclosure surface == the reviewed 53.
|
|
104
|
-
// 2. applyOpenAIProfile SECOND (outermost) — a business-tool registration is
|
|
105
|
-
// first gated/redacted/annotated/openWorldHint-justified by OpenAI, THEN
|
|
106
|
-
// collapsed by the grouped interceptor, so the terse collapsed line is the
|
|
107
|
-
// final description and OpenAI's handler redaction survives.
|
|
108
|
-
// Order only matters when BOTH are active (openai-mcp grouped); openai-only and
|
|
109
|
-
// grouped-only are unaffected by the swap.
|
|
110
|
-
// Apply grouped tool-exposure profile if enabled (progressive disclosure).
|
|
111
|
-
// FRIHET_TOOL_MODE=grouped collapses the full tool descriptions into terse
|
|
112
|
-
// one-liners + adds list_tool_groups / search_tools / describe_tool meta-tools,
|
|
113
|
-
// so agents load depth on demand instead of a flat 157-tool wall of context.
|
|
114
|
-
// Default (unset / "full") is byte-identical to current behavior. When OpenAI
|
|
115
|
-
// mode is also on, pass the reviewed allow-list so the catalog/meta-tools are
|
|
116
|
-
// pinned to exactly the 53 reviewed tools.
|
|
117
110
|
if (toolMode === "grouped") {
|
|
118
|
-
applyToolExposureProfile(server, openaiMode ? { allowlist: OPENAI_REVIEWED_TOOL_ALLOWLIST } : undefined);
|
|
119
111
|
log({
|
|
120
112
|
level: "info",
|
|
121
113
|
message: `Grouped tool-exposure active — tools collapsed to terse summaries, ${GROUPED_META_TOOL_COUNT} discovery meta-tools added (list_tool_groups, search_tools, describe_tool); full depth served on demand`,
|
|
122
114
|
operation: "startup",
|
|
123
115
|
});
|
|
124
116
|
}
|
|
125
|
-
// Apply OpenAI-safe profile if enabled (strips sensitive fields, fixes annotations)
|
|
126
117
|
if (openaiMode) {
|
|
127
|
-
applyOpenAIProfile(server);
|
|
128
118
|
log({
|
|
129
119
|
level: "info",
|
|
130
120
|
message: `OpenAI safety profile active — ${OPENAI_ALLOWED_TOOL_COUNT} tools allowed, prompts hidden, ${OPENAI_EXCLUDED_COUNT} defense-in-depth exclusions, ${OPENAI_EXCLUDED_RESOURCE_COUNT} resources excluded, gov IDs + credentials redacted`,
|
|
131
121
|
operation: "startup",
|
|
132
122
|
});
|
|
133
123
|
}
|
|
134
|
-
|
|
135
|
-
registerAllTools(server, client);
|
|
136
|
-
// Register 11 resources (7 static + 4 dynamic via API)
|
|
137
|
-
registerAllResources(server, client);
|
|
138
|
-
// Register 10 workflow prompts
|
|
139
|
-
registerAllPrompts(server);
|
|
124
|
+
registerMcpSurface(server, client, localMcpSurfaceComposition(openaiMode, toolMode === "grouped"));
|
|
140
125
|
// Register shutdown hook to log final metrics summary
|
|
141
126
|
registerShutdownHook();
|
|
142
127
|
// Connect via stdio transport
|
|
143
128
|
const transport = new StdioServerTransport();
|
|
144
129
|
server.connect(transport).then(() => {
|
|
145
|
-
console.error(`[frihet-mcp] v${PKG_VERSION} |
|
|
130
|
+
console.error(`[frihet-mcp] v${PKG_VERSION} | capability truth enabled | https://github.com/Frihet-io/frihet-mcp`);
|
|
146
131
|
log({
|
|
147
132
|
level: "info",
|
|
148
133
|
message: "Frihet MCP server running on stdio",
|
package/dist/logger.d.ts
CHANGED
|
@@ -37,10 +37,10 @@ export declare function logToolCall(tool: string, startTime: number, success: bo
|
|
|
37
37
|
/**
|
|
38
38
|
* Log an outbound API call with timing.
|
|
39
39
|
*/
|
|
40
|
-
export declare function logApiCall(method: string,
|
|
40
|
+
export declare function logApiCall(method: string, _path: string, statusCode: number, durationMs: number): void;
|
|
41
41
|
/**
|
|
42
42
|
* Log a rate-limit retry.
|
|
43
43
|
*/
|
|
44
|
-
export declare function logRetry(method: string,
|
|
44
|
+
export declare function logRetry(method: string, _path: string, retryCount: number, delayMs: number): void;
|
|
45
45
|
export {};
|
|
46
46
|
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.js
CHANGED
|
@@ -6,6 +6,107 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Set FRIHET_MCP_DEBUG=1 to enable debug-level logs.
|
|
8
8
|
*/
|
|
9
|
+
const OPERATION_MESSAGES = {
|
|
10
|
+
startup: "MCP startup event",
|
|
11
|
+
session_init: "MCP session event",
|
|
12
|
+
tool_call: "MCP tool call completed",
|
|
13
|
+
tool_error: "MCP tool error",
|
|
14
|
+
api_call: "MCP API call completed",
|
|
15
|
+
api_retry: "MCP API retry scheduled",
|
|
16
|
+
http_request: "MCP HTTP request completed",
|
|
17
|
+
oauth_authorize: "MCP OAuth authorization event",
|
|
18
|
+
oauth_callback: "MCP OAuth callback event",
|
|
19
|
+
shutdown_metrics: "MCP shutdown event",
|
|
20
|
+
langfuse_send: "MCP telemetry delivery event",
|
|
21
|
+
langfuse_trace: "MCP telemetry trace event",
|
|
22
|
+
};
|
|
23
|
+
const TOOL_NAME = /^[a-z][a-z0-9_]{0,63}$/u;
|
|
24
|
+
const PACKAGE_VERSION = /^\d{1,3}\.\d{1,3}\.\d{1,3}$/u;
|
|
25
|
+
const SAFE_METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
26
|
+
const SAFE_TRANSPORTS = new Set(["stdio", "remote"]);
|
|
27
|
+
const SAFE_MCP_VERSIONS = new Set(["mcp/1.0"]);
|
|
28
|
+
const LOCAL_ERROR_CODES = new Set([
|
|
29
|
+
"tool_error",
|
|
30
|
+
"telemetry_http_error",
|
|
31
|
+
"telemetry_error",
|
|
32
|
+
"startup_error",
|
|
33
|
+
"unknown_error",
|
|
34
|
+
]);
|
|
35
|
+
function toolNameToken(value) {
|
|
36
|
+
return typeof value === "string" && TOOL_NAME.test(value) ? value : undefined;
|
|
37
|
+
}
|
|
38
|
+
function operationalErrorCode(value, statusCode) {
|
|
39
|
+
if (statusCode === 401)
|
|
40
|
+
return "unauthorized";
|
|
41
|
+
if (statusCode === 403)
|
|
42
|
+
return "forbidden";
|
|
43
|
+
if (statusCode === 404)
|
|
44
|
+
return "not_found";
|
|
45
|
+
if (statusCode === 408)
|
|
46
|
+
return "request_timeout";
|
|
47
|
+
if (statusCode === 429)
|
|
48
|
+
return "rate_limited";
|
|
49
|
+
if (statusCode !== undefined && statusCode >= 400 && statusCode < 500)
|
|
50
|
+
return "client_error";
|
|
51
|
+
if (statusCode !== undefined && statusCode >= 500)
|
|
52
|
+
return "backend_error";
|
|
53
|
+
return typeof value === "string" && LOCAL_ERROR_CODES.has(value) ? value : "unknown_error";
|
|
54
|
+
}
|
|
55
|
+
function safeInteger(value, min, max) {
|
|
56
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max
|
|
57
|
+
? value
|
|
58
|
+
: undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Central operational metadata allowlist.
|
|
62
|
+
*
|
|
63
|
+
* Callers may pass arbitrary objects for backwards compatibility, but only
|
|
64
|
+
* bounded protocol/runtime facts can cross the logging boundary. Dynamic
|
|
65
|
+
* paths, identifiers, user agents, emails, provider bodies, and nested objects
|
|
66
|
+
* are intentionally discarded.
|
|
67
|
+
*/
|
|
68
|
+
function sanitizeMetadata(metadata) {
|
|
69
|
+
if (!metadata)
|
|
70
|
+
return undefined;
|
|
71
|
+
const safe = {};
|
|
72
|
+
const version = typeof metadata.version === "string" && PACKAGE_VERSION.test(metadata.version)
|
|
73
|
+
? metadata.version
|
|
74
|
+
: undefined;
|
|
75
|
+
const transport = typeof metadata.transport === "string" && SAFE_TRANSPORTS.has(metadata.transport)
|
|
76
|
+
? metadata.transport
|
|
77
|
+
: undefined;
|
|
78
|
+
const mcpVersion = typeof metadata.mcpVersion === "string" && SAFE_MCP_VERSIONS.has(metadata.mcpVersion)
|
|
79
|
+
? metadata.mcpVersion
|
|
80
|
+
: undefined;
|
|
81
|
+
const method = typeof metadata.method === "string" && SAFE_METHODS.has(metadata.method)
|
|
82
|
+
? metadata.method
|
|
83
|
+
: undefined;
|
|
84
|
+
const statusCode = safeInteger(metadata.statusCode, 0, 599);
|
|
85
|
+
const retryCount = safeInteger(metadata.retryCount, 0, 100);
|
|
86
|
+
const delayMs = safeInteger(metadata.delayMs, 0, 86_400_000);
|
|
87
|
+
const uptime = safeInteger(metadata.uptime, 0, Number.MAX_SAFE_INTEGER);
|
|
88
|
+
if (version)
|
|
89
|
+
safe.version = version;
|
|
90
|
+
if (transport)
|
|
91
|
+
safe.transport = transport;
|
|
92
|
+
if (mcpVersion)
|
|
93
|
+
safe.mcpVersion = mcpVersion;
|
|
94
|
+
if (method)
|
|
95
|
+
safe.method = method;
|
|
96
|
+
if (statusCode !== undefined)
|
|
97
|
+
safe.statusCode = statusCode;
|
|
98
|
+
if (retryCount !== undefined)
|
|
99
|
+
safe.retryCount = retryCount;
|
|
100
|
+
if (delayMs !== undefined)
|
|
101
|
+
safe.delayMs = delayMs;
|
|
102
|
+
if (uptime !== undefined)
|
|
103
|
+
safe.uptime = uptime;
|
|
104
|
+
if (typeof metadata.success === "boolean")
|
|
105
|
+
safe.success = metadata.success;
|
|
106
|
+
if (typeof metadata.stub === "boolean")
|
|
107
|
+
safe.stub = metadata.stub;
|
|
108
|
+
return Object.keys(safe).length > 0 ? safe : undefined;
|
|
109
|
+
}
|
|
9
110
|
/**
|
|
10
111
|
* Returns true if debug logging is enabled.
|
|
11
112
|
* Checks env var in Node.js; always false in Workers unless overridden.
|
|
@@ -25,10 +126,41 @@ export function log(entry) {
|
|
|
25
126
|
if (entry.level === "debug" && !isDebugEnabled()) {
|
|
26
127
|
return;
|
|
27
128
|
}
|
|
129
|
+
const operation = typeof entry.operation === "string" && Object.hasOwn(OPERATION_MESSAGES, entry.operation)
|
|
130
|
+
? entry.operation
|
|
131
|
+
: undefined;
|
|
132
|
+
const tool = toolNameToken(entry.tool);
|
|
133
|
+
const durationMs = typeof entry.durationMs === "number"
|
|
134
|
+
&& Number.isFinite(entry.durationMs)
|
|
135
|
+
&& entry.durationMs >= 0
|
|
136
|
+
&& entry.durationMs <= 86_400_000
|
|
137
|
+
? Math.round(entry.durationMs)
|
|
138
|
+
: undefined;
|
|
139
|
+
const errorStatus = safeInteger(entry.error?.statusCode, 0, 599);
|
|
140
|
+
const errorCode = operationalErrorCode(entry.error?.code, errorStatus);
|
|
141
|
+
const metadata = sanitizeMetadata(entry.metadata);
|
|
28
142
|
const full = {
|
|
29
|
-
|
|
143
|
+
level: entry.level,
|
|
144
|
+
message: operation === "tool_call" && entry.level === "error"
|
|
145
|
+
? "MCP tool call failed"
|
|
146
|
+
: operation
|
|
147
|
+
? (OPERATION_MESSAGES[operation] ?? "MCP operational event")
|
|
148
|
+
: "MCP operational event",
|
|
30
149
|
service: "frihet-mcp",
|
|
31
150
|
timestamp: new Date().toISOString(),
|
|
151
|
+
...(tool ? { tool } : {}),
|
|
152
|
+
...(operation ? { operation } : {}),
|
|
153
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
154
|
+
...(entry.error
|
|
155
|
+
? {
|
|
156
|
+
error: {
|
|
157
|
+
message: "MCP operation failed",
|
|
158
|
+
code: errorCode,
|
|
159
|
+
...(errorStatus !== undefined ? { statusCode: errorStatus } : {}),
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
: {}),
|
|
163
|
+
...(metadata ? { metadata } : {}),
|
|
32
164
|
};
|
|
33
165
|
// Remove undefined fields for cleaner output
|
|
34
166
|
const cleaned = JSON.stringify(full, (_key, value) => value === undefined ? undefined : value);
|
|
@@ -47,7 +179,7 @@ export function logToolCall(tool, startTime, success, error) {
|
|
|
47
179
|
operation: "tool_call",
|
|
48
180
|
durationMs,
|
|
49
181
|
error: {
|
|
50
|
-
message:
|
|
182
|
+
message: "MCP operation failed",
|
|
51
183
|
code: error.errorCode ?? error.name,
|
|
52
184
|
statusCode: error.statusCode,
|
|
53
185
|
},
|
|
@@ -66,25 +198,25 @@ export function logToolCall(tool, startTime, success, error) {
|
|
|
66
198
|
/**
|
|
67
199
|
* Log an outbound API call with timing.
|
|
68
200
|
*/
|
|
69
|
-
export function logApiCall(method,
|
|
201
|
+
export function logApiCall(method, _path, statusCode, durationMs) {
|
|
70
202
|
const level = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "info";
|
|
71
203
|
log({
|
|
72
204
|
level,
|
|
73
|
-
message:
|
|
205
|
+
message: "MCP API call completed",
|
|
74
206
|
operation: "api_call",
|
|
75
207
|
durationMs,
|
|
76
|
-
metadata: { method,
|
|
208
|
+
metadata: { method, statusCode },
|
|
77
209
|
});
|
|
78
210
|
}
|
|
79
211
|
/**
|
|
80
212
|
* Log a rate-limit retry.
|
|
81
213
|
*/
|
|
82
|
-
export function logRetry(method,
|
|
214
|
+
export function logRetry(method, _path, retryCount, delayMs) {
|
|
83
215
|
log({
|
|
84
216
|
level: "warn",
|
|
85
|
-
message:
|
|
217
|
+
message: "MCP API retry scheduled",
|
|
86
218
|
operation: "api_retry",
|
|
87
|
-
metadata: { method,
|
|
219
|
+
metadata: { method, retryCount: retryCount + 1, delayMs },
|
|
88
220
|
});
|
|
89
221
|
}
|
|
90
222
|
//# sourceMappingURL=logger.js.map
|
package/dist/observability.d.ts
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Design:
|
|
8
8
|
* - Fail-open: any Langfuse error logs a warning and lets the tool proceed.
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
9
|
+
* - Data minimization: only bounded operational facts cross the telemetry
|
|
10
|
+
* boundary. Tool input/output, arbitrary error text, and user/workspace
|
|
11
|
+
* identity are never serialized.
|
|
11
12
|
* - Fire-and-forget: traces are sent via waitUntil (Workers) or unref'd promise
|
|
12
13
|
* (Node.js) so they never block tool responses.
|
|
13
14
|
*
|
|
@@ -18,6 +19,12 @@
|
|
|
18
19
|
*
|
|
19
20
|
* Docs: https://langfuse.com/docs/api/reference/overview
|
|
20
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Langfuse receives a Basic Authorization header, so its authority is exact.
|
|
24
|
+
* The current product contract documents one hosted origin; arbitrary Frihet
|
|
25
|
+
* subdomains and self-hosted overrides are intentionally not trusted here.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeLangfuseBaseUrl(value: string): string;
|
|
21
28
|
/**
|
|
22
29
|
* Called once from FrihetMCP.init() in the Worker to inject env vars.
|
|
23
30
|
* Not needed in Node.js stdio mode (reads from process.env directly).
|
|
@@ -33,8 +40,6 @@ interface LangfuseSpanBody {
|
|
|
33
40
|
name: string;
|
|
34
41
|
startTime: string;
|
|
35
42
|
endTime: string;
|
|
36
|
-
input?: unknown;
|
|
37
|
-
output?: unknown;
|
|
38
43
|
metadata?: Record<string, unknown>;
|
|
39
44
|
level?: "DEFAULT" | "DEBUG" | "WARNING" | "ERROR";
|
|
40
45
|
statusMessage?: string;
|
|
@@ -43,11 +48,8 @@ interface LangfuseTraceBody {
|
|
|
43
48
|
id: string;
|
|
44
49
|
name: string;
|
|
45
50
|
timestamp: string;
|
|
46
|
-
input?: unknown;
|
|
47
|
-
output?: unknown;
|
|
48
51
|
metadata?: Record<string, unknown>;
|
|
49
52
|
tags?: string[];
|
|
50
|
-
userId?: string;
|
|
51
53
|
}
|
|
52
54
|
interface IngestionBatch {
|
|
53
55
|
batch: Array<{
|
|
@@ -58,8 +60,7 @@ interface IngestionBatch {
|
|
|
58
60
|
}>;
|
|
59
61
|
}
|
|
60
62
|
interface StubMarker {
|
|
61
|
-
|
|
62
|
-
note?: string;
|
|
63
|
+
stub: true;
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
65
66
|
* Inspect a resolved tool output for stub / not-implemented / unavailable
|
|
@@ -79,33 +80,24 @@ interface StubMarker {
|
|
|
79
80
|
*/
|
|
80
81
|
export declare function inspectStubMarker(output: unknown): StubMarker | null;
|
|
81
82
|
interface TraceContext {
|
|
82
|
-
/** User-Agent from MCP client or other client identifier */
|
|
83
|
-
clientName?: string;
|
|
84
83
|
/** MCP protocol version */
|
|
85
84
|
mcpVersion?: string;
|
|
86
|
-
/** Frihet workspace/user ID — will be hashed */
|
|
87
|
-
userId?: string;
|
|
88
85
|
}
|
|
89
86
|
/**
|
|
90
|
-
* Set session-level context
|
|
87
|
+
* Set session-level protocol context. Client identity is intentionally absent.
|
|
91
88
|
* Call once from server init; applies to all subsequent traces.
|
|
92
89
|
*/
|
|
93
90
|
export declare function setTraceContext(ctx: TraceContext): void;
|
|
94
91
|
interface TracePayloadParams {
|
|
95
92
|
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
93
|
isError: boolean;
|
|
101
|
-
|
|
94
|
+
errorClass?: string;
|
|
95
|
+
errorCode?: string;
|
|
96
|
+
statusCode?: number;
|
|
102
97
|
startTime: Date;
|
|
103
98
|
endTime: Date;
|
|
104
99
|
traceId: string;
|
|
105
100
|
spanId: string;
|
|
106
|
-
/** Already-hashed user id, or undefined. */
|
|
107
|
-
userIdHashed?: string;
|
|
108
|
-
clientName?: string;
|
|
109
101
|
mcpVersion?: string;
|
|
110
102
|
/** Fabricated-stub marker, or null on a genuine result. */
|
|
111
103
|
stub: StubMarker | null;
|
|
@@ -113,12 +105,9 @@ interface TracePayloadParams {
|
|
|
113
105
|
/**
|
|
114
106
|
* Builds the Langfuse trace+span ingestion batch for a single tool call.
|
|
115
107
|
*
|
|
116
|
-
* CRITICAL (Trust):
|
|
117
|
-
*
|
|
118
|
-
*
|
|
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.
|
|
108
|
+
* CRITICAL (Trust): this builder is an allowlist. It has no input/output or
|
|
109
|
+
* identity fields and ignores any extra runtime properties a caller supplies.
|
|
110
|
+
* Exported so tests can assert the exact operational shape.
|
|
122
111
|
*/
|
|
123
112
|
export declare function buildTracePayload(p: TracePayloadParams): IngestionBatch;
|
|
124
113
|
/**
|
|
@@ -132,6 +121,6 @@ export declare function buildTracePayload(p: TracePayloadParams): IngestionBatch
|
|
|
132
121
|
* @param fn Async tool handler to wrap
|
|
133
122
|
* @returns Result of fn
|
|
134
123
|
*/
|
|
135
|
-
export declare function traceMCPTool<T>(toolName: string,
|
|
124
|
+
export declare function traceMCPTool<T>(toolName: string, _input: unknown, fn: () => Promise<T>): Promise<T>;
|
|
136
125
|
export {};
|
|
137
126
|
//# sourceMappingURL=observability.d.ts.map
|