@frihet/mcp-server 1.16.6 → 2.0.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 +168 -82
- 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 +552 -0
- package/dist/capability-truth.d.ts +29 -0
- package/dist/capability-truth.js +262 -0
- 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 +240 -0
- package/dist/claude-profile.d.ts +18 -0
- package/dist/claude-profile.js +260 -0
- package/dist/client-interface.d.ts +47 -33
- package/dist/client.d.ts +172 -40
- package/dist/client.js +708 -165
- package/dist/demo-client.d.ts +41 -32
- package/dist/demo-client.js +202 -31
- package/dist/demo-fixtures.d.ts +3 -3
- package/dist/demo-fixtures.js +7 -7
- 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 +44 -0
- package/dist/fiscal-aliases.js +62 -0
- 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 +68 -59
- package/dist/logger.d.ts +2 -2
- package/dist/logger.js +140 -8
- package/dist/muse-profile.d.ts +19 -0
- package/dist/muse-profile.js +122 -0
- package/dist/observability.d.ts +18 -29
- package/dist/observability.js +194 -106
- package/dist/openai-profile.d.ts +26 -22
- package/dist/openai-profile.js +1193 -157
- package/dist/openai-review-contract.d.ts +15 -6
- package/dist/openai-review-contract.js +714 -41
- package/dist/openai-review-oauth.d.ts +149 -2
- package/dist/openai-review-oauth.js +220 -6
- package/dist/prompts/register-all.d.ts +2 -0
- package/dist/prompts/register-all.js +34 -78
- package/dist/public-capability-contract.d.ts +39 -0
- package/dist/public-capability-contract.js +186 -0
- package/dist/redaction.d.ts +15 -0
- package/dist/redaction.js +83 -0
- package/dist/resources/register-all.js +154 -64
- package/dist/retry-after.d.ts +19 -0
- package/dist/retry-after.js +84 -0
- package/dist/server-composition.d.ts +19 -0
- package/dist/server-composition.js +74 -0
- package/dist/tool-exposure.d.ts +31 -27
- package/dist/tool-exposure.js +211 -87
- package/dist/tools/accountingClose.d.ts +14 -5
- package/dist/tools/accountingClose.js +41 -23
- 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 +261 -15
- package/dist/tools/crm.js +22 -21
- package/dist/tools/deposits.js +114 -85
- package/dist/tools/einvoice.d.ts +16 -2
- package/dist/tools/einvoice.js +59 -12
- package/dist/tools/expenses.d.ts +16 -0
- package/dist/tools/expenses.js +47 -27
- 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.d.ts +2 -3
- package/dist/tools/hr.js +49 -20
- 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 +166 -12
- package/dist/tools/invoices.js +224 -54
- package/dist/tools/kitchen.js +19 -9
- 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 +172 -26
- 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 +1445 -109
- package/dist/tools/shared.js +478 -102
- package/dist/tools/team.js +24 -28
- package/dist/tools/time.js +4 -12
- package/dist/tools/vendors.js +249 -15
- package/dist/tools/webhooks.js +35 -30
- package/dist/types.d.ts +31 -5
- package/dist/workflows/daily-operational-brief.d.ts +163 -0
- package/dist/workflows/daily-operational-brief.js +881 -0
- package/docs/agent-onboarding.json +391 -0
- package/package.json +49 -12
- package/dist/tools/onboarding.d.ts +0 -19
- package/dist/tools/onboarding.js +0 -64
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
2
|
+
/** Validate a manual-mode fetch response without owning any network authority. */
|
|
3
|
+
export async function rejectRedirectResponse(response) {
|
|
4
|
+
if (REDIRECT_STATUSES.has(response.status)) {
|
|
5
|
+
try {
|
|
6
|
+
await response.body?.cancel();
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
/* Best effort; still reject. */
|
|
10
|
+
}
|
|
11
|
+
// Never include Location, URL, headers or body in the error.
|
|
12
|
+
throw new TypeError("Redirect responses are not allowed");
|
|
13
|
+
}
|
|
14
|
+
return response;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=fetch-no-redirect.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fiscal `modelo` tool naming aliases — prefix-drift fix (issue #50).
|
|
3
|
+
*
|
|
4
|
+
* ── Problem ──────────────────────────────────────────────────────────────
|
|
5
|
+
* The `modelo` fiscal tools split 5/5 on naming prefix: the mainland AEAT
|
|
6
|
+
* models (`fiscal.ts`) use `get_modelo_*`, while the Canary IGIC + Impuesto
|
|
7
|
+
* Sociedades models (`igic.ts`, `impuesto_sociedades.ts`) use the house
|
|
8
|
+
* `frihet_*` convention. An agent that learns the `frihet_modelo_NNN_summary`
|
|
9
|
+
* pattern from IGIC/IS will guess `frihet_modelo_303_summary` for VAT and get
|
|
10
|
+
* tool-not-found — 303/130/347 are the most-used models.
|
|
11
|
+
*
|
|
12
|
+
* ── Fix ──────────────────────────────────────────────────────────────────
|
|
13
|
+
* Register thin NAME ALIASES that resolve to the exact same registered tool
|
|
14
|
+
* (same handler, same schema, same annotations — zero semantic divergence),
|
|
15
|
+
* WITHOUT calling `registerTool()` a second time. The MCP SDK's `McpServer`
|
|
16
|
+
* resolves `tools/call` by looking up `this._registeredTools[name]`
|
|
17
|
+
* (mcp.js: `setToolRequestHandlers` -> `CallToolRequestSchema` handler), so
|
|
18
|
+
* pointing an alias key at the SAME registered-tool object makes it callable
|
|
19
|
+
* with zero duplication and zero drift risk.
|
|
20
|
+
*
|
|
21
|
+
* This intentionally does NOT touch `src/tools/*.ts` (where the 158-tool
|
|
22
|
+
* audit / pin tests count literal `registerTool(` call sites) and is applied
|
|
23
|
+
* to the REAL `McpServer` instance only, AFTER `registerAllTools()` — the
|
|
24
|
+
* `tool-exposure.test.ts` / `openai-*.test.ts` suites exercise
|
|
25
|
+
* `registerAllTools()` against a bare `StubMcpServer` and never call
|
|
26
|
+
* `applyFiscalAliases`, so the 158-tool pins are untouched by design.
|
|
27
|
+
*
|
|
28
|
+
* KNOWN LIMITATION: this relies on the SDK's internal (unprefixed-by-type,
|
|
29
|
+
* but underscore-private) `_registeredTools` field. It is FAIL-OPEN — if a
|
|
30
|
+
* future SDK release renames/removes that field, `applyFiscalAliases` is a
|
|
31
|
+
* silent no-op (the canonical `get_modelo_*` names keep working; only the
|
|
32
|
+
* alias convenience is lost). Pinned by `fiscal-aliases.test.ts`.
|
|
33
|
+
*/
|
|
34
|
+
/** alias name -> canonical registered tool name. */
|
|
35
|
+
export declare const FISCAL_MODELO_ALIASES: Readonly<Record<string, string>>;
|
|
36
|
+
/**
|
|
37
|
+
* Apply the fiscal `modelo` aliases to a live `McpServer` instance. Call this
|
|
38
|
+
* AFTER `registerAllTools()` so every canonical tool already exists.
|
|
39
|
+
*
|
|
40
|
+
* Fail-open: if the SDK's internal registry is missing or reshaped, this is a
|
|
41
|
+
* silent no-op — it never throws, and never blocks server startup.
|
|
42
|
+
*/
|
|
43
|
+
export declare function applyFiscalAliases(server: unknown): void;
|
|
44
|
+
//# sourceMappingURL=fiscal-aliases.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fiscal `modelo` tool naming aliases — prefix-drift fix (issue #50).
|
|
3
|
+
*
|
|
4
|
+
* ── Problem ──────────────────────────────────────────────────────────────
|
|
5
|
+
* The `modelo` fiscal tools split 5/5 on naming prefix: the mainland AEAT
|
|
6
|
+
* models (`fiscal.ts`) use `get_modelo_*`, while the Canary IGIC + Impuesto
|
|
7
|
+
* Sociedades models (`igic.ts`, `impuesto_sociedades.ts`) use the house
|
|
8
|
+
* `frihet_*` convention. An agent that learns the `frihet_modelo_NNN_summary`
|
|
9
|
+
* pattern from IGIC/IS will guess `frihet_modelo_303_summary` for VAT and get
|
|
10
|
+
* tool-not-found — 303/130/347 are the most-used models.
|
|
11
|
+
*
|
|
12
|
+
* ── Fix ──────────────────────────────────────────────────────────────────
|
|
13
|
+
* Register thin NAME ALIASES that resolve to the exact same registered tool
|
|
14
|
+
* (same handler, same schema, same annotations — zero semantic divergence),
|
|
15
|
+
* WITHOUT calling `registerTool()` a second time. The MCP SDK's `McpServer`
|
|
16
|
+
* resolves `tools/call` by looking up `this._registeredTools[name]`
|
|
17
|
+
* (mcp.js: `setToolRequestHandlers` -> `CallToolRequestSchema` handler), so
|
|
18
|
+
* pointing an alias key at the SAME registered-tool object makes it callable
|
|
19
|
+
* with zero duplication and zero drift risk.
|
|
20
|
+
*
|
|
21
|
+
* This intentionally does NOT touch `src/tools/*.ts` (where the 158-tool
|
|
22
|
+
* audit / pin tests count literal `registerTool(` call sites) and is applied
|
|
23
|
+
* to the REAL `McpServer` instance only, AFTER `registerAllTools()` — the
|
|
24
|
+
* `tool-exposure.test.ts` / `openai-*.test.ts` suites exercise
|
|
25
|
+
* `registerAllTools()` against a bare `StubMcpServer` and never call
|
|
26
|
+
* `applyFiscalAliases`, so the 158-tool pins are untouched by design.
|
|
27
|
+
*
|
|
28
|
+
* KNOWN LIMITATION: this relies on the SDK's internal (unprefixed-by-type,
|
|
29
|
+
* but underscore-private) `_registeredTools` field. It is FAIL-OPEN — if a
|
|
30
|
+
* future SDK release renames/removes that field, `applyFiscalAliases` is a
|
|
31
|
+
* silent no-op (the canonical `get_modelo_*` names keep working; only the
|
|
32
|
+
* alias convenience is lost). Pinned by `fiscal-aliases.test.ts`.
|
|
33
|
+
*/
|
|
34
|
+
/** alias name -> canonical registered tool name. */
|
|
35
|
+
export const FISCAL_MODELO_ALIASES = {
|
|
36
|
+
frihet_modelo_303_summary: "get_modelo_303_summary",
|
|
37
|
+
frihet_modelo_130_summary: "get_modelo_130_summary",
|
|
38
|
+
frihet_modelo_390_summary: "get_modelo_390_summary",
|
|
39
|
+
frihet_modelo_180_summary: "get_modelo_180_summary",
|
|
40
|
+
frihet_modelo_347_summary: "get_modelo_347_summary",
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Apply the fiscal `modelo` aliases to a live `McpServer` instance. Call this
|
|
44
|
+
* AFTER `registerAllTools()` so every canonical tool already exists.
|
|
45
|
+
*
|
|
46
|
+
* Fail-open: if the SDK's internal registry is missing or reshaped, this is a
|
|
47
|
+
* silent no-op — it never throws, and never blocks server startup.
|
|
48
|
+
*/
|
|
49
|
+
export function applyFiscalAliases(server) {
|
|
50
|
+
const registry = server._registeredTools;
|
|
51
|
+
if (!registry || typeof registry !== "object")
|
|
52
|
+
return;
|
|
53
|
+
for (const [alias, canonical] of Object.entries(FISCAL_MODELO_ALIASES)) {
|
|
54
|
+
const target = registry[canonical];
|
|
55
|
+
if (!target)
|
|
56
|
+
continue; // Canonical tool not registered in this profile (e.g. an allow-listed mode) — skip, no divergence.
|
|
57
|
+
if (registry[alias])
|
|
58
|
+
continue; // Never overwrite an existing registration.
|
|
59
|
+
registry[alias] = target; // Same object reference as the canonical tool — zero semantic divergence.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=fiscal-aliases.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Period contract for the READ-ONLY fiscal modelo summaries.
|
|
3
|
+
*
|
|
4
|
+
* Single source for BOTH the client (which query param to send) and the tools
|
|
5
|
+
* (what to validate before calling, what to compare after). Mirrors the
|
|
6
|
+
* deployed Frihet-ERP backend (functions/src/publicApi.ts):
|
|
7
|
+
* - 303/130 read `?quarter=YYYY-Q[1-4]`, default current quarter
|
|
8
|
+
* - 390 reads `?year=YYYY`, default current year
|
|
9
|
+
* - 347 reads `?year=YYYY`, default current year
|
|
10
|
+
* Every other modelo code has no backend route (404 "Unknown fiscal model").
|
|
11
|
+
* The backend silently answers the CURRENT period for any param it does not
|
|
12
|
+
* read, so the param name here is load-bearing.
|
|
13
|
+
*/
|
|
14
|
+
export interface FiscalPeriodRule {
|
|
15
|
+
/** Query param the backend reads. */
|
|
16
|
+
param: "quarter" | "year";
|
|
17
|
+
pattern: RegExp;
|
|
18
|
+
format: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const FISCAL_PERIOD_RULES: Readonly<Record<string, FiscalPeriodRule>>;
|
|
21
|
+
/**
|
|
22
|
+
* Query object for GET /fiscal/modelo/{code}. Throws for a modelo code without
|
|
23
|
+
* a backend rule so no caller can fall back to an ignored `?period=` param.
|
|
24
|
+
*/
|
|
25
|
+
export declare function fiscalModeloQuery(modeloCode: string, period?: string): Record<string, string | undefined>;
|
|
26
|
+
//# sourceMappingURL=fiscal-period.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Period contract for the READ-ONLY fiscal modelo summaries.
|
|
3
|
+
*
|
|
4
|
+
* Single source for BOTH the client (which query param to send) and the tools
|
|
5
|
+
* (what to validate before calling, what to compare after). Mirrors the
|
|
6
|
+
* deployed Frihet-ERP backend (functions/src/publicApi.ts):
|
|
7
|
+
* - 303/130 read `?quarter=YYYY-Q[1-4]`, default current quarter
|
|
8
|
+
* - 390 reads `?year=YYYY`, default current year
|
|
9
|
+
* - 347 reads `?year=YYYY`, default current year
|
|
10
|
+
* Every other modelo code has no backend route (404 "Unknown fiscal model").
|
|
11
|
+
* The backend silently answers the CURRENT period for any param it does not
|
|
12
|
+
* read, so the param name here is load-bearing.
|
|
13
|
+
*/
|
|
14
|
+
export const FISCAL_PERIOD_RULES = {
|
|
15
|
+
"303": { param: "quarter", pattern: /^\d{4}-Q[1-4]$/, format: "YYYY-Q1..YYYY-Q4" },
|
|
16
|
+
"130": { param: "quarter", pattern: /^\d{4}-Q[1-4]$/, format: "YYYY-Q1..YYYY-Q4" },
|
|
17
|
+
"390": { param: "year", pattern: /^\d{4}$/, format: "YYYY" },
|
|
18
|
+
"347": { param: "year", pattern: /^\d{4}$/, format: "YYYY" },
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Query object for GET /fiscal/modelo/{code}. Throws for a modelo code without
|
|
22
|
+
* a backend rule so no caller can fall back to an ignored `?period=` param.
|
|
23
|
+
*/
|
|
24
|
+
export function fiscalModeloQuery(modeloCode, period) {
|
|
25
|
+
const rule = FISCAL_PERIOD_RULES[modeloCode];
|
|
26
|
+
if (!rule) {
|
|
27
|
+
throw new Error(`No deployed fiscal summary backend for Modelo ${modeloCode}`);
|
|
28
|
+
}
|
|
29
|
+
return { [rule.param]: period };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=fiscal-period.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Connected Apps Profile for the Frihet MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Implements the curated 18-tool financial management surface submitted
|
|
5
|
+
* for the Google Connected Apps Platform Developer Preview (Gemini app & Google Search).
|
|
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 GOOGLE_CANDIDATE_TOOLS: ReadonlySet<string>;
|
|
14
|
+
export declare const GOOGLE_CANDIDATE_TOOL_COUNT: number;
|
|
15
|
+
export declare const GOOGLE_INSTRUCTIONS = "Frihet ERP \u2014 natural-language business and financial management for Google Connected Apps (Gemini & Search).\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 googleToolResult(result: unknown): unknown;
|
|
17
|
+
export declare const GOOGLE_FORCED_INPUT_VALUES: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
18
|
+
export declare function applyGoogleProfile(server: any): void;
|
|
19
|
+
//# sourceMappingURL=google-profile.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Connected Apps Profile for the Frihet MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Implements the curated 18-tool financial management surface submitted
|
|
5
|
+
* for the Google Connected Apps Platform Developer Preview (Gemini app & Google Search).
|
|
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 GOOGLE_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 GOOGLE_CANDIDATE_TOOL_COUNT = GOOGLE_CANDIDATE_TOOLS.size; // 18
|
|
35
|
+
export const GOOGLE_INSTRUCTIONS = `Frihet ERP — natural-language business and financial management for Google Connected Apps (Gemini & Search).
|
|
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 googleToolResult(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 GOOGLE_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 applyGoogleProfile(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 (!GOOGLE_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} [Google Connected Apps: 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} [Google Connected Apps: 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 = GOOGLE_FORCED_INPUT_VALUES[name];
|
|
103
|
+
if (forced) {
|
|
104
|
+
input = { ...input, ...forced };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const res = await handler(input, ...args.slice(1));
|
|
108
|
+
return googleToolResult(res);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return googleToolResult({
|
|
112
|
+
isError: true,
|
|
113
|
+
_meta: { "io.frihet/operationOutcomeUnknown": true },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
// Google Connected Apps profile intentionally registers 0 raw prompts or dynamic resources
|
|
119
|
+
server.registerResource = () => undefined;
|
|
120
|
+
server.registerPrompt = () => undefined;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=google-profile.js.map
|
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,15 +29,26 @@ const PKG_VERSION = (() => {
|
|
|
28
29
|
return "0.0.0";
|
|
29
30
|
}
|
|
30
31
|
})();
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import { resolveToolMode,
|
|
32
|
+
import { OPENAI_ALLOWED_TOOL_COUNT, OPENAI_EXCLUDED_COUNT, OPENAI_EXCLUDED_RESOURCE_COUNT } from "./openai-profile.js";
|
|
33
|
+
import { CLAUDE_CANDIDATE_INSTRUCTIONS, CLAUDE_CANDIDATE_TOOLS } from "./claude-profile.js";
|
|
34
|
+
import { GOOGLE_INSTRUCTIONS, GOOGLE_CANDIDATE_TOOL_COUNT } from "./google-profile.js";
|
|
35
|
+
import { MUSE_INSTRUCTIONS, MUSE_CANDIDATE_TOOL_COUNT } from "./muse-profile.js";
|
|
36
|
+
import { resolveToolMode, GROUPED_META_TOOL_COUNT } from "./tool-exposure.js";
|
|
37
|
+
import { localMcpSurfaceComposition, registerMcpSurface, } from "./server-composition.js";
|
|
38
|
+
import { AGENT_SERVER_INSTRUCTIONS, API_KEY_LOCATION, } from "./agent-onboarding.js";
|
|
36
39
|
import { log } from "./logger.js";
|
|
37
40
|
import { registerShutdownHook } from "./metrics.js";
|
|
38
41
|
import { setTraceContext } from "./observability.js";
|
|
39
42
|
function main() {
|
|
43
|
+
const openaiMode = process.env.FRIHET_OPENAI_MODE === "true";
|
|
44
|
+
const claudeMode = process.env.FRIHET_CLAUDE_MODE === "true";
|
|
45
|
+
const googleMode = process.env.FRIHET_GOOGLE_MODE === "true";
|
|
46
|
+
const museMode = process.env.FRIHET_MUSE_MODE === "true";
|
|
47
|
+
const activeModes = [openaiMode, claudeMode, googleMode, museMode].filter(Boolean).length;
|
|
48
|
+
if (activeModes > 1) {
|
|
49
|
+
console.error("Error: FRIHET_OPENAI_MODE, FRIHET_CLAUDE_MODE, FRIHET_GOOGLE_MODE, and FRIHET_MUSE_MODE are mutually exclusive");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
40
52
|
const apiKey = process.env.FRIHET_API_KEY;
|
|
41
53
|
// Demo mode: FRIHET_DEMO=1|true serves fixture-backed example data with NO
|
|
42
54
|
// network calls and NO API key required. It's an explicit opt-in, so it wins
|
|
@@ -44,33 +56,40 @@ function main() {
|
|
|
44
56
|
const demoFlag = process.env.FRIHET_DEMO;
|
|
45
57
|
const demoMode = demoFlag === "1" || demoFlag === "true";
|
|
46
58
|
if (!demoMode && !apiKey) {
|
|
59
|
+
// Human-readable first, then one machine-readable line. An agent that only
|
|
60
|
+
// gets prose here has to guess; the JSON line gives it a code to branch on
|
|
61
|
+
// and the two recoveries that actually exist. /settings/api is the real,
|
|
62
|
+
// routable screen — there is no "Developers" section in the app.
|
|
47
63
|
console.error("Error: FRIHET_API_KEY environment variable is required.\n\n" +
|
|
48
64
|
"Get your API key:\n" +
|
|
49
65
|
" 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" +
|
|
66
|
+
" 2. Open https://app.frihet.io/settings/api\n" +
|
|
67
|
+
" 3. Create a key (it starts with fri_) and add it to your MCP configuration\n\n" +
|
|
52
68
|
"Or try it instantly with no key: set FRIHET_DEMO=1 for example data.\n\n" +
|
|
53
69
|
"Documentation: https://docs.frihet.io/desarrolladores/mcp-server\n");
|
|
70
|
+
console.error(JSON.stringify({
|
|
71
|
+
error: {
|
|
72
|
+
code: "FRIHET_API_KEY_MISSING",
|
|
73
|
+
message: "FRIHET_API_KEY environment variable is required",
|
|
74
|
+
obtainAt: API_KEY_LOCATION,
|
|
75
|
+
recovery: [
|
|
76
|
+
{ action: "set_env", env: { FRIHET_API_KEY: "fri_<key>" } },
|
|
77
|
+
{ action: "run_without_credentials", env: { FRIHET_DEMO: "1" } },
|
|
78
|
+
],
|
|
79
|
+
docs: "https://docs.frihet.io/desarrolladores/mcp-server",
|
|
80
|
+
},
|
|
81
|
+
}));
|
|
54
82
|
process.exit(1);
|
|
55
83
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
84
|
+
const baseUrlRaw = process.env.FRIHET_API_URL;
|
|
85
|
+
let baseUrl;
|
|
86
|
+
if (!demoMode && baseUrlRaw !== undefined) {
|
|
59
87
|
try {
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
console.error(`Error: FRIHET_API_URL is not a valid URL: "${baseUrl}"\n` +
|
|
64
|
-
"It must be a valid https:// URL with a frihet.io hostname.\n");
|
|
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);
|
|
88
|
+
baseUrl = normalizePublicApiBaseUrl(baseUrlRaw);
|
|
70
89
|
}
|
|
71
|
-
|
|
72
|
-
console.error(`Error:
|
|
73
|
-
"
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error(`Error: ${error instanceof Error ? error.message : "FRIHET_API_URL is invalid"}.\n` +
|
|
92
|
+
"It must be a canonical https:// URL with a trusted frihet.io hostname.\n");
|
|
74
93
|
process.exit(1);
|
|
75
94
|
}
|
|
76
95
|
}
|
|
@@ -80,69 +99,59 @@ function main() {
|
|
|
80
99
|
if (demoMode) {
|
|
81
100
|
console.error("[frihet-mcp] DEMO MODE — serving example fixtures, no real data, nothing persisted");
|
|
82
101
|
}
|
|
83
|
-
// Set
|
|
84
|
-
//
|
|
102
|
+
// Set the fixed protocol fact for Langfuse (reads LANGFUSE_* from process.env automatically).
|
|
103
|
+
// Client identity is deliberately omitted from telemetry.
|
|
85
104
|
setTraceContext({
|
|
86
105
|
mcpVersion: "mcp/1.0",
|
|
87
|
-
clientName: process.env.FRIHET_CLIENT_NAME,
|
|
88
106
|
});
|
|
89
107
|
const server = new McpServer({
|
|
90
108
|
name: "frihet-erp",
|
|
91
109
|
version: PKG_VERSION,
|
|
92
|
-
description:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
110
|
+
description: claudeMode
|
|
111
|
+
? `Local Claude candidate for Frihet: ${CLAUDE_CANDIDATE_TOOLS.size} canonical operations with reviewed fiscal aliases, 1 currencies resource and 2 receivables prompts. Optional grouped discovery adds 3 names. API-dependent operations require live workspace verification; this is not a published Directory connector.`
|
|
112
|
+
: googleMode
|
|
113
|
+
? `Frihet ERP profile for Google Connected Apps (Gemini & Search): ${GOOGLE_CANDIDATE_TOOL_COUNT} canonical operations with draft-only human-in-the-loop protection.`
|
|
114
|
+
: museMode
|
|
115
|
+
? `Frihet ERP connector for Meta Muse: ${MUSE_CANDIDATE_TOOL_COUNT} canonical operations with draft-only human-in-the-loop protection.`
|
|
116
|
+
:
|
|
117
|
+
"AI-native MCP server for Frihet ERP — invoices, expenses, clients, products, quotes, webhooks, and deposits. " +
|
|
118
|
+
"Provides a catalogue of 158 canonical operations; fiscal aliases and optional grouped discovery names are reported separately. " +
|
|
119
|
+
"The local package serves 11 resources (7 static + 4 API-backed) and 10 workflow prompts " +
|
|
120
|
+
"with full Spanish tax compliance (IVA, IGIC, IPSI).",
|
|
121
|
+
}, {
|
|
122
|
+
// Delivered by `initialize` to every MCP client, which hands it to the model
|
|
123
|
+
// before the first tool call. This is the only onboarding channel that costs
|
|
124
|
+
// the operator zero configuration. Source: src/agent-onboarding.ts.
|
|
125
|
+
instructions: claudeMode
|
|
126
|
+
? CLAUDE_CANDIDATE_INSTRUCTIONS
|
|
127
|
+
: googleMode
|
|
128
|
+
? GOOGLE_INSTRUCTIONS
|
|
129
|
+
: museMode
|
|
130
|
+
? MUSE_INSTRUCTIONS
|
|
131
|
+
: AGENT_SERVER_INSTRUCTIONS,
|
|
96
132
|
});
|
|
97
|
-
const openaiMode = process.env.FRIHET_OPENAI_MODE === "true";
|
|
98
133
|
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
134
|
if (toolMode === "grouped") {
|
|
118
|
-
applyToolExposureProfile(server, openaiMode ? { allowlist: OPENAI_REVIEWED_TOOL_ALLOWLIST } : undefined);
|
|
119
135
|
log({
|
|
120
136
|
level: "info",
|
|
121
137
|
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
138
|
operation: "startup",
|
|
123
139
|
});
|
|
124
140
|
}
|
|
125
|
-
// Apply OpenAI-safe profile if enabled (strips sensitive fields, fixes annotations)
|
|
126
141
|
if (openaiMode) {
|
|
127
|
-
applyOpenAIProfile(server);
|
|
128
142
|
log({
|
|
129
143
|
level: "info",
|
|
130
144
|
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
145
|
operation: "startup",
|
|
132
146
|
});
|
|
133
147
|
}
|
|
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);
|
|
148
|
+
registerMcpSurface(server, client, localMcpSurfaceComposition(openaiMode, toolMode === "grouped", claudeMode, googleMode, museMode));
|
|
140
149
|
// Register shutdown hook to log final metrics summary
|
|
141
150
|
registerShutdownHook();
|
|
142
151
|
// Connect via stdio transport
|
|
143
152
|
const transport = new StdioServerTransport();
|
|
144
153
|
server.connect(transport).then(() => {
|
|
145
|
-
console.error(`[frihet-mcp] v${PKG_VERSION} |
|
|
154
|
+
console.error(`[frihet-mcp] v${PKG_VERSION} | capability truth enabled | https://github.com/Frihet-io/frihet-mcp`);
|
|
146
155
|
log({
|
|
147
156
|
level: "info",
|
|
148
157
|
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, delaySource?: "retry_after" | "backoff"): void;
|
|
45
45
|
export {};
|
|
46
46
|
//# sourceMappingURL=logger.d.ts.map
|