#
tokens: 2960/50000 5/5 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

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

# Files

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

```
node_modules
package-lock.json
```

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

```markdown
# UIThub MCP Server

[![smithery badge](https://smithery.ai/badge/@janwilmake/uithub-mcp)](https://smithery.ai/server/@janwilmake/uithub-mcp)

Model Context Protocol (MCP) server for interacting with the [uithub API](https://uithub.com), which provides a convenient way to fetch GitHub repository contents.

This MCP server allows Claude to retrieve and analyze code from GitHub repositories, making it a powerful tool for understanding and discussing code.

## TODO

- ✅ Simple MCP Server for Claude Desktop
- Make MCP for cursor too https://docs.cursor.com/context/model-context-protocol
- MCP cline support https://github.com/cline/mcp-marketplace
- Button to learn to install MCPs on separate page.
- Add patch api to MCP Server

## Features

- Retrieve repository contents with smart filtering options
- Specify file extensions to include or exclude
- Integrate with Claude Desktop for natural language exploration of repositories

## Installation

### Installing via Smithery

To install uithub-mcp for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@janwilmake/uithub-mcp):

```bash
npx -y @smithery/cli install @janwilmake/uithub-mcp --client claude
```

### Manual Installation
1. `npx uithub-mcp init`
2. restart claude

```

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

```json
{
  "$schema": "https://unpkg.com/wrangler@latest/config-schema.json",
  "name": "uithub-remote-mcp",
  "main": "index.ts",
  "compatibility_date": "2025-09-01",
  "route": { "custom_domain": true, "pattern": "mcp.uithub.com" }
}

```

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

```json
{
  "name": "uithub-mcp",
  "version": "0.2.0",
  "description": "MCP server for interacting with UIThub API",
  "license": "MIT",
  "type": "module",
  "main": "index.ts",
  "files": [
    "index.ts",
    "README.md"
  ],
  "dependencies": {
    "simplerauth-client": "0.0.19"
  }
}

```

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

```typescript
import { withSimplerAuth } from "simplerauth-client";

interface Env {
  // Add any environment variables here if needed
}

interface AuthenticatedContext extends ExecutionContext {
  user: {
    id: string;
    name: string;
    username: string;
    profile_image_url?: string;
    verified?: boolean;
  };
  accessToken: string;
  authenticated: boolean;
}

const TOOLS = [
  {
    name: "getRepositoryContents",
    description:
      "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.",
    inputSchema: {
      type: "object",
      properties: {
        owner: {
          type: "string",
          description: "GitHub repository owner",
        },
        repo: {
          type: "string",
          description: "GitHub repository name",
        },
        branch: {
          type: "string",
          description: "Branch name (defaults to main if not provided)",
        },
        path: {
          type: "string",
          description: "File or directory path within the repository",
        },
        ext: {
          type: "string",
          description: "Comma-separated list of file extensions to include",
        },
        dir: {
          type: "string",
          description: "Comma-separated list of directories to include",
        },
        excludeExt: {
          type: "string",
          description: "Comma-separated list of file extensions to exclude",
        },
        excludeDir: {
          type: "string",
          description: "Comma-separated list of directories to exclude",
        },
        maxFileSize: {
          type: "integer",
          description: "Maximum file size to include (in bytes)",
        },
        maxTokens: {
          type: "integer",
          description:
            "Limit the response to a maximum number of tokens (defaults to 50000)",
        },
        omitFiles: {
          type: "boolean",
          description: "If true, response will not include the file contents",
        },
        omitTree: {
          type: "boolean",
          description: "If true, response will not include the directory tree",
        },
      },
      required: ["owner", "repo"],
    },
  },
];

async function handleMCPRequest(
  request: Request,
  env: Env,
  ctx: AuthenticatedContext
): Promise<Response> {
  // Handle CORS preflight
  if (request.method === "OPTIONS") {
    return new Response(null, {
      status: 204,
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "POST, OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type, Authorization, Accept",
        "Access-Control-Max-Age": "86400",
      },
    });
  }

  // Require authentication
  if (!ctx.authenticated) {
    return new Response(
      JSON.stringify({
        jsonrpc: "2.0",
        id: null,
        error: {
          code: -32001,
          message: "Authentication required. Please login with GitHub first.",
        },
      }),
      {
        status: 401,
        headers: {
          "Content-Type": "application/json",
          "Access-Control-Allow-Origin": "*",
        },
      }
    );
  }

  let message: any;
  try {
    message = await request.json();
  } catch (error) {
    return createError(null, -32700, "Parse error");
  }

  // Handle initialize
  if (message.method === "initialize") {
    return new Response(
      JSON.stringify({
        jsonrpc: "2.0",
        id: message.id,
        result: {
          protocolVersion: "2025-03-26",
          capabilities: { tools: {} },
          serverInfo: {
            name: "UIThub-Remote-MCP",
            version: "1.0.0",
          },
        },
      }),
      {
        headers: {
          "Content-Type": "application/json",
          "Access-Control-Allow-Origin": "*",
        },
      }
    );
  }

  // Handle initialized notification
  if (message.method === "notifications/initialized") {
    return new Response(null, {
      status: 202,
      headers: {
        "Access-Control-Allow-Origin": "*",
      },
    });
  }

  // Handle tools/list
  if (message.method === "tools/list") {
    return new Response(
      JSON.stringify({
        jsonrpc: "2.0",
        id: message.id,
        result: { tools: TOOLS },
      }),
      {
        headers: {
          "Content-Type": "application/json",
          "Access-Control-Allow-Origin": "*",
        },
      }
    );
  }

  // Handle tools/call
  if (message.method === "tools/call") {
    const { name, arguments: args } = message.params;

    if (name !== "getRepositoryContents") {
      return createError(message.id, -32602, `Unknown tool: ${name}`);
    }

    return await handleGetRepositoryContents(message.id, args, ctx.accessToken);
  }

  return createError(message.id, -32601, `Method not found: ${message.method}`);
}

async function handleGetRepositoryContents(
  messageId: any,
  args: any,
  accessToken: string
): Promise<Response> {
  const {
    owner,
    repo,
    branch = "main",
    path = "",
    ext,
    dir,
    excludeExt,
    excludeDir,
    maxFileSize,
    maxTokens = 50000,
    omitFiles,
    omitTree,
  } = args;

  if (!owner || !repo) {
    return createError(
      messageId,
      -32602,
      "Missing required parameters: owner and repo"
    );
  }

  try {
    // Build URLSearchParams for UIThub API
    const params = new URLSearchParams();
    if (ext) params.append("ext", ext);
    if (dir) params.append("dir", dir);
    if (excludeExt) params.append("exclude-ext", excludeExt);
    if (excludeDir) params.append("exclude-dir", excludeDir);
    if (maxFileSize) params.append("maxFileSize", maxFileSize.toString());
    params.append("maxTokens", maxTokens.toString());
    if (omitFiles) params.append("omitFiles", "true");
    if (omitTree) params.append("omitTree", "true");

    // Use the GitHub access token for UIThub API
    if (accessToken) {
      params.append("apiKey", accessToken);
    }

    // Construct the UIThub API URL
    const pathSegment = path ? `/${path}` : "";
    const url = `https://uithub.com/${owner}/${repo}/tree/${branch}${pathSegment}?${params.toString()}`;

    // Make request to UIThub API
    const response = await fetch(url, {
      headers: { Accept: "text/markdown" },
    });

    if (!response.ok) {
      const error = await response.text();
      return createError(messageId, -32603, `UIThub API error: ${error}`);
    }

    const responseText = await response.text();

    return new Response(
      JSON.stringify({
        jsonrpc: "2.0",
        id: messageId,
        result: {
          content: [{ type: "text", text: responseText }],
          isError: false,
        },
      }),
      {
        headers: {
          "Content-Type": "application/json",
          "Access-Control-Allow-Origin": "*",
        },
      }
    );
  } catch (error) {
    return createError(
      messageId,
      -32603,
      `Error fetching repository contents: ${error.message}`
    );
  }
}

function createError(id: any, code: number, message: string): Response {
  return new Response(
    JSON.stringify({
      jsonrpc: "2.0",
      id,
      error: { code, message },
    }),
    {
      status: 200, // JSON-RPC errors use 200 status
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
    }
  );
}

// Main handler wrapped with SimplerAuth
async function handler(
  request: Request,
  env: Env,
  ctx: AuthenticatedContext
): Promise<Response> {
  const url = new URL(request.url);

  // Handle MCP endpoint
  if (url.pathname === "/mcp") {
    return handleMCPRequest(request, env, ctx);
  }

  // Handle root - show connection instructions
  if (url.pathname === "/") {
    const loginUrl = ctx.authenticated
      ? ""
      : `<p><a href="/authorize">Login with GitHub</a> first to use the MCP server.</p>`;

    console.log({ user: ctx.user });
    return new Response(
      `<!DOCTYPE html>
      <html>
      <head>
        <title>UIThub Remote MCP Server</title>
        <style>
          body { font-family: system-ui, sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
          pre { background: #f5f5f5; padding: 1rem; border-radius: 4px; overflow-x: auto; }
          .user-info { background: #e8f5e9; padding: 1rem; border-radius: 4px; margin: 1rem 0; }
        </style>
      </head>
      <body>
        <h1>UIThub Remote MCP Server</h1>
        
        ${
          ctx.authenticated
            ? `
          <div class="user-info">
            <strong>Logged in as:</strong> ${ctx.user.name || ""} @${
                ctx.user.login || ctx.user.username
              }
          </div>
        `
            : loginUrl
        }
        
        <p>This is a remote MCP server that provides access to GitHub repositories through UIThub API.</p>
        
        <h2>Usage</h2>
        <p>Connect your MCP client to:</p>
        <pre>${url.origin}/mcp</pre>
        
        <p>Available tools:</p>
        <ul>
          <li><strong>getRepositoryContents</strong> - Get repository contents from GitHub with filtering options</li>
        </ul>
        
        <h2>Authentication</h2>
        <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>
        
        ${ctx.authenticated ? `<p><a href="/logout">Logout</a></p>` : ""}
      </body>
      </html>`,
      { headers: { "Content-Type": "text/html" } }
    );
  }

  // Handle 404
  return new Response("Not Found", { status: 404 });
}

// Export the handler wrapped with SimplerAuth
export default {
  fetch: withSimplerAuth(handler, {
    isLoginRequired: false, // We handle auth manually for MCP endpoint
    oauthProviderHost: "gh.simplerauth.com",
    scope: "repo read:user",
  }),
};

```