@lerianstudio/matcher-mcp 1.0.0 → 1.1.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.
Files changed (102) hide show
  1. package/dist/config.js +33 -0
  2. package/dist/config.js.map +1 -1
  3. package/dist/matcher/client.js +13 -4
  4. package/dist/matcher/client.js.map +1 -1
  5. package/dist/server.js +7 -0
  6. package/dist/server.js.map +1 -1
  7. package/dist/spec/curated-operations.js +14 -9
  8. package/dist/spec/curated-operations.js.map +1 -1
  9. package/dist/spec/index.js +15 -0
  10. package/dist/spec/index.js.map +1 -1
  11. package/dist/spec/openapi.yaml +22 -4
  12. package/dist/tools/context/index.js +17 -10
  13. package/dist/tools/context/index.js.map +1 -1
  14. package/dist/tools/context/setup-progress.js +56 -0
  15. package/dist/tools/context/setup-progress.js.map +1 -0
  16. package/dist/tools/dashboard/aggregates.js +5 -2
  17. package/dist/tools/dashboard/aggregates.js.map +1 -1
  18. package/dist/tools/dashboard/cash-impact.js +4 -3
  19. package/dist/tools/dashboard/cash-impact.js.map +1 -1
  20. package/dist/tools/dashboard/match-rate.js +4 -2
  21. package/dist/tools/dashboard/match-rate.js.map +1 -1
  22. package/dist/tools/dashboard/metrics.js +4 -3
  23. package/dist/tools/dashboard/metrics.js.map +1 -1
  24. package/dist/tools/dashboard/shared.js +16 -4
  25. package/dist/tools/dashboard/shared.js.map +1 -1
  26. package/dist/tools/dashboard/sla.js +4 -3
  27. package/dist/tools/dashboard/sla.js.map +1 -1
  28. package/dist/tools/dashboard/source-breakdown.js +4 -3
  29. package/dist/tools/dashboard/source-breakdown.js.map +1 -1
  30. package/dist/tools/dashboard/volume.js +5 -3
  31. package/dist/tools/dashboard/volume.js.map +1 -1
  32. package/dist/tools/fee-schedule/create.js +17 -6
  33. package/dist/tools/fee-schedule/create.js.map +1 -1
  34. package/dist/tools/field-map/create.js +62 -0
  35. package/dist/tools/field-map/create.js.map +1 -0
  36. package/dist/tools/field-map/delete.js +35 -0
  37. package/dist/tools/field-map/delete.js.map +1 -0
  38. package/dist/tools/field-map/get.js +43 -0
  39. package/dist/tools/field-map/get.js.map +1 -0
  40. package/dist/tools/field-map/index.js +31 -0
  41. package/dist/tools/field-map/index.js.map +1 -0
  42. package/dist/tools/field-map/list.js +64 -0
  43. package/dist/tools/field-map/list.js.map +1 -0
  44. package/dist/tools/field-map/shared.js +114 -0
  45. package/dist/tools/field-map/shared.js.map +1 -0
  46. package/dist/tools/field-map/update.js +46 -0
  47. package/dist/tools/field-map/update.js.map +1 -0
  48. package/dist/tools/generic/describe-operation.js +40 -0
  49. package/dist/tools/generic/describe-operation.js.map +1 -1
  50. package/dist/tools/generic/invoke.js +95 -7
  51. package/dist/tools/generic/invoke.js.map +1 -1
  52. package/dist/tools/ingestion/fetch-source.js +105 -0
  53. package/dist/tools/ingestion/fetch-source.js.map +1 -0
  54. package/dist/tools/ingestion/index.js +10 -8
  55. package/dist/tools/ingestion/index.js.map +1 -1
  56. package/dist/tools/ingestion/shared.js +8 -3
  57. package/dist/tools/ingestion/shared.js.map +1 -1
  58. package/dist/tools/ingestion/upload.js +166 -0
  59. package/dist/tools/ingestion/upload.js.map +1 -0
  60. package/dist/tools/match-rule/create.js +13 -10
  61. package/dist/tools/match-rule/create.js.map +1 -1
  62. package/dist/tools/match-rule/list.js +2 -2
  63. package/dist/tools/match-rule/list.js.map +1 -1
  64. package/dist/tools/match-rule/shared.js +23 -0
  65. package/dist/tools/match-rule/shared.js.map +1 -1
  66. package/dist/tools/match-rule/update.js +6 -6
  67. package/dist/tools/match-rule/update.js.map +1 -1
  68. package/dist/tools/matching/get.js +38 -15
  69. package/dist/tools/matching/get.js.map +1 -1
  70. package/dist/tools/matching/shared.js +33 -3
  71. package/dist/tools/matching/shared.js.map +1 -1
  72. package/dist/tools/matching/start.js +38 -12
  73. package/dist/tools/matching/start.js.map +1 -1
  74. package/dist/tools/report/count-exceptions.js +3 -2
  75. package/dist/tools/report/count-exceptions.js.map +1 -1
  76. package/dist/tools/report/count-matched.js +3 -2
  77. package/dist/tools/report/count-matched.js.map +1 -1
  78. package/dist/tools/report/count-transactions.js +3 -2
  79. package/dist/tools/report/count-transactions.js.map +1 -1
  80. package/dist/tools/report/count-unmatched.js +3 -2
  81. package/dist/tools/report/count-unmatched.js.map +1 -1
  82. package/dist/tools/report/export-exceptions.js +6 -4
  83. package/dist/tools/report/export-exceptions.js.map +1 -1
  84. package/dist/tools/report/export-matched.js +3 -1
  85. package/dist/tools/report/export-matched.js.map +1 -1
  86. package/dist/tools/report/export-summary.js +3 -1
  87. package/dist/tools/report/export-summary.js.map +1 -1
  88. package/dist/tools/report/export-unmatched.js +5 -3
  89. package/dist/tools/report/export-unmatched.js.map +1 -1
  90. package/dist/tools/report/export-variance.js +3 -1
  91. package/dist/tools/report/export-variance.js.map +1 -1
  92. package/dist/tools/report/matched.js +5 -3
  93. package/dist/tools/report/matched.js.map +1 -1
  94. package/dist/tools/report/shared.js +16 -4
  95. package/dist/tools/report/shared.js.map +1 -1
  96. package/dist/tools/report/summary.js +5 -3
  97. package/dist/tools/report/summary.js.map +1 -1
  98. package/dist/tools/report/unmatched.js +5 -3
  99. package/dist/tools/report/unmatched.js.map +1 -1
  100. package/dist/tools/report/variance.js +5 -3
  101. package/dist/tools/report/variance.js.map +1 -1
  102. package/package.json +1 -1
@@ -0,0 +1,166 @@
1
+ // `ingestion_upload` — curated POST
2
+ // /v1/imports/contexts/{contextId}/sources/{sourceId}/upload.
3
+ //
4
+ // The backend endpoint is multipart/form-data, which a JSON MCP tool can't
5
+ // carry directly. This tool accepts the file body one of two ways and the
6
+ // relay rebuilds the real multipart request either way: wrap the bytes in a
7
+ // `FormData` and let `matcher/client.ts`'s FormData branch hand it to `fetch`
8
+ // untouched (no JSON.stringify, no content-type override — the runtime sets
9
+ // the multipart boundary itself).
10
+ //
11
+ // - `contentBase64`: the file inline, base64-encoded. Fine for small files,
12
+ // but base64-in-a-tool-call does NOT scale — an LLM client must emit the
13
+ // whole encoded string in its OUTPUT tokens, and base64 inflates bytes by
14
+ // ~33% on top of that (a 1000-row file is easily ~150k tokens). Practical
15
+ // ceiling: the relay's inbound JSON body cap (1 MiB default,
16
+ // MAX_BODY_BYTES override) means raw files up to ~750 KB upload cleanly.
17
+ // - `fileUrl`: a URL to the file. The RELAY fetches the bytes server-side
18
+ // (see fetch-source.ts) — the client only ever emits a short URL, so this
19
+ // is the path for real files. The fetch is SSRF-guarded: only hostnames
20
+ // in MCP_UPLOAD_ALLOWED_HOSTS (exact match, empty by default) are
21
+ // reachable, redirects are rejected, and the response is size-capped by
22
+ // MCP_UPLOAD_MAX_FETCH_BYTES.
23
+ //
24
+ // Exactly one of the two must be provided — validated in the handler (Huma's
25
+ // inputSchema is a raw Zod shape, not a refinable ZodObject, so cross-field
26
+ // "exactly one of" can't live in the schema itself).
27
+ //
28
+ // Field order matters: `format` MUST be appended BEFORE `file`. The backend's
29
+ // streaming multipart parser binds the declared format from whichever field
30
+ // arrives first; if `file` arrives first it falls back to inferring the
31
+ // format from the filename extension, which rejects e.g. a camt053 upload
32
+ // with `400 MTCH-0308`. See ui/scripts/seed-demo-client.mjs `uploadFile` for
33
+ // the same contract used by the demo seeder.
34
+ //
35
+ // contextId and sourceId are routing path params, not tenant fields — matcher
36
+ // validates both against the relayed token's tenant. No tenant field is
37
+ // accepted here either.
38
+ import { z } from 'zod';
39
+ import { loadConfig } from '../../config.js';
40
+ import { fetchSourceFile } from './fetch-source.js';
41
+ import { contextSourceUploadPath, dispatchIngestion } from './shared.js';
42
+ /** Declared parse formats the backend accepts for an ingestion upload. */
43
+ const UPLOAD_FORMATS = ['csv', 'json', 'xml', 'camt053'];
44
+ /** Zod raw shape for `ingestion_upload` input — path ids + format + file. */
45
+ export const ingestionUploadInputShape = {
46
+ contextId: z
47
+ .string()
48
+ .min(1)
49
+ .describe('The reconciliation context id (UUID). Routing path param, not a tenant ' +
50
+ 'field. The context MUST be ACTIVE — uploading to a DRAFT context is ' +
51
+ 'rejected with 403 "context is not active".'),
52
+ sourceId: z
53
+ .string()
54
+ .min(1)
55
+ .describe('The source id (UUID) within the context to upload into. Must already ' +
56
+ 'exist (create it first with source_create).'),
57
+ format: z
58
+ .enum(UPLOAD_FORMATS)
59
+ .describe('The declared parse format of the file: csv | json | xml | camt053. ' +
60
+ 'Sent as the first multipart field — matcher binds the format from it ' +
61
+ 'before reading the file, so always pass the true format rather than ' +
62
+ 'relying on filename-extension inference.'),
63
+ filename: z
64
+ .string()
65
+ .min(1)
66
+ .describe('The original file name (e.g. "transactions.csv"), sent as the ' +
67
+ 'multipart filename for the file part.'),
68
+ contentBase64: z
69
+ .string()
70
+ .base64()
71
+ .optional()
72
+ .describe('The file bytes, base64-encoded. For SMALL files only — base64 inflates ' +
73
+ 'bytes by ~33% and the LLM client must emit the whole encoded string ' +
74
+ 'as output tokens. The relay\'s inbound JSON body is capped (1 MiB by ' +
75
+ 'default, MAX_BODY_BYTES env override), so raw files up to ~750 KB ' +
76
+ 'upload cleanly under the default. Exactly one of contentBase64 / ' +
77
+ 'fileUrl is required; for real files prefer fileUrl.'),
78
+ fileUrl: z
79
+ .string()
80
+ .min(1)
81
+ .optional()
82
+ .describe('A URL the RELAY fetches the file from server-side — the client sends ' +
83
+ 'only this short URL, not the file bytes, so this is the path for ' +
84
+ 'real files (no output-token inflation). The fetch is SSRF-guarded: ' +
85
+ 'only hostnames listed in MCP_UPLOAD_ALLOWED_HOSTS are reachable ' +
86
+ '(empty allowlist by default — every host is rejected until an ' +
87
+ 'operator opts one in), redirects are rejected, and the response is ' +
88
+ 'capped at MCP_UPLOAD_MAX_FETCH_BYTES (50 MiB default). Exactly one ' +
89
+ 'of contentBase64 / fileUrl is required.'),
90
+ };
91
+ /**
92
+ * Register `ingestion_upload`. Resolves the file bytes from exactly one of
93
+ * `contentBase64` (decode) or `fileUrl` (SSRF-guarded server-side fetch — see
94
+ * fetch-source.ts), then rebuilds a `FormData` with `format` appended before
95
+ * `file` (order is load-bearing — see module comment) and dispatches the POST
96
+ * through the shared fail-closed relay; a matcher error (e.g. 403 inactive
97
+ * context, 400 malformed file) surfaces via the shared toToolError.
98
+ */
99
+ export function registerIngestionUploadTool(server) {
100
+ server.registerTool('ingestion_upload', {
101
+ description: 'Upload a file into a source for ingestion. Provide the file body ' +
102
+ 'via EXACTLY ONE of two ways: (1) contentBase64 — the file inline as ' +
103
+ 'base64 in JSON, for SMALL files only (raw files up to ~750 KB; ' +
104
+ 'larger payloads burn the calling LLM\'s output tokens and risk the ' +
105
+ 'relay\'s inbound body cap). (2) fileUrl — a URL the relay fetches ' +
106
+ 'server-side, so the client sends only the short URL; this is the ' +
107
+ 'path for real files. fileUrl fetches are restricted to an explicit ' +
108
+ 'hostname allowlist (MCP_UPLOAD_ALLOWED_HOSTS, empty by default) and ' +
109
+ 'size-capped (MCP_UPLOAD_MAX_FETCH_BYTES); a non-allowlisted host is ' +
110
+ 'rejected before any network call. Either way the relay rebuilds the ' +
111
+ 'multipart/form-data upload the matcher backend expects. ' +
112
+ 'Prerequisites: the context must be ACTIVE and the sourceId must ' +
113
+ 'already exist. No tenant field is accepted. Returns the 202 job ' +
114
+ 'response, or a structured RFC 9457 error (e.g. 403 if the context ' +
115
+ 'is not ACTIVE).',
116
+ inputSchema: ingestionUploadInputShape,
117
+ }, async (args, extra) => {
118
+ const hasBase64 = args.contentBase64 !== undefined;
119
+ const hasFileUrl = args.fileUrl !== undefined;
120
+ if (hasBase64 === hasFileUrl) {
121
+ return {
122
+ isError: true,
123
+ content: [
124
+ {
125
+ type: 'text',
126
+ text: 'ingestion_upload requires exactly one of contentBase64 or ' +
127
+ `fileUrl, got ${hasBase64 ? 'both' : 'neither'}.`,
128
+ },
129
+ ],
130
+ };
131
+ }
132
+ let bytes;
133
+ if (hasBase64) {
134
+ bytes = Buffer.from(args.contentBase64, 'base64');
135
+ }
136
+ else {
137
+ const cfg = loadConfig();
138
+ try {
139
+ bytes = await fetchSourceFile(args.fileUrl, {
140
+ allowedHosts: cfg.uploadAllowedHosts,
141
+ maxBytes: cfg.uploadMaxFetchBytes,
142
+ timeoutMs: cfg.timeoutMs,
143
+ });
144
+ }
145
+ catch (err) {
146
+ const message = err instanceof Error ? err.message : 'unknown error';
147
+ return {
148
+ isError: true,
149
+ content: [
150
+ { type: 'text', text: `failed to fetch fileUrl: ${message}` },
151
+ ],
152
+ };
153
+ }
154
+ }
155
+ const form = new FormData();
156
+ // 'format' MUST precede 'file' — see module comment.
157
+ form.append('format', args.format);
158
+ form.append('file', new Blob([bytes]), args.filename);
159
+ return dispatchIngestion({
160
+ method: 'POST',
161
+ path: contextSourceUploadPath(args.contextId, args.sourceId),
162
+ body: form,
163
+ }, extra);
164
+ });
165
+ }
166
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../../src/tools/ingestion/upload.ts"],"names":[],"mappings":"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"}
@@ -13,9 +13,14 @@
13
13
  // exists in the schema: the rule is created for the tenant of the relayed bearer
14
14
  // token, and matcher validates that the context belongs to that tenant.
15
15
  import { z } from 'zod';
16
- import { contextRulesPath, dispatchMatchRule } from './shared.js';
17
- /** Matching strategy. EXACT / TOLERANCE / DATE_LAG there is no FUZZY. */
18
- export const MATCH_RULE_TYPES = ['EXACT', 'TOLERANCE', 'DATE_LAG'];
16
+ import { contextRulesPath, dispatchMatchRule, RULE_CONFIG_DESC_BODY, } from './shared.js';
17
+ /** Matching strategy. EXACT / TOLERANCE / DATE_LAG / FUZZY (spec oneof). */
18
+ export const MATCH_RULE_TYPES = [
19
+ 'EXACT',
20
+ 'TOLERANCE',
21
+ 'DATE_LAG',
22
+ 'FUZZY',
23
+ ];
19
24
  /** Zod raw shape for `match_rule_create` input — mirrors CreateMatchRuleRequest. */
20
25
  export const createMatchRuleInputShape = {
21
26
  contextId: z
@@ -34,14 +39,12 @@ export const createMatchRuleInputShape = {
34
39
  .enum(MATCH_RULE_TYPES)
35
40
  .describe('Matching strategy. EXACT requires identical field values; TOLERANCE ' +
36
41
  'allows configured numeric slack; DATE_LAG allows a configured date ' +
37
- 'offset between sides.'),
42
+ 'offset between sides; FUZZY matches on normalized/similarity-scored ' +
43
+ 'references (free-text memos, truncated refs) and ALWAYS proposes for ' +
44
+ 'review — never auto-confirms.'),
38
45
  config: z
39
46
  .record(z.string(), z.unknown())
40
- .describe('Rule-specific configuration object (matched fields and tolerance/lag ' +
41
- 'parameters). Its shape depends on `type` and is validated server-side. ' +
42
- 'EXACT: {"fields":["amount","reference"]}. TOLERANCE: ' +
43
- '{"fields":["amount"],"toleranceAbs":"0.01"} or "tolerancePct". ' +
44
- 'DATE_LAG: {"fields":["date"],"maxLagDays":2}.'),
47
+ .describe('Rule-specific ' + RULE_CONFIG_DESC_BODY),
45
48
  };
46
49
  /**
47
50
  * Register `match_rule_create`. Splits the routing `contextId` (path) from the
@@ -52,7 +55,7 @@ export function registerMatchRuleCreateTool(server) {
52
55
  server.registerTool('match_rule_create', {
53
56
  description: 'Create a match rule within a reconciliation context. Provide contextId ' +
54
57
  '(UUID path), priority (1–1000, unique within the context), type ' +
55
- '(EXACT|TOLERANCE|DATE_LAG), and config (a type-specific object validated ' +
58
+ '(EXACT|TOLERANCE|DATE_LAG|FUZZY), and config (a type-specific object validated ' +
56
59
  'server-side). No tenant field is accepted — the rule is created for your ' +
57
60
  "token's tenant. Returns the created rule, or a structured RFC 9457 error.",
58
61
  inputSchema: createMatchRuleInputShape,
@@ -1 +1 @@
1
- {"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/tools/match-rule/create.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,yEAAyE;AACzE,+EAA+E;AAC/E,8EAA8E;AAC9E,iFAAiF;AACjF,8EAA8E;AAC9E,kFAAkF;AAClF,gFAAgF;AAChF,WAAW;AACX,EAAE;AACF,2EAA2E;AAC3E,iFAAiF;AACjF,wEAAwE;AAExE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEjE,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,CAAU,CAAA;AAE3E,oFAAoF;AACpF,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,yEAAyE;QACvE,qEAAqE,CACxE;IACH,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,IAAI,CAAC;SACT,QAAQ,CACP,uEAAuE;QACrE,qBAAqB,CACxB;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,gBAAgB,CAAC;SACtB,QAAQ,CACP,sEAAsE;QACpE,qEAAqE;QACrE,uBAAuB,CAC1B;IACH,MAAM,EAAE,CAAC;SACN,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/B,QAAQ,CACP,uEAAuE;QACrE,yEAAyE;QACzE,uDAAuD;QACvD,iEAAiE;QACjE,+CAA+C,CAClD;CACJ,CAAA;AAOD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAiB;IAC3D,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,WAAW,EACT,yEAAyE;YACzE,kEAAkE;YAClE,2EAA2E;YAC3E,2EAA2E;YAC3E,2EAA2E;QAC7E,WAAW,EAAE,yBAAyB;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAA;QACnC,OAAO,iBAAiB,CACtB,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,EAC3D,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/tools/match-rule/create.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,yEAAyE;AACzE,+EAA+E;AAC/E,8EAA8E;AAC9E,iFAAiF;AACjF,8EAA8E;AAC9E,kFAAkF;AAClF,gFAAgF;AAChF,WAAW;AACX,EAAE;AACF,2EAA2E;AAC3E,iFAAiF;AACjF,wEAAwE;AAExE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AAEpB,4EAA4E;AAC5E,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,OAAO;IACP,WAAW;IACX,UAAU;IACV,OAAO;CACC,CAAA;AAEV,oFAAoF;AACpF,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,yEAAyE;QACvE,qEAAqE,CACxE;IACH,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,IAAI,CAAC;SACT,QAAQ,CACP,uEAAuE;QACrE,qBAAqB,CACxB;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,gBAAgB,CAAC;SACtB,QAAQ,CACP,sEAAsE;QACpE,qEAAqE;QACrE,sEAAsE;QACtE,uEAAuE;QACvE,+BAA+B,CAClC;IACH,MAAM,EAAE,CAAC;SACN,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/B,QAAQ,CAAC,gBAAgB,GAAG,qBAAqB,CAAC;CACtD,CAAA;AAOD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAiB;IAC3D,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,WAAW,EACT,yEAAyE;YACzE,kEAAkE;YAClE,iFAAiF;YACjF,2EAA2E;YAC3E,2EAA2E;QAC7E,WAAW,EAAE,yBAAyB;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAA;QACnC,OAAO,iBAAiB,CACtB,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,EAC3D,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
@@ -19,7 +19,7 @@ export const listMatchRulesInputShape = {
19
19
  type: z
20
20
  .enum(MATCH_RULE_TYPES)
21
21
  .optional()
22
- .describe('Optional filter by rule type (EXACT|TOLERANCE|DATE_LAG). Omit to list all.'),
22
+ .describe('Optional filter by rule type (EXACT|TOLERANCE|DATE_LAG|FUZZY). Omit to list all.'),
23
23
  limit: z
24
24
  .number()
25
25
  .int()
@@ -56,7 +56,7 @@ export function registerMatchRuleListTool(server) {
56
56
  server.registerTool('match_rule_list', {
57
57
  description: 'List the match rules of a reconciliation context (cursor-paginated). ' +
58
58
  'Provide contextId (UUID path); optionally filter by type ' +
59
- '(EXACT|TOLERANCE|DATE_LAG) and pass limit (1–200) and/or cursor. The ' +
59
+ '(EXACT|TOLERANCE|DATE_LAG|FUZZY) and pass limit (1–200) and/or cursor. The ' +
60
60
  'response carries { items, nextCursor } — paginate by re-calling with the ' +
61
61
  'returned nextCursor. No tenant field is accepted.',
62
62
  inputSchema: listMatchRulesInputShape,
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/tools/match-rule/list.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,4EAA4E;AAC5E,iEAAiE;AACjE,6EAA6E;AAC7E,wEAAwE;AACxE,iFAAiF;AACjF,4BAA4B;AAE5B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEjE,6FAA6F;AAC7F,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,yEAAyE;QACvE,4BAA4B,CAC/B;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,gBAAgB,CAAC;SACtB,QAAQ,EAAE;SACV,QAAQ,CACP,4EAA4E,CAC7E;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;AAOD,+EAA+E;AAC/E,SAAS,cAAc,CAAC,IAAyB;IAC/C,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,yBAAyB,CAAC,MAAiB;IACzD,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,WAAW,EACT,uEAAuE;YACvE,2DAA2D;YAC3D,uEAAuE;YACvE,2EAA2E;YAC3E,mDAAmD;QACrD,WAAW,EAAE,wBAAwB;KACtC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;QAClC,OAAO,iBAAiB,CACtB;YACE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC;YACtC,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"}
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/tools/match-rule/list.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,4EAA4E;AAC5E,iEAAiE;AACjE,6EAA6E;AAC7E,wEAAwE;AACxE,iFAAiF;AACjF,4BAA4B;AAE5B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEjE,6FAA6F;AAC7F,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,yEAAyE;QACvE,4BAA4B,CAC/B;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,gBAAgB,CAAC;SACtB,QAAQ,EAAE;SACV,QAAQ,CACP,kFAAkF,CACnF;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;AAOD,+EAA+E;AAC/E,SAAS,cAAc,CAAC,IAAyB;IAC/C,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,yBAAyB,CAAC,MAAiB;IACzD,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,WAAW,EACT,uEAAuE;YACvE,2DAA2D;YAC3D,6EAA6E;YAC7E,2EAA2E;YAC3E,mDAAmD;QACrD,WAAW,EAAE,wBAAwB;KACtC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;QAClC,OAAO,iBAAiB,CACtB;YACE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC;YACtC,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"}
@@ -32,6 +32,29 @@ export function contextRulePath(contextId, ruleId) {
32
32
  export function contextRulesReorderPath(contextId) {
33
33
  return `${contextRulesPath(contextId)}/reorder`;
34
34
  }
35
+ /**
36
+ * Shared body of the `config` field description, centralized so create and
37
+ * update stay in lockstep as the rule vocabulary grows. Callers prepend the
38
+ * lead phrase ("Rule-specific " / "New rule-specific ") to preserve their exact
39
+ * historical wording.
40
+ */
41
+ export const RULE_CONFIG_DESC_BODY = 'configuration object; shape depends on `type` and is ' +
42
+ 'validated server-side (unknown keys are rejected with a 422). Built-in ' +
43
+ 'toggles: matchAmount, matchCurrency, matchDate, matchReference ' +
44
+ '(booleans). EXACT example: {"matchAmount":true,"matchCurrency":true,' +
45
+ '"matchReference":true,"matchDate":false}. TOLERANCE adds absTolerance ' +
46
+ 'and percentTolerance (decimal STRINGS, e.g. "0.05") and dateWindowDays ' +
47
+ '(int): {"absTolerance":"0.05","percentTolerance":"0","dateWindowDays":4}. ' +
48
+ 'TOLERANCE also accepts fxVarianceBand (decimal string fraction, e.g. ' +
49
+ '"0.0001" for 1bp) for cross-currency residual matches. DATE_LAG uses ' +
50
+ 'minDays, maxDays (ints) and inclusive (bool): {"minDays":0,"maxDays":4,' +
51
+ '"inclusive":true}. FUZZY uses minSimilarity (a 0..1 decimal threshold, ' +
52
+ 'default 0.80) plus toggles: {"minSimilarity":0.85,"matchAmount":true,' +
53
+ '"matchCurrency":true}. EXACT/TOLERANCE also accept matchFields ' +
54
+ '(composite-key array: [{ field, mode: required|conditional|off, ' +
55
+ 'tolerance? }]). Optional across types: timezone (IANA zone), calendar ' +
56
+ '(US_FED|BR_ANBIMA), businessDayMode (DATE_LAG only), and signMode ' +
57
+ '(same|opposite|ignore).';
35
58
  /**
36
59
  * Run one assembled request through the relay: fail-closed auth, dispatch, and
37
60
  * RFC 9457 → tool-error mapping — identical to the generic invoke's dispatch so
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/tools/match-rule/shared.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,6DAA6D;AAC7D,mEAAmE;AACnE,+EAA+E;AAC/E,+EAA+E;AAC/E,2EAA2E;AAC3E,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,kEAAkE;AAClE,6EAA6E;AAC7E,yBAAyB;AACzB,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,2EAA2E;AAC3E,0EAA0E;AAC1E,2EAA2E;AAC3E,yCAAyC;AAIzC,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GAErB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEjE,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,OAAO,gBAAgB,kBAAkB,CAAC,SAAS,CAAC,QAAQ,CAAA;AAC9D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,MAAc;IAC/D,OAAO,GAAG,gBAAgB,CAAC,SAAS,CAAC,IAAI,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAA;AACvE,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,uBAAuB,CAAC,SAAiB;IACvD,OAAO,GAAG,gBAAgB,CAAC,SAAS,CAAC,UAAU,CAAA;AACjD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,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,oCAAoC;QACpC,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"}
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/tools/match-rule/shared.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,6DAA6D;AAC7D,mEAAmE;AACnE,+EAA+E;AAC/E,+EAA+E;AAC/E,2EAA2E;AAC3E,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,kEAAkE;AAClE,6EAA6E;AAC7E,yBAAyB;AACzB,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,2EAA2E;AAC3E,0EAA0E;AAC1E,2EAA2E;AAC3E,yCAAyC;AAIzC,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GAErB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEjE,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,OAAO,gBAAgB,kBAAkB,CAAC,SAAS,CAAC,QAAQ,CAAA;AAC9D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,MAAc;IAC/D,OAAO,GAAG,gBAAgB,CAAC,SAAS,CAAC,IAAI,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAA;AACvE,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,uBAAuB,CAAC,SAAiB;IACvD,OAAO,GAAG,gBAAgB,CAAC,SAAS,CAAC,UAAU,CAAA;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAChC,uDAAuD;IACvD,yEAAyE;IACzE,iEAAiE;IACjE,sEAAsE;IACtE,wEAAwE;IACxE,yEAAyE;IACzE,4EAA4E;IAC5E,uEAAuE;IACvE,uEAAuE;IACvE,yEAAyE;IACzE,yEAAyE;IACzE,uEAAuE;IACvE,iEAAiE;IACjE,kEAAkE;IAClE,wEAAwE;IACxE,oEAAoE;IACpE,yBAAyB,CAAA;AAE3B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,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,oCAAoC;QACpC,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"}
@@ -6,7 +6,7 @@
6
6
  // (matcher scopes to the relayed token's tenant via the context).
7
7
  import { z } from 'zod';
8
8
  import { MATCH_RULE_TYPES } from './create.js';
9
- import { contextRulePath, dispatchMatchRule } from './shared.js';
9
+ import { contextRulePath, dispatchMatchRule, RULE_CONFIG_DESC_BODY, } from './shared.js';
10
10
  /** Zod raw shape for `match_rule_update` input — ids (path) + optional fields. */
11
11
  export const updateMatchRuleInputShape = {
12
12
  contextId: z
@@ -25,13 +25,13 @@ export const updateMatchRuleInputShape = {
25
25
  type: z
26
26
  .enum(MATCH_RULE_TYPES)
27
27
  .optional()
28
- .describe('New matching strategy (EXACT|TOLERANCE|DATE_LAG). Changing type usually ' +
29
- 'requires a matching `config` change.'),
28
+ .describe('New matching strategy (EXACT|TOLERANCE|DATE_LAG|FUZZY). FUZZY matches on ' +
29
+ 'similarity-scored references and always proposes for review (never ' +
30
+ 'auto-confirms). Changing type usually requires a matching `config` change.'),
30
31
  config: z
31
32
  .record(z.string(), z.unknown())
32
33
  .optional()
33
- .describe('New rule-specific configuration object (matched fields and tolerance/lag ' +
34
- 'parameters). Shape depends on `type`; validated server-side.'),
34
+ .describe('New rule-specific ' + RULE_CONFIG_DESC_BODY),
35
35
  };
36
36
  /**
37
37
  * Build the PATCH body from the input minus the path-only `contextId`/`ruleId`.
@@ -53,7 +53,7 @@ export function registerMatchRuleUpdateTool(server) {
53
53
  server.registerTool('match_rule_update', {
54
54
  description: 'Update a match rule by id (UUID) within a context (UUID). All fields are ' +
55
55
  'optional (PATCH): priority (1–1000, unique), type ' +
56
- '(EXACT|TOLERANCE|DATE_LAG), config (type-specific, validated server-side). ' +
56
+ '(EXACT|TOLERANCE|DATE_LAG|FUZZY), config (type-specific, validated server-side). ' +
57
57
  'No tenant field is accepted. Returns the updated rule, or a structured ' +
58
58
  'RFC 9457 error.',
59
59
  inputSchema: updateMatchRuleInputShape,
@@ -1 +1 @@
1
- {"version":3,"file":"update.js","sourceRoot":"","sources":["../../../src/tools/match-rule/update.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,yEAAyE;AACzE,kEAAkE;AAElE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEhE,kFAAkF;AAClF,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,+EAA+E,CAChF;IACH,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IACzE,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,IAAI,CAAC;SACT,QAAQ,EAAE;SACV,QAAQ,CACP,wEAAwE;QACtE,qBAAqB,CACxB;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,gBAAgB,CAAC;SACtB,QAAQ,EAAE;SACV,QAAQ,CACP,0EAA0E;QACxE,sCAAsC,CACzC;IACH,MAAM,EAAE,CAAC;SACN,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/B,QAAQ,EAAE;SACV,QAAQ,CACP,2EAA2E;QACzE,8DAA8D,CACjE;CACJ,CAAA;AAOD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,IAA0B;IAE1B,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAA;IAChE,KAAK,UAAU,CAAA;IACf,KAAK,OAAO,CAAA;IACZ,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAiB;IAC3D,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,WAAW,EACT,2EAA2E;YAC3E,oDAAoD;YACpD,6EAA6E;YAC7E,yEAAyE;YACzE,iBAAiB;QACnB,WAAW,EAAE,yBAAyB;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,OAAO,iBAAiB,CACtB;YACE,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC;YAClD,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC;SAC5B,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../../../src/tools/match-rule/update.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,yEAAyE;AACzE,kEAAkE;AAElE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AAEpB,kFAAkF;AAClF,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,+EAA+E,CAChF;IACH,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IACzE,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,IAAI,CAAC;SACT,QAAQ,EAAE;SACV,QAAQ,CACP,wEAAwE;QACtE,qBAAqB,CACxB;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,gBAAgB,CAAC;SACtB,QAAQ,EAAE;SACV,QAAQ,CACP,2EAA2E;QACzE,qEAAqE;QACrE,4EAA4E,CAC/E;IACH,MAAM,EAAE,CAAC;SACN,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/B,QAAQ,EAAE;SACV,QAAQ,CAAC,oBAAoB,GAAG,qBAAqB,CAAC;CAC1D,CAAA;AAOD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,IAA0B;IAE1B,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAA;IAChE,KAAK,UAAU,CAAA;IACf,KAAK,OAAO,CAAA;IACZ,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAiB;IAC3D,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,WAAW,EACT,2EAA2E;YAC3E,oDAAoD;YACpD,mFAAmF;YACnF,yEAAyE;YACzE,iBAAiB;QACnB,WAAW,EAAE,yBAAyB;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,OAAO,iBAAiB,CACtB;YACE,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC;YAClD,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC;SAC5B,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
@@ -1,15 +1,24 @@
1
1
  // `match_run_get` — curated GET /v1/matching/runs/{runId}?contextId=<id>.
2
2
  //
3
3
  // Reads one match run's detail by id: status, timing, aggregate stats, and the
4
- // per-rule matched-group counts (ruleStats). The matcher payload is returned
5
- // verbatim — this is how an operator polls a run started by `match_run_start`.
4
+ // per-rule matched-group counts (ruleStats). This is how an operator polls a
5
+ // run started by `match_run_start`. In-flight states (keep polling): QUEUED,
6
+ // PROCESSING, FINALIZING. Terminal states (stop): COMPLETED, FAILED.
7
+ //
8
+ // `stats` is NOT returned fully verbatim: matcher's per-rule keys
9
+ // (`rule_matched:<uuid>`, `rule_meta:<uuid>:<type>`) are stripped before the
10
+ // response reaches the model (see `stripRuleStatKeys` in shared.ts) — they are
11
+ // UUID-keyed noise that duplicates the clean `ruleStats[]` array already in the
12
+ // same payload. Every static stats key (matches, unmatched_left, etc.) and
13
+ // `ruleStats[]` itself pass through unchanged.
6
14
  //
7
15
  // Path/query split (per the spec): `runId` is the PATH param, but `contextId` is
8
16
  // a REQUIRED QUERY param (not part of the path), so matcher can scope the run to
9
17
  // the right context. `contextId` is a routing param matcher validates ownership
10
18
  // of, NOT a tenant field; no tenant field is accepted.
11
19
  import { z } from 'zod';
12
- import { dispatchMatching, runPath } from './shared.js';
20
+ import { requireMatcherClient } from '../../auth/request-token.js';
21
+ import { dispatchMatching, runPath, stripRuleStatKeys } from './shared.js';
13
22
  /** Zod raw shape for `match_run_get` input — runId (path) + contextId (query, required). */
14
23
  export const getMatchRunInputShape = {
15
24
  runId: z
@@ -22,27 +31,41 @@ export const getMatchRunInputShape = {
22
31
  .describe('The reconciliation context id (UUID) the run belongs to. Sent as a query ' +
23
32
  'param (required); routing param scoped to your token.'),
24
33
  };
34
+ /**
35
+ * Fetch the run and return it with `stats` cleaned of the dynamic per-rule
36
+ * keys (see `stripRuleStatKeys`). Delegates to the shared `dispatchMatching`
37
+ * relay (fail-closed auth, RFC 9457 → tool-error mapping) and passes a
38
+ * success-body transform — the one matching read that post-processes the body.
39
+ */
40
+ function fetchAndCleanRun(runId, contextId, extra, clientFor = requireMatcherClient) {
41
+ return dispatchMatching({ method: 'GET', path: runPath(runId), query: { contextId } }, extra, clientFor, (run) => run !== undefined && run !== null
42
+ ? {
43
+ ...run,
44
+ stats: stripRuleStatKeys(run.stats),
45
+ }
46
+ : run);
47
+ }
25
48
  /**
26
49
  * Register `match_run_get`. Substitutes runId into the path and sends contextId
27
- * as a query param; dispatches a GET through the shared fail-closed relay. The
28
- * run payload (status/stats/ruleStats) is returned verbatim; a matcher error
29
- * (e.g. 404) surfaces via the shared toToolError.
50
+ * as a query param; fetches through the fail-closed relay and strips the
51
+ * dynamic per-rule `stats` keys (see module doc) before returning. A matcher
52
+ * error (e.g. 404) surfaces via the shared toToolError.
30
53
  */
31
54
  export function registerMatchRunGetTool(server) {
32
55
  server.registerTool('match_run_get', {
33
56
  description: 'Fetch a single match run by id, including status, timing, aggregate ' +
34
57
  'statistics, and per-rule matched-group counts. Use this to poll a run ' +
35
- 'started by match_run_start. Requires runId and contextId (contextId is ' +
36
- 'sent as a query param). Both are routing params scoped to your token; no ' +
37
- 'tenant field is accepted. Returns the run detail, or a structured RFC ' +
38
- '9457 error (e.g. 404).',
58
+ 'started by match_run_start. status is one of: QUEUED, PROCESSING, ' +
59
+ 'FINALIZING (all IN-FLIGHT still running, keep polling; FINALIZING is a ' +
60
+ 'transient near-done state, not an error) or COMPLETED, FAILED (TERMINAL ' +
61
+ ' stop polling). Requires runId and contextId (contextId is sent as a ' +
62
+ 'query param). Both are routing params scoped to your token; no tenant ' +
63
+ 'field is accepted. Returns the run detail (stats excludes the raw ' +
64
+ 'rule_matched:/rule_meta: keys — see ruleStats[] for the same data, ' +
65
+ 'readable), or a structured RFC 9457 error (e.g. 404).',
39
66
  inputSchema: getMatchRunInputShape,
40
67
  }, async (args, extra) => {
41
- return dispatchMatching({
42
- method: 'GET',
43
- path: runPath(args.runId),
44
- query: { contextId: args.contextId },
45
- }, extra);
68
+ return fetchAndCleanRun(args.runId, args.contextId, extra);
46
69
  });
47
70
  }
48
71
  //# sourceMappingURL=get.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"get.js","sourceRoot":"","sources":["../../../src/tools/matching/get.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,+EAA+E;AAC/E,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,gFAAgF;AAChF,uDAAuD;AAEvD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAEvD,4FAA4F;AAC5F,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,mCAAmC,CAAC;IAChD,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,2EAA2E;QACzE,uDAAuD,CAC1D;CACJ,CAAA;AAOD;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,WAAW,EACT,sEAAsE;YACtE,wEAAwE;YACxE,yEAAyE;YACzE,2EAA2E;YAC3E,wEAAwE;YACxE,wBAAwB;QAC1B,WAAW,EAAE,qBAAqB;KACnC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,OAAO,gBAAgB,CACrB;YACE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;YACzB,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE;SACrC,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"get.js","sourceRoot":"","sources":["../../../src/tools/matching/get.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,6EAA6E;AAC7E,qEAAqE;AACrE,EAAE;AACF,kEAAkE;AAClE,6EAA6E;AAC7E,+EAA+E;AAC/E,gFAAgF;AAChF,2EAA2E;AAC3E,+CAA+C;AAC/C,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,gFAAgF;AAChF,uDAAuD;AAEvD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAMvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAElE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAE1E,4FAA4F;AAC5F,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,mCAAmC,CAAC;IAChD,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,2EAA2E;QACzE,uDAAuD,CAC1D;CACJ,CAAA;AAOD;;;;;GAKG;AACH,SAAS,gBAAgB,CACvB,KAAa,EACb,SAAiB,EACjB,KAAmC,EACnC,YAAyD,oBAAoB;IAE7E,OAAO,gBAAgB,CACrB,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAC7D,KAAK,EACL,SAAS,EACT,CAAC,GAAG,EAAE,EAAE,CACN,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI;QAC/B,CAAC,CAAC;YACE,GAAI,GAA+B;YACnC,KAAK,EAAE,iBAAiB,CAAE,GAA+B,CAAC,KAAgC,CAAC;SAC5F;QACH,CAAC,CAAC,GAAG,CACV,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,WAAW,EACT,sEAAsE;YACtE,wEAAwE;YACxE,oEAAoE;YACpE,2EAA2E;YAC3E,0EAA0E;YAC1E,wEAAwE;YACxE,wEAAwE;YACxE,oEAAoE;YACpE,qEAAqE;YACrE,uDAAuD;QACzD,WAAW,EAAE,qBAAqB;KACnC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,OAAO,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IAC5D,CAAC,CACF,CAAA;AACH,CAAC"}
@@ -38,6 +38,34 @@ export function runPath(runId) {
38
38
  export function runGroupsPath(runId) {
39
39
  return `/v1/matching/runs/${encodeURIComponent(runId)}/groups`;
40
40
  }
41
+ /**
42
+ * Per-rule stat-key prefixes matcher writes into a run's `stats` map
43
+ * (entities.StatKeyRuleMatchedPrefix / StatKeyRuleMetaPrefix on the backend):
44
+ * `rule_matched:<ruleID>` and `rule_meta:<ruleID>:<TYPE>`. UUID-keyed and
45
+ * unreadable on the wire — and pure duplication, since the same payload's
46
+ * `ruleStats[]` array (`{ruleId, priority, type, matched}`) is derived from
47
+ * these exact keys server-side (RuleStatsFromStats).
48
+ */
49
+ const RULE_STAT_KEY_PREFIXES = ['rule_matched:', 'rule_meta:'];
50
+ /**
51
+ * Strip the dynamic per-rule `rule_matched:<uuid>` / `rule_meta:<uuid>:<type>`
52
+ * keys from a run's `stats` map before it reaches the model. Every other key
53
+ * (the static aggregates: matches, unmatched_left, auto_matched_left, etc.) is
54
+ * passed through untouched. The per-rule data is NOT lost — it remains
55
+ * available, readable, in the response's `ruleStats[]` array.
56
+ */
57
+ export function stripRuleStatKeys(stats) {
58
+ if (stats === undefined || stats === null) {
59
+ return stats;
60
+ }
61
+ const cleaned = {};
62
+ for (const [key, value] of Object.entries(stats)) {
63
+ if (!RULE_STAT_KEY_PREFIXES.some((prefix) => key.startsWith(prefix))) {
64
+ cleaned[key] = value;
65
+ }
66
+ }
67
+ return cleaned;
68
+ }
41
69
  /**
42
70
  * Run one assembled request through the relay: fail-closed auth, dispatch, and
43
71
  * RFC 9457 → tool-error mapping — identical to the generic invoke's dispatch so
@@ -46,7 +74,7 @@ export function runGroupsPath(runId) {
46
74
  * `clientFor` is injectable for tests; production callers pass nothing and get
47
75
  * the request-scoped, fail-closed `requireMatcherClient`.
48
76
  */
49
- export async function dispatchMatching(request, extra, clientFor = requireMatcherClient) {
77
+ export async function dispatchMatching(request, extra, clientFor = requireMatcherClient, transform) {
50
78
  let client;
51
79
  try {
52
80
  // Fail-closed on a missing / empty / non-Bearer credential.
@@ -61,8 +89,10 @@ export async function dispatchMatching(request, extra, clientFor = requireMatche
61
89
  try {
62
90
  const result = await client.request(request);
63
91
  // Success → matcher's run payload returned as-is (202 { runId, status },
64
- // run detail status/stats/ruleStats, or the group pagination envelope).
65
- return { content: [{ type: 'text', text: JSON.stringify(result ?? null) }] };
92
+ // run detail status/stats/ruleStats, or the group pagination envelope),
93
+ // unless a caller-supplied transform post-processes the body first.
94
+ const output = transform ? transform(result) : result;
95
+ return { content: [{ type: 'text', text: JSON.stringify(output ?? null) }] };
66
96
  }
67
97
  catch (err) {
68
98
  // RFC 9457 problem → the SHARED structured tool error (status/title/detail/
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/tools/matching/shared.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,8EAA8E;AAC9E,4EAA4E;AAC5E,wEAAwE;AACxE,gFAAgF;AAChF,iFAAiF;AACjF,+EAA+E;AAC/E,wEAAwE;AACxE,EAAE;AACF,gFAAgF;AAChF,kFAAkF;AAClF,6EAA6E;AAC7E,kFAAkF;AAClF,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,2EAA2E;AAC3E,iFAAiF;AACjF,yEAAyE;AAIzE,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GAErB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEjE,oEAAoE;AACpE,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,OAAO,yBAAyB,kBAAkB,CAAC,SAAS,CAAC,MAAM,CAAA;AACrE,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,OAAO,yBAAyB,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAA;AACtE,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,qBAAqB,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAA;AACzD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,qBAAqB,kBAAkB,CAAC,KAAK,CAAC,SAAS,CAAA;AAChE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,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,yEAAyE;QACzE,wEAAwE;QACxE,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"}
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/tools/matching/shared.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,12 @@
1
1
  // `match_run_start` — curated POST /v1/matching/contexts/{contextId}/run.
2
2
  //
3
- // Triggers a matching run for a reconciliation context. This is an async action:
4
- // matcher responds 202 with { runId, status } and processes in the background —
5
- // the operator polls `match_run_get` with the returned runId to follow progress.
3
+ // Triggers a matching run for a reconciliation context. Both submit paths return
4
+ // HTTP 202 with { runId, status }; the `status` distinguishes them:
5
+ // - sync (async omitted/false): the run executes in-request and the 202 carries
6
+ // the TERMINAL run — status COMPLETED or FAILED. No polling needed.
7
+ // - async (async:true): the run is created QUEUED and executed by a background
8
+ // worker; the 202 carries status QUEUED. The operator polls `match_run_get`
9
+ // with the returned runId to follow progress.
6
10
  // This tool does NOT auto-poll; it returns the 202 acceptance body verbatim.
7
11
  //
8
12
  // `contextId` is a routing path param (matcher validates it belongs to the
@@ -27,28 +31,50 @@ export const startMatchRunInputShape = {
27
31
  .enum(RUN_MODES)
28
32
  .describe('Execution mode. DRY_RUN tests the rules without committing any matches; ' +
29
33
  'COMMIT executes the rules and persists the resulting matches.'),
34
+ async: z
35
+ .boolean()
36
+ .optional()
37
+ .describe('Submit path (optional, default false). Both return 202 with ' +
38
+ '{ runId, status }; the status distinguishes them. Omit (or false) to ' +
39
+ 'run in-request: the 202 carries the TERMINAL run (status COMPLETED or ' +
40
+ 'FAILED). Set true to enqueue: the run is created QUEUED and a background ' +
41
+ 'worker executes it — the 202 status is QUEUED and you poll match_run_get ' +
42
+ 'with the runId until it reaches COMPLETED/FAILED.'),
30
43
  };
31
44
  /**
32
- * Register `match_run_start`. Substitutes contextId into the path and sends only
33
- * `{ mode }` as the body (per RunMatchRequest, which forbids extra properties).
34
- * Dispatches through the shared fail-closed relay; a matcher error surfaces via
35
- * the shared toToolError.
45
+ * Register `match_run_start`. Substitutes contextId into the path and sends
46
+ * `{ mode }` as the body, adding `async` ONLY when the caller set it (per
47
+ * RunMatchRequest, which accepts `async` but forbids unknown properties so the
48
+ * key is omitted when undefined to keep the body minimal). Dispatches through the
49
+ * shared fail-closed relay; a matcher error surfaces via the shared toToolError.
36
50
  */
37
51
  export function registerMatchRunStartTool(server) {
38
52
  server.registerTool('match_run_start', {
39
- description: 'Trigger a matching run for a reconciliation context. Async: returns 202 ' +
40
- 'with { runId, status } and processes in the background — poll ' +
41
- 'match_run_get with the runId to follow progress (this tool does not ' +
53
+ description: 'Trigger a matching run for a reconciliation context. Both submit paths ' +
54
+ 'return 202 with { runId, status }; the status distinguishes them. Omit ' +
55
+ 'async (or set false) to run in-request: the 202 carries the TERMINAL run ' +
56
+ '(status COMPLETED or FAILED). Set async:true to enqueue: the run is ' +
57
+ 'created QUEUED and a background worker executes it (202 status QUEUED) — ' +
58
+ 'poll match_run_get with the runId to follow progress (this tool does not ' +
42
59
  'auto-poll). mode is required: DRY_RUN tests rules without committing, ' +
43
60
  'COMMIT persists the matches. contextId is a routing param scoped to your ' +
44
61
  'token (no tenant field is accepted). Returns the acceptance body, or a ' +
45
- 'structured RFC 9457 error.',
62
+ 'structured RFC 9457 error (e.g. 422 when the rule set is not ' +
63
+ 'stream-bounded and cannot run at unbounded scale).',
46
64
  inputSchema: startMatchRunInputShape,
47
65
  }, async (args, extra) => {
66
+ // Omit `async` unless the caller set it — match the { mode } body style and
67
+ // keep the body minimal for RunMatchRequest's no-unknown-props contract.
68
+ const body = {
69
+ mode: args.mode,
70
+ };
71
+ if (args.async !== undefined) {
72
+ body.async = args.async;
73
+ }
48
74
  return dispatchMatching({
49
75
  method: 'POST',
50
76
  path: contextRunPath(args.contextId),
51
- body: { mode: args.mode },
77
+ body,
52
78
  }, extra);
53
79
  });
54
80
  }
@@ -1 +1 @@
1
- {"version":3,"file":"start.js","sourceRoot":"","sources":["../../../src/tools/matching/start.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,iFAAiF;AACjF,gFAAgF;AAChF,iFAAiF;AACjF,6EAA6E;AAC7E,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAE5E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE9D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAU,CAAA;AAEvD,oFAAoF;AACpF,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,2EAA2E;QACzE,8CAA8C,CACjD;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,SAAS,CAAC;SACf,QAAQ,CACP,0EAA0E;QACxE,+DAA+D,CAClE;CACJ,CAAA;AAOD;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAiB;IACzD,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,WAAW,EACT,0EAA0E;YAC1E,gEAAgE;YAChE,sEAAsE;YACtE,wEAAwE;YACxE,2EAA2E;YAC3E,yEAAyE;YACzE,4BAA4B;QAC9B,WAAW,EAAE,uBAAuB;KACrC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,OAAO,gBAAgB,CACrB;YACE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC;YACpC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;SAC1B,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"start.js","sourceRoot":"","sources":["../../../src/tools/matching/start.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,iFAAiF;AACjF,oEAAoE;AACpE,kFAAkF;AAClF,wEAAwE;AACxE,iFAAiF;AACjF,gFAAgF;AAChF,kDAAkD;AAClD,6EAA6E;AAC7E,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAE5E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE9D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAU,CAAA;AAEvD,oFAAoF;AACpF,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,2EAA2E;QACzE,8CAA8C,CACjD;IACH,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,SAAS,CAAC;SACf,QAAQ,CACP,0EAA0E;QACxE,+DAA+D,CAClE;IACH,KAAK,EAAE,CAAC;SACL,OAAO,EAAE;SACT,QAAQ,EAAE;SACV,QAAQ,CACP,8DAA8D;QAC5D,uEAAuE;QACvE,wEAAwE;QACxE,2EAA2E;QAC3E,2EAA2E;QAC3E,mDAAmD,CACtD;CACJ,CAAA;AAOD;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAiB;IACzD,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,WAAW,EACT,yEAAyE;YACzE,yEAAyE;YACzE,2EAA2E;YAC3E,sEAAsE;YACtE,2EAA2E;YAC3E,2EAA2E;YAC3E,wEAAwE;YACxE,2EAA2E;YAC3E,yEAAyE;YACzE,+DAA+D;YAC/D,oDAAoD;QACtD,WAAW,EAAE,uBAAuB;KACrC,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,4EAA4E;QAC5E,yEAAyE;QACzE,MAAM,IAAI,GAA0D;YAClE,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAA;QACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;QACzB,CAAC;QACD,OAAO,gBAAgB,CACrB;YACE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC;YACpC,IAAI;SACL,EACD,KAAK,CACN,CAAA;IACH,CAAC,CACF,CAAA;AACH,CAAC"}
@@ -30,8 +30,9 @@ export function registerReportCountExceptionsTool(server) {
30
30
  'between a sync download (<1000 rows) and an async export. The date ' +
31
31
  "filter applies to each exception's created_at. Pass contextId (UUID " +
32
32
  'path param — not a tenant field), and optionally date_from/date_to ' +
33
- '(YYYY-MM-DD, window ≤ 90 days) and source_id. No tenant field is ' +
34
- 'accepted.',
33
+ '(YYYY-MM-DD, window ≤ 90 days; if omitted, defaults to the last ~30 ' +
34
+ 'days and SILENTLY EXCLUDES older exceptions — pass an explicit range ' +
35
+ 'for a full picture) and source_id. No tenant field is accepted.',
35
36
  inputSchema: countExceptionsInputShape,
36
37
  }, async (args, extra) => {
37
38
  const query = buildReportFilterQuery(args);