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 +7 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/server.js +55 -12
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
355
|
+
analysis_type: 'AI-powered fit scoring -- NOT a simple keyword match'
|
|
356
356
|
};
|
|
357
357
|
} catch(e) {
|
|
358
|
-
scoringMeta = { error: 'AI scoring unavailable
|
|
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);
|