@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.
Files changed (220) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +281 -0
  3. package/dist/auth/request-token.js +117 -0
  4. package/dist/auth/request-token.js.map +1 -0
  5. package/dist/config.js +95 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/matcher/client.js +168 -0
  8. package/dist/matcher/client.js.map +1 -0
  9. package/dist/observability/otel.js +223 -0
  10. package/dist/observability/otel.js.map +1 -0
  11. package/dist/server.js +245 -0
  12. package/dist/server.js.map +1 -0
  13. package/dist/spec/curated-operations.js +145 -0
  14. package/dist/spec/curated-operations.js.map +1 -0
  15. package/dist/spec/index.js +155 -0
  16. package/dist/spec/index.js.map +1 -0
  17. package/dist/spec/load.js +76 -0
  18. package/dist/spec/load.js.map +1 -0
  19. package/dist/spec/openapi.yaml +15217 -0
  20. package/dist/tools/context/create.js +100 -0
  21. package/dist/tools/context/create.js.map +1 -0
  22. package/dist/tools/context/get.js +31 -0
  23. package/dist/tools/context/get.js.map +1 -0
  24. package/dist/tools/context/index.js +21 -0
  25. package/dist/tools/context/index.js.map +1 -0
  26. package/dist/tools/context/list.js +78 -0
  27. package/dist/tools/context/list.js.map +1 -0
  28. package/dist/tools/context/shared.js +61 -0
  29. package/dist/tools/context/shared.js.map +1 -0
  30. package/dist/tools/context/update.js +105 -0
  31. package/dist/tools/context/update.js.map +1 -0
  32. package/dist/tools/dashboard/aggregates.js +34 -0
  33. package/dist/tools/dashboard/aggregates.js.map +1 -0
  34. package/dist/tools/dashboard/cash-impact.js +36 -0
  35. package/dist/tools/dashboard/cash-impact.js.map +1 -0
  36. package/dist/tools/dashboard/index.js +27 -0
  37. package/dist/tools/dashboard/index.js.map +1 -0
  38. package/dist/tools/dashboard/match-rate.js +35 -0
  39. package/dist/tools/dashboard/match-rate.js.map +1 -0
  40. package/dist/tools/dashboard/metrics.js +36 -0
  41. package/dist/tools/dashboard/metrics.js.map +1 -0
  42. package/dist/tools/dashboard/shared.js +122 -0
  43. package/dist/tools/dashboard/shared.js.map +1 -0
  44. package/dist/tools/dashboard/sla.js +33 -0
  45. package/dist/tools/dashboard/sla.js.map +1 -0
  46. package/dist/tools/dashboard/source-breakdown.js +36 -0
  47. package/dist/tools/dashboard/source-breakdown.js.map +1 -0
  48. package/dist/tools/dashboard/volume.js +35 -0
  49. package/dist/tools/dashboard/volume.js.map +1 -0
  50. package/dist/tools/dispute/close.js +49 -0
  51. package/dist/tools/dispute/close.js.map +1 -0
  52. package/dist/tools/dispute/get.js +27 -0
  53. package/dist/tools/dispute/get.js.map +1 -0
  54. package/dist/tools/dispute/index.js +17 -0
  55. package/dist/tools/dispute/index.js.map +1 -0
  56. package/dist/tools/dispute/list.js +137 -0
  57. package/dist/tools/dispute/list.js.map +1 -0
  58. package/dist/tools/dispute/shared.js +66 -0
  59. package/dist/tools/dispute/shared.js.map +1 -0
  60. package/dist/tools/dispute/submit-evidence.js +58 -0
  61. package/dist/tools/dispute/submit-evidence.js.map +1 -0
  62. package/dist/tools/exception/add-comment.js +50 -0
  63. package/dist/tools/exception/add-comment.js.map +1 -0
  64. package/dist/tools/exception/adjust-entry.js +71 -0
  65. package/dist/tools/exception/adjust-entry.js.map +1 -0
  66. package/dist/tools/exception/bulk-assign.js +47 -0
  67. package/dist/tools/exception/bulk-assign.js.map +1 -0
  68. package/dist/tools/exception/bulk-dispatch.js +69 -0
  69. package/dist/tools/exception/bulk-dispatch.js.map +1 -0
  70. package/dist/tools/exception/bulk-resolve.js +64 -0
  71. package/dist/tools/exception/bulk-resolve.js.map +1 -0
  72. package/dist/tools/exception/delete-comment.js +40 -0
  73. package/dist/tools/exception/delete-comment.js.map +1 -0
  74. package/dist/tools/exception/dispatch.js +64 -0
  75. package/dist/tools/exception/dispatch.js.map +1 -0
  76. package/dist/tools/exception/force-match.js +55 -0
  77. package/dist/tools/exception/force-match.js.map +1 -0
  78. package/dist/tools/exception/get.js +34 -0
  79. package/dist/tools/exception/get.js.map +1 -0
  80. package/dist/tools/exception/history.js +61 -0
  81. package/dist/tools/exception/history.js.map +1 -0
  82. package/dist/tools/exception/index.js +39 -0
  83. package/dist/tools/exception/index.js.map +1 -0
  84. package/dist/tools/exception/list-comments.js +35 -0
  85. package/dist/tools/exception/list-comments.js.map +1 -0
  86. package/dist/tools/exception/list.js +169 -0
  87. package/dist/tools/exception/list.js.map +1 -0
  88. package/dist/tools/exception/open-dispute.js +53 -0
  89. package/dist/tools/exception/open-dispute.js.map +1 -0
  90. package/dist/tools/exception/shared.js +98 -0
  91. package/dist/tools/exception/shared.js.map +1 -0
  92. package/dist/tools/fee-rule/create.js +106 -0
  93. package/dist/tools/fee-rule/create.js.map +1 -0
  94. package/dist/tools/fee-rule/delete.js +27 -0
  95. package/dist/tools/fee-rule/delete.js.map +1 -0
  96. package/dist/tools/fee-rule/get.js +27 -0
  97. package/dist/tools/fee-rule/get.js.map +1 -0
  98. package/dist/tools/fee-rule/index.js +21 -0
  99. package/dist/tools/fee-rule/index.js.map +1 -0
  100. package/dist/tools/fee-rule/list.js +64 -0
  101. package/dist/tools/fee-rule/list.js.map +1 -0
  102. package/dist/tools/fee-rule/shared.js +67 -0
  103. package/dist/tools/fee-rule/shared.js.map +1 -0
  104. package/dist/tools/fee-rule/update.js +70 -0
  105. package/dist/tools/fee-rule/update.js.map +1 -0
  106. package/dist/tools/fee-schedule/create.js +105 -0
  107. package/dist/tools/fee-schedule/create.js.map +1 -0
  108. package/dist/tools/fee-schedule/delete.js +32 -0
  109. package/dist/tools/fee-schedule/delete.js.map +1 -0
  110. package/dist/tools/fee-schedule/get.js +30 -0
  111. package/dist/tools/fee-schedule/get.js.map +1 -0
  112. package/dist/tools/fee-schedule/index.js +22 -0
  113. package/dist/tools/fee-schedule/index.js.map +1 -0
  114. package/dist/tools/fee-schedule/list.js +56 -0
  115. package/dist/tools/fee-schedule/list.js.map +1 -0
  116. package/dist/tools/fee-schedule/shared.js +58 -0
  117. package/dist/tools/fee-schedule/shared.js.map +1 -0
  118. package/dist/tools/fee-schedule/simulate.js +54 -0
  119. package/dist/tools/fee-schedule/simulate.js.map +1 -0
  120. package/dist/tools/fee-schedule/update.js +69 -0
  121. package/dist/tools/fee-schedule/update.js.map +1 -0
  122. package/dist/tools/generic/describe-operation.js +157 -0
  123. package/dist/tools/generic/describe-operation.js.map +1 -0
  124. package/dist/tools/generic/invoke.js +343 -0
  125. package/dist/tools/generic/invoke.js.map +1 -0
  126. package/dist/tools/generic/list-operations.js +73 -0
  127. package/dist/tools/generic/list-operations.js.map +1 -0
  128. package/dist/tools/ingestion/index.js +29 -0
  129. package/dist/tools/ingestion/index.js.map +1 -0
  130. package/dist/tools/ingestion/job-errors-list.js +45 -0
  131. package/dist/tools/ingestion/job-errors-list.js.map +1 -0
  132. package/dist/tools/ingestion/job-get.js +42 -0
  133. package/dist/tools/ingestion/job-get.js.map +1 -0
  134. package/dist/tools/ingestion/job-transactions-list.js +93 -0
  135. package/dist/tools/ingestion/job-transactions-list.js.map +1 -0
  136. package/dist/tools/ingestion/jobs-list.js +89 -0
  137. package/dist/tools/ingestion/jobs-list.js.map +1 -0
  138. package/dist/tools/ingestion/shared.js +84 -0
  139. package/dist/tools/ingestion/shared.js.map +1 -0
  140. package/dist/tools/ingestion/transaction-ignore.js +51 -0
  141. package/dist/tools/ingestion/transaction-ignore.js.map +1 -0
  142. package/dist/tools/ingestion/transactions-search.js +143 -0
  143. package/dist/tools/ingestion/transactions-search.js.map +1 -0
  144. package/dist/tools/match-rule/create.js +64 -0
  145. package/dist/tools/match-rule/create.js.map +1 -0
  146. package/dist/tools/match-rule/delete.js +35 -0
  147. package/dist/tools/match-rule/delete.js.map +1 -0
  148. package/dist/tools/match-rule/get.js +32 -0
  149. package/dist/tools/match-rule/get.js.map +1 -0
  150. package/dist/tools/match-rule/index.js +22 -0
  151. package/dist/tools/match-rule/index.js.map +1 -0
  152. package/dist/tools/match-rule/list.js +72 -0
  153. package/dist/tools/match-rule/list.js.map +1 -0
  154. package/dist/tools/match-rule/reorder.js +45 -0
  155. package/dist/tools/match-rule/reorder.js.map +1 -0
  156. package/dist/tools/match-rule/shared.js +71 -0
  157. package/dist/tools/match-rule/shared.js.map +1 -0
  158. package/dist/tools/match-rule/update.js +68 -0
  159. package/dist/tools/match-rule/update.js.map +1 -0
  160. package/dist/tools/matching/get.js +48 -0
  161. package/dist/tools/matching/get.js.map +1 -0
  162. package/dist/tools/matching/groups.js +98 -0
  163. package/dist/tools/matching/groups.js.map +1 -0
  164. package/dist/tools/matching/index.js +19 -0
  165. package/dist/tools/matching/index.js.map +1 -0
  166. package/dist/tools/matching/list.js +76 -0
  167. package/dist/tools/matching/list.js.map +1 -0
  168. package/dist/tools/matching/shared.js +77 -0
  169. package/dist/tools/matching/shared.js.map +1 -0
  170. package/dist/tools/matching/start.js +55 -0
  171. package/dist/tools/matching/start.js.map +1 -0
  172. package/dist/tools/report/count-exceptions.js +45 -0
  173. package/dist/tools/report/count-exceptions.js.map +1 -0
  174. package/dist/tools/report/count-matched.js +46 -0
  175. package/dist/tools/report/count-matched.js.map +1 -0
  176. package/dist/tools/report/count-transactions.js +44 -0
  177. package/dist/tools/report/count-transactions.js.map +1 -0
  178. package/dist/tools/report/count-unmatched.js +44 -0
  179. package/dist/tools/report/count-unmatched.js.map +1 -0
  180. package/dist/tools/report/export-exceptions.js +46 -0
  181. package/dist/tools/report/export-exceptions.js.map +1 -0
  182. package/dist/tools/report/export-matched.js +47 -0
  183. package/dist/tools/report/export-matched.js.map +1 -0
  184. package/dist/tools/report/export-summary.js +47 -0
  185. package/dist/tools/report/export-summary.js.map +1 -0
  186. package/dist/tools/report/export-unmatched.js +47 -0
  187. package/dist/tools/report/export-unmatched.js.map +1 -0
  188. package/dist/tools/report/export-variance.js +46 -0
  189. package/dist/tools/report/export-variance.js.map +1 -0
  190. package/dist/tools/report/index.js +40 -0
  191. package/dist/tools/report/index.js.map +1 -0
  192. package/dist/tools/report/matched.js +44 -0
  193. package/dist/tools/report/matched.js.map +1 -0
  194. package/dist/tools/report/shared.js +176 -0
  195. package/dist/tools/report/shared.js.map +1 -0
  196. package/dist/tools/report/summary.js +41 -0
  197. package/dist/tools/report/summary.js.map +1 -0
  198. package/dist/tools/report/unmatched.js +44 -0
  199. package/dist/tools/report/unmatched.js.map +1 -0
  200. package/dist/tools/report/variance.js +44 -0
  201. package/dist/tools/report/variance.js.map +1 -0
  202. package/dist/tools/source/create.js +84 -0
  203. package/dist/tools/source/create.js.map +1 -0
  204. package/dist/tools/source/get.js +41 -0
  205. package/dist/tools/source/get.js.map +1 -0
  206. package/dist/tools/source/index.js +22 -0
  207. package/dist/tools/source/index.js.map +1 -0
  208. package/dist/tools/source/list.js +72 -0
  209. package/dist/tools/source/list.js.map +1 -0
  210. package/dist/tools/source/shared.js +63 -0
  211. package/dist/tools/source/shared.js.map +1 -0
  212. package/dist/tools/source/update.js +73 -0
  213. package/dist/tools/source/update.js.map +1 -0
  214. package/dist/tools/tool-error.js +74 -0
  215. package/dist/tools/tool-error.js.map +1 -0
  216. package/dist/transport/body-limit.js +82 -0
  217. package/dist/transport/body-limit.js.map +1 -0
  218. package/dist/transport/http.js +154 -0
  219. package/dist/transport/http.js.map +1 -0
  220. package/package.json +68 -0
package/dist/config.js ADDED
@@ -0,0 +1,95 @@
1
+ // Bootstrap configuration for the Matcher MCP server.
2
+ //
3
+ // Values are read from the environment once and frozen into a typed object.
4
+ // Defaults are chosen to avoid collisions with matcher dev (matcher runs on
5
+ // 4018; this server defaults to 4019). The matcher base URL is surfaced here
6
+ // so the HTTP client (task 1.3.1) and the generic invoke / curated tools
7
+ // consume a single source of truth instead of reading process.env directly.
8
+ const DEFAULT_PORT = 4019;
9
+ const DEFAULT_MATCHER_API_URL = 'http://localhost:4018';
10
+ const DEFAULT_TIMEOUT_MS = 30_000;
11
+ const DEFAULT_MAX_BODY_BYTES = 1_048_576; // 1 MiB
12
+ const DEFAULT_OTEL_SERVICE_NAME = 'matcher-mcp';
13
+ /**
14
+ * Parse a port from a raw env string, falling back to the default when unset.
15
+ * An explicitly-set but invalid value is a misconfiguration and throws, rather
16
+ * than silently degrading to the default (fail-loud at boot).
17
+ */
18
+ function parsePort(raw, fallback) {
19
+ if (raw === undefined || raw === '') {
20
+ return fallback;
21
+ }
22
+ const value = Number(raw);
23
+ if (!Number.isInteger(value) || value < 1 || value > 65535) {
24
+ throw new Error(`invalid MCP_PORT: ${raw} (expected an integer in 1..65535)`);
25
+ }
26
+ return value;
27
+ }
28
+ /**
29
+ * Parse a positive timeout (ms) from a raw env string, falling back to the
30
+ * default when unset. An explicitly-set but invalid value is a misconfiguration
31
+ * and throws, rather than silently degrading to the default (fail-loud at boot).
32
+ */
33
+ function parseTimeoutMs(raw, fallback) {
34
+ if (raw === undefined || raw === '') {
35
+ return fallback;
36
+ }
37
+ const value = Number(raw);
38
+ if (!Number.isInteger(value) || value < 1) {
39
+ throw new Error(`invalid MATCHER_TIMEOUT_MS: ${raw} (expected a positive integer of milliseconds)`);
40
+ }
41
+ return value;
42
+ }
43
+ /**
44
+ * Parse a positive byte count from a raw env string, falling back to the default
45
+ * when unset. An explicitly-set but invalid value is a misconfiguration and
46
+ * throws, rather than silently degrading to the default (fail-loud at boot).
47
+ */
48
+ function parseMaxBodyBytes(raw, fallback) {
49
+ if (raw === undefined || raw === '') {
50
+ return fallback;
51
+ }
52
+ const value = Number(raw);
53
+ if (!Number.isInteger(value) || value < 1) {
54
+ throw new Error(`invalid MAX_BODY_BYTES: ${raw} (expected a positive integer of bytes)`);
55
+ }
56
+ return value;
57
+ }
58
+ /**
59
+ * Parse a boolean flag from a raw env string, falling back to the default when
60
+ * unset. Accepts `true`/`false` and `1`/`0` (case-insensitive). An explicitly-set
61
+ * but unrecognized value is a misconfiguration and throws, rather than silently
62
+ * coercing to a default (fail-loud at boot).
63
+ */
64
+ function parseBool(raw, fallback, key) {
65
+ if (raw === undefined || raw === '') {
66
+ return fallback;
67
+ }
68
+ const normalized = raw.trim().toLowerCase();
69
+ if (normalized === 'true' || normalized === '1') {
70
+ return true;
71
+ }
72
+ if (normalized === 'false' || normalized === '0') {
73
+ return false;
74
+ }
75
+ throw new Error(`invalid ${key}: ${raw} (expected one of true, false, 1, 0)`);
76
+ }
77
+ /**
78
+ * Build the config from a given environment map (defaults to process.env).
79
+ * Pure and side-effect-free so tests can drive it with a synthetic env.
80
+ */
81
+ export function loadConfig(env = process.env) {
82
+ const matcherApiUrl = env.MATCHER_API_URL?.trim() || DEFAULT_MATCHER_API_URL;
83
+ const otelEndpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();
84
+ const otelServiceName = env.OTEL_RESOURCE_SERVICE_NAME?.trim() || DEFAULT_OTEL_SERVICE_NAME;
85
+ return Object.freeze({
86
+ port: parsePort(env.MCP_PORT, DEFAULT_PORT),
87
+ matcherApiUrl: matcherApiUrl.replace(/\/+$/, ''),
88
+ timeoutMs: parseTimeoutMs(env.MATCHER_TIMEOUT_MS, DEFAULT_TIMEOUT_MS),
89
+ maxBodyBytes: parseMaxBodyBytes(env.MAX_BODY_BYTES, DEFAULT_MAX_BODY_BYTES),
90
+ enableTelemetry: parseBool(env.ENABLE_TELEMETRY, false, 'ENABLE_TELEMETRY'),
91
+ otelExporterOtlpEndpoint: otelEndpoint || undefined,
92
+ otelServiceName,
93
+ });
94
+ }
95
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,168 @@
1
+ // Matcher HTTP client contract — the single typed boundary every tool (generic
2
+ // invoke + curated) uses to reach the matcher API.
3
+ //
4
+ // The contract (MatcherRequest / MatcherProblem / MatcherApiError / MatcherClient
5
+ // / createMatcherClient) is fixed by the plan's Shared Contracts. This file
6
+ // supplies the concrete `fetch`-based implementation (task 1.3.1) that both the
7
+ // generic invoke (1.2.3) and every curated tool depend on.
8
+ //
9
+ // Security invariants (asserted by tests):
10
+ // - The bearer token is sent on the outbound `Authorization` header and is
11
+ // NEVER logged and NEVER placed in a thrown error message.
12
+ // - The caller always receives a `MatcherApiError` on any failure path (matcher
13
+ // status >= 400, malformed body, network failure, or timeout) — never a raw
14
+ // fetch/abort error — so error handling upstream is uniform.
15
+ import { loadConfig } from '../config.js';
16
+ import { injectTraceContext } from '../observability/otel.js';
17
+ /** Thrown by `MatcherClient.request` on any matcher status >= 400 (or transport failure). */
18
+ export class MatcherApiError extends Error {
19
+ /** HTTP status from matcher (synthetic, e.g. 504, for transport failures). */
20
+ status;
21
+ /** Best-effort parsed RFC 9457 problem document. */
22
+ problem;
23
+ constructor(message, status, problem = {}) {
24
+ super(message);
25
+ this.name = 'MatcherApiError';
26
+ this.status = status;
27
+ this.problem = problem;
28
+ }
29
+ }
30
+ /**
31
+ * Synthetic status used when a request never produced an HTTP response (network
32
+ * failure or client-enforced timeout). 504 (Gateway Timeout) reads correctly to
33
+ * an operator: the relay could not reach / hear back from matcher in time.
34
+ */
35
+ const TRANSPORT_FAILURE_STATUS = 504;
36
+ /** Build an absolute request URL from the base, path, and an optional query map. */
37
+ function buildUrl(baseUrl, path, query) {
38
+ const url = new URL(path, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);
39
+ if (query) {
40
+ for (const [key, value] of Object.entries(query)) {
41
+ if (value !== undefined) {
42
+ url.searchParams.set(key, String(value));
43
+ }
44
+ }
45
+ }
46
+ return url.toString();
47
+ }
48
+ /**
49
+ * Best-effort parse a matcher error response into a `MatcherProblem`. Matcher
50
+ * returns RFC 9457 `application/problem+json` on errors; when the body is not
51
+ * usable problem+json (empty, non-JSON, or JSON of the wrong shape) we fall back
52
+ * to the HTTP status text so the problem always carries at least a `title`.
53
+ */
54
+ async function parseProblem(res) {
55
+ const fallbackTitle = res.statusText || `HTTP ${res.status}`;
56
+ let raw;
57
+ try {
58
+ const text = await res.text();
59
+ if (!text.trim()) {
60
+ return { title: fallbackTitle };
61
+ }
62
+ raw = JSON.parse(text);
63
+ }
64
+ catch {
65
+ return { title: fallbackTitle };
66
+ }
67
+ if (typeof raw !== 'object' || raw === null) {
68
+ return { title: fallbackTitle };
69
+ }
70
+ const obj = raw;
71
+ const str = (key) => typeof obj[key] === 'string' ? obj[key] : undefined;
72
+ return {
73
+ type: str('type'),
74
+ title: str('title') ?? fallbackTitle,
75
+ detail: str('detail'),
76
+ code: str('code'),
77
+ instance: str('instance'),
78
+ errors: obj.errors,
79
+ };
80
+ }
81
+ /**
82
+ * Parse a successful response body as JSON. A 204 or any empty body resolves to
83
+ * `undefined` (matcher returns no content for deletes / some updates).
84
+ */
85
+ async function parseSuccess(res) {
86
+ if (res.status === 204) {
87
+ return undefined;
88
+ }
89
+ const text = await res.text();
90
+ if (!text.trim()) {
91
+ return undefined;
92
+ }
93
+ return JSON.parse(text);
94
+ }
95
+ /**
96
+ * Build a `MatcherClient` bound to a single caller's bearer token.
97
+ *
98
+ * NEVER call this with a global/default token — the token is always the inbound
99
+ * caller's, resolved request-scoped (see auth/request-token.ts).
100
+ *
101
+ * The per-request timeout comes from config (`MATCHER_TIMEOUT_MS`, default 30s);
102
+ * the Shared Contracts signature fixes the two parameters, so the client reads
103
+ * the timeout from config itself rather than taking a third argument.
104
+ */
105
+ export function createMatcherClient(baseUrl, bearerToken) {
106
+ const timeoutMs = loadConfig().timeoutMs;
107
+ return {
108
+ async request(req) {
109
+ const url = buildUrl(baseUrl, req.path, req.query);
110
+ const headers = {
111
+ // The caller's token, forwarded verbatim. Never logged.
112
+ authorization: `Bearer ${bearerToken}`,
113
+ accept: 'application/json',
114
+ };
115
+ let serializedBody;
116
+ if (req.body !== undefined) {
117
+ headers['content-type'] = 'application/json';
118
+ serializedBody = JSON.stringify(req.body);
119
+ }
120
+ // Inject the W3C trace context so the matcher call is a child of the
121
+ // tool-call span (MCP → matcher correlation in the collector). This writes
122
+ // ONLY `traceparent`/`tracestate` — it never reads or overwrites the
123
+ // `authorization` header, so the verbatim token relay above is untouched.
124
+ // A no-op when telemetry is disabled.
125
+ injectTraceContext(headers);
126
+ const controller = new AbortController();
127
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
128
+ let res;
129
+ try {
130
+ res = await fetch(url, {
131
+ method: req.method,
132
+ headers,
133
+ body: serializedBody,
134
+ signal: controller.signal,
135
+ });
136
+ }
137
+ catch (err) {
138
+ // Network failure or timeout abort: normalize to MatcherApiError so
139
+ // callers always get the same type. The token is in `headers` above but
140
+ // is never referenced here — the message describes only method + path.
141
+ const title = controller.signal.aborted
142
+ ? `matcher request timed out after ${timeoutMs}ms`
143
+ : 'matcher request failed before a response was received';
144
+ const detail = err instanceof Error ? err.message : 'unknown transport error';
145
+ throw new MatcherApiError(`${title}: ${req.method} ${req.path}`, TRANSPORT_FAILURE_STATUS, { title, detail });
146
+ }
147
+ finally {
148
+ clearTimeout(timer);
149
+ }
150
+ if (!res.ok) {
151
+ const problem = await parseProblem(res);
152
+ // Message carries status + problem title — never the token.
153
+ const summary = problem.title ?? res.statusText ?? `HTTP ${res.status}`;
154
+ throw new MatcherApiError(`matcher returned ${res.status}: ${summary}`, res.status, problem);
155
+ }
156
+ try {
157
+ return await parseSuccess(res);
158
+ }
159
+ catch (err) {
160
+ // A 2xx with an unparseable body is still a failure the caller must see
161
+ // as a MatcherApiError, not a raw JSON SyntaxError.
162
+ const detail = err instanceof Error ? err.message : 'unknown parse error';
163
+ throw new MatcherApiError(`matcher returned ${res.status} with an unparseable JSON body`, res.status, { title: 'invalid response body from matcher', detail });
164
+ }
165
+ },
166
+ };
167
+ }
168
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/matcher/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,223 @@
1
+ // OpenTelemetry telemetry for the Matcher MCP server.
2
+ //
3
+ // The relay is intended as a hosted, monitored service, so it is instrumented
4
+ // like the rest of the stack: traces + metrics over OTLP HTTP, correlated with
5
+ // matcher's own spans (the outbound matcher call carries a W3C `traceparent`,
6
+ // injected via `injectTraceContext`). The env names mirror matcher's Go service
7
+ // (`ENABLE_TELEMETRY` / `OTEL_EXPORTER_OTLP_ENDPOINT` / `OTEL_RESOURCE_SERVICE_NAME`)
8
+ // so the MCP slots into the same collector config.
9
+ //
10
+ // Everything is gated on `config.enableTelemetry` (default OFF):
11
+ // - Disabled → `initTelemetry` is a COMPLETE no-op: no NodeSDK, no providers,
12
+ // no exporters, no network. `instrumentToolCall` just runs the function and
13
+ // `injectTraceContext` leaves the headers untouched. Dev/test and
14
+ // single-operator runs pay nothing.
15
+ // - Enabled → a NodeSDK is started with OTLP HTTP trace + metric exporters and
16
+ // a resource stamping `service.name` (default `matcher-mcp`) and the build
17
+ // version from package.json.
18
+ //
19
+ // Token-relay rule (NON-NEGOTIABLE): telemetry records ONLY the tool name, the
20
+ // call outcome (ok / error), and — on a matcher API failure — the matcher HTTP
21
+ // status. It NEVER records the caller's bearer token, raw arguments, or request
22
+ // headers, in any span attribute, metric attribute, event, or log. No tenant
23
+ // identifier is read or emitted anywhere.
24
+ import { context, metrics, propagation, SpanStatusCode, trace, ValueType, } from '@opentelemetry/api';
25
+ import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
26
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
27
+ import { resourceFromAttributes } from '@opentelemetry/resources';
28
+ import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
29
+ import { NodeSDK } from '@opentelemetry/sdk-node';
30
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from '@opentelemetry/semantic-conventions';
31
+ import {} from '../config.js';
32
+ /** The instrumentation scope name for the relay's own tracer + meter. */
33
+ const INSTRUMENTATION_SCOPE = 'matcher-mcp';
34
+ /** Span/metric attribute keys. Deliberately a closed, token-free set. */
35
+ const ATTR_TOOL_NAME = 'mcp.tool.name';
36
+ const ATTR_OUTCOME = 'mcp.tool.outcome';
37
+ const ATTR_MATCHER_STATUS = 'matcher.http.status';
38
+ /** Outcome attribute values — the only two states a tool call reports. */
39
+ const OUTCOME_OK = 'ok';
40
+ const OUTCOME_ERROR = 'error';
41
+ /**
42
+ * The running SDK, when telemetry is enabled. `undefined` means telemetry is
43
+ * off (or not yet initialized) — in that state every helper degrades to a
44
+ * zero-cost no-op. Module-local so `shutdownTelemetry` can stop exactly what
45
+ * `initTelemetry` started.
46
+ */
47
+ let sdk;
48
+ /**
49
+ * The version stamped on the telemetry resource, captured at init so
50
+ * `instrumentToolCall` does not need it threaded through. Only meaningful while
51
+ * telemetry is enabled.
52
+ */
53
+ let serviceVersion = '0.0.0';
54
+ /**
55
+ * Initialize OpenTelemetry from config. A COMPLETE no-op when
56
+ * `config.enableTelemetry` is false: no SDK, no providers, no exporters, no
57
+ * network — returns immediately. Idempotent: a second call while already
58
+ * initialized is ignored.
59
+ *
60
+ * When enabled, starts a NodeSDK exporting traces + metrics over OTLP HTTP to
61
+ * `config.otelExporterOtlpEndpoint` (or the SDK default when unset), with a
62
+ * resource carrying `service.name = config.otelServiceName` and the build
63
+ * version.
64
+ */
65
+ export function initTelemetry(config, build = { version: serviceVersion }) {
66
+ if (!config.enableTelemetry) {
67
+ return;
68
+ }
69
+ if (sdk !== undefined) {
70
+ return;
71
+ }
72
+ serviceVersion = build.version;
73
+ const resource = resourceFromAttributes({
74
+ [ATTR_SERVICE_NAME]: config.otelServiceName,
75
+ [ATTR_SERVICE_VERSION]: build.version,
76
+ });
77
+ // The exporters read OTEL_EXPORTER_OTLP_ENDPOINT from their options; an
78
+ // undefined `url` lets the exporter fall back to its own env/default handling.
79
+ const traceExporter = new OTLPTraceExporter(config.otelExporterOtlpEndpoint !== undefined
80
+ ? { url: config.otelExporterOtlpEndpoint }
81
+ : {});
82
+ const metricExporter = new OTLPMetricExporter(config.otelExporterOtlpEndpoint !== undefined
83
+ ? { url: config.otelExporterOtlpEndpoint }
84
+ : {});
85
+ sdk = new NodeSDK({
86
+ resource,
87
+ traceExporter,
88
+ metricReaders: [new PeriodicExportingMetricReader({ exporter: metricExporter })],
89
+ });
90
+ sdk.start();
91
+ }
92
+ /**
93
+ * Stop telemetry and flush any buffered traces/metrics. A no-op when telemetry
94
+ * was never started (disabled), so callers can invoke it unconditionally in a
95
+ * shutdown path. Clears the module state so a later `initTelemetry` can start
96
+ * fresh.
97
+ */
98
+ export async function shutdownTelemetry() {
99
+ if (sdk === undefined) {
100
+ return;
101
+ }
102
+ const stopping = sdk;
103
+ sdk = undefined;
104
+ await stopping.shutdown();
105
+ }
106
+ /** Whether telemetry is currently active. Exposed for tests + the helpers. */
107
+ export function isTelemetryEnabled() {
108
+ return sdk !== undefined;
109
+ }
110
+ /** The relay's tracer (no-op tracer when telemetry is disabled). */
111
+ function getTracer() {
112
+ return trace.getTracer(INSTRUMENTATION_SCOPE, serviceVersion);
113
+ }
114
+ /** The relay's meter (no-op meter when telemetry is disabled). */
115
+ function getMeter() {
116
+ return metrics.getMeter(INSTRUMENTATION_SCOPE, serviceVersion);
117
+ }
118
+ /**
119
+ * Lazily-created call counter + latency histogram, shared across all tool
120
+ * calls. Created on first use so a disabled run never touches the metrics API
121
+ * beyond a no-op meter. When telemetry is disabled the instruments are no-op
122
+ * (the API returns inert instruments), so recording is free.
123
+ */
124
+ let toolCallCounter;
125
+ let toolLatencyHistogram;
126
+ function getToolCallCounter() {
127
+ toolCallCounter ??= getMeter().createCounter('mcp.tool.calls', {
128
+ description: 'Count of MCP tool invocations, labeled by tool name + outcome.',
129
+ valueType: ValueType.INT,
130
+ });
131
+ return toolCallCounter;
132
+ }
133
+ function getToolLatencyHistogram() {
134
+ toolLatencyHistogram ??= getMeter().createHistogram('mcp.tool.duration', {
135
+ description: 'Latency of MCP tool invocations in milliseconds.',
136
+ unit: 'ms',
137
+ valueType: ValueType.DOUBLE,
138
+ });
139
+ return toolLatencyHistogram;
140
+ }
141
+ /**
142
+ * The structural shape of a matcher API error — read without importing
143
+ * `MatcherApiError` to keep this module free of a dependency on the matcher
144
+ * client. Only the numeric `status` is read, and only to label the outcome; no
145
+ * other field is touched. This is the ONLY error data telemetry records.
146
+ */
147
+ function matcherStatusOf(err) {
148
+ if (typeof err === 'object' &&
149
+ err !== null &&
150
+ 'status' in err &&
151
+ typeof err.status === 'number') {
152
+ return err.status;
153
+ }
154
+ return undefined;
155
+ }
156
+ /**
157
+ * Wrap a tool's execution in a span + metrics. Opens a span named
158
+ * `mcp.tool.<name>`, records the call counter and the latency histogram, and on
159
+ * a thrown error marks the span error + records the matcher HTTP status when the
160
+ * thrown value carries one.
161
+ *
162
+ * Records ONLY: the tool name, the outcome (ok/error), and — on a matcher
163
+ * failure — the matcher HTTP status. It NEVER records the caller's token, the
164
+ * raw arguments, request headers, or any tenant identifier. When telemetry is
165
+ * disabled this is effectively just `fn()` (no-op tracer + inert instruments),
166
+ * so it is always safe to wrap a handler with it.
167
+ */
168
+ export async function instrumentToolCall(name, fn) {
169
+ const tracer = getTracer();
170
+ const counter = getToolCallCounter();
171
+ const histogram = getToolLatencyHistogram();
172
+ const startedAt = Date.now();
173
+ return tracer.startActiveSpan(`mcp.tool.${name}`, async (span) => {
174
+ span.setAttribute(ATTR_TOOL_NAME, name);
175
+ try {
176
+ const result = await fn();
177
+ span.setAttribute(ATTR_OUTCOME, OUTCOME_OK);
178
+ span.setStatus({ code: SpanStatusCode.OK });
179
+ counter.add(1, { [ATTR_TOOL_NAME]: name, [ATTR_OUTCOME]: OUTCOME_OK });
180
+ return result;
181
+ }
182
+ catch (err) {
183
+ const status = matcherStatusOf(err);
184
+ span.setAttribute(ATTR_OUTCOME, OUTCOME_ERROR);
185
+ if (status !== undefined) {
186
+ span.setAttribute(ATTR_MATCHER_STATUS, status);
187
+ }
188
+ // Record only that the call failed and (when present) the matcher status.
189
+ // The error message is NOT recorded — it could be operator-influenced and
190
+ // is never worth the leakage risk.
191
+ span.setStatus({ code: SpanStatusCode.ERROR });
192
+ const attrs = {
193
+ [ATTR_TOOL_NAME]: name,
194
+ [ATTR_OUTCOME]: OUTCOME_ERROR,
195
+ };
196
+ if (status !== undefined) {
197
+ attrs[ATTR_MATCHER_STATUS] = status;
198
+ }
199
+ counter.add(1, attrs);
200
+ throw err;
201
+ }
202
+ finally {
203
+ histogram.record(Date.now() - startedAt, { [ATTR_TOOL_NAME]: name });
204
+ span.end();
205
+ }
206
+ });
207
+ }
208
+ /**
209
+ * Inject the current W3C trace context into an outbound header object so the
210
+ * matcher call is a child of the tool-call span (MCP → matcher correlation in
211
+ * the collector). The header object is mutated in place and returned for
212
+ * convenience. A no-op when telemetry is disabled (the no-op propagator writes
213
+ * nothing), so the matcher client can call it unconditionally.
214
+ *
215
+ * It writes ONLY W3C trace headers (`traceparent` / `tracestate`); it never
216
+ * reads or writes an `authorization` header, so the verbatim token relay is
217
+ * untouched.
218
+ */
219
+ export function injectTraceContext(headers) {
220
+ propagation.inject(context.active(), headers);
221
+ return headers;
222
+ }
223
+ //# sourceMappingURL=otel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.js","sourceRoot":"","sources":["../../src/observability/otel.ts"],"names":[],"mappings":"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"}