@frihet/mcp-server 2.0.0 → 2.0.2
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 +96 -363
- package/dist/canary/mcp-harness.d.ts +1 -1
- package/dist/canary/mcp-harness.js +3 -2
- package/dist/claude-output-privacy.js +9 -0
- package/dist/claude-profile.js +3 -1
- package/dist/client-interface.d.ts +7 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +16 -0
- package/dist/demo-client.d.ts +15 -16
- package/dist/demo-client.js +102 -30
- package/dist/demo-fixtures.d.ts +14 -4
- package/dist/demo-fixtures.js +84 -16
- package/dist/invoice-payment-link.d.ts +189 -0
- package/dist/invoice-payment-link.js +371 -0
- package/dist/mpp-sandbox.d.ts +176 -0
- package/dist/mpp-sandbox.js +612 -0
- package/dist/openai-103/openai-expansion-103.d.ts +1192 -0
- package/dist/openai-103/openai-expansion-103.js +1628 -0
- package/dist/openai-profile.d.ts +5 -1
- package/dist/openai-profile.js +14 -2
- package/dist/openai-review-contract.js +2 -1
- package/dist/server-composition.d.ts +1 -0
- package/dist/server-composition.js +45 -0
- package/dist/tool-exposure.d.ts +1 -0
- package/dist/tool-exposure.js +10 -3
- package/dist/tools/clients.js +2 -0
- package/dist/tools/purchaseOrders.d.ts +30 -0
- package/dist/tools/purchaseOrders.js +102 -0
- package/dist/tools/register-all.js +2 -0
- package/dist/tools/shared.d.ts +43 -1
- package/dist/tools/shared.js +46 -0
- package/dist/tools/vendors.js +2 -0
- package/docs/agent-onboarding.json +2 -2
- package/package.json +9 -9
package/dist/openai-profile.d.ts
CHANGED
|
@@ -26,6 +26,10 @@ export declare const OPENAI_OAUTH_SECURITY_SCHEMES: readonly [{
|
|
|
26
26
|
readonly type: "oauth2";
|
|
27
27
|
readonly scopes: readonly string[];
|
|
28
28
|
}];
|
|
29
|
+
export declare const OPENAI_103_READ_ONLY_CANDIDATE_TOOLS: ReadonlySet<string>;
|
|
30
|
+
export interface ApplyOpenAIProfileOptions {
|
|
31
|
+
candidateVersion?: "1.0.2" | "1.0.3";
|
|
32
|
+
}
|
|
29
33
|
export declare const OPENAI_WORKSPACE_WEBHOOK_EVENT_TOOLS: ReadonlySet<string>;
|
|
30
34
|
export declare const OPENAI_REVIEW_CONFIRM_REQUIRED_TOOLS: ReadonlySet<string>;
|
|
31
35
|
/** Strictly necessary row fields for reviewed collection/search responses. */
|
|
@@ -62,7 +66,7 @@ export declare const OPENAI_CSP: string;
|
|
|
62
66
|
* registerAllResources(server, client);
|
|
63
67
|
* ```
|
|
64
68
|
*/
|
|
65
|
-
export declare function applyOpenAIProfile(server: any): void;
|
|
69
|
+
export declare function applyOpenAIProfile(server: any, options?: ApplyOpenAIProfileOptions): void;
|
|
66
70
|
/** Number of tools excluded in OpenAI mode (for logging). */
|
|
67
71
|
export declare const OPENAI_EXCLUDED_COUNT: number;
|
|
68
72
|
/** Number of tools explicitly allowed in OpenAI mode. */
|
package/dist/openai-profile.js
CHANGED
|
@@ -429,6 +429,12 @@ function installOpenAIToolsListSecurityProjection(server) {
|
|
|
429
429
|
});
|
|
430
430
|
};
|
|
431
431
|
}
|
|
432
|
+
export const OPENAI_103_READ_ONLY_CANDIDATE_TOOLS = new Set([
|
|
433
|
+
"get_daily_operational_brief",
|
|
434
|
+
"get_business_period_summary",
|
|
435
|
+
"search_clients",
|
|
436
|
+
"search_vendors",
|
|
437
|
+
]);
|
|
432
438
|
export const OPENAI_WORKSPACE_WEBHOOK_EVENT_TOOLS = new Set([
|
|
433
439
|
"create_invoice",
|
|
434
440
|
"create_expense",
|
|
@@ -1167,15 +1173,21 @@ export const OPENAI_CSP = "default-src 'self'; " +
|
|
|
1167
1173
|
* ```
|
|
1168
1174
|
*/
|
|
1169
1175
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1170
|
-
export function applyOpenAIProfile(server) {
|
|
1176
|
+
export function applyOpenAIProfile(server, options) {
|
|
1171
1177
|
installOpenAIToolsListSecurityProjection(server);
|
|
1172
1178
|
const fieldsToRedact = PROFILE.redactOutputFields;
|
|
1179
|
+
const activeIncludeTools = new Set(PROFILE.includeTools);
|
|
1180
|
+
if (options?.candidateVersion === "1.0.3") {
|
|
1181
|
+
for (const tool of OPENAI_103_READ_ONLY_CANDIDATE_TOOLS) {
|
|
1182
|
+
activeIncludeTools.add(tool);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1173
1185
|
/* ── Intercept registerTool ─────────────────────────────────────── */
|
|
1174
1186
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
1175
1187
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1176
1188
|
server.registerTool = (name, config, handler) => {
|
|
1177
1189
|
// 0. Keep the public ChatGPT app to the reviewed core tool surface
|
|
1178
|
-
if (!
|
|
1190
|
+
if (!activeIncludeTools.has(name))
|
|
1179
1191
|
return;
|
|
1180
1192
|
// 1. Skip excluded tools entirely
|
|
1181
1193
|
if (PROFILE.excludeTools.has(name))
|
|
@@ -657,7 +657,7 @@ export function assertOpenAIReviewContract(actual, expected) {
|
|
|
657
657
|
const outputRequired = outputSchema?.required;
|
|
658
658
|
const success = jsonObject(outputProperties?.success);
|
|
659
659
|
const result = jsonObject(outputProperties?.result);
|
|
660
|
-
const branches = result?.
|
|
660
|
+
const branches = result?.oneOf;
|
|
661
661
|
const branchObjects = Array.isArray(branches)
|
|
662
662
|
? branches.map((branch) => jsonObject(branch))
|
|
663
663
|
: [];
|
|
@@ -687,6 +687,7 @@ export function assertOpenAIReviewContract(actual, expected) {
|
|
|
687
687
|
outputRequired.length !== 3 ||
|
|
688
688
|
success?.const !== true ||
|
|
689
689
|
!result ||
|
|
690
|
+
Object.keys(result).length !== 1 ||
|
|
690
691
|
branchObjects.length !== 2 ||
|
|
691
692
|
branchObjects.some((branch) => !branch) ||
|
|
692
693
|
branchByOutcome.size !== 2 ||
|
|
@@ -12,6 +12,7 @@ export interface McpSurfaceComposition {
|
|
|
12
12
|
readonly claudeMode?: boolean;
|
|
13
13
|
readonly googleMode?: boolean;
|
|
14
14
|
readonly museMode?: boolean;
|
|
15
|
+
readonly demoMode?: boolean;
|
|
15
16
|
}
|
|
16
17
|
export declare function localMcpSurfaceComposition(openaiMode: boolean, groupedMode: boolean, claudeMode?: boolean, googleMode?: boolean, museMode?: boolean): McpSurfaceComposition;
|
|
17
18
|
export declare function remoteMcpSurfaceComposition(openaiMode: boolean, groupedMode: boolean, claudeMode?: boolean, googleMode?: boolean, museMode?: boolean): McpSurfaceComposition;
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* generated public-surface contract. Transport/auth setup stays with each host;
|
|
4
4
|
* the exposed MCP operations/resources/prompts are composed here exactly once.
|
|
5
5
|
*/
|
|
6
|
+
import { z } from "zod/v4";
|
|
7
|
+
import { READ_STAMP } from "./demo-fixtures.js";
|
|
6
8
|
import { applyPublicCapabilityTruth, buildLocalDiscoveryCapability, CAPABILITY_META_KEY, } from "./capability-truth.js";
|
|
7
9
|
import { applyFiscalAliases } from "./fiscal-aliases.js";
|
|
8
10
|
import { applyClaudeCandidateProfile, CLAUDE_CANDIDATE_TOOLS, CLAUDE_GROUP_METADATA } from "./claude-profile.js";
|
|
@@ -33,6 +35,48 @@ export function registerMcpSurface(server, client, options) {
|
|
|
33
35
|
annotations: { ...config.annotations, title: config.title },
|
|
34
36
|
}, handler);
|
|
35
37
|
}
|
|
38
|
+
let clientIsDemo = false;
|
|
39
|
+
try {
|
|
40
|
+
clientIsDemo =
|
|
41
|
+
client?.isDemoClient === true ||
|
|
42
|
+
client?.constructor?.name === "DemoFrihetClient";
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
clientIsDemo = false;
|
|
46
|
+
}
|
|
47
|
+
const isDemoMode = options.demoMode === true || clientIsDemo;
|
|
48
|
+
if (isDemoMode) {
|
|
49
|
+
// Install closest to the SDK so profile projection/redaction runs first.
|
|
50
|
+
// Only trusted server/client configuration enables this boundary; neither
|
|
51
|
+
// input arguments nor response metadata can switch a live server to demo.
|
|
52
|
+
const registerTool = server.registerTool.bind(server);
|
|
53
|
+
server.registerTool = (name, config, handler) => {
|
|
54
|
+
const schema = config.outputSchema;
|
|
55
|
+
if (!(schema instanceof z.ZodObject)
|
|
56
|
+
|| !["data", "total", "limit", "offset"].every((key) => key in schema.shape)) {
|
|
57
|
+
return registerTool(name, config, handler);
|
|
58
|
+
}
|
|
59
|
+
return registerTool(name, {
|
|
60
|
+
...config,
|
|
61
|
+
outputSchema: schema.extend({
|
|
62
|
+
_demo: z.literal(true),
|
|
63
|
+
_demoNotice: z.literal(READ_STAMP._demoNotice),
|
|
64
|
+
}),
|
|
65
|
+
}, (async (args, extra) => {
|
|
66
|
+
const result = await handler(args, extra);
|
|
67
|
+
if (result.isError || !result.structuredContent)
|
|
68
|
+
return result;
|
|
69
|
+
const structuredContent = { ...result.structuredContent, ...READ_STAMP };
|
|
70
|
+
return {
|
|
71
|
+
...result,
|
|
72
|
+
structuredContent,
|
|
73
|
+
// JSON mirrors structuredContent for hosts that only read text.
|
|
74
|
+
// Keep existing summaries/hints too; no warning is silently dropped.
|
|
75
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }, ...result.content],
|
|
76
|
+
};
|
|
77
|
+
}));
|
|
78
|
+
};
|
|
79
|
+
}
|
|
36
80
|
if (options.groupedMode) {
|
|
37
81
|
if (options.openaiMode) {
|
|
38
82
|
applyOpenAIReviewProfiles(server);
|
|
@@ -48,6 +92,7 @@ export function registerMcpSurface(server, client, options) {
|
|
|
48
92
|
metaKey: CAPABILITY_META_KEY,
|
|
49
93
|
localDiscovery: buildLocalDiscoveryCapability,
|
|
50
94
|
},
|
|
95
|
+
demoMode: isDemoMode,
|
|
51
96
|
});
|
|
52
97
|
}
|
|
53
98
|
}
|
package/dist/tool-exposure.d.ts
CHANGED
|
@@ -141,6 +141,7 @@ export declare function applyToolExposureProfile(server: any, options?: {
|
|
|
141
141
|
type: "oauth2";
|
|
142
142
|
scopes: readonly string[];
|
|
143
143
|
}>;
|
|
144
|
+
demoMode?: boolean;
|
|
144
145
|
}): ToolExposureHandle;
|
|
145
146
|
/** Number of meta-tools added in grouped mode (for logging). */
|
|
146
147
|
export declare const GROUPED_META_TOOL_COUNT: number;
|
package/dist/tool-exposure.js
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
* @see ./openai-profile.ts — the sibling interceptor this mirrors.
|
|
42
42
|
*/
|
|
43
43
|
import { z } from "zod/v4";
|
|
44
|
+
import { READ_STAMP } from "./demo-fixtures.js";
|
|
44
45
|
/**
|
|
45
46
|
* Domain metadata. Keep these terse — they are the only group-level prose the
|
|
46
47
|
* agent loads up front. Depth lives in the per-tool descriptions, fetched via
|
|
@@ -112,6 +113,7 @@ export const FILE_TO_GROUP = {
|
|
|
112
113
|
deposits: "invoicing",
|
|
113
114
|
expenses: "expenses",
|
|
114
115
|
vendors: "expenses",
|
|
116
|
+
purchaseOrders: "expenses",
|
|
115
117
|
fiscal: "fiscal",
|
|
116
118
|
igic: "fiscal",
|
|
117
119
|
impuesto_sociedades: "fiscal",
|
|
@@ -176,7 +178,7 @@ export function groupForTool(name) {
|
|
|
176
178
|
return "fiscal";
|
|
177
179
|
if (/(invoice|quote|credit_note|late_fee|recurring|deposit)/.test(n))
|
|
178
180
|
return "invoicing";
|
|
179
|
-
if (/(expense|vendor)/.test(n))
|
|
181
|
+
if (/(expense|vendor|purchase_order)/.test(n))
|
|
180
182
|
return "expenses";
|
|
181
183
|
if (/(bank|transaction)/.test(n))
|
|
182
184
|
return "banking";
|
|
@@ -390,7 +392,7 @@ server, options) {
|
|
|
390
392
|
};
|
|
391
393
|
// Register the meta-tools eagerly. Their handlers close over `catalog`/`groups`
|
|
392
394
|
// which finish populating as the real tools register right after this call.
|
|
393
|
-
registerMetaTools(originalRegisterTool, handle, visibleGroupIds, groupMetadata, options?.capabilityTruth, options?.securitySchemes, options?.describeToolExample);
|
|
395
|
+
registerMetaTools(originalRegisterTool, handle, visibleGroupIds, groupMetadata, options?.capabilityTruth, options?.securitySchemes, options?.describeToolExample, options?.demoMode === true);
|
|
394
396
|
return handle;
|
|
395
397
|
}
|
|
396
398
|
/* ------------------------------------------------------------------ */
|
|
@@ -420,8 +422,9 @@ function textResult(payload) {
|
|
|
420
422
|
*/
|
|
421
423
|
function registerMetaTools(
|
|
422
424
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
423
|
-
originalRegisterTool, handle, visibleGroupIds, groupMetadata, capabilityTruth, securitySchemes, describeToolExample = "create_invoice") {
|
|
425
|
+
originalRegisterTool, handle, visibleGroupIds, groupMetadata, capabilityTruth, securitySchemes, describeToolExample = "create_invoice", demoMode = false) {
|
|
424
426
|
const { catalog, groups } = handle;
|
|
427
|
+
const demoStamp = demoMode ? READ_STAMP : {};
|
|
425
428
|
const oauthConfig = securitySchemes
|
|
426
429
|
? {
|
|
427
430
|
securitySchemes: securitySchemes.map((scheme) => ({
|
|
@@ -529,6 +532,7 @@ originalRegisterTool, handle, visibleGroupIds, groupMetadata, capabilityTruth, s
|
|
|
529
532
|
}))
|
|
530
533
|
.filter((g) => g.toolCount > 0);
|
|
531
534
|
return textResult({
|
|
535
|
+
...demoStamp,
|
|
532
536
|
groups: out,
|
|
533
537
|
totalGroups: out.length,
|
|
534
538
|
totalTools: catalog.size,
|
|
@@ -591,6 +595,7 @@ originalRegisterTool, handle, visibleGroupIds, groupMetadata, capabilityTruth, s
|
|
|
591
595
|
score,
|
|
592
596
|
}));
|
|
593
597
|
return textResult({
|
|
598
|
+
...demoStamp,
|
|
594
599
|
query: query || null,
|
|
595
600
|
group: groupFilter ?? null,
|
|
596
601
|
count: scored.length,
|
|
@@ -626,6 +631,7 @@ originalRegisterTool, handle, visibleGroupIds, groupMetadata, capabilityTruth, s
|
|
|
626
631
|
{
|
|
627
632
|
type: "text",
|
|
628
633
|
text: JSON.stringify({
|
|
634
|
+
...demoStamp,
|
|
629
635
|
error: name
|
|
630
636
|
? `No tool named '${name}'.`
|
|
631
637
|
: "Provide { name } — the exact tool name from search_tools.",
|
|
@@ -638,6 +644,7 @@ originalRegisterTool, handle, visibleGroupIds, groupMetadata, capabilityTruth, s
|
|
|
638
644
|
};
|
|
639
645
|
}
|
|
640
646
|
return textResult({
|
|
647
|
+
...demoStamp,
|
|
641
648
|
name: entry.name,
|
|
642
649
|
group: entry.group,
|
|
643
650
|
title: entry.title,
|
package/dist/tools/clients.js
CHANGED
|
@@ -226,6 +226,7 @@ export function registerClientTools(server, client) {
|
|
|
226
226
|
"Error: la busqueda textual ('q') no se puede combinar con el cursor 'after' porque el backend pagina las busquedas mediante 'offset'/'limit' e ignora 'after'. Usa 'offset' y 'limit' con 'q', u omite 'q' al paginar con 'after'.",
|
|
227
227
|
}],
|
|
228
228
|
isError: true,
|
|
229
|
+
_meta: { "io.frihet/cursorWithQueryUnsupported": true },
|
|
229
230
|
};
|
|
230
231
|
}
|
|
231
232
|
let result = await client.listClients({ limit, offset, after, fields, q, stage });
|
|
@@ -278,6 +279,7 @@ export function registerClientTools(server, client) {
|
|
|
278
279
|
`Error: la busqueda de clientes alcanzo el limite de escaneo de 500 documentos del ERP (truncated=true; ${partialCount} coincidencia(s) en los primeros 500 registros) y no puede devolverse como resultado exhaustivo. Acota 'q' (o filtra por 'stage') o pagina sin 'q' usando 'after'.`,
|
|
279
280
|
}],
|
|
280
281
|
isError: true,
|
|
282
|
+
_meta: { "io.frihet/searchScanTruncated": true },
|
|
281
283
|
};
|
|
282
284
|
}
|
|
283
285
|
const cleanEnvelope = {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purchase-order read tools for the Frihet MCP server.
|
|
3
|
+
*
|
|
4
|
+
* READ-ONLY surface for the /v1/purchase-orders family (ADR 0027 S2, server
|
|
5
|
+
* authority shipped in Frihet-ERP #2359). The single-object read returns the
|
|
6
|
+
* stored document as it lives in the workspace; list returns the same shape
|
|
7
|
+
* paged by status and vendorId, ordered by createdAt desc.
|
|
8
|
+
*
|
|
9
|
+
* Scope semantics: list_purchase_orders and get_purchase_order both classify
|
|
10
|
+
* as `read` under the family router (functions/src/publicApi/apiKeyScopes.ts
|
|
11
|
+
* → requiredPurchaseOrderApiAccess), so a restricted API key needs at least
|
|
12
|
+
* one of `*`, `read`, `purchase-orders:*` or `purchase-orders:read`.
|
|
13
|
+
*
|
|
14
|
+
* WRITE tools (create draft, send, confirm, cancel, receive, reverse-receipt,
|
|
15
|
+
* bill) are deliberately NOT exposed here: they move money or stock and need
|
|
16
|
+
* human-authority semantics (draft → show → confirm) that this surface does not
|
|
17
|
+
* define. No write route is called from this file or from the client methods.
|
|
18
|
+
*
|
|
19
|
+
* NOT IN SCOPE for this surface (correction to a prior brief):
|
|
20
|
+
* - Physical count truth (#2355): no callable, no API route. Not designed.
|
|
21
|
+
* - Inventory balances (/v1/inventory/balances): ADR 0027 S4, not landed.
|
|
22
|
+
*/
|
|
23
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24
|
+
import type { IFrihetClient } from "../client-interface.js";
|
|
25
|
+
/**
|
|
26
|
+
* Read tools for purchase orders. Tool names follow the canonical
|
|
27
|
+
* snake_case convention used across the catalog.
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerPurchaseOrderTools(server: McpServer, client: IFrihetClient): void;
|
|
30
|
+
//# sourceMappingURL=purchaseOrders.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purchase-order read tools for the Frihet MCP server.
|
|
3
|
+
*
|
|
4
|
+
* READ-ONLY surface for the /v1/purchase-orders family (ADR 0027 S2, server
|
|
5
|
+
* authority shipped in Frihet-ERP #2359). The single-object read returns the
|
|
6
|
+
* stored document as it lives in the workspace; list returns the same shape
|
|
7
|
+
* paged by status and vendorId, ordered by createdAt desc.
|
|
8
|
+
*
|
|
9
|
+
* Scope semantics: list_purchase_orders and get_purchase_order both classify
|
|
10
|
+
* as `read` under the family router (functions/src/publicApi/apiKeyScopes.ts
|
|
11
|
+
* → requiredPurchaseOrderApiAccess), so a restricted API key needs at least
|
|
12
|
+
* one of `*`, `read`, `purchase-orders:*` or `purchase-orders:read`.
|
|
13
|
+
*
|
|
14
|
+
* WRITE tools (create draft, send, confirm, cancel, receive, reverse-receipt,
|
|
15
|
+
* bill) are deliberately NOT exposed here: they move money or stock and need
|
|
16
|
+
* human-authority semantics (draft → show → confirm) that this surface does not
|
|
17
|
+
* define. No write route is called from this file or from the client methods.
|
|
18
|
+
*
|
|
19
|
+
* NOT IN SCOPE for this surface (correction to a prior brief):
|
|
20
|
+
* - Physical count truth (#2355): no callable, no API route. Not designed.
|
|
21
|
+
* - Inventory balances (/v1/inventory/balances): ADR 0027 S4, not landed.
|
|
22
|
+
*/
|
|
23
|
+
import { z } from "zod/v4";
|
|
24
|
+
import { withToolLogging, formatPaginatedResponse, formatRecord, listContent, getContent, READ_ONLY_ANNOTATIONS, paginatedOutput, purchaseOrderItemOutput, } from "./shared.js";
|
|
25
|
+
/**
|
|
26
|
+
* Read tools for purchase orders. Tool names follow the canonical
|
|
27
|
+
* snake_case convention used across the catalog.
|
|
28
|
+
*/
|
|
29
|
+
export function registerPurchaseOrderTools(server, client) {
|
|
30
|
+
// -- list_purchase_orders --
|
|
31
|
+
server.registerTool("list_purchase_orders", {
|
|
32
|
+
title: "List Purchase Orders",
|
|
33
|
+
description: "List purchase orders in the workspace with optional filters. " +
|
|
34
|
+
"Returns one entry per PO with vendor, status, line items and totals. " +
|
|
35
|
+
"Statuses reflect the server-owned lifecycle (ADR 0027 S2): " +
|
|
36
|
+
"draft → sent → confirmed → received → billed, with cancel as a terminal branch. " +
|
|
37
|
+
"/ Lista pedidos de compra del workspace con filtros opcionales. " +
|
|
38
|
+
"Devuelve un registro por pedido con proveedor, estado, lineas y totales. " +
|
|
39
|
+
"Los estados reflejan el ciclo de vida del servidor (ADR 0027 S2): " +
|
|
40
|
+
"borrador → enviado → confirmado → recibido → facturado, con cancelado como rama terminal.",
|
|
41
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
42
|
+
inputSchema: {
|
|
43
|
+
status: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Filter by PO status (draft, sent, confirmed, received, billed, cancelled) / " +
|
|
47
|
+
"Filtro por estado del pedido"),
|
|
48
|
+
vendorId: z
|
|
49
|
+
.string()
|
|
50
|
+
.trim()
|
|
51
|
+
.min(1)
|
|
52
|
+
.max(128)
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Filter by vendor ID / Filtro por ID de proveedor"),
|
|
55
|
+
limit: z
|
|
56
|
+
.number()
|
|
57
|
+
.int()
|
|
58
|
+
.min(1)
|
|
59
|
+
.max(100)
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Max results (1-100) / Resultados maximos"),
|
|
62
|
+
offset: z
|
|
63
|
+
.number()
|
|
64
|
+
.int()
|
|
65
|
+
.min(0)
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Offset / Desplazamiento"),
|
|
68
|
+
},
|
|
69
|
+
outputSchema: paginatedOutput(purchaseOrderItemOutput),
|
|
70
|
+
}, async ({ status, vendorId, limit, offset }) => withToolLogging("list_purchase_orders", async () => {
|
|
71
|
+
const result = await client.listPurchaseOrders({ status, vendorId, limit, offset });
|
|
72
|
+
return {
|
|
73
|
+
content: [listContent(formatPaginatedResponse("purchase_orders", result))],
|
|
74
|
+
structuredContent: result,
|
|
75
|
+
};
|
|
76
|
+
}));
|
|
77
|
+
// -- get_purchase_order --
|
|
78
|
+
server.registerTool("get_purchase_order", {
|
|
79
|
+
title: "Get Purchase Order",
|
|
80
|
+
description: "Get a single purchase order by its ID. Returns the full document: " +
|
|
81
|
+
"vendor, line items, totals, status, issue/expected dates and currency. " +
|
|
82
|
+
"/ Obtiene un pedido de compra por su ID. Devuelve el documento completo: " +
|
|
83
|
+
"proveedor, lineas, totales, estado, fechas de emision y esperada y moneda.",
|
|
84
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
85
|
+
inputSchema: {
|
|
86
|
+
id: z
|
|
87
|
+
.string()
|
|
88
|
+
.trim()
|
|
89
|
+
.min(1)
|
|
90
|
+
.max(128)
|
|
91
|
+
.describe("Purchase-order ID / ID del pedido de compra"),
|
|
92
|
+
},
|
|
93
|
+
outputSchema: purchaseOrderItemOutput,
|
|
94
|
+
}, async ({ id }) => withToolLogging("get_purchase_order", async () => {
|
|
95
|
+
const result = await client.getPurchaseOrder(id);
|
|
96
|
+
return {
|
|
97
|
+
content: [getContent(formatRecord("PurchaseOrder", result))],
|
|
98
|
+
structuredContent: result,
|
|
99
|
+
};
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=purchaseOrders.js.map
|
|
@@ -15,6 +15,7 @@ import { registerClientTools } from "./clients.js";
|
|
|
15
15
|
import { registerProductTools } from "./products.js";
|
|
16
16
|
import { registerQuoteTools } from "./quotes.js";
|
|
17
17
|
import { registerVendorTools } from "./vendors.js";
|
|
18
|
+
import { registerPurchaseOrderTools } from "./purchaseOrders.js";
|
|
18
19
|
import { registerWebhookTools } from "./webhooks.js";
|
|
19
20
|
import { registerIntelligenceTools } from "./intelligence.js";
|
|
20
21
|
import { registerCrmTools } from "./crm.js";
|
|
@@ -89,6 +90,7 @@ export function registerAllTools(server, client) {
|
|
|
89
90
|
registerProductTools(server, client);
|
|
90
91
|
registerQuoteTools(server, client);
|
|
91
92
|
registerVendorTools(server, client);
|
|
93
|
+
registerPurchaseOrderTools(server, client);
|
|
92
94
|
registerWebhookTools(server, client);
|
|
93
95
|
registerDepositTools(server, client);
|
|
94
96
|
registerEInvoiceTools(server, client);
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -247,6 +247,48 @@ export declare const vendorItemOutput: z.ZodObject<{
|
|
|
247
247
|
createdAt: z.ZodOptional<z.ZodString>;
|
|
248
248
|
updatedAt: z.ZodOptional<z.ZodString>;
|
|
249
249
|
}, z.core.$loose>;
|
|
250
|
+
/**
|
|
251
|
+
* Purchase-order output (ADR 0027 S2, server-owned lifecycle).
|
|
252
|
+
*
|
|
253
|
+
* Fields mirror the live API shape returned by /v1/purchase-orders GETs:
|
|
254
|
+
* - `status` ∈ {draft, sent, confirmed, received, billed, cancelled}
|
|
255
|
+
* - `items[].taxRate` is optional because per-line tax rates may override
|
|
256
|
+
* the document tax rate; the family router accepts it as a string-coerced
|
|
257
|
+
* number in the request, so the response can carry it.
|
|
258
|
+
* - `currency` is an ISO 4217 code (e.g. "EUR"). It is the same code the
|
|
259
|
+
* server stamps on the stored doc.
|
|
260
|
+
* - `createdAt`/`updatedAt` are ISO 8601; `issueDate`/`expectedDate` are
|
|
261
|
+
* YYYY-MM-DD.
|
|
262
|
+
*
|
|
263
|
+
* `.passthrough()` preserves receipts (`receipts[]`) and the linked expense
|
|
264
|
+
* id (`billedExpenseId`) once the order moves past `billed`, plus any
|
|
265
|
+
* additional fields the server adds in later S-stages without breaking
|
|
266
|
+
* downstream consumers.
|
|
267
|
+
*/
|
|
268
|
+
export declare const purchaseOrderItemOutput: z.ZodObject<{
|
|
269
|
+
id: z.ZodString;
|
|
270
|
+
poNumber: z.ZodOptional<z.ZodString>;
|
|
271
|
+
vendorId: z.ZodOptional<z.ZodString>;
|
|
272
|
+
vendorName: z.ZodOptional<z.ZodString>;
|
|
273
|
+
status: z.ZodOptional<z.ZodString>;
|
|
274
|
+
items: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
275
|
+
description: z.ZodString;
|
|
276
|
+
quantity: z.ZodNumber;
|
|
277
|
+
unitPrice: z.ZodNumber;
|
|
278
|
+
productId: z.ZodOptional<z.ZodString>;
|
|
279
|
+
taxRate: z.ZodOptional<z.ZodNumber>;
|
|
280
|
+
}, z.core.$loose>>>;
|
|
281
|
+
issueDate: z.ZodOptional<z.ZodString>;
|
|
282
|
+
expectedDate: z.ZodOptional<z.ZodString>;
|
|
283
|
+
taxRate: z.ZodOptional<z.ZodNumber>;
|
|
284
|
+
subtotal: z.ZodOptional<z.ZodNumber>;
|
|
285
|
+
tax: z.ZodOptional<z.ZodNumber>;
|
|
286
|
+
total: z.ZodOptional<z.ZodNumber>;
|
|
287
|
+
currency: z.ZodOptional<z.ZodString>;
|
|
288
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
289
|
+
createdAt: z.ZodOptional<z.ZodString>;
|
|
290
|
+
updatedAt: z.ZodOptional<z.ZodString>;
|
|
291
|
+
}, z.core.$loose>;
|
|
250
292
|
export declare const webhookItemOutput: z.ZodObject<{
|
|
251
293
|
id: z.ZodString;
|
|
252
294
|
userId: z.ZodOptional<z.ZodString>;
|
|
@@ -537,8 +579,8 @@ export declare const bankTransactionItemOutput: z.ZodObject<{
|
|
|
537
579
|
postedAt: z.ZodOptional<z.ZodString>;
|
|
538
580
|
category: z.ZodOptional<z.ZodString>;
|
|
539
581
|
status: z.ZodOptional<z.ZodEnum<{
|
|
540
|
-
pending: "pending";
|
|
541
582
|
posted: "posted";
|
|
583
|
+
pending: "pending";
|
|
542
584
|
excluded: "excluded";
|
|
543
585
|
}>>;
|
|
544
586
|
matchedDocId: z.ZodOptional<z.ZodString>;
|
package/dist/tools/shared.js
CHANGED
|
@@ -555,6 +555,52 @@ export const vendorItemOutput = z.object({
|
|
|
555
555
|
createdAt: z.string().optional(),
|
|
556
556
|
updatedAt: z.string().optional(),
|
|
557
557
|
}).passthrough();
|
|
558
|
+
/**
|
|
559
|
+
* Purchase-order output (ADR 0027 S2, server-owned lifecycle).
|
|
560
|
+
*
|
|
561
|
+
* Fields mirror the live API shape returned by /v1/purchase-orders GETs:
|
|
562
|
+
* - `status` ∈ {draft, sent, confirmed, received, billed, cancelled}
|
|
563
|
+
* - `items[].taxRate` is optional because per-line tax rates may override
|
|
564
|
+
* the document tax rate; the family router accepts it as a string-coerced
|
|
565
|
+
* number in the request, so the response can carry it.
|
|
566
|
+
* - `currency` is an ISO 4217 code (e.g. "EUR"). It is the same code the
|
|
567
|
+
* server stamps on the stored doc.
|
|
568
|
+
* - `createdAt`/`updatedAt` are ISO 8601; `issueDate`/`expectedDate` are
|
|
569
|
+
* YYYY-MM-DD.
|
|
570
|
+
*
|
|
571
|
+
* `.passthrough()` preserves receipts (`receipts[]`) and the linked expense
|
|
572
|
+
* id (`billedExpenseId`) once the order moves past `billed`, plus any
|
|
573
|
+
* additional fields the server adds in later S-stages without breaking
|
|
574
|
+
* downstream consumers.
|
|
575
|
+
*/
|
|
576
|
+
export const purchaseOrderItemOutput = z.object({
|
|
577
|
+
id: z.string(),
|
|
578
|
+
poNumber: z.string().optional(),
|
|
579
|
+
vendorId: z.string().optional(),
|
|
580
|
+
vendorName: z.string().optional(),
|
|
581
|
+
status: z.string().optional(),
|
|
582
|
+
items: z
|
|
583
|
+
.array(z
|
|
584
|
+
.object({
|
|
585
|
+
description: z.string(),
|
|
586
|
+
quantity: z.number(),
|
|
587
|
+
unitPrice: z.number(),
|
|
588
|
+
productId: z.string().optional(),
|
|
589
|
+
taxRate: z.number().optional(),
|
|
590
|
+
})
|
|
591
|
+
.passthrough())
|
|
592
|
+
.optional(),
|
|
593
|
+
issueDate: z.string().optional(),
|
|
594
|
+
expectedDate: z.string().optional(),
|
|
595
|
+
taxRate: z.number().optional(),
|
|
596
|
+
subtotal: z.number().optional(),
|
|
597
|
+
tax: z.number().optional(),
|
|
598
|
+
total: z.number().optional(),
|
|
599
|
+
currency: z.string().optional(),
|
|
600
|
+
notes: z.string().optional(),
|
|
601
|
+
createdAt: z.string().optional(),
|
|
602
|
+
updatedAt: z.string().optional(),
|
|
603
|
+
}).passthrough();
|
|
558
604
|
export const webhookItemOutput = z.object({
|
|
559
605
|
id: z.string(),
|
|
560
606
|
userId: z.string().optional(),
|
package/dist/tools/vendors.js
CHANGED
|
@@ -209,6 +209,7 @@ export function registerVendorTools(server, client) {
|
|
|
209
209
|
"Error: la busqueda textual ('q') no se puede combinar con el cursor 'after' porque el backend pagina las busquedas mediante 'offset'/'limit' e ignora 'after'. Usa 'offset' y 'limit' con 'q', u omite 'q' al paginar con 'after'.",
|
|
210
210
|
}],
|
|
211
211
|
isError: true,
|
|
212
|
+
_meta: { "io.frihet/cursorWithQueryUnsupported": true },
|
|
212
213
|
};
|
|
213
214
|
}
|
|
214
215
|
let result = await client.listVendors({ q, limit, offset, after, fields });
|
|
@@ -253,6 +254,7 @@ export function registerVendorTools(server, client) {
|
|
|
253
254
|
`Error: la busqueda de proveedores alcanzo el limite de escaneo de 500 documentos del ERP (truncated=true; ${partialCount} coincidencia(s) en los primeros 500 registros) y no puede devolverse como resultado exhaustivo. Acota 'q' o pagina sin 'q' usando 'after'.`,
|
|
254
255
|
}],
|
|
255
256
|
isError: true,
|
|
257
|
+
_meta: { "io.frihet/searchScanTruncated": true },
|
|
256
258
|
};
|
|
257
259
|
}
|
|
258
260
|
const cleanEnvelope = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frihet/mcp-server",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "AI-native MCP server for Frihet ERP —
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "AI-native MCP server for Frihet ERP — 158 canonical operations for invoicing, expenses, CRM, banking, POS and ES/EU fiscal workflows, with conservative capability and side-effect metadata.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
12
|
-
"test": "npm run build && node --test dist/__tests__/resources-eu-catalog.test.js && node --test dist/__tests__/resources-p1-contract.test.js dist/__tests__/openai-profile.test.js dist/__tests__/openai-client-tax-id-contract.test.js dist/__tests__/tool-exposure.test.js dist/__tests__/fiscal-aliases.test.js dist/__tests__/einvoice-tools.test.js dist/__tests__/einvoice-day4-tools.test.js dist/__tests__/expenses-tools.test.js dist/__tests__/stay-tools.test.js dist/__tests__/pos-tools.test.js dist/__tests__/kitchen-tools.test.js dist/__tests__/banking-tools.test.js dist/__tests__/banking-client-contract.test.js dist/__tests__/pagination-cursor-param.test.js dist/__tests__/fiscal-tools.test.js dist/__tests__/fiscal-period-contract.test.js dist/__tests__/time-tools.test.js dist/__tests__/deposits-tools.test.js dist/__tests__/recurring-tools.test.js dist/__tests__/team-tools.test.js dist/__tests__/team-tools-real-sdk.test.js dist/__tests__/d4b-hr-payroll-onboarding-tools.test.js dist/__tests__/hr-payroll-period-contract.test.js dist/__tests__/webhook-crm-reviewed-contract.test.js dist/__tests__/audit-server-version.test.js dist/__tests__/search-tools.test.js dist/__tests__/openai-full-compose.test.js dist/__tests__/openai-review-descriptor.test.js dist/__tests__/directory-profile-policy.test.js dist/__tests__/claude-profile.test.js dist/__tests__/claude-output-privacy.test.js dist/__tests__/claude-write-route-contract.test.js dist/__tests__/contract.test.js dist/__tests__/observability-redaction.test.js dist/__tests__/security-containment-gate0.test.js dist/__tests__/intelligence-duplicate-invoice.test.js dist/__tests__/get-envelope-unwrap-regression.test.js dist/__tests__/binary-content-types.test.js dist/__tests__/api-error-remediation.test.js dist/__tests__/mutation-unwrap-and-schema-regression.test.js dist/__tests__/schema-envelope-guard.test.js dist/__tests__/paginated-strict-output.test.js dist/__tests__/paginated-metadata-contract.test.js dist/__tests__/demo-mode.test.js dist/__tests__/idempotency-key-contract.test.js dist/__tests__/retry-after-contract.test.js dist/__tests__/source-header-contract.test.js dist/__tests__/no-provisioning-credential-to-public-api.test.js dist/__tests__/truth-in-descriptions.test.js dist/__tests__/mcp-baseline-mutation.test.js dist/__tests__/forms-elicitation.test.js dist/__tests__/agent-onboarding.test.js scripts/__tests__/openai-worker-review-wire.test.mjs scripts/__tests__/sync-openapi-retry.test.mjs scripts/__tests__/conformance-provenance.test.mjs scripts/__tests__/conformance-phase0.test.mjs scripts/__tests__/published-artifact-drift.test.mjs scripts/__tests__/cross-surface-authority.test.mjs scripts/__tests__/release-workflow-contract.test.mjs scripts/__tests__/openai-wrangler-resolution.test.mjs scripts/__tests__/audit-mcp-refs-detector.test.mjs scripts/__tests__/openai-public-surface.test.mjs scripts/__tests__/claude-plugin-package.test.mjs scripts/__tests__/claude-hosted-release.test.mjs scripts/__tests__/claude-hosted-acceptance.test.mjs scripts/__tests__/claude-oauth-provider.test.mjs scripts/__tests__/audit-mcp-refs-published-sot.test.mjs scripts/__tests__/claude-bootstrap-secrets.test.mjs scripts/__tests__/no-public-leak.test.mjs",
|
|
12
|
+
"test": "npm run build && node --test dist/__tests__/resources-eu-catalog.test.js && node --test dist/__tests__/resources-p1-contract.test.js dist/__tests__/openai-profile.test.js dist/__tests__/openai-client-tax-id-contract.test.js dist/__tests__/tool-exposure.test.js dist/__tests__/fiscal-aliases.test.js dist/__tests__/einvoice-tools.test.js dist/__tests__/einvoice-day4-tools.test.js dist/__tests__/expenses-tools.test.js dist/__tests__/stay-tools.test.js dist/__tests__/pos-tools.test.js dist/__tests__/kitchen-tools.test.js dist/__tests__/banking-tools.test.js dist/__tests__/banking-client-contract.test.js dist/__tests__/pagination-cursor-param.test.js dist/__tests__/fiscal-tools.test.js dist/__tests__/fiscal-period-contract.test.js dist/__tests__/time-tools.test.js dist/__tests__/deposits-tools.test.js dist/__tests__/recurring-tools.test.js dist/__tests__/team-tools.test.js dist/__tests__/team-tools-real-sdk.test.js dist/__tests__/d4b-hr-payroll-onboarding-tools.test.js dist/__tests__/hr-payroll-period-contract.test.js dist/__tests__/webhook-crm-reviewed-contract.test.js dist/__tests__/audit-server-version.test.js dist/__tests__/search-tools.test.js dist/__tests__/openai-full-compose.test.js dist/__tests__/openai-review-descriptor.test.js dist/__tests__/directory-profile-policy.test.js dist/__tests__/claude-profile.test.js dist/__tests__/claude-output-privacy.test.js dist/__tests__/claude-write-route-contract.test.js dist/__tests__/contract.test.js dist/__tests__/observability-redaction.test.js dist/__tests__/security-containment-gate0.test.js dist/__tests__/intelligence-duplicate-invoice.test.js dist/__tests__/get-envelope-unwrap-regression.test.js dist/__tests__/binary-content-types.test.js dist/__tests__/api-error-remediation.test.js dist/__tests__/mutation-unwrap-and-schema-regression.test.js dist/__tests__/schema-envelope-guard.test.js dist/__tests__/paginated-strict-output.test.js dist/__tests__/paginated-metadata-contract.test.js dist/__tests__/purchase-orders-tools.test.js dist/__tests__/purchase-orders-client-contract.test.js dist/__tests__/demo-mode.test.js dist/__tests__/idempotency-key-contract.test.js dist/__tests__/retry-after-contract.test.js dist/__tests__/source-header-contract.test.js dist/__tests__/no-provisioning-credential-to-public-api.test.js dist/__tests__/truth-in-descriptions.test.js dist/__tests__/mcp-baseline-mutation.test.js dist/__tests__/forms-elicitation.test.js dist/__tests__/agent-onboarding.test.js scripts/__tests__/openai-worker-review-wire.test.mjs scripts/__tests__/sync-openapi-retry.test.mjs scripts/__tests__/conformance-provenance.test.mjs scripts/__tests__/conformance-phase0.test.mjs scripts/__tests__/published-artifact-drift.test.mjs scripts/__tests__/cross-surface-authority.test.mjs scripts/__tests__/release-workflow-contract.test.mjs scripts/__tests__/openai-wrangler-resolution.test.mjs scripts/__tests__/audit-mcp-refs-detector.test.mjs scripts/__tests__/openai-public-surface.test.mjs scripts/__tests__/claude-plugin-package.test.mjs scripts/__tests__/claude-hosted-release.test.mjs scripts/__tests__/claude-hosted-acceptance.test.mjs scripts/__tests__/claude-oauth-provider.test.mjs scripts/__tests__/audit-mcp-refs-published-sot.test.mjs scripts/__tests__/claude-bootstrap-secrets.test.mjs scripts/__tests__/no-public-leak.test.mjs",
|
|
13
13
|
"canary:mcp": "npm run build && node scripts/canary-mcp.mjs",
|
|
14
14
|
"canary:mcp:check": "npm run build && node scripts/canary-mcp.mjs --check",
|
|
15
15
|
"canary:mcp:snapshot": "npm run build && node scripts/canary-mcp.mjs --snapshot",
|
|
@@ -121,17 +121,17 @@
|
|
|
121
121
|
"LICENSE"
|
|
122
122
|
],
|
|
123
123
|
"dependencies": {
|
|
124
|
-
"@modelcontextprotocol/sdk": "1.
|
|
124
|
+
"@modelcontextprotocol/sdk": "1.32.2"
|
|
125
125
|
},
|
|
126
126
|
"overrides": {
|
|
127
|
-
"hono": ">=4.13.
|
|
128
|
-
"ip-address": ">=10.7.
|
|
127
|
+
"hono": ">=4.13.16 <5",
|
|
128
|
+
"ip-address": ">=10.7.6 <11",
|
|
129
129
|
"proxy-addr": ">=2.0.8",
|
|
130
|
-
"qs": ">=6.
|
|
130
|
+
"qs": ">=6.17.1 <7",
|
|
131
131
|
"@ai-sdk/provider-utils": ">=4.0.33 <5"
|
|
132
132
|
},
|
|
133
133
|
"peerDependencies": {
|
|
134
|
-
"zod": "
|
|
134
|
+
"zod": "^4.6.6"
|
|
135
135
|
},
|
|
136
136
|
"peerDependenciesMeta": {
|
|
137
137
|
"zod": {
|
|
@@ -142,6 +142,6 @@
|
|
|
142
142
|
"@types/node": "^22.0.0",
|
|
143
143
|
"typescript": "^5.7.0",
|
|
144
144
|
"ajv": "8.18.0",
|
|
145
|
-
"zod": "^
|
|
145
|
+
"zod": "^4.6.7"
|
|
146
146
|
}
|
|
147
147
|
}
|