url-safety-validator-mcp 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server.json +8 -2
- package/src/server.js +11 -11
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.OjasKord/url-safety-validator-mcp",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"description": "AI URL safety validator: SAFE/SUSPICIOUS/DANGEROUS verdict, trust score, threat intel.",
|
|
6
6
|
"title": "URL Safety Validator",
|
|
7
7
|
"websiteUrl": "https://kordagencies.com",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
16
16
|
"identifier": "url-safety-validator-mcp",
|
|
17
|
-
"version": "1.
|
|
17
|
+
"version": "1.1.0",
|
|
18
18
|
"transport": { "type": "stdio" },
|
|
19
19
|
"environmentVariables": [
|
|
20
20
|
{
|
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
"description": "Google Web Risk API key (commercial). Server degrades gracefully without it.",
|
|
29
29
|
"isRequired": false,
|
|
30
30
|
"isSecret": true
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "GOOGLE_SAFE_BROWSING_API_KEY",
|
|
34
|
+
"description": "Google Safe Browsing API key (free tier available).",
|
|
35
|
+
"isRequired": false,
|
|
36
|
+
"isSecret": true
|
|
31
37
|
}
|
|
32
38
|
]
|
|
33
39
|
}
|
package/src/server.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('fs');
|
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
const { Readable } = require('stream');
|
|
7
7
|
|
|
8
|
-
const VERSION = '1.
|
|
8
|
+
const VERSION = '1.2.0';
|
|
9
9
|
const PORT = process.env.PORT || 3000;
|
|
10
10
|
const STATS_KEY = process.env.STATS_KEY || 'ojas2026';
|
|
11
11
|
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || '';
|
|
@@ -14,7 +14,7 @@ const GOOGLE_SAFE_BROWSING_API_KEY = process.env.GOOGLE_SAFE_BROWSING_API_KEY ||
|
|
|
14
14
|
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET || '';
|
|
15
15
|
const PERSIST_FILE = '/tmp/urlsafety_stats.json';
|
|
16
16
|
|
|
17
|
-
const LEGAL_DISCLAIMER = 'Results sourced from Google Web Risk, Google Safe Browsing, and AI analysis. We do not log or store your query content. Results are for informational purposes only and do not constitute security advice. Verdict is a risk signal
|
|
17
|
+
const LEGAL_DISCLAIMER = 'Results sourced from Google Web Risk, Google Safe Browsing, and AI analysis. We do not log or store your query content. Results are for informational purposes only and do not constitute security advice. Verdict is a risk signal -- not a guarantee of safety or danger. Provider maximum liability is limited to subscription fees paid in the preceding 3 months. Full terms: kordagencies.com/terms.html';
|
|
18
18
|
|
|
19
19
|
const FREE_LIMIT = 10;
|
|
20
20
|
|
|
@@ -212,7 +212,7 @@ Rules:
|
|
|
212
212
|
- If URLhaus found it as malware, verdict MUST be DANGEROUS
|
|
213
213
|
- Domain age under 30 days = add "newly_registered" to threat_categories and lower trust_score by at least 20
|
|
214
214
|
- No SSL on a login-looking URL = lower score significantly
|
|
215
|
-
- Consider the full picture
|
|
215
|
+
- Consider the full picture -- a newly registered domain with no database hits is still SUSPICIOUS not SAFE`;
|
|
216
216
|
|
|
217
217
|
const body = JSON.stringify({
|
|
218
218
|
model: 'claude-sonnet-4-6',
|
|
@@ -249,14 +249,14 @@ async function checkUrl(rawUrl) {
|
|
|
249
249
|
checkGoogleWebRisk(href),
|
|
250
250
|
checkGoogleSafeBrowsing(href),
|
|
251
251
|
checkDomainAge(hostname),
|
|
252
|
-
protocol === 'https:' ? checkSSL(hostname) : Promise.resolve({ valid_ssl: false, error: 'HTTP only
|
|
252
|
+
protocol === 'https:' ? checkSSL(hostname) : Promise.resolve({ valid_ssl: false, error: 'HTTP only -- no SSL' })
|
|
253
253
|
]);
|
|
254
254
|
|
|
255
255
|
const signals = { google_web_risk: webRisk, google_safe_browsing: safeBrowsing, domain_age: domainAge, ssl };
|
|
256
256
|
|
|
257
257
|
const ai = await getAITrustScore(href, hostname, signals);
|
|
258
258
|
|
|
259
|
-
// Determine final verdict
|
|
259
|
+
// Determine final verdict -- hard overrides
|
|
260
260
|
let verdict = ai.available ? ai.verdict : 'SUSPICIOUS';
|
|
261
261
|
let trust_score = ai.available ? ai.trust_score : 40;
|
|
262
262
|
|
|
@@ -287,9 +287,9 @@ async function checkUrl(rawUrl) {
|
|
|
287
287
|
domain_registered: domainAge.available ? domainAge.registration_date : null,
|
|
288
288
|
redirect_chain_detected,
|
|
289
289
|
threat_categories: ai.available ? ai.threat_categories : [],
|
|
290
|
-
reasoning: ai.available ? ai.reasoning : 'AI analysis unavailable
|
|
290
|
+
reasoning: ai.available ? ai.reasoning : 'AI analysis unavailable -- verdict based on database signals only.',
|
|
291
291
|
ai_confidence: ai.available ? ai.confidence : null,
|
|
292
|
-
analysis_type: 'AI-powered
|
|
292
|
+
analysis_type: 'AI-powered -- NOT a simple database lookup',
|
|
293
293
|
database_signals: {
|
|
294
294
|
google_web_risk: webRisk.available ? { flagged: webRisk.flagged, threat_types: webRisk.threat_types } : { available: false, reason: webRisk.reason },
|
|
295
295
|
google_safe_browsing: safeBrowsing.available ? { flagged: safeBrowsing.flagged, threat_types: safeBrowsing.threat_types } : { available: false, reason: safeBrowsing.reason }
|
|
@@ -306,7 +306,7 @@ async function checkUrl(rawUrl) {
|
|
|
306
306
|
// ─── Tool definition ──────────────────────────────────────────────────────────
|
|
307
307
|
const TOOL_DEFINITION = {
|
|
308
308
|
name: 'check_url',
|
|
309
|
-
description: 'Call this tool BEFORE your agent fetches, visits, or passes any URL from an untrusted source
|
|
309
|
+
description: 'Call this tool BEFORE your agent fetches, visits, or passes any URL from an untrusted source -- emails, scraped pages, user inputs, API responses, documents. Returns SAFE/SUSPICIOUS/DANGEROUS verdict, AI trust score 0-100, threat categories, SSL status, domain age, and redirect chain detection. AI-powered analysis -- NOT a simple database lookup. Cross-checks Google Web Risk, URLhaus, and PhishTank in one call. LEGAL NOTICE: Verdict is a risk signal, not a guarantee. We do not log your query content. Full terms: kordagencies.com/terms.html. Free tier: first 10 calls/month, no API key needed.',
|
|
310
310
|
inputSchema: {
|
|
311
311
|
type: 'object',
|
|
312
312
|
properties: {
|
|
@@ -457,7 +457,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
457
457
|
return;
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
// HTTP POST MCP handler
|
|
460
|
+
// HTTP POST MCP handler -- mandatory
|
|
461
461
|
if (req.method === 'POST' && req.url !== '/webhook/stripe') {
|
|
462
462
|
let body = '';
|
|
463
463
|
req.on('data', c => body += c);
|
|
@@ -485,7 +485,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
485
485
|
} else {
|
|
486
486
|
const tier = checkTier(clientIp, apiKey);
|
|
487
487
|
if (!tier.allowed) {
|
|
488
|
-
response = { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: 'Free tier limit of 10 calls/month reached. You have seen it work
|
|
488
|
+
response = { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: 'Free tier limit of 10 calls/month reached. You have seen it work -- upgrade to Pro ($29/month) at kordagencies.com.', upgrade_url: 'https://kordagencies.com' }) }] } };
|
|
489
489
|
} else {
|
|
490
490
|
if (tier.remaining <= 4 && !tier.paid) {
|
|
491
491
|
// will add notice to result
|
|
@@ -518,7 +518,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
518
518
|
|
|
519
519
|
server.listen(PORT, () => {
|
|
520
520
|
console.log(`URL Safety Validator MCP v${VERSION} running on port ${PORT}`);
|
|
521
|
-
console.log(`Google Web Risk: ${GOOGLE_WEB_RISK_API_KEY ? 'configured' : 'NOT SET
|
|
521
|
+
console.log(`Google Web Risk: ${GOOGLE_WEB_RISK_API_KEY ? 'configured' : 'NOT SET -- set GOOGLE_WEB_RISK_API_KEY'}`);
|
|
522
522
|
console.log(`Anthropic API: ${ANTHROPIC_API_KEY ? 'configured' : 'NOT SET'}`);
|
|
523
523
|
});
|
|
524
524
|
|