|
1 | 1 | #!/usr/bin/env node |
2 | | -// RepoLens MCP server (thin proof). Exposes one tool, `scan_repo`, that reads a |
3 | | -// GitHub repo's real source and returns RepoLens's verdict-first analysis as |
4 | | -// structured JSON. Local stdio transport, bring-your-own Anthropic key. |
| 2 | +// RepoLens MCP server. Exposes RepoLens's repo analysis as MCP tools over local |
| 3 | +// stdio, bring-your-own Anthropic key. Each tool reuses the extension's own |
| 4 | +// pipeline modules; the only MCP-specific piece is the env-key Anthropic call. |
5 | 5 | // |
6 | | -// The analysis pipeline is the extension's own modules, imported verbatim: |
7 | | -// fetchRepoData -> buildPrompt -> (Anthropic) -> parseClaudeResponse |
8 | | -// The only MCP-specific piece is the env-key Anthropic call (see ./anthropic.js). |
| 6 | +// Tools: |
| 7 | +// scan_repo — verdict-first analysis (fit/health/pros/cons/flags) |
| 8 | +// blueprint_scene — laid-out nodes/edges graph of how the repo is built |
9 | 9 |
|
10 | 10 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; |
11 | 11 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; |
12 | 12 | import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; |
13 | 13 |
|
14 | | -import { fetchRepoData } from '../fetcher.js'; |
15 | | -import { buildPrompt } from '../prompt.js'; |
16 | | -import { parseClaudeResponse } from '../parser.js'; |
17 | | -import { parseRepoInput } from './repo-input.js'; |
18 | | -import { callAnthropic } from './anthropic.js'; |
| 14 | +import { SCAN_TOOL, runScanRepo } from './scan-repo.js'; |
| 15 | +import { BLUEPRINT_TOOL, runBlueprintScene } from './blueprint-scene.js'; |
19 | 16 |
|
20 | | -const SCAN_TOOL = { |
21 | | - name: 'scan_repo', |
22 | | - description: |
23 | | - "Read a GitHub repo's real source and return RepoLens's verdict-first analysis: " + |
24 | | - 'overall fit, a health score, pros, cons, red flags, and capabilities. Use this when ' + |
25 | | - 'the user wants a structured read on whether to use a repository, not its README pitch.', |
26 | | - inputSchema: { |
27 | | - type: 'object', |
28 | | - properties: { |
29 | | - repo: { type: 'string', description: 'A repo as owner/name or a GitHub URL' }, |
30 | | - }, |
31 | | - required: ['repo'], |
32 | | - additionalProperties: false, |
33 | | - }, |
34 | | - outputSchema: { |
35 | | - type: 'object', |
36 | | - properties: { |
37 | | - repoId: { type: 'string' }, |
38 | | - platform: { type: 'string' }, |
39 | | - language: { type: 'string' }, |
40 | | - license: { type: 'string' }, |
41 | | - stars: { type: 'number' }, |
42 | | - description: { type: 'string' }, |
43 | | - fit: { type: 'string', description: 'overall fit verdict' }, |
44 | | - health: { type: 'object', description: 'health score + signals' }, |
45 | | - pros: { type: 'array', items: { type: 'string' } }, |
46 | | - cons: { type: 'array', items: { type: 'string' } }, |
47 | | - red_flags: { type: 'array' }, |
48 | | - capabilities: { type: 'array', items: { type: 'string' } }, |
49 | | - }, |
50 | | - required: ['repoId', 'fit'], |
51 | | - }, |
| 17 | +const TOOLS = { |
| 18 | + [SCAN_TOOL.name]: { def: SCAN_TOOL, run: runScanRepo }, |
| 19 | + [BLUEPRINT_TOOL.name]: { def: BLUEPRINT_TOOL, run: runBlueprintScene }, |
52 | 20 | }; |
53 | 21 |
|
54 | 22 | const server = new Server( |
55 | 23 | { name: 'repolens', version: '0.1.0' }, |
56 | 24 | { capabilities: { tools: {} } }, |
57 | 25 | ); |
58 | 26 |
|
59 | | -server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [SCAN_TOOL] })); |
| 27 | +server.setRequestHandler(ListToolsRequestSchema, async () => ({ |
| 28 | + tools: Object.values(TOOLS).map((t) => t.def), |
| 29 | +})); |
60 | 30 |
|
61 | 31 | server.setRequestHandler(CallToolRequestSchema, async (req) => { |
62 | | - if (req.params.name !== 'scan_repo') { |
| 32 | + const tool = TOOLS[req.params.name]; |
| 33 | + if (!tool) { |
63 | 34 | return { isError: true, content: [{ type: 'text', text: `Unknown tool: ${req.params.name}` }] }; |
64 | 35 | } |
65 | 36 | try { |
66 | | - const { platform, repoId } = parseRepoInput(req.params.arguments?.repo); |
67 | | - const repoData = await fetchRepoData(platform, repoId); |
68 | | - const text = await callAnthropic(buildPrompt(repoData)); |
69 | | - const analysis = parseClaudeResponse(text); |
70 | | - const result = { |
71 | | - repoId: repoData.repoId, |
72 | | - platform, |
73 | | - language: repoData.language, |
74 | | - license: repoData.license, |
75 | | - stars: repoData.stars, |
76 | | - description: repoData.description, |
77 | | - ...analysis, |
78 | | - }; |
| 37 | + const result = await tool.run(req.params.arguments || {}); |
79 | 38 | return { |
80 | 39 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], |
81 | 40 | structuredContent: result, |
82 | 41 | }; |
83 | 42 | } catch (err) { |
84 | | - return { isError: true, content: [{ type: 'text', text: `scan_repo failed: ${err.message}` }] }; |
| 43 | + return { |
| 44 | + isError: true, |
| 45 | + content: [{ type: 'text', text: `${req.params.name} failed: ${err.message}` }], |
| 46 | + }; |
85 | 47 | } |
86 | 48 | }); |
87 | 49 |
|
|
0 commit comments