tender-mcp 1.2.3 → 1.2.4

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,10 @@
1
+ ## [1.2.4] - 2026-04-27
2
+
3
+ ### Added
4
+ - `token_count` field on all tool responses — lets orchestrator budget ledgers track token cost per call
5
+ - `/ready` endpoint — returns 200 when `ANTHROPIC_API_KEY` and `SAM_GOV_API_KEY` are present, 503 otherwise
6
+ - Phase 4 enhanced error objects: `category`, `retryable`, `retry_after_ms`, `fallback_tool`, `trace_id` on all error paths across both tools
7
+
1
8
  ## [1.2.3] - 2026-04-26
2
9
 
3
10
  ### Improved
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.3",
4
+ "version": "1.2.4",
5
5
  "description": "Government tender search and AI opportunity scoring for AI agents. UK Contracts Finder, EU TED, US SAM.gov.",
6
6
  "main": "src/server.js",
7
7
  "scripts": {
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.OjasKord/tender-mcp",
4
4
  "title": "Tender MCP",
5
5
  "description": "Government tender search for AI agents. UK, EU and US procurement opportunities.",
6
- "version": "1.2.3",
6
+ "version": "1.2.4",
7
7
  "websiteUrl": "https://kordagencies.com",
8
8
  "repository": {
9
9
  "url": "https://github.com/OjasKord/tender-mcp",
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "tender-mcp",
16
- "version": "1.2.3",
16
+ "version": "1.2.4",
17
17
  "transport": { "type": "stdio" },
18
18
  "environmentVariables": [
19
19
  { "name": "ANTHROPIC_API_KEY", "description": "Anthropic API key for AI-powered tender scoring", "isRequired": true, "isSecret": true }
package/src/server.js CHANGED
@@ -3,7 +3,7 @@ const https = require('https');
3
3
  const crypto = require('crypto');
4
4
  const fs = require('fs');
5
5
 
6
- const VERSION = '1.2.3';
6
+ const VERSION = '1.2.4';
7
7
  const PERSIST_FILE = '/tmp/tender_stats.json';
8
8
  const RESEND_API_KEY = process.env.RESEND_API_KEY || '';
9
9
  const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || '';
@@ -296,7 +296,7 @@ async function executeTool(name, args, tier) {
296
296
  // ── TOOL 1: search_tenders ──────────────────────────────────────────────────
297
297
  if (name === 'search_tenders') {
298
298
  const { keyword, company_profile, sources = ['uk', 'eu', 'us'], limit, days_old, min_score } = args;
299
- if (!keyword) return { error: 'keyword is required', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
299
+ if (!keyword) return { error: 'keyword is required', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'search_tenders', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
300
300
 
301
301
  const fetchLimit = Math.min(limit || 10, 25);
302
302
  const daysOld = days_old || 30;
@@ -384,12 +384,12 @@ async function executeTool(name, args, tier) {
384
384
  // ── TOOL 2: get_tender_intelligence ────────────────────────────────────────
385
385
  if (name === 'get_tender_intelligence') {
386
386
  const { mode, keywords, keyword, sources = ['uk', 'eu', 'us'], limit } = args;
387
- if (!mode) return { error: 'mode is required: DAILY_DIGEST or AWARD_HISTORY', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
387
+ if (!mode) return { error: 'mode is required: DAILY_DIGEST or AWARD_HISTORY', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'get_tender_intelligence', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
388
388
 
389
389
  // ── DAILY_DIGEST ──
390
390
  if (mode === 'DAILY_DIGEST') {
391
391
  if (!keywords || !Array.isArray(keywords) || keywords.length === 0) {
392
- return { error: 'keywords array is required for DAILY_DIGEST mode', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
392
+ return { error: 'keywords array is required for DAILY_DIGEST mode', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'get_tender_intelligence', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
393
393
  }
394
394
 
395
395
  // Free tier preview: run one keyword, return count only — no full results
@@ -456,7 +456,7 @@ async function executeTool(name, args, tier) {
456
456
 
457
457
  // ── AWARD_HISTORY ──
458
458
  if (mode === 'AWARD_HISTORY') {
459
- if (!keyword) return { error: 'keyword is required for AWARD_HISTORY mode', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
459
+ if (!keyword) return { error: 'keyword is required for AWARD_HISTORY mode', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'get_tender_intelligence', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
460
460
  const maxResults = Math.min(limit || 10, 25);
461
461
 
462
462
  // Free tier preview: run search, return winner count + one sample name only
@@ -524,10 +524,10 @@ async function executeTool(name, args, tier) {
524
524
  };
525
525
  }
526
526
 
527
- return { error: 'Invalid mode. Use DAILY_DIGEST or AWARD_HISTORY.', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
527
+ return { error: 'Invalid mode. Use DAILY_DIGEST or AWARD_HISTORY.', agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', retryable: false, retry_after_ms: null, fallback_tool: 'get_tender_intelligence', trace_id: Math.random().toString(36).slice(2, 10), _disclaimer: LEGAL_DISCLAIMER };
528
528
  }
529
529
 
530
- return { error: 'Unknown tool: ' + name, agent_action: 'RETRY_IN_2_MIN' };
530
+ return { error: 'Unknown tool: ' + name, agent_action: 'RETRY_IN_2_MIN', category: 'unknown_tool', retryable: false, retry_after_ms: null, fallback_tool: null, trace_id: Math.random().toString(36).slice(2, 10) };
531
531
  }
532
532
 
533
533
  // ─── ACCESS CONTROL ───────────────────────────────────────────────────────────
@@ -614,9 +614,17 @@ const server = http.createServer(async (req, res) => {
614
614
  return;
615
615
  }
616
616
 
617
+ if (req.url === '/ready' && (req.method === 'GET' || req.method === 'HEAD')) {
618
+ const checks = { anthropic: !!ANTHROPIC_API_KEY, sam_gov: !!SAM_GOV_API_KEY };
619
+ const ready = checks.anthropic && checks.sam_gov;
620
+ res.writeHead(ready ? 200 : 503, { ...cors, 'Content-Type': 'application/json' });
621
+ res.end(JSON.stringify({ status: ready ? 'ready' : 'not_ready', version: VERSION, checks }));
622
+ return;
623
+ }
624
+
617
625
  if (req.url === '/.well-known/mcp/server-card.json') {
618
626
  res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
619
- res.end(JSON.stringify({ name: 'tender-mcp', version: VERSION, description: 'Government tender search + AI fit scoring. UK, EU, US. Free tier: 10 searches/month.', tools: tools.map(t => ({ name: t.name, description: t.description.slice(0, 100) })), transport: 'stdio', homepage: 'https://kordagencies.com', author: 'ojas1' }));
627
+ res.end(JSON.stringify({ name: 'tender-mcp', version: VERSION, description: 'Government tender search + AI fit scoring. UK, EU, US. Free tier: 10 searches/month.', tools: tools.map(t => ({ name: t.name, description: t.description.slice(0, 100) })), transport: 'streamable-http', homepage: 'https://kordagencies.com', author: 'ojas1', token_footprint_min: 300, token_footprint_max: 800, token_footprint_avg: 550, idempotent_tools: ['search_tenders', 'get_tender_intelligence'], circuit_breaker: false, health_endpoint: '/health', ready_endpoint: '/ready' }));
620
628
  return;
621
629
  }
622
630