lowdown-proxy 0.1.14 → 0.1.15

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
@@ -39,6 +39,27 @@ Wrap any MCP server in your client config:
39
39
  }
40
40
  ```
41
41
 
42
+ ## Lookup tools for agents (MCP server)
43
+
44
+ The same package ships an MCP server (stdio) that lets an agent query Lowdown directly:
45
+
46
+ ```jsonc
47
+ {
48
+ "mcpServers": {
49
+ "lowdown": { "command": "npx", "args": ["-y", "-p", "lowdown-proxy", "lowdown-mcp"] }
50
+ }
51
+ }
52
+ ```
53
+
54
+ | Tool | What it returns |
55
+ |---|---|
56
+ | `get_lowdown` | Interactions reported for a target and, for MCP Registry servers, the Scanner observations (see [Scanner observations](#scanner-observations)) |
57
+ | `compare_tools` | 2 to 5 targets side by side, ordered by reported success rate; Scanner data is not used for the ordering |
58
+ | `record_interaction` | Records the outcome of using a tool or service |
59
+ | `get_node_stats` | Contribution stats of your node |
60
+
61
+ The same data is available over HTTP without installing anything: see [Public API](#public-api), the [OpenAPI document](https://lowdown-proxy.vercel.app/openapi.json), and [`llms.txt`](https://lowdown-proxy.vercel.app/llms.txt). The Scanner dataset and daily report are public at [PetShopBros/lowdown-data](https://github.com/PetShopBros/lowdown-data) (CC BY 4.0).
62
+
42
63
  ## Options
43
64
 
44
65
  | Flag | Description | Default |
@@ -103,6 +124,32 @@ Contributor response (with node):
103
124
 
104
125
  Fuzzy matching supported — short names like `brave-search`, `fetch`, `github` resolve automatically.
105
126
 
127
+ ### Scanner observations
128
+
129
+ For MCP servers listed in the official MCP Registry, the response also carries a separate `scanner` block: what Lowdown's Scanner observed when it connected to the server (`initialize` and `tools/list` only — it never calls a tool). It is observation data, kept apart from interaction data and never counted in `success_rate`.
130
+
131
+ ```bash
132
+ curl https://lowdown-proxy.vercel.app/api/reputation/mcp:io.github.user/server
133
+ ```
134
+
135
+ ```json
136
+ {
137
+ "target": "mcp:io.github.user/server",
138
+ "interactions": 0,
139
+ "success_rate": null,
140
+ "scanner": {
141
+ "target": "mcp:io.github.user/server",
142
+ "last_observed_at": "2026-10-07T19:17:00+00:00",
143
+ "observations_30d": 28,
144
+ "latest": { "reached_level": 2, "http_status": 200, "latency_ms": 312, "tool_count": 7 },
145
+ "schema_versions_30d": 1,
146
+ "status_distribution_30d": { "200": 28 }
147
+ }
148
+ }
149
+ ```
150
+
151
+ HTTP statuses are reported as observed — a `401` is a `401`, not "dead".
152
+
106
153
  ## Why Lowdown
107
154
 
108
155
  Agents often start with no shared history of a tool's past behavior. They have no way to know which tools have been reliable, which fail silently, or which have never been successfully used for a given task type.
@@ -175,6 +222,10 @@ MCP 서버를 감싸는 stdio 프록시가 첫 번째 진입점입니다.
175
222
 
176
223
  프록시를 실행하는 것 자체가 노드 참여입니다. 기여할수록 더 상세한 데이터를 조회할 수 있습니다.
177
224
 
225
+ ### Scanner 관찰 데이터
226
+
227
+ 공식 MCP Registry에 등록된 원격 MCP 서버는 응답에 `scanner` 블록이 함께 옵니다. Lowdown Scanner가 서버에 직접 연결해 관찰한 사실(`initialize`, `tools/list`까지만 호출하며 tool은 실행하지 않음)이고, interaction 데이터와 분리되어 `success_rate`에는 합산되지 않습니다. HTTP 상태는 해석 없이 관찰된 값 그대로 보여줍니다.
228
+
178
229
  ### 설계 원칙 (요약)
179
230
 
180
231
  - **설정 없음**: 프록시를 실행하면 모든 것이 자동으로 처리됩니다.
package/dist/cli.js CHANGED
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { runProxy } from "./proxy.js";
3
- // 사용법: npx lowdown-proxy [--actor <id>] [--target <label>] [--source organic|seeded|synthetic] -- <command> [args...]
3
+ // 사용법:
4
+ // npx lowdown-proxy → 인자 없음: Lowdown 조회용 MCP 서버(stdio) 시작 (Registry 설치 경로)
5
+ // npx lowdown-proxy [--actor <id>] [--target <label>] [--source organic|seeded|synthetic] -- <command> [args...]
6
+ // → MCP 서버를 감싸는 기록 프록시
4
7
  function parseArgs(argv) {
5
8
  const dashIndex = argv.indexOf("--");
6
9
  if (dashIndex === -1) {
7
10
  console.error("사용법: lowdown-proxy [--actor <id>] [--target <label>] [--source organic|seeded|synthetic] -- <command> [args...]\n" +
8
- "예시: lowdown-proxy -- npx some-mcp-server");
11
+ "예시: lowdown-proxy -- npx some-mcp-server\n" +
12
+ "인자 없이 실행하면 Lowdown 조회용 MCP 서버가 시작됩니다.");
9
13
  process.exit(1);
10
14
  }
11
15
  const flags = argv.slice(0, dashIndex);
@@ -26,5 +30,11 @@ function parseArgs(argv) {
26
30
  args: commandParts.slice(1),
27
31
  };
28
32
  }
29
- const opts = parseArgs(process.argv.slice(2));
30
- runProxy(opts);
33
+ const argv = process.argv.slice(2);
34
+ if (argv.length === 0) {
35
+ // MCP Registry 는 이 패키지를 인자 없이 실행한다 → 조회 도구(get_lowdown 등)를 제공하는 MCP 서버로 동작
36
+ await import("./mcp-server.js");
37
+ }
38
+ else {
39
+ runProxy(parseArgs(argv));
40
+ }
@@ -1,17 +1,31 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createRequire } from "node:module";
3
4
  import { z } from "zod";
4
5
  import { getNodeId } from "./node-identity.js";
5
6
  const BASE_URL = process.env.LOWDOWN_API_URL ?? "https://lowdown-proxy.vercel.app";
6
7
  const NODE_ID = getNodeId();
8
+ // dist/mcp-server.js 기준 ../package.json (npm 패키지에 항상 포함됨)
9
+ const PKG_VERSION = (() => {
10
+ try {
11
+ return createRequire(import.meta.url)("../package.json").version;
12
+ }
13
+ catch {
14
+ return "0.0.0";
15
+ }
16
+ })();
7
17
  const server = new McpServer({
8
18
  name: "lowdown",
9
- version: "0.1.0",
19
+ version: PKG_VERSION,
10
20
  });
11
21
  // ── 1. get_lowdown ──────────────────────────────────────────────
12
- server.tool("get_lowdown", "Get the interaction history and reputation of an agent, tool, or service. " +
13
- "Call this before choosing between providers to make an informed decision.", {
14
- target: z.string().describe("Identifier of the agent/tool/service. e.g. 'mcp:vendor/tool-name' or 'agent:some-agent-id'"),
22
+ server.tool("get_lowdown", "Look up what Lowdown has recorded about an agent, tool, or service. " +
23
+ "Returns interactions reported by agents (count, outcomes, success rate) and, for MCP Registry servers, " +
24
+ "a separate `scanner` block: daily Lowdown Scanner observations of initialize and tools/list reachability, " +
25
+ "HTTP status, tool count, and tool-schema changes over the last 30 days. " +
26
+ "Scanner data is observation, not a score. A target outside the observed sample returns `observed: false`, " +
27
+ "which says nothing about its status.", {
28
+ target: z.string().describe("MCP Registry name with the mcp: prefix (e.g. 'mcp:io.github.user/server'), a short name, or an id such as 'agent:some-agent-id'"),
15
29
  }, async ({ target }) => {
16
30
  const res = await fetch(`${BASE_URL}/api/reputation/${encodeURIComponent(target)}`, { headers: { "x-lowdown-source": "organic", "x-lowdown-node-id": NODE_ID } });
17
31
  const data = await res.json();
@@ -27,13 +41,16 @@ server.tool("record_interaction", "Record an interaction with an agent, tool, or
27
41
  target_type: z.enum(["agent", "tool", "service", "human"]),
28
42
  task_type: z.string().describe("What kind of task was performed. e.g. 'web_search', 'code_gen'"),
29
43
  outcome: z.enum(["success", "partial", "failure"]),
44
+ failure_type: z.enum(["invalid_arguments", "timeout", "server_error", "tool_error", "unknown"])
45
+ .optional()
46
+ .describe("Required when outcome is failure. Distinguish tool failure vs argument error."),
30
47
  latency_ms: z.number().optional().describe("How long it took in milliseconds"),
31
48
  source: z.enum(["organic", "seeded", "synthetic"]).optional(),
32
- }, async ({ actor, target, target_type, task_type, outcome, latency_ms, source }) => {
49
+ }, async ({ actor, target, target_type, task_type, outcome, failure_type, latency_ms, source }) => {
33
50
  const res = await fetch(`${BASE_URL}/api/interactions`, {
34
51
  method: "POST",
35
52
  headers: { "Content-Type": "application/json" },
36
- body: JSON.stringify({ actor, target, target_type, task_type, outcome, latency_ms, source }),
53
+ body: JSON.stringify({ actor, target, target_type, task_type, outcome, failure_type, latency_ms, source }),
37
54
  });
38
55
  const data = await res.json();
39
56
  return {
@@ -41,8 +58,10 @@ server.tool("record_interaction", "Record an interaction with an agent, tool, or
41
58
  };
42
59
  });
43
60
  // ── 3. compare_tools ────────────────────────────────────────────
44
- server.tool("compare_tools", "Compare multiple tools or agents by their recorded interaction data. " +
45
- "Returns a ranked list with success rates and confidence levels.", {
61
+ server.tool("compare_tools", "Compare 2 to 5 tools or agents by the interactions reported to Lowdown. " +
62
+ "Candidates are ordered by reported success rate (candidates without data last), with interaction count and confidence. " +
63
+ "Scanner observations are not used for the ordering. " +
64
+ "A suggestion is included only when the first candidate has at least 5 reported interactions.", {
46
65
  candidates: z.array(z.string()).min(2).max(5).describe("List of tool/agent identifiers to compare"),
47
66
  task_type: z.string().optional().describe("Filter by task type if you want task-specific comparison"),
48
67
  }, async ({ candidates, task_type }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lowdown-proxy",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "mcpName": "io.github.PetShopBros/lowdown-proxy",
5
5
  "description": "Record and query AI agent/tool/service interactions. Shared interaction history for the multi-agent ecosystem.",
6
6
  "type": "module",
@@ -14,7 +14,10 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "build": "tsc",
17
- "dev": "tsx src/cli.ts"
17
+ "dev": "tsx src/cli.ts",
18
+ "scanner:typecheck": "tsc -p scanner",
19
+ "scanner:test": "node --test scanner/lib.test.ts scanner/reputation.test.ts scanner/report.test.ts",
20
+ "scanner:dry": "node scanner/scan.ts --dry-run --limit=30"
18
21
  },
19
22
  "keywords": [
20
23
  "mcp",