@clien-ai/mcp 0.15.0 → 0.15.1
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/CHANGELOG.md +19 -0
- package/dist/hypothesis-semantics.js +9 -1
- package/dist/hypothesis-semantics.js.map +1 -1
- package/dist/tools/market-signals.js +3 -12
- package/dist/tools/market-signals.js.map +1 -1
- package/dist/tools/market-sizing-proof.js +3 -10
- package/dist/tools/market-sizing-proof.js.map +1 -1
- package/dist/tools/report-digest.js +43 -37
- package/dist/tools/report-digest.js.map +1 -1
- package/dist/tools/scoped-research.js +4 -2
- package/dist/tools/scoped-research.js.map +1 -1
- package/dist/types/receipt-url.js +105 -0
- package/dist/types/receipt-url.js.map +1 -0
- package/dist/types/report.js +38 -40
- package/dist/types/report.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FUL-1219 / FUL-1225: which stored source URLs and platform labels an MCP channel may SHOW, and how a
|
|
3
|
+
* receipt whose URL the projection withheld still answers the questions its URL used to answer.
|
|
4
|
+
*
|
|
5
|
+
* Imports nothing, so `hypothesis-semantics.ts` (which `report.ts` imports) can read it too.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ THE MARKERS BELOW ARE TRUSTED ONLY ON PROJECTED DATA. `projectReportDataForMcp` deletes any
|
|
8
|
+
* stored copy before it sets them, so a reader must be handed the output of exactly one projection:
|
|
9
|
+
* on unprojected report JSON a forged `urlWithheld` would read as a reachable receipt.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The `href` of a stored source URL an MCP channel may show, or null.
|
|
13
|
+
*
|
|
14
|
+
* The producer writes each URL through `normalizeUrl` (`agent/src/exa-fetch-tool.ts`), i.e. a WHATWG
|
|
15
|
+
* `URL` serialisation, which is always printable ASCII. So a stored URL with any other character —
|
|
16
|
+
* a newline, a tab, a bidi or zero-width mark, a `←` — was not written by the producer and is
|
|
17
|
+
* refused. `new URL()` alone would NOT refuse it: the parser silently strips tabs and newlines, so
|
|
18
|
+
* the raw string would pass and reach `structuredContent` unchanged. Also refuses non-http(s) and
|
|
19
|
+
* credential-carrying URLs.
|
|
20
|
+
*/
|
|
21
|
+
export function safeSourceUrl(value) {
|
|
22
|
+
if (typeof value !== 'string' || !/^https?:\/\/[\x21-\x7e]+$/i.test(value))
|
|
23
|
+
return null;
|
|
24
|
+
return isSafeHttpDestination(value) ? new URL(value).href : null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* FUL-1225: a stored `platform` label an MCP channel may show: printable ASCII on one line (space
|
|
28
|
+
* allowed), else null. Anything else (a newline, a bidi or zero-width mark) was not a host label.
|
|
29
|
+
*/
|
|
30
|
+
export function safeSourcePlatform(value) {
|
|
31
|
+
return typeof value === 'string' && /^[\x20-\x7e]*$/.test(value) ? value : null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* FUL-534's reachability test: a non-credentialed http(s) URL the WHATWG parser accepts. Decides
|
|
35
|
+
* whether a receipt earns its GROUNDED / Cited presentation. It is deliberately NOT the display
|
|
36
|
+
* test ({@link safeSourceUrl}): tightening it would downgrade claims, which FUL-1225 must not do.
|
|
37
|
+
*/
|
|
38
|
+
export function isSafeHttpDestination(value) {
|
|
39
|
+
if (typeof value !== 'string' || !/^https?:\/\//i.test(value))
|
|
40
|
+
return false;
|
|
41
|
+
try {
|
|
42
|
+
const url = new URL(value);
|
|
43
|
+
return (url.protocol === 'http:' || url.protocol === 'https:') && !url.username && !url.password;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Set by the projection on a `reportEvidence` or persona-source row whose URL it blanked
|
|
51
|
+
* (`url: ''`) because the URL is unsafe to show, while the stored URL still passed
|
|
52
|
+
* {@link isSafeHttpDestination}. It means "still a receipt, link withheld".
|
|
53
|
+
*/
|
|
54
|
+
export const RECEIPT_URL_WITHHELD_KEY = 'urlWithheld';
|
|
55
|
+
/**
|
|
56
|
+
* Set beside {@link RECEIPT_URL_WITHHELD_KEY}: the withheld URL's hostname (ASCII by WHATWG
|
|
57
|
+
* construction, and re-checked). Integrity checks that compare hosts (`sameNamedVendor`) read it,
|
|
58
|
+
* so withholding a URL for display never changes an integrity or coverage decision.
|
|
59
|
+
*/
|
|
60
|
+
export const RECEIPT_URL_WITHHELD_HOST_KEY = 'urlWithheldHost';
|
|
61
|
+
/** The hostname the projection preserves for a withheld URL, or null. */
|
|
62
|
+
export function withheldUrlHost(value) {
|
|
63
|
+
if (!isSafeHttpDestination(value))
|
|
64
|
+
return null;
|
|
65
|
+
const host = new URL(value).hostname;
|
|
66
|
+
return /^[a-z0-9.-]+$/.test(host) ? host : null;
|
|
67
|
+
}
|
|
68
|
+
function asRow(row) {
|
|
69
|
+
return row !== null && typeof row === 'object' && !Array.isArray(row) ? row : null;
|
|
70
|
+
}
|
|
71
|
+
function isWithheld(record) {
|
|
72
|
+
return record[RECEIPT_URL_WITHHELD_KEY] === true && record.url === '';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* FUL-534 + FUL-1225: does a receipt row have a safe destination, shown or withheld? Every
|
|
76
|
+
* receipt-granting reader calls this instead of testing `row.url`, so blanking an unprintable URL
|
|
77
|
+
* never changes a claim's state or tally.
|
|
78
|
+
*/
|
|
79
|
+
export function receiptRowHasSafeDestination(row) {
|
|
80
|
+
const record = asRow(row);
|
|
81
|
+
if (!record)
|
|
82
|
+
return false;
|
|
83
|
+
if (isWithheld(record))
|
|
84
|
+
return true;
|
|
85
|
+
return isSafeHttpDestination(record.url);
|
|
86
|
+
}
|
|
87
|
+
/** The hostname behind a receipt row's URL, shown or withheld, or null. */
|
|
88
|
+
export function receiptRowHostname(row) {
|
|
89
|
+
const record = asRow(row);
|
|
90
|
+
if (!record)
|
|
91
|
+
return null;
|
|
92
|
+
if (isWithheld(record)) {
|
|
93
|
+
const host = record[RECEIPT_URL_WITHHELD_HOST_KEY];
|
|
94
|
+
return typeof host === 'string' && /^[a-z0-9.-]+$/.test(host) ? host : null;
|
|
95
|
+
}
|
|
96
|
+
if (typeof record.url !== 'string')
|
|
97
|
+
return null;
|
|
98
|
+
try {
|
|
99
|
+
return new URL(record.url).hostname;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=receipt-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receipt-url.js","sourceRoot":"","sources":["../../src/types/receipt-url.ts"],"names":[],"mappings":"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"}
|
package/dist/types/report.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
import { CONFIDENCE_BASES, CONFIDENCE_LEVELS, EVIDENCE_SUFFICIENCY_LEVELS, HYPOTHESIS_PRIORITIES, HYPOTHESIS_SEMANTICS_VERSION, LEGACY_HYPOTHESIS_SEMANTICS_VERSION, ROBUSTNESS_OUTCOMES, VERDICT_STATUSES, finalClauseCoverageMatchesIntegrity, hasCoherentRobustnessWorking, hypothesisWebReceiptAuthenticates, normalizeFinalHypothesisState, readRobustnessCounts, reportClaimNeedsIndependentMarketAuthority, robustnessWorkingContradictsSummary, sourceAuthenticatesMarketEconomics, } from '../hypothesis-semantics.js';
|
|
16
|
+
import { RECEIPT_URL_WITHHELD_HOST_KEY, RECEIPT_URL_WITHHELD_KEY, receiptRowHasSafeDestination, safeSourcePlatform, safeSourceUrl, withheldUrlHost, } from './receipt-url.js';
|
|
16
17
|
// ⚠️ A types module importing from `tools/` reads backwards, and it is deliberate.
|
|
17
18
|
// `content-type-display.ts` imports NOTHING — it is the package's content-type vocabulary, which
|
|
18
19
|
// happens to live beside the two renderers that were its first callers, and the FUL-341 coupling
|
|
@@ -696,29 +697,9 @@ export function isModelAttestedReportClaim(claim) {
|
|
|
696
697
|
return typeof sourceId === 'string' && sourceId.trim().length > 0 &&
|
|
697
698
|
typeof quote === 'string' && quote.trim().length > 0;
|
|
698
699
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
* The producer writes each URL through `normalizeUrl` (`agent/src/exa-fetch-tool.ts`), i.e. a WHATWG
|
|
703
|
-
* `URL` serialisation, which is always printable ASCII. So a stored URL with any other character —
|
|
704
|
-
* a newline, a tab, a bidi or zero-width mark, a `←` — was not written by the producer and is
|
|
705
|
-
* refused. `new URL()` alone would NOT refuse it: the parser silently strips tabs and newlines, so
|
|
706
|
-
* the raw string would pass and reach `structuredContent` unchanged. Also refuses non-http(s) and
|
|
707
|
-
* credential-carrying URLs.
|
|
708
|
-
*/
|
|
709
|
-
export function safeUnverifiedEvidenceUrl(value) {
|
|
710
|
-
if (typeof value !== 'string' || !/^https?:\/\/[\x21-\x7e]+$/i.test(value))
|
|
711
|
-
return null;
|
|
712
|
-
try {
|
|
713
|
-
const url = new URL(value);
|
|
714
|
-
if ((url.protocol !== 'http:' && url.protocol !== 'https:') || url.username || url.password)
|
|
715
|
-
return null;
|
|
716
|
-
return url.href;
|
|
717
|
-
}
|
|
718
|
-
catch {
|
|
719
|
-
return null;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
700
|
+
// FUL-1219 / FUL-1225: the URL display and reachability helpers live in `receipt-url.ts` (it imports
|
|
701
|
+
// nothing, so `hypothesis-semantics.ts` can read them too); re-exported for this module's callers.
|
|
702
|
+
export { RECEIPT_URL_WITHHELD_HOST_KEY, RECEIPT_URL_WITHHELD_KEY, isSafeHttpDestination, receiptRowHasSafeDestination, safeSourcePlatform, safeSourceUrl, } from './receipt-url.js';
|
|
722
703
|
/**
|
|
723
704
|
* A single REPORT-level (market/competitor) claim. Shape mirrors {@link ClaimSchema}
|
|
724
705
|
* but carries a `section` instead of a `personaId` — report claims are not attributed
|
|
@@ -1208,7 +1189,7 @@ export const ResearchReportDataSchema = z
|
|
|
1208
1189
|
reportEvidence: z
|
|
1209
1190
|
.array(ReportEvidenceSchema)
|
|
1210
1191
|
.optional()
|
|
1211
|
-
.describe('The reportClaims receipt pool: cached market/competitor evidence pages ("RRCP-s{n}" indexes this array) with the verbatim highlight a claim\'s span was matched against.'),
|
|
1192
|
+
.describe('The reportClaims receipt pool: cached market/competitor evidence pages ("RRCP-s{n}" indexes this array) with the verbatim highlight a claim\'s span was matched against. A row with `url: ""` and `urlWithheld: true` (plus `urlWithheldHost`) is still a receipt whose stored URL was unsafe to show; its claims keep their state.'),
|
|
1212
1193
|
/**
|
|
1213
1194
|
* FUL-1219: the pool behind `reportClaims[].unverifiedSources`. Soft per row so one bad row
|
|
1214
1195
|
* becomes a hole and every other "RUVS-s{n}" keeps its index.
|
|
@@ -1387,18 +1368,6 @@ export function projectReportDataForMcp(raw) {
|
|
|
1387
1368
|
const asProjectionRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
1388
1369
|
? value
|
|
1389
1370
|
: null;
|
|
1390
|
-
const safeProjectionUrl = (value) => {
|
|
1391
|
-
if (typeof value !== 'string' || !/^https?:\/\//i.test(value))
|
|
1392
|
-
return false;
|
|
1393
|
-
try {
|
|
1394
|
-
const url = new URL(value);
|
|
1395
|
-
return (url.protocol === 'http:' || url.protocol === 'https:') &&
|
|
1396
|
-
!url.username && !url.password;
|
|
1397
|
-
}
|
|
1398
|
-
catch {
|
|
1399
|
-
return false;
|
|
1400
|
-
}
|
|
1401
|
-
};
|
|
1402
1371
|
const hasProjectionVisibleContent = (value) => typeof value === 'string' &&
|
|
1403
1372
|
value.replace(/[\p{C}\p{Z}\p{Default_Ignorable_Code_Point}]/gu, '').length > 0;
|
|
1404
1373
|
const hasHighlights = Object.prototype.hasOwnProperty.call(record, 'interviewHighlights');
|
|
@@ -1451,6 +1420,27 @@ export function projectReportDataForMcp(raw) {
|
|
|
1451
1420
|
if (!source)
|
|
1452
1421
|
return rawSource;
|
|
1453
1422
|
const next = { ...source };
|
|
1423
|
+
// FUL-1225: a URL the display check refuses is blanked, with the host-derived `platform`, so
|
|
1424
|
+
// no channel carries it. The row itself stays, so every RRCP-s{n} / RCP-p{i}-s{j} keeps its
|
|
1425
|
+
// index, and the markers keep FUL-534's reachability answer and the hostname for every
|
|
1426
|
+
// receipt-granting or host-comparing reader (`receiptRowHasSafeDestination`,
|
|
1427
|
+
// `receiptRowHostname`): no claim or integrity decision changes. Stored markers are untrusted.
|
|
1428
|
+
delete next[RECEIPT_URL_WITHHELD_KEY];
|
|
1429
|
+
delete next[RECEIPT_URL_WITHHELD_HOST_KEY];
|
|
1430
|
+
if (Object.prototype.hasOwnProperty.call(source, 'url') && source.url !== '' && !safeSourceUrl(source.url)) {
|
|
1431
|
+
next.url = '';
|
|
1432
|
+
if (Object.prototype.hasOwnProperty.call(source, 'platform'))
|
|
1433
|
+
next.platform = '';
|
|
1434
|
+
const host = withheldUrlHost(source.url);
|
|
1435
|
+
if (host !== null) {
|
|
1436
|
+
next[RECEIPT_URL_WITHHELD_KEY] = true;
|
|
1437
|
+
next[RECEIPT_URL_WITHHELD_HOST_KEY] = host;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
else if (Object.prototype.hasOwnProperty.call(source, 'platform') && safeSourcePlatform(source.platform) === null) {
|
|
1441
|
+
// A platform label that is not printable ASCII on one line reaches no channel either.
|
|
1442
|
+
next.platform = '';
|
|
1443
|
+
}
|
|
1454
1444
|
if (Object.prototype.hasOwnProperty.call(source, 'quote')) {
|
|
1455
1445
|
if (typeof source.quote === 'string' && !isDisplaySuitableExcerpt(source.quote)) {
|
|
1456
1446
|
// `undefined` is absent on the JSON wire but remains distinguishable from a historical
|
|
@@ -1550,6 +1540,14 @@ export function projectReportDataForMcp(raw) {
|
|
|
1550
1540
|
if (!thread)
|
|
1551
1541
|
return rawThread;
|
|
1552
1542
|
const nextThread = { ...thread };
|
|
1543
|
+
// FUL-1225: a thread URL is display-only (every forum receipt renders from its cached
|
|
1544
|
+
// excerpt without one), so an unsafe one is simply dropped.
|
|
1545
|
+
if (Object.prototype.hasOwnProperty.call(thread, 'url') && thread.url !== '' && !safeSourceUrl(thread.url)) {
|
|
1546
|
+
delete nextThread.url;
|
|
1547
|
+
}
|
|
1548
|
+
if (Object.prototype.hasOwnProperty.call(thread, 'platform') && safeSourcePlatform(thread.platform) === null) {
|
|
1549
|
+
delete nextThread.platform;
|
|
1550
|
+
}
|
|
1553
1551
|
if (Array.isArray(thread.relevantQuotes)) {
|
|
1554
1552
|
nextThread.relevantQuotes = thread.relevantQuotes.filter(isDisplaySuitableExcerpt);
|
|
1555
1553
|
}
|
|
@@ -1615,9 +1613,9 @@ export function projectReportDataForMcp(raw) {
|
|
|
1615
1613
|
const unverifiedEvidence = Array.isArray(record.unverifiedEvidence)
|
|
1616
1614
|
? record.unverifiedEvidence.map((rawRow) => {
|
|
1617
1615
|
const row = asProjectionRecord(rawRow);
|
|
1618
|
-
if (!row || !
|
|
1616
|
+
if (!row || !safeSourceUrl(row.url))
|
|
1619
1617
|
return undefined;
|
|
1620
|
-
return Object.fromEntries(Object.keys(UnverifiedEvidenceSchema.shape).filter((key) => key in row).map((key) => [key, row[key]]));
|
|
1618
|
+
return Object.fromEntries(Object.keys(UnverifiedEvidenceSchema.shape).filter((key) => key in row).map((key) => [key, key === 'platform' ? safeSourcePlatform(row[key]) ?? '' : row[key]]));
|
|
1621
1619
|
})
|
|
1622
1620
|
: [];
|
|
1623
1621
|
if (hasUnverifiedEvidence) {
|
|
@@ -1653,7 +1651,7 @@ export function projectReportDataForMcp(raw) {
|
|
|
1653
1651
|
const row = index === null ? undefined : unverifiedEvidence[index];
|
|
1654
1652
|
if (!parsed.success || !row || row.section !== claim.section)
|
|
1655
1653
|
return [];
|
|
1656
|
-
const href =
|
|
1654
|
+
const href = safeSourceUrl(row.url);
|
|
1657
1655
|
if (!href || seen.has(href))
|
|
1658
1656
|
return [];
|
|
1659
1657
|
seen.add(href);
|
|
@@ -1897,7 +1895,7 @@ export function projectReportDataForMcp(raw) {
|
|
|
1897
1895
|
? projected.reportEvidence[Number(match[1])]
|
|
1898
1896
|
: undefined);
|
|
1899
1897
|
return Boolean(source &&
|
|
1900
|
-
|
|
1898
|
+
receiptRowHasSafeDestination(source) &&
|
|
1901
1899
|
(!Object.prototype.hasOwnProperty.call(source, 'quote') ||
|
|
1902
1900
|
isDisplaySuitableExcerpt(source.quote)) &&
|
|
1903
1901
|
(source.section === 'market' || source.section === 'competitor' || source.section === 'summary'));
|