#
tokens: 4005/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── index.ts
├── package.json
├── README.md
└── wrangler.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules
2 | package-lock.json
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # UIThub MCP Server
 2 | 
 3 | [![smithery badge](https://smithery.ai/badge/@janwilmake/uithub-mcp)](https://smithery.ai/server/@janwilmake/uithub-mcp)
 4 | 
 5 | Model Context Protocol (MCP) server for interacting with the [uithub API](https://uithub.com), which provides a convenient way to fetch GitHub repository contents.
 6 | 
 7 | This MCP server allows Claude to retrieve and analyze code from GitHub repositories, making it a powerful tool for understanding and discussing code.
 8 | 
 9 | ## TODO
10 | 
11 | - ✅ Simple MCP Server for Claude Desktop
12 | - Make MCP for cursor too https://docs.cursor.com/context/model-context-protocol
13 | - MCP cline support https://github.com/cline/mcp-marketplace
14 | - Button to learn to install MCPs on separate page.
15 | - Add patch api to MCP Server
16 | 
17 | ## Features
18 | 
19 | - Retrieve repository contents with smart filtering options
20 | - Specify file extensions to include or exclude
21 | - Integrate with Claude Desktop for natural language exploration of repositories
22 | 
23 | ## Installation
24 | 
25 | ### Installing via Smithery
26 | 
27 | To install uithub-mcp for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@janwilmake/uithub-mcp):
28 | 
29 | ```bash
30 | npx -y @smithery/cli install @janwilmake/uithub-mcp --client claude
31 | ```
32 | 
33 | ### Manual Installation
34 | 1. `npx uithub-mcp init`
35 | 2. restart claude
36 | 
```

--------------------------------------------------------------------------------
/wrangler.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "$schema": "https://unpkg.com/wrangler@latest/config-schema.json",
3 |   "name": "uithub-remote-mcp",
4 |   "main": "index.ts",
5 |   "compatibility_date": "2025-09-01",
6 |   "route": { "custom_domain": true, "pattern": "mcp.uithub.com" }
7 | }
8 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "uithub-mcp",
 3 |   "version": "0.2.0",
 4 |   "description": "MCP server for interacting with UIThub API",
 5 |   "license": "MIT",
 6 |   "type": "module",
 7 |   "main": "index.ts",
 8 |   "files": [
 9 |     "index.ts",
10 |     "README.md"
11 |   ],
12 |   "dependencies": {
13 |     "simplerauth-client": "0.0.19"
14 |   }
15 | }
16 | 
```

--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { withSimplerAuth } from "simplerauth-client";
  2 | 
  3 | interface Env {
  4 |   // Add any environment variables here if needed
  5 | }
  6 | 
  7 | interface AuthenticatedContext extends ExecutionContext {
  8 |   user: {
  9 |     id: string;
 10 |     name: string;
 11 |     username: string;
 12 |     profile_image_url?: string;
 13 |     verified?: boolean;
 14 |   };
 15 |   accessToken: string;
 16 |   authenticated: boolean;
 17 | }
 18 | 
 19 | const TOOLS = [
 20 |   {
 21 |     name: "getRepositoryContents",
 22 |     description:
 23 |       "Get repository contents from GitHub. Unless otherwise instructed, ensure to always first get the tree only (omitFiles:true) to get an idea of the file structure. Afterwards, use the different filters to get only the context relevant to cater to the user request.",
 24 |     inputSchema: {
 25 |       type: "object",
 26 |       properties: {
 27 |         owner: {
 28 |           type: "string",
 29 |           description: "GitHub repository owner",
 30 |         },
 31 |         repo: {
 32 |           type: "string",
 33 |           description: "GitHub repository name",
 34 |         },
 35 |         branch: {
 36 |           type: "string",
 37 |           description: "Branch name (defaults to main if not provided)",
 38 |         },
 39 |         path: {
 40 |           type: "string",
 41 |           description: "File or directory path within the repository",
 42 |         },
 43 |         ext: {
 44 |           type: "string",
 45 |           description: "Comma-separated list of file extensions to include",
 46 |         },
 47 |         dir: {
 48 |           type: "string",
 49 |           description: "Comma-separated list of directories to include",
 50 |         },
 51 |         excludeExt: {
 52 |           type: "string",
 53 |           description: "Comma-separated list of file extensions to exclude",
 54 |         },
 55 |         excludeDir: {
 56 |           type: "string",
 57 |           description: "Comma-separated list of directories to exclude",
 58 |         },
 59 |         maxFileSize: {
 60 |           type: "integer",
 61 |           description: "Maximum file size to include (in bytes)",
 62 |         },
 63 |         maxTokens: {
 64 |           type: "integer",
 65 |           description:
 66 |             "Limit the response to a maximum number of tokens (defaults to 50000)",
 67 |         },
 68 |         omitFiles: {
 69 |           type: "boolean",
 70 |           description: "If true, response will not include the file contents",
 71 |         },
 72 |         omitTree: {
 73 |           type: "boolean",
 74 |           description: "If true, response will not include the directory tree",
 75 |         },
 76 |       },
 77 |       required: ["owner", "repo"],
 78 |     },
 79 |   },
 80 | ];
 81 | 
 82 | async function handleMCPRequest(
 83 |   request: Request,
 84 |   env: Env,
 85 |   ctx: AuthenticatedContext
 86 | ): Promise<Response> {
 87 |   // Handle CORS preflight
 88 |   if (request.method === "OPTIONS") {
 89 |     return new Response(null, {
 90 |       status: 204,
 91 |       headers: {
 92 |         "Access-Control-Allow-Origin": "*",
 93 |         "Access-Control-Allow-Methods": "POST, OPTIONS",
 94 |         "Access-Control-Allow-Headers": "Content-Type, Authorization, Accept",
 95 |         "Access-Control-Max-Age": "86400",
 96 |       },
 97 |     });
 98 |   }
 99 | 
100 |   // Require authentication
101 |   if (!ctx.authenticated) {
102 |     return new Response(
103 |       JSON.stringify({
104 |         jsonrpc: "2.0",
105 |         id: null,
106 |         error: {
107 |           code: -32001,
108 |           message: "Authentication required. Please login with GitHub first.",
109 |         },
110 |       }),
111 |       {
112 |         status: 401,
113 |         headers: {
114 |           "Content-Type": "application/json",
115 |           "Access-Control-Allow-Origin": "*",
116 |         },
117 |       }
118 |     );
119 |   }
120 | 
121 |   let message: any;
122 |   try {
123 |     message = await request.json();
124 |   } catch (error) {
125 |     return createError(null, -32700, "Parse error");
126 |   }
127 | 
128 |   // Handle initialize
129 |   if (message.method === "initialize") {
130 |     return new Response(
131 |       JSON.stringify({
132 |         jsonrpc: "2.0",
133 |         id: message.id,
134 |         result: {
135 |           protocolVersion: "2025-03-26",
136 |           capabilities: { tools: {} },
137 |           serverInfo: {
138 |             name: "UIThub-Remote-MCP",
139 |             version: "1.0.0",
140 |           },
141 |         },
142 |       }),
143 |       {
144 |         headers: {
145 |           "Content-Type": "application/json",
146 |           "Access-Control-Allow-Origin": "*",
147 |         },
148 |       }
149 |     );
150 |   }
151 | 
152 |   // Handle initialized notification
153 |   if (message.method === "notifications/initialized") {
154 |     return new Response(null, {
155 |       status: 202,
156 |       headers: {
157 |         "Access-Control-Allow-Origin": "*",
158 |       },
159 |     });
160 |   }
161 | 
162 |   // Handle tools/list
163 |   if (message.method === "tools/list") {
164 |     return new Response(
165 |       JSON.stringify({
166 |         jsonrpc: "2.0",
167 |         id: message.id,
168 |         result: { tools: TOOLS },
169 |       }),
170 |       {
171 |         headers: {
172 |           "Content-Type": "application/json",
173 |           "Access-Control-Allow-Origin": "*",
174 |         },
175 |       }
176 |     );
177 |   }
178 | 
179 |   // Handle tools/call
180 |   if (message.method === "tools/call") {
181 |     const { name, arguments: args } = message.params;
182 | 
183 |     if (name !== "getRepositoryContents") {
184 |       return createError(message.id, -32602, `Unknown tool: ${name}`);
185 |     }
186 | 
187 |     return await handleGetRepositoryContents(message.id, args, ctx.accessToken);
188 |   }
189 | 
190 |   return createError(message.id, -32601, `Method not found: ${message.method}`);
191 | }
192 | 
193 | async function handleGetRepositoryContents(
194 |   messageId: any,
195 |   args: any,
196 |   accessToken: string
197 | ): Promise<Response> {
198 |   const {
199 |     owner,
200 |     repo,
201 |     branch = "main",
202 |     path = "",
203 |     ext,
204 |     dir,
205 |     excludeExt,
206 |     excludeDir,
207 |     maxFileSize,
208 |     maxTokens = 50000,
209 |     omitFiles,
210 |     omitTree,
211 |   } = args;
212 | 
213 |   if (!owner || !repo) {
214 |     return createError(
215 |       messageId,
216 |       -32602,
217 |       "Missing required parameters: owner and repo"
218 |     );
219 |   }
220 | 
221 |   try {
222 |     // Build URLSearchParams for UIThub API
223 |     const params = new URLSearchParams();
224 |     if (ext) params.append("ext", ext);
225 |     if (dir) params.append("dir", dir);
226 |     if (excludeExt) params.append("exclude-ext", excludeExt);
227 |     if (excludeDir) params.append("exclude-dir", excludeDir);
228 |     if (maxFileSize) params.append("maxFileSize", maxFileSize.toString());
229 |     params.append("maxTokens", maxTokens.toString());
230 |     if (omitFiles) params.append("omitFiles", "true");
231 |     if (omitTree) params.append("omitTree", "true");
232 | 
233 |     // Use the GitHub access token for UIThub API
234 |     if (accessToken) {
235 |       params.append("apiKey", accessToken);
236 |     }
237 | 
238 |     // Construct the UIThub API URL
239 |     const pathSegment = path ? `/${path}` : "";
240 |     const url = `https://uithub.com/${owner}/${repo}/tree/${branch}${pathSegment}?${params.toString()}`;
241 | 
242 |     // Make request to UIThub API
243 |     const response = await fetch(url, {
244 |       headers: { Accept: "text/markdown" },
245 |     });
246 | 
247 |     if (!response.ok) {
248 |       const error = await response.text();
249 |       return createError(messageId, -32603, `UIThub API error: ${error}`);
250 |     }
251 | 
252 |     const responseText = await response.text();
253 | 
254 |     return new Response(
255 |       JSON.stringify({
256 |         jsonrpc: "2.0",
257 |         id: messageId,
258 |         result: {
259 |           content: [{ type: "text", text: responseText }],
260 |           isError: false,
261 |         },
262 |       }),
263 |       {
264 |         headers: {
265 |           "Content-Type": "application/json",
266 |           "Access-Control-Allow-Origin": "*",
267 |         },
268 |       }
269 |     );
270 |   } catch (error) {
271 |     return createError(
272 |       messageId,
273 |       -32603,
274 |       `Error fetching repository contents: ${error.message}`
275 |     );
276 |   }
277 | }
278 | 
279 | function createError(id: any, code: number, message: string): Response {
280 |   return new Response(
281 |     JSON.stringify({
282 |       jsonrpc: "2.0",
283 |       id,
284 |       error: { code, message },
285 |     }),
286 |     {
287 |       status: 200, // JSON-RPC errors use 200 status
288 |       headers: {
289 |         "Content-Type": "application/json",
290 |         "Access-Control-Allow-Origin": "*",
291 |       },
292 |     }
293 |   );
294 | }
295 | 
296 | // Main handler wrapped with SimplerAuth
297 | async function handler(
298 |   request: Request,
299 |   env: Env,
300 |   ctx: AuthenticatedContext
301 | ): Promise<Response> {
302 |   const url = new URL(request.url);
303 | 
304 |   // Handle MCP endpoint
305 |   if (url.pathname === "/mcp") {
306 |     return handleMCPRequest(request, env, ctx);
307 |   }
308 | 
309 |   // Handle root - show connection instructions
310 |   if (url.pathname === "/") {
311 |     const loginUrl = ctx.authenticated
312 |       ? ""
313 |       : `<p><a href="/authorize">Login with GitHub</a> first to use the MCP server.</p>`;
314 | 
315 |     console.log({ user: ctx.user });
316 |     return new Response(
317 |       `<!DOCTYPE html>
318 |       <html>
319 |       <head>
320 |         <title>UIThub Remote MCP Server</title>
321 |         <style>
322 |           body { font-family: system-ui, sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
323 |           pre { background: #f5f5f5; padding: 1rem; border-radius: 4px; overflow-x: auto; }
324 |           .user-info { background: #e8f5e9; padding: 1rem; border-radius: 4px; margin: 1rem 0; }
325 |         </style>
326 |       </head>
327 |       <body>
328 |         <h1>UIThub Remote MCP Server</h1>
329 |         
330 |         ${
331 |           ctx.authenticated
332 |             ? `
333 |           <div class="user-info">
334 |             <strong>Logged in as:</strong> ${ctx.user.name || ""} @${
335 |                 ctx.user.login || ctx.user.username
336 |               }
337 |           </div>
338 |         `
339 |             : loginUrl
340 |         }
341 |         
342 |         <p>This is a remote MCP server that provides access to GitHub repositories through UIThub API.</p>
343 |         
344 |         <h2>Usage</h2>
345 |         <p>Connect your MCP client to:</p>
346 |         <pre>${url.origin}/mcp</pre>
347 |         
348 |         <p>Available tools:</p>
349 |         <ul>
350 |           <li><strong>getRepositoryContents</strong> - Get repository contents from GitHub with filtering options</li>
351 |         </ul>
352 |         
353 |         <h2>Authentication</h2>
354 |         <p>This server requires GitHub authentication. Your GitHub access token will be used to make requests to the UIThub API, allowing access to private repositories if you have permission.</p>
355 |         
356 |         ${ctx.authenticated ? `<p><a href="/logout">Logout</a></p>` : ""}
357 |       </body>
358 |       </html>`,
359 |       { headers: { "Content-Type": "text/html" } }
360 |     );
361 |   }
362 | 
363 |   // Handle 404
364 |   return new Response("Not Found", { status: 404 });
365 | }
366 | 
367 | // Export the handler wrapped with SimplerAuth
368 | export default {
369 |   fetch: withSimplerAuth(handler, {
370 |     isLoginRequired: false, // We handle auth manually for MCP endpoint
371 |     oauthProviderHost: "gh.simplerauth.com",
372 |     scope: "repo read:user",
373 |   }),
374 | };
375 | 
```