canli-validation-mcp 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -3
- package/package.json +5 -4
- package/src/schemas.mjs +54 -10
- package/src/server.mjs +49 -46
package/README.md
CHANGED
|
@@ -5,6 +5,34 @@
|
|
|
5
5
|
[](https://www.bestpractices.dev/projects/14954)
|
|
6
6
|
[](https://glama.ai/mcp/servers/arhancanli/canli-validation-mcp)
|
|
7
7
|
|
|
8
|
+
**Is your best backtest real, or just the luckiest of the variants you tried?** This MCP server
|
|
9
|
+
lets Claude, Cursor or any MCP client answer that with the standard corrections: the deflated
|
|
10
|
+
Sharpe ratio, the CSCV probability of backtest overfitting, the minimum track record length, the
|
|
11
|
+
haircut Sharpe ratio, and luck-equivalent trials. Free and MIT-licensed.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Claude Code, computed on your machine: no key, nothing sent anywhere
|
|
17
|
+
claude mcp add canli-local --env CANLI_LOCAL=1 -- npx -y canli-validation-mcp
|
|
18
|
+
|
|
19
|
+
# the same tools with signed, stored receipts from the free API (a free key is issued on first use)
|
|
20
|
+
claude mcp add canli -- npx -y canli-validation-mcp
|
|
21
|
+
|
|
22
|
+
# nothing to install: the hosted endpoint
|
|
23
|
+
claude mcp add --transport http canli https://canlicapital.com/mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then ask, for example: *"I tried 229 variants and kept the best: an annualised Sharpe of
|
|
27
|
+
1.5 over 730 daily returns (365 a year), skew -0.5, kurtosis 5, and the variants'
|
|
28
|
+
Sharpe ratios spread by 0.57. Is it real?"* The assistant calls `validate_deflated_sharpe`: the
|
|
29
|
+
best of 229 skill-less variants would reach 1.60 by luck alone, so the probability that the
|
|
30
|
+
Sharpe is above zero falls from 98.1% to 44.4% once the search is counted. Claude Desktop and any
|
|
31
|
+
other stdio client (Cursor, VS Code) run the same `npx` command; see "Claude Desktop" and
|
|
32
|
+
"Generic stdio client" below.
|
|
33
|
+
|
|
34
|
+
## What it does
|
|
35
|
+
|
|
8
36
|
An MCP (Model Context Protocol) server over canlicapital.com's free, keyed validation API. It
|
|
9
37
|
gives a coding agent fourteen tools: issue a free key, run the eight validators (deflated Sharpe,
|
|
10
38
|
CSCV overfitting, paper-evidence conformance, breadth ceiling, minimum track record length,
|
|
@@ -165,7 +193,9 @@ claude mcp add --transport http canli https://canlicapital.com/mcp
|
|
|
165
193
|
```
|
|
166
194
|
|
|
167
195
|
Without a key, requests run under a shared anonymous key, so the daily validation quota is shared
|
|
168
|
-
by every hosted caller.
|
|
196
|
+
by every hosted caller. When that shared quota is used up for the day, validations are still
|
|
197
|
+
answered, computed by the same code on the hosted endpoint, but without a stored receipt; the
|
|
198
|
+
result says so. For your own quota, issue a free key (see
|
|
169
199
|
[/developers](https://canlicapital.com/developers#quickstart)) and send it as a header:
|
|
170
200
|
|
|
171
201
|
```bash
|
|
@@ -348,6 +378,8 @@ HTTP stub. It neither publishes a package nor issues a production API key.
|
|
|
348
378
|
|
|
349
379
|
## Dependencies
|
|
350
380
|
|
|
351
|
-
Only `@modelcontextprotocol/
|
|
352
|
-
|
|
381
|
+
Only `@modelcontextprotocol/server` (pinned exact; the MCP SDK's server package, which itself
|
|
382
|
+
depends only on `@modelcontextprotocol/core` and `zod`) and `zod` (pinned exact): four packages in
|
|
383
|
+
the installed tree, where 0.7 and earlier pulled in about 95 through `@modelcontextprotocol/sdk`.
|
|
384
|
+
No other runtime dependency is added, and nothing in this package touches the site's root `package.json`,
|
|
353
385
|
`.vercelignore`, `api/`, `scripts/`, `js/`, or `public/`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canli-validation-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Check whether a backtest is real: deflated Sharpe, CSCV probability of backtest overfitting, minimum track record and backtest length, haircut Sharpe and luck-equivalent trials, as an MCP server. Local mode, hosted endpoint, signed receipts.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"test": "node --test test/*.test.mjs",
|
|
19
|
+
"test": "node --test test/*.test.mjs test/*.test.js",
|
|
20
20
|
"test:package": "node test/package-smoke.mjs"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@modelcontextprotocol/
|
|
23
|
+
"@modelcontextprotocol/server": "2.1.0",
|
|
24
24
|
"zod": "4.6.5"
|
|
25
25
|
},
|
|
26
26
|
"mcpName": "io.github.arhancanli/canli-validation-mcp",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"url": "https://github.com/arhancanli/canlicapital/issues"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
+
"@modelcontextprotocol/client": "2.1.0",
|
|
37
38
|
"fast-check": "4.10.2"
|
|
38
39
|
}
|
|
39
40
|
}
|
package/src/schemas.mjs
CHANGED
|
@@ -303,17 +303,61 @@ export const REGISTRY_DESCRIPTION_MAX = 100;
|
|
|
303
303
|
// calls the tool, not only inside the returned envelope.
|
|
304
304
|
export const TOOL_DESCRIPTIONS = Object.freeze({
|
|
305
305
|
get_key: `Issue a free canlicapital.com validation key (POST /api/v1/keys) and hold it in memory for this session. Only needed before a validation when neither CANLI_KEY nor local mode is set; the read tools (get_receipt, service_status, company_financial_history) never need a key. ${LIMITS_SENTENCES.quotas}`,
|
|
306
|
-
validate_deflated_sharpe: `
|
|
307
|
-
audit_backtest: `Audit one strategy's return series in one call: deflated Sharpe, the minimum track record length for its Sharpe to beat the benchmark, and, with every variant's returns, the probability of backtest overfitting. Point returns_file at the backtest's CSV or JSON rather than copying long series into the call. Each check is the matching validate_ tool's result with its own receipt, side by side; the audit does not grade the strategy. Uses one validation per check. ${LIMITS_SENTENCES.notAdmission}`,
|
|
308
|
-
validate_overfitting: `Probability (0 to 1)
|
|
309
|
-
validate_paper_evidence: `Whether a paper or simulated performance record meets canli.paper-evidence.v0, with each failure's JSON pointer. ${LIMITS_SENTENCES.scope}`,
|
|
310
|
-
validate_backtest_length: `Minimum backtest length, in years, before the best of N independent trials is not expected to reach a target Sharpe by luck, and with backtest_years, the most independent trials those years allow. Send effective_independent_trials, backtest_years, or both. ${LIMITS_SENTENCES.notAdmission}`,
|
|
311
|
-
validate_luck_trials: `How many skill-less strategies a search would have had to try for its best to reach this Sharpe by luck, and, with a trial count, the chance that it did. Calibrated by Monte Carlo. ${LIMITS_SENTENCES.notAdmission}`,
|
|
312
|
-
validate_haircut_sharpe: `Haircut Sharpe ratio for multiple testing (Harvey and Liu, 2015): the Sharpe a single test would have needed once the number of tests is counted, by Bonferroni and for independent tests, and with the other tests' Sharpe ratios by Holm and BHY. ${LIMITS_SENTENCES.notAdmission}`,
|
|
313
|
-
validate_track_record: `Minimum track record length, in observations and years, for an observed Sharpe to beat a benchmark at a confidence level, and with observations, the record's probabilistic Sharpe so far. ${LIMITS_SENTENCES.notAdmission}`,
|
|
314
|
-
validate_breadth: `Highest book Sharpe reachable by adding sleeves of this quality and correlation, the Sharpe at a sleeve count, and the sleeves a target needs. ${LIMITS_SENTENCES.scope}`,
|
|
306
|
+
validate_deflated_sharpe: `Deflated Sharpe ratio: the probability (0 to 1) that one selected strategy's Sharpe beats the best Sharpe that luck alone would give across the variants tried, with the probabilistic Sharpe and that luck benchmark. Use it for the strategy you kept after a search, when you know how many variants were tried and how their Sharpe ratios spread; send the seven statistics or a return series, not both. With every variant's returns use validate_overfitting; to state luck as a trial count use validate_luck_trials; for a multiple-testing haircut use validate_haircut_sharpe. ${LIMITS_SENTENCES.notAdmission}`,
|
|
307
|
+
audit_backtest: `Audit one strategy's return series in one call: deflated Sharpe, the minimum track record length for its Sharpe to beat the benchmark, and, with every variant's returns, the probability of backtest overfitting. Point returns_file at the backtest's CSV or JSON rather than copying long series into the call. Each check is the matching validate_ tool's result with its own receipt, side by side; the audit does not grade the strategy. Prefer it to calling those validators one by one when you have one strategy's return series. Uses one validation per check. ${LIMITS_SENTENCES.notAdmission}`,
|
|
308
|
+
validate_overfitting: `Probability of backtest overfitting (0 to 1) by CSCV: how often the variant that is best in sample falls below the median out of sample. Use it when you have every variant's returns as a matrix (periods by variants); with summary statistics only, use validate_deflated_sharpe. ${LIMITS_SENTENCES.notAdmission}`,
|
|
309
|
+
validate_paper_evidence: `Whether a paper or simulated performance record meets canli.paper-evidence.v0, with each failure's JSON pointer. Use it on a record document before publishing or relying on it: it checks structure and required disclosures, not whether the returns are good. ${LIMITS_SENTENCES.scope}`,
|
|
310
|
+
validate_backtest_length: `Minimum backtest length, in years, before the best of N independent trials is not expected to reach a target Sharpe by luck, and with backtest_years, the most independent trials those years allow. Send effective_independent_trials, backtest_years, or both. Use it before or while planning a search, to size the backtest for the number of trials; once a search has a result, use validate_deflated_sharpe. ${LIMITS_SENTENCES.notAdmission}`,
|
|
311
|
+
validate_luck_trials: `How many skill-less strategies a search would have had to try for its best to reach this Sharpe by luck, and, with a trial count, the chance that it did. Calibrated by Monte Carlo. Use it to state luck as a count ("as good as the best of N random tries") or to test a result against the trials actually run; for a probability that the Sharpe is real, use validate_deflated_sharpe. ${LIMITS_SENTENCES.notAdmission}`,
|
|
312
|
+
validate_haircut_sharpe: `Haircut Sharpe ratio for multiple testing (Harvey and Liu, 2015): the Sharpe a single test would have needed once the number of tests is counted, by Bonferroni and for independent tests, and with the other tests' Sharpe ratios by Holm and BHY. Use it to report a Sharpe adjusted for the number of tests, as finance papers do; for a probability that the Sharpe is real, use validate_deflated_sharpe. ${LIMITS_SENTENCES.notAdmission}`,
|
|
313
|
+
validate_track_record: `Minimum track record length, in observations and years, for an observed Sharpe to beat a benchmark at a confidence level, and with observations, the record's probabilistic Sharpe so far. Use it for a live or paper record: how long it must run before its Sharpe is evidence. To size a backtest against the number of trials, use validate_backtest_length. ${LIMITS_SENTENCES.notAdmission}`,
|
|
314
|
+
validate_breadth: `Highest book Sharpe reachable by adding sleeves of this quality and correlation, the Sharpe at a sleeve count, and the sleeves a target needs. Use it for portfolio construction, to see what adding strategies can and cannot do; it validates no single strategy. ${LIMITS_SENTENCES.scope}`,
|
|
315
315
|
verify_receipt: `Check a validation receipt's Ed25519 signature offline against the canlicapital.com public key bundled in this package, that its output hashes to its output_sha256, and that its content hashes to its id. Send an id to fetch the receipt first, or a receipt already fetched. ${LIMITS_SENTENCES.unsigned}`,
|
|
316
|
-
get_receipt: `Fetch a stored verdict by receipt id (GET /api/v1/receipts/{id}) to re-
|
|
316
|
+
get_receipt: `Fetch a stored verdict by receipt id (GET /api/v1/receipts/{id}) to re-read an earlier result. No key. To check that the receipt is genuine, use verify_receipt. ${LIMITS_SENTENCES.unsigned}`,
|
|
317
317
|
service_status: `Whether the validation API is up, with quota constants (GET /api/v1/validate/status); check after a timeout before resubmitting. No key. ${LIMITS_SENTENCES.scope}`,
|
|
318
318
|
company_financial_history: `SEC-reported financial history for one company from the canlicapital.com company reference (GET /company-data/{cik}.json), by cik or by ticker (resolved through GET /api/v1/company-tickers.json, companies in the release only). Without a concept it lists the available histories; with one it returns observations, newest first, each with its filing accession, form, filed date and unit, plus the SHA-256 of the original SEC response. No key required. ${COMPANY_REFERENCE_BOUNDARY}`,
|
|
319
319
|
});
|
|
320
|
+
|
|
321
|
+
// Output schemas (0.8.0). Published OPEN: extra fields always pass. A client validates a tool's
|
|
322
|
+
// structured result against the output schema it listed, so a closed schema turns every field a
|
|
323
|
+
// later version adds into a failed call (2026-09-26, the mcp-factory servers). Every field is
|
|
324
|
+
// optional because refusals and local results carry different subsets. They are kept terse: the
|
|
325
|
+
// tool list is re-sent to the model every turn, and one sentence per schema says what to read.
|
|
326
|
+
const refusal = z.looseObject({}).nullable().optional();
|
|
327
|
+
const sentences = z.array(z.string()).optional();
|
|
328
|
+
const loose = z.looseObject({}).optional();
|
|
329
|
+
|
|
330
|
+
export const validationOutput = z
|
|
331
|
+
.looseObject({
|
|
332
|
+
data: loose,
|
|
333
|
+
error: refusal,
|
|
334
|
+
limits: sentences,
|
|
335
|
+
receipt: z.looseObject({}).nullable().optional(),
|
|
336
|
+
computed: z.string().optional(),
|
|
337
|
+
note: z.string().optional(),
|
|
338
|
+
})
|
|
339
|
+
.describe("data.result holds the statistics (data.plain_reading states them); limits say what the result does not establish; receipt ({id, url}) is the signed record, null when none was stored; error ({code, message}) is set only on refusal.");
|
|
340
|
+
|
|
341
|
+
export const auditOutput = z
|
|
342
|
+
.looseObject({ readings: loose, checks: loose, not_run: loose, limits: sentences, note: z.string().optional(), error: refusal })
|
|
343
|
+
.describe("checks holds each check's full validate_ result by name; readings one sentence per check; not_run the checks skipped and why.");
|
|
344
|
+
|
|
345
|
+
export const keyOutput = z
|
|
346
|
+
.looseObject({ note: z.string().optional(), key_source: z.string().optional(), key_present: z.boolean().optional(), data: loose, error: refusal })
|
|
347
|
+
.describe("key_source says which key the session uses; data.key is set when a new key was issued.");
|
|
348
|
+
|
|
349
|
+
export const receiptOutput = z
|
|
350
|
+
.looseObject({ data: loose, error: refusal, limits: sentences })
|
|
351
|
+
.describe("data is the stored receipt: validator, input, output, their hashes, source hashes and signature.");
|
|
352
|
+
|
|
353
|
+
export const verifyReceiptOutput = z
|
|
354
|
+
.looseObject({ receipt_id: z.string().nullable().optional(), valid: z.boolean().optional(), checks: z.unknown().optional(), key_id: z.string().nullable().optional(), meaning: z.string().optional(), error: refusal })
|
|
355
|
+
.describe("valid is true only when the signature, output hash and content id all check out; checks lists each.");
|
|
356
|
+
|
|
357
|
+
export const statusOutput = z
|
|
358
|
+
.looseObject({ data: loose, limits: sentences, error: refusal })
|
|
359
|
+
.describe("data.store_reachable, data.quotas and, when available, data.usage for this key.");
|
|
360
|
+
|
|
361
|
+
export const companyHistoryOutput = z
|
|
362
|
+
.looseObject({ company: loose, claim_boundary: z.string().optional(), source: loose, histories: z.array(z.unknown()).optional(), history: loose, error: refusal })
|
|
363
|
+
.describe("Without a concept, histories lists what is available; with one, history holds the observations newest first as columns and rows, each with its filing. Values are as reported to the SEC.");
|
package/src/server.mjs
CHANGED
|
@@ -8,33 +8,13 @@
|
|
|
8
8
|
// CANLI_KEY; when CANLI_KEY is set, get_key does not call the network.
|
|
9
9
|
import { readFileSync, realpathSync } from "node:fs";
|
|
10
10
|
import { pathToFileURL } from "node:url";
|
|
11
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
11
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { computeLocally } from "./local.mjs";
|
|
14
14
|
import { readMatrixFile, readSeriesFile } from "./series-file.mjs";
|
|
15
15
|
import { verifyReceipt } from "./local/js/receipt-statement.js";
|
|
16
|
-
import { StdioServerTransport } from "@modelcontextprotocol/
|
|
17
|
-
import {
|
|
18
|
-
breadthInput,
|
|
19
|
-
trackRecordInput,
|
|
20
|
-
auditBacktestInput,
|
|
21
|
-
verifyReceiptToolShape,
|
|
22
|
-
backtestLengthInput,
|
|
23
|
-
haircutSharpeInput,
|
|
24
|
-
luckTrialsInput,
|
|
25
|
-
auditBacktestToolShape,
|
|
26
|
-
companyHistoryInput,
|
|
27
|
-
companyHistoryToolShape,
|
|
28
|
-
deflatedSharpeInput,
|
|
29
|
-
deflatedSharpeToolShape,
|
|
30
|
-
emptyInput,
|
|
31
|
-
getKeyInput,
|
|
32
|
-
getReceiptInput,
|
|
33
|
-
overfittingInput,
|
|
34
|
-
LIMITS_SENTENCES,
|
|
35
|
-
paperEvidenceInput,
|
|
36
|
-
TOOL_DESCRIPTIONS,
|
|
37
|
-
} from "./schemas.mjs";
|
|
16
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
17
|
+
import { breadthInput, trackRecordInput, auditBacktestInput, verifyReceiptToolShape, backtestLengthInput, haircutSharpeInput, luckTrialsInput, auditBacktestToolShape, companyHistoryInput, companyHistoryToolShape, deflatedSharpeInput, deflatedSharpeToolShape, emptyInput, getKeyInput, getReceiptInput, overfittingInput, LIMITS_SENTENCES, paperEvidenceInput, TOOL_DESCRIPTIONS, validationOutput, auditOutput, keyOutput, receiptOutput, verifyReceiptOutput, statusOutput, companyHistoryOutput } from "./schemas.mjs";
|
|
38
18
|
|
|
39
19
|
export const DEFAULT_BASE = "https://canlicapital.com";
|
|
40
20
|
export const SERVER_NAME = "canlicapital-validation-mcp";
|
|
@@ -132,6 +112,29 @@ async function callApi(session, { path, method = "GET", body }) {
|
|
|
132
112
|
return { envelope, failed: res.status >= 400 || Boolean(envelope?.error) };
|
|
133
113
|
}
|
|
134
114
|
|
|
115
|
+
// The hosted endpoint's shared anonymous key has one daily quota for every caller who connects
|
|
116
|
+
// without a key of their own. When it is used up, the answer is still computed, by the same code
|
|
117
|
+
// the API runs (src/local, byte for byte), on the hosted endpoint; what the caller loses is the
|
|
118
|
+
// stored, signed receipt, and the note says how to get one. A caller's own key, or a missing
|
|
119
|
+
// shared key, still gets the API's refusal unchanged.
|
|
120
|
+
export const SHARED_QUOTA_NOTE = "The shared anonymous quota of this hosted endpoint is used up for today (it resets at 00:00 UTC), so this result was computed by the same code on the hosted endpoint and no receipt was stored. For a receipt and a quota of your own, get a free key at https://canlicapital.com/developers#quickstart and send it as 'Authorization: Bearer <key>'. To run with no quota at all, on your own machine: npx -y canli-validation-mcp with CANLI_LOCAL=1.";
|
|
121
|
+
|
|
122
|
+
async function validateRemote(session, tool, path, body) {
|
|
123
|
+
let response = await callApi(session, { path, method: "POST", body });
|
|
124
|
+
// stdio without CANLI_KEY: the first validation used to come back 401 and the model had to work
|
|
125
|
+
// out that get_key comes first. Issue the free key once, as get_key would, and retry once.
|
|
126
|
+
if (response.failed && response.envelope?.error?.code === "unauthorized" && !session.hosted && !session.key && !session.envKey) {
|
|
127
|
+
const issued = await callApi(session, { path: "/api/v1/keys", method: "POST", body: { label: "auto" } });
|
|
128
|
+
if (!issued.failed && issued.envelope?.data?.key) {
|
|
129
|
+
session.key = issued.envelope.data.key;
|
|
130
|
+
response = await callApi(session, { path, method: "POST", body });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!response.failed || session.hosted?.keySource !== "shared" || response.envelope?.error?.code !== "quota_exhausted") return response;
|
|
134
|
+
const fallback = computeLocally(tool, body);
|
|
135
|
+
return { ...fallback, envelope: { ...fallback.envelope, computed: "hosted_without_receipt", note: SHARED_QUOTA_NOTE } };
|
|
136
|
+
}
|
|
137
|
+
|
|
135
138
|
// Compact context (0.3.0): minified JSON. Indentation is whitespace an agent pays for in tokens and
|
|
136
139
|
// never reads; every field, boundary sentence and provenance value is kept. Measured on the live
|
|
137
140
|
// Apple StockholdersEquity record (README, "Compact context"): 2,214 -> 1,560 tokens minified,
|
|
@@ -230,56 +233,56 @@ export async function toolValidateDeflatedSharpe(session, args) {
|
|
|
230
233
|
);
|
|
231
234
|
}
|
|
232
235
|
if (session.local) { const local = computeLocally("validate_deflated_sharpe", parsed.data); return validationText(session, local); }
|
|
233
|
-
const response = await
|
|
236
|
+
const response = await validateRemote(session, "validate_deflated_sharpe", "/api/v1/validate/deflated-sharpe", parsed.data);
|
|
234
237
|
return validationText(session, response);
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
export async function toolValidateOverfitting(session, args) {
|
|
238
241
|
const body = parseOrThrow(overfittingInput, args, "validate_overfitting");
|
|
239
242
|
if (session.local) { const local = computeLocally("validate_overfitting", body); return validationText(session, local); }
|
|
240
|
-
const response = await
|
|
243
|
+
const response = await validateRemote(session, "validate_overfitting", "/api/v1/validate/overfitting", body);
|
|
241
244
|
return validationText(session, response);
|
|
242
245
|
}
|
|
243
246
|
|
|
244
247
|
export async function toolValidatePaperEvidence(session, args) {
|
|
245
248
|
const body = parseOrThrow(paperEvidenceInput, args, "validate_paper_evidence");
|
|
246
249
|
if (session.local) { const local = computeLocally("validate_paper_evidence", body); return validationText(session, local); }
|
|
247
|
-
const response = await
|
|
250
|
+
const response = await validateRemote(session, "validate_paper_evidence", "/api/v1/validate/paper-evidence", body);
|
|
248
251
|
return validationText(session, response);
|
|
249
252
|
}
|
|
250
253
|
|
|
251
254
|
export async function toolValidateBreadth(session, args) {
|
|
252
255
|
const body = parseOrThrow(breadthInput, args, "validate_breadth");
|
|
253
256
|
if (session.local) { const local = computeLocally("validate_breadth", body); return validationText(session, local); }
|
|
254
|
-
const response = await
|
|
257
|
+
const response = await validateRemote(session, "validate_breadth", "/api/v1/validate/breadth", body);
|
|
255
258
|
return validationText(session, response);
|
|
256
259
|
}
|
|
257
260
|
|
|
258
261
|
export async function toolValidateTrackRecord(session, args) {
|
|
259
262
|
const body = parseOrThrow(trackRecordInput, args, "validate_track_record");
|
|
260
263
|
if (session.local) { const local = computeLocally("validate_track_record", body); return validationText(session, local); }
|
|
261
|
-
const response = await
|
|
264
|
+
const response = await validateRemote(session, "validate_track_record", "/api/v1/validate/track-record", body);
|
|
262
265
|
return validationText(session, response);
|
|
263
266
|
}
|
|
264
267
|
|
|
265
268
|
export async function toolValidateBacktestLength(session, args) {
|
|
266
269
|
const body = parseOrThrow(backtestLengthInput, args, "validate_backtest_length");
|
|
267
270
|
if (session.local) { const local = computeLocally("validate_backtest_length", body); return validationText(session, local); }
|
|
268
|
-
const response = await
|
|
271
|
+
const response = await validateRemote(session, "validate_backtest_length", "/api/v1/validate/backtest-length", body);
|
|
269
272
|
return validationText(session, response);
|
|
270
273
|
}
|
|
271
274
|
|
|
272
275
|
export async function toolValidateHaircutSharpe(session, args) {
|
|
273
276
|
const body = parseOrThrow(haircutSharpeInput, args, "validate_haircut_sharpe");
|
|
274
277
|
if (session.local) { const local = computeLocally("validate_haircut_sharpe", body); return validationText(session, local); }
|
|
275
|
-
const response = await
|
|
278
|
+
const response = await validateRemote(session, "validate_haircut_sharpe", "/api/v1/validate/haircut-sharpe", body);
|
|
276
279
|
return validationText(session, response);
|
|
277
280
|
}
|
|
278
281
|
|
|
279
282
|
export async function toolValidateLuckTrials(session, args) {
|
|
280
283
|
const body = parseOrThrow(luckTrialsInput, args, "validate_luck_trials");
|
|
281
284
|
if (session.local) { const local = computeLocally("validate_luck_trials", body); return validationText(session, local); }
|
|
282
|
-
const response = await
|
|
285
|
+
const response = await validateRemote(session, "validate_luck_trials", "/api/v1/validate/luck-trials", body);
|
|
283
286
|
return validationText(session, response);
|
|
284
287
|
}
|
|
285
288
|
|
|
@@ -287,7 +290,7 @@ export async function toolValidateLuckTrials(session, args) {
|
|
|
287
290
|
// through the API (one validation of quota, one receipt).
|
|
288
291
|
async function runValidator(session, tool, path, body) {
|
|
289
292
|
if (session.local) return computeLocally(tool, body);
|
|
290
|
-
return
|
|
293
|
+
return validateRemote(session, tool, path, body);
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
const sameJson = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
@@ -503,72 +506,72 @@ export function registerTools(server, session) {
|
|
|
503
506
|
const register = (name, ...rest) => { if (enabled.has(name)) server.registerTool(name, ...rest); };
|
|
504
507
|
register(
|
|
505
508
|
"get_key",
|
|
506
|
-
{ title: "Get a free validation key", annotations: { title: "Get a free validation key", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, description: TOOL_DESCRIPTIONS.get_key, inputSchema: getKeyInput },
|
|
509
|
+
{ title: "Get a free validation key", annotations: { title: "Get a free validation key", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, description: TOOL_DESCRIPTIONS.get_key, inputSchema: getKeyInput, outputSchema: keyOutput },
|
|
507
510
|
(args) => toolGetKey(session, args),
|
|
508
511
|
);
|
|
509
512
|
register(
|
|
510
513
|
"validate_deflated_sharpe",
|
|
511
|
-
{ title: "Validate deflated Sharpe", annotations: { title: "Validate deflated Sharpe", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_deflated_sharpe, inputSchema: deflatedSharpeToolShape },
|
|
514
|
+
{ title: "Validate deflated Sharpe", annotations: { title: "Validate deflated Sharpe", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_deflated_sharpe, inputSchema: deflatedSharpeToolShape, outputSchema: validationOutput },
|
|
512
515
|
(args) => toolValidateDeflatedSharpe(session, args),
|
|
513
516
|
);
|
|
514
517
|
register(
|
|
515
518
|
"validate_overfitting",
|
|
516
|
-
{ title: "Validate overfitting (CSCV)", annotations: { title: "Validate overfitting (CSCV)", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_overfitting, inputSchema: overfittingInput },
|
|
519
|
+
{ title: "Validate overfitting (CSCV)", annotations: { title: "Validate overfitting (CSCV)", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_overfitting, inputSchema: overfittingInput, outputSchema: validationOutput },
|
|
517
520
|
(args) => toolValidateOverfitting(session, args),
|
|
518
521
|
);
|
|
519
522
|
register(
|
|
520
523
|
"validate_paper_evidence",
|
|
521
|
-
{ title: "Validate paper evidence", annotations: { title: "Validate paper evidence", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_paper_evidence, inputSchema: paperEvidenceInput },
|
|
524
|
+
{ title: "Validate paper evidence", annotations: { title: "Validate paper evidence", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_paper_evidence, inputSchema: paperEvidenceInput, outputSchema: validationOutput },
|
|
522
525
|
(args) => toolValidatePaperEvidence(session, args),
|
|
523
526
|
);
|
|
524
527
|
register(
|
|
525
528
|
"validate_breadth",
|
|
526
|
-
{ title: "Validate breadth ceiling", annotations: { title: "Validate breadth ceiling", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_breadth, inputSchema: breadthInput },
|
|
529
|
+
{ title: "Validate breadth ceiling", annotations: { title: "Validate breadth ceiling", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_breadth, inputSchema: breadthInput, outputSchema: validationOutput },
|
|
527
530
|
(args) => toolValidateBreadth(session, args),
|
|
528
531
|
);
|
|
529
532
|
register(
|
|
530
533
|
"validate_track_record",
|
|
531
|
-
{ title: "Minimum track record length", annotations: { title: "Minimum track record length", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_track_record, inputSchema: trackRecordInput },
|
|
534
|
+
{ title: "Minimum track record length", annotations: { title: "Minimum track record length", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_track_record, inputSchema: trackRecordInput, outputSchema: validationOutput },
|
|
532
535
|
(args) => toolValidateTrackRecord(session, args),
|
|
533
536
|
);
|
|
534
537
|
register(
|
|
535
538
|
"validate_backtest_length",
|
|
536
|
-
{ title: "Minimum backtest length", annotations: { title: "Minimum backtest length", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_backtest_length, inputSchema: backtestLengthInput },
|
|
539
|
+
{ title: "Minimum backtest length", annotations: { title: "Minimum backtest length", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_backtest_length, inputSchema: backtestLengthInput, outputSchema: validationOutput },
|
|
537
540
|
(args) => toolValidateBacktestLength(session, args),
|
|
538
541
|
);
|
|
539
542
|
register(
|
|
540
543
|
"validate_haircut_sharpe",
|
|
541
|
-
{ title: "Haircut Sharpe ratio", annotations: { title: "Haircut Sharpe ratio", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_haircut_sharpe, inputSchema: haircutSharpeInput },
|
|
544
|
+
{ title: "Haircut Sharpe ratio", annotations: { title: "Haircut Sharpe ratio", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_haircut_sharpe, inputSchema: haircutSharpeInput, outputSchema: validationOutput },
|
|
542
545
|
(args) => toolValidateHaircutSharpe(session, args),
|
|
543
546
|
);
|
|
544
547
|
register(
|
|
545
548
|
"validate_luck_trials",
|
|
546
|
-
{ title: "Luck-equivalent trials", annotations: { title: "Luck-equivalent trials", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_luck_trials, inputSchema: luckTrialsInput },
|
|
549
|
+
{ title: "Luck-equivalent trials", annotations: { title: "Luck-equivalent trials", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.validate_luck_trials, inputSchema: luckTrialsInput, outputSchema: validationOutput },
|
|
547
550
|
(args) => toolValidateLuckTrials(session, args),
|
|
548
551
|
);
|
|
549
552
|
register(
|
|
550
553
|
"audit_backtest",
|
|
551
|
-
{ title: "Audit a backtest", annotations: { title: "Audit a backtest", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.audit_backtest, inputSchema: auditBacktestToolShape },
|
|
554
|
+
{ title: "Audit a backtest", annotations: { title: "Audit a backtest", ...WRITES_RECEIPT }, description: TOOL_DESCRIPTIONS.audit_backtest, inputSchema: auditBacktestToolShape, outputSchema: auditOutput },
|
|
552
555
|
(args) => toolAuditBacktest(session, args),
|
|
553
556
|
);
|
|
554
557
|
register(
|
|
555
558
|
"get_receipt",
|
|
556
|
-
{ title: "Get a receipt", annotations: { title: "Get a receipt", ...READ_ONLY }, description: TOOL_DESCRIPTIONS.get_receipt, inputSchema: getReceiptInput },
|
|
559
|
+
{ title: "Get a receipt", annotations: { title: "Get a receipt", ...READ_ONLY }, description: TOOL_DESCRIPTIONS.get_receipt, inputSchema: getReceiptInput, outputSchema: receiptOutput },
|
|
557
560
|
(args) => toolGetReceipt(session, args),
|
|
558
561
|
);
|
|
559
562
|
register(
|
|
560
563
|
"verify_receipt",
|
|
561
|
-
{ title: "Verify a receipt", annotations: { title: "Verify a receipt", ...READ_ONLY }, description: TOOL_DESCRIPTIONS.verify_receipt, inputSchema: verifyReceiptToolShape },
|
|
564
|
+
{ title: "Verify a receipt", annotations: { title: "Verify a receipt", ...READ_ONLY }, description: TOOL_DESCRIPTIONS.verify_receipt, inputSchema: verifyReceiptToolShape, outputSchema: verifyReceiptOutput },
|
|
562
565
|
(args) => toolVerifyReceipt(session, args),
|
|
563
566
|
);
|
|
564
567
|
register(
|
|
565
568
|
"service_status",
|
|
566
|
-
{ title: "Service status", annotations: { title: "Service status", ...READ_ONLY }, description: TOOL_DESCRIPTIONS.service_status, inputSchema: emptyInput },
|
|
569
|
+
{ title: "Service status", annotations: { title: "Service status", ...READ_ONLY }, description: TOOL_DESCRIPTIONS.service_status, inputSchema: emptyInput, outputSchema: statusOutput },
|
|
567
570
|
() => toolServiceStatus(session),
|
|
568
571
|
);
|
|
569
572
|
register(
|
|
570
573
|
"company_financial_history",
|
|
571
|
-
{ title: "Company financial history (SEC)", annotations: { title: "Company financial history (SEC)", ...READ_ONLY }, description: TOOL_DESCRIPTIONS.company_financial_history, inputSchema: companyHistoryToolShape },
|
|
574
|
+
{ title: "Company financial history (SEC)", annotations: { title: "Company financial history (SEC)", ...READ_ONLY }, description: TOOL_DESCRIPTIONS.company_financial_history, inputSchema: companyHistoryToolShape, outputSchema: companyHistoryOutput },
|
|
572
575
|
(args) => toolCompanyFinancialHistory(session, args),
|
|
573
576
|
);
|
|
574
577
|
}
|