github-rag-mcp 0.1.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/manifest.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "manifest_version": "0.3",
3
+ "name": "github-rag-mcp",
4
+ "display_name": "GitHub RAG MCP",
5
+ "version": "0.1.0",
6
+ "description": "Semantic search for GitHub issues and PRs via Cloudflare Worker + Vectorize.",
7
+ "long_description": "GitHub RAG MCP provides AI with ambient context about GitHub issue/PR state via semantic and structured search. Issues and PRs are indexed into Cloudflare Vectorize with BGE-M3 embeddings, enabling natural language search with optional metadata filters. Counterpart to github-webhook-mcp (push-based notifications) — together they give AI a complete view of GitHub project state.",
8
+ "author": {
9
+ "name": "Liplus Project",
10
+ "url": "https://github.com/Liplus-Project"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/Liplus-Project/github-rag-mcp.git"
15
+ },
16
+ "homepage": "https://github.com/Liplus-Project/github-rag-mcp",
17
+ "documentation": "https://github.com/Liplus-Project/github-rag-mcp#readme",
18
+ "support": "https://github.com/Liplus-Project/github-rag-mcp/discussions",
19
+ "license": "MIT",
20
+ "server": {
21
+ "type": "node",
22
+ "entry_point": "server/index.js",
23
+ "mcp_config": {
24
+ "command": "node",
25
+ "args": [
26
+ "${__dirname}/server/index.js"
27
+ ],
28
+ "env": {
29
+ "RAG_WORKER_URL": "${user_config.worker_url}"
30
+ }
31
+ }
32
+ },
33
+ "user_config": {
34
+ "worker_url": {
35
+ "description": "URL of the Cloudflare Worker endpoint (e.g. https://github-rag-mcp.liplus.workers.dev). Authentication is handled automatically via OAuth.",
36
+ "type": "string",
37
+ "required": true,
38
+ "title": "Worker URL"
39
+ }
40
+ },
41
+ "tools": [
42
+ {
43
+ "name": "search_issues",
44
+ "description": "Semantic search for GitHub issues and PRs combined with structured filters."
45
+ },
46
+ {
47
+ "name": "get_issue_context",
48
+ "description": "Get aggregated context for a single issue/PR including related PRs, branch status, and CI status."
49
+ },
50
+ {
51
+ "name": "list_recent_activity",
52
+ "description": "List recent issue/PR activity across tracked repositories."
53
+ }
54
+ ],
55
+ "compatibility": {
56
+ "claude_desktop": ">=0.11.0",
57
+ "platforms": [
58
+ "win32",
59
+ "darwin",
60
+ "linux"
61
+ ],
62
+ "runtimes": {
63
+ "node": ">=18.0.0"
64
+ }
65
+ },
66
+ "keywords": [
67
+ "github",
68
+ "issues",
69
+ "search",
70
+ "rag",
71
+ "vectorize",
72
+ "cloudflare",
73
+ "mcp",
74
+ "claude"
75
+ ],
76
+ "privacy_policies": [
77
+ "https://smgjp.com/privacy-policy-github-rag-mcp/"
78
+ ]
79
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "github-rag-mcp",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for semantic search of GitHub issues/PRs via Cloudflare Worker",
5
+ "type": "module",
6
+ "bin": {
7
+ "github-rag-mcp": "server/index.js"
8
+ },
9
+ "main": "./server/index.js",
10
+ "files": [
11
+ "server/",
12
+ "manifest.json",
13
+ "server.json"
14
+ ],
15
+ "scripts": {
16
+ "start": "node server/index.js",
17
+ "test": "node --check server/index.js",
18
+ "pack:mcpb": "mcpb pack"
19
+ },
20
+ "dependencies": {
21
+ "@modelcontextprotocol/sdk": "^1.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "@anthropic-ai/mcpb": "^2.1.2"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "keywords": [
30
+ "mcp",
31
+ "github",
32
+ "issues",
33
+ "search",
34
+ "rag",
35
+ "cloudflare"
36
+ ],
37
+ "mcpName": "io.github.Liplus-Project/github-rag-mcp",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/Liplus-Project/github-rag-mcp.git"
42
+ }
43
+ }
@@ -0,0 +1,554 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * GitHub RAG MCP — Cloudflare Worker bridge
4
+ *
5
+ * Thin stdio MCP server that proxies tool calls to a remote
6
+ * Cloudflare Worker + Durable Object backend via Streamable HTTP.
7
+ * Authenticates via OAuth 2.1 with PKCE (localhost callback).
8
+ *
9
+ * Tools are proxied to the Worker's MCP endpoint:
10
+ * search_issues — semantic + structured search via Vectorize + Workers AI
11
+ * get_issue_context — aggregated issue view with related PRs, branch, CI
12
+ * list_recent_activity — recent changes across tracked repositories
13
+ */
14
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
15
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16
+ import {
17
+ ListToolsRequestSchema,
18
+ CallToolRequestSchema,
19
+ } from "@modelcontextprotocol/sdk/types.js";
20
+ import { createServer } from "node:http";
21
+ import { randomBytes, createHash } from "node:crypto";
22
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
23
+ import { homedir } from "node:os";
24
+ import { join } from "node:path";
25
+ import { exec } from "node:child_process";
26
+ import { createRequire } from "node:module";
27
+
28
+ const require = createRequire(import.meta.url);
29
+ const { version: PACKAGE_VERSION } = require("../package.json");
30
+
31
+ const WORKER_URL =
32
+ process.env.RAG_WORKER_URL ||
33
+ "https://github-rag-mcp.liplus.workers.dev";
34
+
35
+ // ── OAuth Token Storage ──────────────────────────────────────────────────────
36
+
37
+ const TOKEN_DIR = join(homedir(), ".github-rag-mcp");
38
+ const TOKEN_FILE = join(TOKEN_DIR, "oauth-tokens.json");
39
+ const CLIENT_REG_FILE = join(TOKEN_DIR, "oauth-client.json");
40
+
41
+ async function loadTokens() {
42
+ try {
43
+ const data = await readFile(TOKEN_FILE, "utf-8");
44
+ return JSON.parse(data);
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ async function saveTokens(tokens) {
51
+ await mkdir(TOKEN_DIR, { recursive: true });
52
+ await writeFile(TOKEN_FILE, JSON.stringify(tokens, null, 2), { mode: 0o600 });
53
+ }
54
+
55
+ let _cachedTokens = null;
56
+
57
+ // ── PKCE Utilities ───────────────────────────────────────────────────────────
58
+
59
+ function generateCodeVerifier() {
60
+ return randomBytes(32).toString("base64url");
61
+ }
62
+
63
+ function generateCodeChallenge(verifier) {
64
+ return createHash("sha256").update(verifier).digest("base64url");
65
+ }
66
+
67
+ // ── OAuth Discovery & Registration ───────────────────────────────────────────
68
+
69
+ async function discoverOAuthMetadata() {
70
+ const res = await fetch(`${WORKER_URL}/.well-known/oauth-authorization-server`);
71
+ if (!res.ok) {
72
+ throw new Error(`OAuth discovery failed: ${res.status}`);
73
+ }
74
+ return await res.json();
75
+ }
76
+
77
+ async function loadClientRegistration() {
78
+ try {
79
+ const data = await readFile(CLIENT_REG_FILE, "utf-8");
80
+ return JSON.parse(data);
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+
86
+ async function saveClientRegistration(reg) {
87
+ await mkdir(TOKEN_DIR, { recursive: true });
88
+ await writeFile(CLIENT_REG_FILE, JSON.stringify(reg, null, 2), { mode: 0o600 });
89
+ }
90
+
91
+ async function ensureClientRegistration(metadata, redirectUris) {
92
+ const existing = await loadClientRegistration();
93
+ if (existing) return existing;
94
+
95
+ if (!metadata.registration_endpoint) {
96
+ throw new Error("OAuth server does not support dynamic client registration");
97
+ }
98
+
99
+ const res = await fetch(metadata.registration_endpoint, {
100
+ method: "POST",
101
+ headers: { "Content-Type": "application/json" },
102
+ body: JSON.stringify({
103
+ client_name: "github-rag-mcp-cli",
104
+ redirect_uris: redirectUris,
105
+ grant_types: ["authorization_code", "refresh_token"],
106
+ response_types: ["code"],
107
+ token_endpoint_auth_method: "none",
108
+ }),
109
+ });
110
+
111
+ if (!res.ok) {
112
+ throw new Error(`Client registration failed: ${res.status} ${await res.text()}`);
113
+ }
114
+
115
+ const reg = await res.json();
116
+ await saveClientRegistration(reg);
117
+ return reg;
118
+ }
119
+
120
+ // ── OAuth Localhost Callback Flow ────────────────────────────────────────────
121
+
122
+ let _pendingOAuth = null;
123
+
124
+ class OAuthPendingError extends Error {
125
+ constructor(authUrl) {
126
+ super("OAuth authentication required");
127
+ this.authUrl = authUrl;
128
+ }
129
+ }
130
+
131
+ function openBrowser(url) {
132
+ if (process.platform === "win32") {
133
+ exec(`start "" "${url}"`);
134
+ } else {
135
+ const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
136
+ exec(`${openCmd} "${url}"`);
137
+ }
138
+ }
139
+
140
+ async function startOAuthFlow() {
141
+ const metadata = await discoverOAuthMetadata();
142
+
143
+ const callbackServer = createServer();
144
+ await new Promise((resolve) => {
145
+ callbackServer.listen(0, "127.0.0.1", () => resolve());
146
+ });
147
+ const port = callbackServer.address().port;
148
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
149
+
150
+ const client = await ensureClientRegistration(metadata, [
151
+ redirectUri,
152
+ `http://localhost:${port}/callback`,
153
+ ]);
154
+
155
+ const codeVerifier = generateCodeVerifier();
156
+ const codeChallenge = generateCodeChallenge(codeVerifier);
157
+ const state = randomBytes(16).toString("hex");
158
+
159
+ const authUrl = new URL(metadata.authorization_endpoint);
160
+ authUrl.searchParams.set("response_type", "code");
161
+ authUrl.searchParams.set("client_id", client.client_id);
162
+ authUrl.searchParams.set("redirect_uri", redirectUri);
163
+ authUrl.searchParams.set("state", state);
164
+ authUrl.searchParams.set("code_challenge", codeChallenge);
165
+ authUrl.searchParams.set("code_challenge_method", "S256");
166
+
167
+ const tokenPromise = new Promise((resolve, reject) => {
168
+ const timeout = setTimeout(() => {
169
+ callbackServer.close();
170
+ _pendingOAuth = null;
171
+ reject(new Error("OAuth callback timed out after 5 minutes"));
172
+ }, 5 * 60 * 1000);
173
+
174
+ callbackServer.on("request", async (req, res) => {
175
+ const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
176
+ if (url.pathname !== "/callback") {
177
+ res.writeHead(404);
178
+ res.end("Not found");
179
+ return;
180
+ }
181
+
182
+ const code = url.searchParams.get("code");
183
+ const returnedState = url.searchParams.get("state");
184
+ const error = url.searchParams.get("error");
185
+
186
+ if (error) {
187
+ res.writeHead(200, { "Content-Type": "text/html" });
188
+ res.end("<html><body><h1>Authorization failed</h1><p>You can close this tab.</p></body></html>");
189
+ clearTimeout(timeout);
190
+ callbackServer.close();
191
+ _pendingOAuth = null;
192
+ reject(new Error(`OAuth authorization failed: ${error}`));
193
+ return;
194
+ }
195
+
196
+ if (!code || returnedState !== state) {
197
+ res.writeHead(400, { "Content-Type": "text/html" });
198
+ res.end("<html><body><h1>Invalid callback</h1></body></html>");
199
+ return;
200
+ }
201
+
202
+ res.writeHead(200, { "Content-Type": "text/html" });
203
+ res.end("<html><body><h1>Authorization successful</h1><p>You can close this tab.</p></body></html>");
204
+ clearTimeout(timeout);
205
+ callbackServer.close();
206
+
207
+ try {
208
+ const tokenRes = await fetch(metadata.token_endpoint, {
209
+ method: "POST",
210
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
211
+ body: new URLSearchParams({
212
+ grant_type: "authorization_code",
213
+ code,
214
+ redirect_uri: redirectUri,
215
+ client_id: client.client_id,
216
+ code_verifier: codeVerifier,
217
+ }),
218
+ });
219
+
220
+ if (!tokenRes.ok) {
221
+ _pendingOAuth = null;
222
+ reject(new Error(`Token exchange failed: ${tokenRes.status} ${await tokenRes.text()}`));
223
+ return;
224
+ }
225
+
226
+ const tokenData = await tokenRes.json();
227
+ const tokens = {
228
+ access_token: tokenData.access_token,
229
+ refresh_token: tokenData.refresh_token,
230
+ expires_at: tokenData.expires_in
231
+ ? Date.now() + tokenData.expires_in * 1000
232
+ : undefined,
233
+ };
234
+
235
+ await saveTokens(tokens);
236
+ _pendingOAuth = null;
237
+ resolve(tokens);
238
+ } catch (err) {
239
+ _pendingOAuth = null;
240
+ reject(err);
241
+ }
242
+ });
243
+ });
244
+
245
+ openBrowser(authUrl.toString());
246
+ process.stderr.write(
247
+ `\n[github-rag-mcp] Opening browser for authentication...\n`,
248
+ );
249
+
250
+ _pendingOAuth = { authUrl: authUrl.toString(), tokenPromise };
251
+ return _pendingOAuth;
252
+ }
253
+
254
+ async function performOAuthFlow() {
255
+ if (_pendingOAuth) {
256
+ const result = await Promise.race([
257
+ _pendingOAuth.tokenPromise,
258
+ new Promise((resolve) => setTimeout(() => resolve(null), 2000)),
259
+ ]);
260
+ if (result && result.access_token) return result;
261
+ throw new OAuthPendingError(_pendingOAuth.authUrl);
262
+ }
263
+
264
+ const pending = await startOAuthFlow();
265
+
266
+ const result = await Promise.race([
267
+ pending.tokenPromise,
268
+ new Promise((resolve) => setTimeout(() => resolve(null), 3000)),
269
+ ]);
270
+ if (result && result.access_token) return result;
271
+
272
+ throw new OAuthPendingError(pending.authUrl);
273
+ }
274
+
275
+ async function refreshAccessToken(refreshToken) {
276
+ const metadata = await discoverOAuthMetadata();
277
+ const client = await loadClientRegistration();
278
+ if (!client) throw new Error("No client registration found");
279
+
280
+ const res = await fetch(metadata.token_endpoint, {
281
+ method: "POST",
282
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
283
+ body: new URLSearchParams({
284
+ grant_type: "refresh_token",
285
+ refresh_token: refreshToken,
286
+ client_id: client.client_id,
287
+ }),
288
+ });
289
+
290
+ if (!res.ok) {
291
+ throw new Error(`Token refresh failed: ${res.status}`);
292
+ }
293
+
294
+ const data = await res.json();
295
+
296
+ const tokens = {
297
+ access_token: data.access_token,
298
+ refresh_token: data.refresh_token || refreshToken,
299
+ expires_at: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
300
+ };
301
+
302
+ await saveTokens(tokens);
303
+ return tokens;
304
+ }
305
+
306
+ async function getAccessToken() {
307
+ if (!_cachedTokens) {
308
+ _cachedTokens = await loadTokens();
309
+ }
310
+
311
+ if (_cachedTokens) {
312
+ if (!_cachedTokens.expires_at || _cachedTokens.expires_at > Date.now() + 60_000) {
313
+ return _cachedTokens.access_token;
314
+ }
315
+
316
+ if (_cachedTokens.refresh_token) {
317
+ try {
318
+ _cachedTokens = await refreshAccessToken(_cachedTokens.refresh_token);
319
+ return _cachedTokens.access_token;
320
+ } catch {
321
+ // Refresh failed, fall through to full OAuth flow
322
+ }
323
+ }
324
+ }
325
+
326
+ _cachedTokens = await performOAuthFlow();
327
+ return _cachedTokens.access_token;
328
+ }
329
+
330
+ /** Build common headers with OAuth Bearer auth */
331
+ async function authHeaders(extra) {
332
+ const h = { ...extra };
333
+ const token = await getAccessToken();
334
+ if (token) h["Authorization"] = `Bearer ${token}`;
335
+ return h;
336
+ }
337
+
338
+ // ── Remote MCP Session (lazy, reused) ────────────────────────────────────────
339
+
340
+ let _sessionId = null;
341
+
342
+ async function getSessionId() {
343
+ if (_sessionId) return _sessionId;
344
+
345
+ const res = await fetch(`${WORKER_URL}/mcp`, {
346
+ method: "POST",
347
+ headers: await authHeaders({
348
+ "Content-Type": "application/json",
349
+ Accept: "application/json, text/event-stream",
350
+ }),
351
+ body: JSON.stringify({
352
+ jsonrpc: "2.0",
353
+ method: "initialize",
354
+ params: {
355
+ protocolVersion: "2024-11-05",
356
+ capabilities: {},
357
+ clientInfo: { name: "github-rag-mcp-bridge", version: PACKAGE_VERSION },
358
+ },
359
+ id: "init",
360
+ }),
361
+ });
362
+
363
+ _sessionId = res.headers.get("mcp-session-id") || "";
364
+ return _sessionId;
365
+ }
366
+
367
+ async function callRemoteTool(name, args) {
368
+ const sessionId = await getSessionId();
369
+
370
+ const res = await fetch(`${WORKER_URL}/mcp`, {
371
+ method: "POST",
372
+ headers: await authHeaders({
373
+ "Content-Type": "application/json",
374
+ Accept: "application/json, text/event-stream",
375
+ "mcp-session-id": sessionId,
376
+ }),
377
+ body: JSON.stringify({
378
+ jsonrpc: "2.0",
379
+ method: "tools/call",
380
+ params: { name, arguments: args },
381
+ id: crypto.randomUUID(),
382
+ }),
383
+ });
384
+
385
+ // 401 = token expired or revoked, re-authenticate and retry
386
+ if (res.status === 401) {
387
+ _cachedTokens = null;
388
+ _sessionId = null;
389
+ return callRemoteTool(name, args);
390
+ }
391
+
392
+ const text = await res.text();
393
+
394
+ // Streamable HTTP may return SSE format
395
+ const dataLine = text.split("\n").find((l) => l.startsWith("data: "));
396
+ const json = dataLine ? JSON.parse(dataLine.slice(6)) : JSON.parse(text);
397
+
398
+ if (json.error) {
399
+ // Session expired — retry once with a fresh session
400
+ if (json.error.code === -32600 || json.error.code === -32001) {
401
+ _sessionId = null;
402
+ return callRemoteTool(name, args);
403
+ }
404
+ return { content: [{ type: "text", text: JSON.stringify(json.error) }] };
405
+ }
406
+
407
+ return json.result;
408
+ }
409
+
410
+ // ── MCP Server Setup ─────────────────────────────────────────────────────────
411
+
412
+ const server = new Server(
413
+ { name: "github-rag-mcp", version: PACKAGE_VERSION },
414
+ { capabilities: { tools: {} } },
415
+ );
416
+
417
+ // ── Tool Definitions ─────────────────────────────────────────────────────────
418
+
419
+ const TOOLS = [
420
+ {
421
+ name: "search_issues",
422
+ title: "Search Issues",
423
+ description:
424
+ "Semantic search for GitHub issues and PRs combined with structured filters. " +
425
+ "Uses embedding similarity (BGE-M3) with optional metadata filters.",
426
+ inputSchema: {
427
+ type: "object",
428
+ properties: {
429
+ query: {
430
+ type: "string",
431
+ description: "Natural language search query",
432
+ },
433
+ repo: {
434
+ type: "string",
435
+ description: "Filter by repository (owner/repo)",
436
+ },
437
+ state: {
438
+ type: "string",
439
+ enum: ["open", "closed", "all"],
440
+ description: "Filter by state (default: all)",
441
+ },
442
+ labels: {
443
+ type: "array",
444
+ items: { type: "string" },
445
+ description: "Filter by label names (AND logic)",
446
+ },
447
+ milestone: {
448
+ type: "string",
449
+ description: "Filter by milestone title",
450
+ },
451
+ assignee: {
452
+ type: "string",
453
+ description: "Filter by assignee login",
454
+ },
455
+ type: {
456
+ type: "string",
457
+ enum: ["issue", "pull_request", "all"],
458
+ description: "Filter by type (default: all)",
459
+ },
460
+ top_k: {
461
+ type: "number",
462
+ description: "Max results (default: 10, max: 50)",
463
+ },
464
+ },
465
+ required: ["query"],
466
+ },
467
+ annotations: {
468
+ title: "Search Issues",
469
+ readOnlyHint: true,
470
+ },
471
+ },
472
+ {
473
+ name: "get_issue_context",
474
+ title: "Get Issue Context",
475
+ description:
476
+ "Get aggregated context for a single issue/PR including related PRs, branch status, and CI status.",
477
+ inputSchema: {
478
+ type: "object",
479
+ properties: {
480
+ repo: {
481
+ type: "string",
482
+ description: "Repository (owner/repo)",
483
+ },
484
+ number: {
485
+ type: "number",
486
+ description: "Issue or PR number",
487
+ },
488
+ },
489
+ required: ["repo", "number"],
490
+ },
491
+ annotations: {
492
+ title: "Get Issue Context",
493
+ readOnlyHint: true,
494
+ },
495
+ },
496
+ {
497
+ name: "list_recent_activity",
498
+ title: "List Recent Activity",
499
+ description:
500
+ "List recent issue/PR activity across tracked repositories. " +
501
+ "Returns changes classified as created, updated, or closed.",
502
+ inputSchema: {
503
+ type: "object",
504
+ properties: {
505
+ repo: {
506
+ type: "string",
507
+ description: "Filter by repository (owner/repo)",
508
+ },
509
+ since: {
510
+ type: "string",
511
+ description: "ISO 8601 timestamp for activity start (default: last 24 hours)",
512
+ },
513
+ limit: {
514
+ type: "number",
515
+ description: "Max results (default: 20, max: 100)",
516
+ },
517
+ },
518
+ },
519
+ annotations: {
520
+ title: "List Recent Activity",
521
+ readOnlyHint: true,
522
+ },
523
+ },
524
+ ];
525
+
526
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
527
+
528
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
529
+ const { name, arguments: args } = req.params;
530
+ try {
531
+ return await callRemoteTool(name, args ?? {});
532
+ } catch (err) {
533
+ if (err instanceof OAuthPendingError) {
534
+ return {
535
+ content: [
536
+ {
537
+ type: "text",
538
+ text: `Authentication required. A browser window should have opened for authorization. After authorizing in the browser, retry the tool call.`,
539
+ },
540
+ ],
541
+ isError: true,
542
+ };
543
+ }
544
+ return {
545
+ content: [{ type: "text", text: `Failed to reach worker: ${err}` }],
546
+ isError: true,
547
+ };
548
+ }
549
+ });
550
+
551
+ // ── Start ────────────────────────────────────────────────────────────────────
552
+
553
+ const transport = new StdioServerTransport();
554
+ await server.connect(transport);
package/server.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.Liplus-Project/github-rag-mcp",
4
+ "description": "Semantic search for GitHub issues and PRs via Cloudflare Worker + Vectorize embeddings",
5
+ "version": "0.1.0",
6
+ "repository": {
7
+ "url": "https://github.com/Liplus-Project/github-rag-mcp",
8
+ "source": "github",
9
+ "subfolder": "mcp-server"
10
+ },
11
+ "packages": [
12
+ {
13
+ "registryType": "npm",
14
+ "identifier": "github-rag-mcp",
15
+ "version": "0.1.0",
16
+ "runtimeHint": "npx",
17
+ "transport": {
18
+ "type": "stdio"
19
+ },
20
+ "environmentVariables": [
21
+ {
22
+ "name": "RAG_WORKER_URL",
23
+ "description": "URL of your deployed Cloudflare Worker endpoint (default: https://github-rag-mcp.liplus.workers.dev)",
24
+ "isRequired": false
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ }