local-model-suitability-mcp 1.1.23 → 1.1.25
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 +3 -0
- package/package.json +1 -1
- package/src/server.js +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.24] - 2026-06-26
|
|
4
|
+
- fix: trial extension requests now written to Redis (lms:trial:{email}) on grant -- permanent audit trail that survives redeploys; previously in-memory only
|
|
5
|
+
|
|
3
6
|
## [1.1.23] - 2026-06-25
|
|
4
7
|
- feat: calls_remaining field added to check_local_viability response -- "unlimited" for paid keys, numeric free-tier headroom otherwise (HTTP POST and stdio transports)
|
|
5
8
|
- feat: verdict_ttl field added (86400s/24h)
|
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.25",
|
|
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/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.25';
|
|
7
7
|
const FIRST_DEPLOYED = '2026-04-13T06:41:38Z';
|
|
8
8
|
const LIFETIME_CALLS_REDIS_KEY = 'lms:lifetime_calls';
|
|
9
9
|
const UPTIME_HEARTBEAT_KEY = 'lms:uptime:heartbeat_count';
|
|
@@ -24,7 +24,7 @@ function nowISO() { return new Date().toISOString(); }
|
|
|
24
24
|
const cors = {
|
|
25
25
|
'Access-Control-Allow-Origin': '*',
|
|
26
26
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, HEAD',
|
|
27
|
-
'Access-Control-Allow-Headers': 'Content-Type, x-api-key, x-stats-key'
|
|
27
|
+
'Access-Control-Allow-Headers': 'Content-Type, x-api-key, x-stats-key, x-owner-key'
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
// ── Stats persistence ─────────────────────────────────────────────────────────
|
|
@@ -312,6 +312,7 @@ async function saveFreeTierToRedis() {
|
|
|
312
312
|
|
|
313
313
|
// ── Anthropic client ──────────────────────────────────────────────────────────
|
|
314
314
|
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
315
|
+
const OWNER_KEY = process.env.OWNER_KEY || '';
|
|
315
316
|
|
|
316
317
|
// ── Cloud pricing reference (approximate, per 1K tokens, mid-2026) ───────────
|
|
317
318
|
const CLOUD_PRICING = {
|
|
@@ -715,6 +716,7 @@ const server = createServer(async (req, res) => {
|
|
|
715
716
|
stats.free_tier_calls_by_ip[clientIp][month] = Math.max(0, current - TRIAL_EXTENSION_CALLS);
|
|
716
717
|
trialExtensions.set(emailKey, { name, email, use_case: use_case || '', ip: clientIp, granted_at: nowISO() });
|
|
717
718
|
saveStats();
|
|
719
|
+
await redisSet(REDIS_PREFIX + ':trial:' + email.toLowerCase().trim(), { name, email, use_case: use_case || '', ip: clientIp, timestamp: nowISO(), server: 'local-model-suitability-mcp' });
|
|
718
720
|
// 24h follow-up record -- processed by /process-trial-followups (fleet cron)
|
|
719
721
|
await redisSet(REDIS_PREFIX + ':followup:' + email.toLowerCase().trim(), { email, name, server: 'local-model-suitability-mcp', granted_at: nowISO(), sent: false });
|
|
720
722
|
const sendTrialEmail = async (to, subject, html) => {
|
|
@@ -884,7 +886,12 @@ const server = createServer(async (req, res) => {
|
|
|
884
886
|
result: { content: [{ type: 'text', text: JSON.stringify({ error: 'task is required — describe what you are about to send to the cloud model', likely_cause: 'required field missing or malformed', retryable: false, retry_after_ms: null, fallback_tool: null, agent_action: 'PROVIDE_REQUIRED_FIELD', category: 'invalid_input', trace_id: nowISO(), _disclaimer: LEGAL_DISCLAIMER }) }] }
|
|
885
887
|
};
|
|
886
888
|
} else {
|
|
887
|
-
const
|
|
889
|
+
const isOwner = OWNER_KEY !== '' && (req.headers['x-owner-key'] || request.owner_key || '') === OWNER_KEY;
|
|
890
|
+
if (isOwner) {
|
|
891
|
+
redisIncr('lms:owner_calls:' + new Date().toISOString().slice(0, 7)).catch(() => {});
|
|
892
|
+
console.log('[owner] owner key used');
|
|
893
|
+
}
|
|
894
|
+
const access = isOwner ? { allowed: true, tier: 'owner', plan: 'owner', remaining: Infinity } : await checkAccess(clientIp, apiKey);
|
|
888
895
|
|
|
889
896
|
if (!access.allowed) {
|
|
890
897
|
statusCode = 402;
|