freshcontext-mcp 0.3.16 → 0.3.17
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/.env.example +3 -0
- package/README.md +6 -6
- package/dist/adapters/finance.js +87 -101
- package/dist/adapters/gdelt.js +1 -1
- package/dist/adapters/gebiz.js +1 -1
- package/dist/adapters/hackernews.js +43 -13
- package/dist/adapters/productHunt.js +8 -4
- package/dist/adapters/repoSearch.js +1 -1
- package/dist/adapters/secFilings.js +1 -1
- package/dist/security.js +1 -1
- package/dist/server.js +10 -10
- package/dist/tools/freshnessStamp.js +23 -3
- package/freshcontext.schema.json +1 -1
- package/package.json +14 -7
- package/server.json +3 -3
- package/.github/workflows/publish.yml +0 -32
- package/RESEARCH.md +0 -487
- package/RISKS.md +0 -137
- package/cleanup.ps1 +0 -99
- package/demo/README.md +0 -70
- package/demo/data.json +0 -88
- package/demo/generate.mjs +0 -199
- package/demo/index.html +0 -513
- package/demo/logo-export.html +0 -61
- package/demo/logo.svg +0 -23
- package/freshcontext-validate.js +0 -196
- package/time-check.ps1 +0 -46
package/freshcontext-validate.js
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* freshcontext-validate
|
|
5
|
-
* CLI validator for FreshContext-compatible responses
|
|
6
|
-
* Spec: https://github.com/PrinceGabriel-lgtm/freshcontext-mcp/blob/main/FRESHCONTEXT_SPEC.md
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* node freshcontext-validate.js '<json_string>'
|
|
10
|
-
* node freshcontext-validate.js --stdin
|
|
11
|
-
* node freshcontext-validate.js --file <path>
|
|
12
|
-
* node freshcontext-validate.js --help
|
|
13
|
-
*
|
|
14
|
-
* Compliance levels:
|
|
15
|
-
* FreshContext-scored ★★★ — JSON form + numeric freshness_score
|
|
16
|
-
* FreshContext-compatible ★★ — JSON/envelope with retrieved_at + confidence
|
|
17
|
-
* FreshContext-aware ★ — retrieved_at only, no confidence
|
|
18
|
-
* FAIL — missing required fields
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const REQUIRED_JSON_FIELDS = ['source_url', 'retrieved_at', 'freshness_confidence', 'adapter'];
|
|
22
|
-
const VALID_CONFIDENCE = ['high', 'medium', 'low'];
|
|
23
|
-
const ENVELOPE_START = '[FRESHCONTEXT]';
|
|
24
|
-
const ENVELOPE_END = '[/FRESHCONTEXT]';
|
|
25
|
-
const REQUIRED_ENVELOPE_FIELDS = ['Source:', 'Published:', 'Retrieved:', 'Confidence:'];
|
|
26
|
-
|
|
27
|
-
const g = s => `\x1b[32m${s}\x1b[0m`;
|
|
28
|
-
const r = s => `\x1b[31m${s}\x1b[0m`;
|
|
29
|
-
const y = s => `\x1b[33m${s}\x1b[0m`;
|
|
30
|
-
const b = s => `\x1b[34m${s}\x1b[0m`;
|
|
31
|
-
const d = s => `\x1b[2m${s}\x1b[0m`;
|
|
32
|
-
const bold = s => `\x1b[1m${s}\x1b[0m`;
|
|
33
|
-
|
|
34
|
-
const pass = msg => ({ ok: true, warn: false, msg: ` ${g('✓')} ${msg}` });
|
|
35
|
-
const fail = msg => ({ ok: false, warn: false, msg: ` ${r('✕')} ${msg}` });
|
|
36
|
-
const warn = msg => ({ ok: true, warn: true, msg: ` ${y('!')} ${msg}` });
|
|
37
|
-
|
|
38
|
-
function validateJSON(obj) {
|
|
39
|
-
const results = [];
|
|
40
|
-
const fc = obj.freshcontext;
|
|
41
|
-
if (!fc) return [fail('No "freshcontext" key found in JSON response')];
|
|
42
|
-
|
|
43
|
-
for (const field of REQUIRED_JSON_FIELDS) {
|
|
44
|
-
if (fc[field] === undefined || fc[field] === null) {
|
|
45
|
-
results.push(fail(`Missing required field: freshcontext.${field}`));
|
|
46
|
-
} else {
|
|
47
|
-
results.push(pass(`freshcontext.${field} = ${d(String(fc[field]).slice(0, 80))}`));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (fc.retrieved_at) {
|
|
52
|
-
const dt = new Date(fc.retrieved_at);
|
|
53
|
-
if (isNaN(dt.getTime())) {
|
|
54
|
-
results.push(fail(`retrieved_at is not valid ISO 8601: "${fc.retrieved_at}"`));
|
|
55
|
-
} else {
|
|
56
|
-
results.push(pass(`retrieved_at is valid ISO 8601`));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (fc.freshness_confidence && !VALID_CONFIDENCE.includes(fc.freshness_confidence)) {
|
|
61
|
-
results.push(fail(`freshness_confidence must be high, medium, or low. Got: "${fc.freshness_confidence}"`));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (fc.freshness_score !== undefined && fc.freshness_score !== null) {
|
|
65
|
-
if (typeof fc.freshness_score !== 'number' || fc.freshness_score < 0 || fc.freshness_score > 100) {
|
|
66
|
-
results.push(fail(`freshness_score must be 0-100. Got: ${fc.freshness_score}`));
|
|
67
|
-
} else {
|
|
68
|
-
const col = fc.freshness_score >= 70 ? g : fc.freshness_score >= 50 ? y : r;
|
|
69
|
-
results.push(pass(`freshness_score: ${col(fc.freshness_score + '/100')}`));
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
results.push(warn('freshness_score not present (optional — required for ★★★ level)'));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return results;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function validateEnvelope(text) {
|
|
79
|
-
const results = [];
|
|
80
|
-
if (!text.includes(ENVELOPE_START)) return [fail(`Missing opening tag: ${ENVELOPE_START}`)];
|
|
81
|
-
if (!text.includes(ENVELOPE_END)) return [fail(`Missing closing tag: ${ENVELOPE_END}`)];
|
|
82
|
-
results.push(pass('Envelope tags present'));
|
|
83
|
-
|
|
84
|
-
const start = text.indexOf(ENVELOPE_START) + ENVELOPE_START.length;
|
|
85
|
-
const end = text.indexOf(ENVELOPE_END);
|
|
86
|
-
const envelope = text.slice(start, end);
|
|
87
|
-
|
|
88
|
-
for (const field of REQUIRED_ENVELOPE_FIELDS) {
|
|
89
|
-
if (!envelope.includes(field)) {
|
|
90
|
-
results.push(fail(`Missing field: ${field}`));
|
|
91
|
-
} else {
|
|
92
|
-
const line = envelope.split('\n').find(l => l.trim().startsWith(field));
|
|
93
|
-
results.push(pass(`${field.replace(':', '')} — ${d((line || '').trim().slice(0, 70))}`));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const confLine = envelope.split('\n').find(l => l.trim().startsWith('Confidence:'));
|
|
98
|
-
if (confLine) {
|
|
99
|
-
const confVal = confLine.split(':')[1]?.trim().toLowerCase();
|
|
100
|
-
if (!VALID_CONFIDENCE.includes(confVal)) {
|
|
101
|
-
results.push(fail(`Confidence must be high, medium, or low. Got: "${confVal}"`));
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return results;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function complianceLevel(results, hasScore, mode) {
|
|
109
|
-
const failed = results.filter(res => !res.ok).length;
|
|
110
|
-
if (failed > 0) return { level: 'FAIL', colour: r };
|
|
111
|
-
if (mode === 'json' && hasScore) return { level: 'FreshContext-scored \u2605\u2605\u2605', colour: g };
|
|
112
|
-
return { level: 'FreshContext-compatible \u2605\u2605', colour: g };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function validateString(input, label) {
|
|
116
|
-
console.log(`\n${bold('freshcontext-validate')} ${d('v1.0.0')}`);
|
|
117
|
-
console.log(d('\u2500'.repeat(52)));
|
|
118
|
-
console.log(`${b('Input:')} ${d(label)}\n`);
|
|
119
|
-
|
|
120
|
-
let results = [];
|
|
121
|
-
let hasScore = false;
|
|
122
|
-
let mode = 'envelope';
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
const parsed = JSON.parse(input);
|
|
126
|
-
mode = 'json';
|
|
127
|
-
console.log(`${d('Mode:')} ${b('JSON structured form')}\n`);
|
|
128
|
-
results = validateJSON(parsed);
|
|
129
|
-
hasScore = parsed?.freshcontext?.freshness_score !== undefined
|
|
130
|
-
&& parsed?.freshcontext?.freshness_score !== null;
|
|
131
|
-
} catch {
|
|
132
|
-
if (input.includes(ENVELOPE_START)) {
|
|
133
|
-
console.log(`${d('Mode:')} ${b('Text envelope')}\n`);
|
|
134
|
-
results = validateEnvelope(input);
|
|
135
|
-
} else {
|
|
136
|
-
console.log(r('✕ Input is neither valid JSON nor a FreshContext text envelope.\n'));
|
|
137
|
-
console.log(d('Expected:'));
|
|
138
|
-
console.log(` ${d('JSON:')} {"freshcontext": {"source_url": "...", "retrieved_at": "...", ...}}`);
|
|
139
|
-
console.log(` ${d('Envelope:')} [FRESHCONTEXT]\\nSource: ...\\n[/FRESHCONTEXT]`);
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
results.forEach(res => console.log(res.msg));
|
|
145
|
-
|
|
146
|
-
const passed = results.filter(res => res.ok && !res.warn).length;
|
|
147
|
-
const failed = results.filter(res => !res.ok).length;
|
|
148
|
-
const warned = results.filter(res => res.warn).length;
|
|
149
|
-
|
|
150
|
-
const { level, colour } = complianceLevel(results, hasScore, mode);
|
|
151
|
-
|
|
152
|
-
console.log(`\n${d('\u2500'.repeat(52))}`);
|
|
153
|
-
console.log(`${d('Checks:')} ${g(passed + ' passed')}${warned ? ', ' + y(warned + ' warnings') : ''}${failed ? ', ' + r(failed + ' failed') : ''}`);
|
|
154
|
-
console.log(`${d('Result:')} ${colour(bold(level))}\n`);
|
|
155
|
-
|
|
156
|
-
if (failed > 0) process.exit(1);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const args = process.argv.slice(2);
|
|
160
|
-
|
|
161
|
-
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
162
|
-
console.log(`
|
|
163
|
-
${bold('freshcontext-validate')} ${d('v1.0.0')}
|
|
164
|
-
Validates FreshContext-compatible responses against the spec.
|
|
165
|
-
|
|
166
|
-
${bold('USAGE')}
|
|
167
|
-
node freshcontext-validate.js ${d('<json_or_envelope_string>')}
|
|
168
|
-
node freshcontext-validate.js --file ${d('<path>')}
|
|
169
|
-
node freshcontext-validate.js --stdin
|
|
170
|
-
echo '...' | node freshcontext-validate.js --stdin
|
|
171
|
-
|
|
172
|
-
${bold('COMPLIANCE LEVELS')}
|
|
173
|
-
${g('FreshContext-scored \u2605\u2605\u2605')} Full JSON form + numeric freshness_score
|
|
174
|
-
${g('FreshContext-compatible \u2605\u2605')} JSON/envelope with retrieved_at + confidence
|
|
175
|
-
${r('FAIL')} Missing required fields
|
|
176
|
-
|
|
177
|
-
${bold('SPEC')}
|
|
178
|
-
https://freshcontext-site.pages.dev/spec.html
|
|
179
|
-
https://github.com/PrinceGabriel-lgtm/freshcontext-mcp/blob/main/FRESHCONTEXT_SPEC.md
|
|
180
|
-
`);
|
|
181
|
-
process.exit(0);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (args[0] === '--stdin') {
|
|
185
|
-
let data = '';
|
|
186
|
-
process.stdin.setEncoding('utf8');
|
|
187
|
-
process.stdin.on('data', chunk => data += chunk);
|
|
188
|
-
process.stdin.on('end', () => validateString(data.trim(), 'stdin'));
|
|
189
|
-
} else if (args[0] === '--file') {
|
|
190
|
-
const path = args[1];
|
|
191
|
-
if (!path) { console.error(r('--file requires a path argument')); process.exit(1); }
|
|
192
|
-
const fs = require('fs');
|
|
193
|
-
validateString(fs.readFileSync(path, 'utf8').trim(), path);
|
|
194
|
-
} else {
|
|
195
|
-
validateString(args[0], 'inline input');
|
|
196
|
-
}
|
package/time-check.ps1
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# time-check.ps1 — Print a session header for Claude conversations
|
|
2
|
-
# Usage: ./time-check.ps1
|
|
3
|
-
# Then paste the output at the start of your message to Claude.
|
|
4
|
-
|
|
5
|
-
$now = Get-Date
|
|
6
|
-
$utc = $now.ToUniversalTime()
|
|
7
|
-
$dayOfWeek = $now.DayOfWeek
|
|
8
|
-
$weekNumber = (Get-Culture).Calendar.GetWeekOfYear($now, [System.Globalization.CalendarWeekRule]::FirstFourDayWeek, [DayOfWeek]::Monday)
|
|
9
|
-
|
|
10
|
-
# Sun position approximation for Grootfontein (-19.57°, 18.12°)
|
|
11
|
-
# Simple model: sunrise ~6:00, sunset ~18:30 (varies seasonally, close enough for vibes)
|
|
12
|
-
$hour = $now.Hour
|
|
13
|
-
$timeOfDay = switch ($hour) {
|
|
14
|
-
{$_ -lt 5} { "deep night" }
|
|
15
|
-
{$_ -lt 7} { "before dawn" }
|
|
16
|
-
{$_ -lt 9} { "early morning" }
|
|
17
|
-
{$_ -lt 12} { "morning" }
|
|
18
|
-
{$_ -lt 14} { "midday" }
|
|
19
|
-
{$_ -lt 17} { "afternoon" }
|
|
20
|
-
{$_ -lt 19} { "early evening" }
|
|
21
|
-
{$_ -lt 22} { "evening" }
|
|
22
|
-
default { "late night" }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
# US East Coast equivalent (CAT is UTC+2, ET is UTC-4 or UTC-5 depending on DST)
|
|
26
|
-
# Simple approximation: CAT - 6h ≈ ET in summer, CAT - 7h ≈ ET in winter
|
|
27
|
-
$etHour = ($hour - 6 + 24) % 24
|
|
28
|
-
$etTime = "{0:D2}:{1:D2} ET" -f $etHour, $now.Minute
|
|
29
|
-
|
|
30
|
-
# Output
|
|
31
|
-
Write-Host ""
|
|
32
|
-
Write-Host "═══════════════════════════════════════════" -ForegroundColor Cyan
|
|
33
|
-
Write-Host " Session header for Claude (paste this in)" -ForegroundColor Cyan
|
|
34
|
-
Write-Host "═══════════════════════════════════════════" -ForegroundColor Cyan
|
|
35
|
-
Write-Host ""
|
|
36
|
-
Write-Host "Local time: $($now.ToString('yyyy-MM-dd HH:mm')) CAT ($timeOfDay), $dayOfWeek" -ForegroundColor Green
|
|
37
|
-
Write-Host "UTC: $($utc.ToString('yyyy-MM-dd HH:mm')) UTC" -ForegroundColor Gray
|
|
38
|
-
Write-Host "ET (US): ~$etTime" -ForegroundColor Gray
|
|
39
|
-
Write-Host "Week: Week $weekNumber of $($now.Year)" -ForegroundColor Gray
|
|
40
|
-
Write-Host ""
|
|
41
|
-
|
|
42
|
-
# Also copy to clipboard so you don't have to retype it
|
|
43
|
-
$header = "[$($now.ToString('yyyy-MM-dd HH:mm')) CAT, $dayOfWeek $timeOfDay]"
|
|
44
|
-
$header | Set-Clipboard
|
|
45
|
-
Write-Host "→ Copied to clipboard: $header" -ForegroundColor Yellow
|
|
46
|
-
Write-Host ""
|