tender-mcp 1.2.8 → 1.2.9

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,8 @@
1
+ ## [1.2.9] - 2026-06-02
2
+
3
+ ### Fixed
4
+ - fix: IP extraction fixed for Cloudflare proxy headers — free tier gate now enforces correctly
5
+
1
6
  ## [1.2.5] - 2026-04-28
2
7
 
3
8
  ### Changed
package/README.md CHANGED
@@ -22,6 +22,49 @@ Or via Smithery:
22
22
  npx -y @smithery/cli@latest mcp add OjasKord/tender-mcp
23
23
  ```
24
24
 
25
+ ## Harness Integration
26
+
27
+ ### Claude Code / Claude Desktop (.mcp.json)
28
+ ```json
29
+ {
30
+ "mcpServers": {
31
+ "tender": {
32
+ "type": "http",
33
+ "url": "https://tender-mcp-production.up.railway.app"
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ### LangChain (Python)
40
+ ```python
41
+ from langchain_mcp_adapters.client import MultiServerMCPClient
42
+ client = MultiServerMCPClient({
43
+ "tender": {
44
+ "url": "https://tender-mcp-production.up.railway.app",
45
+ "transport": "http"
46
+ }
47
+ })
48
+ tools = await client.get_tools()
49
+ ```
50
+
51
+ ### OpenAI Agents SDK (Python)
52
+ ```python
53
+ from agents import Agent, HostedMCPTool
54
+ agent = Agent(
55
+ name="Assistant",
56
+ tools=[HostedMCPTool(tool_config={
57
+ "type": "mcp",
58
+ "server_label": "tender",
59
+ "server_url": "https://tender-mcp-production.up.railway.app",
60
+ "require_approval": "never"
61
+ })]
62
+ )
63
+ ```
64
+
65
+ ### LangGraph
66
+ Same as LangChain above — langchain-mcp-adapters works with LangGraph natively.
67
+
25
68
  ## Why Use This
26
69
 
27
70
  Any business that sells to government needs to monitor tender opportunities. But searching three separate government portals daily, reading hundreds of notices, and manually judging relevance takes hours. Tender MCP does it in seconds — search UK, EU, and US simultaneously, then let AI score which opportunities actually match your capabilities.
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.8",
4
+ "version": "1.2.9",
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/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.8';
6
+ const VERSION = '1.2.9';
7
7
  const PRO_UPGRADE_URL = 'https://buy.stripe.com/9B600i5k1bPv2xC6Fqebu0n';
8
8
  const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/7sY7sKaEldXDegk0h2ebu0o';
9
9
  const PERSIST_FILE = '/tmp/tender_stats.json';
@@ -571,7 +571,8 @@ function checkAccess(req, toolName) {
571
571
  }
572
572
 
573
573
  // Free tier — allow all tools, but pass tier='free' so executeTool can gate paid features
574
- const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
574
+ const rawIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
575
+ const ip = rawIp.split(',')[0].trim();
575
576
  const monthKey = getMonthKey(ip);
576
577
  const calls = freeTierUsage.get(monthKey) || 0;
577
578
  if (calls >= FREE_TIER_LIMIT) {
@@ -696,7 +697,8 @@ const server = http.createServer(async (req, res) => {
696
697
  if (!name || !email) { res.writeHead(400, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'name and email are required', agent_action: 'PROVIDE_REQUIRED_FIELDS' })); return; }
697
698
  const emailKey = 'trial:' + email.toLowerCase().trim();
698
699
  if (trialExtensions.has(emailKey)) { res.writeHead(409, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Trial extension already granted for this email.', upgrade_url: PRO_UPGRADE_URL, agent_action: 'INFORM_USER_TRIAL_ALREADY_USED' })); return; }
699
- const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
700
+ const rawIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
701
+ const ip = rawIp.split(',')[0].trim();
700
702
  const monthKey = getMonthKey(ip);
701
703
  const currentCalls = freeTierUsage.get(monthKey) || 0;
702
704
  freeTierUsage.set(monthKey, Math.max(0, currentCalls - TRIAL_EXTENSION_CALLS));
@@ -753,7 +755,8 @@ const server = http.createServer(async (req, res) => {
753
755
  return;
754
756
  }
755
757
 
756
- const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
758
+ const rawIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
759
+ const ip = rawIp.split(',')[0].trim();
757
760
  usageLog.push({ tool: name, tier: access.tier, time: nowISO(), ip: ip.slice(0, 8) + '...' });
758
761
  if (usageLog.length > 1000) usageLog.shift();
759
762
  toolUsageCounts[name] = (toolUsageCounts[name] || 0) + 1;