freshcontext-mcp 0.3.18 → 0.3.20
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 +87 -19
- package/dist/adapters/arxiv.js +2 -1
- package/dist/adapters/changelog.js +4 -2
- package/dist/adapters/finance.js +1 -1
- package/dist/adapters/gdelt.js +1 -1
- package/dist/adapters/gebiz.js +1 -1
- package/dist/adapters/reddit.js +11 -4
- package/dist/adapters/repoSearch.js +1 -1
- package/dist/adapters/secFilings.js +1 -1
- package/dist/core/envelope.js +9 -1
- package/dist/security.js +3 -1
- package/dist/server.js +40 -2
- package/dist/tools/evaluateContext.js +146 -0
- package/docs/CLIENT_SETUP.md +166 -0
- package/docs/CODEX_MCP_USAGE.md +7 -7
- package/docs/CORE_API.md +12 -8
- package/docs/CORE_MCP_BOUNDARY.md +106 -0
- package/docs/FUTURE_LANES.md +196 -0
- package/docs/HA_PRI_V2_DESIGN.md +7 -1
- package/docs/HA_PRI_V2_PRODUCTION_ENFORCEMENT_PLAN.md +414 -0
- package/docs/RELEASE_INTEGRITY.md +2 -0
- package/docs/RELEASE_NOTES.md +22 -5
- package/docs/SIGNAL_CONTRACT.md +213 -17
- package/docs/SOURCE_PROFILES.md +3 -3
- package/package-script-guard.mjs +76 -28
- package/package.json +14 -7
- package/server.json +3 -3
- package/docs/OPERATIONAL_DEMO_RUNBOOK.md +0 -458
package/dist/adapters/reddit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizeQuery, validateUrl } from "../security.js";
|
|
1
2
|
/**
|
|
2
3
|
* Reddit adapter — public JSON API, no auth required.
|
|
3
4
|
* Accepts subreddit URLs or search queries.
|
|
@@ -6,10 +7,15 @@
|
|
|
6
7
|
*/
|
|
7
8
|
export async function redditAdapter(options) {
|
|
8
9
|
let apiUrl = options.url;
|
|
9
|
-
// If they pass a plain subreddit
|
|
10
|
+
// If they pass a plain subreddit or search query, build a Reddit JSON URL.
|
|
10
11
|
if (!apiUrl.startsWith("http")) {
|
|
11
|
-
const clean = apiUrl.replace(/^r\//, "");
|
|
12
|
-
|
|
12
|
+
const clean = sanitizeQuery(apiUrl, 120).replace(/^r\//, "").replace(/^\/+|\/+$/g, "");
|
|
13
|
+
if (/^[A-Za-z0-9_]{2,21}$/.test(clean)) {
|
|
14
|
+
apiUrl = `https://www.reddit.com/r/${clean}/.json?limit=25&sort=hot`;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
apiUrl = `https://www.reddit.com/search.json?q=${encodeURIComponent(clean)}&sort=new&limit=25`;
|
|
18
|
+
}
|
|
13
19
|
}
|
|
14
20
|
// Ensure we hit the JSON endpoint
|
|
15
21
|
if (!apiUrl.includes(".json")) {
|
|
@@ -19,7 +25,8 @@ export async function redditAdapter(options) {
|
|
|
19
25
|
if (!apiUrl.includes("limit=")) {
|
|
20
26
|
apiUrl += (apiUrl.includes("?") ? "&" : "?") + "limit=25";
|
|
21
27
|
}
|
|
22
|
-
const
|
|
28
|
+
const safeUrl = validateUrl(apiUrl, "reddit");
|
|
29
|
+
const res = await fetch(safeUrl, {
|
|
23
30
|
headers: {
|
|
24
31
|
"User-Agent": "freshcontext-mcp/0.1.5 (https://github.com/PrinceGabriel-lgtm/freshcontext-mcp)",
|
|
25
32
|
"Accept": "application/json",
|
|
@@ -22,7 +22,7 @@ export async function repoSearchAdapter(options) {
|
|
|
22
22
|
const res = await fetch(apiUrl, {
|
|
23
23
|
headers: {
|
|
24
24
|
Accept: "application/vnd.github.v3+json",
|
|
25
|
-
"User-Agent": "freshcontext-mcp/0.3.
|
|
25
|
+
"User-Agent": "freshcontext-mcp/0.3.19 (https://github.com/PrinceGabriel-lgtm/freshcontext-mcp)",
|
|
26
26
|
},
|
|
27
27
|
});
|
|
28
28
|
if (!res.ok) {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
const HEADERS = {
|
|
14
14
|
"Accept": "application/json",
|
|
15
|
-
"User-Agent": "freshcontext-mcp/0.3.
|
|
15
|
+
"User-Agent": "freshcontext-mcp/0.3.19 (https://github.com/PrinceGabriel-lgtm/freshcontext-mcp)",
|
|
16
16
|
};
|
|
17
17
|
async function fetchSecFilings(query, maxResults = 10) {
|
|
18
18
|
const today = new Date().toISOString().slice(0, 10);
|
package/dist/core/envelope.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { calculateFreshnessScore, isMeaningfullyFutureDate, scoreLabel } from "./decay.js";
|
|
2
2
|
import { looksLikeFailedAdapterContent } from "./guards.js";
|
|
3
|
+
export const MAX_ENVELOPE_CONTENT_LENGTH = 20000;
|
|
4
|
+
function clampEnvelopeMaxLength(maxLength) {
|
|
5
|
+
if (maxLength === 0)
|
|
6
|
+
return 0;
|
|
7
|
+
if (maxLength === undefined || !Number.isFinite(maxLength))
|
|
8
|
+
return 8000;
|
|
9
|
+
return Math.min(MAX_ENVELOPE_CONTENT_LENGTH, Math.max(1, Math.floor(maxLength)));
|
|
10
|
+
}
|
|
3
11
|
export function stampFreshness(result, options, adapter) {
|
|
4
12
|
const retrieved_at = new Date().toISOString();
|
|
5
13
|
const failedContent = looksLikeFailedAdapterContent(result.raw);
|
|
@@ -8,7 +16,7 @@ export function stampFreshness(result, options, adapter) {
|
|
|
8
16
|
const freshness_confidence = failedContent || futureDated ? "low" : result.freshness_confidence;
|
|
9
17
|
const freshness_score = calculateFreshnessScore(content_date, retrieved_at, adapter);
|
|
10
18
|
return {
|
|
11
|
-
content: result.raw.slice(0, options.maxLength
|
|
19
|
+
content: result.raw.slice(0, clampEnvelopeMaxLength(options.maxLength)),
|
|
12
20
|
source_url: options.url,
|
|
13
21
|
content_date,
|
|
14
22
|
retrieved_at,
|
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.js
CHANGED
|
@@ -19,12 +19,50 @@ import { secFilingsAdapter } from "./adapters/secFilings.js";
|
|
|
19
19
|
import { gdeltAdapter } from "./adapters/gdelt.js";
|
|
20
20
|
import { gebizAdapter } from "./adapters/gebiz.js";
|
|
21
21
|
import { stampFreshness, formatForLLM } from "./tools/freshnessStamp.js";
|
|
22
|
+
import { EvaluateContextInputError, evaluateContextInput, formatEvaluateContextResult, } from "./tools/evaluateContext.js";
|
|
22
23
|
import { formatSecurityError } from "./security.js";
|
|
23
24
|
const server = new McpServer({
|
|
24
25
|
name: "freshcontext-mcp",
|
|
25
|
-
version: "0.3.
|
|
26
|
+
version: "0.3.20",
|
|
26
27
|
});
|
|
27
|
-
|
|
28
|
+
const signalInputSchema = z.object({
|
|
29
|
+
id: z.string().optional(),
|
|
30
|
+
source: z.string().min(1).describe("Source URL, URI, document id, or stable source label."),
|
|
31
|
+
source_type: z.string().optional().describe("Source type such as arxiv, jobs, official_docs, custom, or user_provided."),
|
|
32
|
+
title: z.string().optional(),
|
|
33
|
+
content: z.string().optional(),
|
|
34
|
+
published_at: z.string().nullable().optional(),
|
|
35
|
+
content_date: z.string().nullable().optional(),
|
|
36
|
+
retrieved_at: z.string().nullable().optional(),
|
|
37
|
+
semantic_score: z.number().optional().describe("Optional relevance score from 0..1. Core clamps out-of-range values."),
|
|
38
|
+
date_confidence: z.enum(["high", "medium", "low", "unknown"]).optional(),
|
|
39
|
+
freshness_confidence: z.enum(["high", "medium", "low"]).optional(),
|
|
40
|
+
status: z.enum(["success", "partial", "stale", "failed", "unknown"]).optional(),
|
|
41
|
+
metadata: z.record(z.unknown()).optional(),
|
|
42
|
+
}).passthrough();
|
|
43
|
+
// Tool: evaluate_context
|
|
44
|
+
server.registerTool("evaluate_context", {
|
|
45
|
+
description: "Evaluate caller-provided candidate context and return decision-ready output. This is the primary FreshContext judgment path: it does not fetch, crawl, scrape, browse, read folders, or call adapters.",
|
|
46
|
+
inputSchema: z.object({
|
|
47
|
+
profile: z.string().min(1).describe("Source Profile id, e.g. academic_research, jobs_opportunities, market_finance, official_docs, local_custom."),
|
|
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).max(100).describe("Candidate context items provided by the caller. FreshContext evaluates these; it does not retrieve them."),
|
|
50
|
+
now: z.string().optional().describe("Optional ISO timestamp for deterministic evaluation."),
|
|
51
|
+
}),
|
|
52
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
53
|
+
}, async ({ profile, intent, signals, now }) => {
|
|
54
|
+
try {
|
|
55
|
+
const result = evaluateContextInput({ profile, intent, signals, now });
|
|
56
|
+
return { content: [{ type: "text", text: formatEvaluateContextResult(result) }] };
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (err instanceof EvaluateContextInputError) {
|
|
60
|
+
return { content: [{ type: "text", text: `[FreshContext evaluate_context error]\n${err.message}` }] };
|
|
61
|
+
}
|
|
62
|
+
return { content: [{ type: "text", text: formatSecurityError(err) }] };
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// ─── Reference adapter: extract_github ───────────────────────────────────────
|
|
28
66
|
server.registerTool("extract_github", {
|
|
29
67
|
description: "Extract real-time data from a GitHub repository — README, stars, forks, language, topics, last commit. Returns timestamped freshcontext.",
|
|
30
68
|
inputSchema: z.object({
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { evaluateSignals, getSourceProfile, interpretEvaluations, } from "../core/index.js";
|
|
2
|
+
const SUPPORTED_INTENTS = [
|
|
3
|
+
"citation_check",
|
|
4
|
+
"student_research",
|
|
5
|
+
"developer_adoption",
|
|
6
|
+
"job_search",
|
|
7
|
+
"market_watch",
|
|
8
|
+
"business_due_diligence",
|
|
9
|
+
"medical_literature_triage",
|
|
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;
|
|
15
|
+
export class EvaluateContextInputError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "EvaluateContextInputError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function isRecord(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function isIntentProfileId(value) {
|
|
25
|
+
return SUPPORTED_INTENTS.includes(value);
|
|
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
|
+
}
|
|
33
|
+
function validateSignal(value, index) {
|
|
34
|
+
if (!isRecord(value)) {
|
|
35
|
+
throw new EvaluateContextInputError(`signals[${index}] must be an object.`);
|
|
36
|
+
}
|
|
37
|
+
if (typeof value.source !== "string" || value.source.trim().length === 0) {
|
|
38
|
+
throw new EvaluateContextInputError(`signals[${index}].source must be a non-empty string.`);
|
|
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);
|
|
43
|
+
if ((typeof value.title !== "string" || value.title.trim().length === 0)
|
|
44
|
+
&& (typeof value.content !== "string" || value.content.trim().length === 0)) {
|
|
45
|
+
throw new EvaluateContextInputError(`signals[${index}] must include title or content.`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...value,
|
|
49
|
+
source: value.source,
|
|
50
|
+
title: typeof value.title === "string" ? value.title : undefined,
|
|
51
|
+
content: typeof value.content === "string" ? value.content : undefined,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function evaluateContextInput(input) {
|
|
55
|
+
const profile = getSourceProfile(input.profile);
|
|
56
|
+
if (!profile) {
|
|
57
|
+
throw new EvaluateContextInputError(`Unknown source profile: ${input.profile}.`);
|
|
58
|
+
}
|
|
59
|
+
if (!isIntentProfileId(input.intent)) {
|
|
60
|
+
throw new EvaluateContextInputError(`Unsupported intent profile: ${input.intent}.`);
|
|
61
|
+
}
|
|
62
|
+
if (!Array.isArray(input.signals)) {
|
|
63
|
+
throw new EvaluateContextInputError("signals must be an array.");
|
|
64
|
+
}
|
|
65
|
+
if (input.signals.length === 0) {
|
|
66
|
+
throw new EvaluateContextInputError("signals must contain at least one candidate context item.");
|
|
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
|
+
}
|
|
74
|
+
const signals = input.signals.map(validateSignal);
|
|
75
|
+
const options = input.now ? { now: input.now } : {};
|
|
76
|
+
const evaluations = evaluateSignals(signals, options);
|
|
77
|
+
const decisions = interpretEvaluations(evaluations, {
|
|
78
|
+
sourceProfile: profile,
|
|
79
|
+
intentProfile: input.intent,
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
profile,
|
|
83
|
+
intent: input.intent,
|
|
84
|
+
items: evaluations.map((evaluation, index) => ({
|
|
85
|
+
evaluation,
|
|
86
|
+
decision: decisions[index],
|
|
87
|
+
})),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function sourceTitle(evaluation) {
|
|
91
|
+
if (evaluation.signal.title)
|
|
92
|
+
return evaluation.signal.title;
|
|
93
|
+
if (evaluation.signal.content)
|
|
94
|
+
return evaluation.signal.content.slice(0, 80);
|
|
95
|
+
return evaluation.signal.source;
|
|
96
|
+
}
|
|
97
|
+
function formatFreshness(score) {
|
|
98
|
+
return score === null ? "unknown" : `${Math.round(score)}/100`;
|
|
99
|
+
}
|
|
100
|
+
function formatRank(score) {
|
|
101
|
+
return score.toFixed(3);
|
|
102
|
+
}
|
|
103
|
+
function formatUtility(score) {
|
|
104
|
+
return `${Number(score.toFixed(1))}/100`;
|
|
105
|
+
}
|
|
106
|
+
function formatList(values) {
|
|
107
|
+
return values.length > 0 ? values.join("; ") : "None";
|
|
108
|
+
}
|
|
109
|
+
export function formatEvaluateContextResult(result) {
|
|
110
|
+
const lines = [
|
|
111
|
+
"FreshContext evaluate_context",
|
|
112
|
+
"Candidate context -> Core evaluation -> decision-ready context",
|
|
113
|
+
"",
|
|
114
|
+
`Profile: ${result.profile.profile_id}`,
|
|
115
|
+
`Purpose: ${result.profile.purpose}`,
|
|
116
|
+
`Intent: ${result.intent}`,
|
|
117
|
+
"",
|
|
118
|
+
];
|
|
119
|
+
result.items.forEach((item, index) => {
|
|
120
|
+
const { evaluation, decision } = item;
|
|
121
|
+
lines.push(`${index + 1}. ${sourceTitle(evaluation)}`, ` Decision: ${decision.label}`, ` Meaning: ${decision.meaning}`, ` Action: ${decision.action}`, ` Warnings: ${formatList(decision.warnings)}`, ` Source: ${evaluation.signal.source}`, ` Freshness: ${formatFreshness(evaluation.freshness_score)}`, ` Rank score: ${formatRank(evaluation.ranked.final_score)}`, ` Utility: ${formatUtility(evaluation.utility.score)}`, ` Confidence: ${evaluation.ranked.confidence}`, ` Why: ${evaluation.explanation}`, "");
|
|
122
|
+
});
|
|
123
|
+
const structured = {
|
|
124
|
+
profile: result.profile.profile_id,
|
|
125
|
+
intent: result.intent,
|
|
126
|
+
results: result.items.map((item, index) => ({
|
|
127
|
+
index: index + 1,
|
|
128
|
+
title: sourceTitle(item.evaluation),
|
|
129
|
+
source: item.evaluation.signal.source,
|
|
130
|
+
source_type: item.evaluation.signal.source_type,
|
|
131
|
+
decision: item.decision.decision,
|
|
132
|
+
label: item.decision.label,
|
|
133
|
+
meaning: item.decision.meaning,
|
|
134
|
+
action: item.decision.action,
|
|
135
|
+
warnings: item.decision.warnings,
|
|
136
|
+
reasons: item.decision.reasons,
|
|
137
|
+
freshness_score: item.evaluation.freshness_score,
|
|
138
|
+
rank_score: item.evaluation.ranked.final_score,
|
|
139
|
+
utility_score: item.evaluation.utility.score,
|
|
140
|
+
confidence: item.evaluation.ranked.confidence,
|
|
141
|
+
why: item.evaluation.explanation,
|
|
142
|
+
})),
|
|
143
|
+
};
|
|
144
|
+
lines.push("[FRESHCONTEXT_EVALUATION_JSON]", JSON.stringify(structured, null, 2), "[/FRESHCONTEXT_EVALUATION_JSON]");
|
|
145
|
+
return lines.join("\n");
|
|
146
|
+
}
|
|
@@ -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.19` 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.19",
|
|
109
|
+
"server_version": "0.3.19",
|
|
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-11 at `0.3.19 / 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
|
@@ -12,7 +12,7 @@ The verified local server entrypoint is:
|
|
|
12
12
|
& '<node-executable>' '<repo-root>\dist\server.js'
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The MCP server exposes 21
|
|
15
|
+
The MCP server exposes 22 tools: the front-door `evaluate_context` tool plus 21 read-only reference adapters. The local smoke test verifies the package version, server version, expected tool count, the generic context-evaluation path, and representative adapter calls.
|
|
16
16
|
|
|
17
17
|
No credential is required for the local stdio smoke path.
|
|
18
18
|
|
|
@@ -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-11 as a live Worker MCP endpoint exposing `0.3.19 / 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,9 +83,9 @@ Expected result:
|
|
|
83
83
|
```json
|
|
84
84
|
{
|
|
85
85
|
"ok": true,
|
|
86
|
-
"package_version": "0.3.
|
|
87
|
-
"server_version": "0.3.
|
|
88
|
-
"tool_count":
|
|
86
|
+
"package_version": "0.3.19",
|
|
87
|
+
"server_version": "0.3.19",
|
|
88
|
+
"tool_count": 22
|
|
89
89
|
}
|
|
90
90
|
```
|
|
91
91
|
|
|
@@ -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
|
|
@@ -112,5 +112,5 @@ If Codex cannot start the server:
|
|
|
112
112
|
- Confirm `dist/server.js` exists. If not, run `npm run build`.
|
|
113
113
|
- Confirm Node is installed with `node -v`. The package requires Node.js 20 or newer.
|
|
114
114
|
- If `node` is not found by Codex, use the full executable path from `node -p "process.execPath"`.
|
|
115
|
-
- Run `npm run smoke:stdio` from the repository root and confirm `tool_count` is
|
|
115
|
+
- Run `npm run smoke:stdio` from the repository root and confirm `tool_count` is 22.
|
|
116
116
|
- If the remote setup fails, verify network access, `npx` availability, and the remote endpoint separately. Do not treat remote failure as evidence that local stdio is broken.
|
package/docs/CORE_API.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# FreshContext Core API
|
|
2
2
|
|
|
3
|
-
FreshContext Core is the reusable engine layer in the current integrated MCP
|
|
3
|
+
FreshContext Core is the reusable engine layer in the current integrated Core/MCP package. It owns signal normalization, envelope creation, freshness scoring, failure honesty, Source Profiles, decision output, rank/explain primitives, the context-utility primitive, and pure provenance helpers.
|
|
4
4
|
|
|
5
5
|
MCP, Worker HTTP, future REST, and future CLI/SDK surfaces should use Core as the contract center instead of redefining freshness or envelope behavior per host.
|
|
6
6
|
|
|
7
|
+
For the package-level boundary between Core, MCP, adapters, and deployment surfaces, see [Core / MCP Boundary](./CORE_MCP_BOUNDARY.md).
|
|
8
|
+
|
|
7
9
|
## Stable Public Core API
|
|
8
10
|
|
|
9
11
|
Import stable Core functions from:
|
|
@@ -74,9 +76,9 @@ The demo reads caller-provided JSON with `profile`, `intent`, and `signals`, the
|
|
|
74
76
|
|
|
75
77
|
These types describe the stable envelope and adapter result contract.
|
|
76
78
|
|
|
77
|
-
## Signal Contract v1
|
|
78
|
-
|
|
79
|
-
Signal Contract v1 is the
|
|
79
|
+
## Signal Contract v1
|
|
80
|
+
|
|
81
|
+
Signal Contract v1 is the current FreshContext input standard: the stable shape for candidate context before it is ranked, wrapped, stored, judged by `evaluate_context`, or passed to an agent workflow.
|
|
80
82
|
|
|
81
83
|
Public exports:
|
|
82
84
|
|
|
@@ -88,9 +90,11 @@ Public exports:
|
|
|
88
90
|
- `SignalContractVersion`
|
|
89
91
|
- `SignalNormalizeOptions`
|
|
90
92
|
|
|
91
|
-
`published_at` is the canonical signal timestamp. `content_date` is accepted as an adapter/envelope compatibility alias. Normalization clears invalid or meaningfully future-dated timestamps, marks failed/error-looking content as `status: "failed"`, clamps `semantic_score` into `0..1`, and records normalization reasons.
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
`published_at` is the canonical signal timestamp. `content_date` is accepted as an adapter/envelope compatibility alias. Normalization clears invalid or meaningfully future-dated timestamps, marks failed/error-looking content as `status: "failed"`, clamps `semantic_score` into `0..1`, and records normalization reasons.
|
|
94
|
+
|
|
95
|
+
Future context signals and control signals are optional future metadata layers, not replacements for Signal Contract v1 and not required public input fields today.
|
|
96
|
+
|
|
97
|
+
See [Signal Contract v1](./SIGNAL_CONTRACT.md).
|
|
94
98
|
|
|
95
99
|
## Source Profiles
|
|
96
100
|
|
|
@@ -108,7 +112,7 @@ Public exports:
|
|
|
108
112
|
- `SourceFailurePolicy`
|
|
109
113
|
- `SourceSurface`
|
|
110
114
|
|
|
111
|
-
They reframe the 21
|
|
115
|
+
They reframe the 21 named adapter tools as reference adapters and source-profile examples instead of the product identity. The MCP server also exposes `evaluate_context` as the generic caller-provided context evaluation path. Source Profiles do not implement `retrieve(...)`, Operator mode, adapter selection, crawling, local file search, or any host/runtime behavior.
|
|
112
116
|
|
|
113
117
|
## Decision Helper
|
|
114
118
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# FreshContext Core / MCP Boundary
|
|
2
|
+
|
|
3
|
+
FreshContext is the context judgment layer between retrieval and reasoning.
|
|
4
|
+
|
|
5
|
+
The current npm package is intentionally named `freshcontext-mcp` because MCP is the first live interface. That does not make MCP the whole product. MCP is a host layer over the FreshContext Core engine.
|
|
6
|
+
|
|
7
|
+
## Product Shape
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Candidate context
|
|
11
|
+
-> FreshContext Core
|
|
12
|
+
-> decision-ready context
|
|
13
|
+
-> MCP / Worker / future REST / future SDK / future CLI
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
FreshContext Core owns the judgment contract:
|
|
17
|
+
|
|
18
|
+
- signal normalization
|
|
19
|
+
- freshness scoring
|
|
20
|
+
- source profiles
|
|
21
|
+
- context utility sidecar scoring
|
|
22
|
+
- rank and explanation primitives
|
|
23
|
+
- decision helper output
|
|
24
|
+
- envelope and provenance helpers
|
|
25
|
+
|
|
26
|
+
MCP owns the live reference interface:
|
|
27
|
+
|
|
28
|
+
- tool registration
|
|
29
|
+
- MCP input schemas
|
|
30
|
+
- stdio transport
|
|
31
|
+
- client-facing tool descriptions
|
|
32
|
+
- formatting Core/adapter output for MCP clients
|
|
33
|
+
|
|
34
|
+
Adapters own source intake:
|
|
35
|
+
|
|
36
|
+
- source-specific fetching
|
|
37
|
+
- source-specific parsing
|
|
38
|
+
- timestamp extraction
|
|
39
|
+
- source-specific normalization
|
|
40
|
+
- failure normalization before Core evaluation
|
|
41
|
+
|
|
42
|
+
Worker/site surfaces own deployment concerns:
|
|
43
|
+
|
|
44
|
+
- Cloudflare runtime behavior
|
|
45
|
+
- KV/D1/cache/feed concerns
|
|
46
|
+
- hosted demo/site presentation
|
|
47
|
+
- runtime guards and deployment configuration
|
|
48
|
+
|
|
49
|
+
## Current Live Boundary
|
|
50
|
+
|
|
51
|
+
Live today:
|
|
52
|
+
|
|
53
|
+
- npm package: `freshcontext-mcp@0.3.19`
|
|
54
|
+
- MCP stdio server and published binary: `freshcontext-mcp`
|
|
55
|
+
- `evaluate_context` MCP tool for caller-provided candidate context
|
|
56
|
+
- 21 named read-only reference adapters
|
|
57
|
+
- Core signal evaluation
|
|
58
|
+
- Source Profiles
|
|
59
|
+
- Decision Helper
|
|
60
|
+
- adapter registry metadata
|
|
61
|
+
- arXiv signal-to-decision proof
|
|
62
|
+
- bring-your-own-context local demos
|
|
63
|
+
- Trust Scanner release gate
|
|
64
|
+
|
|
65
|
+
Not live today:
|
|
66
|
+
|
|
67
|
+
- standalone Core npm package
|
|
68
|
+
- package rename to `freshcontext`
|
|
69
|
+
- Operator / `retrieve(...)`
|
|
70
|
+
- automatic browser crawling
|
|
71
|
+
- automatic local folder/PDF scanning
|
|
72
|
+
- hosted enterprise dashboard or billing
|
|
73
|
+
- hard Ha-Pri v2 production enforcement
|
|
74
|
+
- full adapter ingestion into pure signal paths
|
|
75
|
+
|
|
76
|
+
## Future Package Split
|
|
77
|
+
|
|
78
|
+
The safe split path is staged:
|
|
79
|
+
|
|
80
|
+
1. Keep `freshcontext-mcp` stable for current users.
|
|
81
|
+
2. Maintain Core as a pure internal export surface.
|
|
82
|
+
3. Audit Core dependencies, Node/browser compatibility, and API stability.
|
|
83
|
+
4. Publish a standalone Core package only after compatibility tests exist.
|
|
84
|
+
5. Make `freshcontext-mcp` depend on the standalone Core package.
|
|
85
|
+
6. Consider a repo rename to `freshcontext` only after package/client links are stable.
|
|
86
|
+
|
|
87
|
+
The expected future shape is:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
freshcontext
|
|
91
|
+
packages/core reusable judgment engine
|
|
92
|
+
packages/adapters reference source intake assets
|
|
93
|
+
packages/mcp MCP host layer
|
|
94
|
+
packages/cli future local evaluator
|
|
95
|
+
docs
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Compatibility Rule
|
|
99
|
+
|
|
100
|
+
Do not remove the current compatibility lanes until a dedicated migration pass exists:
|
|
101
|
+
|
|
102
|
+
- `src/types.ts` re-exports legacy adapter types from Core.
|
|
103
|
+
- `src/tools/freshnessStamp.ts` re-exports envelope helpers for older MCP/npm import paths.
|
|
104
|
+
- `dist/server.js` remains the package `main` and MCP binary target.
|
|
105
|
+
|
|
106
|
+
The architecture direction is clear, but the public runtime should stay boring and stable.
|