@ex7/mcp 0.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 (3) hide show
  1. package/README.md +62 -0
  2. package/dist/index.js +2110 -0
  3. package/package.json +44 -0
package/dist/index.js ADDED
@@ -0,0 +1,2110 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
+
5
+ // src/index.ts
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+
8
+ // ../sdk/src/auth/provider.ts
9
+ function staticToken(token) {
10
+ const value = token && token.length > 0 ? token : null;
11
+ return { getToken: () => value };
12
+ }
13
+ function envToken(name = "EX7_TOKEN") {
14
+ return {
15
+ getToken: () => {
16
+ if (typeof process === "undefined" || !process.env)
17
+ return null;
18
+ const v = process.env[name];
19
+ return v && v.length > 0 ? v : null;
20
+ }
21
+ };
22
+ }
23
+ function chainAuth(...providers) {
24
+ const list = providers.filter((p) => !!p);
25
+ const provider = {
26
+ async getToken(ctx) {
27
+ for (const p of list) {
28
+ const t = await p.getToken(ctx);
29
+ if (t)
30
+ return t;
31
+ }
32
+ return null;
33
+ }
34
+ };
35
+ if (list.some((p) => typeof p.invalidate === "function")) {
36
+ provider.invalidate = async () => {
37
+ for (const p of list)
38
+ await p.invalidate?.();
39
+ };
40
+ }
41
+ return provider;
42
+ }
43
+
44
+ // ../sdk/src/config.ts
45
+ var SDK_VERSION = "0.1.0";
46
+ var DEFAULT_BASE_URL = "https://ex7capital.com";
47
+ var DEFAULT_TIMEOUT_MS = 30000;
48
+ function resolveConfig(config = {}) {
49
+ const baseUrl = (config.baseUrl ?? envBaseUrl() ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
50
+ const auth = config.auth ?? chainAuth(staticToken(config.token), envToken("EX7_TOKEN"));
51
+ const resolved = {
52
+ baseUrl,
53
+ auth,
54
+ timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
55
+ userAgent: config.userAgent ?? `@ex7/sdk/${SDK_VERSION}`
56
+ };
57
+ if (config.agentToken !== undefined)
58
+ resolved.agentToken = config.agentToken;
59
+ if (config.fetch !== undefined)
60
+ resolved.fetch = config.fetch;
61
+ if (config.headers !== undefined)
62
+ resolved.headers = config.headers;
63
+ return resolved;
64
+ }
65
+ function envBaseUrl() {
66
+ if (typeof process === "undefined" || !process.env)
67
+ return;
68
+ return process.env.EX7_API_URL || undefined;
69
+ }
70
+
71
+ // ../sdk/src/errors.ts
72
+ class EX7Error extends Error {
73
+ status;
74
+ code;
75
+ detail;
76
+ requestId;
77
+ constructor(status, code, message, detail, requestId) {
78
+ super(message);
79
+ this.name = "EX7Error";
80
+ this.status = status;
81
+ this.code = code;
82
+ this.detail = detail;
83
+ if (requestId !== undefined)
84
+ this.requestId = requestId;
85
+ Object.setPrototypeOf(this, new.target.prototype);
86
+ }
87
+ }
88
+
89
+ class EX7AuthError extends EX7Error {
90
+ constructor(status, code, message, detail, requestId) {
91
+ super(status, code, message, detail, requestId);
92
+ this.name = "EX7AuthError";
93
+ }
94
+ }
95
+
96
+ class EX7QuotaError extends EX7Error {
97
+ constructor(status, code, message, detail, requestId) {
98
+ super(status, code, message, detail, requestId);
99
+ this.name = "EX7QuotaError";
100
+ }
101
+ }
102
+
103
+ class EX7RateLimitError extends EX7Error {
104
+ retryAfterMs;
105
+ constructor(status, code, message, detail, requestId, retryAfterMs) {
106
+ super(status, code, message, detail, requestId);
107
+ this.name = "EX7RateLimitError";
108
+ if (retryAfterMs !== undefined)
109
+ this.retryAfterMs = retryAfterMs;
110
+ }
111
+ }
112
+
113
+ class EX7ValidationError extends EX7Error {
114
+ issues;
115
+ constructor(status, code, message, issues = [], detail, requestId) {
116
+ super(status, code, message, detail, requestId);
117
+ this.name = "EX7ValidationError";
118
+ this.issues = issues;
119
+ }
120
+ }
121
+
122
+ class EX7NotFoundError extends EX7Error {
123
+ constructor(status, code, message, detail, requestId) {
124
+ super(status, code, message, detail, requestId);
125
+ this.name = "EX7NotFoundError";
126
+ }
127
+ }
128
+
129
+ class EX7GateError extends EX7Error {
130
+ constructor(status, code, message, detail, requestId) {
131
+ super(status, code, message, detail, requestId);
132
+ this.name = "EX7GateError";
133
+ }
134
+ }
135
+
136
+ class EX7NetworkError extends EX7Error {
137
+ constructor(code, message, detail) {
138
+ super(0, code, message, detail);
139
+ this.name = "EX7NetworkError";
140
+ }
141
+ }
142
+ function extractIssues(body) {
143
+ if (Array.isArray(body.issues))
144
+ return body.issues;
145
+ if (body.detail && typeof body.detail === "object" && Array.isArray(body.detail.issues)) {
146
+ return body.detail.issues;
147
+ }
148
+ return [];
149
+ }
150
+ function errorFromStatus(status, body, fallbackMessage, headers) {
151
+ const code = body?.error ?? body?.code ?? (status > 0 ? `http_${status}` : "network_error");
152
+ const message = body?.message ?? body?.reason ?? fallbackMessage;
153
+ const detail = body?.detail ?? body?.details ?? undefined;
154
+ const requestId = headers?.get("x-request-id") ?? undefined;
155
+ switch (status) {
156
+ case 401:
157
+ case 403:
158
+ return new EX7AuthError(status, code, message, detail, requestId);
159
+ case 402:
160
+ return new EX7QuotaError(status, code, message, detail, requestId);
161
+ case 404:
162
+ return new EX7NotFoundError(status, code, message, detail, requestId);
163
+ case 409:
164
+ return new EX7GateError(status, code, message, detail, requestId);
165
+ case 422:
166
+ return new EX7ValidationError(status, code, message, body ? extractIssues(body) : [], detail, requestId);
167
+ case 429: {
168
+ const ra = headers?.get("retry-after");
169
+ const retryAfterMs = ra ? parseRetryAfter(ra) : undefined;
170
+ return new EX7RateLimitError(status, code, message, detail, requestId, retryAfterMs);
171
+ }
172
+ default:
173
+ if (code === "quota_exceeded") {
174
+ return new EX7QuotaError(status, code, message, detail, requestId);
175
+ }
176
+ return new EX7Error(status, code, message, detail, requestId);
177
+ }
178
+ }
179
+ function parseRetryAfter(value) {
180
+ const secs = Number(value);
181
+ if (Number.isFinite(secs))
182
+ return secs * 1000;
183
+ const date = Date.parse(value);
184
+ if (Number.isFinite(date))
185
+ return Math.max(0, date - Date.now());
186
+ return;
187
+ }
188
+ async function parseErrorResponse(res) {
189
+ const fallback = `HTTP ${res.status} ${res.statusText}`;
190
+ const ct = res.headers.get("content-type") ?? "";
191
+ if (ct.includes("application/json")) {
192
+ try {
193
+ const body = await res.json();
194
+ return errorFromStatus(res.status, body, fallback, res.headers);
195
+ } catch {
196
+ return errorFromStatus(res.status, undefined, fallback, res.headers);
197
+ }
198
+ }
199
+ let textDetail;
200
+ try {
201
+ const txt = await res.text();
202
+ if (txt)
203
+ textDetail = txt.slice(0, 1000);
204
+ } catch {}
205
+ return errorFromStatus(res.status, textDetail ? { detail: textDetail } : undefined, fallback, res.headers);
206
+ }
207
+
208
+ // ../sdk/src/http.ts
209
+ class HttpClient {
210
+ cfg;
211
+ constructor(cfg) {
212
+ this.cfg = cfg;
213
+ }
214
+ get baseUrl() {
215
+ return this.cfg.baseUrl;
216
+ }
217
+ getFetch() {
218
+ const f = this.cfg.fetch ?? globalThis.fetch;
219
+ if (!f) {
220
+ throw new EX7NetworkError("no_fetch", "global fetch is unavailable; pass a fetch implementation to createClient");
221
+ }
222
+ return f;
223
+ }
224
+ buildUrl(opts) {
225
+ const base = this.cfg.baseUrl.replace(/\/+$/, "");
226
+ const path = opts.path.replace(/^\/+/, "");
227
+ let url = `${base}/${path}`;
228
+ if (opts.query) {
229
+ const qs = new URLSearchParams;
230
+ for (const [k, v] of Object.entries(opts.query)) {
231
+ if (v === undefined)
232
+ continue;
233
+ qs.set(k, String(v));
234
+ }
235
+ const s = qs.toString();
236
+ if (s)
237
+ url += `?${s}`;
238
+ }
239
+ return url;
240
+ }
241
+ async buildHeaders(opts, accept) {
242
+ const h = {
243
+ accept,
244
+ "user-agent": this.cfg.userAgent,
245
+ ...this.cfg.defaultHeaders ?? {},
246
+ ...opts.headers ?? {}
247
+ };
248
+ if (opts.body !== undefined && !h["content-type"]) {
249
+ h["content-type"] = "application/json";
250
+ }
251
+ const mode = opts.auth ?? "bearer";
252
+ if (mode === "agent") {
253
+ if (!this.cfg.agentToken) {
254
+ throw new EX7Error(0, "no_agent_token", "agent auth was requested but no agentToken is configured on the client");
255
+ }
256
+ h["x-ex7-agent-id"] = this.cfg.agentToken;
257
+ } else if (mode === "bearer") {
258
+ const token = await this.cfg.auth.getToken({ baseUrl: this.cfg.baseUrl });
259
+ if (token)
260
+ h["authorization"] = `Bearer ${token}`;
261
+ }
262
+ return h;
263
+ }
264
+ async json(opts) {
265
+ const mode = opts.auth ?? "bearer";
266
+ let res = await this.fetchOnce(opts, "application/json");
267
+ if (res.status === 401 && mode === "bearer" && this.cfg.auth.invalidate) {
268
+ await this.cfg.auth.invalidate();
269
+ res = await this.fetchOnce(opts, "application/json");
270
+ }
271
+ if (!res.ok)
272
+ throw await parseErrorResponse(res);
273
+ const ct = res.headers.get("content-type") ?? "";
274
+ if (ct.includes("application/json")) {
275
+ return await res.json();
276
+ }
277
+ return await res.text();
278
+ }
279
+ async fetchOnce(opts, accept) {
280
+ const timeoutMs = opts.timeoutMs ?? this.cfg.timeoutMs;
281
+ const timeoutCtrl = new AbortController;
282
+ const timer = timeoutMs > 0 ? setTimeout(() => timeoutCtrl.abort(new Error(`timeout ${timeoutMs}ms`)), timeoutMs) : null;
283
+ const url = this.buildUrl(opts);
284
+ const headers = await this.buildHeaders(opts, accept);
285
+ const fetchFn = this.getFetch();
286
+ try {
287
+ return await fetchFn(url, {
288
+ method: opts.method ?? "GET",
289
+ headers,
290
+ body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
291
+ signal: composeSignals(opts.signal, timeoutCtrl.signal)
292
+ });
293
+ } catch (err) {
294
+ const msg = err instanceof Error ? err.message : String(err);
295
+ throw new EX7NetworkError("network_error", `cannot reach ${url}: ${msg}`);
296
+ } finally {
297
+ if (timer)
298
+ clearTimeout(timer);
299
+ }
300
+ }
301
+ async* sse(opts) {
302
+ const url = this.buildUrl(opts);
303
+ const headers = await this.buildHeaders(opts, "text/event-stream");
304
+ const fetchFn = this.getFetch();
305
+ let res;
306
+ try {
307
+ res = await fetchFn(url, {
308
+ method: opts.method ?? "GET",
309
+ headers,
310
+ body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
311
+ signal: opts.signal
312
+ });
313
+ } catch (err) {
314
+ const msg = err instanceof Error ? err.message : String(err);
315
+ throw new EX7NetworkError("network_error", `cannot reach ${url}: ${msg}`);
316
+ }
317
+ if (!res.ok)
318
+ throw await parseErrorResponse(res);
319
+ if (!res.body) {
320
+ throw new EX7Error(res.status, "no_body", "SSE response has no body");
321
+ }
322
+ const reader = res.body.getReader();
323
+ const decoder = new TextDecoder("utf-8");
324
+ let buf = "";
325
+ while (true) {
326
+ const { value, done } = await reader.read();
327
+ if (done)
328
+ break;
329
+ buf += decoder.decode(value, { stream: true });
330
+ let idx;
331
+ while ((idx = buf.search(/\r?\n\r?\n/)) !== -1) {
332
+ const block = buf.slice(0, idx);
333
+ const match = buf.slice(idx).match(/^\r?\n\r?\n/);
334
+ buf = buf.slice(idx + (match?.[0].length ?? 2));
335
+ const ev = parseSseBlock(block);
336
+ if (ev)
337
+ yield ev;
338
+ }
339
+ }
340
+ }
341
+ }
342
+ function composeSignals(a, b) {
343
+ if (!a)
344
+ return b;
345
+ const ctrl = new AbortController;
346
+ const onAbort = () => ctrl.abort();
347
+ a.addEventListener("abort", onAbort, { once: true });
348
+ b.addEventListener("abort", onAbort, { once: true });
349
+ return ctrl.signal;
350
+ }
351
+ function parseSseBlock(block) {
352
+ if (!block.trim())
353
+ return null;
354
+ let event = null;
355
+ const dataLines = [];
356
+ for (const line of block.split(/\r?\n/)) {
357
+ if (line.startsWith(":"))
358
+ continue;
359
+ const colon = line.indexOf(":");
360
+ const field = colon === -1 ? line : line.slice(0, colon);
361
+ const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /, "");
362
+ if (field === "event")
363
+ event = value;
364
+ else if (field === "data")
365
+ dataLines.push(value);
366
+ }
367
+ if (dataLines.length === 0 && event === null)
368
+ return null;
369
+ const raw = dataLines.join(`
370
+ `);
371
+ let data = raw;
372
+ if (raw.length > 0 && (raw.startsWith("{") || raw.startsWith("["))) {
373
+ try {
374
+ data = JSON.parse(raw);
375
+ } catch {}
376
+ }
377
+ return { event, data, raw };
378
+ }
379
+
380
+ // ../sdk/src/graph/schema.ts
381
+ var BUCKET_OF = {
382
+ indicator: "indicator",
383
+ analysis: "analysis",
384
+ filter: "filter",
385
+ order: "order",
386
+ bollinger_bands: "indicator",
387
+ macd: "indicator",
388
+ stochastic: "indicator",
389
+ vwap_node: "indicator",
390
+ delta_divergence: "analysis",
391
+ time_of_day_filter: "filter",
392
+ volatility_regime_filter: "filter",
393
+ trailing_stop_order: "order",
394
+ breakeven_stop: "order",
395
+ partial_exit: "order",
396
+ hedge_order: "order"
397
+ };
398
+ var ALL_GRAPH_NODE_TYPES = Object.keys(BUCKET_OF);
399
+ var GRAPH_VERSION = 1;
400
+
401
+ // ../sdk/src/graph/validator.ts
402
+ var MAX_NODES = 500;
403
+ var MAX_EDGES = 1000;
404
+ var MAX_BYTES = 1 * 1024 * 1024;
405
+ var VALID_NODE_TYPES = new Set(ALL_GRAPH_NODE_TYPES);
406
+ var BUCKET_COMPATIBILITY = {
407
+ indicator: new Set(["indicator", "analysis", "filter", "order"]),
408
+ analysis: new Set(["analysis", "filter", "order"]),
409
+ filter: new Set(["filter", "order"]),
410
+ order: new Set(["order"])
411
+ };
412
+ function validateStrategyGraph(input) {
413
+ const issues = [];
414
+ if (typeof input === "string" && input.length > MAX_BYTES) {
415
+ return fail([
416
+ {
417
+ path: "$",
418
+ expected: `≤ ${MAX_BYTES} bytes`,
419
+ got: String(input.length),
420
+ message: `Strategy graph is too large: ${input.length} bytes (cap ${MAX_BYTES} = 1 MiB)`
421
+ }
422
+ ]);
423
+ }
424
+ let raw = input;
425
+ if (typeof input === "string") {
426
+ try {
427
+ raw = JSON.parse(input);
428
+ } catch (e) {
429
+ const msg = e instanceof Error ? e.message : String(e);
430
+ return fail([
431
+ {
432
+ path: "$",
433
+ expected: "valid JSON",
434
+ got: msg,
435
+ message: `Strategy graph is not valid JSON: ${msg}`
436
+ }
437
+ ]);
438
+ }
439
+ }
440
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
441
+ return fail([
442
+ {
443
+ path: "$",
444
+ expected: "JSON object",
445
+ got: Array.isArray(raw) ? "array" : typeof raw,
446
+ message: "Strategy graph must be a JSON object"
447
+ }
448
+ ]);
449
+ }
450
+ const obj = raw;
451
+ if (obj.version !== undefined && obj.version !== GRAPH_VERSION) {
452
+ return fail([
453
+ {
454
+ path: "version",
455
+ expected: String(GRAPH_VERSION),
456
+ got: JSON.stringify(obj.version),
457
+ message: `Graph version must be ${GRAPH_VERSION}, got ${JSON.stringify(obj.version)} (this client may be outdated)`
458
+ }
459
+ ]);
460
+ }
461
+ if (!Array.isArray(obj.nodes)) {
462
+ return fail([
463
+ {
464
+ path: "nodes",
465
+ expected: "array",
466
+ got: typeof obj.nodes,
467
+ message: "`nodes` must be an array of node objects"
468
+ }
469
+ ]);
470
+ }
471
+ if (!Array.isArray(obj.edges)) {
472
+ return fail([
473
+ {
474
+ path: "edges",
475
+ expected: "array",
476
+ got: typeof obj.edges,
477
+ message: "`edges` must be an array of edge objects"
478
+ }
479
+ ]);
480
+ }
481
+ if (obj.nodes.length > MAX_NODES) {
482
+ issues.push({
483
+ path: "nodes",
484
+ expected: `≤ ${MAX_NODES}`,
485
+ got: String(obj.nodes.length),
486
+ message: `Too many nodes: ${obj.nodes.length} exceeds cap of ${MAX_NODES}`
487
+ });
488
+ }
489
+ if (obj.edges.length > MAX_EDGES) {
490
+ issues.push({
491
+ path: "edges",
492
+ expected: `≤ ${MAX_EDGES}`,
493
+ got: String(obj.edges.length),
494
+ message: `Too many edges: ${obj.edges.length} exceeds cap of ${MAX_EDGES}`
495
+ });
496
+ }
497
+ if (issues.length > 0)
498
+ return fail(issues);
499
+ const nodes = [];
500
+ const seenNodeIds = new Set;
501
+ const nodeTypeById = new Map;
502
+ for (let i = 0;i < obj.nodes.length; i++) {
503
+ const r = validateNode(obj.nodes[i], i);
504
+ if (r.issue) {
505
+ issues.push(r.issue);
506
+ continue;
507
+ }
508
+ if (seenNodeIds.has(r.node.id)) {
509
+ issues.push({
510
+ path: `nodes[${i}].id`,
511
+ node_id: r.node.id,
512
+ expected: "unique within graph",
513
+ got: JSON.stringify(r.node.id),
514
+ message: `Node id '${r.node.id}' appears twice; ids must be unique`
515
+ });
516
+ continue;
517
+ }
518
+ seenNodeIds.add(r.node.id);
519
+ nodeTypeById.set(r.node.id, r.node.type);
520
+ nodes.push(r.node);
521
+ }
522
+ const edges = [];
523
+ const seenEdgeIds = new Set;
524
+ const adjacency = new Map;
525
+ for (let i = 0;i < obj.edges.length; i++) {
526
+ const r = validateEdge(obj.edges[i], i, nodeTypeById);
527
+ if (r.issue) {
528
+ issues.push(r.issue);
529
+ continue;
530
+ }
531
+ if (seenEdgeIds.has(r.edge.id)) {
532
+ issues.push({
533
+ path: `edges[${i}].id`,
534
+ expected: "unique within graph",
535
+ got: JSON.stringify(r.edge.id),
536
+ message: `Edge id '${r.edge.id}' appears twice; ids must be unique`
537
+ });
538
+ continue;
539
+ }
540
+ seenEdgeIds.add(r.edge.id);
541
+ edges.push(r.edge);
542
+ const outList = adjacency.get(r.edge.source) ?? [];
543
+ outList.push(r.edge.target);
544
+ adjacency.set(r.edge.source, outList);
545
+ }
546
+ if (edges.length > 0 && nodes.length > 0) {
547
+ const cycle = findCycle(nodes, adjacency);
548
+ if (cycle) {
549
+ issues.push({
550
+ path: "$",
551
+ expected: "directed acyclic graph",
552
+ got: cycle.join(" → "),
553
+ message: `Cycle detected: ${cycle.join(" → ")}. Strategy graphs must be DAGs.`
554
+ });
555
+ }
556
+ }
557
+ if (issues.length > 0)
558
+ return fail(issues);
559
+ return { ok: true, graph: { nodes, edges, version: GRAPH_VERSION } };
560
+ }
561
+ function extractGraphFromBody(body) {
562
+ if (body && typeof body === "object" && !Array.isArray(body)) {
563
+ const obj = body;
564
+ if ("graph_json" in obj)
565
+ return obj.graph_json;
566
+ }
567
+ return body;
568
+ }
569
+ function fail(issues) {
570
+ return { ok: false, errors: issues.map((i) => i.message), issues };
571
+ }
572
+ function validateNode(cand, i) {
573
+ const at = `nodes[${i}]`;
574
+ if (!cand || typeof cand !== "object" || Array.isArray(cand)) {
575
+ return {
576
+ issue: {
577
+ path: at,
578
+ expected: "object",
579
+ got: Array.isArray(cand) ? "array" : typeof cand,
580
+ message: `Node at index ${i} must be an object`
581
+ },
582
+ node: {}
583
+ };
584
+ }
585
+ const c = cand;
586
+ if (typeof c.id !== "string" || c.id.length === 0) {
587
+ return {
588
+ issue: {
589
+ path: `${at}.id`,
590
+ expected: "non-empty string",
591
+ got: typeof c.id === "string" ? '""' : typeof c.id,
592
+ message: `Node at index ${i} is missing 'id'`
593
+ },
594
+ node: {}
595
+ };
596
+ }
597
+ if (typeof c.type !== "string" || !VALID_NODE_TYPES.has(c.type)) {
598
+ return {
599
+ issue: {
600
+ path: `${at}.type`,
601
+ node_id: c.id,
602
+ expected: `one of: ${ALL_GRAPH_NODE_TYPES.join(", ")}`,
603
+ got: JSON.stringify(c.type),
604
+ message: `Node '${c.id}' has unknown type ${JSON.stringify(c.type)}. Valid: ${ALL_GRAPH_NODE_TYPES.join(", ")}`
605
+ },
606
+ node: {}
607
+ };
608
+ }
609
+ if (typeof c.subtype !== "string" || c.subtype.length === 0) {
610
+ return {
611
+ issue: {
612
+ path: `${at}.subtype`,
613
+ node_id: c.id,
614
+ expected: "non-empty string",
615
+ got: typeof c.subtype === "string" ? '""' : typeof c.subtype,
616
+ message: `Node '${c.id}' is missing 'subtype'`
617
+ },
618
+ node: {}
619
+ };
620
+ }
621
+ const pos = c.position;
622
+ if (!pos || typeof pos !== "object" || Array.isArray(pos) || typeof pos.x !== "number" || typeof pos.y !== "number" || !Number.isFinite(pos.x) || !Number.isFinite(pos.y)) {
623
+ return {
624
+ issue: {
625
+ path: `${at}.position`,
626
+ node_id: c.id,
627
+ expected: "{ x: finite number, y: finite number }",
628
+ got: JSON.stringify(pos),
629
+ message: `Node '${c.id}' position must be { x, y } with finite numbers`
630
+ },
631
+ node: {}
632
+ };
633
+ }
634
+ if (!c.params || typeof c.params !== "object" || Array.isArray(c.params)) {
635
+ return {
636
+ issue: {
637
+ path: `${at}.params`,
638
+ node_id: c.id,
639
+ expected: "plain object",
640
+ got: Array.isArray(c.params) ? "array" : typeof c.params,
641
+ message: `Node '${c.id}' params must be a plain object`
642
+ },
643
+ node: {}
644
+ };
645
+ }
646
+ return {
647
+ node: {
648
+ id: c.id,
649
+ type: c.type,
650
+ subtype: c.subtype,
651
+ position: { x: pos.x, y: pos.y },
652
+ params: c.params
653
+ }
654
+ };
655
+ }
656
+ function validateEdge(cand, i, nodeTypeById) {
657
+ const at = `edges[${i}]`;
658
+ if (!cand || typeof cand !== "object" || Array.isArray(cand)) {
659
+ return {
660
+ issue: {
661
+ path: at,
662
+ expected: "object",
663
+ got: Array.isArray(cand) ? "array" : typeof cand,
664
+ message: `Edge at index ${i} must be an object`
665
+ },
666
+ edge: {}
667
+ };
668
+ }
669
+ const c = cand;
670
+ if (typeof c.id !== "string" || c.id.length === 0) {
671
+ return {
672
+ issue: {
673
+ path: `${at}.id`,
674
+ expected: "non-empty string",
675
+ got: typeof c.id === "string" ? '""' : typeof c.id,
676
+ message: `Edge at index ${i} is missing 'id'`
677
+ },
678
+ edge: {}
679
+ };
680
+ }
681
+ if (typeof c.source !== "string" || c.source.length === 0) {
682
+ return {
683
+ issue: {
684
+ path: `${at}.source`,
685
+ expected: "non-empty string",
686
+ got: typeof c.source === "string" ? '""' : typeof c.source,
687
+ message: `Edge '${c.id}' is missing 'source'`
688
+ },
689
+ edge: {}
690
+ };
691
+ }
692
+ if (typeof c.target !== "string" || c.target.length === 0) {
693
+ return {
694
+ issue: {
695
+ path: `${at}.target`,
696
+ expected: "non-empty string",
697
+ got: typeof c.target === "string" ? '""' : typeof c.target,
698
+ message: `Edge '${c.id}' is missing 'target'`
699
+ },
700
+ edge: {}
701
+ };
702
+ }
703
+ if (c.source === c.target) {
704
+ return {
705
+ issue: {
706
+ path: at,
707
+ expected: "source ≠ target",
708
+ got: `${c.source} → ${c.source}`,
709
+ message: `Edge '${c.id}' is a self-loop on '${c.source}'; self-loops are not allowed`
710
+ },
711
+ edge: {}
712
+ };
713
+ }
714
+ const sType = nodeTypeById.get(c.source);
715
+ if (!sType) {
716
+ return {
717
+ issue: {
718
+ path: `${at}.source`,
719
+ expected: "id of a node in the graph",
720
+ got: JSON.stringify(c.source),
721
+ message: `Edge '${c.id}' source '${c.source}' is not a node in the graph`
722
+ },
723
+ edge: {}
724
+ };
725
+ }
726
+ const tType = nodeTypeById.get(c.target);
727
+ if (!tType) {
728
+ return {
729
+ issue: {
730
+ path: `${at}.target`,
731
+ expected: "id of a node in the graph",
732
+ got: JSON.stringify(c.target),
733
+ message: `Edge '${c.id}' target '${c.target}' is not a node in the graph`
734
+ },
735
+ edge: {}
736
+ };
737
+ }
738
+ const srcBucket = BUCKET_OF[sType];
739
+ const tgtBucket = BUCKET_OF[tType];
740
+ const allowed = BUCKET_COMPATIBILITY[srcBucket];
741
+ if (!allowed.has(tgtBucket)) {
742
+ const allowedList = Array.from(allowed).join(", ");
743
+ return {
744
+ issue: {
745
+ path: at,
746
+ expected: `${srcBucket} → one of: ${allowedList}`,
747
+ got: `${srcBucket} → ${tgtBucket}`,
748
+ message: `Edge '${c.id}' connects ${srcBucket} '${c.source}' to ${tgtBucket} '${c.target}'. Pipeline order is indicator → analysis → filter → order; allowed from ${srcBucket}: ${allowedList}.`
749
+ },
750
+ edge: {}
751
+ };
752
+ }
753
+ const out = { id: c.id, source: c.source, target: c.target };
754
+ if (typeof c.sourceHandle === "string")
755
+ out.sourceHandle = c.sourceHandle;
756
+ if (typeof c.targetHandle === "string")
757
+ out.targetHandle = c.targetHandle;
758
+ return { edge: out };
759
+ }
760
+ function findCycle(nodes, adjacency) {
761
+ const colour = new Map;
762
+ for (const n of nodes)
763
+ colour.set(n.id, 0);
764
+ const stack = [];
765
+ let cycle = null;
766
+ function dfs(id) {
767
+ colour.set(id, 1);
768
+ stack.push(id);
769
+ const out = adjacency.get(id) ?? [];
770
+ for (const next of out) {
771
+ const c = colour.get(next) ?? 0;
772
+ if (c === 1) {
773
+ const idx = stack.indexOf(next);
774
+ cycle = stack.slice(idx).concat(next);
775
+ return true;
776
+ }
777
+ if (c === 0 && dfs(next))
778
+ return true;
779
+ }
780
+ stack.pop();
781
+ colour.set(id, 2);
782
+ return false;
783
+ }
784
+ for (const n of nodes) {
785
+ if (colour.get(n.id) === 0) {
786
+ if (dfs(n.id))
787
+ return cycle;
788
+ }
789
+ }
790
+ return null;
791
+ }
792
+
793
+ // ../sdk/src/resources/strategies.ts
794
+ var TERMINAL = new Set(["done", "error"]);
795
+
796
+ class StrategiesResource {
797
+ http;
798
+ constructor(http) {
799
+ this.http = http;
800
+ }
801
+ list(params = {}, o = {}) {
802
+ return this.http.json({
803
+ path: "api/backend/strategies",
804
+ query: {
805
+ limit: params.limit,
806
+ cursor: params.cursor,
807
+ instrument: params.instrument,
808
+ is_published: params.is_published,
809
+ q: params.q
810
+ },
811
+ ...o
812
+ });
813
+ }
814
+ async* listAll(params = {}, o = {}) {
815
+ let cursor = undefined;
816
+ do {
817
+ const page = await this.list({ ...params, cursor: cursor ?? undefined }, o);
818
+ for (const s of page.strategies)
819
+ yield s;
820
+ cursor = page.next_cursor;
821
+ } while (cursor);
822
+ }
823
+ get(id, o = {}) {
824
+ return this.http.json({
825
+ path: `api/backend/strategies/${encodeURIComponent(id)}`,
826
+ ...o
827
+ });
828
+ }
829
+ async create(input, o = {}) {
830
+ const { validate = true, ...callOpts } = o;
831
+ if (validate) {
832
+ const v = validateStrategyGraph(input.graph_json);
833
+ if (!v.ok) {
834
+ throw new EX7ValidationError(0, "invalid_graph", `graph failed validation: ${v.issues.length} issue(s)`, v.issues);
835
+ }
836
+ }
837
+ return this.http.json({
838
+ path: "api/backend/strategies",
839
+ method: "POST",
840
+ body: input,
841
+ ...callOpts
842
+ });
843
+ }
844
+ async delete(id, o = {}) {
845
+ await this.http.json({
846
+ path: `api/backend/strategies/${encodeURIComponent(id)}`,
847
+ method: "DELETE",
848
+ ...o
849
+ });
850
+ return { ok: true, id };
851
+ }
852
+ export(id, o = {}) {
853
+ return this.http.json({
854
+ path: `api/backend/strategies/${encodeURIComponent(id)}/export`,
855
+ ...o
856
+ });
857
+ }
858
+ async judge(id, o = {}) {
859
+ const path = `api/backend/strategies/${encodeURIComponent(id)}/judge`;
860
+ const maxTries = o.maxTries ?? 30;
861
+ const { signal, timeoutMs } = o;
862
+ let run = await this.http.json({ path, method: "POST", signal, timeoutMs });
863
+ o.onTick?.(run);
864
+ for (let i = 0;i < maxTries && !TERMINAL.has(run.status); i++) {
865
+ await sleep(o.intervalMs ?? Math.min(1000 + i * 250, 4000));
866
+ run = await this.http.json({ path, method: "GET", signal, timeoutMs });
867
+ o.onTick?.(run);
868
+ }
869
+ return run;
870
+ }
871
+ publish(id, o = {}) {
872
+ return this.setPublished(id, true, o);
873
+ }
874
+ unpublish(id, o = {}) {
875
+ return this.setPublished(id, false, o);
876
+ }
877
+ setPublished(id, publish, o = {}) {
878
+ return this.http.json({
879
+ path: `api/backend/strategies/${encodeURIComponent(id)}/${publish ? "publish" : "unpublish"}`,
880
+ method: "POST",
881
+ ...o
882
+ });
883
+ }
884
+ deployPlan(id, input = {}, o = {}) {
885
+ return this.http.json({
886
+ path: `api/backend/strategies/${encodeURIComponent(id)}/deploy-plan`,
887
+ method: "POST",
888
+ body: {
889
+ max_contracts: input.maxContracts ?? 1,
890
+ dd_limit: input.ddLimit ?? 2000,
891
+ single_day_bust_safe: input.bustSafe ?? false,
892
+ allow_promising: input.allowPromising ?? false
893
+ },
894
+ ...o
895
+ });
896
+ }
897
+ }
898
+ function sleep(ms) {
899
+ return new Promise((r) => setTimeout(r, ms));
900
+ }
901
+
902
+ // ../sdk/src/resources/judge.ts
903
+ var TERMINAL2 = new Set(["done", "error", "failed"]);
904
+
905
+ class JudgeResource {
906
+ http;
907
+ constructor(http) {
908
+ this.http = http;
909
+ }
910
+ async* run(strategyId, o = {}) {
911
+ const metrics = {};
912
+ let verdict;
913
+ let tier;
914
+ let snapshot;
915
+ const stream = this.http.sse({
916
+ path: `api/judge/run/${encodeURIComponent(strategyId)}`,
917
+ method: "POST",
918
+ timeoutMs: 0,
919
+ ...o.signal ? { signal: o.signal } : {}
920
+ });
921
+ for await (const ev of stream) {
922
+ const data = ev.data && typeof ev.data === "object" ? ev.data : {};
923
+ if (data.metric && typeof data.metric.value === "number") {
924
+ metrics[data.metric.key] = data.metric.value;
925
+ }
926
+ if (data.verdict)
927
+ verdict = data.verdict;
928
+ if (data.tier)
929
+ tier = data.tier;
930
+ if (data.snapshot)
931
+ snapshot = data.snapshot;
932
+ yield data;
933
+ }
934
+ return buildSummary(verdict, tier, snapshot, metrics);
935
+ }
936
+ async runToEnd(strategyId, o = {}) {
937
+ const gen = this.run(strategyId, o);
938
+ let next = await gen.next();
939
+ while (!next.done) {
940
+ o.onPhase?.(next.value);
941
+ next = await gen.next();
942
+ }
943
+ return next.value;
944
+ }
945
+ runs = {
946
+ create: (graph, o = {}) => this.http.json({
947
+ path: "api/judge/runs",
948
+ method: "POST",
949
+ body: { graph },
950
+ ...o
951
+ }),
952
+ get: (runId, o = {}) => this.http.json({
953
+ path: `api/judge/runs/${encodeURIComponent(runId)}`,
954
+ ...o
955
+ })
956
+ };
957
+ async runFromGraph(graph, o = {}) {
958
+ const { signal, timeoutMs } = o;
959
+ const sub = await this.runs.create(graph, { signal, timeoutMs });
960
+ let run = sub;
961
+ o.onTick?.({ run_id: run.run_id, status: run.status });
962
+ const maxTries = o.maxTries ?? 30;
963
+ for (let i = 0;i < maxTries && !TERMINAL2.has(run.status); i++) {
964
+ await sleep2(o.intervalMs ?? Math.min(1000 + i * 250, 4000));
965
+ run = await this.runs.get(sub.run_id, { signal, timeoutMs });
966
+ o.onTick?.({ run_id: run.run_id, status: run.status });
967
+ }
968
+ return run;
969
+ }
970
+ health(o = {}) {
971
+ return this.http.json({ path: "api/judge/health", auth: "none", ...o });
972
+ }
973
+ }
974
+ function buildSummary(verdict, tier, snapshot, metrics) {
975
+ const summary = { metrics };
976
+ if (verdict)
977
+ summary.verdict = verdict;
978
+ if (tier)
979
+ summary.tier = tier;
980
+ if (snapshot)
981
+ summary.snapshot = snapshot;
982
+ return summary;
983
+ }
984
+ function sleep2(ms) {
985
+ return new Promise((r) => setTimeout(r, ms));
986
+ }
987
+
988
+ // ../sdk/src/resources/vix.ts
989
+ class VixResource {
990
+ http;
991
+ constructor(http) {
992
+ this.http = http;
993
+ }
994
+ generate(input, o = {}) {
995
+ return this.http.json({
996
+ path: "api/backend/vix/generate",
997
+ method: "POST",
998
+ body: {
999
+ prompt: input.prompt,
1000
+ ...input.currentGraph ? { current_graph: input.currentGraph } : {},
1001
+ ...input.tone ? { tone: input.tone } : {}
1002
+ },
1003
+ ...o
1004
+ });
1005
+ }
1006
+ generateStrategy(input, o = {}) {
1007
+ return this.http.json({
1008
+ path: "api/vix/generate-strategy",
1009
+ method: "POST",
1010
+ body: {
1011
+ prompt: input.prompt,
1012
+ ...input.instrument ? { instrument: input.instrument } : {},
1013
+ ...input.timeframe ? { timeframe: input.timeframe } : {}
1014
+ },
1015
+ ...o
1016
+ });
1017
+ }
1018
+ quota(o = {}) {
1019
+ return this.http.json({ path: "api/vix/quota", ...o });
1020
+ }
1021
+ }
1022
+
1023
+ // ../sdk/src/resources/backtest.ts
1024
+ var TERMINAL3 = new Set(["succeeded", "failed", "cancelled"]);
1025
+
1026
+ class BacktestResource {
1027
+ http;
1028
+ constructor(http) {
1029
+ this.http = http;
1030
+ }
1031
+ async run(strategyId, input, o = {}) {
1032
+ const res = await this.http.json({
1033
+ path: `api/backend/strategies/${encodeURIComponent(strategyId)}/backtest`,
1034
+ method: "POST",
1035
+ body: {
1036
+ start_date: input.from,
1037
+ end_date: input.to,
1038
+ ...input.instrument ? { instrument: input.instrument } : {}
1039
+ },
1040
+ ...o
1041
+ });
1042
+ return res.job;
1043
+ }
1044
+ async list(strategyId, params = {}, o = {}) {
1045
+ const res = await this.http.json({
1046
+ path: `api/backend/strategies/${encodeURIComponent(strategyId)}/backtest`,
1047
+ query: { limit: params.limit },
1048
+ ...o
1049
+ });
1050
+ return res.jobs;
1051
+ }
1052
+ async get(strategyId, jobId, o = {}) {
1053
+ const res = await this.http.json({
1054
+ path: `api/backend/strategies/${encodeURIComponent(strategyId)}/backtest/${encodeURIComponent(jobId)}`,
1055
+ ...o
1056
+ });
1057
+ return res.job;
1058
+ }
1059
+ async cancel(strategyId, jobId, o = {}) {
1060
+ const res = await this.http.json({
1061
+ path: `api/backend/strategies/${encodeURIComponent(strategyId)}/backtest/${encodeURIComponent(jobId)}`,
1062
+ method: "DELETE",
1063
+ ...o
1064
+ });
1065
+ return res.job;
1066
+ }
1067
+ async waitFor(strategyId, jobId, o = {}) {
1068
+ const { signal } = o;
1069
+ const maxWaitMs = o.maxWaitMs ?? 30 * 60000;
1070
+ const started = Date.now();
1071
+ let delay = o.intervalMs ?? 1500;
1072
+ let job = await this.get(strategyId, jobId, { signal });
1073
+ o.onTick?.(job);
1074
+ while (!TERMINAL3.has(job.status)) {
1075
+ if (Date.now() - started > maxWaitMs) {
1076
+ throw new EX7Error(0, "backtest_timeout", `backtest ${jobId} did not finish within ${maxWaitMs}ms`);
1077
+ }
1078
+ await sleep3(delay, signal);
1079
+ delay = Math.min(Math.round(delay * 1.4), 1e4);
1080
+ job = await this.get(strategyId, jobId, { signal });
1081
+ o.onTick?.(job);
1082
+ }
1083
+ if (job.status !== "succeeded") {
1084
+ throw new EX7Error(0, `backtest_${job.status}`, job.error_message ?? `backtest ${jobId} ${job.status}`, job);
1085
+ }
1086
+ return job;
1087
+ }
1088
+ async runAndWait(strategyId, input, o = {}) {
1089
+ const job = await this.run(strategyId, input, { signal: o.signal });
1090
+ return this.waitFor(strategyId, job.id, o);
1091
+ }
1092
+ }
1093
+ function sleep3(ms, signal) {
1094
+ return new Promise((resolve, reject) => {
1095
+ if (signal?.aborted)
1096
+ return reject(new Error("aborted"));
1097
+ const t = setTimeout(resolve, ms);
1098
+ signal?.addEventListener("abort", () => {
1099
+ clearTimeout(t);
1100
+ reject(new Error("aborted"));
1101
+ }, { once: true });
1102
+ });
1103
+ }
1104
+
1105
+ // ../sdk/src/resources/signals.ts
1106
+ class SignalsResource {
1107
+ http;
1108
+ constructor(http) {
1109
+ this.http = http;
1110
+ }
1111
+ list(params = {}, o = {}) {
1112
+ return this.http.json({
1113
+ path: "api/backend/signals",
1114
+ query: { limit: params.limit },
1115
+ ...o
1116
+ });
1117
+ }
1118
+ async* watch(o = {}) {
1119
+ const reconnect = o.reconnect ?? true;
1120
+ const minMs = o.backoff?.minMs ?? 1000;
1121
+ const maxMs = o.backoff?.maxMs ?? 30000;
1122
+ let delay = minMs;
1123
+ for (;; ) {
1124
+ try {
1125
+ const stream = this.http.sse({
1126
+ path: "api/backend/signals/stream",
1127
+ timeoutMs: 0,
1128
+ ...o.signal ? { signal: o.signal } : {}
1129
+ });
1130
+ for await (const ev of stream) {
1131
+ if (ev.data && typeof ev.data === "object") {
1132
+ yield ev.data;
1133
+ }
1134
+ delay = minMs;
1135
+ }
1136
+ } catch (err) {
1137
+ if (!reconnect || o.signal?.aborted)
1138
+ throw err;
1139
+ }
1140
+ if (!reconnect || o.signal?.aborted)
1141
+ return;
1142
+ await sleep4(delay, o.signal);
1143
+ delay = Math.min(delay * 2, maxMs);
1144
+ }
1145
+ }
1146
+ }
1147
+ function sleep4(ms, signal) {
1148
+ return new Promise((resolve) => {
1149
+ const t = setTimeout(resolve, ms);
1150
+ signal?.addEventListener("abort", () => {
1151
+ clearTimeout(t);
1152
+ resolve();
1153
+ }, { once: true });
1154
+ });
1155
+ }
1156
+
1157
+ // ../sdk/src/resources/agents.ts
1158
+ class AgentsResource {
1159
+ http;
1160
+ constructor(http) {
1161
+ this.http = http;
1162
+ }
1163
+ spawn(input, o = {}) {
1164
+ return this.http.json({
1165
+ path: "api/agent/owner/spawn",
1166
+ method: "POST",
1167
+ body: {
1168
+ orchestrator_provider: input.orchestratorProvider,
1169
+ scopes: input.scopes,
1170
+ ...input.ttlDays !== undefined ? { ttl_days: input.ttlDays } : {},
1171
+ ...input.displayName !== undefined ? { display_name: input.displayName } : {},
1172
+ ...input.orchestratorExternalId !== undefined ? { orchestrator_external_id: input.orchestratorExternalId } : {},
1173
+ ...input.dailySpendingLimitCents !== undefined ? { daily_spending_limit_cents: input.dailySpendingLimitCents } : {}
1174
+ },
1175
+ ...o
1176
+ });
1177
+ }
1178
+ list(o = {}) {
1179
+ return this.http.json({ path: "api/agent/owner/agents", ...o });
1180
+ }
1181
+ revoke(agentId, o = {}) {
1182
+ return this.http.json({
1183
+ path: "api/agent/owner/revoke",
1184
+ method: "POST",
1185
+ body: { agent_id: agentId },
1186
+ ...o
1187
+ });
1188
+ }
1189
+ }
1190
+
1191
+ // ../sdk/src/resources/agent.ts
1192
+ class AgentScopedResource {
1193
+ http;
1194
+ constructor(http) {
1195
+ this.http = http;
1196
+ }
1197
+ vix(prompt, o = {}) {
1198
+ return this.http.json({
1199
+ path: "api/agent/vix/generate",
1200
+ method: "POST",
1201
+ body: { prompt },
1202
+ auth: "agent",
1203
+ ...o
1204
+ });
1205
+ }
1206
+ judge(strategyId, o = {}) {
1207
+ return this.http.json({
1208
+ path: "api/agent/judge/run",
1209
+ method: "POST",
1210
+ body: { strategy_id: strategyId },
1211
+ auth: "agent",
1212
+ ...o
1213
+ });
1214
+ }
1215
+ }
1216
+
1217
+ // ../sdk/src/graph/node-catalog.ts
1218
+ var NODE_CATALOG = {
1219
+ rsi: { type: "indicator", defaultParams: { period: 14 } },
1220
+ ema: { type: "indicator", defaultParams: { period: 20 } },
1221
+ vwap: { type: "indicator", defaultParams: { anchor: "session" } },
1222
+ bollinger: { type: "indicator", defaultParams: { period: 20, stdev: 2 } },
1223
+ zscore: { type: "indicator", defaultParams: { period: 20 } },
1224
+ atr: { type: "indicator", defaultParams: { period: 14 } },
1225
+ volume: { type: "indicator", defaultParams: { mode: "raw" } },
1226
+ bollinger_bands: { type: "bollinger_bands", defaultParams: { period: 20, stdev: 2 } },
1227
+ macd: { type: "macd", defaultParams: { fast: 12, slow: 26, signal: 9 } },
1228
+ stochastic: { type: "stochastic", defaultParams: { k_period: 14, d_period: 3, smoothing: 3 } },
1229
+ vwap_node: { type: "vwap_node", defaultParams: { anchor: "session", bands: false } },
1230
+ regime: { type: "analysis", defaultParams: { lookback: 60 } },
1231
+ vix_filter: { type: "analysis", defaultParams: { allow: ["low_vol", "normal"] } },
1232
+ delta_divergence: { type: "delta_divergence", defaultParams: { lookback: 30, min_delta: 0.5 } },
1233
+ session: { type: "filter", defaultParams: { start: "09:50", end: "15:55", days: "mon-fri" } },
1234
+ news_blackout: { type: "filter", defaultParams: { minutes_before: 5, minutes_after: 5 } },
1235
+ volatility_gate: { type: "filter", defaultParams: { atr_min: 2, atr_max: 20 } },
1236
+ time_of_day_filter: { type: "time_of_day_filter", defaultParams: { start: "09:50", end: "15:55" } },
1237
+ volatility_regime_filter: {
1238
+ type: "volatility_regime_filter",
1239
+ defaultParams: { allow: ["low_vol", "normal"] }
1240
+ },
1241
+ entry_long: { type: "order", defaultParams: { size: 1, order_type: "market" } },
1242
+ entry_short: { type: "order", defaultParams: { size: 1, order_type: "market" } },
1243
+ stop_loss: { type: "order", defaultParams: { mode: "ticks", value: 8 } },
1244
+ take_profit: { type: "order", defaultParams: { mode: "ticks", value: 12 } },
1245
+ trailing_stop_order: { type: "trailing_stop_order", defaultParams: { mode: "ticks", distance: 8 } },
1246
+ breakeven_stop: { type: "breakeven_stop", defaultParams: { trigger_ticks: 6, offset_ticks: 1 } },
1247
+ partial_exit: { type: "partial_exit", defaultParams: { fraction: 0.5, target_ticks: 8 } },
1248
+ hedge_order: { type: "hedge_order", defaultParams: { instrument: "MES", ratio: 1 } }
1249
+ };
1250
+ function resolveNodeType(subtype, fallbackBucket) {
1251
+ return NODE_CATALOG[subtype]?.type ?? fallbackBucket;
1252
+ }
1253
+ function defaultParamsFor(subtype) {
1254
+ const entry = NODE_CATALOG[subtype];
1255
+ return entry ? { ...entry.defaultParams } : {};
1256
+ }
1257
+
1258
+ // ../sdk/src/graph/builder.ts
1259
+ var BUCKET_COLUMN = {
1260
+ indicator: 0,
1261
+ analysis: 1,
1262
+ filter: 2,
1263
+ order: 3
1264
+ };
1265
+ var COLUMN_WIDTH = 320;
1266
+ var ROW_HEIGHT = 110;
1267
+
1268
+ class GraphBuilder {
1269
+ nodes = [];
1270
+ edges = [];
1271
+ subtypeCount = new Map;
1272
+ columnFill = {
1273
+ indicator: 0,
1274
+ analysis: 0,
1275
+ filter: 0,
1276
+ order: 0
1277
+ };
1278
+ node(type, subtype, params, opts) {
1279
+ const n = (this.subtypeCount.get(subtype) ?? 0) + 1;
1280
+ this.subtypeCount.set(subtype, n);
1281
+ const id = opts?.id ?? `${subtype}_${n}`;
1282
+ const bucket = BUCKET_OF[type];
1283
+ const position = opts?.position ?? this.nextPosition(bucket);
1284
+ this.nodes.push({
1285
+ id,
1286
+ type,
1287
+ subtype,
1288
+ position,
1289
+ params: params ?? defaultParamsFor(subtype)
1290
+ });
1291
+ return { id, type };
1292
+ }
1293
+ indicator(subtype, params) {
1294
+ return this.node(resolveNodeType(subtype, "indicator"), subtype, params);
1295
+ }
1296
+ analysis(subtype, params) {
1297
+ return this.node(resolveNodeType(subtype, "analysis"), subtype, params);
1298
+ }
1299
+ filter(subtype, params) {
1300
+ return this.node(resolveNodeType(subtype, "filter"), subtype, params);
1301
+ }
1302
+ order(subtype, params) {
1303
+ return this.node(resolveNodeType(subtype, "order"), subtype, params);
1304
+ }
1305
+ connect(from, to, opts) {
1306
+ const base = `e_${from.id}__${to.id}`;
1307
+ let id = base;
1308
+ let dup = 1;
1309
+ while (this.edges.some((e) => e.id === id))
1310
+ id = `${base}_${++dup}`;
1311
+ const edge = { id, source: from.id, target: to.id };
1312
+ if (opts?.sourceHandle)
1313
+ edge.sourceHandle = opts.sourceHandle;
1314
+ if (opts?.targetHandle)
1315
+ edge.targetHandle = opts.targetHandle;
1316
+ this.edges.push(edge);
1317
+ return this;
1318
+ }
1319
+ build() {
1320
+ const result = this.tryBuild();
1321
+ if (!result.ok) {
1322
+ throw new EX7ValidationError(0, "invalid_graph", `graph failed validation: ${result.issues.length} issue(s)`, result.issues);
1323
+ }
1324
+ return result.graph;
1325
+ }
1326
+ tryBuild() {
1327
+ const draft = {
1328
+ nodes: this.nodes,
1329
+ edges: this.edges,
1330
+ version: GRAPH_VERSION
1331
+ };
1332
+ return validateStrategyGraph(draft);
1333
+ }
1334
+ nextPosition(bucket) {
1335
+ const row = this.columnFill[bucket];
1336
+ this.columnFill[bucket] = row + 1;
1337
+ return { x: BUCKET_COLUMN[bucket] * COLUMN_WIDTH, y: row * ROW_HEIGHT };
1338
+ }
1339
+ }
1340
+ function createGraphBuilder() {
1341
+ return new GraphBuilder;
1342
+ }
1343
+
1344
+ // ../sdk/src/workflow/engine.ts
1345
+ var TIER_RANK = {
1346
+ PASS: 0,
1347
+ PROMISING: 1,
1348
+ INCONCLUSIVE: 2,
1349
+ FAIL: 3
1350
+ };
1351
+ function effectiveMinTier(opts) {
1352
+ if (opts.allowPromising)
1353
+ return "PROMISING";
1354
+ return opts.minTier ?? "PASS";
1355
+ }
1356
+ function satisfiesGate(tier, opts) {
1357
+ if (!tier)
1358
+ return false;
1359
+ return TIER_RANK[tier] <= TIER_RANK[effectiveMinTier(opts)];
1360
+ }
1361
+
1362
+ class WorkflowBuilderImpl {
1363
+ client;
1364
+ steps = [];
1365
+ observers = [];
1366
+ constructor(client) {
1367
+ this.client = client;
1368
+ }
1369
+ fromPrompt(text, o = {}) {
1370
+ const step = { kind: "source", via: "prompt", prompt: text };
1371
+ if (o.instrument)
1372
+ step.instrument = o.instrument;
1373
+ if (o.timeframe)
1374
+ step.timeframe = o.timeframe;
1375
+ this.steps.push(step);
1376
+ return this;
1377
+ }
1378
+ fromGraph(graph) {
1379
+ this.steps.push({ kind: "source", via: "graph", graph });
1380
+ return this;
1381
+ }
1382
+ fromFile(path) {
1383
+ this.steps.push({ kind: "source", via: "file", path });
1384
+ return this;
1385
+ }
1386
+ fromStrategy(id) {
1387
+ this.steps.push({ kind: "source", via: "strategy", id });
1388
+ return this;
1389
+ }
1390
+ validate() {
1391
+ this.steps.push({ kind: "validate" });
1392
+ return this;
1393
+ }
1394
+ create(meta) {
1395
+ this.steps.push({ kind: "create", meta });
1396
+ return this;
1397
+ }
1398
+ judge() {
1399
+ this.steps.push({ kind: "judge" });
1400
+ return this;
1401
+ }
1402
+ gate(opts = {}) {
1403
+ this.steps.push({ kind: "gate", opts });
1404
+ return this;
1405
+ }
1406
+ backtest(opts) {
1407
+ this.steps.push({ kind: "backtest", opts });
1408
+ return this;
1409
+ }
1410
+ deployPlan(opts = {}) {
1411
+ this.steps.push({ kind: "deployPlan", opts });
1412
+ return this;
1413
+ }
1414
+ publish() {
1415
+ this.steps.push({ kind: "publish" });
1416
+ return this;
1417
+ }
1418
+ onPhase(cb) {
1419
+ this.observers.push(cb);
1420
+ return this;
1421
+ }
1422
+ async run(o = {}) {
1423
+ const signal = o.signal;
1424
+ const steps = autoInjectGate(this.steps);
1425
+ assertHasSource(steps);
1426
+ const ctx = {};
1427
+ const phases = [];
1428
+ const startedAt = Date.now();
1429
+ const emit = async (e) => {
1430
+ for (const cb of this.observers)
1431
+ await cb(e);
1432
+ };
1433
+ const runPhase = async (phase, exec) => {
1434
+ const phaseStart = Date.now();
1435
+ await emit({ phase, status: "start", startedAt: phaseStart });
1436
+ const progress = async (data, p) => {
1437
+ await emit({
1438
+ phase,
1439
+ status: "progress",
1440
+ startedAt: phaseStart,
1441
+ data,
1442
+ ...p !== undefined ? { progress: p } : {}
1443
+ });
1444
+ };
1445
+ try {
1446
+ const out = await exec(progress);
1447
+ const rec = {
1448
+ phase,
1449
+ ok: true,
1450
+ output: out,
1451
+ startedAt: phaseStart,
1452
+ elapsedMs: Date.now() - phaseStart
1453
+ };
1454
+ phases.push(rec);
1455
+ await emit({ phase, status: "done", startedAt: phaseStart, elapsedMs: rec.elapsedMs, data: out });
1456
+ return out;
1457
+ } catch (e) {
1458
+ const err = toEX7(e);
1459
+ const rec = {
1460
+ phase,
1461
+ ok: false,
1462
+ error: err,
1463
+ startedAt: phaseStart,
1464
+ elapsedMs: Date.now() - phaseStart
1465
+ };
1466
+ phases.push(rec);
1467
+ await emit({ phase, status: "failed", startedAt: phaseStart, elapsedMs: rec.elapsedMs, error: err });
1468
+ throw err;
1469
+ }
1470
+ };
1471
+ let failedPhase;
1472
+ let error;
1473
+ try {
1474
+ for (const step of steps) {
1475
+ await this.execStep(step, ctx, runPhase, signal);
1476
+ }
1477
+ } catch (e) {
1478
+ error = toEX7(e);
1479
+ failedPhase = phases.length > 0 ? phases[phases.length - 1].phase : undefined;
1480
+ }
1481
+ const result = {
1482
+ ok: error === undefined,
1483
+ phases,
1484
+ startedAt,
1485
+ elapsedMs: Date.now() - startedAt
1486
+ };
1487
+ if (ctx.graph)
1488
+ result.graph = ctx.graph;
1489
+ if (ctx.strategy)
1490
+ result.strategy = ctx.strategy;
1491
+ if (ctx.verdict)
1492
+ result.verdict = ctx.verdict;
1493
+ if (ctx.backtestJob)
1494
+ result.backtestJob = ctx.backtestJob;
1495
+ if (ctx.deploymentPlan)
1496
+ result.deploymentPlan = ctx.deploymentPlan;
1497
+ if (ctx.published !== undefined)
1498
+ result.published = ctx.published;
1499
+ if (failedPhase)
1500
+ result.failedPhase = failedPhase;
1501
+ if (error)
1502
+ result.error = error;
1503
+ return result;
1504
+ }
1505
+ async execStep(step, ctx, runPhase, signal) {
1506
+ const client = this.client;
1507
+ switch (step.kind) {
1508
+ case "source":
1509
+ await runPhase("source", async () => {
1510
+ ctx.graph = await this.resolveSource(step, ctx, signal);
1511
+ return step.via === "strategy" ? ctx.strategy : ctx.graph;
1512
+ });
1513
+ return;
1514
+ case "validate":
1515
+ await runPhase("validate", async () => {
1516
+ const v = validateStrategyGraph(ctx.graph);
1517
+ if (!v.ok) {
1518
+ throw new EX7ValidationError(0, "invalid_graph", `graph failed validation: ${v.issues.length} issue(s)`, v.issues);
1519
+ }
1520
+ ctx.graph = v.graph;
1521
+ return { nodes: v.graph.nodes.length, edges: v.graph.edges.length };
1522
+ });
1523
+ return;
1524
+ case "create":
1525
+ await runPhase("create", async () => {
1526
+ if (!ctx.graph)
1527
+ throw new EX7Error(0, "no_graph", "create requires a graph source");
1528
+ const input = {
1529
+ name: step.meta.name,
1530
+ graph_json: ctx.graph
1531
+ };
1532
+ if (step.meta.description !== undefined)
1533
+ input.description = step.meta.description;
1534
+ if (step.meta.instrument !== undefined)
1535
+ input.instrument = step.meta.instrument;
1536
+ ctx.strategy = await client.strategies.create(input, signal ? { signal } : {});
1537
+ return ctx.strategy;
1538
+ });
1539
+ return;
1540
+ case "judge":
1541
+ await runPhase("judge", async (progress) => {
1542
+ if (!ctx.strategy)
1543
+ throw new EX7Error(0, "no_strategy", "judge requires a created or sourced strategy");
1544
+ const runResult = await client.strategies.judge(ctx.strategy.id, {
1545
+ ...signal ? { signal } : {},
1546
+ onTick: (r) => void progress(r)
1547
+ });
1548
+ const tier = runResult.tier ?? runResult.result?.tier ?? undefined;
1549
+ ctx.verdict = { runResult, ...tier ? { tier } : {} };
1550
+ return ctx.verdict;
1551
+ });
1552
+ return;
1553
+ case "gate":
1554
+ await runPhase("gate", async () => {
1555
+ const tier = ctx.verdict?.tier;
1556
+ if (!satisfiesGate(tier, step.opts)) {
1557
+ const min = effectiveMinTier(step.opts);
1558
+ throw new EX7GateError(0, "gate_unmet", tier ? `verdict tier ${tier} does not meet the gate (minimum ${min})` : "no judge verdict available; gate fails closed (validate first, operate second)");
1559
+ }
1560
+ return { tier, min: effectiveMinTier(step.opts) };
1561
+ });
1562
+ return;
1563
+ case "backtest":
1564
+ await runPhase("backtest", async (progress) => {
1565
+ if (!ctx.strategy)
1566
+ throw new EX7Error(0, "no_strategy", "backtest requires a strategy");
1567
+ const pollOpts = { onTick: (j) => void progress(j) };
1568
+ if (signal)
1569
+ pollOpts.signal = signal;
1570
+ if (step.opts.intervalMs !== undefined)
1571
+ pollOpts.intervalMs = step.opts.intervalMs;
1572
+ if (step.opts.maxWaitMs !== undefined)
1573
+ pollOpts.maxWaitMs = step.opts.maxWaitMs;
1574
+ const input = { from: step.opts.from, to: step.opts.to };
1575
+ if (step.opts.instrument !== undefined)
1576
+ input.instrument = step.opts.instrument;
1577
+ ctx.backtestJob = await client.backtest.runAndWait(ctx.strategy.id, input, pollOpts);
1578
+ return ctx.backtestJob;
1579
+ });
1580
+ return;
1581
+ case "deployPlan":
1582
+ await runPhase("deployPlan", async () => {
1583
+ if (!ctx.strategy)
1584
+ throw new EX7Error(0, "no_strategy", "deployPlan requires a strategy");
1585
+ ctx.deploymentPlan = await client.strategies.deployPlan(ctx.strategy.id, step.opts, signal ? { signal } : {});
1586
+ return ctx.deploymentPlan;
1587
+ });
1588
+ return;
1589
+ case "publish":
1590
+ await runPhase("publish", async () => {
1591
+ if (!ctx.strategy)
1592
+ throw new EX7Error(0, "no_strategy", "publish requires a strategy");
1593
+ const s = await client.strategies.publish(ctx.strategy.id, signal ? { signal } : {});
1594
+ ctx.strategy = s;
1595
+ ctx.published = true;
1596
+ return s;
1597
+ });
1598
+ return;
1599
+ }
1600
+ }
1601
+ async resolveSource(step, ctx, signal) {
1602
+ const client = this.client;
1603
+ switch (step.via) {
1604
+ case "prompt": {
1605
+ const res = await client.vix.generateStrategy({
1606
+ prompt: step.prompt,
1607
+ ...step.instrument ? { instrument: step.instrument } : {},
1608
+ ...step.timeframe ? { timeframe: step.timeframe } : {}
1609
+ }, signal ? { signal } : {});
1610
+ return res.graph;
1611
+ }
1612
+ case "graph":
1613
+ return step.graph;
1614
+ case "file": {
1615
+ const fs = await import("node:fs/promises");
1616
+ const raw = await fs.readFile(step.path, "utf8");
1617
+ return extractGraphFromBody(JSON.parse(raw));
1618
+ }
1619
+ case "strategy": {
1620
+ ctx.strategy = await client.strategies.get(step.id, signal ? { signal } : {});
1621
+ return ctx.strategy.graph_json;
1622
+ }
1623
+ }
1624
+ }
1625
+ }
1626
+ function autoInjectGate(steps) {
1627
+ const hasDeploy = steps.some((s) => s.kind === "deployPlan");
1628
+ const hasGate = steps.some((s) => s.kind === "gate");
1629
+ if (!hasDeploy || hasGate)
1630
+ return steps;
1631
+ const idx = steps.findIndex((s) => s.kind === "deployPlan");
1632
+ const out = steps.slice();
1633
+ out.splice(idx, 0, { kind: "gate", opts: { minTier: "PASS" } });
1634
+ return out;
1635
+ }
1636
+ function assertHasSource(steps) {
1637
+ if (!steps.some((s) => s.kind === "source")) {
1638
+ throw new EX7Error(0, "no_source", "workflow needs a source: fromPrompt(), fromGraph(), fromFile(), or fromStrategy()");
1639
+ }
1640
+ }
1641
+ function toEX7(e) {
1642
+ if (e instanceof EX7Error)
1643
+ return e;
1644
+ if (e instanceof Error)
1645
+ return new EX7Error(0, "workflow_error", e.message);
1646
+ return new EX7Error(0, "workflow_error", String(e));
1647
+ }
1648
+ async function runIterate(client, o) {
1649
+ const attempts = [];
1650
+ const signal = o.signal;
1651
+ const emit = async (e) => {
1652
+ if (o.onPhase)
1653
+ await o.onPhase(e);
1654
+ };
1655
+ let next = o.prompt;
1656
+ for (let attempt = 1;attempt <= o.maxAttempts; attempt++) {
1657
+ try {
1658
+ const graph = await produceGraph(client, next, o, signal, attempt, emit);
1659
+ const v = validateStrategyGraph(graph);
1660
+ if (!v.ok) {
1661
+ const err = new EX7ValidationError(0, "invalid_graph", `attempt ${attempt}: graph failed validation`, v.issues);
1662
+ attempts.push({ attempt, graph, passed: false, error: err });
1663
+ if (!await advance(o, { attempt, graph, reason: "invalid_graph" }, (n) => next = n))
1664
+ break;
1665
+ continue;
1666
+ }
1667
+ const name = o.name?.(attempt) ?? `vix-iter-${attempt}`;
1668
+ const createInput = {
1669
+ name,
1670
+ graph_json: v.graph
1671
+ };
1672
+ if (o.instrument)
1673
+ createInput.instrument = o.instrument;
1674
+ const at = { phase: "create", status: "start", startedAt: Date.now(), attempt };
1675
+ await emit(at);
1676
+ const strategy = await client.strategies.create(createInput, signal ? { signal } : {});
1677
+ await emit({ phase: "judge", status: "start", startedAt: Date.now(), attempt });
1678
+ const runResult = await client.strategies.judge(strategy.id, signal ? { signal } : {});
1679
+ const tier = runResult.tier ?? runResult.result?.tier ?? undefined;
1680
+ const passed = satisfiesGate(tier, o.gate);
1681
+ const record = { attempt, graph: v.graph, strategy, passed };
1682
+ if (tier)
1683
+ record.tier = tier;
1684
+ attempts.push(record);
1685
+ await emit({
1686
+ phase: "gate",
1687
+ status: passed ? "done" : "failed",
1688
+ startedAt: Date.now(),
1689
+ attempt,
1690
+ data: { tier }
1691
+ });
1692
+ if (passed) {
1693
+ if (o.cleanupFailed)
1694
+ await cleanup(client, attempts, strategy.id);
1695
+ const out2 = { ok: true, attempts, passed: record };
1696
+ if (tier)
1697
+ out2.finalTier = tier;
1698
+ return out2;
1699
+ }
1700
+ const failure = {
1701
+ attempt,
1702
+ graph: v.graph,
1703
+ strategy,
1704
+ result: runResult,
1705
+ reason: tier ? `tier ${tier}` : "no verdict"
1706
+ };
1707
+ if (tier)
1708
+ failure.tier = tier;
1709
+ if (attempt < o.maxAttempts) {
1710
+ if (!await advance(o, failure, (n) => next = n))
1711
+ break;
1712
+ }
1713
+ } catch (e) {
1714
+ const err = toEX7(e);
1715
+ attempts.push({ attempt, graph: emptyishGraph(), passed: false, error: err });
1716
+ break;
1717
+ }
1718
+ }
1719
+ if (o.cleanupFailed)
1720
+ await cleanup(client, attempts);
1721
+ const last = attempts[attempts.length - 1];
1722
+ const out = { ok: false, attempts };
1723
+ if (last?.tier)
1724
+ out.finalTier = last.tier;
1725
+ return out;
1726
+ }
1727
+ async function produceGraph(client, input, o, signal, attempt, emit) {
1728
+ if (typeof input !== "string")
1729
+ return input;
1730
+ await emit({ phase: "source", status: "start", startedAt: Date.now(), attempt });
1731
+ const res = await client.vix.generateStrategy({
1732
+ prompt: input,
1733
+ ...o.instrument ? { instrument: o.instrument } : {},
1734
+ ...o.timeframe ? { timeframe: o.timeframe } : {}
1735
+ }, signal ? { signal } : {});
1736
+ return res.graph;
1737
+ }
1738
+ async function advance(o, failure, setNext) {
1739
+ if (!o.refine)
1740
+ return false;
1741
+ const next = await o.refine(failure);
1742
+ setNext(next);
1743
+ return true;
1744
+ }
1745
+ async function cleanup(client, attempts, keepId) {
1746
+ for (const a of attempts) {
1747
+ if (!a.strategy || a.strategy.id === keepId || a.passed)
1748
+ continue;
1749
+ try {
1750
+ await client.strategies.delete(a.strategy.id);
1751
+ } catch {}
1752
+ }
1753
+ }
1754
+ function emptyishGraph() {
1755
+ return { nodes: [], edges: [], version: 1 };
1756
+ }
1757
+ function createWorkflowFactory(client) {
1758
+ const factory = () => new WorkflowBuilderImpl(client);
1759
+ factory.iterate = (o) => runIterate(client, o);
1760
+ return factory;
1761
+ }
1762
+
1763
+ // ../sdk/src/client.ts
1764
+ function createClient(config = {}) {
1765
+ const resolved = resolveConfig(config);
1766
+ const httpCfg = {
1767
+ baseUrl: resolved.baseUrl,
1768
+ auth: resolved.auth,
1769
+ timeoutMs: resolved.timeoutMs,
1770
+ userAgent: resolved.userAgent,
1771
+ agentToken: resolved.agentToken,
1772
+ fetch: resolved.fetch,
1773
+ defaultHeaders: resolved.headers
1774
+ };
1775
+ const http = new HttpClient(httpCfg);
1776
+ const client = {
1777
+ config: resolved,
1778
+ http,
1779
+ strategies: new StrategiesResource(http),
1780
+ judge: new JudgeResource(http),
1781
+ vix: new VixResource(http),
1782
+ backtest: new BacktestResource(http),
1783
+ signals: new SignalsResource(http),
1784
+ agents: new AgentsResource(http),
1785
+ agent: new AgentScopedResource(http),
1786
+ workflow: undefined,
1787
+ graph: () => createGraphBuilder(),
1788
+ status: (o = {}) => http.json({ path: "api/health", auth: "none", ...o }),
1789
+ asAgent: (agentToken) => createClient({ ...config, agentToken }),
1790
+ request: (opts) => http.json(opts)
1791
+ };
1792
+ client.workflow = createWorkflowFactory(client);
1793
+ return client;
1794
+ }
1795
+ // src/server.ts
1796
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1797
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
1798
+
1799
+ // src/tools.ts
1800
+ function reqString(args, key) {
1801
+ const v = args[key];
1802
+ if (typeof v !== "string" || v.length === 0) {
1803
+ throw Object.assign(new Error(`missing required string '${key}'`), { code: "invalid_args", status: 400 });
1804
+ }
1805
+ return v;
1806
+ }
1807
+ function optNumber(args, key) {
1808
+ const v = args[key];
1809
+ return typeof v === "number" ? v : undefined;
1810
+ }
1811
+ function optString(args, key) {
1812
+ const v = args[key];
1813
+ return typeof v === "string" && v.length > 0 ? v : undefined;
1814
+ }
1815
+ var TOOLS = [
1816
+ {
1817
+ definition: {
1818
+ name: "ex7_status",
1819
+ description: "Readiness probe for the EX7 platform (db, backend, services). No auth required.",
1820
+ inputSchema: { type: "object", properties: {}, additionalProperties: false }
1821
+ },
1822
+ handler: (ctx) => ctx.client.status()
1823
+ },
1824
+ {
1825
+ definition: {
1826
+ name: "ex7_vix_generate",
1827
+ description: "Generate a strategy-graph delta (nodes_to_add/edges_to_add) from a natural-language idea. With an agent token this uses the deterministic, $0 agent pipeline.",
1828
+ inputSchema: {
1829
+ type: "object",
1830
+ properties: { prompt: { type: "string", minLength: 1, maxLength: 4000 } },
1831
+ required: ["prompt"],
1832
+ additionalProperties: false
1833
+ }
1834
+ },
1835
+ handler: (ctx, args) => {
1836
+ const prompt = reqString(args, "prompt");
1837
+ return ctx.agentMode ? ctx.client.agent.vix(prompt) : ctx.client.vix.generate({ prompt });
1838
+ }
1839
+ },
1840
+ {
1841
+ definition: {
1842
+ name: "ex7_strategy_validate",
1843
+ description: "Validate a strategy graph structurally, offline (no network). Accepts a bare graph or a { graph_json } wrapper.",
1844
+ inputSchema: {
1845
+ type: "object",
1846
+ properties: { graph_json: { type: "object" } },
1847
+ required: ["graph_json"],
1848
+ additionalProperties: true
1849
+ }
1850
+ },
1851
+ handler: async (_ctx, args) => {
1852
+ const result = validateStrategyGraph(extractGraphFromBody(args.graph_json));
1853
+ return result.ok ? { ok: true, nodes: result.graph.nodes.length, edges: result.graph.edges.length } : { ok: false, errors: result.errors, issues: result.issues };
1854
+ }
1855
+ },
1856
+ {
1857
+ definition: {
1858
+ name: "ex7_strategy_create",
1859
+ description: "Create (persist) a strategy from a graph. The graph is validated locally first.",
1860
+ inputSchema: {
1861
+ type: "object",
1862
+ properties: {
1863
+ name: { type: "string", minLength: 1 },
1864
+ description: { type: "string" },
1865
+ instrument: { type: "string", enum: ["NQ", "ES", "CL", "MNQ", "MES", "MCL"] },
1866
+ graph_json: { type: "object" }
1867
+ },
1868
+ required: ["name", "graph_json"],
1869
+ additionalProperties: false
1870
+ }
1871
+ },
1872
+ handler: (ctx, args) => {
1873
+ const input = {
1874
+ name: reqString(args, "name"),
1875
+ graph_json: args.graph_json
1876
+ };
1877
+ const desc = optString(args, "description");
1878
+ if (desc)
1879
+ input.description = desc;
1880
+ const inst = optString(args, "instrument");
1881
+ if (inst)
1882
+ input.instrument = inst;
1883
+ return ctx.client.strategies.create(input);
1884
+ }
1885
+ },
1886
+ {
1887
+ definition: {
1888
+ name: "ex7_judge_run",
1889
+ description: "Run The Judge on a saved strategy and return the terminal verdict (tier + metrics). With an agent token this is a deterministic $0 fixture lookup.",
1890
+ inputSchema: {
1891
+ type: "object",
1892
+ properties: { strategy_id: { type: "string", minLength: 1 } },
1893
+ required: ["strategy_id"],
1894
+ additionalProperties: false
1895
+ }
1896
+ },
1897
+ handler: (ctx, args) => {
1898
+ const id = reqString(args, "strategy_id");
1899
+ return ctx.agentMode ? ctx.client.agent.judge(id) : ctx.client.strategies.judge(id);
1900
+ }
1901
+ },
1902
+ {
1903
+ definition: {
1904
+ name: "ex7_backtest_run",
1905
+ description: "Enqueue a backtest and wait for the terminal job (metrics on success).",
1906
+ inputSchema: {
1907
+ type: "object",
1908
+ properties: {
1909
+ strategy_id: { type: "string", minLength: 1 },
1910
+ from: { type: "string", description: "YYYY-MM-DD" },
1911
+ to: { type: "string", description: "YYYY-MM-DD" },
1912
+ instrument: { type: "string", enum: ["NQ", "ES", "CL", "MNQ", "MES", "MCL"] }
1913
+ },
1914
+ required: ["strategy_id", "from", "to"],
1915
+ additionalProperties: false
1916
+ }
1917
+ },
1918
+ handler: (ctx, args) => {
1919
+ const input = {
1920
+ from: reqString(args, "from"),
1921
+ to: reqString(args, "to")
1922
+ };
1923
+ const inst = optString(args, "instrument");
1924
+ if (inst)
1925
+ input.instrument = inst;
1926
+ return ctx.client.backtest.runAndWait(reqString(args, "strategy_id"), input);
1927
+ }
1928
+ },
1929
+ {
1930
+ definition: {
1931
+ name: "ex7_deploy_plan",
1932
+ description: "Compile a deployment plan for a PASS strategy. EX7 never executes orders: the result is a webhook TEMPLATE + disclaimer you wire to your own broker. Requires a passing Judge verdict.",
1933
+ inputSchema: {
1934
+ type: "object",
1935
+ properties: {
1936
+ strategy_id: { type: "string", minLength: 1 },
1937
+ max_contracts: { type: "number", minimum: 1, maximum: 50 },
1938
+ dd_limit: { type: "number" },
1939
+ bust_safe: { type: "boolean" },
1940
+ allow_promising: { type: "boolean" }
1941
+ },
1942
+ required: ["strategy_id"],
1943
+ additionalProperties: false
1944
+ }
1945
+ },
1946
+ handler: (ctx, args) => {
1947
+ const input = {};
1948
+ const mc = optNumber(args, "max_contracts");
1949
+ if (mc !== undefined)
1950
+ input.maxContracts = mc;
1951
+ const dd = optNumber(args, "dd_limit");
1952
+ if (dd !== undefined)
1953
+ input.ddLimit = dd;
1954
+ if (typeof args.bust_safe === "boolean")
1955
+ input.bustSafe = args.bust_safe;
1956
+ if (typeof args.allow_promising === "boolean")
1957
+ input.allowPromising = args.allow_promising;
1958
+ return ctx.client.strategies.deployPlan(reqString(args, "strategy_id"), input);
1959
+ }
1960
+ },
1961
+ {
1962
+ definition: {
1963
+ name: "ex7_strategy_publish",
1964
+ description: "Publish a validated strategy (or unpublish it with { unpublish: true }).",
1965
+ inputSchema: {
1966
+ type: "object",
1967
+ properties: {
1968
+ strategy_id: { type: "string", minLength: 1 },
1969
+ unpublish: { type: "boolean" }
1970
+ },
1971
+ required: ["strategy_id"],
1972
+ additionalProperties: false
1973
+ }
1974
+ },
1975
+ handler: (ctx, args) => {
1976
+ const id = reqString(args, "strategy_id");
1977
+ return args.unpublish === true ? ctx.client.strategies.unpublish(id) : ctx.client.strategies.publish(id);
1978
+ }
1979
+ },
1980
+ {
1981
+ definition: {
1982
+ name: "ex7_signals_list",
1983
+ description: "List recent live trading signals (one-shot read).",
1984
+ inputSchema: {
1985
+ type: "object",
1986
+ properties: { limit: { type: "number", minimum: 1, maximum: 200 } },
1987
+ additionalProperties: false
1988
+ }
1989
+ },
1990
+ handler: (ctx, args) => {
1991
+ const limit = optNumber(args, "limit");
1992
+ return ctx.client.signals.list(limit !== undefined ? { limit } : {});
1993
+ }
1994
+ },
1995
+ {
1996
+ definition: {
1997
+ name: "ex7_workflow_run",
1998
+ description: "Run the full EX7 workflow from a prompt: generate -> validate -> create -> judge -> gate (PASS) and, optionally, backtest and a deploy-plan. Returns a per-phase summary. Operate steps run only on a real PASS.",
1999
+ inputSchema: {
2000
+ type: "object",
2001
+ properties: {
2002
+ prompt: { type: "string", minLength: 1, maxLength: 4000 },
2003
+ name: { type: "string", minLength: 1 },
2004
+ instrument: { type: "string", enum: ["NQ", "ES", "CL", "MNQ", "MES", "MCL"] },
2005
+ min_tier: { type: "string", enum: ["PASS", "PROMISING"] },
2006
+ run_backtest: { type: "boolean" },
2007
+ backtest_from: { type: "string" },
2008
+ backtest_to: { type: "string" },
2009
+ want_deploy_plan: { type: "boolean" },
2010
+ deploy_max_contracts: { type: "number", minimum: 1, maximum: 50 }
2011
+ },
2012
+ required: ["prompt", "name"],
2013
+ additionalProperties: false
2014
+ }
2015
+ },
2016
+ handler: async (ctx, args) => {
2017
+ const prompt = reqString(args, "prompt");
2018
+ const name = reqString(args, "name");
2019
+ const instrument = optString(args, "instrument");
2020
+ const minTier = optString(args, "min_tier") ?? "PASS";
2021
+ let wf = ctx.client.workflow().fromPrompt(prompt, instrument ? { instrument } : {}).validate().create(instrument ? { name, instrument } : { name }).judge().gate({ minTier });
2022
+ if (args.run_backtest === true) {
2023
+ const from = reqString(args, "backtest_from");
2024
+ const to = reqString(args, "backtest_to");
2025
+ wf = wf.backtest(instrument ? { from, to, instrument } : { from, to });
2026
+ }
2027
+ if (args.want_deploy_plan === true) {
2028
+ const mc = optNumber(args, "deploy_max_contracts");
2029
+ wf = wf.deployPlan(mc !== undefined ? { maxContracts: mc } : {});
2030
+ }
2031
+ const result = await wf.run();
2032
+ return {
2033
+ ok: result.ok,
2034
+ strategy_id: result.strategy?.id,
2035
+ judge_tier: result.verdict?.tier,
2036
+ backtest_metrics: result.backtestJob?.metrics ?? undefined,
2037
+ deploy_plan: result.deploymentPlan,
2038
+ published: result.published,
2039
+ failed_phase: result.failedPhase,
2040
+ error: result.error ? { code: result.error.code, message: result.error.message } : undefined,
2041
+ phases: result.phases.map((p) => ({ phase: p.phase, ok: p.ok, ms: p.elapsedMs }))
2042
+ };
2043
+ }
2044
+ }
2045
+ ];
2046
+
2047
+ // src/server.ts
2048
+ var SERVER_NAME = "ex7";
2049
+ var SERVER_VERSION = "0.1.0";
2050
+ function createServer(deps) {
2051
+ const ctx = {
2052
+ client: deps.client,
2053
+ agentMode: deps.agentMode ?? !!deps.client.config.agentToken
2054
+ };
2055
+ const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} } });
2056
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
2057
+ tools: TOOLS.map((t) => t.definition)
2058
+ }));
2059
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
2060
+ const tool = TOOLS.find((t) => t.definition.name === req.params.name);
2061
+ if (!tool) {
2062
+ return {
2063
+ content: [{ type: "text", text: JSON.stringify({ error: "unknown_tool", name: req.params.name }) }],
2064
+ isError: true
2065
+ };
2066
+ }
2067
+ try {
2068
+ const args = req.params.arguments ?? {};
2069
+ const result = await tool.handler(ctx, args);
2070
+ return { content: [{ type: "text", text: JSON.stringify(result ?? null) }] };
2071
+ } catch (e) {
2072
+ const err = e;
2073
+ return {
2074
+ content: [
2075
+ {
2076
+ type: "text",
2077
+ text: JSON.stringify({
2078
+ error: err.code ?? "error",
2079
+ status: err.status,
2080
+ message: err.message ?? String(e)
2081
+ })
2082
+ }
2083
+ ],
2084
+ isError: true
2085
+ };
2086
+ }
2087
+ });
2088
+ return server;
2089
+ }
2090
+
2091
+ // src/index.ts
2092
+ async function main() {
2093
+ const baseUrl = process.env.EX7_BASE_URL || process.env.EX7_API_URL || "https://ex7capital.com";
2094
+ const config = { baseUrl, userAgent: "ex7-mcp/0.1.0" };
2095
+ if (process.env.EX7_TOKEN)
2096
+ config.token = process.env.EX7_TOKEN;
2097
+ if (process.env.EX7_AGENT_TOKEN)
2098
+ config.agentToken = process.env.EX7_AGENT_TOKEN;
2099
+ const client = createClient(config);
2100
+ const server = createServer({ client });
2101
+ const transport = new StdioServerTransport;
2102
+ await server.connect(transport);
2103
+ process.stderr.write(`ex7-mcp ready (base ${baseUrl}${config.agentToken ? ", agent mode" : ""})
2104
+ `);
2105
+ }
2106
+ main().catch((e) => {
2107
+ process.stderr.write(`ex7-mcp fatal: ${e instanceof Error ? e.message : String(e)}
2108
+ `);
2109
+ process.exit(1);
2110
+ });