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 +5 -0
- package/README.md +47 -4
- package/package.json +1 -1
- package/src/server.js +45 -6
package/CHANGELOG.md
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 |
|
|
44
|
+
| Plan | Calls | Price |
|
|
45
45
|
|---|---|---|
|
|
46
|
-
| Free |
|
|
47
|
-
|
|
|
48
|
-
|
|
|
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.
|
|
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
|
+
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
|
-
|
|
31
|
-
|
|
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 = '';
|