local-model-suitability-mcp 1.1.6 → 1.1.8

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,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.8] - 2026-06-02
4
+
5
+ ### Fixed
6
+ - fix: IP extraction fixed for Cloudflare proxy headers — free tier gate now enforces correctly
7
+
3
8
  ## [1.1.5] - 2026-04-28
4
9
 
5
10
  ### Changed
package/README.md CHANGED
@@ -41,11 +41,11 @@ Call this BEFORE every cloud inference call. If verdict is `LOCAL`, skip the clo
41
41
 
42
42
  ## Pricing
43
43
 
44
- | Plan | Price | Calls/month |
44
+ | Plan | Calls | Price |
45
45
  |---|---|---|
46
- | Free | $0 | 20 |
47
- | Pro | $99/month | 2,000 |
48
- | Enterprise | $299/month | Unlimited |
46
+ | Free | 20/month | $0 |
47
+ | Starter | 500-call bundle | $20 |
48
+ | Pro | 2,000-call bundle | $70 |
49
49
 
50
50
  [Subscribe at kordagencies.com](https://kordagencies.com)
51
51
 
@@ -68,6 +68,49 @@ Call this BEFORE every cloud inference call. If verdict is `LOCAL`, skip the clo
68
68
 
69
69
  Free tier requires no API key — tracked by IP.
70
70
 
71
+ ## Harness Integration
72
+
73
+ ### Claude Code / Claude Desktop (.mcp.json)
74
+ ```json
75
+ {
76
+ "mcpServers": {
77
+ "local-model-suitability": {
78
+ "type": "http",
79
+ "url": "https://local-model-suitability-mcp-production.up.railway.app"
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ ### LangChain (Python)
86
+ ```python
87
+ from langchain_mcp_adapters.client import MultiServerMCPClient
88
+ client = MultiServerMCPClient({
89
+ "local-model-suitability": {
90
+ "url": "https://local-model-suitability-mcp-production.up.railway.app",
91
+ "transport": "http"
92
+ }
93
+ })
94
+ tools = await client.get_tools()
95
+ ```
96
+
97
+ ### OpenAI Agents SDK (Python)
98
+ ```python
99
+ from agents import Agent, HostedMCPTool
100
+ agent = Agent(
101
+ name="Assistant",
102
+ tools=[HostedMCPTool(tool_config={
103
+ "type": "mcp",
104
+ "server_label": "local-model-suitability",
105
+ "server_url": "https://local-model-suitability-mcp-production.up.railway.app",
106
+ "require_approval": "never"
107
+ })]
108
+ )
109
+ ```
110
+
111
+ ### LangGraph
112
+ Same as LangChain above — langchain-mcp-adapters works with LangGraph natively.
113
+
71
114
  ## Legal
72
115
 
73
116
  Results are for cost-optimisation guidance only and do not constitute technical advice. Full terms: [kordagencies.com/terms.html](https://kordagencies.com/terms.html)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "local-model-suitability-mcp",
3
3
  "mcpName": "io.github.OjasKord/local-model-suitability-mcp",
4
- "version": "1.1.6",
4
+ "version": "1.1.8",
5
5
  "description": "Check whether a task can run on a local model instead of cloud. Save money on every call that does not need cloud inference.",
6
6
  "main": "src/server.js",
7
7
  "type": "module",
package/src/server.js CHANGED
@@ -3,7 +3,7 @@ import { createHmac, timingSafeEqual } from 'crypto';
3
3
  import { readFileSync, writeFileSync } from 'fs';
4
4
  import Anthropic from '@anthropic-ai/sdk';
5
5
 
6
- const VERSION = '1.1.6';
6
+ const VERSION = '1.1.8';
7
7
  const PRO_UPGRADE_URL = 'https://buy.stripe.com/cNibJ08wd7zf6NS0h2ebu0p';
8
8
  const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/28E9AS27PbPvfkoe7Sebu0q';
9
9
  const PERSIST_FILE = '/tmp/lms_stats.json';
@@ -23,19 +23,23 @@ let stats = {
23
23
  recent_calls: [],
24
24
  free_tier_calls_by_ip: {}
25
25
  };
26
+ const trialExtensions = new Map();
27
+ const TRIAL_EXTENSION_CALLS = 10;
26
28
 
27
29
  function loadStats() {
28
30
  try {
29
31
  const data = JSON.parse(readFileSync(PERSIST_FILE, 'utf8'));
30
- stats = data;
31
- console.log('[lms] stats loaded from disk');
32
+ const { trialExtensions: te, ...rest } = data;
33
+ stats = rest;
34
+ if (te) te.forEach(([k, v]) => trialExtensions.set(k, v));
35
+ console.log('[lms] stats loaded from disk, ' + trialExtensions.size + ' trial extensions');
32
36
  } catch(e) {
33
37
  console.log('[lms] no stats file found — fresh start');
34
38
  }
35
39
  }
36
40
 
37
41
  function saveStats() {
38
- try { writeFileSync(PERSIST_FILE, JSON.stringify(stats)); } catch(e) {}
42
+ try { writeFileSync(PERSIST_FILE, JSON.stringify({ ...stats, trialExtensions: [...trialExtensions.entries()] })); } catch(e) {}
39
43
  }
40
44
 
41
45
  loadStats();
@@ -66,7 +70,7 @@ function checkAccess(ip, apiKey) {
66
70
  const count = getFreeTierCount(ip);
67
71
  const remaining = FREE_TIER_LIMIT - count;
68
72
  if (remaining <= 0) {
69
- return { allowed: false, tier: 'free', remaining: 0 };
73
+ return { allowed: false, tier: 'free', remaining: 0, reason: 'Free tier limit of ' + FREE_TIER_LIMIT + ' calls/month reached. Option 1: POST /trial-extension with {"name":"...","email":"...","use_case":"..."} for 10 extra free calls. Option 2: Upgrade at ' + PRO_UPGRADE_URL + ' (500 calls, never expire).', trial_extension: { endpoint: '/trial-extension', method: 'POST', body: { name: 'string', email: 'string', use_case: 'string' } }, upgrade_url: PRO_UPGRADE_URL };
70
74
  }
71
75
  return { allowed: true, tier: 'free', remaining, count };
72
76
  }
@@ -329,7 +333,8 @@ const server = createServer(async (req, res) => {
329
333
  free_tier_total_calls,
330
334
  paid_keys_issued: apiKeys.size,
331
335
  tool_usage: stats.tool_usage,
332
- recent_calls: stats.recent_calls.slice(-20).reverse()
336
+ recent_calls: stats.recent_calls.slice(-20).reverse(),
337
+ trial_extensions_granted: trialExtensions.size
333
338
  }));
334
339
  return;
335
340
  }
@@ -341,6 +346,40 @@ const server = createServer(async (req, res) => {
341
346
  return;
342
347
  }
343
348
 
349
+ // Trial extension
350
+ if (req.url === '/trial-extension' && req.method === 'POST') {
351
+ let body = '';
352
+ req.on('data', c => body += c);
353
+ req.on('end', async () => {
354
+ try {
355
+ const { name, email, use_case } = JSON.parse(body);
356
+ 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; }
357
+ const emailKey = 'trial:' + email.toLowerCase().trim();
358
+ 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; }
359
+ const month = MONTH_KEY();
360
+ if (!stats.free_tier_calls_by_ip[clientIp]) stats.free_tier_calls_by_ip[clientIp] = {};
361
+ const current = stats.free_tier_calls_by_ip[clientIp][month] || 0;
362
+ stats.free_tier_calls_by_ip[clientIp][month] = Math.max(0, current - TRIAL_EXTENSION_CALLS);
363
+ trialExtensions.set(emailKey, { name, email, use_case: use_case || '', ip: clientIp, granted_at: nowISO() });
364
+ saveStats();
365
+ const sendTrialEmail = async (to, subject, html) => {
366
+ await fetch('https://api.resend.com/emails', {
367
+ method: 'POST',
368
+ headers: { 'Authorization': `Bearer ${process.env.RESEND_API_KEY}`, 'Content-Type': 'application/json' },
369
+ body: JSON.stringify({ from: 'Local Model Suitability MCP <ojas@kordagencies.com>', to: [to], subject, html })
370
+ }).catch(e => console.error('[lms] email error:', e.message));
371
+ };
372
+ await sendTrialEmail('ojas@kordagencies.com', 'Local Model Suitability MCP -- Trial Extension: ' + name,
373
+ '<p><b>Name:</b> ' + name + '<br><b>Email:</b> ' + email + '<br><b>Use case:</b> ' + (use_case || 'Not provided') + '<br><b>IP:</b> ' + clientIp + '<br><b>Calls granted:</b> ' + TRIAL_EXTENSION_CALLS + '</p>');
374
+ await sendTrialEmail(email, TRIAL_EXTENSION_CALLS + ' extra free calls added -- Local Model Suitability MCP',
375
+ '<p>Hi ' + name + ',</p><p>Your ' + TRIAL_EXTENSION_CALLS + ' extra free calls have been added. You can keep using Local Model Suitability MCP right now -- no action needed.</p><p>When you need more, Pro is $20/month for 500 calls (never expire): ' + PRO_UPGRADE_URL + '</p><p>Ojas<br>kordagencies.com</p>');
376
+ res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
377
+ res.end(JSON.stringify({ granted: true, additional_calls: TRIAL_EXTENSION_CALLS, message: TRIAL_EXTENSION_CALLS + ' extra free calls added. Check your email for confirmation.', upgrade_url: PRO_UPGRADE_URL }));
378
+ } catch(e) { res.writeHead(400, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: e.message, agent_action: 'RETRY_IN_2_MIN' })); }
379
+ });
380
+ return;
381
+ }
382
+
344
383
  // Stripe webhook
345
384
  if (req.url === '/webhook/stripe' && req.method === 'POST') {
346
385
  let body = '';