hvtracker-mcp 0.1.1 → 0.2.0

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 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`: compact trust profile for a tracked AI agent or framework.
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
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { pathToFileURL } from "node:url";
3
+ import { realpathSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
4
5
 
5
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
8
  import { z } from "zod";
8
9
 
9
- const VERSION = "0.1.1";
10
+ const VERSION = "0.2.0";
10
11
  const DEFAULT_BASE_URL = "https://hvtracker.net";
11
12
  const BASE_URL = (process.env.HVTRACKER_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
12
13
  const TIMEOUT_MS = Number(process.env.HVTRACKER_TIMEOUT_SECONDS || 20) * 1000;
@@ -91,7 +92,7 @@ export async function checkAgentTrust(nameOrRepo) {
91
92
  }
92
93
 
93
94
  const slug = verdict.slug;
94
- return {
95
+ const result = {
95
96
  query: nameOrRepo,
96
97
  tracked: true,
97
98
  trusted: verdict.trusted,
@@ -103,8 +104,68 @@ export async function checkAgentTrust(nameOrRepo) {
103
104
  mcp_server_support: verdict.mcp_server_support,
104
105
  tool_permissions: verdict.tool_permissions || [],
105
106
  profile_url: slug ? `${BASE_URL}/agents/${slug}/` : null,
107
+ credential_url: slug ? `${BASE_URL}/data/agents/${slug}.json` : null,
106
108
  reasons: verdict.reasons || []
107
109
  };
110
+ if (slug) {
111
+ try {
112
+ const agent = await apiGet(`/data/agents/${slug}.json`);
113
+ const ext = agent.external_service_dependencies || {};
114
+ const tooling = agent.tool_plugin_surface || {};
115
+ const drift = agent.package_provenance_drift || {};
116
+ result.coverage_grade = agent.coverage_grade ?? null;
117
+ result.capabilities = {
118
+ mcp_status: (agent.mcp_server_support || {}).status || "none",
119
+ provider_count: (ext.providers || []).length,
120
+ requires_api_keys: Boolean(ext.requires_api_keys),
121
+ plugin_system: tooling.plugin_system || "none",
122
+ drift_status: drift.status || "not_applicable"
123
+ };
124
+ } catch {
125
+ // enrichment is best-effort; the verdict stands alone
126
+ }
127
+ }
128
+ return result;
129
+ }
130
+
131
+ export async function compareAgents(a, b) {
132
+ const [ra, rb] = [await checkAgentTrust(a), await checkAgentTrust(b)];
133
+ if (!ra.tracked || !rb.tracked) {
134
+ const missing = [[a, ra], [b, rb]].filter(([, r]) => !r.tracked).map(([q]) => q);
135
+ return {
136
+ a: ra,
137
+ b: rb,
138
+ verdict: `No verdict: ${missing.join(", ")} not in the registry — no independent trust evidence to compare.`,
139
+ compare_url: null
140
+ };
141
+ }
142
+ const sa = ra.trust_score || 0;
143
+ const sb = rb.trust_score || 0;
144
+ let verdict;
145
+ if (sa === sb) {
146
+ verdict = `${ra.repo} and ${rb.repo} tie at HVTrust ${sa} (grades ${ra.evidence_grade}/${rb.evidence_grade}).`;
147
+ } else {
148
+ const [hi, lo] = sa > sb ? [ra, rb] : [rb, ra];
149
+ 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}).`;
150
+ }
151
+ let compareUrl = null;
152
+ if (ra.slug && rb.slug) {
153
+ const [first, second] = [ra.slug, rb.slug].sort();
154
+ const candidate = `${BASE_URL}/compare/${first}-vs-${second}/`;
155
+ try {
156
+ const probe = await fetch(candidate, {
157
+ method: "HEAD",
158
+ headers: { "user-agent": `hvtracker-mcp/${VERSION}` },
159
+ signal: AbortSignal.timeout(TIMEOUT_MS)
160
+ });
161
+ if (probe.ok) {
162
+ compareUrl = candidate;
163
+ }
164
+ } catch {
165
+ // no published compare page (or unreachable) — omit the link
166
+ }
167
+ }
168
+ return { a: ra, b: rb, verdict, compare_url: compareUrl };
108
169
  }
109
170
 
110
171
  export async function searchAgents(query = "", category = "", limit = 10) {
@@ -178,6 +239,20 @@ export function createServer() {
178
239
  async ({ name_or_repo }) => asToolResult(await checkAgentTrust(name_or_repo))
179
240
  );
180
241
 
242
+ server.registerTool(
243
+ "compare_agents",
244
+ {
245
+ title: "Compare Agents",
246
+ description: "Compare two tracked AI agents side by side: trust scores, grades, runtime capabilities, and an evidence-based verdict.",
247
+ inputSchema: {
248
+ a: z.string().describe("First agent — name, slug, GitHub repo/URL, or package."),
249
+ b: z.string().describe("Second agent — name, slug, GitHub repo/URL, or package.")
250
+ },
251
+ annotations: READ_ONLY
252
+ },
253
+ async ({ a, b }) => asToolResult(await compareAgents(a, b))
254
+ );
255
+
181
256
  server.registerTool(
182
257
  "search_agents",
183
258
  {
@@ -202,7 +277,18 @@ export async function runStdio() {
202
277
  await server.connect(new StdioServerTransport());
203
278
  }
204
279
 
205
- if (import.meta.url === pathToFileURL(process.argv[1]).href) {
280
+ function isEntrypoint() {
281
+ if (!process.argv[1]) {
282
+ return false;
283
+ }
284
+ try {
285
+ return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
286
+ } catch {
287
+ return process.argv[1] === fileURLToPath(import.meta.url);
288
+ }
289
+ }
290
+
291
+ if (isEntrypoint()) {
206
292
  runStdio().catch((error) => {
207
293
  console.error(error);
208
294
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hvtracker-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for HVTracker trust checks.",
5
5
  "license": "MIT",
6
6
  "type": "module",
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.1",
6
+ "version": "0.2.0",
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.1",
21
+ "version": "0.2.0",
22
22
  "transport": {
23
23
  "type": "stdio"
24
24
  }
@@ -26,18 +26,25 @@
26
26
  {
27
27
  "registryType": "pypi",
28
28
  "identifier": "hvtracker-mcp",
29
- "version": "0.1.1",
29
+ "version": "0.2.0",
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.1",
36
+ "identifier": "ghcr.io/yugantm/hvtracker-mcp:v0.2.0",
37
37
  "transport": {
38
38
  "type": "stdio"
39
- }
39
+ },
40
+ "version": "0.2.0"
40
41
  }
41
42
  ],
42
- "tags": ["security", "trust", "ai-agents", "mcp", "supply-chain"]
43
+ "tags": [
44
+ "security",
45
+ "trust",
46
+ "ai-agents",
47
+ "mcp",
48
+ "supply-chain"
49
+ ]
43
50
  }