hvtracker-mcp 0.1.2 → 0.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/README.md +2 -1
- package/bin/hvtracker-mcp.js +99 -3
- package/package.json +1 -1
- package/server.json +11 -5
package/README.md
CHANGED
|
@@ -23,7 +23,8 @@ package-based installation.
|
|
|
23
23
|
## Tools
|
|
24
24
|
|
|
25
25
|
- `verify_mcp_server`: pre-connect trust verdict for an MCP server, package, GitHub repo, or agent name.
|
|
26
|
-
- `check_agent_trust`:
|
|
26
|
+
- `check_agent_trust`: trust profile for a tracked AI agent or framework — incl. runtime capabilities (MCP status, providers, plugin surface, provenance drift) and the URL of its Ed25519-signed trust credential.
|
|
27
|
+
- `compare_agents`: two agents side by side with an evidence-based verdict and the published compare-page link.
|
|
27
28
|
- `search_agents`: search the HVTracker registry by name, repo, description, or category.
|
|
28
29
|
|
|
29
30
|
## Local Install
|
package/bin/hvtracker-mcp.js
CHANGED
|
@@ -7,10 +7,13 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
-
const VERSION = "0.1
|
|
10
|
+
const VERSION = "0.2.1";
|
|
11
11
|
const DEFAULT_BASE_URL = "https://hvtracker.net";
|
|
12
12
|
const BASE_URL = (process.env.HVTRACKER_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
13
13
|
const TIMEOUT_MS = Number(process.env.HVTRACKER_TIMEOUT_SECONDS || 20) * 1000;
|
|
14
|
+
// The full leaderboard is ~1MB and the API serves it with Cache-Control
|
|
15
|
+
// max-age=900; re-pulling it per search_agents call is wasted origin load.
|
|
16
|
+
const BOARD_TTL_MS = Number(process.env.HVTRACKER_BOARD_TTL_SECONDS || 900) * 1000;
|
|
14
17
|
const READ_ONLY = {
|
|
15
18
|
readOnlyHint: true,
|
|
16
19
|
destructiveHint: false,
|
|
@@ -40,6 +43,25 @@ export async function apiGet(path, params = {}) {
|
|
|
40
43
|
return payload;
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
const boardCache = { at: 0, data: null };
|
|
47
|
+
|
|
48
|
+
export function resetBoardCache() {
|
|
49
|
+
boardCache.at = 0;
|
|
50
|
+
boardCache.data = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Returns /api/v1/agents, cached for BOARD_TTL_MS. Failures are not cached —
|
|
54
|
+
// the next call retries.
|
|
55
|
+
async function getBoard() {
|
|
56
|
+
if (boardCache.data !== null && Date.now() - boardCache.at < BOARD_TTL_MS) {
|
|
57
|
+
return boardCache.data;
|
|
58
|
+
}
|
|
59
|
+
const data = await apiGet("/api/v1/agents");
|
|
60
|
+
boardCache.data = data;
|
|
61
|
+
boardCache.at = Date.now();
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
|
|
43
65
|
function asToolResult(value) {
|
|
44
66
|
return {
|
|
45
67
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
@@ -92,7 +114,7 @@ export async function checkAgentTrust(nameOrRepo) {
|
|
|
92
114
|
}
|
|
93
115
|
|
|
94
116
|
const slug = verdict.slug;
|
|
95
|
-
|
|
117
|
+
const result = {
|
|
96
118
|
query: nameOrRepo,
|
|
97
119
|
tracked: true,
|
|
98
120
|
trusted: verdict.trusted,
|
|
@@ -104,13 +126,73 @@ export async function checkAgentTrust(nameOrRepo) {
|
|
|
104
126
|
mcp_server_support: verdict.mcp_server_support,
|
|
105
127
|
tool_permissions: verdict.tool_permissions || [],
|
|
106
128
|
profile_url: slug ? `${BASE_URL}/agents/${slug}/` : null,
|
|
129
|
+
credential_url: slug ? `${BASE_URL}/data/agents/${slug}.json` : null,
|
|
107
130
|
reasons: verdict.reasons || []
|
|
108
131
|
};
|
|
132
|
+
if (slug) {
|
|
133
|
+
try {
|
|
134
|
+
const agent = await apiGet(`/data/agents/${slug}.json`);
|
|
135
|
+
const ext = agent.external_service_dependencies || {};
|
|
136
|
+
const tooling = agent.tool_plugin_surface || {};
|
|
137
|
+
const drift = agent.package_provenance_drift || {};
|
|
138
|
+
result.coverage_grade = agent.coverage_grade ?? null;
|
|
139
|
+
result.capabilities = {
|
|
140
|
+
mcp_status: (agent.mcp_server_support || {}).status || "none",
|
|
141
|
+
provider_count: (ext.providers || []).length,
|
|
142
|
+
requires_api_keys: Boolean(ext.requires_api_keys),
|
|
143
|
+
plugin_system: tooling.plugin_system || "none",
|
|
144
|
+
drift_status: drift.status || "not_applicable"
|
|
145
|
+
};
|
|
146
|
+
} catch {
|
|
147
|
+
// enrichment is best-effort; the verdict stands alone
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function compareAgents(a, b) {
|
|
154
|
+
const [ra, rb] = [await checkAgentTrust(a), await checkAgentTrust(b)];
|
|
155
|
+
if (!ra.tracked || !rb.tracked) {
|
|
156
|
+
const missing = [[a, ra], [b, rb]].filter(([, r]) => !r.tracked).map(([q]) => q);
|
|
157
|
+
return {
|
|
158
|
+
a: ra,
|
|
159
|
+
b: rb,
|
|
160
|
+
verdict: `No verdict: ${missing.join(", ")} not in the registry — no independent trust evidence to compare.`,
|
|
161
|
+
compare_url: null
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const sa = ra.trust_score || 0;
|
|
165
|
+
const sb = rb.trust_score || 0;
|
|
166
|
+
let verdict;
|
|
167
|
+
if (sa === sb) {
|
|
168
|
+
verdict = `${ra.repo} and ${rb.repo} tie at HVTrust ${sa} (grades ${ra.evidence_grade}/${rb.evidence_grade}).`;
|
|
169
|
+
} else {
|
|
170
|
+
const [hi, lo] = sa > sb ? [ra, rb] : [rb, ra];
|
|
171
|
+
verdict = `${hi.repo} scores higher on verifiable trust: HVTrust ${hi.trust_score} (grade ${hi.evidence_grade}) vs ${lo.repo} at ${lo.trust_score} (grade ${lo.evidence_grade}).`;
|
|
172
|
+
}
|
|
173
|
+
let compareUrl = null;
|
|
174
|
+
if (ra.slug && rb.slug) {
|
|
175
|
+
const [first, second] = [ra.slug, rb.slug].sort();
|
|
176
|
+
const candidate = `${BASE_URL}/compare/${first}-vs-${second}/`;
|
|
177
|
+
try {
|
|
178
|
+
const probe = await fetch(candidate, {
|
|
179
|
+
method: "HEAD",
|
|
180
|
+
headers: { "user-agent": `hvtracker-mcp/${VERSION}` },
|
|
181
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
182
|
+
});
|
|
183
|
+
if (probe.ok) {
|
|
184
|
+
compareUrl = candidate;
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
// no published compare page (or unreachable) — omit the link
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return { a: ra, b: rb, verdict, compare_url: compareUrl };
|
|
109
191
|
}
|
|
110
192
|
|
|
111
193
|
export async function searchAgents(query = "", category = "", limit = 10) {
|
|
112
194
|
try {
|
|
113
|
-
const data = await
|
|
195
|
+
const data = await getBoard();
|
|
114
196
|
const ql = String(query || "").trim().toLowerCase();
|
|
115
197
|
const cl = String(category || "").trim().toLowerCase();
|
|
116
198
|
const matches = [];
|
|
@@ -179,6 +261,20 @@ export function createServer() {
|
|
|
179
261
|
async ({ name_or_repo }) => asToolResult(await checkAgentTrust(name_or_repo))
|
|
180
262
|
);
|
|
181
263
|
|
|
264
|
+
server.registerTool(
|
|
265
|
+
"compare_agents",
|
|
266
|
+
{
|
|
267
|
+
title: "Compare Agents",
|
|
268
|
+
description: "Compare two tracked AI agents side by side: trust scores, grades, runtime capabilities, and an evidence-based verdict.",
|
|
269
|
+
inputSchema: {
|
|
270
|
+
a: z.string().describe("First agent — name, slug, GitHub repo/URL, or package."),
|
|
271
|
+
b: z.string().describe("Second agent — name, slug, GitHub repo/URL, or package.")
|
|
272
|
+
},
|
|
273
|
+
annotations: READ_ONLY
|
|
274
|
+
},
|
|
275
|
+
async ({ a, b }) => asToolResult(await compareAgents(a, b))
|
|
276
|
+
);
|
|
277
|
+
|
|
182
278
|
server.registerTool(
|
|
183
279
|
"search_agents",
|
|
184
280
|
{
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.YugantM/hvtracker-mcp",
|
|
4
4
|
"title": "HVTracker MCP",
|
|
5
5
|
"description": "Pre-connect trust checks for AI agents and MCP servers using HVTracker's public trust registry.",
|
|
6
|
-
"version": "0.1
|
|
6
|
+
"version": "0.2.1",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/YugantM/hvtracker-mcp",
|
|
9
9
|
"source": "github"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
{
|
|
19
19
|
"registryType": "npm",
|
|
20
20
|
"identifier": "hvtracker-mcp",
|
|
21
|
-
"version": "0.1
|
|
21
|
+
"version": "0.2.1",
|
|
22
22
|
"transport": {
|
|
23
23
|
"type": "stdio"
|
|
24
24
|
}
|
|
@@ -26,18 +26,24 @@
|
|
|
26
26
|
{
|
|
27
27
|
"registryType": "pypi",
|
|
28
28
|
"identifier": "hvtracker-mcp",
|
|
29
|
-
"version": "0.1
|
|
29
|
+
"version": "0.2.1",
|
|
30
30
|
"transport": {
|
|
31
31
|
"type": "stdio"
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"registryType": "oci",
|
|
36
|
-
"identifier": "ghcr.io/yugantm/hvtracker-mcp:v0.1
|
|
36
|
+
"identifier": "ghcr.io/yugantm/hvtracker-mcp:v0.2.1",
|
|
37
37
|
"transport": {
|
|
38
38
|
"type": "stdio"
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
],
|
|
42
|
-
"tags": [
|
|
42
|
+
"tags": [
|
|
43
|
+
"security",
|
|
44
|
+
"trust",
|
|
45
|
+
"ai-agents",
|
|
46
|
+
"mcp",
|
|
47
|
+
"supply-chain"
|
|
48
|
+
]
|
|
43
49
|
}
|