@absolutejs/mcp 0.15.1 → 0.16.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/CHANGELOG.md +6 -0
- package/README.md +2 -0
- package/changelog.json +10 -0
- package/dist/commerce.js +223 -0
- package/dist/index.js +276 -53
- package/dist/src/billingTools.d.ts +30 -0
- package/dist/src/commerce.d.ts +1 -0
- package/dist/src/index.d.ts +1 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ This file is generated by `absolute-changelog` from the entries in
|
|
|
6
6
|
`changelog/`. Edit an entry, not this file — and add new ones under
|
|
7
7
|
`changelog/unreleased/`.
|
|
8
8
|
|
|
9
|
+
## 0.16.0 — 2026-09-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Add account-bound billing status, paginated receipts, bounded usage and reviewed billing-management links**
|
|
14
|
+
|
|
9
15
|
## 0.15.1 — 2026-09-11
|
|
10
16
|
|
|
11
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -431,3 +431,5 @@ on the Change Date.
|
|
|
431
431
|
## Secure credit checkout
|
|
432
432
|
|
|
433
433
|
`createCheckoutHandoffTool` and `createPurchaseStatusTool` provide account-bound credit checkout and recovery contracts. The issuer must bind server pricing and identity; route these tools through the commerce guard. Checkout is classified as `external_checkout`, so restricted and unverified channels cannot discover or execute it. Status works at zero credits. Never pass card data in tool input. See [host rules](docs/commerce-host-rules.md).
|
|
434
|
+
|
|
435
|
+
`createBillingReportTools` binds billing status, paginated receipts, and bounded UTC usage reports to authenticated reader callbacks. These non-transactional tools remain available at zero balance. The shared billing projections discard payment/provider secrets and enforce reconciled usage breakdowns. `createBillingManagementTool` returns a fixed HTTPS browser page requiring normal browser authentication; because that page can initiate purchases, it retains `external_checkout` commerce classification. Never label a purchase-capable page informational to bypass host restrictions.
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/mcp",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.16.0",
|
|
7
|
+
"date": "2026-09-11",
|
|
8
|
+
"changes": [
|
|
9
|
+
{
|
|
10
|
+
"kind": "added",
|
|
11
|
+
"summary": "Add account-bound billing status, paginated receipts, bounded usage and reviewed billing-management links"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
},
|
|
5
15
|
{
|
|
6
16
|
"version": "0.15.1",
|
|
7
17
|
"date": "2026-09-11",
|
package/dist/commerce.js
CHANGED
|
@@ -145,6 +145,227 @@ var createPurchaseStatusTool = (options) => ({
|
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
|
+
// node_modules/@absolutejs/billing/dist/reports.js
|
|
149
|
+
var DAY_MS = 86400000;
|
|
150
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
151
|
+
var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
152
|
+
var only = (input, keys) => {
|
|
153
|
+
if (!record2(input) || Object.keys(input).some((key) => !keys.includes(key)))
|
|
154
|
+
throw new Error("Invalid report input");
|
|
155
|
+
return input;
|
|
156
|
+
};
|
|
157
|
+
var integer = (value) => {
|
|
158
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
159
|
+
throw new Error("Invalid report amount");
|
|
160
|
+
return value;
|
|
161
|
+
};
|
|
162
|
+
var iso = (value) => {
|
|
163
|
+
if (!Number.isFinite(Date.parse(value)) || new Date(value).toISOString() !== value)
|
|
164
|
+
throw new Error("Invalid report timestamp");
|
|
165
|
+
return value;
|
|
166
|
+
};
|
|
167
|
+
var day = (value) => {
|
|
168
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value) || new Date(value + "T00:00:00.000Z").toISOString().slice(0, 10) !== value)
|
|
169
|
+
throw new Error("Expected a UTC calendar date");
|
|
170
|
+
return value;
|
|
171
|
+
};
|
|
172
|
+
var parseUsageRange = (input, now = new Date) => {
|
|
173
|
+
const args = only(input, ["from", "to"]);
|
|
174
|
+
const tomorrow = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) + DAY_MS);
|
|
175
|
+
const to = day(args.to ?? tomorrow.toISOString().slice(0, 10));
|
|
176
|
+
const from = day(args.from ?? new Date(Date.parse(to) - 30 * DAY_MS).toISOString().slice(0, 10));
|
|
177
|
+
const duration = Date.parse(to) - Date.parse(from);
|
|
178
|
+
if (duration <= 0 || duration > 90 * DAY_MS || Date.parse(to) > tomorrow.getTime())
|
|
179
|
+
throw new Error("Choose a range of 1\u201390 UTC days, ending no later than tomorrow");
|
|
180
|
+
return { from, to };
|
|
181
|
+
};
|
|
182
|
+
var parseReceiptPage = (input) => {
|
|
183
|
+
const args = only(input, ["limit", "cursor"]);
|
|
184
|
+
const limit = args.limit ?? 20;
|
|
185
|
+
if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > 50)
|
|
186
|
+
throw new Error("Receipt limit must be 1\u201350");
|
|
187
|
+
if (args.cursor === undefined || args.cursor === null)
|
|
188
|
+
return { limit, cursor: null };
|
|
189
|
+
if (typeof args.cursor !== "string" || args.cursor.length > 256 || !/^[A-Za-z0-9_-]+$/.test(args.cursor))
|
|
190
|
+
throw new Error("Invalid receipt cursor");
|
|
191
|
+
const decoded = JSON.parse(Buffer.from(args.cursor, "base64url").toString("utf8"));
|
|
192
|
+
if (!record2(decoded) || typeof decoded.at !== "string" || typeof decoded.id !== "string" || !UUID.test(decoded.id))
|
|
193
|
+
throw new Error("Invalid receipt cursor");
|
|
194
|
+
return { limit, cursor: { at: iso(decoded.at), id: decoded.id } };
|
|
195
|
+
};
|
|
196
|
+
var projectBillingStatus = (value) => ({
|
|
197
|
+
portalAccess: value.portalAccess === true,
|
|
198
|
+
subscription: value.subscription ? {
|
|
199
|
+
status: String(value.subscription.status).slice(0, 64),
|
|
200
|
+
renewsAt: value.subscription.renewsAt === null ? null : iso(value.subscription.renewsAt),
|
|
201
|
+
cancelAtPeriodEnd: value.subscription.cancelAtPeriodEnd === true
|
|
202
|
+
} : null,
|
|
203
|
+
credits: {
|
|
204
|
+
remaining: integer(value.credits.remaining),
|
|
205
|
+
reserved: integer(value.credits.reserved),
|
|
206
|
+
purchased: integer(value.credits.purchased),
|
|
207
|
+
promotional: integer(value.credits.promotional),
|
|
208
|
+
debt: integer(value.credits.debt)
|
|
209
|
+
},
|
|
210
|
+
automaticRefill: false
|
|
211
|
+
});
|
|
212
|
+
var projectReceiptPage = (value, limit) => {
|
|
213
|
+
if (value.receipts.length > limit)
|
|
214
|
+
throw new Error("Receipt page exceeds its limit");
|
|
215
|
+
if (value.nextCursor !== null)
|
|
216
|
+
parseReceiptPage({ cursor: value.nextCursor });
|
|
217
|
+
return {
|
|
218
|
+
receipts: value.receipts.map((row) => {
|
|
219
|
+
if (!UUID.test(row.id) || !/^[A-Z]{3}$/.test(row.currency) || !["credit_purchase", "initial", "plan_change", "renewal"].includes(row.source) || !["paid", "partially_refunded", "refunded"].includes(row.status) || row.refundedAmountCents > row.amountCents)
|
|
220
|
+
throw new Error("Invalid receipt");
|
|
221
|
+
return {
|
|
222
|
+
id: row.id,
|
|
223
|
+
issuedAt: iso(row.issuedAt),
|
|
224
|
+
currency: row.currency,
|
|
225
|
+
amountCents: integer(row.amountCents),
|
|
226
|
+
refundedAmountCents: integer(row.refundedAmountCents),
|
|
227
|
+
source: row.source,
|
|
228
|
+
status: row.status
|
|
229
|
+
};
|
|
230
|
+
}),
|
|
231
|
+
nextCursor: value.nextCursor
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
var projectUsageReport = (value, range) => {
|
|
235
|
+
if (value.from !== range.from || value.to !== range.to || value.byDay.length > 90 || value.byFeature.length > 21)
|
|
236
|
+
throw new Error("Invalid usage report bounds");
|
|
237
|
+
const byDay = value.byDay.map((row) => ({
|
|
238
|
+
day: day(row.day),
|
|
239
|
+
credits: integer(row.credits),
|
|
240
|
+
events: integer(row.events)
|
|
241
|
+
}));
|
|
242
|
+
const byFeature = value.byFeature.map((row) => ({
|
|
243
|
+
feature: row.feature.slice(0, 128),
|
|
244
|
+
credits: integer(row.credits),
|
|
245
|
+
events: integer(row.events)
|
|
246
|
+
}));
|
|
247
|
+
const credits = integer(value.creditsConsumed), events = integer(value.events);
|
|
248
|
+
for (const rows of [byDay, byFeature])
|
|
249
|
+
if (rows.reduce((sum, row) => sum + row.credits, 0) !== credits || rows.reduce((sum, row) => sum + row.events, 0) !== events)
|
|
250
|
+
throw new Error("Usage breakdown does not reconcile");
|
|
251
|
+
if (new Set(byDay.map((row) => row.day)).size !== byDay.length || byDay.some((row) => row.day < range.from || row.day >= range.to))
|
|
252
|
+
throw new Error("Invalid usage days");
|
|
253
|
+
return {
|
|
254
|
+
from: range.from,
|
|
255
|
+
to: range.to,
|
|
256
|
+
creditsConsumed: credits,
|
|
257
|
+
events,
|
|
258
|
+
byDay,
|
|
259
|
+
byFeature
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// src/billingTools.ts
|
|
264
|
+
var result = (text, data) => ({
|
|
265
|
+
content: [{ type: "text", text }],
|
|
266
|
+
structuredContent: { ...data }
|
|
267
|
+
});
|
|
268
|
+
var empty = (input) => {
|
|
269
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).length)
|
|
270
|
+
throw new Error("This tool takes no arguments");
|
|
271
|
+
};
|
|
272
|
+
var createBillingReportTools = (readers) => ({
|
|
273
|
+
get_billing_status: {
|
|
274
|
+
annotations: {
|
|
275
|
+
readOnlyHint: true,
|
|
276
|
+
destructiveHint: false,
|
|
277
|
+
openWorldHint: false
|
|
278
|
+
},
|
|
279
|
+
commerce: {
|
|
280
|
+
action: "entitlement_status",
|
|
281
|
+
categories: ["usage_credits", "subscription"]
|
|
282
|
+
},
|
|
283
|
+
description: "Read subscription and service-credit status for this account, including reserved credits and debt. Does not charge, renew or cancel anything. Available at zero balance.",
|
|
284
|
+
inputSchema: {
|
|
285
|
+
type: "object",
|
|
286
|
+
properties: {},
|
|
287
|
+
additionalProperties: false
|
|
288
|
+
},
|
|
289
|
+
handler: async (input) => {
|
|
290
|
+
empty(input);
|
|
291
|
+
const status = projectBillingStatus(await readers.status());
|
|
292
|
+
return result(`${status.credits.remaining} service credits available, ${status.credits.reserved} reserved. Portal access: ${status.portalAccess ? "enabled" : "not enabled"}. Automatic refill is off.`, status);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
list_receipts: {
|
|
296
|
+
annotations: {
|
|
297
|
+
readOnlyHint: true,
|
|
298
|
+
destructiveHint: false,
|
|
299
|
+
openWorldHint: false
|
|
300
|
+
},
|
|
301
|
+
commerce: {
|
|
302
|
+
action: "entitlement_status",
|
|
303
|
+
categories: ["usage_credits", "subscription"]
|
|
304
|
+
},
|
|
305
|
+
description: "List this account's payment receipts, newest first, with original amounts and refunds recorded to date. Amounts are currency minor units, not consumed credits. Follow nextCursor for more. No payment credentials or provider references are exposed.",
|
|
306
|
+
inputSchema: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: {
|
|
309
|
+
limit: { type: "integer", minimum: 1, maximum: 50, default: 20 },
|
|
310
|
+
cursor: { type: ["string", "null"], maxLength: 256 }
|
|
311
|
+
},
|
|
312
|
+
additionalProperties: false
|
|
313
|
+
},
|
|
314
|
+
handler: async (input) => {
|
|
315
|
+
const request = parseReceiptPage(input);
|
|
316
|
+
const page = projectReceiptPage(await readers.receipts(request), request.limit);
|
|
317
|
+
return result(`${page.receipts.length} receipts.${page.nextCursor ? " More receipts are available using nextCursor." : ""}`, page);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
get_usage_report: {
|
|
321
|
+
annotations: {
|
|
322
|
+
readOnlyHint: true,
|
|
323
|
+
destructiveHint: false,
|
|
324
|
+
openWorldHint: false
|
|
325
|
+
},
|
|
326
|
+
commerce: { action: "entitlement_status", categories: ["usage_credits"] },
|
|
327
|
+
description: "Read recorded service-credit consumption by UTC day and feature. from is inclusive and to is exclusive; at most 90 days, default last 30 days including today. Credits consumed are not dollars paid or the assistant's own tokens. Does not estimate ROI or expose internal provider costs.",
|
|
328
|
+
inputSchema: {
|
|
329
|
+
type: "object",
|
|
330
|
+
properties: {
|
|
331
|
+
from: { type: "string", format: "date" },
|
|
332
|
+
to: { type: "string", format: "date" }
|
|
333
|
+
},
|
|
334
|
+
additionalProperties: false
|
|
335
|
+
},
|
|
336
|
+
handler: async (input) => {
|
|
337
|
+
const range = parseUsageRange(input, readers.now?.());
|
|
338
|
+
const usage = projectUsageReport(await readers.usage(range), range);
|
|
339
|
+
return result(`${usage.creditsConsumed} service credits across ${usage.events} recorded events, ${range.from} inclusive to ${range.to} exclusive (UTC).`, usage);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
var createBillingManagementTool = (destination) => {
|
|
344
|
+
const url = new URL(destination);
|
|
345
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
|
|
346
|
+
throw new Error("Billing management requires a fixed HTTPS page URL");
|
|
347
|
+
return {
|
|
348
|
+
annotations: {
|
|
349
|
+
readOnlyHint: true,
|
|
350
|
+
destructiveHint: false,
|
|
351
|
+
openWorldHint: true
|
|
352
|
+
},
|
|
353
|
+
commerce: {
|
|
354
|
+
action: "external_checkout",
|
|
355
|
+
categories: ["usage_credits", "subscription"]
|
|
356
|
+
},
|
|
357
|
+
description: "Open the service's authenticated billing page to buy credits, review receipts, or manage an existing subscription. Uses a matching browser login; may require sign-in. This link does not grant a login session or authorize any payment.",
|
|
358
|
+
inputSchema: {
|
|
359
|
+
type: "object",
|
|
360
|
+
properties: {},
|
|
361
|
+
additionalProperties: false
|
|
362
|
+
},
|
|
363
|
+
handler: async (input) => {
|
|
364
|
+
empty(input);
|
|
365
|
+
return result(`Manage billing on ${url.hostname}: ${url.href}. Sign in if needed; changes require your confirmation.`, { url: url.href, requiresBrowserAuthentication: true });
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
};
|
|
148
369
|
|
|
149
370
|
// src/commerce.ts
|
|
150
371
|
var COMMERCE_POLICY_VERSION = "2026-09-10.1";
|
|
@@ -254,6 +475,8 @@ var evaluateCommerce = (req, context, now = new Date) => {
|
|
|
254
475
|
export {
|
|
255
476
|
COMMERCE_POLICY_SOURCES,
|
|
256
477
|
COMMERCE_POLICY_VERSION,
|
|
478
|
+
createBillingManagementTool,
|
|
479
|
+
createBillingReportTools,
|
|
257
480
|
createCheckoutHandoffTool,
|
|
258
481
|
createCreditBalanceTool,
|
|
259
482
|
createPurchaseStatusTool,
|
package/dist/index.js
CHANGED
|
@@ -145,6 +145,227 @@ var createPurchaseStatusTool = (options) => ({
|
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
|
+
// node_modules/@absolutejs/billing/dist/reports.js
|
|
149
|
+
var DAY_MS = 86400000;
|
|
150
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
151
|
+
var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
152
|
+
var only = (input, keys) => {
|
|
153
|
+
if (!record2(input) || Object.keys(input).some((key) => !keys.includes(key)))
|
|
154
|
+
throw new Error("Invalid report input");
|
|
155
|
+
return input;
|
|
156
|
+
};
|
|
157
|
+
var integer = (value) => {
|
|
158
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
159
|
+
throw new Error("Invalid report amount");
|
|
160
|
+
return value;
|
|
161
|
+
};
|
|
162
|
+
var iso = (value) => {
|
|
163
|
+
if (!Number.isFinite(Date.parse(value)) || new Date(value).toISOString() !== value)
|
|
164
|
+
throw new Error("Invalid report timestamp");
|
|
165
|
+
return value;
|
|
166
|
+
};
|
|
167
|
+
var day = (value) => {
|
|
168
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value) || new Date(value + "T00:00:00.000Z").toISOString().slice(0, 10) !== value)
|
|
169
|
+
throw new Error("Expected a UTC calendar date");
|
|
170
|
+
return value;
|
|
171
|
+
};
|
|
172
|
+
var parseUsageRange = (input, now = new Date) => {
|
|
173
|
+
const args = only(input, ["from", "to"]);
|
|
174
|
+
const tomorrow = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) + DAY_MS);
|
|
175
|
+
const to = day(args.to ?? tomorrow.toISOString().slice(0, 10));
|
|
176
|
+
const from = day(args.from ?? new Date(Date.parse(to) - 30 * DAY_MS).toISOString().slice(0, 10));
|
|
177
|
+
const duration = Date.parse(to) - Date.parse(from);
|
|
178
|
+
if (duration <= 0 || duration > 90 * DAY_MS || Date.parse(to) > tomorrow.getTime())
|
|
179
|
+
throw new Error("Choose a range of 1\u201390 UTC days, ending no later than tomorrow");
|
|
180
|
+
return { from, to };
|
|
181
|
+
};
|
|
182
|
+
var parseReceiptPage = (input) => {
|
|
183
|
+
const args = only(input, ["limit", "cursor"]);
|
|
184
|
+
const limit = args.limit ?? 20;
|
|
185
|
+
if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > 50)
|
|
186
|
+
throw new Error("Receipt limit must be 1\u201350");
|
|
187
|
+
if (args.cursor === undefined || args.cursor === null)
|
|
188
|
+
return { limit, cursor: null };
|
|
189
|
+
if (typeof args.cursor !== "string" || args.cursor.length > 256 || !/^[A-Za-z0-9_-]+$/.test(args.cursor))
|
|
190
|
+
throw new Error("Invalid receipt cursor");
|
|
191
|
+
const decoded = JSON.parse(Buffer.from(args.cursor, "base64url").toString("utf8"));
|
|
192
|
+
if (!record2(decoded) || typeof decoded.at !== "string" || typeof decoded.id !== "string" || !UUID.test(decoded.id))
|
|
193
|
+
throw new Error("Invalid receipt cursor");
|
|
194
|
+
return { limit, cursor: { at: iso(decoded.at), id: decoded.id } };
|
|
195
|
+
};
|
|
196
|
+
var projectBillingStatus = (value) => ({
|
|
197
|
+
portalAccess: value.portalAccess === true,
|
|
198
|
+
subscription: value.subscription ? {
|
|
199
|
+
status: String(value.subscription.status).slice(0, 64),
|
|
200
|
+
renewsAt: value.subscription.renewsAt === null ? null : iso(value.subscription.renewsAt),
|
|
201
|
+
cancelAtPeriodEnd: value.subscription.cancelAtPeriodEnd === true
|
|
202
|
+
} : null,
|
|
203
|
+
credits: {
|
|
204
|
+
remaining: integer(value.credits.remaining),
|
|
205
|
+
reserved: integer(value.credits.reserved),
|
|
206
|
+
purchased: integer(value.credits.purchased),
|
|
207
|
+
promotional: integer(value.credits.promotional),
|
|
208
|
+
debt: integer(value.credits.debt)
|
|
209
|
+
},
|
|
210
|
+
automaticRefill: false
|
|
211
|
+
});
|
|
212
|
+
var projectReceiptPage = (value, limit) => {
|
|
213
|
+
if (value.receipts.length > limit)
|
|
214
|
+
throw new Error("Receipt page exceeds its limit");
|
|
215
|
+
if (value.nextCursor !== null)
|
|
216
|
+
parseReceiptPage({ cursor: value.nextCursor });
|
|
217
|
+
return {
|
|
218
|
+
receipts: value.receipts.map((row) => {
|
|
219
|
+
if (!UUID.test(row.id) || !/^[A-Z]{3}$/.test(row.currency) || !["credit_purchase", "initial", "plan_change", "renewal"].includes(row.source) || !["paid", "partially_refunded", "refunded"].includes(row.status) || row.refundedAmountCents > row.amountCents)
|
|
220
|
+
throw new Error("Invalid receipt");
|
|
221
|
+
return {
|
|
222
|
+
id: row.id,
|
|
223
|
+
issuedAt: iso(row.issuedAt),
|
|
224
|
+
currency: row.currency,
|
|
225
|
+
amountCents: integer(row.amountCents),
|
|
226
|
+
refundedAmountCents: integer(row.refundedAmountCents),
|
|
227
|
+
source: row.source,
|
|
228
|
+
status: row.status
|
|
229
|
+
};
|
|
230
|
+
}),
|
|
231
|
+
nextCursor: value.nextCursor
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
var projectUsageReport = (value, range) => {
|
|
235
|
+
if (value.from !== range.from || value.to !== range.to || value.byDay.length > 90 || value.byFeature.length > 21)
|
|
236
|
+
throw new Error("Invalid usage report bounds");
|
|
237
|
+
const byDay = value.byDay.map((row) => ({
|
|
238
|
+
day: day(row.day),
|
|
239
|
+
credits: integer(row.credits),
|
|
240
|
+
events: integer(row.events)
|
|
241
|
+
}));
|
|
242
|
+
const byFeature = value.byFeature.map((row) => ({
|
|
243
|
+
feature: row.feature.slice(0, 128),
|
|
244
|
+
credits: integer(row.credits),
|
|
245
|
+
events: integer(row.events)
|
|
246
|
+
}));
|
|
247
|
+
const credits = integer(value.creditsConsumed), events = integer(value.events);
|
|
248
|
+
for (const rows of [byDay, byFeature])
|
|
249
|
+
if (rows.reduce((sum, row) => sum + row.credits, 0) !== credits || rows.reduce((sum, row) => sum + row.events, 0) !== events)
|
|
250
|
+
throw new Error("Usage breakdown does not reconcile");
|
|
251
|
+
if (new Set(byDay.map((row) => row.day)).size !== byDay.length || byDay.some((row) => row.day < range.from || row.day >= range.to))
|
|
252
|
+
throw new Error("Invalid usage days");
|
|
253
|
+
return {
|
|
254
|
+
from: range.from,
|
|
255
|
+
to: range.to,
|
|
256
|
+
creditsConsumed: credits,
|
|
257
|
+
events,
|
|
258
|
+
byDay,
|
|
259
|
+
byFeature
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// src/billingTools.ts
|
|
264
|
+
var result = (text, data) => ({
|
|
265
|
+
content: [{ type: "text", text }],
|
|
266
|
+
structuredContent: { ...data }
|
|
267
|
+
});
|
|
268
|
+
var empty = (input) => {
|
|
269
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).length)
|
|
270
|
+
throw new Error("This tool takes no arguments");
|
|
271
|
+
};
|
|
272
|
+
var createBillingReportTools = (readers) => ({
|
|
273
|
+
get_billing_status: {
|
|
274
|
+
annotations: {
|
|
275
|
+
readOnlyHint: true,
|
|
276
|
+
destructiveHint: false,
|
|
277
|
+
openWorldHint: false
|
|
278
|
+
},
|
|
279
|
+
commerce: {
|
|
280
|
+
action: "entitlement_status",
|
|
281
|
+
categories: ["usage_credits", "subscription"]
|
|
282
|
+
},
|
|
283
|
+
description: "Read subscription and service-credit status for this account, including reserved credits and debt. Does not charge, renew or cancel anything. Available at zero balance.",
|
|
284
|
+
inputSchema: {
|
|
285
|
+
type: "object",
|
|
286
|
+
properties: {},
|
|
287
|
+
additionalProperties: false
|
|
288
|
+
},
|
|
289
|
+
handler: async (input) => {
|
|
290
|
+
empty(input);
|
|
291
|
+
const status = projectBillingStatus(await readers.status());
|
|
292
|
+
return result(`${status.credits.remaining} service credits available, ${status.credits.reserved} reserved. Portal access: ${status.portalAccess ? "enabled" : "not enabled"}. Automatic refill is off.`, status);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
list_receipts: {
|
|
296
|
+
annotations: {
|
|
297
|
+
readOnlyHint: true,
|
|
298
|
+
destructiveHint: false,
|
|
299
|
+
openWorldHint: false
|
|
300
|
+
},
|
|
301
|
+
commerce: {
|
|
302
|
+
action: "entitlement_status",
|
|
303
|
+
categories: ["usage_credits", "subscription"]
|
|
304
|
+
},
|
|
305
|
+
description: "List this account's payment receipts, newest first, with original amounts and refunds recorded to date. Amounts are currency minor units, not consumed credits. Follow nextCursor for more. No payment credentials or provider references are exposed.",
|
|
306
|
+
inputSchema: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: {
|
|
309
|
+
limit: { type: "integer", minimum: 1, maximum: 50, default: 20 },
|
|
310
|
+
cursor: { type: ["string", "null"], maxLength: 256 }
|
|
311
|
+
},
|
|
312
|
+
additionalProperties: false
|
|
313
|
+
},
|
|
314
|
+
handler: async (input) => {
|
|
315
|
+
const request = parseReceiptPage(input);
|
|
316
|
+
const page = projectReceiptPage(await readers.receipts(request), request.limit);
|
|
317
|
+
return result(`${page.receipts.length} receipts.${page.nextCursor ? " More receipts are available using nextCursor." : ""}`, page);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
get_usage_report: {
|
|
321
|
+
annotations: {
|
|
322
|
+
readOnlyHint: true,
|
|
323
|
+
destructiveHint: false,
|
|
324
|
+
openWorldHint: false
|
|
325
|
+
},
|
|
326
|
+
commerce: { action: "entitlement_status", categories: ["usage_credits"] },
|
|
327
|
+
description: "Read recorded service-credit consumption by UTC day and feature. from is inclusive and to is exclusive; at most 90 days, default last 30 days including today. Credits consumed are not dollars paid or the assistant's own tokens. Does not estimate ROI or expose internal provider costs.",
|
|
328
|
+
inputSchema: {
|
|
329
|
+
type: "object",
|
|
330
|
+
properties: {
|
|
331
|
+
from: { type: "string", format: "date" },
|
|
332
|
+
to: { type: "string", format: "date" }
|
|
333
|
+
},
|
|
334
|
+
additionalProperties: false
|
|
335
|
+
},
|
|
336
|
+
handler: async (input) => {
|
|
337
|
+
const range = parseUsageRange(input, readers.now?.());
|
|
338
|
+
const usage = projectUsageReport(await readers.usage(range), range);
|
|
339
|
+
return result(`${usage.creditsConsumed} service credits across ${usage.events} recorded events, ${range.from} inclusive to ${range.to} exclusive (UTC).`, usage);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
var createBillingManagementTool = (destination) => {
|
|
344
|
+
const url = new URL(destination);
|
|
345
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
|
|
346
|
+
throw new Error("Billing management requires a fixed HTTPS page URL");
|
|
347
|
+
return {
|
|
348
|
+
annotations: {
|
|
349
|
+
readOnlyHint: true,
|
|
350
|
+
destructiveHint: false,
|
|
351
|
+
openWorldHint: true
|
|
352
|
+
},
|
|
353
|
+
commerce: {
|
|
354
|
+
action: "external_checkout",
|
|
355
|
+
categories: ["usage_credits", "subscription"]
|
|
356
|
+
},
|
|
357
|
+
description: "Open the service's authenticated billing page to buy credits, review receipts, or manage an existing subscription. Uses a matching browser login; may require sign-in. This link does not grant a login session or authorize any payment.",
|
|
358
|
+
inputSchema: {
|
|
359
|
+
type: "object",
|
|
360
|
+
properties: {},
|
|
361
|
+
additionalProperties: false
|
|
362
|
+
},
|
|
363
|
+
handler: async (input) => {
|
|
364
|
+
empty(input);
|
|
365
|
+
return result(`Manage billing on ${url.hostname}: ${url.href}. Sign in if needed; changes require your confirmation.`, { url: url.href, requiresBrowserAuthentication: true });
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
};
|
|
148
369
|
|
|
149
370
|
// src/commerce.ts
|
|
150
371
|
var COMMERCE_POLICY_VERSION = "2026-09-10.1";
|
|
@@ -373,7 +594,7 @@ var createMcpClient = (options) => {
|
|
|
373
594
|
let sessionId = null;
|
|
374
595
|
let nextId = 1;
|
|
375
596
|
const authorizationHeaders = (method) => options.authorization?.headers({ method, url: options.url }) ?? {};
|
|
376
|
-
const respond = async (id,
|
|
597
|
+
const respond = async (id, result2) => {
|
|
377
598
|
const headers = {
|
|
378
599
|
"content-type": "application/json",
|
|
379
600
|
"mcp-protocol-version": protocolVersion,
|
|
@@ -383,7 +604,7 @@ var createMcpClient = (options) => {
|
|
|
383
604
|
if (sessionId !== null)
|
|
384
605
|
headers["mcp-session-id"] = sessionId;
|
|
385
606
|
await doFetch(options.url, {
|
|
386
|
-
body: JSON.stringify({ id, jsonrpc: "2.0", result }),
|
|
607
|
+
body: JSON.stringify({ id, jsonrpc: "2.0", result: result2 }),
|
|
387
608
|
headers,
|
|
388
609
|
method: "POST"
|
|
389
610
|
}).catch(() => {
|
|
@@ -404,8 +625,8 @@ var createMcpClient = (options) => {
|
|
|
404
625
|
mode: "form",
|
|
405
626
|
requestedSchema: isRecord(params.requestedSchema) ? params.requestedSchema : {}
|
|
406
627
|
};
|
|
407
|
-
const
|
|
408
|
-
await respond(message.id, request.mode === "url" &&
|
|
628
|
+
const result2 = options.onElicit ? await options.onElicit(request) : { action: "decline" };
|
|
629
|
+
await respond(message.id, request.mode === "url" && result2.action === "accept" ? { action: "accept" } : result2);
|
|
409
630
|
};
|
|
410
631
|
const rpc = async (method, params) => {
|
|
411
632
|
const controller = new AbortController;
|
|
@@ -490,7 +711,7 @@ var createMcpClient = (options) => {
|
|
|
490
711
|
});
|
|
491
712
|
};
|
|
492
713
|
const initialize = async () => {
|
|
493
|
-
const
|
|
714
|
+
const result2 = await rpc("initialize", {
|
|
494
715
|
capabilities: options.onElicit ? { elicitation: { form: {}, url: {} } } : {},
|
|
495
716
|
clientInfo: options.clientInfo ?? {
|
|
496
717
|
name: "@absolutejs/mcp",
|
|
@@ -498,19 +719,19 @@ var createMcpClient = (options) => {
|
|
|
498
719
|
},
|
|
499
720
|
protocolVersion
|
|
500
721
|
});
|
|
501
|
-
if (isRecord(
|
|
502
|
-
protocolVersion =
|
|
722
|
+
if (isRecord(result2) && typeof result2.protocolVersion === "string") {
|
|
723
|
+
protocolVersion = result2.protocolVersion;
|
|
503
724
|
}
|
|
504
725
|
await notify("notifications/initialized");
|
|
505
|
-
return isRecord(
|
|
726
|
+
return isRecord(result2) ? result2 : {};
|
|
506
727
|
};
|
|
507
728
|
const MAX_LIST_PAGES = 40;
|
|
508
729
|
const listTools = async () => {
|
|
509
730
|
const collected = [];
|
|
510
731
|
let cursor;
|
|
511
732
|
for (let page = 0;page < MAX_LIST_PAGES; page += 1) {
|
|
512
|
-
const
|
|
513
|
-
const tools = isRecord(
|
|
733
|
+
const result2 = await rpc("tools/list", cursor === undefined ? undefined : { cursor });
|
|
734
|
+
const tools = isRecord(result2) && Array.isArray(result2.tools) ? result2.tools : [];
|
|
514
735
|
collected.push(...tools.filter(isRecord).map((tool) => ({
|
|
515
736
|
annotations: isRecord(tool.annotations) ? tool.annotations : undefined,
|
|
516
737
|
coaz: typeof tool.coaz === "boolean" ? tool.coaz : undefined,
|
|
@@ -520,7 +741,7 @@ var createMcpClient = (options) => {
|
|
|
520
741
|
outputSchema: isRecord(tool.outputSchema) ? tool.outputSchema : undefined,
|
|
521
742
|
taskSupport: isRecord(tool.execution) && (tool.execution.taskSupport === "forbidden" || tool.execution.taskSupport === "optional" || tool.execution.taskSupport === "required") ? tool.execution.taskSupport : undefined
|
|
522
743
|
})));
|
|
523
|
-
const next = isRecord(
|
|
744
|
+
const next = isRecord(result2) && typeof result2.nextCursor === "string" ? result2.nextCursor : undefined;
|
|
524
745
|
if (next === undefined)
|
|
525
746
|
break;
|
|
526
747
|
cursor = next;
|
|
@@ -528,9 +749,9 @@ var createMcpClient = (options) => {
|
|
|
528
749
|
return collected;
|
|
529
750
|
};
|
|
530
751
|
const callTool = async (name, args) => {
|
|
531
|
-
const
|
|
532
|
-
if (isRecord(
|
|
533
|
-
return
|
|
752
|
+
const result2 = await rpc("tools/call", { arguments: args ?? {}, name });
|
|
753
|
+
if (isRecord(result2) && Array.isArray(result2.content)) {
|
|
754
|
+
return result2;
|
|
534
755
|
}
|
|
535
756
|
return { content: [], isError: false };
|
|
536
757
|
};
|
|
@@ -541,31 +762,31 @@ var createMcpClient = (options) => {
|
|
|
541
762
|
return value;
|
|
542
763
|
};
|
|
543
764
|
const callToolAsTask = async (name, args, options2 = {}) => {
|
|
544
|
-
const
|
|
765
|
+
const result2 = await rpc("tools/call", {
|
|
545
766
|
arguments: args ?? {},
|
|
546
767
|
name,
|
|
547
768
|
task: options2.ttl === undefined ? {} : { ttl: options2.ttl }
|
|
548
769
|
});
|
|
549
|
-
return taskFrom(isRecord(
|
|
770
|
+
return taskFrom(isRecord(result2) ? result2.task : undefined);
|
|
550
771
|
};
|
|
551
772
|
const getTask = async (taskId) => taskFrom(await rpc("tasks/get", { taskId }));
|
|
552
773
|
const cancelTask = async (taskId) => taskFrom(await rpc("tasks/cancel", { taskId }));
|
|
553
774
|
const getTaskResult = async (taskId) => {
|
|
554
|
-
const
|
|
555
|
-
if (!isRecord(
|
|
775
|
+
const result2 = await rpc("tasks/result", { taskId });
|
|
776
|
+
if (!isRecord(result2) || !Array.isArray(result2.content)) {
|
|
556
777
|
throw new McpClientError("Malformed MCP task result");
|
|
557
778
|
}
|
|
558
|
-
return
|
|
779
|
+
return result2;
|
|
559
780
|
};
|
|
560
781
|
const listTasks = async () => {
|
|
561
782
|
const collected = [];
|
|
562
783
|
let cursor;
|
|
563
784
|
for (let page = 0;page < MAX_LIST_PAGES; page += 1) {
|
|
564
|
-
const
|
|
565
|
-
if (isRecord(
|
|
566
|
-
collected.push(...
|
|
785
|
+
const result2 = await rpc("tasks/list", cursor === undefined ? undefined : { cursor });
|
|
786
|
+
if (isRecord(result2) && Array.isArray(result2.tasks)) {
|
|
787
|
+
collected.push(...result2.tasks.map(taskFrom));
|
|
567
788
|
}
|
|
568
|
-
const next = isRecord(
|
|
789
|
+
const next = isRecord(result2) && typeof result2.nextCursor === "string" ? result2.nextCursor : undefined;
|
|
569
790
|
if (next === undefined)
|
|
570
791
|
break;
|
|
571
792
|
cursor = next;
|
|
@@ -576,11 +797,11 @@ var createMcpClient = (options) => {
|
|
|
576
797
|
const collected = [];
|
|
577
798
|
let cursor;
|
|
578
799
|
for (let page = 0;page < MAX_LIST_PAGES; page += 1) {
|
|
579
|
-
const
|
|
580
|
-
if (isRecord(
|
|
581
|
-
collected.push(...
|
|
800
|
+
const result2 = await rpc("resources/list", cursor === undefined ? undefined : { cursor });
|
|
801
|
+
if (isRecord(result2) && Array.isArray(result2.resources)) {
|
|
802
|
+
collected.push(...result2.resources);
|
|
582
803
|
}
|
|
583
|
-
const next = isRecord(
|
|
804
|
+
const next = isRecord(result2) && typeof result2.nextCursor === "string" ? result2.nextCursor : undefined;
|
|
584
805
|
if (next === undefined)
|
|
585
806
|
break;
|
|
586
807
|
cursor = next;
|
|
@@ -849,17 +1070,17 @@ var createMcpOAuthProvider = (options) => {
|
|
|
849
1070
|
redirectUri: options.redirectUri,
|
|
850
1071
|
scopes
|
|
851
1072
|
});
|
|
852
|
-
const
|
|
1073
|
+
const result2 = await options.onAuthorize({
|
|
853
1074
|
authorizationUrl: request.authorizationUrl,
|
|
854
1075
|
state: request.state,
|
|
855
1076
|
scopes,
|
|
856
1077
|
resource: found.resource.resource
|
|
857
1078
|
});
|
|
858
|
-
if (
|
|
1079
|
+
if (result2.state !== request.state)
|
|
859
1080
|
throw new Error("OAuth state mismatch");
|
|
860
1081
|
const params = new URLSearchParams({
|
|
861
1082
|
grant_type: "authorization_code",
|
|
862
|
-
code:
|
|
1083
|
+
code: result2.code,
|
|
863
1084
|
client_id: options.clientId,
|
|
864
1085
|
redirect_uri: options.redirectUri,
|
|
865
1086
|
code_verifier: request.codeVerifier,
|
|
@@ -899,7 +1120,7 @@ var HTTP_METHOD_NOT_ALLOWED = 405;
|
|
|
899
1120
|
var jsonHeaders = {
|
|
900
1121
|
"content-type": "application/json"
|
|
901
1122
|
};
|
|
902
|
-
var rpcResult = (id,
|
|
1123
|
+
var rpcResult = (id, result2) => new Response(JSON.stringify({ id, jsonrpc: "2.0", result: result2 }), {
|
|
903
1124
|
headers: jsonHeaders
|
|
904
1125
|
});
|
|
905
1126
|
var rpcError = (id, code, message, data) => new Response(JSON.stringify({
|
|
@@ -963,7 +1184,7 @@ var createMemoryMcpTaskStore = () => {
|
|
|
963
1184
|
};
|
|
964
1185
|
};
|
|
965
1186
|
var publicMcpTask = ({ authorizationKey, ...task }) => {
|
|
966
|
-
const { error, inputRequests, pollIntervalMs, result, ttlMs, ...rest } = task;
|
|
1187
|
+
const { error, inputRequests, pollIntervalMs, result: result2, ttlMs, ...rest } = task;
|
|
967
1188
|
return {
|
|
968
1189
|
...rest,
|
|
969
1190
|
...pollIntervalMs === undefined ? {} : { pollInterval: pollIntervalMs },
|
|
@@ -1142,12 +1363,12 @@ var runTool = async (config, caller, scopes, id, name, args, meta, tool, context
|
|
|
1142
1363
|
};
|
|
1143
1364
|
return normalizeResult(await tool.handler(args, context));
|
|
1144
1365
|
};
|
|
1145
|
-
let
|
|
1366
|
+
let result2;
|
|
1146
1367
|
const eligibility = await commerceDecision(config, caller, name, tool);
|
|
1147
1368
|
if (eligibility)
|
|
1148
1369
|
meta.commerceDecision = eligibility;
|
|
1149
1370
|
if (eligibility?.allowed === false) {
|
|
1150
|
-
|
|
1371
|
+
result2 = {
|
|
1151
1372
|
content: [
|
|
1152
1373
|
{
|
|
1153
1374
|
type: "text",
|
|
@@ -1161,7 +1382,7 @@ var runTool = async (config, caller, scopes, id, name, args, meta, tool, context
|
|
|
1161
1382
|
}
|
|
1162
1383
|
};
|
|
1163
1384
|
} else if (tool.authorization === undefined) {
|
|
1164
|
-
|
|
1385
|
+
result2 = await invoke();
|
|
1165
1386
|
} else {
|
|
1166
1387
|
const agency = config.agency;
|
|
1167
1388
|
if (agency === undefined)
|
|
@@ -1193,7 +1414,7 @@ var runTool = async (config, caller, scopes, id, name, args, meta, tool, context
|
|
|
1193
1414
|
meta.agencyActionId = requested.action.actionId;
|
|
1194
1415
|
meta.agencyDecisionId = requested.decision.decisionId;
|
|
1195
1416
|
if (requested.decision.kind === "deny") {
|
|
1196
|
-
|
|
1417
|
+
result2 = {
|
|
1197
1418
|
content: [
|
|
1198
1419
|
{
|
|
1199
1420
|
text: requested.decision.requestable ? `Action requires approval (${requested.action.actionId})` : `Action denied: ${requested.decision.reason}`,
|
|
@@ -1216,11 +1437,11 @@ var runTool = async (config, caller, scopes, id, name, args, meta, tool, context
|
|
|
1216
1437
|
});
|
|
1217
1438
|
meta.agencyLeaseId = lease.leaseId;
|
|
1218
1439
|
meta.agencyReceiptId = executed.receipt.receiptId;
|
|
1219
|
-
|
|
1440
|
+
result2 = executed.result;
|
|
1220
1441
|
}
|
|
1221
1442
|
}
|
|
1222
|
-
ok =
|
|
1223
|
-
payload = { id, jsonrpc: "2.0", result };
|
|
1443
|
+
ok = result2.isError !== true;
|
|
1444
|
+
payload = { id, jsonrpc: "2.0", result: result2 };
|
|
1224
1445
|
} catch (error) {
|
|
1225
1446
|
const detail = error instanceof Error ? error.message : "unknown error";
|
|
1226
1447
|
payload = {
|
|
@@ -1347,10 +1568,10 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
1347
1568
|
await tasks.store.save(task);
|
|
1348
1569
|
setTimeout(() => {
|
|
1349
1570
|
runTool(config, caller, scopes, id, name, args, meta, tool, noElicit).then(async (payload2) => {
|
|
1350
|
-
const
|
|
1571
|
+
const result2 = isRecord(payload2) && isRecord(payload2.result) ? payload2.result : { content: [], isError: true };
|
|
1351
1572
|
await tasks.store.update(task.taskId, {
|
|
1352
|
-
result,
|
|
1353
|
-
status:
|
|
1573
|
+
result: result2,
|
|
1574
|
+
status: result2.isError === true ? "failed" : "completed"
|
|
1354
1575
|
});
|
|
1355
1576
|
}).catch(async (error) => {
|
|
1356
1577
|
await tasks.store.update(task.taskId, {
|
|
@@ -1414,14 +1635,14 @@ var tasksResult = async (config, caller, id, params, signal) => {
|
|
|
1414
1635
|
if (task.error !== undefined) {
|
|
1415
1636
|
return rpcError(id, typeof task.error.code === "number" ? task.error.code : JSONRPC_INTERNAL_ERROR, typeof task.error.message === "string" ? task.error.message : "Task failed");
|
|
1416
1637
|
}
|
|
1417
|
-
const
|
|
1638
|
+
const result2 = task.result ?? {
|
|
1418
1639
|
content: [],
|
|
1419
1640
|
isError: task.status !== "completed"
|
|
1420
1641
|
};
|
|
1421
1642
|
return rpcResult(id, {
|
|
1422
|
-
...
|
|
1643
|
+
...result2,
|
|
1423
1644
|
_meta: {
|
|
1424
|
-
...isRecord(
|
|
1645
|
+
...isRecord(result2._meta) ? result2._meta : {},
|
|
1425
1646
|
"io.modelcontextprotocol/related-task": { taskId: task.taskId }
|
|
1426
1647
|
}
|
|
1427
1648
|
});
|
|
@@ -1547,11 +1768,11 @@ var elicitAnswer = async (message, context) => {
|
|
|
1547
1768
|
const requestId = typeof message.id === "string" ? message.id : null;
|
|
1548
1769
|
if (!requestId || !context.sessions)
|
|
1549
1770
|
return notificationAck();
|
|
1550
|
-
const
|
|
1551
|
-
const action =
|
|
1771
|
+
const result2 = isRecord(message.result) ? message.result : null;
|
|
1772
|
+
const action = result2?.action;
|
|
1552
1773
|
const answer = action === "accept" ? {
|
|
1553
1774
|
action: "accept",
|
|
1554
|
-
content: isRecord(
|
|
1775
|
+
content: isRecord(result2?.content) ? result2.content : {}
|
|
1555
1776
|
} : action === "decline" ? { action: "decline" } : { action: "cancel" };
|
|
1556
1777
|
await context.sessions.resolveElicit({
|
|
1557
1778
|
requestId,
|
|
@@ -2003,9 +2224,9 @@ var createPostgresMcpTaskStore = ({
|
|
|
2003
2224
|
update: async (taskId, update) => {
|
|
2004
2225
|
const updatedAt = now().toISOString();
|
|
2005
2226
|
const data = { ...update, lastUpdatedAt: updatedAt };
|
|
2006
|
-
const
|
|
2007
|
-
if (
|
|
2008
|
-
return
|
|
2227
|
+
const result2 = await client.query(`UPDATE ${ns}.tasks SET status = COALESCE($2::text, status), updated_at = $3::timestamptz, data = data || $4::jsonb WHERE task_id = $1 AND status NOT IN ('cancelled','completed','failed') RETURNING data`, [taskId, update.status ?? null, updatedAt, JSON.stringify(data)]);
|
|
2228
|
+
if (result2.rows[0] !== undefined)
|
|
2229
|
+
return result2.rows[0].data;
|
|
2009
2230
|
return (await client.query(`SELECT data FROM ${ns}.tasks WHERE task_id = $1`, [taskId])).rows[0]?.data ?? null;
|
|
2010
2231
|
}
|
|
2011
2232
|
};
|
|
@@ -2037,12 +2258,12 @@ var createPostgresMcpSessionStore = ({
|
|
|
2037
2258
|
},
|
|
2038
2259
|
get: async (id) => {
|
|
2039
2260
|
const current = now();
|
|
2040
|
-
const
|
|
2261
|
+
const result2 = await client.query(`UPDATE ${ns}.sessions SET last_seen_at = $2::timestamptz, expires_at = $3::timestamptz WHERE session_id = $1 AND expires_at > $2::timestamptz RETURNING can_elicit, can_elicit_url`, [
|
|
2041
2262
|
id,
|
|
2042
2263
|
current.toISOString(),
|
|
2043
2264
|
new Date(current.getTime() + ttlMs).toISOString()
|
|
2044
2265
|
]);
|
|
2045
|
-
const row =
|
|
2266
|
+
const row = result2.rows[0];
|
|
2046
2267
|
return row === undefined ? null : {
|
|
2047
2268
|
canElicit: row.can_elicit,
|
|
2048
2269
|
canElicitUrl: row.can_elicit_url
|
|
@@ -2094,6 +2315,8 @@ export {
|
|
|
2094
2315
|
MCP_LATEST_PROTOCOL_VERSION,
|
|
2095
2316
|
McpClientError,
|
|
2096
2317
|
budgetedMcpTool,
|
|
2318
|
+
createBillingManagementTool,
|
|
2319
|
+
createBillingReportTools,
|
|
2097
2320
|
createCheckoutHandoffTool,
|
|
2098
2321
|
createCreditBalanceTool,
|
|
2099
2322
|
createMcpAuthorizationRequest,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type BillingStatus, type ReceiptPage, type ReceiptPageRequest, type UsageRange, type CustomerUsageReport } from "@absolutejs/billing/reports";
|
|
2
|
+
import type { McpToolRegistry, McpToolResult } from "./types";
|
|
3
|
+
/** Bind each reader to the authenticated account. Keep these reads outside
|
|
4
|
+
* paid-work gates; never include account IDs or provider credentials in input. */
|
|
5
|
+
export declare const createBillingReportTools: (readers: {
|
|
6
|
+
status: () => Promise<BillingStatus>;
|
|
7
|
+
receipts: (request: ReceiptPageRequest) => Promise<ReceiptPage>;
|
|
8
|
+
usage: (range: UsageRange) => Promise<CustomerUsageReport>;
|
|
9
|
+
now?: () => Date;
|
|
10
|
+
}) => McpToolRegistry;
|
|
11
|
+
/** This destination can initiate purchases, so it must retain the checkout
|
|
12
|
+
* classification. A generic billing-page label cannot bypass host rules. */
|
|
13
|
+
export declare const createBillingManagementTool: (destination: string) => {
|
|
14
|
+
annotations: {
|
|
15
|
+
readOnlyHint: boolean;
|
|
16
|
+
destructiveHint: boolean;
|
|
17
|
+
openWorldHint: boolean;
|
|
18
|
+
};
|
|
19
|
+
commerce: {
|
|
20
|
+
action: "external_checkout";
|
|
21
|
+
categories: ("subscription" | "usage_credits")[];
|
|
22
|
+
};
|
|
23
|
+
description: string;
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: string;
|
|
26
|
+
properties: {};
|
|
27
|
+
additionalProperties: boolean;
|
|
28
|
+
};
|
|
29
|
+
handler: (input: unknown) => Promise<McpToolResult>;
|
|
30
|
+
};
|
package/dist/src/commerce.d.ts
CHANGED
|
@@ -49,3 +49,4 @@ export type CommerceDecision = {
|
|
|
49
49
|
export declare const evaluateCommerce: (req: CommerceRequirement, context: CommerceContext, now?: Date) => CommerceDecision;
|
|
50
50
|
export { createCreditBalanceTool, type McpCreditBalance } from "./creditStatus";
|
|
51
51
|
export { createCheckoutHandoffTool, createPurchaseStatusTool, type McpPurchaseStatus, } from "./checkoutTools";
|
|
52
|
+
export { createBillingReportTools, createBillingManagementTool, } from "./billingTools";
|
package/dist/src/index.d.ts
CHANGED
|
@@ -46,3 +46,4 @@ export * from "./commerce";
|
|
|
46
46
|
export { createCreditBalanceTool, type McpCreditBalance } from "./creditStatus";
|
|
47
47
|
export { budgetedMcpTool, type McpCreditWorkRequest } from "./budgetedTool";
|
|
48
48
|
export { createCheckoutHandoffTool, createPurchaseStatusTool, type McpPurchaseStatus, } from "./checkoutTools";
|
|
49
|
+
export { createBillingReportTools, createBillingManagementTool, } from "./billingTools";
|
package/package.json
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@absolutejs/manifest": "^0.9.0",
|
|
20
|
-
"@sinclair/typebox": "^0.34.0"
|
|
20
|
+
"@sinclair/typebox": "^0.34.0",
|
|
21
|
+
"@absolutejs/billing": "^0.9.0"
|
|
21
22
|
},
|
|
22
23
|
"license": "BUSL-1.1",
|
|
23
24
|
"absolutejs": {
|
|
@@ -72,5 +73,5 @@
|
|
|
72
73
|
"prepublishOnly": "bun run check:package"
|
|
73
74
|
},
|
|
74
75
|
"types": "./dist/src/index.d.ts",
|
|
75
|
-
"version": "0.
|
|
76
|
+
"version": "0.16.0"
|
|
76
77
|
}
|