freshcontext-mcp 0.3.19 → 0.3.21
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/FRESHCONTEXT_SPEC.md +317 -0
- package/METHODOLOGY.md +381 -0
- package/README.md +55 -5
- package/SECURITY.md +9 -7
- package/dist/adapters/arxiv.d.ts +15 -0
- package/dist/adapters/arxiv.js +3 -2
- package/dist/adapters/changelog.d.ts +2 -0
- package/dist/adapters/changelog.js +4 -2
- package/dist/adapters/finance.d.ts +2 -0
- package/dist/adapters/finance.js +1 -1
- package/dist/adapters/gdelt.d.ts +2 -0
- package/dist/adapters/gdelt.js +1 -1
- package/dist/adapters/gebiz.d.ts +2 -0
- package/dist/adapters/gebiz.js +1 -1
- package/dist/adapters/github.d.ts +2 -0
- package/dist/adapters/govcontracts.d.ts +2 -0
- package/dist/adapters/hackernews.d.ts +2 -0
- package/dist/adapters/jobs.d.ts +2 -0
- package/dist/adapters/jobs.js +6 -6
- package/dist/adapters/packageTrends.d.ts +2 -0
- package/dist/adapters/productHunt.d.ts +2 -0
- package/dist/adapters/reddit.d.ts +8 -0
- package/dist/adapters/reddit.js +12 -5
- package/dist/adapters/registry.d.ts +19 -0
- package/dist/adapters/repoSearch.d.ts +2 -0
- package/dist/adapters/repoSearch.js +1 -1
- package/dist/adapters/scholar.d.ts +2 -0
- package/dist/adapters/secFilings.d.ts +2 -0
- package/dist/adapters/secFilings.js +1 -1
- package/dist/adapters/yc.d.ts +2 -0
- package/dist/core/decay.d.ts +5 -0
- package/dist/core/decision.d.ts +3 -0
- package/dist/core/decision.js +1 -3
- package/dist/core/envelope.d.ts +5 -0
- package/dist/core/envelope.js +9 -1
- package/dist/core/explain.d.ts +12 -0
- package/dist/core/guards.d.ts +1 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.js +2 -0
- package/dist/core/pipeline.d.ts +3 -0
- package/dist/core/pipeline.js +8 -0
- package/dist/core/provenance.d.ts +5 -0
- package/dist/core/provenanceReadiness.d.ts +2 -0
- package/dist/core/provenanceReadiness.js +220 -0
- package/dist/core/rank.d.ts +4 -0
- package/dist/core/readable.d.ts +2 -0
- package/dist/core/readable.js +75 -0
- package/dist/core/signal.d.ts +3 -0
- package/dist/core/sourceProfiles.d.ts +4 -0
- package/dist/core/types.d.ts +239 -0
- package/dist/core/utility.d.ts +2 -0
- package/dist/rest/handler.d.ts +1 -0
- package/dist/security.d.ts +15 -0
- package/dist/security.js +3 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.js +2 -2
- package/dist/tools/evaluateContext.d.ts +21 -0
- package/dist/tools/evaluateContext.js +22 -1
- package/dist/tools/freshnessStamp.d.ts +1 -0
- package/dist/types.d.ts +1 -0
- package/docs/API_DESIGN.md +28 -1
- package/docs/CLIENT_SETUP.md +166 -0
- package/docs/CODEX_MCP_USAGE.md +4 -4
- package/docs/CORE_API.md +69 -5
- package/docs/CORE_MCP_BOUNDARY.md +13 -4
- package/docs/FUTURE_LANES.md +26 -3
- package/docs/HA_PRI_V2_DESIGN.md +7 -1
- package/docs/HA_PRI_V2_PRODUCTION_ENFORCEMENT_PLAN.md +414 -0
- package/docs/HUMAN_READABLE_OUTPUT_CONTRACT.md +293 -0
- package/docs/RELEASE_INTEGRITY.md +1 -1
- package/docs/RELEASE_NOTES.md +33 -5
- package/docs/SIGNAL_CONTRACT.md +200 -2
- package/package-script-guard.mjs +59 -3
- package/package.json +33 -13
- package/server.json +2 -2
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
export interface FreshContext {
|
|
2
|
+
content: string;
|
|
3
|
+
source_url: string;
|
|
4
|
+
content_date: string | null;
|
|
5
|
+
retrieved_at: string;
|
|
6
|
+
freshness_confidence: "high" | "medium" | "low";
|
|
7
|
+
freshness_score: number | null;
|
|
8
|
+
adapter: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ExtractOptions {
|
|
11
|
+
url: string;
|
|
12
|
+
prompt?: string;
|
|
13
|
+
maxLength?: number;
|
|
14
|
+
location?: string;
|
|
15
|
+
remoteOnly?: boolean;
|
|
16
|
+
maxAgeDays?: number;
|
|
17
|
+
keywords?: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface AdapterResult {
|
|
20
|
+
raw: string;
|
|
21
|
+
content_date: string | null;
|
|
22
|
+
freshness_confidence: "high" | "medium" | "low";
|
|
23
|
+
}
|
|
24
|
+
export interface EnvelopeFormatOptions {
|
|
25
|
+
unknownDateText?: string;
|
|
26
|
+
publishedLabel?: string;
|
|
27
|
+
}
|
|
28
|
+
export type SignalConfidence = "high" | "medium" | "low";
|
|
29
|
+
export type SignalDateConfidence = SignalConfidence | "unknown";
|
|
30
|
+
export type SignalContractVersion = "freshcontext.signal.v1";
|
|
31
|
+
export type SourceAuthorityHint = "high" | "medium" | "low";
|
|
32
|
+
export type SourceDatePolicy = "strict" | "balanced" | "lenient";
|
|
33
|
+
export type SourceFailurePolicy = "exclude" | "downgrade" | "warn";
|
|
34
|
+
export type SourceSurface = "mcp" | "rest" | "sdk" | "cli" | "operator";
|
|
35
|
+
export type SourceProfileId = "official_docs" | "code_activity" | "social_pulse" | "academic_research" | "market_finance" | "jobs_opportunities" | "government_regulatory" | "company_intel" | "composite_landscape" | "local_custom";
|
|
36
|
+
export type ContextDecision = "use_first" | "cite_as_primary" | "cite_as_supporting" | "use_as_background" | "needs_verification" | "needs_refresh" | "watch_only" | "exclude";
|
|
37
|
+
export type IntentProfileId = "citation_check" | "student_research" | "developer_adoption" | "job_search" | "market_watch" | "business_due_diligence" | "medical_literature_triage";
|
|
38
|
+
export type ContextUtilityStatus = "success" | "partial" | "stale" | "failed" | "unknown";
|
|
39
|
+
export interface SignalNormalizeOptions {
|
|
40
|
+
defaultSourceType?: string;
|
|
41
|
+
now?: Date | string;
|
|
42
|
+
}
|
|
43
|
+
export interface SourceProfile {
|
|
44
|
+
profile_id: SourceProfileId;
|
|
45
|
+
source_types: string[];
|
|
46
|
+
purpose: string;
|
|
47
|
+
default_decay_lambda: number;
|
|
48
|
+
half_life_hours: number;
|
|
49
|
+
authority_hint: SourceAuthorityHint;
|
|
50
|
+
date_policy: SourceDatePolicy;
|
|
51
|
+
failure_policy: SourceFailurePolicy;
|
|
52
|
+
recommended_surfaces: SourceSurface[];
|
|
53
|
+
}
|
|
54
|
+
export interface ContextDecisionOptions {
|
|
55
|
+
sourceProfile?: SourceProfile | SourceProfileId;
|
|
56
|
+
intentProfile?: IntentProfileId;
|
|
57
|
+
}
|
|
58
|
+
export interface ContextDecisionResult {
|
|
59
|
+
decision: ContextDecision;
|
|
60
|
+
label: string;
|
|
61
|
+
meaning: string;
|
|
62
|
+
action: string;
|
|
63
|
+
reasons: string[];
|
|
64
|
+
warnings: string[];
|
|
65
|
+
}
|
|
66
|
+
export interface HumanReadableHandoffResult {
|
|
67
|
+
safe_for_agent_handoff: boolean;
|
|
68
|
+
reason: string;
|
|
69
|
+
}
|
|
70
|
+
export interface HumanReadableContextResult {
|
|
71
|
+
label: string;
|
|
72
|
+
summary: string;
|
|
73
|
+
why: string[];
|
|
74
|
+
action: string;
|
|
75
|
+
warnings: string[];
|
|
76
|
+
handoff: HumanReadableHandoffResult;
|
|
77
|
+
}
|
|
78
|
+
export interface FreshContextSignalInput {
|
|
79
|
+
id?: string;
|
|
80
|
+
source: string;
|
|
81
|
+
source_type?: string;
|
|
82
|
+
title?: string;
|
|
83
|
+
content?: string;
|
|
84
|
+
published_at?: string | null;
|
|
85
|
+
content_date?: string | null;
|
|
86
|
+
retrieved_at?: string | null;
|
|
87
|
+
semantic_score?: number;
|
|
88
|
+
date_confidence?: SignalDateConfidence;
|
|
89
|
+
freshness_confidence?: SignalConfidence;
|
|
90
|
+
status?: ContextUtilityStatus;
|
|
91
|
+
metadata?: Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
export interface FreshContextSignal {
|
|
94
|
+
contract_version: SignalContractVersion;
|
|
95
|
+
id?: string;
|
|
96
|
+
source: string;
|
|
97
|
+
source_type: string;
|
|
98
|
+
title?: string;
|
|
99
|
+
content?: string;
|
|
100
|
+
published_at: string | null;
|
|
101
|
+
retrieved_at: string;
|
|
102
|
+
semantic_score: number;
|
|
103
|
+
date_confidence: SignalDateConfidence;
|
|
104
|
+
status: ContextUtilityStatus;
|
|
105
|
+
metadata: Record<string, unknown>;
|
|
106
|
+
reasons: string[];
|
|
107
|
+
}
|
|
108
|
+
export interface FreshSignal {
|
|
109
|
+
id?: string;
|
|
110
|
+
source: string;
|
|
111
|
+
source_type?: string;
|
|
112
|
+
title?: string;
|
|
113
|
+
content?: string;
|
|
114
|
+
published_at?: string | null;
|
|
115
|
+
content_date?: string | null;
|
|
116
|
+
retrieved_at?: string | null;
|
|
117
|
+
semantic_score: number;
|
|
118
|
+
date_confidence?: SignalDateConfidence;
|
|
119
|
+
freshness_confidence?: SignalConfidence;
|
|
120
|
+
status?: ContextUtilityStatus;
|
|
121
|
+
metadata?: Record<string, unknown>;
|
|
122
|
+
}
|
|
123
|
+
export interface RankedSignal extends FreshSignal {
|
|
124
|
+
freshness_score: number | null;
|
|
125
|
+
final_score: number;
|
|
126
|
+
confidence: SignalConfidence;
|
|
127
|
+
reason: string;
|
|
128
|
+
}
|
|
129
|
+
export interface RankOptions {
|
|
130
|
+
semanticWeight?: number;
|
|
131
|
+
freshnessWeight?: number;
|
|
132
|
+
defaultSourceType?: string;
|
|
133
|
+
now?: Date | string;
|
|
134
|
+
}
|
|
135
|
+
export interface ContextUtilityInput {
|
|
136
|
+
contextualRelevance: number;
|
|
137
|
+
lambda: number;
|
|
138
|
+
ageHours: number;
|
|
139
|
+
dateConfidence?: SignalConfidence | "unknown";
|
|
140
|
+
status?: ContextUtilityStatus;
|
|
141
|
+
}
|
|
142
|
+
export interface ContextUtilityResult {
|
|
143
|
+
score: number;
|
|
144
|
+
contextualRelevance: number;
|
|
145
|
+
decayFactor: number;
|
|
146
|
+
dateConfidenceFactor: number;
|
|
147
|
+
statusFactor: number;
|
|
148
|
+
lambda: number;
|
|
149
|
+
ageHours: number;
|
|
150
|
+
status: ContextUtilityStatus;
|
|
151
|
+
reasons: string[];
|
|
152
|
+
}
|
|
153
|
+
export interface HaPriV2Input {
|
|
154
|
+
resultId: string;
|
|
155
|
+
rawContent: string;
|
|
156
|
+
semanticFingerprint?: string | null;
|
|
157
|
+
adapter: string;
|
|
158
|
+
publishedAt?: string | null;
|
|
159
|
+
retrievedAt?: string | null;
|
|
160
|
+
engineVersion: string;
|
|
161
|
+
}
|
|
162
|
+
export interface HaPriV2Material {
|
|
163
|
+
version: "FRESHCONTEXT_HA_PRI_V2";
|
|
164
|
+
resultId: string;
|
|
165
|
+
canonicalContentSha256: string;
|
|
166
|
+
semanticFingerprintSha256: string;
|
|
167
|
+
adapter: string;
|
|
168
|
+
publishedAt: string;
|
|
169
|
+
retrievedAt: string;
|
|
170
|
+
engineVersion: string;
|
|
171
|
+
signingPayload: string;
|
|
172
|
+
}
|
|
173
|
+
export interface HaPriV2Result extends HaPriV2Material {
|
|
174
|
+
haPriSigV2: string;
|
|
175
|
+
}
|
|
176
|
+
export type HaPriVerificationStatus = "valid" | "invalid" | "unknown";
|
|
177
|
+
export interface HaPriV2VerificationResult {
|
|
178
|
+
status: HaPriVerificationStatus;
|
|
179
|
+
expected: string | null;
|
|
180
|
+
actual: string | null;
|
|
181
|
+
reasons: string[];
|
|
182
|
+
}
|
|
183
|
+
export type ProvenanceReadinessState = "complete" | "partial" | "incomplete" | "unknown" | "derived";
|
|
184
|
+
export type ProvenanceSourceIdentityCompleteness = "complete" | "weak" | "missing" | "unusable";
|
|
185
|
+
export type ProvenanceTimingCompleteness = "complete" | "partial" | "missing" | "unknown";
|
|
186
|
+
export type ProvenanceReadinessInput = Partial<FreshContextSignalInput> | FreshContextSignal;
|
|
187
|
+
export interface ProvenanceReadinessOptions extends SignalNormalizeOptions {
|
|
188
|
+
resultId?: string;
|
|
189
|
+
semanticFingerprint?: string | null;
|
|
190
|
+
engineVersion?: string;
|
|
191
|
+
}
|
|
192
|
+
export interface ProvenanceSourceIdentityResult {
|
|
193
|
+
source: string | null;
|
|
194
|
+
source_type: string | null;
|
|
195
|
+
result_id: string | null;
|
|
196
|
+
completeness: ProvenanceSourceIdentityCompleteness;
|
|
197
|
+
}
|
|
198
|
+
export interface ProvenanceReadinessResult {
|
|
199
|
+
state: ProvenanceReadinessState;
|
|
200
|
+
source_identity: ProvenanceSourceIdentityResult;
|
|
201
|
+
source_type: string | null;
|
|
202
|
+
published_at: string | null;
|
|
203
|
+
retrieved_at: string | null;
|
|
204
|
+
timing_confidence: SignalDateConfidence;
|
|
205
|
+
timing_completeness: ProvenanceTimingCompleteness;
|
|
206
|
+
canonical_content_sha256: string | null;
|
|
207
|
+
semantic_fingerprint_sha256: string | null;
|
|
208
|
+
ha_pri_v2: HaPriV2Result | null;
|
|
209
|
+
warnings: string[];
|
|
210
|
+
reasons: string[];
|
|
211
|
+
}
|
|
212
|
+
export interface CoreSignalProvenanceOptions {
|
|
213
|
+
resultId?: string;
|
|
214
|
+
semanticFingerprint?: string | null;
|
|
215
|
+
engineVersion?: string;
|
|
216
|
+
}
|
|
217
|
+
export interface CoreSignalEnvelopeResult {
|
|
218
|
+
context: FreshContext;
|
|
219
|
+
text: string;
|
|
220
|
+
structured: object;
|
|
221
|
+
}
|
|
222
|
+
export interface CoreSignalEvaluationOptions extends SignalNormalizeOptions, RankOptions {
|
|
223
|
+
includeEnvelope?: boolean;
|
|
224
|
+
envelopeMaxLength?: number;
|
|
225
|
+
envelopeFormat?: EnvelopeFormatOptions;
|
|
226
|
+
includeProvenance?: boolean;
|
|
227
|
+
provenance?: CoreSignalProvenanceOptions;
|
|
228
|
+
}
|
|
229
|
+
export interface CoreSignalEvaluationResult {
|
|
230
|
+
signal: FreshContextSignal;
|
|
231
|
+
freshness_score: number | null;
|
|
232
|
+
utility: ContextUtilityResult;
|
|
233
|
+
ranked: RankedSignal;
|
|
234
|
+
explanation: string;
|
|
235
|
+
envelope?: CoreSignalEnvelopeResult;
|
|
236
|
+
provenance?: HaPriV2Result;
|
|
237
|
+
provenance_readiness: ProvenanceReadinessResult;
|
|
238
|
+
reasons: string[];
|
|
239
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function handleRestRequest(request: Request): Promise<Response>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* freshcontext-mcp security module
|
|
3
|
+
* Input sanitization, domain allowlists, and request validation
|
|
4
|
+
*/
|
|
5
|
+
export declare const ALLOWED_DOMAINS: Record<string, string[]>;
|
|
6
|
+
export declare const MAX_URL_LENGTH = 500;
|
|
7
|
+
export declare const MAX_QUERY_LENGTH = 200;
|
|
8
|
+
export declare const MAX_PACKAGES_LENGTH = 300;
|
|
9
|
+
export declare class SecurityError extends Error {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
export declare function validateUrl(rawUrl: string, adapterName: keyof typeof ALLOWED_DOMAINS): string;
|
|
13
|
+
export declare function sanitizeQuery(query: string, maxLength?: number): string;
|
|
14
|
+
export declare function sanitizePackages(input: string): string;
|
|
15
|
+
export declare function formatSecurityError(err: unknown): string;
|
package/dist/security.js
CHANGED
|
@@ -10,9 +10,11 @@ export const ALLOWED_DOMAINS = {
|
|
|
10
10
|
yc: ["www.ycombinator.com", "ycombinator.com"],
|
|
11
11
|
repoSearch: [], // uses GitHub API directly, no browser
|
|
12
12
|
packageTrends: [], // uses npm/PyPI APIs directly, no browser
|
|
13
|
-
reddit: [
|
|
13
|
+
reddit: ["www.reddit.com", "reddit.com", "old.reddit.com"],
|
|
14
14
|
finance: [], // uses Stooq quote API, no browser
|
|
15
|
+
arxiv: ["export.arxiv.org", "arxiv.org"],
|
|
15
16
|
productHunt: ["www.producthunt.com", "producthunt.com"],
|
|
17
|
+
changelog: [], // accepts public changelog URLs but blocks private/internal targets
|
|
16
18
|
};
|
|
17
19
|
// ─── Blocked IP ranges and internal hostnames ────────────────────────────────
|
|
18
20
|
const BLOCKED_PATTERNS = [
|
package/dist/server.d.ts
ADDED
package/dist/server.js
CHANGED
|
@@ -23,7 +23,7 @@ import { EvaluateContextInputError, evaluateContextInput, formatEvaluateContextR
|
|
|
23
23
|
import { formatSecurityError } from "./security.js";
|
|
24
24
|
const server = new McpServer({
|
|
25
25
|
name: "freshcontext-mcp",
|
|
26
|
-
version: "0.3.
|
|
26
|
+
version: "0.3.21",
|
|
27
27
|
});
|
|
28
28
|
const signalInputSchema = z.object({
|
|
29
29
|
id: z.string().optional(),
|
|
@@ -46,7 +46,7 @@ server.registerTool("evaluate_context", {
|
|
|
46
46
|
inputSchema: z.object({
|
|
47
47
|
profile: z.string().min(1).describe("Source Profile id, e.g. academic_research, jobs_opportunities, market_finance, official_docs, local_custom."),
|
|
48
48
|
intent: z.string().min(1).describe("Intent Profile id, e.g. citation_check, student_research, developer_adoption, job_search, market_watch, business_due_diligence, medical_literature_triage."),
|
|
49
|
-
signals: z.array(signalInputSchema).min(1).describe("Candidate context items provided by the caller. FreshContext evaluates these; it does not retrieve them."),
|
|
49
|
+
signals: z.array(signalInputSchema).min(1).max(100).describe("Candidate context items provided by the caller. FreshContext evaluates these; it does not retrieve them."),
|
|
50
50
|
now: z.string().optional().describe("Optional ISO timestamp for deterministic evaluation."),
|
|
51
51
|
}),
|
|
52
52
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ContextDecisionResult, CoreSignalEvaluationResult, IntentProfileId, SourceProfile } from "../core/index.js";
|
|
2
|
+
export declare class EvaluateContextInputError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export interface EvaluateContextInput {
|
|
6
|
+
profile: string;
|
|
7
|
+
intent: string;
|
|
8
|
+
signals: unknown;
|
|
9
|
+
now?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface EvaluateContextItem {
|
|
12
|
+
evaluation: CoreSignalEvaluationResult;
|
|
13
|
+
decision: ContextDecisionResult;
|
|
14
|
+
}
|
|
15
|
+
export interface EvaluateContextResult {
|
|
16
|
+
profile: SourceProfile;
|
|
17
|
+
intent: IntentProfileId;
|
|
18
|
+
items: EvaluateContextItem[];
|
|
19
|
+
}
|
|
20
|
+
export declare function evaluateContextInput(input: EvaluateContextInput): EvaluateContextResult;
|
|
21
|
+
export declare function formatEvaluateContextResult(result: EvaluateContextResult): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { evaluateSignals, getSourceProfile, interpretEvaluations, } from "../core/index.js";
|
|
1
|
+
import { evaluateSignals, getSourceProfile, interpretEvaluations, toReadableContextResult, } from "../core/index.js";
|
|
2
2
|
const SUPPORTED_INTENTS = [
|
|
3
3
|
"citation_check",
|
|
4
4
|
"student_research",
|
|
@@ -8,6 +8,10 @@ const SUPPORTED_INTENTS = [
|
|
|
8
8
|
"business_due_diligence",
|
|
9
9
|
"medical_literature_triage",
|
|
10
10
|
];
|
|
11
|
+
const MAX_CONTEXT_SIGNALS = 100;
|
|
12
|
+
const MAX_SOURCE_CHARS = 2048;
|
|
13
|
+
const MAX_TITLE_CHARS = 1000;
|
|
14
|
+
const MAX_CONTENT_CHARS = 50000;
|
|
11
15
|
export class EvaluateContextInputError extends Error {
|
|
12
16
|
constructor(message) {
|
|
13
17
|
super(message);
|
|
@@ -20,6 +24,12 @@ function isRecord(value) {
|
|
|
20
24
|
function isIntentProfileId(value) {
|
|
21
25
|
return SUPPORTED_INTENTS.includes(value);
|
|
22
26
|
}
|
|
27
|
+
function assertMaxLength(value, field, maxLength, index) {
|
|
28
|
+
if (typeof value === "string" && value.length > maxLength) {
|
|
29
|
+
const prefix = index === undefined ? "" : `signals[${index}].`;
|
|
30
|
+
throw new EvaluateContextInputError(`${prefix}${field} exceeds maximum length of ${maxLength} characters.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
23
33
|
function validateSignal(value, index) {
|
|
24
34
|
if (!isRecord(value)) {
|
|
25
35
|
throw new EvaluateContextInputError(`signals[${index}] must be an object.`);
|
|
@@ -27,6 +37,9 @@ function validateSignal(value, index) {
|
|
|
27
37
|
if (typeof value.source !== "string" || value.source.trim().length === 0) {
|
|
28
38
|
throw new EvaluateContextInputError(`signals[${index}].source must be a non-empty string.`);
|
|
29
39
|
}
|
|
40
|
+
assertMaxLength(value.source, "source", MAX_SOURCE_CHARS, index);
|
|
41
|
+
assertMaxLength(value.title, "title", MAX_TITLE_CHARS, index);
|
|
42
|
+
assertMaxLength(value.content, "content", MAX_CONTENT_CHARS, index);
|
|
30
43
|
if ((typeof value.title !== "string" || value.title.trim().length === 0)
|
|
31
44
|
&& (typeof value.content !== "string" || value.content.trim().length === 0)) {
|
|
32
45
|
throw new EvaluateContextInputError(`signals[${index}] must include title or content.`);
|
|
@@ -52,6 +65,12 @@ export function evaluateContextInput(input) {
|
|
|
52
65
|
if (input.signals.length === 0) {
|
|
53
66
|
throw new EvaluateContextInputError("signals must contain at least one candidate context item.");
|
|
54
67
|
}
|
|
68
|
+
if (input.signals.length > MAX_CONTEXT_SIGNALS) {
|
|
69
|
+
throw new EvaluateContextInputError(`signals must contain at most ${MAX_CONTEXT_SIGNALS} candidate context items.`);
|
|
70
|
+
}
|
|
71
|
+
if (input.now !== undefined && Number.isNaN(new Date(input.now).getTime())) {
|
|
72
|
+
throw new EvaluateContextInputError("now must be a valid timestamp string when provided.");
|
|
73
|
+
}
|
|
55
74
|
const signals = input.signals.map(validateSignal);
|
|
56
75
|
const options = input.now ? { now: input.now } : {};
|
|
57
76
|
const evaluations = evaluateSignals(signals, options);
|
|
@@ -120,6 +139,8 @@ export function formatEvaluateContextResult(result) {
|
|
|
120
139
|
utility_score: item.evaluation.utility.score,
|
|
121
140
|
confidence: item.evaluation.ranked.confidence,
|
|
122
141
|
why: item.evaluation.explanation,
|
|
142
|
+
provenance_readiness: item.evaluation.provenance_readiness,
|
|
143
|
+
readable: toReadableContextResult(item.evaluation, item.decision),
|
|
123
144
|
})),
|
|
124
145
|
};
|
|
125
146
|
lines.push("[FRESHCONTEXT_EVALUATION_JSON]", JSON.stringify(structured, null, 2), "[/FRESHCONTEXT_EVALUATION_JSON]");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LAMBDA, stampFreshness, toStructuredJSON, formatForLLM, } from "../core/index.js";
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { FreshContext, ExtractOptions, AdapterResult } from "./core/types.js";
|
package/docs/API_DESIGN.md
CHANGED
|
@@ -12,7 +12,7 @@ The REST host should expose the simplest useful path:
|
|
|
12
12
|
|
|
13
13
|
1. User has raw retrieved signals.
|
|
14
14
|
2. User sends those signals to FreshContext.
|
|
15
|
-
3. Core evaluates freshness, confidence, ranking, explanation, optional envelope, and optional provenance.
|
|
15
|
+
3. Core evaluates freshness, confidence, ranking, explanation, provenance readiness, optional envelope, and optional provenance.
|
|
16
16
|
4. Host returns ranked context.
|
|
17
17
|
5. Agent or app uses best context first.
|
|
18
18
|
|
|
@@ -122,6 +122,27 @@ Shape: `CoreSignalEvaluationResult`.
|
|
|
122
122
|
"reason": "Strong semantic match and current freshness for blog."
|
|
123
123
|
},
|
|
124
124
|
"explanation": "Strong semantic match and current freshness for blog.",
|
|
125
|
+
"provenance_readiness": {
|
|
126
|
+
"state": "complete",
|
|
127
|
+
"source_identity": {
|
|
128
|
+
"source": "https://example.com/article",
|
|
129
|
+
"source_type": "blog",
|
|
130
|
+
"result_id": "sig_001",
|
|
131
|
+
"completeness": "complete"
|
|
132
|
+
},
|
|
133
|
+
"source_type": "blog",
|
|
134
|
+
"published_at": "2026-05-24T12:00:00.000Z",
|
|
135
|
+
"retrieved_at": "2026-05-24T13:00:00.000Z",
|
|
136
|
+
"timing_confidence": "high",
|
|
137
|
+
"timing_completeness": "complete",
|
|
138
|
+
"canonical_content_sha256": "abc123...",
|
|
139
|
+
"semantic_fingerprint_sha256": null,
|
|
140
|
+
"ha_pri_v2": null,
|
|
141
|
+
"warnings": [],
|
|
142
|
+
"reasons": [
|
|
143
|
+
"semantic fingerprint was not provided"
|
|
144
|
+
]
|
|
145
|
+
},
|
|
125
146
|
"envelope": {
|
|
126
147
|
"context": {},
|
|
127
148
|
"text": "[FRESHCONTEXT]...",
|
|
@@ -131,6 +152,8 @@ Shape: `CoreSignalEvaluationResult`.
|
|
|
131
152
|
}
|
|
132
153
|
```
|
|
133
154
|
|
|
155
|
+
`provenance_readiness` is always a Core judgment sidecar. It classifies source identity and timing completeness. It must not be used by the REST host to fetch, verify truth, reject context by policy, or change ranking.
|
|
156
|
+
|
|
134
157
|
The REST host must not fetch upstream data, cache results, write D1, enforce Ha-Pri, or alter ranking policy.
|
|
135
158
|
|
|
136
159
|
## POST /v1/evaluate-batch
|
|
@@ -181,6 +204,9 @@ Evaluates and ranks multiple signals.
|
|
|
181
204
|
"final_score": 0.85
|
|
182
205
|
},
|
|
183
206
|
"explanation": "Strong semantic match and current freshness for blog.",
|
|
207
|
+
"provenance_readiness": {
|
|
208
|
+
"state": "complete"
|
|
209
|
+
},
|
|
184
210
|
"reasons": []
|
|
185
211
|
}
|
|
186
212
|
]
|
|
@@ -342,6 +368,7 @@ Core owns:
|
|
|
342
368
|
- timestamp/future-date/failure guards
|
|
343
369
|
- freshness scoring
|
|
344
370
|
- context utility sidecar
|
|
371
|
+
- provenance readiness sidecar
|
|
345
372
|
- default rank/explain behavior
|
|
346
373
|
- optional envelope generation
|
|
347
374
|
- optional Ha-Pri v2 material preparation
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# FreshContext Client Setup
|
|
2
|
+
|
|
3
|
+
FreshContext is live as an MCP stdio package on npm.
|
|
4
|
+
|
|
5
|
+
Use this guide when connecting Claude Desktop, Codex, or another MCP-compatible client to the published package.
|
|
6
|
+
|
|
7
|
+
## What You Should See
|
|
8
|
+
|
|
9
|
+
FreshContext `0.3.21` exposes:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
22 tools = evaluate_context + 21 read-only reference adapters
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The primary interface is:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
evaluate_context
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Use it when another retriever, agent, database, note parser, PDF extractor, or local script already has candidate context and needs FreshContext to judge what deserves to reach the model.
|
|
22
|
+
|
|
23
|
+
## Claude Desktop: Published Package
|
|
24
|
+
|
|
25
|
+
Add this to your Claude Desktop config, then restart Claude.
|
|
26
|
+
|
|
27
|
+
macOS:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
~/Library/Application Support/Claude/claude_desktop_config.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Windows:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
%APPDATA%\Claude\claude_desktop_config.json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Config:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"freshcontext": {
|
|
45
|
+
"command": "npx",
|
|
46
|
+
"args": ["-y", "freshcontext-mcp@latest"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
If you previously installed an older global package, refresh it:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install -g freshcontext-mcp@latest
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then this config is also valid when the global npm bin path is visible to Claude:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"freshcontext": {
|
|
64
|
+
"command": "freshcontext-mcp",
|
|
65
|
+
"args": []
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Codex: Local MCP Config
|
|
72
|
+
|
|
73
|
+
For Codex local MCP config, use the published package through `npx`:
|
|
74
|
+
|
|
75
|
+
```toml
|
|
76
|
+
[mcp_servers.freshcontext]
|
|
77
|
+
command = "npx"
|
|
78
|
+
args = ["-y", "freshcontext-mcp@latest"]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If you prefer a source checkout while developing FreshContext itself:
|
|
82
|
+
|
|
83
|
+
```toml
|
|
84
|
+
[mcp_servers.freshcontext]
|
|
85
|
+
command = "node"
|
|
86
|
+
args = ["C:\\Users\\YOUR_USERNAME\\path\\to\\freshcontext-mcp\\dist\\server.js"]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Keep local MCP config files out of git. Do not commit machine-specific paths or credentials.
|
|
90
|
+
|
|
91
|
+
## Source Checkout Setup
|
|
92
|
+
|
|
93
|
+
Use this when contributing to FreshContext itself:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
git clone https://github.com/PrinceGabriel-lgtm/freshcontext-mcp
|
|
97
|
+
cd freshcontext-mcp
|
|
98
|
+
npm install
|
|
99
|
+
npm run build
|
|
100
|
+
npm run smoke:stdio
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Expected smoke result:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"ok": true,
|
|
108
|
+
"package_version": "0.3.21",
|
|
109
|
+
"server_version": "0.3.21",
|
|
110
|
+
"tool_count": 22
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Remote Worker Boundary
|
|
115
|
+
|
|
116
|
+
The repository also declares a remote Streamable HTTP MCP endpoint:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
https://freshcontext-mcp.gimmanuel73.workers.dev/mcp
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Some clients can use `mcp-remote`:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"freshcontext-remote": {
|
|
128
|
+
"command": "npx",
|
|
129
|
+
"args": ["-y", "mcp-remote", "https://freshcontext-mcp.gimmanuel73.workers.dev/mcp"]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The npm/local stdio package remains the safest default client path. The hosted Worker endpoint was verified on 2026-06-12 at `0.3.20 / 22 tools` with `evaluate_context` present and returning decision-first output. Because the Worker is a separate deployment surface, re-run remote verification before claiming future package interfaces are live there.
|
|
136
|
+
|
|
137
|
+
## ChatGPT / OpenAI Connector Boundary
|
|
138
|
+
|
|
139
|
+
Claude and Codex MCP paths are documented now.
|
|
140
|
+
|
|
141
|
+
ChatGPT connector compatibility requires a separate search/fetch compatibility audit before being claimed. Do not assume ChatGPT connector support from Claude/Codex MCP compatibility alone.
|
|
142
|
+
|
|
143
|
+
## Quick Test Prompt
|
|
144
|
+
|
|
145
|
+
After connecting a client, ask it to use `evaluate_context` with this candidate context:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"profile": "academic_research",
|
|
150
|
+
"intent": "citation_check",
|
|
151
|
+
"signals": [
|
|
152
|
+
{
|
|
153
|
+
"title": "Fresh research source",
|
|
154
|
+
"content": "A relevant academic source with a reliable publication date.",
|
|
155
|
+
"source": "https://arxiv.org/abs/2605.12345",
|
|
156
|
+
"source_type": "arxiv",
|
|
157
|
+
"published_at": "2026-05-24T12:00:00.000Z",
|
|
158
|
+
"retrieved_at": "2026-05-24T13:00:00.000Z",
|
|
159
|
+
"semantic_score": 0.94,
|
|
160
|
+
"date_confidence": "high"
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Expected result: decision-first output with a decision, meaning, action, warnings, supporting scores, and a structured FreshContext evaluation JSON block.
|
package/docs/CODEX_MCP_USAGE.md
CHANGED
|
@@ -67,7 +67,7 @@ command = "npx"
|
|
|
67
67
|
args = ["-y", "mcp-remote", "https://freshcontext-mcp.gimmanuel73.workers.dev/mcp"]
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
This remote path was
|
|
70
|
+
This remote path was verified on 2026-06-12 as a live Worker MCP endpoint exposing `0.3.20 / 22 tools`, including `evaluate_context`. That confirms Worker availability and MCP tool discovery. It does not by itself claim Codex Cloud support or guarantee every MCP client can use the remote bridge without its own client-specific setup check.
|
|
71
71
|
|
|
72
72
|
## Verification steps
|
|
73
73
|
|
|
@@ -83,8 +83,8 @@ Expected result:
|
|
|
83
83
|
```json
|
|
84
84
|
{
|
|
85
85
|
"ok": true,
|
|
86
|
-
"package_version": "0.3.
|
|
87
|
-
"server_version": "0.3.
|
|
86
|
+
"package_version": "0.3.21",
|
|
87
|
+
"server_version": "0.3.21",
|
|
88
88
|
"tool_count": 22
|
|
89
89
|
}
|
|
90
90
|
```
|
|
@@ -102,7 +102,7 @@ Expected result: no output and exit code 0.
|
|
|
102
102
|
- Do not place secrets, credentials, registry tokens, npm tokens, GitHub tokens, or Cloudflare tokens in Codex MCP config.
|
|
103
103
|
- Do not read, edit, print, or commit local token files, local environment files, registry credentials, Cloudflare local state, or Wrangler state.
|
|
104
104
|
- Do not commit local Codex config or machine-specific paths.
|
|
105
|
-
- Prefer the local stdio path for
|
|
105
|
+
- Prefer the local stdio path for source-checkout compatibility checks because it is verified by `npm run smoke:stdio`.
|
|
106
106
|
- Do not claim Codex Cloud support unless it is separately tested and documented.
|
|
107
107
|
|
|
108
108
|
## Troubleshooting
|