tender-mcp 1.2.17 → 1.2.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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [1.2.20] - 2026-06-17
2
+ - fix: sendEmail now logs Resend HTTP errors in Railway logs
3
+
4
+ ## [1.2.19] - 2026-06-17
5
+ - fix: Stripe webhook now validates payment_link ID — ignores events not belonging to this server
6
+
7
+ ## [1.2.18] - 2026-06-17
8
+ - feat: SmitheryBot detection on search_tenders and get_tender_intelligence — returns mock empty results without consuming SAM.gov/UK/EU TED API credits
9
+
1
10
  ## [1.2.17] - 2026-06-16
2
11
  - feat: ATO optimisation — purpose verb, usage context, required fields, ToolRank badge
3
12
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tender-mcp",
3
3
  "mcpName": "io.github.OjasKord/tender-mcp",
4
- "version": "1.2.17",
4
+ "version": "1.2.20",
5
5
  "description": "Government tender search for AI agents. UK, EU, US contracts with AI bid scoring. BID/SKIP verdict with deadline and value in one call.",
6
6
  "main": "src/server.js",
7
7
  "scripts": {
package/server.json CHANGED
@@ -1,24 +1,36 @@
1
- {
2
- "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "io.github.OjasKord/tender-mcp",
4
- "title": "Tender MCP",
5
- "description": "Government tender search for AI agents. UK, EU and US procurement opportunities.",
6
- "version": "1.2.6",
7
- "websiteUrl": "https://kordagencies.com",
8
- "repository": {
9
- "url": "https://github.com/OjasKord/tender-mcp",
10
- "source": "github"
11
- },
12
- "packages": [
13
- {
14
- "registryType": "npm",
15
- "identifier": "tender-mcp",
16
- "version": "1.2.6",
17
- "transport": { "type": "stdio" },
18
- "environmentVariables": [
19
- { "name": "ANTHROPIC_API_KEY", "description": "Anthropic API key for AI-powered tender scoring", "isRequired": true, "isSecret": true }
20
- ]
21
- }
22
- ],
23
- "remotes": [{ "type": "streamable-http", "url": "https://tender-mcp-production.up.railway.app" }]
24
- }
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.OjasKord/tender-mcp",
4
+ "title": "Tender MCP",
5
+ "description": "Government tender search for AI agents. UK, EU and US procurement opportunities.",
6
+ "version": "1.2.18",
7
+ "websiteUrl": "https://kordagencies.com",
8
+ "repository": {
9
+ "url": "https://github.com/OjasKord/tender-mcp",
10
+ "source": "github"
11
+ },
12
+ "packages": [
13
+ {
14
+ "registryType": "npm",
15
+ "identifier": "tender-mcp",
16
+ "version": "1.2.17",
17
+ "transport": {
18
+ "type": "stdio"
19
+ },
20
+ "environmentVariables": [
21
+ {
22
+ "name": "ANTHROPIC_API_KEY",
23
+ "description": "Anthropic API key for AI-powered tender scoring",
24
+ "isRequired": true,
25
+ "isSecret": true
26
+ }
27
+ ]
28
+ }
29
+ ],
30
+ "remotes": [
31
+ {
32
+ "type": "streamable-http",
33
+ "url": "https://tender-mcp-production.up.railway.app"
34
+ }
35
+ ]
36
+ }
package/src/server.js CHANGED
@@ -3,9 +3,10 @@ const https = require('https');
3
3
  const crypto = require('crypto');
4
4
  const fs = require('fs');
5
5
 
6
- const VERSION = '1.2.17';
6
+ const VERSION = '1.2.20';
7
7
  const PRO_UPGRADE_URL = 'https://buy.stripe.com/9B600i5k1bPv2xC6Fqebu0n';
8
8
  const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/7sY7sKaEldXDegk0h2ebu0o';
9
+ const ALLOWED_PAYMENT_LINK_IDS = ['plink_1TQz8hD6WvRe6sn3qXhoyAWT', 'plink_1TQzAhD6WvRe6sn3P0CAabOs'];
9
10
  const PERSIST_FILE = '/tmp/tender_stats.json';
10
11
  const API_KEYS_FILE = '/tmp/tender_apikeys.json';
11
12
  const RESEND_API_KEY = process.env.RESEND_API_KEY || '';
@@ -117,8 +118,11 @@ async function sendEmail(to, subject, html) {
117
118
  const req = https.request({
118
119
  hostname: 'api.resend.com', path: '/emails', method: 'POST',
119
120
  headers: { 'Authorization': 'Bearer ' + RESEND_API_KEY, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
120
- }, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => resolve({ status: res.statusCode, body: d })); });
121
- req.on('error', e => resolve({ error: e.message }));
121
+ }, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => {
122
+ if (res.statusCode < 200 || res.statusCode >= 300) console.error('[Resend] Email failed: HTTP ' + res.statusCode + ' ' + d);
123
+ resolve({ status: res.statusCode, body: d });
124
+ }); });
125
+ req.on('error', e => { console.error('[Resend] Email network error:', e.message); resolve({ error: e.message }); });
122
126
  req.write(body); req.end();
123
127
  });
124
128
  }
@@ -744,6 +748,11 @@ async function handleStripeWebhook(body, sig) {
744
748
  const event = JSON.parse(body);
745
749
  if (event.type === 'checkout.session.completed') {
746
750
  const session = event.data.object;
751
+ const paymentLinkId = session.payment_link;
752
+ if (paymentLinkId && !ALLOWED_PAYMENT_LINK_IDS.includes(paymentLinkId)) {
753
+ console.log('[tender] Webhook received but payment link ' + paymentLinkId + ' not for this server — ignoring.');
754
+ return { received: true, ignored: true };
755
+ }
747
756
  const email = session.customer_email || session.customer_details?.email;
748
757
  const plan = getPlanFromProduct(session.metadata?.product_name || '');
749
758
  if (email) {
@@ -967,6 +976,12 @@ const server = http.createServer(async (req, res) => {
967
976
  }
968
977
  const _rawIpKs = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
969
978
  const _clientIpKs = _rawIpKs.split(',')[0].trim();
979
+ if (['search_tenders', 'get_tender_intelligence'].includes(name) && (req.headers['user-agent'] || '').toLowerCase().includes('smithery')) {
980
+ // Detect Smithery scanner and return mock response to avoid consuming SAM.gov/UK/EU TED API credits
981
+ res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
982
+ res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ tenders: [], total_found: 0, sources_searched: [], _note: 'Mock response — scanner detected' }) }] } }));
983
+ return;
984
+ }
970
985
  if (['search_tenders', 'get_tender_intelligence'].includes(name) && !checkPerMinuteLimit(_clientIpKs, name, 10)) {
971
986
  res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
972
987
  res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: 'Rate limit exceeded — maximum 10 calls per minute per IP on AI-powered tools. Your workflow is calling this tool too rapidly.', agent_action: 'RETRY_IN_60_SEC', retryable: true, retry_after_ms: 60000, limit: 10, window: '1 minute' }) }] } }));