local-model-suitability-mcp 1.1.1 → 1.1.2
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/package.json +1 -1
- package/server.json +7 -20
- package/src/server.js +52 -6
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.2",
|
|
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/server.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.OjasKord/local-model-suitability-mcp",
|
|
4
|
-
"version": "1.1.0",
|
|
5
|
-
"description": "Check if a task runs locally vs cloud. Save money on calls that don't need cloud inference.",
|
|
6
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.2",
|
|
7
7
|
"websiteUrl": "https://kordagencies.com",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/OjasKord/local-model-suitability-mcp",
|
|
@@ -12,26 +12,13 @@
|
|
|
12
12
|
"packages": [
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
|
-
"registryBaseUrl": "https://registry.npmjs.org",
|
|
16
15
|
"identifier": "local-model-suitability-mcp",
|
|
17
|
-
"version": "1.1.
|
|
18
|
-
"transport": {
|
|
19
|
-
"type": "stdio"
|
|
20
|
-
},
|
|
16
|
+
"version": "1.1.2",
|
|
17
|
+
"transport": { "type": "stdio" },
|
|
21
18
|
"environmentVariables": [
|
|
22
|
-
{
|
|
23
|
-
"name": "ANTHROPIC_API_KEY",
|
|
24
|
-
"description": "Anthropic API key for Claude routing analysis",
|
|
25
|
-
"isRequired": true,
|
|
26
|
-
"isSecret": true
|
|
27
|
-
}
|
|
19
|
+
{ "name": "ANTHROPIC_API_KEY", "description": "Anthropic API key for Claude routing analysis", "isRequired": true, "isSecret": true }
|
|
28
20
|
]
|
|
29
21
|
}
|
|
30
22
|
],
|
|
31
|
-
"remotes": [
|
|
32
|
-
|
|
33
|
-
"type": "streamable-http",
|
|
34
|
-
"url": "https://local-model-suitability-mcp-production.up.railway.app"
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
}
|
|
23
|
+
"remotes": [{ "type": "streamable-http", "url": "https://local-model-suitability-mcp-production.up.railway.app" }]
|
|
24
|
+
}
|
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.2';
|
|
7
7
|
const PERSIST_FILE = '/tmp/lms_stats.json';
|
|
8
8
|
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';
|
|
9
9
|
|
|
@@ -169,7 +169,7 @@ Respond ONLY with a JSON object — no markdown, no explanation outside the JSON
|
|
|
169
169
|
}`;
|
|
170
170
|
|
|
171
171
|
const response = await anthropic.messages.create({
|
|
172
|
-
model: 'claude-sonnet-4-
|
|
172
|
+
model: 'claude-sonnet-4-6',
|
|
173
173
|
max_tokens: 500,
|
|
174
174
|
system: systemPrompt,
|
|
175
175
|
messages: [{ role: 'user', content: `Task to evaluate: ${task}` }]
|
|
@@ -358,7 +358,7 @@ const server = createServer(async (req, res) => {
|
|
|
358
358
|
result: {
|
|
359
359
|
protocolVersion: '2024-11-05',
|
|
360
360
|
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
361
|
-
serverInfo: { name: 'local-model-suitability-mcp', version: VERSION }
|
|
361
|
+
serverInfo: { name: 'local-model-suitability-mcp', version: VERSION, description: 'Checks whether each task can run on a local model instead of cloud -- saves money on every call that does not need cloud inference. Free tier: 20 calls/month, no API key needed.' }
|
|
362
362
|
}
|
|
363
363
|
};
|
|
364
364
|
} else if (request.method === 'notifications/initialized') {
|
|
@@ -375,7 +375,7 @@ const server = createServer(async (req, res) => {
|
|
|
375
375
|
if (!task || task.trim().length === 0) {
|
|
376
376
|
response = {
|
|
377
377
|
jsonrpc: '2.0', id: request.id,
|
|
378
|
-
result: { content: [{ type: 'text', text: JSON.stringify({ error: 'task is required — describe what you are about to send to the cloud model', _disclaimer: LEGAL_DISCLAIMER }) }] }
|
|
378
|
+
result: { content: [{ type: 'text', text: JSON.stringify({ error: 'task is required — describe what you are about to send to the cloud model', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER }) }] }
|
|
379
379
|
};
|
|
380
380
|
} else {
|
|
381
381
|
const access = checkAccess(clientIp, apiKey);
|
|
@@ -383,7 +383,7 @@ const server = createServer(async (req, res) => {
|
|
|
383
383
|
if (!access.allowed) {
|
|
384
384
|
response = {
|
|
385
385
|
jsonrpc: '2.0', id: request.id,
|
|
386
|
-
result: { content: [{ type: 'text', text: JSON.stringify({ error: `Free tier limit of ${FREE_TIER_LIMIT} calls/month reached. You have seen it work
|
|
386
|
+
result: { content: [{ type: 'text', text: JSON.stringify({ error: `Free tier limit of ${FREE_TIER_LIMIT} calls/month reached. You have seen it work -- upgrade to Pro ($99/month) at kordagencies.com to continue saving on cloud costs.`, agent_action: 'Inform user free tier quota is exhausted. Upgrade available at kordagencies.com', upgrade_url: 'https://kordagencies.com' }) }] }
|
|
387
387
|
};
|
|
388
388
|
} else {
|
|
389
389
|
if (access.tier === 'free') incrementFreeTier(clientIp);
|
|
@@ -416,7 +416,7 @@ const server = createServer(async (req, res) => {
|
|
|
416
416
|
console.error('[lms] AI error:', e.message);
|
|
417
417
|
response = {
|
|
418
418
|
jsonrpc: '2.0', id: request.id,
|
|
419
|
-
result: { content: [{ type: 'text', text: JSON.stringify({ error: 'AI analysis temporarily unavailable
|
|
419
|
+
result: { content: [{ type: 'text', text: JSON.stringify({ error: 'AI analysis temporarily unavailable -- this is not a problem with your task. Retry in a few minutes.', agent_action: 'RETRY_IN_2_MIN', checked_at: nowISO(), _disclaimer: LEGAL_DISCLAIMER }) }] }
|
|
420
420
|
};
|
|
421
421
|
}
|
|
422
422
|
}
|
|
@@ -440,6 +440,52 @@ const server = createServer(async (req, res) => {
|
|
|
440
440
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
441
441
|
});
|
|
442
442
|
|
|
443
|
+
function setupStdio() {
|
|
444
|
+
if (process.stdin.isTTY) return;
|
|
445
|
+
let buf = '';
|
|
446
|
+
process.stdin.setEncoding('utf8');
|
|
447
|
+
process.stdin.on('data', chunk => {
|
|
448
|
+
buf += chunk;
|
|
449
|
+
const lines = buf.split('\n');
|
|
450
|
+
buf = lines.pop();
|
|
451
|
+
lines.forEach(async line => {
|
|
452
|
+
if (!line.trim()) return;
|
|
453
|
+
let req;
|
|
454
|
+
try { req = JSON.parse(line); } catch(e) { return; }
|
|
455
|
+
let response;
|
|
456
|
+
if (req.method === 'initialize') {
|
|
457
|
+
response = { jsonrpc: '2.0', id: req.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {}, resources: {}, prompts: {} }, serverInfo: { name: 'local-model-suitability-mcp', version: VERSION, description: 'Checks whether each task can run on a local model instead of cloud -- saves money on every call that does not need cloud inference. Free tier: 20 calls/month, no API key needed.' } } };
|
|
458
|
+
} else if (req.method === 'notifications/initialized') {
|
|
459
|
+
return;
|
|
460
|
+
} else if (req.method === 'tools/list') {
|
|
461
|
+
response = { jsonrpc: '2.0', id: req.id, result: { tools: [TOOL_DEFINITION] } };
|
|
462
|
+
} else if (req.method === 'resources/list') {
|
|
463
|
+
response = { jsonrpc: '2.0', id: req.id, result: { resources: [] } };
|
|
464
|
+
} else if (req.method === 'prompts/list') {
|
|
465
|
+
response = { jsonrpc: '2.0', id: req.id, result: { prompts: [] } };
|
|
466
|
+
} else if (req.method === 'tools/call' && req.params?.name === 'check_local_viability') {
|
|
467
|
+
const { task, quality_threshold, data_sensitivity } = req.params.arguments || {};
|
|
468
|
+
if (!task || task.trim().length === 0) {
|
|
469
|
+
response = { jsonrpc: '2.0', id: req.id, result: { content: [{ type: 'text', text: JSON.stringify({ error: 'task is required', agent_action: 'PROVIDE_REQUIRED_FIELD', _disclaimer: LEGAL_DISCLAIMER }) }] } };
|
|
470
|
+
} else {
|
|
471
|
+
try {
|
|
472
|
+
const result = await checkLocalViability(task, quality_threshold, data_sensitivity);
|
|
473
|
+
response = { jsonrpc: '2.0', id: req.id, result: { content: [{ type: 'text', text: JSON.stringify(result) }] } };
|
|
474
|
+
} catch(e) {
|
|
475
|
+
response = { jsonrpc: '2.0', id: req.id, error: { code: -32603, message: e.message, agent_action: 'RETRY_IN_2_MIN' } };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
response = { jsonrpc: '2.0', id: req.id, error: { code: -32601, message: 'Method not found: ' + req.method } };
|
|
480
|
+
}
|
|
481
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
process.stdin.resume();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
setupStdio();
|
|
488
|
+
|
|
443
489
|
const PORT = process.env.PORT || 3000;
|
|
444
490
|
server.listen(PORT, () => {
|
|
445
491
|
console.log(`[lms] Local Model Suitability MCP v${VERSION} running on port ${PORT}`);
|