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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "url-safety-validator-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "AI-powered URL safety validator MCP server. SAFE/SUSPICIOUS/DANGEROUS verdict for agents.",
5
5
  "main": "src/server.js",
6
6
  "scripts": {
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.0.0",
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.0.0",
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.1.0';
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 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';
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 a newly registered domain with no database hits is still SUSPICIOUS not SAFE`;
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 no SSL' })
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 hard overrides
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 verdict based on database signals only.',
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 NOT a simple database lookup',
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 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.',
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 mandatory
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 upgrade to Pro ($29/month) at kordagencies.com.', upgrade_url: 'https://kordagencies.com' }) }] } };
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 set GOOGLE_WEB_RISK_API_KEY'}`);
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