url-safety-validator-mcp 1.2.10 → 1.2.13

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/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to URL Safety Validator MCP are documented here.
4
4
 
5
+ ## [1.2.13] — 2026-06-08
6
+ - fix: BEFORE trigger language, consequence-first limit error
7
+
8
+ ## [1.2.12] — 2026-06-05
9
+ - feat: Smithery optimisation - updated package.json description/keywords and smithery.yaml with system prompt
10
+
11
+ ## [1.2.11] — 2026-06-04
12
+ - feat: /daily-report endpoint for consolidated daily summary
13
+
5
14
  ## [1.2.10] — 2026-06-04
6
15
 
7
16
  ### Added
package/package.json CHANGED
@@ -1,31 +1,24 @@
1
1
  {
2
2
  "name": "url-safety-validator-mcp",
3
3
  "mcpName": "io.github.OjasKord/url-safety-validator-mcp",
4
- "version": "1.2.10",
5
- "description": "AI-powered URL safety validator MCP server. SAFE/SUSPICIOUS/DANGEROUS verdict for agents.",
4
+ "version": "1.2.13",
5
+ "description": "URL safety checker for AI agents. Detects phishing, malware, typosquatting before your agent visits any link. BLOCK/ALLOW verdict in one call.",
6
6
  "main": "src/server.js",
7
7
  "scripts": {
8
8
  "start": "node src/server.js"
9
9
  },
10
10
  "keywords": [
11
11
  "mcp",
12
- "agent",
13
- "url",
14
- "safety",
15
- "validator",
12
+ "url-safety",
16
13
  "phishing",
17
14
  "malware",
18
- "security",
19
- "threat-intelligence",
20
15
  "web-risk",
21
- "url-checker",
22
- "link-safety",
23
- "phishing-detection",
24
- "google-web-risk",
25
16
  "safe-browsing",
26
- "malware-detection",
27
- "url-scanner",
28
- "cybersecurity"
17
+ "cybersecurity",
18
+ "link-checker",
19
+ "threat-detection",
20
+ "url-validator",
21
+ "ai-agents"
29
22
  ],
30
23
  "author": "Kord Agencies Pte Ltd <ojas@kordagencies.com>",
31
24
  "license": "MIT",
package/smithery.yaml CHANGED
@@ -1,4 +1,8 @@
1
- description: "URL safety checker for AI agents. Detects phishing and malware before your agent visits any link."
1
+ name: URL Safety Validator MCP
2
+ description: "Before your agent fetches, visits, or follows any URL from an untrusted source, call check_url. Returns BLOCK/ALLOW verdict with threat category and trust score."
3
+ categories:
4
+ - Security
5
+ - Web
2
6
  startCommand:
3
7
  type: http
4
8
  url: https://url-safety-validator-mcp-production.up.railway.app
@@ -11,42 +15,4 @@ startCommand:
11
15
  x-from:
12
16
  header: "x-api-key"
13
17
  required: []
14
- systemPrompt: |
15
- URL Safety Validator MCP is a link verification layer for AI agents.
16
-
17
- It combines multiple threat intelligence sources with AI-powered analysis to give agents a safety verdict before visiting, processing, or sharing any URL.
18
-
19
- ONE TOOL, ONE VERDICT
20
-
21
- check_url returns SAFE / SUSPICIOUS / DANGEROUS with a trust score 0-100, threat categories, and an agent_action field -- so your agent can proceed or block without any further analysis.
22
-
23
- WHEN TO USE THIS SERVER
24
-
25
- Use this tool whenever your agent is about to:
26
- - Visit or fetch content from any URL provided by a user or external source
27
- - Follow a link in an email, message, or document
28
- - Share or forward a URL to another system or user
29
- - Process a redirect or shortened URL
30
- - Include an unverified URL in a report or output
31
-
32
- RECOMMENDED WORKFLOW
33
-
34
- Before your agent visits or shares any URL:
35
- 1. check_url(url) - get SAFE / SUSPICIOUS / DANGEROUS verdict with trust score 0-100
36
- Only proceed if verdict is SAFE. Block immediately if DANGEROUS. Flag for review if SUSPICIOUS.
37
-
38
- TOOLS
39
-
40
- check_url (AI-POWERED)
41
- - Checks URL against Google Safe Browsing, domain reputation databases, and AI-powered phishing analysis
42
- - Returns: verdict (SAFE / SUSPICIOUS / DANGEROUS), trust_score 0-100, threat_categories, domain_age_days, redirect_chain, agent_action
43
- - AI analysis catches obfuscated phishing and newly registered domains that blocklists miss
44
- - One call replaces three separate threat intelligence lookups
45
- - Free tier: first 10 calls/month, no API key needed
46
-
47
- LEGAL NOTICE
48
- No URL safety check provides a 100% guarantee. Results are for informational purposes only. We do not log your query content. Full terms: kordagencies.com/terms.html
49
-
50
- FREE TIER
51
- 10 calls/month with no API key.
52
- Upgrade at kordagencies.com -- Pro $49/month, Enterprise $199/month.
18
+ systemPrompt: "You are a URL safety specialist. Before your agent fetches, visits, clicks, or passes any URL from an untrusted source - emails, scraped pages, user inputs, documents, API responses, or redirects - use check_url. A DANGEROUS verdict means stop immediately. Never follow an unchecked URL from an external source."
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.2.10';
8
+ const VERSION = '1.2.13';
9
9
  const PRO_UPGRADE_URL = 'https://buy.stripe.com/5kQeVc9Ah4n3c8c0h2ebu0t';
10
10
  const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/4gMdR88wddXDfko0h2ebu0u';
11
11
  const PORT = process.env.PORT || 3000;
@@ -460,7 +460,7 @@ async function checkUrl(rawUrl) {
460
460
  const TOOL_DEFINITION = {
461
461
  name: 'check_url',
462
462
  idempotent: true,
463
- description: 'Call this immediately before your agent fetches, visits, follows, or passes any URL from an untrusted source — emails, scraped pages, user inputs, documents, API responses, or redirects. A DANGEROUS verdict means do not proceed under any circumstances. A SUSPICIOUS verdict means flag for human review before proceeding. A SAFE verdict means proceed. An agent that follows a URL without checking it first is one weaponised link away from credential exfiltration there is no recovery path once the agent has executed on a malicious page. Checks against Google Web Risk, Google Safe Browsing, RDAP domain age, SSL verification, and AI-powered contextual analysis. Returns agent_action (BLOCK/FLAG_AND_PROCEED/ALLOW), verdict, trust_score 0-100, and threat_categories no further analysis needed. We do not log your query content. Free tier: 10 calls/month, no API key required.',
463
+ description: 'Call this BEFORE your agent fetches, visits, follows, or forwards any URL from an untrusted source — at the moment a URL is received from an email, scraped page, user input, document, API response, or redirect and has not yet been acted on. Returns BLOCK/FLAG_AND_PROCEED/ALLOW verdict with trust score 0-100 and threat categories. An unchecked URL followed by your agent is one weaponised link away from credential exfiltration with no recovery path. A DANGEROUS verdict means stop immediately — do not proceed under any circumstances.',
464
464
  inputSchema: {
465
465
  type: 'object',
466
466
  properties: {
@@ -686,6 +686,58 @@ const server = http.createServer(async (req, res) => {
686
686
  return;
687
687
  }
688
688
 
689
+ if (req.url === '/daily-report' && req.method === 'POST') {
690
+ if (req.headers['x-stats-key'] !== process.env.STATS_KEY) {
691
+ res.writeHead(401, cors); res.end(JSON.stringify({ error: 'Unauthorized' })); return;
692
+ }
693
+ (async () => {
694
+ const today = new Date().toISOString().slice(0, 10);
695
+ const since24h = new Date(Date.now() - 86400000).toISOString();
696
+ const cutoffMs = Date.now() - 86400000;
697
+
698
+ const recentLog = usageLog.filter(e => e.timestamp >= since24h);
699
+ const calls24h = recentLog.length;
700
+ const unique24h = new Set(recentLog.map(e => e.ip)).size;
701
+
702
+ const month = new Date().toISOString().slice(0, 7);
703
+ let limitHits = 0;
704
+ for (const months of Object.values(stats.free_tier_calls_by_ip || {})) {
705
+ if ((months[month] || 0) >= FREE_LIMIT) limitHits++;
706
+ }
707
+
708
+ let trialCount = 0;
709
+ for (const record of trialExtensions.values()) {
710
+ if (record.granted_at && record.granted_at >= since24h) trialCount++;
711
+ }
712
+
713
+ let paidCount = 0;
714
+ for (const record of apiKeys.values()) {
715
+ const ts = record.created_at ? new Date(record.created_at).getTime() : 0;
716
+ if (ts >= cutoffMs) paidCount++;
717
+ }
718
+
719
+ const sessionKeys = await redisKeys(REDIS_PREFIX + ':session:*:' + today);
720
+ const toolBreakdown = {};
721
+ for (const key of sessionKeys) {
722
+ const calls = await redisGet(key) || [];
723
+ calls.forEach(c => { if (c.tool) toolBreakdown[c.tool] = (toolBreakdown[c.tool] || 0) + 1; });
724
+ }
725
+
726
+ res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
727
+ res.end(JSON.stringify({
728
+ server: 'url-safety-validator-mcp',
729
+ date: today,
730
+ calls_24h: calls24h,
731
+ unique_ips_24h: unique24h,
732
+ limit_hits: limitHits,
733
+ trial_extensions: trialCount,
734
+ paid_conversions: paidCount,
735
+ tool_breakdown: toolBreakdown
736
+ }));
737
+ })();
738
+ return;
739
+ }
740
+
689
741
  // HTTP POST MCP handler -- mandatory
690
742
  if (req.method === 'POST' && req.url !== '/webhook/stripe') {
691
743
  let body = '';
@@ -714,7 +766,7 @@ const server = http.createServer(async (req, res) => {
714
766
  } else {
715
767
  const tier = checkTier(clientIp, apiKey);
716
768
  if (!tier.allowed) {
717
- response = { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: 'Free tier limit of ' + FREE_LIMIT + ' calls/month reached. Option 1: POST /trial-extension with {"name":"...","email":"...","use_case":"..."} for 10 extra free calls. Option 2: Upgrade at ' + PRO_UPGRADE_URL + ' (500 calls, never expire).', likely_cause: 'free tier monthly limit reached', retryable: false, retry_after_ms: null, fallback_tool: null, agent_action: 'Inform user that free quota is exhausted.', category: 'rate_limit', trace_id: crypto.randomBytes(8).toString('hex'), upgrade_url: PRO_UPGRADE_URL, trial_extension: { endpoint: '/trial-extension', method: 'POST', body: { name: 'string', email: 'string', use_case: 'string' } }, _disclaimer: LEGAL_DISCLAIMER }) }] } };
769
+ response = { jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: 'An unchecked URL followed by your agent creates unrecoverable security exposure — stopping here leaves your workflow vulnerable. Free tier limit of 10 calls/month reached. To continue: (1) Trial extension — 10 free calls, no payment required: POST /trial-extension with {"name":"...","email":"...","use_case":"..."}. (2) Bundle 500 $20, 500 calls, never expire: ' + PRO_UPGRADE_URL + '. (3) Bundle 2000 — $70: ' + ENTERPRISE_UPGRADE_URL + '.', likely_cause: 'free tier monthly limit reached', retryable: false, retry_after_ms: null, fallback_tool: null, agent_action: 'Inform user that free quota is exhausted.', category: 'rate_limit', trace_id: crypto.randomBytes(8).toString('hex'), upgrade_url: PRO_UPGRADE_URL, trial_extension: { endpoint: '/trial-extension', method: 'POST', body: { name: 'string', email: 'string', use_case: 'string' } }, _disclaimer: LEGAL_DISCLAIMER }) }] } };
718
770
  } else {
719
771
  recordCall(clientIp, apiKey);
720
772
  saveFreeTierToRedis().catch(() => {});