@frihet/mcp-server 1.17.0 → 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 +84 -59
- 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 +240 -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 +27 -21
- package/dist/demo-client.js +31 -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 +5 -2
- package/dist/server-composition.js +35 -5
- package/dist/tool-exposure.d.ts +6 -5
- package/dist/tool-exposure.js +15 -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 +252 -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 +17 -37
- 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 +240 -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
|
@@ -5,6 +5,44 @@
|
|
|
5
5
|
export const OPENAI_REVIEW_ORIGIN = "https://openai-mcp.frihet.io";
|
|
6
6
|
export const FULL_MCP_ORIGIN = "https://mcp.frihet.io";
|
|
7
7
|
export const FRIHET_CONNECTOR_SCOPE = "frihet:workspace.manage";
|
|
8
|
+
export const OPENAI_REVIEW_OAUTH_SCOPES = Object.freeze([
|
|
9
|
+
"openid",
|
|
10
|
+
"email",
|
|
11
|
+
FRIHET_CONNECTOR_SCOPE,
|
|
12
|
+
]);
|
|
13
|
+
/**
|
|
14
|
+
* RFC 8707 resource identifier of the reviewed MCP endpoint. Claude's
|
|
15
|
+
* connector guide requires protected-resource metadata to name the connector
|
|
16
|
+
* URL a user enters, including its path; OpenAI documents that ChatGPT takes
|
|
17
|
+
* the `resource` value it sends from that metadata.
|
|
18
|
+
*/
|
|
19
|
+
export const OPENAI_REVIEW_MCP_RESOURCE = `${OPENAI_REVIEW_ORIGIN}/mcp`;
|
|
20
|
+
/** RFC 9728 section 3.1 path-inserted location of the metadata for
|
|
21
|
+
* OPENAI_REVIEW_MCP_RESOURCE. The 401 challenge points here. */
|
|
22
|
+
export const OPENAI_REVIEW_MCP_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource/mcp";
|
|
23
|
+
/**
|
|
24
|
+
* The only RFC 8707 resource values the reviewed authorization boundary
|
|
25
|
+
* accepts. The canonical MCP URL comes first; the bare origin is the value the
|
|
26
|
+
* root metadata document still publishes (and the only one it published before
|
|
27
|
+
* the path-inserted document existed), so clients using it keep working. Both
|
|
28
|
+
* name this Worker only: the pinned provider matches a token audience by exact
|
|
29
|
+
* origin plus path prefix, so neither value can authorize the full host.
|
|
30
|
+
* The runtime checks that use this set are validateOAuthBoundary (below) and
|
|
31
|
+
* the /token rotation guard in workers/remote-mcp/src/oauth-token-family.ts.
|
|
32
|
+
* The inventory test in workers/remote-mcp/src/__tests__/openai-review-oauth.test.ts
|
|
33
|
+
* lists every runtime line that reads a field named `resource` or `audience`
|
|
34
|
+
* (dot access, string-literal bracket access, or single-line destructuring)
|
|
35
|
+
* and fails on any new such line, whatever it compares or calls. It does not
|
|
36
|
+
* follow a value after it is copied into another variable, multi-line
|
|
37
|
+
* destructuring, or computed property names.
|
|
38
|
+
* Grant props keep `oauthResource: OPENAI_REVIEW_ORIGIN` as the host-binding
|
|
39
|
+
* identity checked at session init and API-key provisioning; that field is not
|
|
40
|
+
* the RFC 8707 value, which the provider stores on the grant itself.
|
|
41
|
+
*/
|
|
42
|
+
export const OPENAI_REVIEW_OAUTH_RESOURCES = Object.freeze([
|
|
43
|
+
OPENAI_REVIEW_MCP_RESOURCE,
|
|
44
|
+
OPENAI_REVIEW_ORIGIN,
|
|
45
|
+
]);
|
|
8
46
|
/** RFC 7636 S256 challenges are the 32-byte SHA-256 digest encoded as
|
|
9
47
|
* unpadded base64url, which is always exactly 43 characters. */
|
|
10
48
|
export function isValidS256CodeChallenge(value) {
|
|
@@ -33,32 +71,41 @@ export const OAUTH_PROVIDER_REVIEW_OPTIONS = {
|
|
|
33
71
|
clientRegistrationEndpoint: "/register",
|
|
34
72
|
// This provider does not implement per-tool OAuth authorization. Advertise
|
|
35
73
|
// one honest connector-wide scope instead of implying that `read` prevents
|
|
36
|
-
// writes.
|
|
37
|
-
|
|
74
|
+
// writes. Where a profile requires confirm=true, that is a client interlock,
|
|
75
|
+
// not an authorization boundary.
|
|
76
|
+
scopesSupported: [...OPENAI_REVIEW_OAUTH_SCOPES],
|
|
38
77
|
accessTokenTTL: 3600,
|
|
39
78
|
refreshTokenTTL: 2592000,
|
|
40
79
|
allowPlainPKCE: false,
|
|
41
80
|
resourceMetadata: {
|
|
42
81
|
resource: OPENAI_REVIEW_ORIGIN,
|
|
43
82
|
authorization_servers: [OPENAI_REVIEW_ORIGIN],
|
|
44
|
-
scopes_supported: [
|
|
83
|
+
scopes_supported: [...OPENAI_REVIEW_OAUTH_SCOPES],
|
|
45
84
|
bearer_methods_supported: ["header"],
|
|
46
|
-
resource_name: "Frihet
|
|
85
|
+
resource_name: "Frihet reviewed connector",
|
|
47
86
|
},
|
|
48
87
|
};
|
|
88
|
+
export function hasExactOpenAIReviewScopes(value) {
|
|
89
|
+
if (!Array.isArray(value) || value.length !== OPENAI_REVIEW_OAUTH_SCOPES.length) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const scopes = new Set(value);
|
|
93
|
+
return scopes.size === OPENAI_REVIEW_OAUTH_SCOPES.length
|
|
94
|
+
&& OPENAI_REVIEW_OAUTH_SCOPES.every((scope) => scopes.has(scope));
|
|
95
|
+
}
|
|
49
96
|
/**
|
|
50
97
|
* Validate the two OAuth values that form the reviewed-host authorization
|
|
51
|
-
* boundary. RFC 8707 resource comparison is exact by design:
|
|
52
|
-
* arbitrary absolute URI would let a token minted by one Frihet
|
|
53
|
-
* the other Worker. Unknown or empty scopes are rejected instead
|
|
54
|
-
* downscoping to an empty token.
|
|
98
|
+
* boundary. RFC 8707 resource comparison is exact string membership by design:
|
|
99
|
+
* accepting an arbitrary absolute URI would let a token minted by one Frihet
|
|
100
|
+
* Worker target the other Worker. Unknown or empty scopes are rejected instead
|
|
101
|
+
* of silently downscoping to an empty token.
|
|
55
102
|
*/
|
|
56
|
-
export function validateOAuthBoundary(parameters,
|
|
103
|
+
export function validateOAuthBoundary(parameters, acceptedResources) {
|
|
57
104
|
if (parameters.resource === undefined && !parameters.requireResource) {
|
|
58
105
|
// The provider will inherit the exact resource stored on the grant.
|
|
59
106
|
}
|
|
60
107
|
else if (typeof parameters.resource !== "string"
|
|
61
|
-
|| parameters.resource
|
|
108
|
+
|| !acceptedResources.includes(parameters.resource)) {
|
|
62
109
|
return {
|
|
63
110
|
ok: false,
|
|
64
111
|
error: "invalid_target",
|
|
@@ -72,11 +119,11 @@ export function validateOAuthBoundary(parameters, expectedResource) {
|
|
|
72
119
|
: typeof parameters.scope === "string"
|
|
73
120
|
? parameters.scope.split(/\s+/u).filter(Boolean)
|
|
74
121
|
: [];
|
|
75
|
-
if (scopes
|
|
122
|
+
if (!hasExactOpenAIReviewScopes(scopes)) {
|
|
76
123
|
return {
|
|
77
124
|
ok: false,
|
|
78
125
|
error: "invalid_scope",
|
|
79
|
-
description: `The
|
|
126
|
+
description: `The required OAuth scopes are ${OPENAI_REVIEW_OAUTH_SCOPES.join(" ")}.`,
|
|
80
127
|
};
|
|
81
128
|
}
|
|
82
129
|
return { ok: true };
|
|
@@ -85,22 +132,38 @@ function endpoint(origin, path) {
|
|
|
85
132
|
return new URL(path, new URL(origin).origin).toString();
|
|
86
133
|
}
|
|
87
134
|
export function buildOpenAIUnauthorizedChallenge(origin = OPENAI_REVIEW_ORIGIN) {
|
|
88
|
-
const resourceMetadataUrl = endpoint(new URL(origin).origin,
|
|
135
|
+
const resourceMetadataUrl = endpoint(new URL(origin).origin, OPENAI_REVIEW_MCP_RESOURCE_METADATA_PATH);
|
|
89
136
|
return `Bearer realm="OAuth", resource_metadata="${resourceMetadataUrl}", ` +
|
|
90
|
-
`scope="${
|
|
137
|
+
`scope="${OPENAI_REVIEW_OAUTH_SCOPES.join(" ")}", error="invalid_token", ` +
|
|
91
138
|
'error_description="Missing or invalid access token"';
|
|
92
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Path-inserted protected-resource metadata for the reviewed MCP endpoint,
|
|
142
|
+
* served by the Worker itself (the pinned provider only serves the root
|
|
143
|
+
* document). It differs from the root document only in `resource`.
|
|
144
|
+
*/
|
|
145
|
+
export function buildReviewedMcpProtectedResourceMetadata(origin = OPENAI_REVIEW_ORIGIN) {
|
|
146
|
+
const normalizedOrigin = new URL(origin).origin;
|
|
147
|
+
return {
|
|
148
|
+
resource: endpoint(normalizedOrigin, OAUTH_PROVIDER_REVIEW_OPTIONS.apiRoute),
|
|
149
|
+
authorization_servers: [normalizedOrigin],
|
|
150
|
+
scopes_supported: [...OAUTH_PROVIDER_REVIEW_OPTIONS.scopesSupported],
|
|
151
|
+
bearer_methods_supported: ["header"],
|
|
152
|
+
resource_name: OAUTH_PROVIDER_REVIEW_OPTIONS.resourceMetadata.resource_name,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
93
155
|
/**
|
|
94
156
|
* Materialize the public metadata generated by workers-oauth-provider 0.3.0
|
|
95
|
-
* from the same options used by the Worker
|
|
96
|
-
*
|
|
157
|
+
* from the same options used by the Worker, plus the path-inserted document
|
|
158
|
+
* the Worker serves itself. The dependency version is pinned separately in
|
|
159
|
+
* the canonical descriptor snapshot.
|
|
97
160
|
*/
|
|
98
161
|
export function buildOpenAIReviewOAuthContract(origin = OPENAI_REVIEW_ORIGIN) {
|
|
99
162
|
const normalizedOrigin = new URL(origin).origin;
|
|
100
163
|
const authorizationEndpoint = endpoint(normalizedOrigin, OAUTH_PROVIDER_REVIEW_OPTIONS.authorizeEndpoint);
|
|
101
164
|
const tokenEndpoint = endpoint(normalizedOrigin, OAUTH_PROVIDER_REVIEW_OPTIONS.tokenEndpoint);
|
|
102
165
|
const registrationEndpoint = endpoint(normalizedOrigin, OAUTH_PROVIDER_REVIEW_OPTIONS.clientRegistrationEndpoint);
|
|
103
|
-
const resourceMetadataUrl = endpoint(normalizedOrigin,
|
|
166
|
+
const resourceMetadataUrl = endpoint(normalizedOrigin, OPENAI_REVIEW_MCP_RESOURCE_METADATA_PATH);
|
|
104
167
|
return {
|
|
105
168
|
authorizationServer: {
|
|
106
169
|
issuer: normalizedOrigin,
|
|
@@ -127,10 +190,91 @@ export function buildOpenAIReviewOAuthContract(origin = OPENAI_REVIEW_ORIGIN) {
|
|
|
127
190
|
bearer_methods_supported: ["header"],
|
|
128
191
|
resource_name: OAUTH_PROVIDER_REVIEW_OPTIONS.resourceMetadata.resource_name,
|
|
129
192
|
},
|
|
193
|
+
protectedResourceMcp: buildReviewedMcpProtectedResourceMetadata(normalizedOrigin),
|
|
194
|
+
openidConfiguration: {
|
|
195
|
+
issuer: normalizedOrigin,
|
|
196
|
+
authorization_endpoint: authorizationEndpoint,
|
|
197
|
+
token_endpoint: tokenEndpoint,
|
|
198
|
+
userinfo_endpoint: endpoint(normalizedOrigin, "/userinfo"),
|
|
199
|
+
registration_endpoint: registrationEndpoint,
|
|
200
|
+
scopes_supported: [...OPENAI_REVIEW_OAUTH_SCOPES],
|
|
201
|
+
response_types_supported: ["code"],
|
|
202
|
+
response_modes_supported: ["query"],
|
|
203
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
204
|
+
subject_types_supported: ["public"],
|
|
205
|
+
token_endpoint_auth_methods_supported: [
|
|
206
|
+
"client_secret_basic",
|
|
207
|
+
"client_secret_post",
|
|
208
|
+
"none",
|
|
209
|
+
],
|
|
210
|
+
code_challenge_methods_supported: ["S256"],
|
|
211
|
+
},
|
|
130
212
|
wwwAuthenticate: {
|
|
131
213
|
resourceMetadataUrl,
|
|
132
214
|
missingTokenHeader: buildOpenAIUnauthorizedChallenge(normalizedOrigin),
|
|
133
215
|
},
|
|
134
216
|
};
|
|
135
217
|
}
|
|
218
|
+
function isRecord(value) {
|
|
219
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
220
|
+
}
|
|
221
|
+
function isVerifiedEmail(email, verified) {
|
|
222
|
+
return verified === true
|
|
223
|
+
&& typeof email === "string"
|
|
224
|
+
&& email.length >= 3
|
|
225
|
+
&& email.length <= 320
|
|
226
|
+
&& email === email.trim()
|
|
227
|
+
&& !/[\u0000-\u001f\u007f]/u.test(email)
|
|
228
|
+
&& /^[^@\s]+@[^@\s]+$/u.test(email);
|
|
229
|
+
}
|
|
230
|
+
export function isVerifiedOpenAIIdentity(value) {
|
|
231
|
+
return typeof value.uid === "string"
|
|
232
|
+
&& value.uid.length > 0
|
|
233
|
+
&& value.uid.length <= 128
|
|
234
|
+
&& isVerifiedEmail(value.email, value.emailVerified);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Validate the provider's opaque access-token summary before serving OIDC
|
|
238
|
+
* UserInfo. The provider already verifies the encrypted token and KV expiry;
|
|
239
|
+
* this second boundary deliberately rechecks every OpenAI-specific binding.
|
|
240
|
+
*/
|
|
241
|
+
export function buildOpenAIUserInfo(token, nowEpochSeconds = Math.floor(Date.now() / 1000), boundary = {
|
|
242
|
+
accessProfile: "openai",
|
|
243
|
+
origin: OPENAI_REVIEW_ORIGIN,
|
|
244
|
+
acceptedResources: OPENAI_REVIEW_OAUTH_RESOURCES,
|
|
245
|
+
}) {
|
|
246
|
+
const audience = token?.audience;
|
|
247
|
+
if (!token
|
|
248
|
+
|| !Number.isSafeInteger(token.expiresAt)
|
|
249
|
+
|| token.expiresAt <= nowEpochSeconds
|
|
250
|
+
|| typeof audience !== "string"
|
|
251
|
+
|| !boundary.acceptedResources.includes(audience)
|
|
252
|
+
|| !hasExactOpenAIReviewScopes(token.scope)
|
|
253
|
+
|| !hasExactOpenAIReviewScopes(token.grant?.scope)
|
|
254
|
+
|| !isRecord(token.grant?.props)) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
const props = token.grant.props;
|
|
258
|
+
const identity = {
|
|
259
|
+
uid: props.userId,
|
|
260
|
+
email: props.email,
|
|
261
|
+
emailVerified: props.emailVerified,
|
|
262
|
+
};
|
|
263
|
+
if (props.accessProfile !== boundary.accessProfile
|
|
264
|
+
|| props.authMethod !== "oauth"
|
|
265
|
+
|| props.oauthIssuer !== boundary.origin
|
|
266
|
+
|| props.oauthResource !== boundary.origin
|
|
267
|
+
|| props.oauthAudience !== audience
|
|
268
|
+
|| props.oauthScope !== FRIHET_CONNECTOR_SCOPE
|
|
269
|
+
|| !hasExactOpenAIReviewScopes(props.oauthScopes)
|
|
270
|
+
|| token.userId !== props.userId
|
|
271
|
+
|| !isVerifiedOpenAIIdentity(identity)) {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
sub: identity.uid,
|
|
276
|
+
email: identity.email,
|
|
277
|
+
email_verified: true,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
136
280
|
//# sourceMappingURL=openai-review-oauth.js.map
|
|
@@ -186,34 +186,21 @@ export function registerAllPrompts(server) {
|
|
|
186
186
|
role: "user",
|
|
187
187
|
content: {
|
|
188
188
|
type: "text",
|
|
189
|
-
text: `Help me
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
` - 16-30 days overdue: Firmer tone, request confirmation of payment date\n` +
|
|
205
|
-
` - 31-60 days overdue: Escalation notice, mention potential late fees\n` +
|
|
206
|
-
` - 60+ days overdue: Final notice, mention potential debt collection\n` +
|
|
207
|
-
` - Provide both English and Spanish versions\n\n` +
|
|
208
|
-
`4. SUGGEST ACTIONS\n` +
|
|
209
|
-
` - Recommend which invoices to update to "overdue" status if still "sent"\n` +
|
|
210
|
-
` - Suggest setting up webhook notifications for future overdue invoices\n` +
|
|
211
|
-
` - Flag any clients with multiple overdue invoices (potential bad debt risk)\n\n` +
|
|
212
|
-
`5. SUMMARY\n` +
|
|
213
|
-
` - Total overdue amount\n` +
|
|
214
|
-
` - Number of clients affected\n` +
|
|
215
|
-
` - Oldest unpaid invoice\n` +
|
|
216
|
-
` - Recommended priority order for follow-up`,
|
|
189
|
+
text: `Help me prepare overdue invoice follow-ups for review.
|
|
190
|
+
|
|
191
|
+
1. GATHER RECORDS
|
|
192
|
+
- Call list_invoices without a status filter (it has no partial status) and select sent, partial and overdue records client-side from each returned status. Paginate every result set using the returned pagination fields until exhausted; deduplicate by invoice ID. If a page fails or results are incomplete, disclose that limitation.
|
|
193
|
+
- Use get_invoice when detail is missing. Record invoice ID/number, client, currency, due date and remaining unpaid balance after recorded payments and credits. Do not count full invoice totals as outstanding for partially paid records. If balance cannot be verified, flag it as unknown instead of assuming it.
|
|
194
|
+
- Confirm the as-of date and workspace timezone. Separate not-yet-due and due-today invoices from overdue invoices. Missing or invalid due dates need review, not an invented age. Invoice status alone does not prove delivery.
|
|
195
|
+
|
|
196
|
+
2. GROUP AND DRAFT
|
|
197
|
+
- Group verified positive overdue balances by client and currency; never sum different currencies or infer conversion rates. Show the source invoices, balances and days overdue.
|
|
198
|
+
- Use get_client to verify contact details. Draft a neutral, professional reminder in English and Spanish, listing invoice numbers, due dates, remaining balances and currencies. Do not assume why payment is late or that a debt is undisputed.
|
|
199
|
+
|
|
200
|
+
3. USER REVIEW
|
|
201
|
+
- Present drafts, recipients and supporting records for user approval before any actions, including sending messages, changing records or setting reminders.
|
|
202
|
+
- Do not automatically impose late fees, write off balances, stop work, threaten collection, assign collectible probabilities or make business decisions based on age alone. Ask the user to review disputes and their agreed terms.
|
|
203
|
+
- Summarize overdue balances per currency, clients affected, oldest verified overdue invoice and any missing evidence. Keep not-yet-due balances separate.`,
|
|
217
204
|
},
|
|
218
205
|
},
|
|
219
206
|
],
|
|
@@ -460,7 +447,7 @@ export function registerAllPrompts(server) {
|
|
|
460
447
|
// -- invoice-aging-review --
|
|
461
448
|
server.registerPrompt("invoice-aging-review", {
|
|
462
449
|
title: "Invoice Aging Review",
|
|
463
|
-
description: "Accounts receivable aging analysis:
|
|
450
|
+
description: "Accounts receivable aging analysis: separate not-yet-due balances from overdue buckets by currency, " +
|
|
464
451
|
"identify top debtors, and suggest collection actions. " +
|
|
465
452
|
"/ Análisis de antigüedad de cuentas por cobrar: agrupar facturas impagadas por tramos, " +
|
|
466
453
|
"identificar mayores deudores y sugerir acciones de cobro.",
|
|
@@ -470,55 +457,22 @@ export function registerAllPrompts(server) {
|
|
|
470
457
|
role: "user",
|
|
471
458
|
content: {
|
|
472
459
|
type: "text",
|
|
473
|
-
text: `
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
` - List of invoices in that bucket\n\n` +
|
|
490
|
-
`3. TOP DEBTORS\n` +
|
|
491
|
-
` - Use get_client for each client with unpaid invoices to get their details\n` +
|
|
492
|
-
` - Rank clients by total outstanding amount (highest first)\n` +
|
|
493
|
-
` - For each top debtor, show:\n` +
|
|
494
|
-
` - Client name and contact info\n` +
|
|
495
|
-
` - Number of unpaid invoices\n` +
|
|
496
|
-
` - Total outstanding amount\n` +
|
|
497
|
-
` - Oldest unpaid invoice date\n` +
|
|
498
|
-
` - Average days overdue\n\n` +
|
|
499
|
-
`4. COLLECTION ACTIONS\n` +
|
|
500
|
-
` Suggest specific actions for each bucket:\n` +
|
|
501
|
-
` - CURRENT (0-30 days): No action needed, monitor normally\n` +
|
|
502
|
-
` - 31-60 DAYS: Send a friendly payment reminder (use overdue-followup prompt pattern)\n` +
|
|
503
|
-
` - 61-90 DAYS: Escalate — phone call + formal written reminder,\n` +
|
|
504
|
-
` consider pausing new work for this client\n` +
|
|
505
|
-
` - 90+ DAYS: Final notice before debt collection,\n` +
|
|
506
|
-
` evaluate write-off vs collection agency cost,\n` +
|
|
507
|
-
` stop all new work for this client\n\n` +
|
|
508
|
-
`5. WRITE-OFF CANDIDATES\n` +
|
|
509
|
-
` - Flag any invoices 90+ days overdue with amounts under €100 — likely not worth pursuing\n` +
|
|
510
|
-
` - For larger amounts 90+ days, suggest formal demand letter before write-off\n` +
|
|
511
|
-
` - Note: written-off invoices may still be tax-deductible as bad debt\n` +
|
|
512
|
-
` (requires documentation of collection attempts)\n\n` +
|
|
513
|
-
`6. SUMMARY DASHBOARD\n` +
|
|
514
|
-
` Present a clear overview:\n` +
|
|
515
|
-
` - Total accounts receivable (all unpaid invoices)\n` +
|
|
516
|
-
` - Breakdown by aging bucket (amount and % of total)\n` +
|
|
517
|
-
` - Weighted collection estimate:\n` +
|
|
518
|
-
` Current: 95% collectible, 31-60: 80%, 61-90: 50%, 90+: 20%\n` +
|
|
519
|
-
` - Expected collectible amount vs total outstanding\n` +
|
|
520
|
-
` - Number of clients with overdue invoices\n` +
|
|
521
|
-
` - Most urgent actions to take (prioritized list)`,
|
|
460
|
+
text: `Prepare an accounts receivable aging review.
|
|
461
|
+
|
|
462
|
+
1. VERIFY THE DATA
|
|
463
|
+
- Call list_invoices without a status filter (it has no partial status) and select sent, partial and overdue records client-side from each returned status. Paginate every result set using the returned pagination fields until exhausted; deduplicate by invoice ID. Report any failed page or incomplete coverage.
|
|
464
|
+
- Use get_invoice when needed to verify currency, due date and remaining unpaid balance after recorded payments and credits. Do not count full invoice totals as outstanding for partially paid invoices. Mark unverifiable balances as unknown; do not invent totals.
|
|
465
|
+
- Confirm the as-of date and workspace timezone. Exclude settled, draft and cancelled records from receivables. Status alone does not establish delivery or an undisputed debt.
|
|
466
|
+
|
|
467
|
+
2. AGE VERIFIED BALANCES
|
|
468
|
+
- Keep separate buckets: not-yet-due; due today; 1-30 days overdue; 31-60 days overdue; 61-90 days overdue; more than 90 days overdue. Missing or invalid due dates form an unknown-age bucket.
|
|
469
|
+
- Group by currency. For each bucket, show invoice IDs, count, remaining unpaid balance and percentage of that currency's verified receivables (only when the denominator is positive). Never combine currencies without a user-supplied conversion basis.
|
|
470
|
+
- Use get_client for client details and summarize positive balances by client within each currency, showing oldest due dates and missing evidence.
|
|
471
|
+
|
|
472
|
+
3. REVIEW BEFORE ACTING
|
|
473
|
+
- Present the per-currency report and possible follow-up drafts using overdue-followup for user approval before any actions. A report is not permission to send, change status or create charges.
|
|
474
|
+
- Do not assign arbitrary collectible probabilities, automatically write off balances, impose late fees, stop work or make business decisions from age or amount alone. Ask for the user's terms, dispute context and decision before proposing an action.
|
|
475
|
+
- Keep not-yet-due amounts separate from overdue exposure and disclose unknown balances and incomplete pagination.`,
|
|
522
476
|
},
|
|
523
477
|
},
|
|
524
478
|
],
|
package/dist/redaction.d.ts
CHANGED
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
*/
|
|
18
18
|
/** Sentinel left in place of a redacted value in cloned (tracing) payloads. */
|
|
19
19
|
export declare const REDACTED = "[redacted]";
|
|
20
|
+
/**
|
|
21
|
+
* Return a backend request id only when it is a short opaque token that is
|
|
22
|
+
* safe to echo to an agent (no whitespace, no prose, no instructions).
|
|
23
|
+
*/
|
|
24
|
+
export declare function safeRequestId(value: unknown): string | undefined;
|
|
20
25
|
/**
|
|
21
26
|
* Allowlist short, single-line human remediation prose for agent display.
|
|
22
27
|
*
|
package/dist/redaction.js
CHANGED
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
*/
|
|
18
18
|
/** Sentinel left in place of a redacted value in cloned (tracing) payloads. */
|
|
19
19
|
export const REDACTED = "[redacted]";
|
|
20
|
+
/** Opaque backend correlation id (publicApi `meta.requestId` / `X-Request-Id`). */
|
|
21
|
+
const REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
22
|
+
/**
|
|
23
|
+
* Return a backend request id only when it is a short opaque token that is
|
|
24
|
+
* safe to echo to an agent (no whitespace, no prose, no instructions).
|
|
25
|
+
*/
|
|
26
|
+
export function safeRequestId(value) {
|
|
27
|
+
return typeof value === "string" && REQUEST_ID_PATTERN.test(value) ? value : undefined;
|
|
28
|
+
}
|
|
20
29
|
const MAX_SERVER_REMEDIATION_CHARS = 500;
|
|
21
30
|
const SERVER_REMEDIATION_PROSE_ALPHABET = /^[\p{L}\p{N} .,;:!?'"()_/*+\-]+$/u;
|
|
22
31
|
const LIVE_EINVOICE_SCOPE_REMEDIATION = "This API key requires the einvoice:read or einvoice:write scope to access e-invoicing endpoints. Add the scope when creating or updating the key.";
|