@lerianstudio/matcher-mcp 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.js +0 -33
- package/dist/config.js.map +1 -1
- package/dist/spec/enum-policy.js +83 -0
- package/dist/spec/enum-policy.js.map +1 -0
- package/dist/spec/openapi.yaml +27 -20
- package/dist/spec/schema-parity.js +393 -0
- package/dist/spec/schema-parity.js.map +1 -0
- package/dist/tools/context/create.js +18 -3
- package/dist/tools/context/create.js.map +1 -1
- package/dist/tools/context/update.js +20 -5
- package/dist/tools/context/update.js.map +1 -1
- package/dist/tools/exception/bulk-dispatch.js +11 -13
- package/dist/tools/exception/bulk-dispatch.js.map +1 -1
- package/dist/tools/exception/list.js +12 -3
- package/dist/tools/exception/list.js.map +1 -1
- package/dist/tools/fee-rule/create.js +36 -13
- package/dist/tools/fee-rule/create.js.map +1 -1
- package/dist/tools/generic/describe-operation.js +3 -2
- package/dist/tools/generic/describe-operation.js.map +1 -1
- package/dist/tools/ingestion/index.js +13 -8
- package/dist/tools/ingestion/index.js.map +1 -1
- package/dist/tools/ingestion/upload-begin.js +116 -0
- package/dist/tools/ingestion/upload-begin.js.map +1 -0
- package/dist/tools/ingestion/upload.js +47 -97
- package/dist/tools/ingestion/upload.js.map +1 -1
- package/dist/tools/source/create.js +6 -3
- package/dist/tools/source/create.js.map +1 -1
- package/package.json +1 -1
- package/dist/tools/ingestion/fetch-source.js +0 -105
- package/dist/tools/ingestion/fetch-source.js.map +0 -1
|
@@ -8,20 +8,35 @@
|
|
|
8
8
|
// validates the context belongs to the relayed token's tenant), NOT a tenant
|
|
9
9
|
// field. The body is the input minus `contextId`.
|
|
10
10
|
//
|
|
11
|
-
// The predicate sub-object is modeled explicitly (the operator
|
|
12
|
-
//
|
|
13
|
-
// shape; `value`/`values` are optional
|
|
11
|
+
// The predicate sub-object is modeled explicitly (the operator algebra is a
|
|
12
|
+
// closed, known set of nine: EQUALS/IN/EXISTS plus GT/GTE/LT/LTE/BETWEEN/NEQ),
|
|
13
|
+
// so an operator-driving LLM gets the exact shape; `value`/`values` are optional
|
|
14
|
+
// because operators like EXISTS consume neither.
|
|
14
15
|
import { z } from 'zod';
|
|
15
16
|
import { contextFeeRulesPath, dispatchFeeRule } from './shared.js';
|
|
16
17
|
/** Side a fee rule applies to. LEFT/RIGHT target one side; ANY matches either. */
|
|
17
18
|
export const FEE_RULE_SIDES = ['LEFT', 'RIGHT', 'ANY'];
|
|
18
|
-
/**
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Predicate comparison operators matcher supports for fee-rule predicates.
|
|
21
|
+
* A closed, stable algebra — kept in spec order (FieldPredicateRequest.operator).
|
|
22
|
+
*/
|
|
23
|
+
export const PREDICATE_OPERATORS = [
|
|
24
|
+
'EQUALS',
|
|
25
|
+
'IN',
|
|
26
|
+
'EXISTS',
|
|
27
|
+
'GT',
|
|
28
|
+
'GTE',
|
|
29
|
+
'LT',
|
|
30
|
+
'LTE',
|
|
31
|
+
'BETWEEN',
|
|
32
|
+
'NEQ',
|
|
33
|
+
];
|
|
20
34
|
/**
|
|
21
35
|
* One field predicate. ANDed with the rule's other predicates. `field` and
|
|
22
|
-
* `operator` are required
|
|
23
|
-
*
|
|
24
|
-
* validator of the
|
|
36
|
+
* `operator` are required. `value` is consumed by EQUALS/NEQ and the numeric
|
|
37
|
+
* comparisons GT/GTE/LT/LTE; `values` by IN and BETWEEN; EXISTS needs neither —
|
|
38
|
+
* both are optional here and matcher is the authoritative validator of the
|
|
39
|
+
* operator/value pairing.
|
|
25
40
|
*/
|
|
26
41
|
export const feeRulePredicateShape = z
|
|
27
42
|
.object({
|
|
@@ -30,16 +45,23 @@ export const feeRulePredicateShape = z
|
|
|
30
45
|
.describe('Transaction metadata field the predicate tests, e.g. "institution".'),
|
|
31
46
|
operator: z
|
|
32
47
|
.enum(PREDICATE_OPERATORS)
|
|
33
|
-
.describe('Comparison operator. EQUALS matches a single `value`;
|
|
34
|
-
'
|
|
48
|
+
.describe('Comparison operator. EQUALS matches a single `value`; NEQ is its ' +
|
|
49
|
+
'negation (numeric when both sides parse as decimals, else ' +
|
|
50
|
+
'case-insensitive string) and consumes `value`. IN matches any of ' +
|
|
51
|
+
'`values`. EXISTS asserts the field is present (needs neither `value` ' +
|
|
52
|
+
'nor `values`). GT/GTE/LT/LTE compare the field numerically against a ' +
|
|
53
|
+
'single decimal `value`. BETWEEN tests inclusive numeric membership in ' +
|
|
54
|
+
'`values`=[lo, hi] (lo<=hi).'),
|
|
35
55
|
value: z
|
|
36
56
|
.string()
|
|
37
57
|
.optional()
|
|
38
|
-
.describe('
|
|
58
|
+
.describe('Single comparison value. Required by EQUALS and NEQ, and by the ' +
|
|
59
|
+
'numeric comparisons GT/GTE/LT/LTE.'),
|
|
39
60
|
values: z
|
|
40
61
|
.array(z.string())
|
|
41
62
|
.optional()
|
|
42
|
-
.describe('
|
|
63
|
+
.describe('List of comparison values. Required by IN (membership) and BETWEEN ' +
|
|
64
|
+
'(exactly [lo, hi], inclusive).'),
|
|
43
65
|
})
|
|
44
66
|
.describe('A single field predicate a transaction must satisfy for the rule to apply.');
|
|
45
67
|
/** Zod raw shape for `fee_rule_create` input — mirrors CreateFeeRuleRequest. */
|
|
@@ -91,7 +113,8 @@ export function registerFeeRuleCreateTool(server) {
|
|
|
91
113
|
'to transactions whose metadata satisfies the predicates. Provide ' +
|
|
92
114
|
'contextId (path), side (LEFT|RIGHT|ANY), feeScheduleId (UUID), name ' +
|
|
93
115
|
'(1–100), priority (unique within the context across all sides), and ' +
|
|
94
|
-
'predicates[] (each { field, operator
|
|
116
|
+
'predicates[] (each { field, operator ' +
|
|
117
|
+
'EQUALS|IN|EXISTS|GT|GTE|LT|LTE|BETWEEN|NEQ, value?, values? }). ' +
|
|
95
118
|
'The rule is scoped to your token\'s tenant (no tenant field is accepted). ' +
|
|
96
119
|
'Returns the created rule, or a structured RFC 9457 error.',
|
|
97
120
|
inputSchema: createFeeRuleInputShape,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/tools/fee-rule/create.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,EAAE;AACF,6EAA6E;AAC7E,iFAAiF;AACjF,+EAA+E;AAC/E,EAAE;AACF,kFAAkF;AAClF,6EAA6E;AAC7E,kDAAkD;AAClD,EAAE;AACF,iFAAiF;AACjF,
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/tools/fee-rule/create.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,8 +32,9 @@ const MAX_SUGGESTIONS = 5;
|
|
|
32
32
|
const NON_JSON_BODY_GUIDANCE = {
|
|
33
33
|
uploadFile: 'uploadFile takes a multipart/form-data body, which this JSON relay ' +
|
|
34
34
|
'cannot construct. Use the curated ingestion_upload tool instead — it ' +
|
|
35
|
-
'accepts the file as contentBase64 (small files)
|
|
36
|
-
'
|
|
35
|
+
'accepts the file as contentBase64 (small files) and rebuilds the ' +
|
|
36
|
+
'multipart request for you. For a real LOCAL file use ingestion_upload_begin ' +
|
|
37
|
+
'to broker a direct client→matcher upload.',
|
|
37
38
|
extractDocument: 'extractDocument takes the document as a raw application/pdf body, not ' +
|
|
38
39
|
'JSON, which this relay cannot construct. There is no MCP path for this ' +
|
|
39
40
|
'operation today — it needs a direct multipart/document upload to the ' +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe-operation.js","sourceRoot":"","sources":["../../../src/tools/generic/describe-operation.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,EAAE;AACF,sEAAsE;AACtE,6EAA6E;AAC7E,+EAA+E;AAC/E,gFAAgF;AAChF,eAAe;AACf,EAAE;AACF,gFAAgF;AAChF,2BAA2B;AAC3B,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,oDAAoD;AACpD,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAC1E,mDAAmD;AAEnD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiCvB,6DAA6D;AAC7D,MAAM,eAAe,GAAG,CAAC,CAAA;AAEzB;;;;;;GAMG;AACH,MAAM,sBAAsB,GAAqC;IAC/D,UAAU,EACR,qEAAqE;QACrE,uEAAuE;QACvE,
|
|
1
|
+
{"version":3,"file":"describe-operation.js","sourceRoot":"","sources":["../../../src/tools/generic/describe-operation.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
// Curated ingestion vertical:
|
|
1
|
+
// Curated ingestion vertical: eight typed tools over the matcher import
|
|
2
2
|
// lifecycle of a reconciliation context — list jobs, get a job, list a job's
|
|
3
3
|
// per-row errors, list a job's transactions, search transactions, ignore a
|
|
4
|
-
// transaction,
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
4
|
+
// transaction, upload a file, and broker a direct local-file upload. Every
|
|
5
|
+
// backend-dispatching op is fully context-nested under
|
|
6
|
+
// /v1/imports/contexts/{contextId}/... Each dispatching tool shares the
|
|
7
|
+
// fail-closed relay (shared.ts) and the RFC 9457 → tool-error mapping
|
|
8
|
+
// (toToolError) with the generic surface, so curated and generic map matcher
|
|
9
|
+
// errors identically. ingestion_upload_begin is MCP-native (no backend
|
|
10
|
+
// dispatch — it computes an upload target locally). No tool carries a tenant
|
|
11
|
+
// field; contextId is a routing path param matcher validates against the
|
|
12
|
+
// relayed token's tenant.
|
|
10
13
|
//
|
|
11
14
|
// NOTE: uploadFile is now curated as ingestion_upload (base64-in-JSON, relay
|
|
12
15
|
// rebuilds the multipart request — see upload.ts). extractDocument stays
|
|
@@ -17,8 +20,9 @@ import { registerIngestionJobTransactionsListTool } from './job-transactions-lis
|
|
|
17
20
|
import { registerIngestionJobsListTool } from './jobs-list.js';
|
|
18
21
|
import { registerIngestionTransactionIgnoreTool } from './transaction-ignore.js';
|
|
19
22
|
import { registerIngestionTransactionsSearchTool } from './transactions-search.js';
|
|
23
|
+
import { registerIngestionUploadBeginTool } from './upload-begin.js';
|
|
20
24
|
import { registerIngestionUploadTool } from './upload.js';
|
|
21
|
-
/** Register all
|
|
25
|
+
/** Register all eight curated ingestion tools on the given server. */
|
|
22
26
|
export function registerIngestionTools(server) {
|
|
23
27
|
registerIngestionJobsListTool(server);
|
|
24
28
|
registerIngestionJobGetTool(server);
|
|
@@ -27,5 +31,6 @@ export function registerIngestionTools(server) {
|
|
|
27
31
|
registerIngestionTransactionsSearchTool(server);
|
|
28
32
|
registerIngestionTransactionIgnoreTool(server);
|
|
29
33
|
registerIngestionUploadTool(server);
|
|
34
|
+
registerIngestionUploadBeginTool(server);
|
|
30
35
|
}
|
|
31
36
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/ingestion/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,6EAA6E;AAC7E,2EAA2E;AAC3E,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/ingestion/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,6EAA6E;AAC7E,2EAA2E;AAC3E,2EAA2E;AAC3E,uDAAuD;AACvD,wEAAwE;AACxE,sEAAsE;AACtE,6EAA6E;AAC7E,uEAAuE;AACvE,6EAA6E;AAC7E,yEAAyE;AACzE,0BAA0B;AAC1B,EAAE;AACF,6EAA6E;AAC7E,yEAAyE;AACzE,6EAA6E;AAI7E,OAAO,EAAE,kCAAkC,EAAE,MAAM,sBAAsB,CAAA;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAA;AAC1D,OAAO,EAAE,wCAAwC,EAAE,MAAM,4BAA4B,CAAA;AACrF,OAAO,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAA;AAC9D,OAAO,EAAE,sCAAsC,EAAE,MAAM,yBAAyB,CAAA;AAChF,OAAO,EAAE,uCAAuC,EAAE,MAAM,0BAA0B,CAAA;AAClF,OAAO,EAAE,gCAAgC,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAA;AAEzD,sEAAsE;AACtE,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,6BAA6B,CAAC,MAAM,CAAC,CAAA;IACrC,2BAA2B,CAAC,MAAM,CAAC,CAAA;IACnC,kCAAkC,CAAC,MAAM,CAAC,CAAA;IAC1C,wCAAwC,CAAC,MAAM,CAAC,CAAA;IAChD,uCAAuC,CAAC,MAAM,CAAC,CAAA;IAC/C,sCAAsC,CAAC,MAAM,CAAC,CAAA;IAC9C,2BAA2B,CAAC,MAAM,CAAC,CAAA;IACnC,gCAAgC,CAAC,MAAM,CAAC,CAAA;AAC1C,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// `ingestion_upload_begin` — MCP-native broker for uploading a LOCAL file.
|
|
2
|
+
//
|
|
3
|
+
// MCP-over-HTTP cannot stream file bytes through the relay, and base64-in-JSON
|
|
4
|
+
// (ingestion_upload's contentBase64) burns the client's output tokens and hits
|
|
5
|
+
// the relay's ~750 KB practical ceiling. For a real LOCAL file the bytes must
|
|
6
|
+
// go AROUND the relay: this tool brokers the upload TARGET, and the client's
|
|
7
|
+
// own Claude POSTs the multipart file DIRECTLY to matcher's public streaming
|
|
8
|
+
// endpoint using the same bearer it sends this relay. The relay never sees the
|
|
9
|
+
// bytes — there is nothing to dispatch server-side, so the handler is a pure
|
|
10
|
+
// local computation (no backend call, no operationId → invisible to the
|
|
11
|
+
// coverage/parity gates).
|
|
12
|
+
//
|
|
13
|
+
// The returned uploadUrl reuses contextSourceUploadPath so the path encoding
|
|
14
|
+
// matches the real endpoint /v1/imports/contexts/{contextId}/sources/{sourceId}
|
|
15
|
+
// /upload byte-for-byte, built from config.matcherApiUrl. That URL MUST be
|
|
16
|
+
// client-reachable: in cluster deployments where the relay talks to matcher
|
|
17
|
+
// over an internal service DNS name, the operator must point MATCHER_API_URL at
|
|
18
|
+
// the public host so the client's own Claude can POST to it directly.
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { loadConfig } from '../../config.js';
|
|
21
|
+
import { contextSourceUploadPath } from './shared.js';
|
|
22
|
+
/** Zod raw shape for `ingestion_upload_begin` input. */
|
|
23
|
+
export const ingestionUploadBeginInputShape = {
|
|
24
|
+
contextId: z
|
|
25
|
+
.string()
|
|
26
|
+
.min(1)
|
|
27
|
+
.describe('The reconciliation context id (UUID) to upload into. The context MUST ' +
|
|
28
|
+
'already exist and be ACTIVE — uploading to a DRAFT context is rejected ' +
|
|
29
|
+
'with 403. Create it first with context_create if needed.'),
|
|
30
|
+
sourceId: z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1)
|
|
33
|
+
.describe('The source id (UUID) within that context to upload into. Must already ' +
|
|
34
|
+
'exist — create it first with source_create if needed.'),
|
|
35
|
+
format: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('The ingestion format you will send (e.g. csv | json | xml | camt053). ' +
|
|
39
|
+
'Used to build a concrete example in the returned instructions; the ' +
|
|
40
|
+
'actual value you send is the `format` multipart field.'),
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Register `ingestion_upload_begin`. Pure local computation: builds the client-
|
|
44
|
+
* reachable upload URL + the multipart contract and returns them as a JSON
|
|
45
|
+
* text block. No relay dispatch, no auth material in the output.
|
|
46
|
+
*/
|
|
47
|
+
export function registerIngestionUploadBeginTool(server) {
|
|
48
|
+
server.registerTool('ingestion_upload_begin', {
|
|
49
|
+
description: 'Broker a DIRECT, client-to-matcher upload of a LOCAL file. MCP-over-' +
|
|
50
|
+
'HTTP cannot stream file bytes through this relay, so instead of ' +
|
|
51
|
+
'sending the bytes here, call this tool to get the upload TARGET, then ' +
|
|
52
|
+
'have YOUR OWN Claude POST the multipart file straight to matcher using ' +
|
|
53
|
+
'the same bearer you send this relay — the relay never touches the ' +
|
|
54
|
+
'bytes. Returns { uploadUrl, method, fileField, formatField, ' +
|
|
55
|
+
'fieldOrder, contextId, sourceId, baseUrlUsed, instructions }. Use this ' +
|
|
56
|
+
'for real local files; for tiny inline payloads use ingestion_upload ' +
|
|
57
|
+
'with contentBase64. No tenant field, no auth material is returned.',
|
|
58
|
+
inputSchema: ingestionUploadBeginInputShape,
|
|
59
|
+
}, async (args) => {
|
|
60
|
+
const cfg = loadConfig();
|
|
61
|
+
const baseUrl = cfg.matcherApiUrl;
|
|
62
|
+
const uploadUrl = baseUrl + contextSourceUploadPath(args.contextId, args.sourceId);
|
|
63
|
+
const formatExample = args.format ?? 'csv';
|
|
64
|
+
const instructions = [
|
|
65
|
+
'Upload a LOCAL file directly to matcher (the relay never sees the ' +
|
|
66
|
+
'bytes). Steps for a blind operator:',
|
|
67
|
+
'1. PREREQUISITES: the context (' +
|
|
68
|
+
args.contextId +
|
|
69
|
+
') must EXIST and be ACTIVE, and the source (' +
|
|
70
|
+
args.sourceId +
|
|
71
|
+
') must EXIST. Create them first with context_create + source_create ' +
|
|
72
|
+
'if needed, and ACTIVATE the context — uploading to a DRAFT context ' +
|
|
73
|
+
'returns 403.',
|
|
74
|
+
'2. POST a multipart/form-data request to uploadUrl (' +
|
|
75
|
+
uploadUrl +
|
|
76
|
+
') reusing YOUR OWN bearer token — send the SAME Authorization header ' +
|
|
77
|
+
'you send this relay. The relay is not in this path; you talk to ' +
|
|
78
|
+
'matcher directly.',
|
|
79
|
+
'3. FIELD ORDER IS LOAD-BEARING: append the `format` field BEFORE the ' +
|
|
80
|
+
'`file` part. If the `file` part arrives first, matcher falls back to ' +
|
|
81
|
+
'inferring the format from the filename extension, which CANNOT ' +
|
|
82
|
+
'detect camt053 (and mis-resolves ambiguous .xml). Example fields in ' +
|
|
83
|
+
'order: format="' +
|
|
84
|
+
formatExample +
|
|
85
|
+
'" then file=@<your-local-file>.',
|
|
86
|
+
'4. On success matcher returns 202 with a jobId. POLL ingestion_job_get ' +
|
|
87
|
+
'(with this contextId and that jobId) to watch status. COMPLETED and ' +
|
|
88
|
+
'FAILED are terminal; on FAILED read the `diagnosis` field for the ' +
|
|
89
|
+
'reason.',
|
|
90
|
+
'5. SIZE CAP: the streaming route is governed by the operator-' +
|
|
91
|
+
'configured runtime key ingestion.max_upload_bytes (default ~1 GiB; ' +
|
|
92
|
+
'range 1 MiB–8 GiB). It counts the TOTAL request body — every part, ' +
|
|
93
|
+
'header, and boundary, not just the file. A body over the limit ' +
|
|
94
|
+
'returns 413 (an over-limit upload detected mid-stream also fails the ' +
|
|
95
|
+
'job with a diagnosis). This cap is independent of the relay body ' +
|
|
96
|
+
'cap, which does not apply since the bytes bypass the relay.',
|
|
97
|
+
'baseUrlUsed came from MATCHER_API_URL, which MUST be a client-' +
|
|
98
|
+
'reachable (public) address. If this URL is an internal/cluster ' +
|
|
99
|
+
'address your local Claude cannot reach, the operator must point ' +
|
|
100
|
+
'MATCHER_API_URL at the public matcher host.',
|
|
101
|
+
].join('\n');
|
|
102
|
+
const contract = {
|
|
103
|
+
uploadUrl,
|
|
104
|
+
method: 'POST',
|
|
105
|
+
fileField: 'file',
|
|
106
|
+
formatField: 'format',
|
|
107
|
+
fieldOrder: ['format', 'file'],
|
|
108
|
+
contextId: args.contextId,
|
|
109
|
+
sourceId: args.sourceId,
|
|
110
|
+
baseUrlUsed: baseUrl,
|
|
111
|
+
instructions,
|
|
112
|
+
};
|
|
113
|
+
return { content: [{ type: 'text', text: JSON.stringify(contract) }] };
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=upload-begin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-begin.js","sourceRoot":"","sources":["../../../src/tools/ingestion/upload-begin.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,28 +2,23 @@
|
|
|
2
2
|
// /v1/imports/contexts/{contextId}/sources/{sourceId}/upload.
|
|
3
3
|
//
|
|
4
4
|
// The backend endpoint is multipart/form-data, which a JSON MCP tool can't
|
|
5
|
-
// carry directly. This tool accepts the file body
|
|
6
|
-
// relay rebuilds the real multipart request
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// carry directly. This tool accepts the file body inline as base64 and the
|
|
6
|
+
// relay rebuilds the real multipart request: wrap the bytes in a `FormData`
|
|
7
|
+
// and let `matcher/client.ts`'s FormData branch hand it to `fetch` untouched
|
|
8
|
+
// (no JSON.stringify, no content-type override — the runtime sets the
|
|
9
|
+
// multipart boundary itself).
|
|
10
10
|
//
|
|
11
|
-
// - `contentBase64`: the file inline, base64-encoded.
|
|
12
|
-
//
|
|
11
|
+
// - `contentBase64`: the file inline, base64-encoded. This is the path for
|
|
12
|
+
// SMALL, programmatically-generated payloads — an LLM client must emit the
|
|
13
13
|
// whole encoded string in its OUTPUT tokens, and base64 inflates bytes by
|
|
14
14
|
// ~33% on top of that (a 1000-row file is easily ~150k tokens). Practical
|
|
15
15
|
// ceiling: the relay's inbound JSON body cap (1 MiB default,
|
|
16
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
17
|
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
18
|
+
// For a real LOCAL file (bytes on the operator's disk), use
|
|
19
|
+
// ingestion_upload_begin (upload-begin.ts) instead, which brokers a direct
|
|
20
|
+
// client→matcher upload so the bytes never traverse the relay or the token
|
|
21
|
+
// budget.
|
|
27
22
|
//
|
|
28
23
|
// Field order matters: `format` MUST be appended BEFORE `file`. The backend's
|
|
29
24
|
// streaming multipart parser binds the declared format from whichever field
|
|
@@ -36,10 +31,13 @@
|
|
|
36
31
|
// validates both against the relayed token's tenant. No tenant field is
|
|
37
32
|
// accepted here either.
|
|
38
33
|
import { z } from 'zod';
|
|
39
|
-
import { loadConfig } from '../../config.js';
|
|
40
|
-
import { fetchSourceFile } from './fetch-source.js';
|
|
41
34
|
import { contextSourceUploadPath, dispatchIngestion } from './shared.js';
|
|
42
|
-
/**
|
|
35
|
+
/** Current known parse formats the backend accepts for an ingestion upload.
|
|
36
|
+
* Not a closed enum on the input schema: ingestion formats are a GROWING set
|
|
37
|
+
* (the connector-parity roadmap adds parsers), so the field is typed as a free
|
|
38
|
+
* string and this list only feeds the description as a hint — a closed enum
|
|
39
|
+
* would lag the backend and reject a valid new format at the client. See
|
|
40
|
+
* docs/enum-policy.md (registered in INTENTIONALLY_OPEN_FIELDS). */
|
|
43
41
|
const UPLOAD_FORMATS = ['csv', 'json', 'xml', 'camt053'];
|
|
44
42
|
/** Zod raw shape for `ingestion_upload` input — path ids + format + file. */
|
|
45
43
|
export const ingestionUploadInputShape = {
|
|
@@ -55,103 +53,55 @@ export const ingestionUploadInputShape = {
|
|
|
55
53
|
.describe('The source id (UUID) within the context to upload into. Must already ' +
|
|
56
54
|
'exist (create it first with source_create).'),
|
|
57
55
|
format: z
|
|
58
|
-
.
|
|
59
|
-
.
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
56
|
+
.string()
|
|
57
|
+
.min(1)
|
|
58
|
+
.describe('The declared parse format of the file. Free-form string — the backend ' +
|
|
59
|
+
'validates and the accepted set grows as new parsers are added. ' +
|
|
60
|
+
'Current known formats: ' +
|
|
61
|
+
UPLOAD_FORMATS.join(' | ') +
|
|
62
|
+
'. Sent as the first multipart field — matcher binds the format from ' +
|
|
63
|
+
'it before reading the file, so always pass the true format rather ' +
|
|
64
|
+
'than relying on filename-extension inference.'),
|
|
63
65
|
filename: z
|
|
64
66
|
.string()
|
|
65
67
|
.min(1)
|
|
66
68
|
.describe('The original file name (e.g. "transactions.csv"), sent as the ' +
|
|
67
69
|
'multipart filename for the file part.'),
|
|
68
70
|
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
71
|
.string()
|
|
80
72
|
.min(1)
|
|
81
|
-
.
|
|
82
|
-
.describe('
|
|
83
|
-
'
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'of contentBase64 / fileUrl is required.'),
|
|
73
|
+
.base64()
|
|
74
|
+
.describe('The file bytes, base64-encoded (required). For SMALL, programmatically-' +
|
|
75
|
+
'generated payloads only — base64 inflates bytes by ~33% and the LLM ' +
|
|
76
|
+
'client must emit the whole encoded string as output tokens. The ' +
|
|
77
|
+
'relay\'s inbound JSON body is capped (1 MiB by default, MAX_BODY_BYTES ' +
|
|
78
|
+
'env override), so raw files up to ~750 KB upload cleanly under the ' +
|
|
79
|
+
'default. For a real LOCAL file use ingestion_upload_begin instead (it ' +
|
|
80
|
+
'brokers a direct upload that bypasses this token/body cap).'),
|
|
90
81
|
};
|
|
91
82
|
/**
|
|
92
|
-
* Register `ingestion_upload`.
|
|
93
|
-
* `
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* context, 400 malformed file) surfaces via the shared toToolError.
|
|
83
|
+
* Register `ingestion_upload`. Decodes the file bytes from `contentBase64`,
|
|
84
|
+
* then rebuilds a `FormData` with `format` appended before `file` (order is
|
|
85
|
+
* load-bearing — see module comment) and dispatches the POST through the shared
|
|
86
|
+
* fail-closed relay; a matcher error (e.g. 403 inactive context, 400 malformed
|
|
87
|
+
* file) surfaces via the shared toToolError.
|
|
98
88
|
*/
|
|
99
89
|
export function registerIngestionUploadTool(server) {
|
|
100
90
|
server.registerTool('ingestion_upload', {
|
|
101
|
-
description: 'Upload a file into a source for ingestion
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
-
'
|
|
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. ' +
|
|
91
|
+
description: 'Upload a file into a source for ingestion by passing the file body ' +
|
|
92
|
+
'inline as contentBase64 — base64 in JSON, for SMALL programmatic ' +
|
|
93
|
+
'payloads only (raw files up to ~750 KB; larger payloads burn the ' +
|
|
94
|
+
'calling LLM\'s output tokens and risk the relay\'s inbound body cap). ' +
|
|
95
|
+
'For a real LOCAL file use ingestion_upload_begin instead (a direct ' +
|
|
96
|
+
'client→matcher upload that bypasses this relay entirely). The relay ' +
|
|
97
|
+
'rebuilds the multipart/form-data upload the matcher backend expects. ' +
|
|
112
98
|
'Prerequisites: the context must be ACTIVE and the sourceId must ' +
|
|
113
99
|
'already exist. No tenant field is accepted. Returns the 202 job ' +
|
|
114
100
|
'response, or a structured RFC 9457 error (e.g. 403 if the context ' +
|
|
115
101
|
'is not ACTIVE).',
|
|
116
102
|
inputSchema: ingestionUploadInputShape,
|
|
117
103
|
}, async (args, extra) => {
|
|
118
|
-
const
|
|
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
|
-
}
|
|
104
|
+
const bytes = Buffer.from(args.contentBase64, 'base64');
|
|
155
105
|
const form = new FormData();
|
|
156
106
|
// 'format' MUST precede 'file' — see module comment.
|
|
157
107
|
form.append('format', args.format);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../../../src/tools/ingestion/upload.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,8DAA8D;AAC9D,EAAE;AACF,2EAA2E;AAC3E,
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../../../src/tools/ingestion/upload.ts"],"names":[],"mappings":"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"}
|
|
@@ -46,8 +46,10 @@ export const createSourceInputShape = {
|
|
|
46
46
|
'reconciled against each other.'),
|
|
47
47
|
config: z
|
|
48
48
|
.record(z.string(), z.unknown())
|
|
49
|
+
.optional()
|
|
49
50
|
.describe('Source-specific configuration object (connection and parsing settings). ' +
|
|
50
|
-
'
|
|
51
|
+
'Optional; defaults to an empty object server-side when omitted. Its ' +
|
|
52
|
+
'shape depends on the source type and is validated server-side.'),
|
|
51
53
|
};
|
|
52
54
|
/**
|
|
53
55
|
* Build the POST body from the input minus the path-only `contextId`. Only the
|
|
@@ -68,8 +70,9 @@ export function registerSourceCreateTool(server) {
|
|
|
68
70
|
server.registerTool('source_create', {
|
|
69
71
|
description: 'Create a reconciliation source under a context. Provide contextId ' +
|
|
70
72
|
'(UUID path param — not a tenant field), name (1–50), type ' +
|
|
71
|
-
'(LEDGER|BANK|GATEWAY|CUSTOM|FETCHER), side (LEFT|RIGHT), and
|
|
72
|
-
'type-specific config object (validated server-side
|
|
73
|
+
'(LEDGER|BANK|GATEWAY|CUSTOM|FETCHER), side (LEFT|RIGHT), and an ' +
|
|
74
|
+
'optional type-specific config object (validated server-side; defaults ' +
|
|
75
|
+
'to an empty object when omitted). The source is ' +
|
|
73
76
|
"created for your token's tenant (no tenant field is accepted). Returns " +
|
|
74
77
|
'the created source, or a structured RFC 9457 error.',
|
|
75
78
|
inputSchema: createSourceInputShape,
|
|
@@ -1 +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,
|
|
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,EAAE;SACV,QAAQ,CACP,0EAA0E;QACxE,sEAAsE;QACtE,gEAAgE,CACnE;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,kEAAkE;YAClE,wEAAwE;YACxE,kDAAkD;YAClD,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"}
|