ai-compute-radar 0.0.0-stage → 1.2.1
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/LICENSE +24 -0
- package/README.md +60 -2
- package/bin.mjs +29 -0
- package/bridge.mjs +106 -0
- package/package.json +14 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AI Compute Radar (https://aicomputeradar.dev)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
The data the bridged server returns is licensed separately: CC BY 4.0,
|
|
24
|
+
attribution "AI Compute Radar (https://aicomputeradar.dev)".
|
package/README.md
CHANGED
|
@@ -1,3 +1,61 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ai-compute-radar
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server for questions that usually end in a guess: will this open model run on my GPU or Mac, what does a GPU-hour cost right now, which open models are gaining attention this week.
|
|
4
|
+
|
|
5
|
+
This package is a stdio bridge to the hosted server at `https://aicomputeradar.dev/api/mcp`. It holds no data, keeps no cache, sends no telemetry and needs no key; it forwards each JSON-RPC message and writes the answer back. Clients that speak Streamable HTTP can use the URL directly and do not need it.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Claude Desktop, Claude Code, Cursor and other stdio clients:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"ai-compute-radar": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["-y", "ai-compute-radar"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Claude Code, one line:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
claude mcp add ai-compute-radar -- npx -y ai-compute-radar
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Clients that speak Streamable HTTP (no package needed):
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"ai-compute-radar": { "url": "https://aicomputeradar.dev/api/mcp" }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Tools
|
|
39
|
+
|
|
40
|
+
- **find_fit** — which tracked models run on a given GPU or Mac at a chosen context length. Weights are measured from the GGUF file listing, the context cache is computed from the model's config, the runtime allowance is a declared assumption; every fit says which part is which (`basis`). Verdicts EXCELLENT / GOOD / TIGHT / OFFLOAD_REQUIRED / UNKNOWN with the arithmetic behind each.
|
|
41
|
+
- **gpu_prices** — the price board: eight GPU classes (RTX 3090 to B200) against eight sources (Vast.ai and Clore.ai marketplace medians, RunPod's posted price, AWS, Azure, Oracle Cloud, Lambda and Nebius list prices), per GPU-hour, with offer counts and the Rent Index.
|
|
42
|
+
- **trending_models** — tracked open models ranked by Heat Score, a published composite of Hugging Face downloads, trending rank and likes, with the raw signals.
|
|
43
|
+
- **list_hardware** — the hardware profiles the fit engine knows, with usable memory after margins.
|
|
44
|
+
- **weekly_pick** — one model a week chosen by a fixed rule, with its readings frozen at selection time.
|
|
45
|
+
|
|
46
|
+
Every response carries `meta.source` (live or fallback) and `meta.collectedAt`. There are no benchmarks, no speed figures and no forecasts; the server says what was measured and nothing else.
|
|
47
|
+
|
|
48
|
+
## Try it
|
|
49
|
+
|
|
50
|
+
"Which open models run comfortably on an RTX 4090 at 16K context, and what is the largest one?"
|
|
51
|
+
"What does an H100 hour cost right now across marketplaces and clouds, and which source is cheapest?"
|
|
52
|
+
"Show me this week's pick and whether it fits a 12 GB card at 32K context."
|
|
53
|
+
|
|
54
|
+
## Options
|
|
55
|
+
|
|
56
|
+
- `--url <endpoint>` or `AI_COMPUTE_RADAR_URL` — another endpoint (a local build, for example).
|
|
57
|
+
- `--version`, `--help`.
|
|
58
|
+
|
|
59
|
+
## Licence
|
|
60
|
+
|
|
61
|
+
Code MIT. Data CC BY 4.0, attribution "AI Compute Radar (https://aicomputeradar.dev)". Method: https://aicomputeradar.dev/methodology · Docs and REST API: https://aicomputeradar.dev/developers
|
package/bin.mjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { DEFAULT_URL, VERSION, run } from "./bridge.mjs";
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
6
|
+
process.stderr.write(
|
|
7
|
+
`ai-compute-radar ${VERSION} — stdio bridge to the AI Compute Radar MCP server\n` +
|
|
8
|
+
`Usage: ai-compute-radar [--url <endpoint>]\n` +
|
|
9
|
+
`Default endpoint: ${DEFAULT_URL} (or AI_COMPUTE_RADAR_URL)\n` +
|
|
10
|
+
`Tools: trending_models, find_fit, gpu_prices, list_hardware, weekly_pick\n` +
|
|
11
|
+
`Docs: https://aicomputeradar.dev/developers\n`,
|
|
12
|
+
);
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
16
|
+
process.stdout.write(`${VERSION}\n`);
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
const flag = args.indexOf("--url");
|
|
20
|
+
const url = flag >= 0 && args[flag + 1] ? args[flag + 1] : process.env.AI_COMPUTE_RADAR_URL || DEFAULT_URL;
|
|
21
|
+
|
|
22
|
+
// stdin closing means the client is gone; leave with it instead of idling on an open handle.
|
|
23
|
+
run({ url }).then(
|
|
24
|
+
() => process.exit(0),
|
|
25
|
+
(error) => {
|
|
26
|
+
process.stderr.write(`ai-compute-radar: ${error?.message ?? error}\n`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
},
|
|
29
|
+
);
|
package/bridge.mjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ai-compute-radar — stdio bridge to the hosted MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Clients that only speak stdio start this file; every JSON-RPC message
|
|
5
|
+
* they write is POSTed unchanged to https://aicomputeradar.dev/api/mcp
|
|
6
|
+
* (Streamable HTTP, stateless, no auth) and every message the server
|
|
7
|
+
* answers is written back, one JSON object per line. No data lives here:
|
|
8
|
+
* no cache, no telemetry, no keys. Node 18 or newer, no dependencies.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_URL = "https://aicomputeradar.dev/api/mcp";
|
|
12
|
+
export const VERSION = "1.2.1";
|
|
13
|
+
|
|
14
|
+
/** Messages in a text/event-stream body, in order; non-JSON data lines are skipped. */
|
|
15
|
+
export function parseSseMessages(text) {
|
|
16
|
+
const messages = [];
|
|
17
|
+
for (const event of text.split(/\r?\n\r?\n/)) {
|
|
18
|
+
const data = event
|
|
19
|
+
.split(/\r?\n/)
|
|
20
|
+
.filter((line) => line.startsWith("data:"))
|
|
21
|
+
.map((line) => line.slice(5).trimStart())
|
|
22
|
+
.join("\n");
|
|
23
|
+
if (!data) continue;
|
|
24
|
+
try {
|
|
25
|
+
messages.push(JSON.parse(data));
|
|
26
|
+
} catch {
|
|
27
|
+
// A keep-alive or a partial line is not a message.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return messages;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Messages in a response body of either kind the server sends. */
|
|
34
|
+
export function parseBody(contentType, text) {
|
|
35
|
+
if (!text.trim()) return [];
|
|
36
|
+
if (/text\/event-stream/i.test(contentType ?? "")) return parseSseMessages(text);
|
|
37
|
+
const parsed = JSON.parse(text);
|
|
38
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function errorFor(message, text) {
|
|
42
|
+
const id = message && typeof message === "object" && "id" in message ? message.id : null;
|
|
43
|
+
return { jsonrpc: "2.0", id, error: { code: -32000, message: text } };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Forward one client message; resolves to the messages to write back.
|
|
48
|
+
* A notification (no id) expects nothing and gets nothing.
|
|
49
|
+
* @param {Record<string, unknown>} message one JSON-RPC message from the client
|
|
50
|
+
* @param {{ url?: string; fetchImpl?: typeof fetch; protocolVersion?: string }} [options]
|
|
51
|
+
* @returns {Promise<Array<Record<string, any>>>}
|
|
52
|
+
*/
|
|
53
|
+
export async function forward(message, { url = DEFAULT_URL, fetchImpl = fetch, protocolVersion } = {}) {
|
|
54
|
+
const headers = {
|
|
55
|
+
"content-type": "application/json",
|
|
56
|
+
accept: "application/json, text/event-stream",
|
|
57
|
+
"user-agent": `ai-compute-radar-bridge/${VERSION}`,
|
|
58
|
+
};
|
|
59
|
+
if (protocolVersion) headers["mcp-protocol-version"] = protocolVersion;
|
|
60
|
+
let response;
|
|
61
|
+
try {
|
|
62
|
+
response = await fetchImpl(url, { method: "POST", headers, body: JSON.stringify(message) });
|
|
63
|
+
} catch (cause) {
|
|
64
|
+
return "id" in message ? [errorFor(message, `Could not reach ${url}: ${cause?.message ?? cause}`)] : [];
|
|
65
|
+
}
|
|
66
|
+
const text = await response.text();
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
return "id" in message ? [errorFor(message, `${url} answered HTTP ${response.status}${text ? `: ${text.slice(0, 200)}` : ""}`)] : [];
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
return parseBody(response.headers.get("content-type"), text);
|
|
72
|
+
} catch {
|
|
73
|
+
return "id" in message ? [errorFor(message, `${url} answered something that is not JSON-RPC`)] : [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Reads stdin line by line, writes server messages to stdout.
|
|
79
|
+
* @param {{ url?: string; input?: NodeJS.ReadableStream; output?: NodeJS.WritableStream; fetchImpl?: typeof fetch }} [options]
|
|
80
|
+
*/
|
|
81
|
+
export async function run({ url = DEFAULT_URL, input = process.stdin, output = process.stdout, fetchImpl = fetch } = {}) {
|
|
82
|
+
const { createInterface } = await import("node:readline");
|
|
83
|
+
const lines = createInterface({ input, crlfDelay: Infinity });
|
|
84
|
+
let protocolVersion;
|
|
85
|
+
const pending = new Set();
|
|
86
|
+
const write = (msg) => output.write(`${JSON.stringify(msg)}\n`);
|
|
87
|
+
for await (const line of lines) {
|
|
88
|
+
if (!line.trim()) continue;
|
|
89
|
+
let message;
|
|
90
|
+
try {
|
|
91
|
+
message = JSON.parse(line);
|
|
92
|
+
} catch {
|
|
93
|
+
write({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (message?.method === "initialize" && typeof message?.params?.protocolVersion === "string") {
|
|
97
|
+
protocolVersion = message.params.protocolVersion;
|
|
98
|
+
}
|
|
99
|
+
const job = forward(message, { url, fetchImpl, protocolVersion }).then((replies) => {
|
|
100
|
+
for (const reply of replies) write(reply);
|
|
101
|
+
});
|
|
102
|
+
pending.add(job);
|
|
103
|
+
job.finally(() => pending.delete(job));
|
|
104
|
+
}
|
|
105
|
+
await Promise.all(pending);
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-compute-radar",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "MCP server bridge for AI Compute Radar: which open models fit your GPU or Mac (measured GGUF sizes + computed context cache), GPU rental prices from eight sources, model momentum, weekly pick. Read-only, no auth, no telemetry.",
|
|
5
|
+
"keywords": ["mcp", "model-context-protocol", "gpu", "vram", "llm", "local-ai", "gpu-rental", "cloud-pricing", "hugging-face", "open-models"],
|
|
6
|
+
"homepage": "https://aicomputeradar.dev/developers",
|
|
7
|
+
"bugs": { "email": "aicomputeradar@gmail.com" },
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "AI Compute Radar (https://aicomputeradar.dev)",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"bin": { "ai-compute-radar": "bin.mjs" },
|
|
12
|
+
"exports": "./bridge.mjs",
|
|
13
|
+
"files": ["bin.mjs", "bridge.mjs", "README.md", "LICENSE"],
|
|
14
|
+
"engines": { "node": ">=18" },
|
|
15
|
+
"mcpName": "dev.aicomputeradar/ai-compute-radar"
|
|
16
|
+
}
|