@frihet/mcp-server 1.17.0 → 2.0.1
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 +106 -348
- package/dist/agent-onboarding.d.ts +1 -1
- package/dist/agent-onboarding.js +3 -3
- package/dist/canary/mcp-harness.d.ts +1 -1
- package/dist/canary/mcp-harness.js +4 -1
- package/dist/capability-truth.js +20 -12
- package/dist/claude-candidate-contract.d.ts +482 -0
- package/dist/claude-candidate-contract.js +87 -0
- package/dist/claude-output-privacy.d.ts +17 -0
- package/dist/claude-output-privacy.js +249 -0
- package/dist/claude-profile.d.ts +18 -0
- package/dist/claude-profile.js +260 -0
- package/dist/client-interface.d.ts +26 -20
- package/dist/client.d.ts +65 -29
- package/dist/client.js +278 -167
- package/dist/demo-client.d.ts +28 -21
- package/dist/demo-client.js +32 -14
- package/dist/deposit-contract.d.ts +29 -0
- package/dist/deposit-contract.js +76 -0
- package/dist/fetch-no-redirect.d.ts +3 -0
- package/dist/fetch-no-redirect.js +16 -0
- package/dist/fiscal-aliases.d.ts +2 -2
- package/dist/fiscal-aliases.js +2 -2
- package/dist/fiscal-period.d.ts +26 -0
- package/dist/fiscal-period.js +31 -0
- package/dist/google-profile.d.ts +19 -0
- package/dist/google-profile.js +122 -0
- package/dist/index.js +31 -7
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +2 -2
- package/dist/muse-profile.d.ts +19 -0
- package/dist/muse-profile.js +122 -0
- package/dist/observability.js +3 -2
- package/dist/openai-profile.d.ts +2 -2
- package/dist/openai-profile.js +37 -11
- package/dist/openai-review-contract.d.ts +1 -1
- package/dist/openai-review-contract.js +4 -5
- package/dist/openai-review-oauth.d.ts +107 -7
- package/dist/openai-review-oauth.js +161 -17
- package/dist/prompts/register-all.js +32 -78
- package/dist/redaction.d.ts +5 -0
- package/dist/redaction.js +9 -0
- package/dist/resources/register-all.js +150 -54
- package/dist/retry-after.d.ts +19 -0
- package/dist/retry-after.js +84 -0
- package/dist/server-composition.d.ts +6 -2
- package/dist/server-composition.js +46 -5
- package/dist/tool-exposure.d.ts +7 -5
- package/dist/tool-exposure.js +21 -13
- package/dist/tools/audit_gl.js +8 -8
- package/dist/tools/backend-availability.d.ts +8 -29
- package/dist/tools/backend-availability.js +31 -30
- package/dist/tools/bank_rules.js +66 -15
- package/dist/tools/banking.js +5 -13
- package/dist/tools/clients.js +254 -6
- package/dist/tools/deposits.js +107 -97
- package/dist/tools/einvoice.d.ts +17 -3
- package/dist/tools/einvoice.js +59 -12
- package/dist/tools/fiscal.d.ts +3 -4
- package/dist/tools/fiscal.js +93 -60
- package/dist/tools/gestoria.js +15 -7
- package/dist/tools/hr.js +41 -11
- package/dist/tools/igic.d.ts +9 -8
- package/dist/tools/igic.js +34 -64
- package/dist/tools/impuesto_sociedades.d.ts +4 -5
- package/dist/tools/impuesto_sociedades.js +16 -37
- package/dist/tools/intelligence.js +164 -10
- package/dist/tools/invoices.js +109 -42
- package/dist/tools/kitchen.js +19 -9
- package/dist/tools/quotes.js +86 -25
- package/dist/tools/recurring.js +26 -25
- package/dist/tools/register-all.d.ts +2 -2
- package/dist/tools/register-all.js +9 -6
- package/dist/tools/search.d.ts +19 -0
- package/dist/tools/search.js +84 -0
- package/dist/tools/shared.d.ts +18 -38
- package/dist/tools/shared.js +96 -30
- package/dist/tools/team.js +8 -16
- package/dist/tools/time.js +4 -12
- package/dist/tools/vendors.js +242 -6
- package/dist/types.d.ts +3 -1
- package/dist/workflows/daily-operational-brief.d.ts +163 -0
- package/dist/workflows/daily-operational-brief.js +881 -0
- package/docs/agent-onboarding.json +15 -5
- package/package.json +26 -12
- package/dist/tools/onboarding.d.ts +0 -19
- package/dist/tools/onboarding.js +0 -64
package/dist/logger.d.ts
CHANGED
|
@@ -41,6 +41,6 @@ export declare function logApiCall(method: string, _path: string, statusCode: nu
|
|
|
41
41
|
/**
|
|
42
42
|
* Log a rate-limit retry.
|
|
43
43
|
*/
|
|
44
|
-
export declare function logRetry(method: string, _path: string, retryCount: number, delayMs: number): void;
|
|
44
|
+
export declare function logRetry(method: string, _path: string, retryCount: number, delayMs: number, delaySource?: "retry_after" | "backoff"): void;
|
|
45
45
|
export {};
|
|
46
46
|
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.js
CHANGED
|
@@ -211,12 +211,12 @@ export function logApiCall(method, _path, statusCode, durationMs) {
|
|
|
211
211
|
/**
|
|
212
212
|
* Log a rate-limit retry.
|
|
213
213
|
*/
|
|
214
|
-
export function logRetry(method, _path, retryCount, delayMs) {
|
|
214
|
+
export function logRetry(method, _path, retryCount, delayMs, delaySource) {
|
|
215
215
|
log({
|
|
216
216
|
level: "warn",
|
|
217
217
|
message: "MCP API retry scheduled",
|
|
218
218
|
operation: "api_retry",
|
|
219
|
-
metadata: { method, retryCount: retryCount + 1, delayMs },
|
|
219
|
+
metadata: { method, retryCount: retryCount + 1, delayMs, delaySource },
|
|
220
220
|
});
|
|
221
221
|
}
|
|
222
222
|
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta Muse Profile for the Frihet MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Implements the curated 18-tool financial management surface submitted
|
|
5
|
+
* for the Meta Muse Business Application catalog.
|
|
6
|
+
*
|
|
7
|
+
* Requirements:
|
|
8
|
+
* - Curated catalogue: 18 high-value small business & freelancer tools.
|
|
9
|
+
* - Human-in-the-loop: Invoices and financial documents are created strictly in draft status.
|
|
10
|
+
* - Zero unconfirmed destructive actions (no direct AEAT / VeriFactu fiscal submissions).
|
|
11
|
+
* - Standard output redaction for sensitive fields.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MUSE_CANDIDATE_TOOLS: ReadonlySet<string>;
|
|
14
|
+
export declare const MUSE_CANDIDATE_TOOL_COUNT: number;
|
|
15
|
+
export declare const MUSE_INSTRUCTIONS = "Frihet ERP \u2014 natural-language business and financial management for Meta Muse.\nUse get_business_context for complete company health and plan limits.\nQuery and review invoices, expenses, quotes, clients, and products with pagination.\nAll created invoices are drafts. Confirm parameters with the user before creating records.";
|
|
16
|
+
export declare function museToolResult(result: unknown): unknown;
|
|
17
|
+
export declare const MUSE_FORCED_INPUT_VALUES: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
18
|
+
export declare function applyMuseProfile(server: any): void;
|
|
19
|
+
//# sourceMappingURL=muse-profile.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta Muse Profile for the Frihet MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Implements the curated 18-tool financial management surface submitted
|
|
5
|
+
* for the Meta Muse Business Application catalog.
|
|
6
|
+
*
|
|
7
|
+
* Requirements:
|
|
8
|
+
* - Curated catalogue: 18 high-value small business & freelancer tools.
|
|
9
|
+
* - Human-in-the-loop: Invoices and financial documents are created strictly in draft status.
|
|
10
|
+
* - Zero unconfirmed destructive actions (no direct AEAT / VeriFactu fiscal submissions).
|
|
11
|
+
* - Standard output redaction for sensitive fields.
|
|
12
|
+
*/
|
|
13
|
+
import { deepRedact, redactText } from "./redaction.js";
|
|
14
|
+
export const MUSE_CANDIDATE_TOOLS = new Set([
|
|
15
|
+
"get_business_context",
|
|
16
|
+
"get_monthly_summary",
|
|
17
|
+
"get_quarterly_taxes",
|
|
18
|
+
"list_invoices",
|
|
19
|
+
"get_invoice",
|
|
20
|
+
"search_invoices",
|
|
21
|
+
"list_expenses",
|
|
22
|
+
"get_expense",
|
|
23
|
+
"create_expense",
|
|
24
|
+
"list_clients",
|
|
25
|
+
"get_client",
|
|
26
|
+
"create_client",
|
|
27
|
+
"list_products",
|
|
28
|
+
"get_product",
|
|
29
|
+
"list_quotes",
|
|
30
|
+
"get_quote",
|
|
31
|
+
"create_quote",
|
|
32
|
+
"create_invoice",
|
|
33
|
+
]);
|
|
34
|
+
export const MUSE_CANDIDATE_TOOL_COUNT = MUSE_CANDIDATE_TOOLS.size; // 18
|
|
35
|
+
export const MUSE_INSTRUCTIONS = `Frihet ERP — natural-language business and financial management for Meta Muse.
|
|
36
|
+
Use get_business_context for complete company health and plan limits.
|
|
37
|
+
Query and review invoices, expenses, quotes, clients, and products with pagination.
|
|
38
|
+
All created invoices are drafts. Confirm parameters with the user before creating records.`;
|
|
39
|
+
export function museToolResult(result) {
|
|
40
|
+
if (result === null || typeof result !== "object")
|
|
41
|
+
return result;
|
|
42
|
+
const res = result;
|
|
43
|
+
// Deep-redact structured output if present (in-place deletion of sensitive fields)
|
|
44
|
+
if (res.structuredContent && typeof res.structuredContent === "object") {
|
|
45
|
+
deepRedact(res.structuredContent);
|
|
46
|
+
}
|
|
47
|
+
// Redact text content items
|
|
48
|
+
if (Array.isArray(res.content)) {
|
|
49
|
+
res.content = res.content.map((item) => {
|
|
50
|
+
if (item && typeof item === "object" && "text" in item && typeof item.text === "string") {
|
|
51
|
+
return {
|
|
52
|
+
...item,
|
|
53
|
+
text: redactText(item.text),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return item;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return res;
|
|
60
|
+
}
|
|
61
|
+
export const MUSE_FORCED_INPUT_VALUES = {
|
|
62
|
+
create_invoice: { status: "draft" },
|
|
63
|
+
create_quote: { status: "draft" },
|
|
64
|
+
};
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
+
export function applyMuseProfile(server) {
|
|
67
|
+
const registerTool = server.registerTool.bind(server);
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
server.registerTool = (name, config, handler) => {
|
|
70
|
+
if (!MUSE_CANDIDATE_TOOLS.has(name))
|
|
71
|
+
return undefined;
|
|
72
|
+
let description = config.description ?? "";
|
|
73
|
+
let inputSchema = config.inputSchema;
|
|
74
|
+
if (name === "create_invoice") {
|
|
75
|
+
description = `${description} [Meta Muse: Generates an invoice strictly in 'draft' status for human review. No irreversible fiscal submission occurs.]`;
|
|
76
|
+
if (inputSchema && typeof inputSchema === "object" && "status" in inputSchema) {
|
|
77
|
+
const { status: _omittedStatus, ...rest } = inputSchema;
|
|
78
|
+
inputSchema = rest;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (name === "create_quote") {
|
|
82
|
+
description = `${description} [Meta Muse: Generates a quote strictly in 'draft' status for human review.]`;
|
|
83
|
+
if (inputSchema && typeof inputSchema === "object" && "status" in inputSchema) {
|
|
84
|
+
const { status: _omittedStatus, ...rest } = inputSchema;
|
|
85
|
+
inputSchema = rest;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return registerTool(name, {
|
|
89
|
+
...config,
|
|
90
|
+
description,
|
|
91
|
+
inputSchema,
|
|
92
|
+
annotations: {
|
|
93
|
+
...config.annotations,
|
|
94
|
+
...(config.annotations?.readOnlyHint !== true ? { destructiveHint: true } : {}),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
async (...args) => {
|
|
99
|
+
try {
|
|
100
|
+
let input = args[0];
|
|
101
|
+
if (input && typeof input === "object") {
|
|
102
|
+
const forced = MUSE_FORCED_INPUT_VALUES[name];
|
|
103
|
+
if (forced) {
|
|
104
|
+
input = { ...input, ...forced };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const res = await handler(input, ...args.slice(1));
|
|
108
|
+
return museToolResult(res);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return museToolResult({
|
|
112
|
+
isError: true,
|
|
113
|
+
_meta: { "io.frihet/operationOutcomeUnknown": true },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
// Meta Muse profile intentionally registers 0 raw prompts or dynamic resources
|
|
119
|
+
server.registerResource = () => undefined;
|
|
120
|
+
server.registerPrompt = () => undefined;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=muse-profile.js.map
|
package/dist/observability.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* Docs: https://langfuse.com/docs/api/reference/overview
|
|
21
21
|
*/
|
|
22
22
|
import { log } from "./logger.js";
|
|
23
|
+
import { rejectRedirectResponse } from "./fetch-no-redirect.js";
|
|
23
24
|
const CANONICAL_LANGFUSE_ORIGIN = "https://langfuse.frihet.io";
|
|
24
25
|
/**
|
|
25
26
|
* Langfuse receives a Basic Authorization header, so its authority is exact.
|
|
@@ -139,14 +140,14 @@ async function sendBatch(config, batch) {
|
|
|
139
140
|
const credentials = btoa(`${config.publicKey}:${config.secretKey}`);
|
|
140
141
|
const resp = await fetch(`${config.baseUrl}/api/public/ingestion`, {
|
|
141
142
|
method: "POST",
|
|
142
|
-
redirect: "
|
|
143
|
+
redirect: "manual",
|
|
143
144
|
headers: {
|
|
144
145
|
"Content-Type": "application/json",
|
|
145
146
|
"Authorization": `Basic ${credentials}`,
|
|
146
147
|
},
|
|
147
148
|
body: JSON.stringify(batch),
|
|
148
149
|
signal: AbortSignal.timeout(5000),
|
|
149
|
-
});
|
|
150
|
+
}).then(rejectRedirectResponse);
|
|
150
151
|
if (!resp.ok) {
|
|
151
152
|
// Never read/log the provider response body. Status is sufficient to
|
|
152
153
|
// correlate an operational delivery failure without importing arbitrary
|
package/dist/openai-profile.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 4. Redacts sensitive fields from all tool outputs
|
|
13
13
|
* 5. Updates descriptions to reflect modified behavior + openWorldHint justifications
|
|
14
14
|
*
|
|
15
|
-
* The full MCP server (
|
|
15
|
+
* The full MCP server (158 business tools + MCP extras) remains available for Claude, Cursor,
|
|
16
16
|
* Windsurf, Cline, Codex, and all other MCP clients.
|
|
17
17
|
*
|
|
18
18
|
* OpenAI-safe mode: 33 reviewed business tools, 0 prompts, 0 resources, and no
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
/** OAuth requirement advertised on every reviewed business tool. */
|
|
25
25
|
export declare const OPENAI_OAUTH_SECURITY_SCHEMES: readonly [{
|
|
26
26
|
readonly type: "oauth2";
|
|
27
|
-
readonly scopes: readonly [
|
|
27
|
+
readonly scopes: readonly string[];
|
|
28
28
|
}];
|
|
29
29
|
export declare const OPENAI_WORKSPACE_WEBHOOK_EVENT_TOOLS: ReadonlySet<string>;
|
|
30
30
|
export declare const OPENAI_REVIEW_CONFIRM_REQUIRED_TOOLS: ReadonlySet<string>;
|
package/dist/openai-profile.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 4. Redacts sensitive fields from all tool outputs
|
|
13
13
|
* 5. Updates descriptions to reflect modified behavior + openWorldHint justifications
|
|
14
14
|
*
|
|
15
|
-
* The full MCP server (
|
|
15
|
+
* The full MCP server (158 business tools + MCP extras) remains available for Claude, Cursor,
|
|
16
16
|
* Windsurf, Cline, Codex, and all other MCP clients.
|
|
17
17
|
*
|
|
18
18
|
* OpenAI-safe mode: 33 reviewed business tools, 0 prompts, 0 resources, and no
|
|
@@ -26,7 +26,13 @@ import { z } from "zod/v4";
|
|
|
26
26
|
import { correctToolAnnotations } from "./capability-truth.js";
|
|
27
27
|
import { MCP_RESOURCE_COUNT } from "./resources/register-all.js";
|
|
28
28
|
import { SENSITIVE_FIELD_NAMES, deepRedact, redactText } from "./redaction.js";
|
|
29
|
-
import {
|
|
29
|
+
import { buildOpenAIUnauthorizedChallenge, hasExactOpenAIReviewScopes, OPENAI_REVIEW_OAUTH_SCOPES, } from "./openai-review-oauth.js";
|
|
30
|
+
const REVIEWED_INVOICE_EVIDENCE_BOUNDARY = " Returns internal invoice records only. Invoice status and issue date do not prove external submission or delivery. " +
|
|
31
|
+
"Do not call this tool to check Peppol delivery receipts, EN 16931 validation results, or e-invoicing transmission or acceptance status. " +
|
|
32
|
+
"If the request only concerns that unavailable evidence, explain the limitation without invoking Frihet tools. " +
|
|
33
|
+
"/ Devuelve solo registros internos de facturas. El estado y la fecha de emision no prueban la presentacion o entrega externa. " +
|
|
34
|
+
"No invoques esta herramienta para comprobar acuses de entrega Peppol, resultados de validacion EN 16931 ni estados de transmision o aceptacion de facturacion electronica. " +
|
|
35
|
+
"Si la solicitud solo trata de esas evidencias no disponibles, explica la limitacion sin invocar herramientas de Frihet.";
|
|
30
36
|
const PROFILE = {
|
|
31
37
|
// -- OpenAI-reviewed core surface ----------------------------------------
|
|
32
38
|
//
|
|
@@ -125,15 +131,29 @@ const PROFILE = {
|
|
|
125
131
|
"and regulated filing data are omitted. " +
|
|
126
132
|
"/ Obtiene un contexto conciso del espacio de trabajo sin identificadores oficiales, " +
|
|
127
133
|
"direcciones precisas ni datos de declaraciones reguladas.",
|
|
134
|
+
list_invoices: "List all invoices with optional pagination and filters. " +
|
|
135
|
+
"Returns a paginated list sorted by issue date (newest first). " +
|
|
136
|
+
"Supports filtering by status (draft/sent/paid/overdue/cancelled) and date range. " +
|
|
137
|
+
"Example: status='paid', from='2026-01-01', to='2026-03-31', limit=20 " +
|
|
138
|
+
"/ Lista facturas con paginacion y filtros opcionales. " +
|
|
139
|
+
"Soporta filtrado por estado y rango de fechas." +
|
|
140
|
+
REVIEWED_INVOICE_EVIDENCE_BOUNDARY,
|
|
128
141
|
get_invoice: "Get one invoice by ID with its stored line items, linked-client context, dates, and status. " +
|
|
129
142
|
"The response contains only stored document fields; a calculated total may be absent. " +
|
|
130
|
-
"/ Obtiene una factura por ID con sus campos almacenados; el total calculado puede no estar presente."
|
|
143
|
+
"/ Obtiene una factura por ID con sus campos almacenados; el total calculado puede no estar presente." +
|
|
144
|
+
REVIEWED_INVOICE_EVIDENCE_BOUNDARY,
|
|
145
|
+
search_invoices: "Search and filter invoices. Supports filtering by status and date range. " +
|
|
146
|
+
"The query parameter searches across client names and invoice content. " +
|
|
147
|
+
"Example: query='Acme', status='paid', from='2026-01-01', to='2026-03-31' " +
|
|
148
|
+
"/ Busca y filtra facturas. Soporta filtrado por estado y rango de fechas. " +
|
|
149
|
+
"El parametro query busca en nombres de clientes y contenido de facturas." +
|
|
150
|
+
REVIEWED_INVOICE_EVIDENCE_BOUNDARY,
|
|
131
151
|
get_quote: "Get one quote by ID with its stored line items, linked-client context, dates, and status. " +
|
|
132
152
|
"The response contains only stored document fields; a calculated total may be absent. " +
|
|
133
153
|
"/ Obtiene un presupuesto por ID con sus campos almacenados; el total calculado puede no estar presente.",
|
|
134
154
|
create_invoice: "Prepare a draft invoice that reserves a document number, advances the numbering counter, and may create/link a client; it cannot issue, email, or file it. " +
|
|
135
155
|
"It requires a client name and at least one line item and always forces draft status. It cannot mark paid, cancel, hash, or submit the invoice to an external filing service. " +
|
|
136
|
-
"This invoice counts toward the workspace's monthly invoice usage and may emit invoice-creation analytics to PostHog's
|
|
156
|
+
"This invoice counts toward the workspace's monthly invoice usage and may emit invoice-creation analytics to PostHog's analytics service. Active owner-configured webhooks may receive the resulting full business events. Frihet may create in-app and Novu notifications for eligible workspace admins or accountants whose preferences allow them. For a referred workspace's first invoice, Frihet may award activation credits to the referring Frihet account. " +
|
|
137
157
|
"/ Prepara una nueva factura en borrador. Este conector siempre fuerza el estado borrador " +
|
|
138
158
|
"y reserva un numero interno; puede crear y vincular el cliente si no existe una coincidencia exacta, pero no emite, envia, marca como pagada, cancela, firma ni presenta la factura.",
|
|
139
159
|
create_expense: "Record an expense on an explicit date; its tax-deductible choice affects Frihet's accounting, files nothing, and a new vendor may persist if the expense fails. " +
|
|
@@ -214,6 +234,7 @@ const PROFILE = {
|
|
|
214
234
|
"documentNumber",
|
|
215
235
|
"poNumber",
|
|
216
236
|
"operationType",
|
|
237
|
+
"fiscalTreatment",
|
|
217
238
|
],
|
|
218
239
|
create_quote: [
|
|
219
240
|
"clientId",
|
|
@@ -281,7 +302,7 @@ const PROFILE = {
|
|
|
281
302
|
};
|
|
282
303
|
/** OAuth requirement advertised on every reviewed business tool. */
|
|
283
304
|
export const OPENAI_OAUTH_SECURITY_SCHEMES = [
|
|
284
|
-
{ type: "oauth2", scopes: [
|
|
305
|
+
{ type: "oauth2", scopes: [...OPENAI_REVIEW_OAUTH_SCOPES] },
|
|
285
306
|
];
|
|
286
307
|
/**
|
|
287
308
|
* @modelcontextprotocol/sdk 1.30 accepts unknown registration config fields,
|
|
@@ -382,13 +403,12 @@ function installOpenAIToolsListSecurityProjection(server) {
|
|
|
382
403
|
|| !isRecord(schemes[0])
|
|
383
404
|
|| schemes[0].type !== "oauth2"
|
|
384
405
|
|| !Array.isArray(schemes[0].scopes)
|
|
385
|
-
|| schemes[0].scopes
|
|
386
|
-
|| schemes[0].scopes[0] !== FRIHET_CONNECTOR_SCOPE) {
|
|
406
|
+
|| !hasExactOpenAIReviewScopes(schemes[0].scopes)) {
|
|
387
407
|
throw new Error(`OpenAI tool ${String(tool.name)} is missing the reviewed OAuth scope`);
|
|
388
408
|
}
|
|
389
409
|
const securitySchemes = [{
|
|
390
410
|
type: "oauth2",
|
|
391
|
-
scopes: [
|
|
411
|
+
scopes: [...OPENAI_REVIEW_OAUTH_SCOPES],
|
|
392
412
|
}];
|
|
393
413
|
const descriptionSources = schemaDescriptions.get(tool.name);
|
|
394
414
|
if (!descriptionSources) {
|
|
@@ -887,7 +907,7 @@ function customizeReviewedOutputValue(name, value) {
|
|
|
887
907
|
}
|
|
888
908
|
if (name === "create_invoice") {
|
|
889
909
|
effects.push("The draft counts toward the workspace's monthly invoice usage.");
|
|
890
|
-
effects.push("Frihet may have sent invoice-creation usage and activation analytics to PostHog's
|
|
910
|
+
effects.push("Frihet may have sent invoice-creation usage and activation analytics to PostHog's analytics service.");
|
|
891
911
|
effects.push("Frihet may create in-app and Novu notifications for eligible workspace admins or accountants whose preferences allow them.");
|
|
892
912
|
effects.push("If this was a referred workspace's first invoice, Frihet may have awarded activation credits to the referring Frihet account.");
|
|
893
913
|
}
|
|
@@ -1064,7 +1084,7 @@ function constrainReviewedDocumentLineItems(schema, name) {
|
|
|
1064
1084
|
}
|
|
1065
1085
|
function reviewedConfirmationDescription(name) {
|
|
1066
1086
|
if (name === "create_invoice") {
|
|
1067
|
-
return "Set true only after the user authorizes creating a numbered draft, advancing the numbering counter, consuming monthly invoice usage, sending invoice-creation analytics to PostHog's
|
|
1087
|
+
return "Set true only after the user authorizes creating a numbered draft, advancing the numbering counter, consuming monthly invoice usage, sending invoice-creation analytics to PostHog's analytics service, possibly creating/linking the client, delivering owner-configured webhooks, notifying eligible workspace admins/accountants, and possibly awarding first-use referral credits.";
|
|
1068
1088
|
}
|
|
1069
1089
|
if (name === "create_quote") {
|
|
1070
1090
|
return "Set true only after the user authorizes creating a numbered draft, advancing the numbering counter, possibly creating/linking the client, and delivering owner-configured webhooks.";
|
|
@@ -1193,7 +1213,7 @@ export function applyOpenAIProfile(server) {
|
|
|
1193
1213
|
!config.description.includes("openWorldHint")) {
|
|
1194
1214
|
const ow = config.annotations?.openWorldHint;
|
|
1195
1215
|
const openWorldRationale = name === "create_invoice"
|
|
1196
|
-
? "may send invoice analytics to PostHog's
|
|
1216
|
+
? "may send invoice analytics to PostHog's analytics service, notify eligible admins/accountants through Novu, award referral credits, and deliver full business events to owner-configured webhook endpoints"
|
|
1197
1217
|
: name === "create_expense"
|
|
1198
1218
|
? "may notify eligible admins/accountants through Novu, may award referral credits, and may deliver full business events to owner-configured webhook endpoints"
|
|
1199
1219
|
: name === "delete_quote"
|
|
@@ -1329,6 +1349,12 @@ export function applyOpenAIProfile(server) {
|
|
|
1329
1349
|
};
|
|
1330
1350
|
}
|
|
1331
1351
|
const result = await handler(handlerInput);
|
|
1352
|
+
if (result.isError === true
|
|
1353
|
+
&& result._meta?.["io.frihet/authenticationRequired"] === true) {
|
|
1354
|
+
result._meta = {
|
|
1355
|
+
"mcp/www_authenticate": [buildOpenAIUnauthorizedChallenge()],
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1332
1358
|
// A transport/body failure, a 5xx, or an idempotency-in-progress response
|
|
1333
1359
|
// after a write was sent is ambiguous: the backend may have committed it
|
|
1334
1360
|
// even though the caller did not receive a usable success response.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
9
9
|
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
10
10
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
|
-
export declare const OPENAI_REVIEW_CONTRACT_VERSION =
|
|
11
|
+
export declare const OPENAI_REVIEW_CONTRACT_VERSION = 7;
|
|
12
12
|
export declare const OPENAI_REVIEW_BUSINESS_TOOL_COUNT = 33;
|
|
13
13
|
export declare const OPENAI_REVIEW_TOTAL_TOOL_COUNT = 33;
|
|
14
14
|
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
@@ -10,9 +10,9 @@ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
|
10
10
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
11
|
import { OPENAI_REVIEW_BUSINESS_CONTEXT_TOP_CLIENTS_MAX, OPENAI_REVIEW_DOCUMENT_LINE_ITEM_MAX, OPENAI_REVIEW_FREE_TEXT_WARNING, OPENAI_REVIEW_FREE_TEXT_WARNING_PATHS, OPENAI_REVIEW_LIST_OUTPUT_FIELDS, OPENAI_REVIEW_OFFSET_MAX, OPENAI_REVIEW_PAGINATION_LIMITS, OPENAI_REVIEW_TEXT_INPUT_LIMITS, OPENAI_REVIEW_CONFIRM_REQUIRED_TOOLS, OPENAI_REVIEWED_TOOL_ALLOWLIST, OPENAI_WORKSPACE_WEBHOOK_EVENT_TOOLS, } from "./openai-profile.js";
|
|
12
12
|
import { SENSITIVE_FIELD_NAMES } from "./redaction.js";
|
|
13
|
-
import {
|
|
13
|
+
import { hasExactOpenAIReviewScopes } from "./openai-review-oauth.js";
|
|
14
14
|
import { registerMcpSurface, remoteMcpSurfaceComposition, } from "./server-composition.js";
|
|
15
|
-
export const OPENAI_REVIEW_CONTRACT_VERSION =
|
|
15
|
+
export const OPENAI_REVIEW_CONTRACT_VERSION = 7;
|
|
16
16
|
export const OPENAI_REVIEW_BUSINESS_TOOL_COUNT = 33;
|
|
17
17
|
export const OPENAI_REVIEW_TOTAL_TOOL_COUNT = OPENAI_REVIEW_BUSINESS_TOOL_COUNT;
|
|
18
18
|
const FORBIDDEN_REVIEW_TOOLS = [
|
|
@@ -296,9 +296,8 @@ function assertReviewedOAuthSecuritySchemes(value, path) {
|
|
|
296
296
|
|| Object.keys(scheme).sort().join(",") !== "scopes,type"
|
|
297
297
|
|| scheme.type !== "oauth2"
|
|
298
298
|
|| !Array.isArray(scheme.scopes)
|
|
299
|
-
|| scheme.scopes
|
|
300
|
-
|
|
301
|
-
throw new Error(`${path} must require only the reviewed connector OAuth scope`);
|
|
299
|
+
|| !hasExactOpenAIReviewScopes(scheme.scopes)) {
|
|
300
|
+
throw new Error(`${path} must require exactly the reviewed OpenAI OAuth scopes`);
|
|
302
301
|
}
|
|
303
302
|
}
|
|
304
303
|
function reviewedInputFieldSchema(tool, path) {
|
|
@@ -5,6 +5,37 @@
|
|
|
5
5
|
export declare const OPENAI_REVIEW_ORIGIN = "https://openai-mcp.frihet.io";
|
|
6
6
|
export declare const FULL_MCP_ORIGIN = "https://mcp.frihet.io";
|
|
7
7
|
export declare const FRIHET_CONNECTOR_SCOPE = "frihet:workspace.manage";
|
|
8
|
+
export declare const OPENAI_REVIEW_OAUTH_SCOPES: readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* RFC 8707 resource identifier of the reviewed MCP endpoint. Claude's
|
|
11
|
+
* connector guide requires protected-resource metadata to name the connector
|
|
12
|
+
* URL a user enters, including its path; OpenAI documents that ChatGPT takes
|
|
13
|
+
* the `resource` value it sends from that metadata.
|
|
14
|
+
*/
|
|
15
|
+
export declare const OPENAI_REVIEW_MCP_RESOURCE = "https://openai-mcp.frihet.io/mcp";
|
|
16
|
+
/** RFC 9728 section 3.1 path-inserted location of the metadata for
|
|
17
|
+
* OPENAI_REVIEW_MCP_RESOURCE. The 401 challenge points here. */
|
|
18
|
+
export declare const OPENAI_REVIEW_MCP_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource/mcp";
|
|
19
|
+
/**
|
|
20
|
+
* The only RFC 8707 resource values the reviewed authorization boundary
|
|
21
|
+
* accepts. The canonical MCP URL comes first; the bare origin is the value the
|
|
22
|
+
* root metadata document still publishes (and the only one it published before
|
|
23
|
+
* the path-inserted document existed), so clients using it keep working. Both
|
|
24
|
+
* name this Worker only: the pinned provider matches a token audience by exact
|
|
25
|
+
* origin plus path prefix, so neither value can authorize the full host.
|
|
26
|
+
* The runtime checks that use this set are validateOAuthBoundary (below) and
|
|
27
|
+
* the /token rotation guard in workers/remote-mcp/src/oauth-token-family.ts.
|
|
28
|
+
* The inventory test in workers/remote-mcp/src/__tests__/openai-review-oauth.test.ts
|
|
29
|
+
* lists every runtime line that reads a field named `resource` or `audience`
|
|
30
|
+
* (dot access, string-literal bracket access, or single-line destructuring)
|
|
31
|
+
* and fails on any new such line, whatever it compares or calls. It does not
|
|
32
|
+
* follow a value after it is copied into another variable, multi-line
|
|
33
|
+
* destructuring, or computed property names.
|
|
34
|
+
* Grant props keep `oauthResource: OPENAI_REVIEW_ORIGIN` as the host-binding
|
|
35
|
+
* identity checked at session init and API-key provisioning; that field is not
|
|
36
|
+
* the RFC 8707 value, which the provider stores on the grant itself.
|
|
37
|
+
*/
|
|
38
|
+
export declare const OPENAI_REVIEW_OAUTH_RESOURCES: readonly string[];
|
|
8
39
|
/** RFC 7636 S256 challenges are the 32-byte SHA-256 digest encoded as
|
|
9
40
|
* unpadded base64url, which is always exactly 43 characters. */
|
|
10
41
|
export declare function isValidS256CodeChallenge(value: string): boolean;
|
|
@@ -53,19 +84,33 @@ interface OAuthBoundaryParameters {
|
|
|
53
84
|
* requests may omit it because the provider reuses the validated grant. */
|
|
54
85
|
requireScope: boolean;
|
|
55
86
|
}
|
|
87
|
+
export declare function hasExactOpenAIReviewScopes(value: unknown): value is string[];
|
|
56
88
|
/**
|
|
57
89
|
* Validate the two OAuth values that form the reviewed-host authorization
|
|
58
|
-
* boundary. RFC 8707 resource comparison is exact by design:
|
|
59
|
-
* arbitrary absolute URI would let a token minted by one Frihet
|
|
60
|
-
* the other Worker. Unknown or empty scopes are rejected instead
|
|
61
|
-
* downscoping to an empty token.
|
|
90
|
+
* boundary. RFC 8707 resource comparison is exact string membership by design:
|
|
91
|
+
* accepting an arbitrary absolute URI would let a token minted by one Frihet
|
|
92
|
+
* Worker target the other Worker. Unknown or empty scopes are rejected instead
|
|
93
|
+
* of silently downscoping to an empty token.
|
|
62
94
|
*/
|
|
63
|
-
export declare function validateOAuthBoundary(parameters: OAuthBoundaryParameters,
|
|
95
|
+
export declare function validateOAuthBoundary(parameters: OAuthBoundaryParameters, acceptedResources: readonly string[]): OAuthBoundaryResult;
|
|
64
96
|
export declare function buildOpenAIUnauthorizedChallenge(origin?: string): string;
|
|
97
|
+
/**
|
|
98
|
+
* Path-inserted protected-resource metadata for the reviewed MCP endpoint,
|
|
99
|
+
* served by the Worker itself (the pinned provider only serves the root
|
|
100
|
+
* document). It differs from the root document only in `resource`.
|
|
101
|
+
*/
|
|
102
|
+
export declare function buildReviewedMcpProtectedResourceMetadata(origin?: string): {
|
|
103
|
+
resource: string;
|
|
104
|
+
authorization_servers: string[];
|
|
105
|
+
scopes_supported: string[];
|
|
106
|
+
bearer_methods_supported: string[];
|
|
107
|
+
resource_name: string;
|
|
108
|
+
};
|
|
65
109
|
/**
|
|
66
110
|
* Materialize the public metadata generated by workers-oauth-provider 0.3.0
|
|
67
|
-
* from the same options used by the Worker
|
|
68
|
-
*
|
|
111
|
+
* from the same options used by the Worker, plus the path-inserted document
|
|
112
|
+
* the Worker serves itself. The dependency version is pinned separately in
|
|
113
|
+
* the canonical descriptor snapshot.
|
|
69
114
|
*/
|
|
70
115
|
export declare function buildOpenAIReviewOAuthContract(origin?: string): {
|
|
71
116
|
authorizationServer: {
|
|
@@ -89,10 +134,65 @@ export declare function buildOpenAIReviewOAuthContract(origin?: string): {
|
|
|
89
134
|
bearer_methods_supported: string[];
|
|
90
135
|
resource_name: string;
|
|
91
136
|
};
|
|
137
|
+
protectedResourceMcp: {
|
|
138
|
+
resource: string;
|
|
139
|
+
authorization_servers: string[];
|
|
140
|
+
scopes_supported: string[];
|
|
141
|
+
bearer_methods_supported: string[];
|
|
142
|
+
resource_name: string;
|
|
143
|
+
};
|
|
144
|
+
openidConfiguration: {
|
|
145
|
+
issuer: string;
|
|
146
|
+
authorization_endpoint: string;
|
|
147
|
+
token_endpoint: string;
|
|
148
|
+
userinfo_endpoint: string;
|
|
149
|
+
registration_endpoint: string;
|
|
150
|
+
scopes_supported: string[];
|
|
151
|
+
response_types_supported: string[];
|
|
152
|
+
response_modes_supported: string[];
|
|
153
|
+
grant_types_supported: string[];
|
|
154
|
+
subject_types_supported: string[];
|
|
155
|
+
token_endpoint_auth_methods_supported: string[];
|
|
156
|
+
code_challenge_methods_supported: string[];
|
|
157
|
+
};
|
|
92
158
|
wwwAuthenticate: {
|
|
93
159
|
resourceMetadataUrl: string;
|
|
94
160
|
missingTokenHeader: string;
|
|
95
161
|
};
|
|
96
162
|
};
|
|
163
|
+
interface ReviewedAccessTokenSummary {
|
|
164
|
+
userId?: unknown;
|
|
165
|
+
expiresAt?: unknown;
|
|
166
|
+
audience?: unknown;
|
|
167
|
+
scope?: unknown;
|
|
168
|
+
grant?: {
|
|
169
|
+
scope?: unknown;
|
|
170
|
+
props?: unknown;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export declare function isVerifiedOpenAIIdentity(value: {
|
|
174
|
+
uid?: unknown;
|
|
175
|
+
email?: unknown;
|
|
176
|
+
emailVerified?: unknown;
|
|
177
|
+
}): value is {
|
|
178
|
+
uid: string;
|
|
179
|
+
email: string;
|
|
180
|
+
emailVerified: true;
|
|
181
|
+
};
|
|
182
|
+
export type OpenAIUserInfo = {
|
|
183
|
+
sub: string;
|
|
184
|
+
email: string;
|
|
185
|
+
email_verified: true;
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Validate the provider's opaque access-token summary before serving OIDC
|
|
189
|
+
* UserInfo. The provider already verifies the encrypted token and KV expiry;
|
|
190
|
+
* this second boundary deliberately rechecks every OpenAI-specific binding.
|
|
191
|
+
*/
|
|
192
|
+
export declare function buildOpenAIUserInfo(token: ReviewedAccessTokenSummary | null, nowEpochSeconds?: number, boundary?: Readonly<{
|
|
193
|
+
accessProfile: "openai" | "claude";
|
|
194
|
+
origin: string;
|
|
195
|
+
acceptedResources: readonly string[];
|
|
196
|
+
}>): OpenAIUserInfo | undefined;
|
|
97
197
|
export {};
|
|
98
198
|
//# sourceMappingURL=openai-review-oauth.d.ts.map
|