tender-mcp 1.2.1 → 1.2.2

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.2] - 2026-04-26
2
+ ### Changed
3
+ - VERSION constant introduced as single source of truth (was behind package.json)
4
+ - Added `agent_action` to all error responses (PROVIDE_REQUIRED_FIELD, RETRY_IN_2_MIN)
5
+ - Added stdio transport for Claude Desktop / npm usage
6
+ - Fixed em-dash in analysis_type and AI scoring error strings (ASCII --)
7
+
1
8
  ## [1.2.0] - 2026-04-21
2
9
  ### Changed
3
10
  - Consolidated from 5 tools to 2: search_tenders and get_tender_intelligence
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.1",
4
+ "version": "1.2.2",
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.1",
6
+ "version": "1.2.2",
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.1",
16
+ "version": "1.2.2",
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.0';
6
+ const VERSION = '1.2.2';
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 || '';
@@ -257,7 +257,7 @@ function normaliseSAMTender(o) {
257
257
  const tools = [
258
258
  {
259
259
  name: 'search_tenders',
260
- description: 'Call this tool any time your agent needs to find and evaluate government contract opportunities. Use when helping a company identify new revenue from public sector clients, when an agent needs to find bid opportunities, or when researching government procurement in a specific sector. Searches UK Contracts Finder, EU TED, and US SAM.gov simultaneously. Returns normalised results with AI fit scoring already applied each tender includes a score 0-100, BID/INVESTIGATE/SKIP recommendation, and specific reasons. One call returns everything the agent needs to act. AI-powered analysis NOT a simple keyword search. Data sourced directly from official government portals. LEGAL NOTICE: Always verify deadlines with the contracting authority before bidding. We do not log your query content. Full terms: kordagencies.com/terms.html. Free tier: first 10 searches/month, no API key needed.',
260
+ description: 'Call this tool when your agent needs to find and evaluate government contract opportunities for a company. Use when identifying new public sector revenue, researching procurement in a specific sector, or qualifying bid opportunities before committing resources. Searches UK Contracts Finder, EU TED, and US SAM.gov simultaneously. Returns normalised results with AI fit scoring already applied -- each tender includes a score 0-100, BID / INVESTIGATE / SKIP recommendation, and specific reasons so your agent can act immediately without further analysis. One call, three markets, machine-ready verdicts. AI-powered -- NOT a simple keyword search. Data sourced directly from official government portals. LEGAL NOTICE: Always verify deadlines with the contracting authority before bidding. We do not log your query content. Full terms: kordagencies.com/terms.html. Free tier: first 10 searches/month, no API key needed.',
261
261
  inputSchema: {
262
262
  type: 'object',
263
263
  properties: {
@@ -273,7 +273,7 @@ const tools = [
273
273
  },
274
274
  {
275
275
  name: 'get_tender_intelligence',
276
- description: 'Call this tool for ongoing procurement intelligence beyond one-off search. Two modes: DAILY_DIGEST returns all new tenders posted in the last 24 hours matching your keywords use as a daily monitoring tool so no new opportunity is missed before competitors see it. AWARD_HISTORY returns past contract winners for a keyword use for competitive intelligence before bidding, to find teaming partners, or to understand which companies dominate a sector. Both modes search UK, EU, and US simultaneously. AI-powered analysis NOT a simple database lookup. LEGAL NOTICE: Award data may be incomplete as not all authorities publish award notices. We do not log your query content. Full terms: kordagencies.com/terms.html. Paid API key required upgrade at kordagencies.com.',
276
+ description: 'Call this tool when your agent needs continuous procurement intelligence rather than a one-off search. DAILY_DIGEST mode: returns all new tenders posted in the last 24 hours matching your keywords -- use this on a daily schedule so your agent never misses an opportunity before competitors see it. AWARD_HISTORY mode: returns past contract winners for a keyword -- use before bidding to understand who dominates a sector, find teaming partners, or set realistic win probability. A company that bids without award history is bidding blind. Both modes search UK, EU, and US simultaneously. AI-powered -- NOT a simple database lookup. LEGAL NOTICE: Award data may be incomplete as not all authorities publish award notices. We do not log your query content. Full terms: kordagencies.com/terms.html. Paid API key required -- upgrade at kordagencies.com.',
277
277
  inputSchema: {
278
278
  type: 'object',
279
279
  properties: {
@@ -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', _disclaimer: LEGAL_DISCLAIMER };
299
+ if (!keyword) return { error: 'keyword is required', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
300
300
 
301
301
  const fetchLimit = Math.min(limit || 10, 25);
302
302
  const daysOld = days_old || 30;
@@ -352,10 +352,10 @@ async function executeTool(name, args, tier) {
352
352
  threshold_used: threshold,
353
353
  top_opportunities: aiResult.top_opportunities || [],
354
354
  market_insight: aiResult.market_insight || null,
355
- analysis_type: 'AI-powered fit scoring NOT a simple keyword match'
355
+ analysis_type: 'AI-powered fit scoring -- NOT a simple keyword match'
356
356
  };
357
357
  } catch(e) {
358
- scoringMeta = { error: 'AI scoring unavailable results returned unscored. Manual review recommended.' };
358
+ scoringMeta = { error: 'AI scoring unavailable -- results returned unscored. Manual review recommended.', agent_action: 'RETRY_IN_2_MIN' };
359
359
  }
360
360
  }
361
361
 
@@ -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', _disclaimer: LEGAL_DISCLAIMER };
387
+ if (!mode) return { error: 'mode is required: DAILY_DIGEST or AWARD_HISTORY', agent_action: 'PROVIDE_REQUIRED_FIELD', _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', _disclaimer: LEGAL_DISCLAIMER };
392
+ return { error: 'keywords array is required for DAILY_DIGEST mode', agent_action: 'PROVIDE_REQUIRED_FIELD', _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', _disclaimer: LEGAL_DISCLAIMER };
459
+ if (!keyword) return { error: 'keyword is required for AWARD_HISTORY mode', agent_action: 'PROVIDE_REQUIRED_FIELD', _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.', _disclaimer: LEGAL_DISCLAIMER };
527
+ return { error: 'Invalid mode. Use DAILY_DIGEST or AWARD_HISTORY.', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER };
528
528
  }
529
529
 
530
- return { error: 'Unknown tool: ' + name };
530
+ return { error: 'Unknown tool: ' + name, agent_action: 'RETRY_IN_2_MIN' };
531
531
  }
532
532
 
533
533
  // ─── ACCESS CONTROL ───────────────────────────────────────────────────────────
@@ -687,7 +687,7 @@ const server = http.createServer(async (req, res) => {
687
687
 
688
688
  if (!access.allowed) {
689
689
  res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
690
- res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: access.reason, upgrade_url: 'https://kordagencies.com', _disclaimer: LEGAL_DISCLAIMER }) }] } }));
690
+ res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: access.reason, agent_action: access.tier === 'invalid' ? 'PROVIDE_REQUIRED_FIELD' : 'Inform user free tier quota is exhausted. Upgrade available at kordagencies.com', upgrade_url: 'https://kordagencies.com', _disclaimer: LEGAL_DISCLAIMER }) }] } }));
691
691
  return;
692
692
  }
693
693
 
@@ -744,6 +744,49 @@ const server = http.createServer(async (req, res) => {
744
744
  res.writeHead(404, cors); res.end(JSON.stringify({ error: 'Not found' }));
745
745
  });
746
746
 
747
+ function setupStdio() {
748
+ if (process.stdin.isTTY) return;
749
+ let buf = '';
750
+ process.stdin.setEncoding('utf8');
751
+ process.stdin.on('data', chunk => {
752
+ buf += chunk;
753
+ let nl;
754
+ while ((nl = buf.indexOf('\n')) !== -1) {
755
+ const line = buf.slice(0, nl).trim();
756
+ buf = buf.slice(nl + 1);
757
+ if (!line) continue;
758
+ let req;
759
+ try { req = JSON.parse(line); } catch(e) { process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }) + '\n'); continue; }
760
+ let resp;
761
+ if (req.method === 'initialize') {
762
+ resp = { jsonrpc: '2.0', id: req.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {}, resources: {}, prompts: {} }, serverInfo: { name: 'tender-mcp', version: VERSION, description: 'Government tender search and AI fit scoring. UK, EU, US. 2 tools. Free tier: 10 searches/month.' } } };
763
+ } else if (req.method === 'notifications/initialized') {
764
+ continue;
765
+ } else if (req.method === 'tools/list') {
766
+ resp = { jsonrpc: '2.0', id: req.id, result: { tools } };
767
+ } else if (req.method === 'resources/list') {
768
+ resp = { jsonrpc: '2.0', id: req.id, result: { resources: [] } };
769
+ } else if (req.method === 'prompts/list') {
770
+ resp = { jsonrpc: '2.0', id: req.id, result: { prompts: [] } };
771
+ } else if (req.method === 'tools/call') {
772
+ const { name, arguments: toolArgs } = req.params || {};
773
+ executeTool(name, toolArgs || {}, 'pro').then(result => {
774
+ process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: req.id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } }) + '\n');
775
+ }).catch(err => {
776
+ process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: req.id, error: { code: -32603, message: err.message } }) + '\n');
777
+ });
778
+ continue;
779
+ } else {
780
+ resp = { jsonrpc: '2.0', id: req.id, error: { code: -32601, message: 'Method not found: ' + req.method } };
781
+ }
782
+ process.stdout.write(JSON.stringify(resp) + '\n');
783
+ }
784
+ });
785
+ process.stdin.resume();
786
+ }
787
+
788
+ setupStdio();
789
+
747
790
  server.listen(PORT, () => {
748
791
  loadStats();
749
792
  console.log('Tender MCP v' + VERSION + ' running on port ' + PORT);