@lerianstudio/matcher-mcp 1.0.0-beta.5
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/LICENSE +202 -0
- package/README.md +281 -0
- package/dist/auth/request-token.js +117 -0
- package/dist/auth/request-token.js.map +1 -0
- package/dist/config.js +95 -0
- package/dist/config.js.map +1 -0
- package/dist/matcher/client.js +168 -0
- package/dist/matcher/client.js.map +1 -0
- package/dist/observability/otel.js +223 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/server.js +245 -0
- package/dist/server.js.map +1 -0
- package/dist/spec/curated-operations.js +145 -0
- package/dist/spec/curated-operations.js.map +1 -0
- package/dist/spec/index.js +155 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/spec/load.js +76 -0
- package/dist/spec/load.js.map +1 -0
- package/dist/spec/openapi.yaml +15217 -0
- package/dist/tools/context/create.js +100 -0
- package/dist/tools/context/create.js.map +1 -0
- package/dist/tools/context/get.js +31 -0
- package/dist/tools/context/get.js.map +1 -0
- package/dist/tools/context/index.js +21 -0
- package/dist/tools/context/index.js.map +1 -0
- package/dist/tools/context/list.js +78 -0
- package/dist/tools/context/list.js.map +1 -0
- package/dist/tools/context/shared.js +61 -0
- package/dist/tools/context/shared.js.map +1 -0
- package/dist/tools/context/update.js +105 -0
- package/dist/tools/context/update.js.map +1 -0
- package/dist/tools/dashboard/aggregates.js +34 -0
- package/dist/tools/dashboard/aggregates.js.map +1 -0
- package/dist/tools/dashboard/cash-impact.js +36 -0
- package/dist/tools/dashboard/cash-impact.js.map +1 -0
- package/dist/tools/dashboard/index.js +27 -0
- package/dist/tools/dashboard/index.js.map +1 -0
- package/dist/tools/dashboard/match-rate.js +35 -0
- package/dist/tools/dashboard/match-rate.js.map +1 -0
- package/dist/tools/dashboard/metrics.js +36 -0
- package/dist/tools/dashboard/metrics.js.map +1 -0
- package/dist/tools/dashboard/shared.js +122 -0
- package/dist/tools/dashboard/shared.js.map +1 -0
- package/dist/tools/dashboard/sla.js +33 -0
- package/dist/tools/dashboard/sla.js.map +1 -0
- package/dist/tools/dashboard/source-breakdown.js +36 -0
- package/dist/tools/dashboard/source-breakdown.js.map +1 -0
- package/dist/tools/dashboard/volume.js +35 -0
- package/dist/tools/dashboard/volume.js.map +1 -0
- package/dist/tools/dispute/close.js +49 -0
- package/dist/tools/dispute/close.js.map +1 -0
- package/dist/tools/dispute/get.js +27 -0
- package/dist/tools/dispute/get.js.map +1 -0
- package/dist/tools/dispute/index.js +17 -0
- package/dist/tools/dispute/index.js.map +1 -0
- package/dist/tools/dispute/list.js +137 -0
- package/dist/tools/dispute/list.js.map +1 -0
- package/dist/tools/dispute/shared.js +66 -0
- package/dist/tools/dispute/shared.js.map +1 -0
- package/dist/tools/dispute/submit-evidence.js +58 -0
- package/dist/tools/dispute/submit-evidence.js.map +1 -0
- package/dist/tools/exception/add-comment.js +50 -0
- package/dist/tools/exception/add-comment.js.map +1 -0
- package/dist/tools/exception/adjust-entry.js +71 -0
- package/dist/tools/exception/adjust-entry.js.map +1 -0
- package/dist/tools/exception/bulk-assign.js +47 -0
- package/dist/tools/exception/bulk-assign.js.map +1 -0
- package/dist/tools/exception/bulk-dispatch.js +69 -0
- package/dist/tools/exception/bulk-dispatch.js.map +1 -0
- package/dist/tools/exception/bulk-resolve.js +64 -0
- package/dist/tools/exception/bulk-resolve.js.map +1 -0
- package/dist/tools/exception/delete-comment.js +40 -0
- package/dist/tools/exception/delete-comment.js.map +1 -0
- package/dist/tools/exception/dispatch.js +64 -0
- package/dist/tools/exception/dispatch.js.map +1 -0
- package/dist/tools/exception/force-match.js +55 -0
- package/dist/tools/exception/force-match.js.map +1 -0
- package/dist/tools/exception/get.js +34 -0
- package/dist/tools/exception/get.js.map +1 -0
- package/dist/tools/exception/history.js +61 -0
- package/dist/tools/exception/history.js.map +1 -0
- package/dist/tools/exception/index.js +39 -0
- package/dist/tools/exception/index.js.map +1 -0
- package/dist/tools/exception/list-comments.js +35 -0
- package/dist/tools/exception/list-comments.js.map +1 -0
- package/dist/tools/exception/list.js +169 -0
- package/dist/tools/exception/list.js.map +1 -0
- package/dist/tools/exception/open-dispute.js +53 -0
- package/dist/tools/exception/open-dispute.js.map +1 -0
- package/dist/tools/exception/shared.js +98 -0
- package/dist/tools/exception/shared.js.map +1 -0
- package/dist/tools/fee-rule/create.js +106 -0
- package/dist/tools/fee-rule/create.js.map +1 -0
- package/dist/tools/fee-rule/delete.js +27 -0
- package/dist/tools/fee-rule/delete.js.map +1 -0
- package/dist/tools/fee-rule/get.js +27 -0
- package/dist/tools/fee-rule/get.js.map +1 -0
- package/dist/tools/fee-rule/index.js +21 -0
- package/dist/tools/fee-rule/index.js.map +1 -0
- package/dist/tools/fee-rule/list.js +64 -0
- package/dist/tools/fee-rule/list.js.map +1 -0
- package/dist/tools/fee-rule/shared.js +67 -0
- package/dist/tools/fee-rule/shared.js.map +1 -0
- package/dist/tools/fee-rule/update.js +70 -0
- package/dist/tools/fee-rule/update.js.map +1 -0
- package/dist/tools/fee-schedule/create.js +105 -0
- package/dist/tools/fee-schedule/create.js.map +1 -0
- package/dist/tools/fee-schedule/delete.js +32 -0
- package/dist/tools/fee-schedule/delete.js.map +1 -0
- package/dist/tools/fee-schedule/get.js +30 -0
- package/dist/tools/fee-schedule/get.js.map +1 -0
- package/dist/tools/fee-schedule/index.js +22 -0
- package/dist/tools/fee-schedule/index.js.map +1 -0
- package/dist/tools/fee-schedule/list.js +56 -0
- package/dist/tools/fee-schedule/list.js.map +1 -0
- package/dist/tools/fee-schedule/shared.js +58 -0
- package/dist/tools/fee-schedule/shared.js.map +1 -0
- package/dist/tools/fee-schedule/simulate.js +54 -0
- package/dist/tools/fee-schedule/simulate.js.map +1 -0
- package/dist/tools/fee-schedule/update.js +69 -0
- package/dist/tools/fee-schedule/update.js.map +1 -0
- package/dist/tools/generic/describe-operation.js +157 -0
- package/dist/tools/generic/describe-operation.js.map +1 -0
- package/dist/tools/generic/invoke.js +343 -0
- package/dist/tools/generic/invoke.js.map +1 -0
- package/dist/tools/generic/list-operations.js +73 -0
- package/dist/tools/generic/list-operations.js.map +1 -0
- package/dist/tools/ingestion/index.js +29 -0
- package/dist/tools/ingestion/index.js.map +1 -0
- package/dist/tools/ingestion/job-errors-list.js +45 -0
- package/dist/tools/ingestion/job-errors-list.js.map +1 -0
- package/dist/tools/ingestion/job-get.js +42 -0
- package/dist/tools/ingestion/job-get.js.map +1 -0
- package/dist/tools/ingestion/job-transactions-list.js +93 -0
- package/dist/tools/ingestion/job-transactions-list.js.map +1 -0
- package/dist/tools/ingestion/jobs-list.js +89 -0
- package/dist/tools/ingestion/jobs-list.js.map +1 -0
- package/dist/tools/ingestion/shared.js +84 -0
- package/dist/tools/ingestion/shared.js.map +1 -0
- package/dist/tools/ingestion/transaction-ignore.js +51 -0
- package/dist/tools/ingestion/transaction-ignore.js.map +1 -0
- package/dist/tools/ingestion/transactions-search.js +143 -0
- package/dist/tools/ingestion/transactions-search.js.map +1 -0
- package/dist/tools/match-rule/create.js +64 -0
- package/dist/tools/match-rule/create.js.map +1 -0
- package/dist/tools/match-rule/delete.js +35 -0
- package/dist/tools/match-rule/delete.js.map +1 -0
- package/dist/tools/match-rule/get.js +32 -0
- package/dist/tools/match-rule/get.js.map +1 -0
- package/dist/tools/match-rule/index.js +22 -0
- package/dist/tools/match-rule/index.js.map +1 -0
- package/dist/tools/match-rule/list.js +72 -0
- package/dist/tools/match-rule/list.js.map +1 -0
- package/dist/tools/match-rule/reorder.js +45 -0
- package/dist/tools/match-rule/reorder.js.map +1 -0
- package/dist/tools/match-rule/shared.js +71 -0
- package/dist/tools/match-rule/shared.js.map +1 -0
- package/dist/tools/match-rule/update.js +68 -0
- package/dist/tools/match-rule/update.js.map +1 -0
- package/dist/tools/matching/get.js +48 -0
- package/dist/tools/matching/get.js.map +1 -0
- package/dist/tools/matching/groups.js +98 -0
- package/dist/tools/matching/groups.js.map +1 -0
- package/dist/tools/matching/index.js +19 -0
- package/dist/tools/matching/index.js.map +1 -0
- package/dist/tools/matching/list.js +76 -0
- package/dist/tools/matching/list.js.map +1 -0
- package/dist/tools/matching/shared.js +77 -0
- package/dist/tools/matching/shared.js.map +1 -0
- package/dist/tools/matching/start.js +55 -0
- package/dist/tools/matching/start.js.map +1 -0
- package/dist/tools/report/count-exceptions.js +45 -0
- package/dist/tools/report/count-exceptions.js.map +1 -0
- package/dist/tools/report/count-matched.js +46 -0
- package/dist/tools/report/count-matched.js.map +1 -0
- package/dist/tools/report/count-transactions.js +44 -0
- package/dist/tools/report/count-transactions.js.map +1 -0
- package/dist/tools/report/count-unmatched.js +44 -0
- package/dist/tools/report/count-unmatched.js.map +1 -0
- package/dist/tools/report/export-exceptions.js +46 -0
- package/dist/tools/report/export-exceptions.js.map +1 -0
- package/dist/tools/report/export-matched.js +47 -0
- package/dist/tools/report/export-matched.js.map +1 -0
- package/dist/tools/report/export-summary.js +47 -0
- package/dist/tools/report/export-summary.js.map +1 -0
- package/dist/tools/report/export-unmatched.js +47 -0
- package/dist/tools/report/export-unmatched.js.map +1 -0
- package/dist/tools/report/export-variance.js +46 -0
- package/dist/tools/report/export-variance.js.map +1 -0
- package/dist/tools/report/index.js +40 -0
- package/dist/tools/report/index.js.map +1 -0
- package/dist/tools/report/matched.js +44 -0
- package/dist/tools/report/matched.js.map +1 -0
- package/dist/tools/report/shared.js +176 -0
- package/dist/tools/report/shared.js.map +1 -0
- package/dist/tools/report/summary.js +41 -0
- package/dist/tools/report/summary.js.map +1 -0
- package/dist/tools/report/unmatched.js +44 -0
- package/dist/tools/report/unmatched.js.map +1 -0
- package/dist/tools/report/variance.js +44 -0
- package/dist/tools/report/variance.js.map +1 -0
- package/dist/tools/source/create.js +84 -0
- package/dist/tools/source/create.js.map +1 -0
- package/dist/tools/source/get.js +41 -0
- package/dist/tools/source/get.js.map +1 -0
- package/dist/tools/source/index.js +22 -0
- package/dist/tools/source/index.js.map +1 -0
- package/dist/tools/source/list.js +72 -0
- package/dist/tools/source/list.js.map +1 -0
- package/dist/tools/source/shared.js +63 -0
- package/dist/tools/source/shared.js.map +1 -0
- package/dist/tools/source/update.js +73 -0
- package/dist/tools/source/update.js.map +1 -0
- package/dist/tools/tool-error.js +74 -0
- package/dist/tools/tool-error.js.map +1 -0
- package/dist/transport/body-limit.js +82 -0
- package/dist/transport/body-limit.js.map +1 -0
- package/dist/transport/http.js +154 -0
- package/dist/transport/http.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unmatched.js","sourceRoot":"","sources":["../../../src/tools/report/unmatched.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,8EAA8E;AAC9E,iFAAiF;AACjF,6EAA6E;AAC7E,qEAAqE;AACrE,+EAA+E;AAC/E,iDAAiD;AAEjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,aAAa,CAAA;AAEpB,yFAAyF;AACzF,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,uEAAuE;QACrE,iDAAiD,CACpD;IACH,GAAG,sBAAsB;IACzB,GAAG,0BAA0B;CAC9B,CAAA;AAOD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAiB;IAC3D,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,WAAW,EACT,iEAAiE;YACjE,qEAAqE;YACrE,oEAAoE;YACpE,yEAAyE;YACzE,sEAAsE;YACtE,4DAA4D;QAC9D,WAAW,EAAE,yBAAyB;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,KAAK,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAA;QAC7C,OAAO,cAAc,CACnB;YACE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,iBAAiB,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC;YACpD,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// `report_variance` — curated GET /v1/reports/contexts/{contextId}/variance.
|
|
2
|
+
//
|
|
3
|
+
// Cursor-paginated variance report rows for a context within a date window.
|
|
4
|
+
// date_from/date_to/source_id filter and cursor/limit/sort_order page; matcher's
|
|
5
|
+
// { items, nextCursor } envelope is returned as-is — no auto-pagination (the
|
|
6
|
+
// model re-calls with the returned cursor). The report is implicitly
|
|
7
|
+
// tenant-scoped by the relayed token; contextId is a routing path param, not a
|
|
8
|
+
// tenant field, and no tenant field is accepted.
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { buildReportPaginatedQuery, contextReportPath, dispatchReport, reportFilterInputShape, reportPaginationInputShape, } from './shared.js';
|
|
11
|
+
/** Zod raw shape for `report_variance` input — contextId path + filter + pagination. */
|
|
12
|
+
export const varianceReportInputShape = {
|
|
13
|
+
contextId: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.describe('The reconciliation context id (UUID) whose variance rows to report. ' +
|
|
17
|
+
'Routing path param, not a tenant field.'),
|
|
18
|
+
...reportFilterInputShape,
|
|
19
|
+
...reportPaginationInputShape,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Register `report_variance`. Passes the date filter + pagination through and
|
|
23
|
+
* returns matcher's pagination envelope verbatim; a matcher error surfaces via
|
|
24
|
+
* the shared toToolError.
|
|
25
|
+
*/
|
|
26
|
+
export function registerReportVarianceTool(server) {
|
|
27
|
+
server.registerTool('report_variance', {
|
|
28
|
+
description: 'List variance report rows for a context within a date window ' +
|
|
29
|
+
'(cursor-paginated). Pass contextId (UUID path param — not a tenant ' +
|
|
30
|
+
'field), and optionally date_from/date_to (YYYY-MM-DD, window ≤ 90 ' +
|
|
31
|
+
'days), source_id, limit (1–200), sort_order (asc|desc), and/or cursor. ' +
|
|
32
|
+
'The response carries { items, nextCursor } — paginate by re-calling ' +
|
|
33
|
+
'with the returned nextCursor. No tenant field is accepted.',
|
|
34
|
+
inputSchema: varianceReportInputShape,
|
|
35
|
+
}, async (args, extra) => {
|
|
36
|
+
const query = buildReportPaginatedQuery(args);
|
|
37
|
+
return dispatchReport({
|
|
38
|
+
method: 'GET',
|
|
39
|
+
path: contextReportPath(args.contextId, 'variance'),
|
|
40
|
+
...(query !== undefined ? { query } : {}),
|
|
41
|
+
}, extra);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=variance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variance.js","sourceRoot":"","sources":["../../../src/tools/report/variance.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,4EAA4E;AAC5E,iFAAiF;AACjF,6EAA6E;AAC7E,qEAAqE;AACrE,+EAA+E;AAC/E,iDAAiD;AAEjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,aAAa,CAAA;AAEpB,wFAAwF;AACxF,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,sEAAsE;QACpE,yCAAyC,CAC5C;IACH,GAAG,sBAAsB;IACzB,GAAG,0BAA0B;CAC9B,CAAA;AAOD;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAiB;IAC1D,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,WAAW,EACT,+DAA+D;YAC/D,qEAAqE;YACrE,oEAAoE;YACpE,yEAAyE;YACzE,sEAAsE;YACtE,4DAA4D;QAC9D,WAAW,EAAE,wBAAwB;KACtC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,KAAK,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAA;QAC7C,OAAO,cAAc,CACnB;YACE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,iBAAiB,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC;YACnD,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// `source_create` — curated POST /v1/contexts/{contextId}/sources.
|
|
2
|
+
//
|
|
3
|
+
// A typed, richly-described front door to creating a reconciliation source under
|
|
4
|
+
// a context, mirroring the spec's CreateSourceRequest. The Zod schema models the
|
|
5
|
+
// required scalars (name, type, side) and the documented enums; `config` is left
|
|
6
|
+
// as an open record (per the established pattern) — its shape depends on the
|
|
7
|
+
// source type and matcher validates it server-side, so we do not duplicate that
|
|
8
|
+
// validation here.
|
|
9
|
+
//
|
|
10
|
+
// `contextId` is a routing path param (matcher validates it belongs to the
|
|
11
|
+
// relayed token's tenant), NOT a tenant field. No tenant field exists in the
|
|
12
|
+
// schema: the source is created for the tenant of the relayed bearer token.
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { contextSourcesPath, dispatchSource } from './shared.js';
|
|
15
|
+
/** Origin of the source data (LEDGER ledger / BANK feed / GATEWAY / CUSTOM / FETCHER pull). */
|
|
16
|
+
export const SOURCE_TYPES = [
|
|
17
|
+
'LEDGER',
|
|
18
|
+
'BANK',
|
|
19
|
+
'GATEWAY',
|
|
20
|
+
'CUSTOM',
|
|
21
|
+
'FETCHER',
|
|
22
|
+
];
|
|
23
|
+
/** Matching side this source feeds — the two sides reconciled against each other. */
|
|
24
|
+
export const SOURCE_SIDES = ['LEFT', 'RIGHT'];
|
|
25
|
+
/** Zod raw shape for `source_create` input — mirrors CreateSourceRequest. */
|
|
26
|
+
export const createSourceInputShape = {
|
|
27
|
+
contextId: z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1)
|
|
30
|
+
.describe('The reconciliation context id (UUID) the source belongs to. This is a ' +
|
|
31
|
+
'routing path param, not a tenant field — matcher validates the context ' +
|
|
32
|
+
"belongs to your token's tenant."),
|
|
33
|
+
name: z
|
|
34
|
+
.string()
|
|
35
|
+
.min(1)
|
|
36
|
+
.max(50)
|
|
37
|
+
.describe('Human-readable name of the source (1–50 chars).'),
|
|
38
|
+
type: z
|
|
39
|
+
.enum(SOURCE_TYPES)
|
|
40
|
+
.describe('Origin of the source data. LEDGER is the internal ledger; BANK is a bank ' +
|
|
41
|
+
'statement feed; GATEWAY is a payment gateway; CUSTOM is a bespoke feed; ' +
|
|
42
|
+
'FETCHER is a discovery-engine pull.'),
|
|
43
|
+
side: z
|
|
44
|
+
.enum(SOURCE_SIDES)
|
|
45
|
+
.describe('Matching side this source feeds. LEFT and RIGHT are the two sides ' +
|
|
46
|
+
'reconciled against each other.'),
|
|
47
|
+
config: z
|
|
48
|
+
.record(z.string(), z.unknown())
|
|
49
|
+
.describe('Source-specific configuration object (connection and parsing settings). ' +
|
|
50
|
+
'Its shape depends on the source type and is validated server-side.'),
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Build the POST body from the input minus the path-only `contextId`. Only the
|
|
54
|
+
* CreateSourceRequest fields are sent; contextId travels in the path.
|
|
55
|
+
*/
|
|
56
|
+
export function buildCreateBody(args) {
|
|
57
|
+
const { contextId: _contextId, ...body } = args;
|
|
58
|
+
void _contextId;
|
|
59
|
+
return body;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Register `source_create`. Substitutes contextId into the path, builds the body
|
|
63
|
+
* from the remaining typed input, and dispatches a POST through the shared
|
|
64
|
+
* fail-closed relay; a matcher error surfaces via the shared toToolError (same
|
|
65
|
+
* shape as matcher_invoke).
|
|
66
|
+
*/
|
|
67
|
+
export function registerSourceCreateTool(server) {
|
|
68
|
+
server.registerTool('source_create', {
|
|
69
|
+
description: 'Create a reconciliation source under a context. Provide contextId ' +
|
|
70
|
+
'(UUID path param — not a tenant field), name (1–50), type ' +
|
|
71
|
+
'(LEDGER|BANK|GATEWAY|CUSTOM|FETCHER), side (LEFT|RIGHT), and a ' +
|
|
72
|
+
'type-specific config object (validated server-side). The source is ' +
|
|
73
|
+
"created for your token's tenant (no tenant field is accepted). Returns " +
|
|
74
|
+
'the created source, or a structured RFC 9457 error.',
|
|
75
|
+
inputSchema: createSourceInputShape,
|
|
76
|
+
}, async (args, extra) => {
|
|
77
|
+
return dispatchSource({
|
|
78
|
+
method: 'POST',
|
|
79
|
+
path: contextSourcesPath(args.contextId),
|
|
80
|
+
body: buildCreateBody(args),
|
|
81
|
+
}, extra);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/tools/source/create.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,iFAAiF;AACjF,6EAA6E;AAC7E,gFAAgF;AAChF,mBAAmB;AACnB,EAAE;AACF,2EAA2E;AAC3E,6EAA6E;AAC7E,4EAA4E;AAE5E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEhE,+FAA+F;AAC/F,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,QAAQ;IACR,MAAM;IACN,SAAS;IACT,QAAQ;IACR,SAAS;CACD,CAAA;AAEV,qFAAqF;AACrF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,OAAO,CAAU,CAAA;AAEtD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,wEAAwE;QACtE,yEAAyE;QACzE,iCAAiC,CACpC;IACH,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,CAAC,iDAAiD,CAAC;IAC9D,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,YAAY,CAAC;SAClB,QAAQ,CACP,2EAA2E;QACzE,0EAA0E;QAC1E,qCAAqC,CACxC;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,YAAY,CAAC;SAClB,QAAQ,CACP,oEAAoE;QAClE,gCAAgC,CACnC;IACH,MAAM,EAAE,CAAC;SACN,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/B,QAAQ,CACP,0EAA0E;QACxE,oEAAoE,CACvE;CACJ,CAAA;AAOD;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAuB;IAEvB,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAA;IAC/C,KAAK,UAAU,CAAA;IACf,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAiB;IACxD,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,WAAW,EACT,oEAAoE;YACpE,4DAA4D;YAC5D,iEAAiE;YACjE,qEAAqE;YACrE,yEAAyE;YACzE,qDAAqD;QACvD,WAAW,EAAE,sBAAsB;KACpC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,OAAO,cAAc,CACnB;YACE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC;YACxC,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC;SAC5B,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// `source_get` — curated GET /v1/contexts/{contextId}/sources/{sourceId}.
|
|
2
|
+
//
|
|
3
|
+
// Reads one reconciliation source by id. Unlike fee-rules, sources keep the
|
|
4
|
+
// contextId on single-resource ops, so both contextId and sourceId are path
|
|
5
|
+
// params. The source must belong to a context owned by the relayed token's
|
|
6
|
+
// tenant — matcher enforces that; no tenant field is accepted.
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { contextSourcePath, dispatchSource } from './shared.js';
|
|
9
|
+
/** Zod raw shape for `source_get` input — both path ids. */
|
|
10
|
+
export const getSourceInputShape = {
|
|
11
|
+
contextId: z
|
|
12
|
+
.string()
|
|
13
|
+
.min(1)
|
|
14
|
+
.describe('The reconciliation context id (UUID). Routing path param, not a tenant ' +
|
|
15
|
+
'field.'),
|
|
16
|
+
sourceId: z
|
|
17
|
+
.string()
|
|
18
|
+
.min(1)
|
|
19
|
+
.describe('The reconciliation source id (UUID) to fetch.'),
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Register `source_get`. Substitutes both ids into the path and dispatches a GET
|
|
23
|
+
* through the shared fail-closed relay; a matcher error (e.g. 404) surfaces via
|
|
24
|
+
* the shared toToolError.
|
|
25
|
+
*/
|
|
26
|
+
export function registerSourceGetTool(server) {
|
|
27
|
+
server.registerTool('source_get', {
|
|
28
|
+
description: 'Fetch a single reconciliation source by id (UUID) within a context. ' +
|
|
29
|
+
'Provide both contextId and sourceId (path params; contextId is not a ' +
|
|
30
|
+
"tenant field). The source must belong to your token's tenant. Returns " +
|
|
31
|
+
'the source, or a structured RFC 9457 error (e.g. 404 if it does not ' +
|
|
32
|
+
'exist or is not yours).',
|
|
33
|
+
inputSchema: getSourceInputShape,
|
|
34
|
+
}, async (args, extra) => {
|
|
35
|
+
return dispatchSource({
|
|
36
|
+
method: 'GET',
|
|
37
|
+
path: contextSourcePath(args.contextId, args.sourceId),
|
|
38
|
+
}, extra);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=get.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get.js","sourceRoot":"","sources":["../../../src/tools/source/get.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,2EAA2E;AAC3E,+DAA+D;AAE/D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAE/D,4DAA4D;AAC5D,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,yEAAyE;QACvE,QAAQ,CACX;IACH,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,+CAA+C,CAAC;CAC7D,CAAA;AAKD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAiB;IACrD,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,WAAW,EACT,sEAAsE;YACtE,uEAAuE;YACvE,wEAAwE;YACxE,sEAAsE;YACtE,yBAAyB;QAC3B,WAAW,EAAE,mBAAmB;KACjC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,OAAO,cAAc,CACnB;YACE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,iBAAiB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;SACvD,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Curated source vertical (task 2.2.2): four typed tools over the matcher
|
|
2
|
+
// reconciliation-source resource — create, list, get, update. (Delete was
|
|
3
|
+
// removed when the matcher app replaced source hard-delete with the
|
|
4
|
+
// archive/restore lifecycle; archive/restore remain reachable via the generic
|
|
5
|
+
// invoke surface.) Sources are fully context-nested on every op (single-resource
|
|
6
|
+
// ops keep contextId, unlike fee-rules). Each tool shares the fail-closed relay
|
|
7
|
+
// (shared.ts) and the RFC 9457 → tool-error mapping (toToolError) with the
|
|
8
|
+
// generic surface, so curated and generic map matcher errors identically. No
|
|
9
|
+
// tool carries a tenant field; contextId is a routing path param matcher
|
|
10
|
+
// validates against the relayed token's tenant.
|
|
11
|
+
import { registerSourceCreateTool } from './create.js';
|
|
12
|
+
import { registerSourceGetTool } from './get.js';
|
|
13
|
+
import { registerSourceListTool } from './list.js';
|
|
14
|
+
import { registerSourceUpdateTool } from './update.js';
|
|
15
|
+
/** Register all four curated source tools on the given server. */
|
|
16
|
+
export function registerSourceTools(server) {
|
|
17
|
+
registerSourceCreateTool(server);
|
|
18
|
+
registerSourceListTool(server);
|
|
19
|
+
registerSourceGetTool(server);
|
|
20
|
+
registerSourceUpdateTool(server);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/source/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,0EAA0E;AAC1E,oEAAoE;AACpE,8EAA8E;AAC9E,iFAAiF;AACjF,gFAAgF;AAChF,2EAA2E;AAC3E,6EAA6E;AAC7E,yEAAyE;AACzE,gDAAgD;AAIhD,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAEtD,kEAAkE;AAClE,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,wBAAwB,CAAC,MAAM,CAAC,CAAA;IAChC,sBAAsB,CAAC,MAAM,CAAC,CAAA;IAC9B,qBAAqB,CAAC,MAAM,CAAC,CAAA;IAC7B,wBAAwB,CAAC,MAAM,CAAC,CAAA;AAClC,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// `source_list` — curated GET /v1/contexts/{contextId}/sources.
|
|
2
|
+
//
|
|
3
|
+
// Cursor-paginated list of a context's reconciliation sources, optionally
|
|
4
|
+
// filtered by type. limit/cursor/type pass through untouched and matcher's
|
|
5
|
+
// { items, nextCursor, ... } envelope is returned as-is — no auto-pagination
|
|
6
|
+
// (the model re-calls with the returned cursor). The list is implicitly
|
|
7
|
+
// tenant-scoped by the relayed token; contextId is a routing path param, not a
|
|
8
|
+
// tenant field, and no tenant field is accepted.
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { SOURCE_TYPES } from './create.js';
|
|
11
|
+
import { contextSourcesPath, dispatchSource } from './shared.js';
|
|
12
|
+
/** Zod raw shape for `source_list` input — contextId path + type filter + pagination. */
|
|
13
|
+
export const listSourcesInputShape = {
|
|
14
|
+
contextId: z
|
|
15
|
+
.string()
|
|
16
|
+
.min(1)
|
|
17
|
+
.describe('The reconciliation context id (UUID) whose sources to list. Routing path ' +
|
|
18
|
+
'param, not a tenant field.'),
|
|
19
|
+
type: z
|
|
20
|
+
.enum(SOURCE_TYPES)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Optional filter by source type (LEDGER|BANK|GATEWAY|CUSTOM|FETCHER).'),
|
|
23
|
+
limit: z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.min(1)
|
|
27
|
+
.max(200)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Maximum number of records to return (1–200, default 20).'),
|
|
30
|
+
cursor: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Opaque pagination cursor from a prior response. Re-call with the ' +
|
|
34
|
+
'returned nextCursor to page forward (there is no auto-pagination).'),
|
|
35
|
+
};
|
|
36
|
+
/** Build the list query, omitting unset knobs so the request stays minimal. */
|
|
37
|
+
function buildListQuery(args) {
|
|
38
|
+
const query = {};
|
|
39
|
+
if (args.type !== undefined) {
|
|
40
|
+
query.type = args.type;
|
|
41
|
+
}
|
|
42
|
+
if (args.limit !== undefined) {
|
|
43
|
+
query.limit = args.limit;
|
|
44
|
+
}
|
|
45
|
+
if (args.cursor !== undefined) {
|
|
46
|
+
query.cursor = args.cursor;
|
|
47
|
+
}
|
|
48
|
+
return Object.keys(query).length > 0 ? query : undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Register `source_list`. Passes type/limit/cursor through and returns matcher's
|
|
52
|
+
* pagination envelope verbatim; a matcher error surfaces via the shared
|
|
53
|
+
* toToolError.
|
|
54
|
+
*/
|
|
55
|
+
export function registerSourceListTool(server) {
|
|
56
|
+
server.registerTool('source_list', {
|
|
57
|
+
description: "List the reconciliation sources under a context (cursor-paginated). " +
|
|
58
|
+
'Pass contextId (UUID path param — not a tenant field), and optionally ' +
|
|
59
|
+
'type (LEDGER|BANK|GATEWAY|CUSTOM|FETCHER), limit (1–200), and/or cursor. ' +
|
|
60
|
+
'The response carries { items, nextCursor } — paginate by re-calling ' +
|
|
61
|
+
'with the returned nextCursor. No tenant field is accepted.',
|
|
62
|
+
inputSchema: listSourcesInputShape,
|
|
63
|
+
}, async (args, extra) => {
|
|
64
|
+
const query = buildListQuery(args);
|
|
65
|
+
return dispatchSource({
|
|
66
|
+
method: 'GET',
|
|
67
|
+
path: contextSourcesPath(args.contextId),
|
|
68
|
+
...(query !== undefined ? { query } : {}),
|
|
69
|
+
}, extra);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/tools/source/list.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,0EAA0E;AAC1E,2EAA2E;AAC3E,6EAA6E;AAC7E,wEAAwE;AACxE,+EAA+E;AAC/E,iDAAiD;AAEjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEhE,yFAAyF;AACzF,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,2EAA2E;QACzE,4BAA4B,CAC/B;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,YAAY,CAAC;SAClB,QAAQ,EAAE;SACV,QAAQ,CACP,sEAAsE,CACvE;IACH,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,0DAA0D,CAAC;IACvE,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,mEAAmE;QACjE,oEAAoE,CACvE;CACJ,CAAA;AAKD,+EAA+E;AAC/E,SAAS,cAAc,CAAC,IAAsB;IAC5C,MAAM,KAAK,GAAoC,EAAE,CAAA;IACjD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;IACxB,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC7B,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;IAC1B,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC9B,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;QACE,WAAW,EACT,sEAAsE;YACtE,wEAAwE;YACxE,2EAA2E;YAC3E,sEAAsE;YACtE,4DAA4D;QAC9D,WAAW,EAAE,qBAAqB;KACnC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;QAClC,OAAO,cAAc,CACnB;YACE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC;YACxC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Shared plumbing for the curated source tools.
|
|
2
|
+
//
|
|
3
|
+
// Sources are the data feeds bound to a reconciliation context. Unlike
|
|
4
|
+
// fee-rules' hybrid path, EVERY source op is fully context-nested: create/list
|
|
5
|
+
// hang off /v1/contexts/{contextId}/sources and get/update/delete off
|
|
6
|
+
// /v1/contexts/{contextId}/sources/{sourceId} — single-resource ops KEEP the
|
|
7
|
+
// contextId. This module factors the two path helpers plus the relay boilerplate
|
|
8
|
+
// exactly once so the five tools stay thin (just "build the request") and so
|
|
9
|
+
// curated + generic surfaces map matcher errors through the identical path
|
|
10
|
+
// (toToolError).
|
|
11
|
+
//
|
|
12
|
+
// Security: the bearer token is never read here directly — requireMatcherClient
|
|
13
|
+
// owns the request-scoped relay and fails closed when no usable credential was
|
|
14
|
+
// provided. The contextId path param is a routing identifier (matcher validates
|
|
15
|
+
// the context belongs to the relayed token's tenant); no tenant identifier is
|
|
16
|
+
// ever read or injected, and no source tool input carries one.
|
|
17
|
+
import { requireMatcherClient, UnauthenticatedError, } from '../../auth/request-token.js';
|
|
18
|
+
import { isMatcherApiError, toToolError } from '../tool-error.js';
|
|
19
|
+
/** Build the context-scoped sources collection path (create/list). */
|
|
20
|
+
export function contextSourcesPath(contextId) {
|
|
21
|
+
return `/v1/contexts/${encodeURIComponent(contextId)}/sources`;
|
|
22
|
+
}
|
|
23
|
+
/** Build the single-source sub-path (get/update/delete) — both ids encoded. */
|
|
24
|
+
export function contextSourcePath(contextId, sourceId) {
|
|
25
|
+
return `${contextSourcesPath(contextId)}/${encodeURIComponent(sourceId)}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Run one assembled request through the relay: fail-closed auth, dispatch, and
|
|
29
|
+
* RFC 9457 → tool-error mapping — identical to the generic invoke's dispatch so
|
|
30
|
+
* both surfaces produce the same structured error shape.
|
|
31
|
+
*
|
|
32
|
+
* `clientFor` is injectable for tests; production callers pass nothing and get
|
|
33
|
+
* the request-scoped, fail-closed `requireMatcherClient`.
|
|
34
|
+
*/
|
|
35
|
+
export async function dispatchSource(request, extra, clientFor = requireMatcherClient) {
|
|
36
|
+
let client;
|
|
37
|
+
try {
|
|
38
|
+
// Fail-closed on a missing / empty / non-Bearer credential.
|
|
39
|
+
client = clientFor(extra);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (err instanceof UnauthenticatedError) {
|
|
43
|
+
return { isError: true, content: [{ type: 'text', text: err.message }] };
|
|
44
|
+
}
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const result = await client.request(request);
|
|
49
|
+
// Success → matcher's JSON returned as-is (incl. pagination envelopes and
|
|
50
|
+
// 204 → null for delete).
|
|
51
|
+
return { content: [{ type: 'text', text: JSON.stringify(result ?? null) }] };
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
// RFC 9457 problem → the SHARED structured tool error (status/title/detail/
|
|
55
|
+
// code preserved). A non-matcher error is a bug, not a matcher failure, and
|
|
56
|
+
// is re-thrown rather than masqueraded as one.
|
|
57
|
+
if (isMatcherApiError(err)) {
|
|
58
|
+
return toToolError(err);
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/tools/source/shared.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,uEAAuE;AACvE,+EAA+E;AAC/E,sEAAsE;AACtE,6EAA6E;AAC7E,iFAAiF;AACjF,6EAA6E;AAC7E,2EAA2E;AAC3E,iBAAiB;AACjB,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,8EAA8E;AAC9E,+DAA+D;AAI/D,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GAErB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEjE,sEAAsE;AACtE,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,OAAO,gBAAgB,kBAAkB,CAAC,SAAS,CAAC,UAAU,CAAA;AAChE,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,QAAgB;IACnE,OAAO,GAAG,kBAAkB,CAAC,SAAS,CAAC,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAA;AAC3E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAuB,EACvB,KAAmC,EACnC,YAAyD,oBAAoB;IAE7E,IAAI,MAAqB,CAAA;IACzB,IAAI,CAAC;QACH,4DAA4D;QAC5D,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAA;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,oBAAoB,EAAE,CAAC;YACxC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE,CAAA;QAC1E,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAC5C,0EAA0E;QAC1E,0BAA0B;QAC1B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,CAAC,EAAE,CAAA;IAC9E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,4EAA4E;QAC5E,4EAA4E;QAC5E,+CAA+C;QAC/C,IAAI,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,WAAW,CAAC,GAAG,CAAC,CAAA;QACzB,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// `source_update` — curated PATCH /v1/contexts/{contextId}/sources/{sourceId}.
|
|
2
|
+
//
|
|
3
|
+
// Partial update of a source's fields, mirroring the spec's UpdateSourceRequest.
|
|
4
|
+
// All body fields are optional (PATCH semantics): name, type, side, config.
|
|
5
|
+
// Both contextId and sourceId are path params (sources keep contextId on
|
|
6
|
+
// single-resource ops); no tenant field is accepted (matcher scopes to the
|
|
7
|
+
// relayed token's tenant).
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { SOURCE_SIDES, SOURCE_TYPES } from './create.js';
|
|
10
|
+
import { contextSourcePath, dispatchSource } from './shared.js';
|
|
11
|
+
/** Zod raw shape for `source_update` input — path ids + optional body fields. */
|
|
12
|
+
export const updateSourceInputShape = {
|
|
13
|
+
contextId: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.describe('The reconciliation context id (UUID). Routing path param, not a tenant ' +
|
|
17
|
+
'field.'),
|
|
18
|
+
sourceId: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.describe('The reconciliation source id (UUID) to update.'),
|
|
22
|
+
name: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1)
|
|
25
|
+
.max(50)
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('New human-readable name of the source (1–50 chars).'),
|
|
28
|
+
type: z
|
|
29
|
+
.enum(SOURCE_TYPES)
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('Origin of the source data (LEDGER|BANK|GATEWAY|CUSTOM|FETCHER).'),
|
|
32
|
+
side: z
|
|
33
|
+
.enum(SOURCE_SIDES)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Matching side this source feeds (LEFT|RIGHT).'),
|
|
36
|
+
config: z
|
|
37
|
+
.record(z.string(), z.unknown())
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Source-specific configuration object (validated server-side).'),
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Build the PATCH body from the input minus the path-only ids. Only the fields
|
|
43
|
+
* the caller actually set are sent — matcher treats absent fields as unchanged.
|
|
44
|
+
*/
|
|
45
|
+
export function buildUpdateBody(args) {
|
|
46
|
+
const { contextId: _contextId, sourceId: _sourceId, ...body } = args;
|
|
47
|
+
void _contextId;
|
|
48
|
+
void _sourceId;
|
|
49
|
+
return body;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Register `source_update`. Splits the path ids from the body fields, dispatches
|
|
53
|
+
* a PATCH through the shared fail-closed relay; a matcher error surfaces via the
|
|
54
|
+
* shared toToolError.
|
|
55
|
+
*/
|
|
56
|
+
export function registerSourceUpdateTool(server) {
|
|
57
|
+
server.registerTool('source_update', {
|
|
58
|
+
description: 'Update fields of a reconciliation source by id (UUID) within a context. ' +
|
|
59
|
+
'Provide contextId and sourceId (path params; contextId is not a tenant ' +
|
|
60
|
+
'field). All body fields are optional (PATCH): name (1–50), type ' +
|
|
61
|
+
'(LEDGER|BANK|GATEWAY|CUSTOM|FETCHER), side (LEFT|RIGHT), config. No ' +
|
|
62
|
+
'tenant field is accepted. Returns the updated source, or a structured ' +
|
|
63
|
+
'RFC 9457 error.',
|
|
64
|
+
inputSchema: updateSourceInputShape,
|
|
65
|
+
}, async (args, extra) => {
|
|
66
|
+
return dispatchSource({
|
|
67
|
+
method: 'PATCH',
|
|
68
|
+
path: contextSourcePath(args.contextId, args.sourceId),
|
|
69
|
+
body: buildUpdateBody(args),
|
|
70
|
+
}, extra);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../../src/tools/source/update.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,iFAAiF;AACjF,4EAA4E;AAC5E,yEAAyE;AACzE,2EAA2E;AAC3E,2BAA2B;AAE3B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAE/D,iFAAiF;AACjF,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,yEAAyE;QACvE,QAAQ,CACX;IACH,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,gDAAgD,CAAC;IAC7D,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,QAAQ,CAAC,qDAAqD,CAAC;IAClE,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,YAAY,CAAC;SAClB,QAAQ,EAAE;SACV,QAAQ,CACP,iEAAiE,CAClE;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,YAAY,CAAC;SAClB,QAAQ,EAAE;SACV,QAAQ,CAAC,+CAA+C,CAAC;IAC5D,MAAM,EAAE,CAAC;SACN,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/B,QAAQ,EAAE;SACV,QAAQ,CACP,+DAA+D,CAChE;CACJ,CAAA;AAOD;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAuB;IAEvB,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAA;IACpE,KAAK,UAAU,CAAA;IACf,KAAK,SAAS,CAAA;IACd,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAiB;IACxD,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,WAAW,EACT,0EAA0E;YAC1E,yEAAyE;YACzE,kEAAkE;YAClE,sEAAsE;YACtE,wEAAwE;YACxE,iBAAiB;QACnB,WAAW,EAAE,sBAAsB;KACpC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,OAAO,cAAc,CACnB;YACE,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,iBAAiB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;YACtD,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC;SAC5B,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Shared error-mapping helper: RFC 9457 problem → structured MCP tool error.
|
|
2
|
+
//
|
|
3
|
+
// Both the generic `matcher_invoke` (task 1.2.3) and every curated tool (task
|
|
4
|
+
// 1.3.2 onward) call `toToolError` so that a matcher failure renders the SAME
|
|
5
|
+
// structured shape regardless of which surface produced it. Keeping this in one
|
|
6
|
+
// place is what makes "curated and generic map errors identically" true rather
|
|
7
|
+
// than aspirational.
|
|
8
|
+
//
|
|
9
|
+
// The mapping deliberately PRESERVES the problem's status/title/detail/code
|
|
10
|
+
// rather than collapsing them into one human string — the model (and any UI on
|
|
11
|
+
// top of it) can branch on `status`/`code` and surface `detail` verbatim. The
|
|
12
|
+
// matcher's bearer token is never part of a MatcherApiError (see matcher/client.ts),
|
|
13
|
+
// so nothing token-bearing can leak through here.
|
|
14
|
+
import { MatcherApiError } from '../matcher/client.js';
|
|
15
|
+
/** Guidance shown alongside `retryable: true` — the request can be retried as-is. */
|
|
16
|
+
const GUIDANCE_RETRYABLE = 'matcher backend was unavailable/timed out; retry';
|
|
17
|
+
/** Guidance shown alongside `retryable: false` — the request itself must change. */
|
|
18
|
+
const GUIDANCE_NOT_RETRYABLE = 'fix the request';
|
|
19
|
+
/**
|
|
20
|
+
* Classify a matcher HTTP status into a retryability hint. Backend
|
|
21
|
+
* unavailability/timeouts (any 5xx — including the synthetic 504 for transport
|
|
22
|
+
* failures — and 429 Too Many Requests) are retryable as-is; every other
|
|
23
|
+
* client-side 4xx is not (the caller must change the request first).
|
|
24
|
+
*/
|
|
25
|
+
function retryabilityFor(status) {
|
|
26
|
+
const retryable = status >= 500 || status === 429;
|
|
27
|
+
return {
|
|
28
|
+
retryable,
|
|
29
|
+
guidance: retryable ? GUIDANCE_RETRYABLE : GUIDANCE_NOT_RETRYABLE,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Map a `MatcherApiError` to an MCP tool error result (`isError: true`). The
|
|
34
|
+
* result carries two content blocks:
|
|
35
|
+
* 1. A one-line human summary (`status — title`) for quick reading.
|
|
36
|
+
* 2. The full structured problem as JSON, so a caller can branch on
|
|
37
|
+
* `status`/`code` and read `detail`/`errors` without re-parsing a sentence.
|
|
38
|
+
*
|
|
39
|
+
* The problem is preserved, never swallowed into a generic "request failed".
|
|
40
|
+
*/
|
|
41
|
+
export function toToolError(err) {
|
|
42
|
+
const { status, problem } = err;
|
|
43
|
+
const { retryable, guidance } = retryabilityFor(status);
|
|
44
|
+
const payload = {
|
|
45
|
+
status,
|
|
46
|
+
...(problem.title !== undefined ? { title: problem.title } : {}),
|
|
47
|
+
...(problem.detail !== undefined ? { detail: problem.detail } : {}),
|
|
48
|
+
...(problem.code !== undefined ? { code: problem.code } : {}),
|
|
49
|
+
...(problem.type !== undefined ? { type: problem.type } : {}),
|
|
50
|
+
...(problem.instance !== undefined ? { instance: problem.instance } : {}),
|
|
51
|
+
...(problem.errors !== undefined ? { errors: problem.errors } : {}),
|
|
52
|
+
retryable,
|
|
53
|
+
guidance,
|
|
54
|
+
};
|
|
55
|
+
const summary = problem.title !== undefined
|
|
56
|
+
? `matcher error ${status}: ${problem.title}`
|
|
57
|
+
: `matcher error ${status}`;
|
|
58
|
+
return {
|
|
59
|
+
isError: true,
|
|
60
|
+
content: [
|
|
61
|
+
{ type: 'text', text: summary },
|
|
62
|
+
{ type: 'text', text: JSON.stringify(payload) },
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Narrow an unknown thrown value to `MatcherApiError`. Lets callers route only
|
|
68
|
+
* matcher API failures through `toToolError` and re-throw anything else (a bug
|
|
69
|
+
* is not a matcher error and should not masquerade as one).
|
|
70
|
+
*/
|
|
71
|
+
export function isMatcherApiError(err) {
|
|
72
|
+
return err instanceof MatcherApiError;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=tool-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-error.js","sourceRoot":"","sources":["../../src/tools/tool-error.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,gFAAgF;AAChF,+EAA+E;AAC/E,qBAAqB;AACrB,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,8EAA8E;AAC9E,qFAAqF;AACrF,kDAAkD;AAIlD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AA4BtD,qFAAqF;AACrF,MAAM,kBAAkB,GACtB,kDAAkD,CAAA;AACpD,oFAAoF;AACpF,MAAM,sBAAsB,GAAG,iBAAiB,CAAA;AAEhD;;;;;GAKG;AACH,SAAS,eAAe,CAAC,MAAc;IAIrC,MAAM,SAAS,GAAG,MAAM,IAAI,GAAG,IAAI,MAAM,KAAK,GAAG,CAAA;IACjD,OAAO;QACL,SAAS;QACT,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,sBAAsB;KAClE,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,GAAoB;IAC9C,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,CAAA;IAC/B,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,eAAe,CAAC,MAAM,CAAC,CAAA;IACvD,MAAM,OAAO,GAAqB;QAChC,MAAM;QACN,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzE,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,SAAS;QACT,QAAQ;KACT,CAAA;IAED,MAAM,OAAO,GACX,OAAO,CAAC,KAAK,KAAK,SAAS;QACzB,CAAC,CAAC,iBAAiB,MAAM,KAAK,OAAO,CAAC,KAAK,EAAE;QAC7C,CAAC,CAAC,iBAAiB,MAAM,EAAE,CAAA;IAE/B,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO,EAAE;YACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE;YAC/B,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE;SAChD;KACF,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,OAAO,GAAG,YAAY,eAAe,CAAA;AACvC,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Inbound body-size guard + server-timeout constants for the HTTP transport.
|
|
2
|
+
//
|
|
3
|
+
// `transport/http.ts` currently reads the request body unbounded (every chunk
|
|
4
|
+
// appended to an array). That is a trivial memory-exhaustion / DoS vector for a
|
|
5
|
+
// network-reachable, credential-bearing relay. This module supplies a small,
|
|
6
|
+
// pure, testable guard the body reader uses to bound the accumulated size, plus
|
|
7
|
+
// the Node HTTP server timeout values integration applies in `createServer`.
|
|
8
|
+
//
|
|
9
|
+
// Token-relay rule: nothing here touches the `Authorization` header or any
|
|
10
|
+
// request content beyond byte counts. `BodyTooLargeError` carries only sizes —
|
|
11
|
+
// never any portion of the body or any token.
|
|
12
|
+
/** Default cap when a config value is not threaded in (matches Config default). */
|
|
13
|
+
export const DEFAULT_MAX_BODY_BYTES = 1_048_576; // 1 MiB
|
|
14
|
+
/**
|
|
15
|
+
* How long the server waits for a complete request (ms) before aborting it.
|
|
16
|
+
* Set on the Node HTTP server in integration (`createServer`). Generous enough
|
|
17
|
+
* for a slow large-but-legal request, short enough to shed a stuck connection.
|
|
18
|
+
*/
|
|
19
|
+
export const REQUEST_TIMEOUT_MS = 60_000;
|
|
20
|
+
/**
|
|
21
|
+
* How long the server waits for the complete request headers (ms). Must be less
|
|
22
|
+
* than `REQUEST_TIMEOUT_MS` (Node enforces this) so a Slowloris-style trickle of
|
|
23
|
+
* headers is dropped well before the overall request budget. Set on the Node
|
|
24
|
+
* HTTP server in integration.
|
|
25
|
+
*/
|
|
26
|
+
export const HEADERS_TIMEOUT_MS = 30_000;
|
|
27
|
+
/**
|
|
28
|
+
* Thrown when accumulated inbound body bytes exceed the configured limit. Reads
|
|
29
|
+
* as HTTP 413 (Payload Too Large) to an operator. Carries only the limit and the
|
|
30
|
+
* observed size — never any body content and never a token.
|
|
31
|
+
*/
|
|
32
|
+
export class BodyTooLargeError extends Error {
|
|
33
|
+
/** HTTP status an operator should see — 413 Payload Too Large. */
|
|
34
|
+
status = 413;
|
|
35
|
+
/** The configured maximum, in bytes. */
|
|
36
|
+
limitBytes;
|
|
37
|
+
/** Bytes accumulated when the limit was first exceeded. */
|
|
38
|
+
observedBytes;
|
|
39
|
+
constructor(limitBytes, observedBytes) {
|
|
40
|
+
super(`request body exceeds the ${limitBytes}-byte limit ` +
|
|
41
|
+
`(received at least ${observedBytes} bytes)`);
|
|
42
|
+
this.name = 'BodyTooLargeError';
|
|
43
|
+
this.limitBytes = limitBytes;
|
|
44
|
+
this.observedBytes = observedBytes;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A running tally of inbound body bytes that fails closed once the limit is
|
|
49
|
+
* exceeded. The body reader calls `add(chunk.length)` for each chunk and stops
|
|
50
|
+
* (the guard throws) before appending an over-limit chunk to memory, so the
|
|
51
|
+
* relay never buffers more than `maxBodyBytes` (+ one chunk) of an attacker's
|
|
52
|
+
* payload.
|
|
53
|
+
*
|
|
54
|
+
* Pure and allocation-free beyond a single number — trivially unit-testable
|
|
55
|
+
* without a socket.
|
|
56
|
+
*/
|
|
57
|
+
export class BodySizeGuard {
|
|
58
|
+
maxBodyBytes;
|
|
59
|
+
total = 0;
|
|
60
|
+
constructor(maxBodyBytes = DEFAULT_MAX_BODY_BYTES) {
|
|
61
|
+
if (!Number.isInteger(maxBodyBytes) || maxBodyBytes < 1) {
|
|
62
|
+
throw new Error(`invalid maxBodyBytes: ${maxBodyBytes} (expected a positive integer)`);
|
|
63
|
+
}
|
|
64
|
+
this.maxBodyBytes = maxBodyBytes;
|
|
65
|
+
}
|
|
66
|
+
/** Bytes accumulated so far. */
|
|
67
|
+
get bytes() {
|
|
68
|
+
return this.total;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Account for `chunkLength` more bytes. Throws `BodyTooLargeError` the moment
|
|
72
|
+
* the running total would exceed the limit — the caller must NOT have appended
|
|
73
|
+
* the chunk to its buffer yet, so the over-limit bytes are never retained.
|
|
74
|
+
*/
|
|
75
|
+
add(chunkLength) {
|
|
76
|
+
this.total += chunkLength;
|
|
77
|
+
if (this.total > this.maxBodyBytes) {
|
|
78
|
+
throw new BodyTooLargeError(this.maxBodyBytes, this.total);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=body-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-limit.js","sourceRoot":"","sources":["../../src/transport/body-limit.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,6EAA6E;AAC7E,gFAAgF;AAChF,6EAA6E;AAC7E,EAAE;AACF,2EAA2E;AAC3E,+EAA+E;AAC/E,8CAA8C;AAE9C,mFAAmF;AACnF,MAAM,CAAC,MAAM,sBAAsB,GAAG,SAAS,CAAA,CAAC,QAAQ;AAExD;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAA;AAExC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAA;AAExC;;;;GAIG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,kEAAkE;IACzD,MAAM,GAAG,GAAG,CAAA;IACrB,wCAAwC;IAC/B,UAAU,CAAQ;IAC3B,2DAA2D;IAClD,aAAa,CAAQ;IAE9B,YAAY,UAAkB,EAAE,aAAqB;QACnD,KAAK,CACH,4BAA4B,UAAU,cAAc;YAClD,sBAAsB,aAAa,SAAS,CAC/C,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;QAC/B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,aAAa,GAAG,aAAa,CAAA;IACpC,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,aAAa;IACP,YAAY,CAAQ;IAC7B,KAAK,GAAG,CAAC,CAAA;IAEjB,YAAY,eAAuB,sBAAsB;QACvD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,KAAK,CACb,yBAAyB,YAAY,gCAAgC,CACtE,CAAA;QACH,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;IAClC,CAAC;IAED,gCAAgC;IAChC,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED;;;;OAIG;IACH,GAAG,CAAC,WAAmB;QACrB,IAAI,CAAC,KAAK,IAAI,WAAW,CAAA;QACzB,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YACnC,MAAM,IAAI,iBAAiB,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;QAC5D,CAAC;IACH,CAAC;CACF"}
|