Skip to content

Commit 25fc698

Browse files
committed
feat(mcp): local stdio MCP server — scan_repo tool (thin proof)
Reuses the extension's pipeline (fetcher/prompt/parser) verbatim; only the env-key Anthropic call is MCP-specific. Local stdio, BYO-key, no backend.
1 parent e58f031 commit 25fc698

7 files changed

Lines changed: 1463 additions & 0 deletions

File tree

mcp/README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# RepoLens MCP server
2+
3+
A local [MCP](https://modelcontextprotocol.io) server that exposes RepoLens's repo
4+
analysis as a tool. An LLM client (Claude Desktop, Cursor, etc.) calls `scan_repo`
5+
and gets RepoLens's verdict-first JSON back — ready to render as components.
6+
7+
This is a **thin proof**: one tool, GitHub-only, Anthropic-only. It reuses the
8+
extension's own pipeline (`fetcher.js``prompt.js``parser.js`); only the
9+
provider call is MCP-specific.
10+
11+
## What stays true
12+
13+
- **Local only.** Runs over stdio, spawned by your client. No hosted backend.
14+
- **Bring your own key.** The Anthropic key comes from the environment, never from
15+
a server or the extension's storage. Nothing phones home.
16+
17+
## Tool
18+
19+
### `scan_repo({ repo })`
20+
21+
`repo` is `owner/name` or a GitHub URL. Returns the analysis JSON:
22+
23+
```json
24+
{
25+
"repoId": "honojs/hono",
26+
"platform": "github",
27+
"language": "TypeScript",
28+
"license": "MIT",
29+
"stars": 21000,
30+
"description": "Small, fast web framework for the edges.",
31+
"fit": "strong",
32+
"health": { "score": 92 },
33+
"pros": ["..."],
34+
"cons": ["..."],
35+
"red_flags": [],
36+
"capabilities": ["routing", "middleware", "edge"]
37+
}
38+
```
39+
40+
## Setup
41+
42+
```bash
43+
cd mcp
44+
npm install
45+
export ANTHROPIC_API_KEY=sk-ant-... # required
46+
export ANTHROPIC_MODEL=claude-sonnet-4-6 # optional override
47+
node server.js # speaks MCP over stdio
48+
```
49+
50+
### Add to Claude Desktop
51+
52+
In `claude_desktop_config.json`:
53+
54+
```json
55+
{
56+
"mcpServers": {
57+
"repolens": {
58+
"command": "node",
59+
"args": ["/absolute/path/to/repolens/mcp/server.js"],
60+
"env": { "ANTHROPIC_API_KEY": "sk-ant-..." }
61+
}
62+
}
63+
}
64+
```
65+
66+
## Notes
67+
68+
- Unauthenticated GitHub requests are rate-limited. For heavier use, a `GITHUB_TOKEN`
69+
pass-through is a follow-up.
70+
- Next tools (follow-ups): `blueprint_scene` (the nodes/edges graph), `deep_dive`,
71+
multi-provider, and npm / PyPI / GitLab.

mcp/anthropic.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Minimal Anthropic Messages API call for the MCP server. The key comes from the
2+
// environment (ANTHROPIC_API_KEY) — never chrome.storage, never a hosted backend —
3+
// so the server stays local and bring-your-own-key. Mirrors the extension's
4+
// callAnthropic shape (background.js): same endpoint, version header, and default model.
5+
6+
const ENDPOINT = 'https://api.anthropic.com/v1/messages';
7+
const DEFAULT_MODEL = 'claude-sonnet-4-6';
8+
9+
/**
10+
* @param {string} prompt - the fully-assembled analysis prompt.
11+
* @returns {Promise<string>} the model's text response.
12+
*/
13+
export async function callAnthropic(prompt) {
14+
const key = process.env.ANTHROPIC_API_KEY;
15+
if (!key) throw new Error('ANTHROPIC_API_KEY is not set in the environment');
16+
const model = process.env.ANTHROPIC_MODEL || DEFAULT_MODEL;
17+
18+
const res = await fetch(ENDPOINT, {
19+
method: 'POST',
20+
headers: {
21+
'content-type': 'application/json',
22+
'anthropic-version': '2023-06-01',
23+
'x-api-key': key,
24+
},
25+
body: JSON.stringify({
26+
model,
27+
max_tokens: 4096,
28+
messages: [{ role: 'user', content: prompt }],
29+
}),
30+
});
31+
32+
if (!res.ok) {
33+
const detail = await res.text().catch(() => '');
34+
throw new Error(`Anthropic API ${res.status}: ${detail.slice(0, 300)}`);
35+
}
36+
37+
const data = await res.json();
38+
const text = (data.content || []).map((b) => b.text || '').join('').trim();
39+
if (!text) throw new Error('Anthropic returned an empty response');
40+
return text;
41+
}

0 commit comments

Comments
 (0)