local-model-suitability-mcp 1.1.16 → 1.1.19
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 +9 -0
- package/package.json +1 -1
- package/server.json +36 -24
- package/src/server.js +64 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.19] - 2026-06-18
|
|
4
|
+
- feat: revoke API key on Stripe refund
|
|
5
|
+
|
|
6
|
+
## [1.1.18] - 2026-06-17
|
|
7
|
+
- fix: Resend fetch now logs HTTP error responses; was silently swallowing non-2xx failures
|
|
8
|
+
|
|
9
|
+
## [1.1.17] - 2026-06-17
|
|
10
|
+
- fix: Stripe webhook now validates payment_link ID — ignores events not belonging to this server
|
|
11
|
+
|
|
3
12
|
## [1.1.16] - 2026-06-16
|
|
4
13
|
- feat: ATO optimisation — purpose verb, usage context, required fields, ToolRank badge
|
|
5
14
|
|
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.19",
|
|
5
5
|
"description": "AI model router for agents. Checks whether a local model can handle the task before calling cloud inference. LOCAL/CLOUD verdict saves cost on every call.",
|
|
6
6
|
"main": "src/server.js",
|
|
7
7
|
"type": "module",
|
package/server.json
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
-
"name": "io.github.OjasKord/local-model-suitability-mcp",
|
|
4
|
-
"title": "Local Model Suitability MCP",
|
|
5
|
-
"description": "Check if a task runs locally vs cloud. Save money on calls that don't need cloud inference.",
|
|
6
|
-
"version": "1.1.
|
|
7
|
-
"websiteUrl": "https://kordagencies.com",
|
|
8
|
-
"repository": {
|
|
9
|
-
"url": "https://github.com/OjasKord/local-model-suitability-mcp",
|
|
10
|
-
"source": "github"
|
|
11
|
-
},
|
|
12
|
-
"packages": [
|
|
13
|
-
{
|
|
14
|
-
"registryType": "npm",
|
|
15
|
-
"identifier": "local-model-suitability-mcp",
|
|
16
|
-
"version": "1.1.
|
|
17
|
-
"transport": {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.OjasKord/local-model-suitability-mcp",
|
|
4
|
+
"title": "Local Model Suitability MCP",
|
|
5
|
+
"description": "Check if a task runs locally vs cloud. Save money on calls that don't need cloud inference.",
|
|
6
|
+
"version": "1.1.16",
|
|
7
|
+
"websiteUrl": "https://kordagencies.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/OjasKord/local-model-suitability-mcp",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"packages": [
|
|
13
|
+
{
|
|
14
|
+
"registryType": "npm",
|
|
15
|
+
"identifier": "local-model-suitability-mcp",
|
|
16
|
+
"version": "1.1.16",
|
|
17
|
+
"transport": {
|
|
18
|
+
"type": "stdio"
|
|
19
|
+
},
|
|
20
|
+
"environmentVariables": [
|
|
21
|
+
{
|
|
22
|
+
"name": "ANTHROPIC_API_KEY",
|
|
23
|
+
"description": "Anthropic API key for Claude routing analysis",
|
|
24
|
+
"isRequired": true,
|
|
25
|
+
"isSecret": true
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"remotes": [
|
|
31
|
+
{
|
|
32
|
+
"type": "streamable-http",
|
|
33
|
+
"url": "https://local-model-suitability-mcp-production.up.railway.app"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
package/src/server.js
CHANGED
|
@@ -3,9 +3,10 @@ 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.19';
|
|
7
7
|
const PRO_UPGRADE_URL = 'https://buy.stripe.com/cNibJ08wd7zf6NS0h2ebu0p';
|
|
8
8
|
const ENTERPRISE_UPGRADE_URL = 'https://buy.stripe.com/28E9AS27PbPvfkoe7Sebu0q';
|
|
9
|
+
const ALLOWED_PAYMENT_LINK_IDS = ['plink_1TQzCBD6WvRe6sn3H1q5t2LF', 'plink_1TQzDSD6WvRe6sn3UM2G1EgX'];
|
|
9
10
|
const PERSIST_FILE = '/tmp/lms_stats.json';
|
|
10
11
|
const LEGAL_DISCLAIMER = 'AI-powered routing analysis. We do not log or store your task content. Results are for cost-optimisation guidance only. Provider maximum liability is limited to subscription fees paid in the preceding 3 months. Full terms: kordagencies.com/terms.html';
|
|
11
12
|
|
|
@@ -160,6 +161,26 @@ async function redisKeys(pattern) {
|
|
|
160
161
|
} catch(e) { return []; }
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
async function redisDelete(key) {
|
|
165
|
+
try {
|
|
166
|
+
const res = await fetch(
|
|
167
|
+
`${UPSTASH_URL}/del/${encodeURIComponent(key)}`,
|
|
168
|
+
{ method: 'POST', headers: { Authorization: `Bearer ${UPSTASH_TOKEN}` } }
|
|
169
|
+
);
|
|
170
|
+
const data = await res.json();
|
|
171
|
+
if (data.error) console.error('[Redis] redisDelete error:', data.error, 'key:', key);
|
|
172
|
+
} catch(e) { console.error('[Redis] redisDelete failed:', e); }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function findCheckoutSessionEmail(paymentIntentId) {
|
|
176
|
+
const res = await fetch(
|
|
177
|
+
`https://api.stripe.com/v1/checkout/sessions?payment_intent=${encodeURIComponent(paymentIntentId)}`,
|
|
178
|
+
{ headers: { Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}` } }
|
|
179
|
+
);
|
|
180
|
+
const data = await res.json();
|
|
181
|
+
return data.data?.[0]?.customer_details?.email || data.data?.[0]?.customer_email || null;
|
|
182
|
+
}
|
|
183
|
+
|
|
163
184
|
async function appendSessionLog(ip, tool) {
|
|
164
185
|
try {
|
|
165
186
|
const ipSafe = ip.replace(/:/g, '_').replace(/\s/g, '');
|
|
@@ -363,6 +384,11 @@ async function handleStripeWebhook(body, sig) {
|
|
|
363
384
|
|
|
364
385
|
if (event.type === 'checkout.session.completed') {
|
|
365
386
|
const session = event.data.object;
|
|
387
|
+
const paymentLinkId = session.payment_link;
|
|
388
|
+
if (paymentLinkId && !ALLOWED_PAYMENT_LINK_IDS.includes(paymentLinkId)) {
|
|
389
|
+
console.log('[lms] Webhook received but payment link ' + paymentLinkId + ' not for this server — ignoring.');
|
|
390
|
+
return { received: true, ignored: true };
|
|
391
|
+
}
|
|
366
392
|
const email = session.customer_details?.email;
|
|
367
393
|
const plan = session.metadata?.plan || 'pro';
|
|
368
394
|
const apiKey = 'lms_' + createHmac('sha256', secret).update(email + Date.now()).digest('hex').slice(0, 32);
|
|
@@ -400,7 +426,43 @@ async function handleStripeWebhook(body, sig) {
|
|
|
400
426
|
<p>Questions? Reply to this email.</p>
|
|
401
427
|
<p style="font-size:12px;color:#666;">Results are for cost-optimisation guidance only. Provider maximum liability limited to subscription fees paid in preceding 3 months. Full terms: <a href="https://kordagencies.com/terms.html">kordagencies.com/terms.html</a></p>`
|
|
402
428
|
})
|
|
403
|
-
}).
|
|
429
|
+
}).then(r => { if (!r.ok) r.text().then(t => console.error('[lms] Resend email failed: HTTP ' + r.status + ' ' + t)); })
|
|
430
|
+
.catch(e => console.error('[lms] Resend network error:', e.message));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (event.type === 'charge.refunded') {
|
|
435
|
+
if (!process.env.STRIPE_SECRET_KEY) {
|
|
436
|
+
console.error('[lms] STRIPE_SECRET_KEY not set — cannot revoke key on refund');
|
|
437
|
+
return { received: true, ignored: true, status: 200 };
|
|
438
|
+
}
|
|
439
|
+
const paymentIntentId = event.data.object.payment_intent;
|
|
440
|
+
if (!paymentIntentId) {
|
|
441
|
+
console.log('[lms] charge.refunded missing payment_intent — ignoring.');
|
|
442
|
+
return { received: true, ignored: true, status: 200 };
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
const email = await findCheckoutSessionEmail(paymentIntentId);
|
|
446
|
+
if (!email) {
|
|
447
|
+
console.log('[lms] No checkout session/email found for refunded payment_intent ' + paymentIntentId);
|
|
448
|
+
return { received: true, ignored: true, status: 200 };
|
|
449
|
+
}
|
|
450
|
+
let revokedKey = null;
|
|
451
|
+
for (const [key, record] of apiKeys.entries()) {
|
|
452
|
+
if (record.email === email) { revokedKey = key; break; }
|
|
453
|
+
}
|
|
454
|
+
if (!revokedKey) {
|
|
455
|
+
console.log('[lms] No API key found for ' + email + ' — refund received, nothing to revoke');
|
|
456
|
+
return { received: true, ignored: true, status: 200 };
|
|
457
|
+
}
|
|
458
|
+
apiKeys.delete(revokedKey);
|
|
459
|
+
await redisDelete(`${REDIS_PREFIX}:key:${revokedKey}`);
|
|
460
|
+
saveStats();
|
|
461
|
+
console.log('[Webhook] API key revoked for ' + email + ' — refund received');
|
|
462
|
+
return { received: true, revoked: true, status: 200 };
|
|
463
|
+
} catch(e) {
|
|
464
|
+
console.error('[lms] charge.refunded handling error:', e.message);
|
|
465
|
+
return { received: true, ignored: true, status: 200 };
|
|
404
466
|
}
|
|
405
467
|
}
|
|
406
468
|
|