-
Notifications
You must be signed in to change notification settings - Fork 31
Ally remote indexing #193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: beta
Are you sure you want to change the base?
Ally remote indexing #193
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,48 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // src/repo/GitHubRepositoryFetcher.ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Octokit } from "octokit"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { RepositoryFetcher, RepoFileMeta } from "./RepositoryFetcher.js"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class GitHubRepositoryFetcher implements RepositoryFetcher { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private client: Octokit; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private owner: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private repo: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private branch: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.client = new Octokit({ auth: token }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async getTree(): Promise<RepoFileMeta[]> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const treeRes = await this.client.rest.git.getTree({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| owner: this.owner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repo: this.repo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tree_sha: this.branch, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recursive: "true", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return treeRes.data.tree.map((item: any) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| path: item.path!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sha: item.sha!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: item.type as "blob" | "tree", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size: item.size, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+31
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async getTree(): Promise<RepoFileMeta[]> { | |
| const treeRes = await this.client.rest.git.getTree({ | |
| owner: this.owner, | |
| repo: this.repo, | |
| tree_sha: this.branch, | |
| recursive: "true", | |
| }); | |
| return treeRes.data.tree.map((item: any) => ({ | |
| path: item.path!, | |
| sha: item.sha!, | |
| type: item.type as "blob" | "tree", | |
| size: item.size, | |
| })); | |
| /** | |
| * Fetches the entire repository tree recursively. | |
| * WARNING: For large repositories, this can be very slow and memory-intensive. | |
| * Consider using the `filter` argument to limit results, and be aware of possible timeouts. | |
| * @param filter Optional filter function to select which files to include. | |
| * @param timeoutMs Optional timeout in milliseconds (default: 10000ms). | |
| */ | |
| async getTree( | |
| filter?: (item: RepoFileMeta) => boolean, | |
| timeoutMs: number = 10000 | |
| ): Promise<RepoFileMeta[]> { | |
| // Add a timeout to the API call | |
| const controller = new AbortController(); | |
| const timeout = setTimeout(() => controller.abort(), timeoutMs); | |
| let treeRes; | |
| try { | |
| treeRes = await this.client.rest.git.getTree({ | |
| owner: this.owner, | |
| repo: this.repo, | |
| tree_sha: this.branch, | |
| recursive: "true", | |
| request: { signal: controller.signal } | |
| }); | |
| } catch (err: any) { | |
| if (err.name === "AbortError") { | |
| throw new Error(`getTree request timed out after ${timeoutMs}ms`); | |
| } | |
| throw err; | |
| } finally { | |
| clearTimeout(timeout); | |
| } | |
| let files = treeRes.data.tree.map((item: any) => ({ | |
| path: item.path!, | |
| sha: item.sha!, | |
| type: item.type as "blob" | "tree", | |
| size: item.size, | |
| })); | |
| if (filter) { | |
| files = files.filter(filter); | |
| } | |
| return files; |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message "Not a file" is unclear and unhelpful for debugging. Consider providing more context, such as: File path '${path}' does not point to a file (may be a directory or symlink).
| throw new Error("Not a file"); | |
| throw new Error(`File path '${path}' does not point to a file (may be a directory or symlink)`); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,22 @@ | ||||||||||||||||||||||
| // src/repo/RemoteIndexer.ts | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import { RepositoryFetcher, RepoFileMeta } from "./RepositoryFetcher.js"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export class RemoteIndexer { | ||||||||||||||||||||||
| private index: Map<string, RepoFileMeta> = new Map(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| constructor(private fetcher: RepositoryFetcher) {} | ||||||||||||||||||||||
|
Comment on lines
+5
to
+8
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| async buildIndex() { | ||||||||||||||||||||||
| const tree = await this.fetcher.getTree(); | ||||||||||||||||||||||
| tree.forEach((item: RepoFileMeta) => this.index.set(item.path, item)); | ||||||||||||||||||||||
|
Comment on lines
+11
to
+12
|
||||||||||||||||||||||
| const tree = await this.fetcher.getTree(); | |
| tree.forEach((item: RepoFileMeta) => this.index.set(item.path, item)); | |
| try { | |
| const tree = await this.fetcher.getTree(); | |
| tree.forEach((item: RepoFileMeta) => this.index.set(item.path, item)); | |
| } catch (error) { | |
| // Handle error appropriately: log and rethrow, or handle as needed | |
| console.error("Failed to build index:", error); | |
| throw error; | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,13 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // src/repo/RepositoryFetcher.ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface RepoFileMeta { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| path: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sha: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "blob" | "tree"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size?: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface RepositoryFetcher { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getTree(): Promise<RepoFileMeta[]>; // indexing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getFileContent(path: string): Promise<string>; // actual file fetching | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+12
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface RepoFileMeta { | |
| path: string; | |
| sha: string; | |
| type: "blob" | "tree"; | |
| size?: number; | |
| } | |
| export interface RepositoryFetcher { | |
| getTree(): Promise<RepoFileMeta[]>; // indexing | |
| getFileContent(path: string): Promise<string>; // actual file fetching | |
| /** | |
| * Metadata about a file or directory in a repository. | |
| */ | |
| export interface RepoFileMeta { | |
| /** | |
| * The path of the file or directory relative to the repository root. | |
| */ | |
| path: string; | |
| /** | |
| * The SHA hash of the file or directory. | |
| */ | |
| sha: string; | |
| /** | |
| * The type of the entry: "blob" for files, "tree" for directories. | |
| */ | |
| type: "blob" | "tree"; | |
| /** | |
| * The size of the file in bytes. May be undefined for directories ("tree") or if the size is not available. | |
| */ | |
| size?: number; | |
| } | |
| /** | |
| * Interface for fetching repository data such as file trees and file contents. | |
| */ | |
| export interface RepositoryFetcher { | |
| /** | |
| * Fetches the file tree of the repository. | |
| * @returns A promise that resolves to an array of RepoFileMeta objects representing files and directories. | |
| * @throws May reject if the repository cannot be accessed or the tree cannot be fetched. | |
| */ | |
| getTree(): Promise<RepoFileMeta[]>; | |
| /** | |
| * Fetches the content of a file at the given path. | |
| * @param path - The path to the file relative to the repository root. | |
| * @returns A promise that resolves to the file content as a string. | |
| * @throws May reject if the file does not exist, cannot be accessed, or if there is a network or permission error. | |
| */ | |
| getFileContent(path: string): Promise<string>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // src/repo/repo-setup.ts | ||
|
|
||
| import { GitHubRepositoryFetcher } from "./GitHubRepositoryFetcher.js"; | ||
| import { RemoteIndexer } from "./RemoteIndexer.js"; | ||
| import { createFetchFromRepoTool } from "../tools/fetch-from-repo.js"; | ||
| import { createDesignRepoContextResource } from "../resources/design-repo-context.js"; | ||
|
|
||
| export interface RepoConfig { | ||
| owner: string; | ||
| repo: string; | ||
| branch: string; | ||
| token: string; | ||
| } | ||
|
|
||
| export async function setupRemoteRepoMCP(config: RepoConfig) { | ||
| const fetcher = new GitHubRepositoryFetcher( | ||
| config.owner, | ||
| config.repo, | ||
| config.branch, | ||
| config.token, | ||
| ); | ||
|
|
||
| const indexer = new RemoteIndexer(fetcher); | ||
| await indexer.buildIndex(); // build repo map | ||
|
|
||
| const fetchFromRepoTool = createFetchFromRepoTool(indexer, fetcher); | ||
| const contextResource = createDesignRepoContextResource(indexer, fetcher); | ||
|
|
||
| return { fetchFromRepoTool, contextResource, indexer }; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||||||||||
| // src/resources/design-repo-context.ts | ||||||||||||||||||
|
|
||||||||||||||||||
| import { RemoteIndexer } from "../repo/RemoteIndexer.js"; | ||||||||||||||||||
| import { RepositoryFetcher } from "../repo/RepositoryFetcher.js"; | ||||||||||||||||||
|
|
||||||||||||||||||
| export function createDesignRepoContextResource( | ||||||||||||||||||
| indexer: RemoteIndexer, | ||||||||||||||||||
| fetcher: RepositoryFetcher, | ||||||||||||||||||
| ) { | ||||||||||||||||||
| return { | ||||||||||||||||||
| uri: "repo://design-context", | ||||||||||||||||||
| name: "Design Repository Context", | ||||||||||||||||||
| description: | ||||||||||||||||||
| "Provides context from the remote design repository including tokens, components, and documentation", | ||||||||||||||||||
| mimeType: "text/plain", | ||||||||||||||||||
| handler: async () => { | ||||||||||||||||||
| const index = indexer.getIndex(); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Automatically select relevant files | ||||||||||||||||||
| const relevantFiles = [...index.keys()].filter((path) => | ||||||||||||||||||
| path.match(/(tokens|design|components|docs).*\.(json|md|tsx?|css)$/), | ||||||||||||||||||
|
||||||||||||||||||
| path.match(/(tokens|design|components|docs).*\.(json|md|tsx?|css)$/), | |
| path.match(/(^|\/)((tokens|design|components|docs).*\.(json|md|tsx?|css))$/), |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded limit of 10 files lacks documentation explaining why this value was chosen. Consider adding a comment explaining the rationale (e.g., token limits, performance constraints) or making it configurable. Also consider logging when files are truncated so users know content is being omitted.
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using any type for the error weakens type safety. Consider using error: unknown and then type-narrowing to check for .message property, or use a more specific error type.
| } catch (error: any) { | |
| parts.push(`### File: ${path}\nError: ${error.message}`); | |
| } catch (error: unknown) { | |
| let errorMessage = "Unknown error"; | |
| if (typeof error === "object" && error !== null && "message" in error && typeof (error as any).message === "string") { | |
| errorMessage = (error as { message: string }).message; | |
| } | |
| parts.push(`### File: ${path}\nError: ${errorMessage}`); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| // src/tools/fetch-from-repo.ts | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import { z } from "zod"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { RemoteIndexer } from "../repo/RemoteIndexer.js"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { RepositoryFetcher } from "../repo/RepositoryFetcher.js"; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| export function createFetchFromRepoTool( | ||||||||||||||||||||||||||||||||||||||||||||
| indexer: RemoteIndexer, | ||||||||||||||||||||||||||||||||||||||||||||
| fetcher: RepositoryFetcher, | ||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||
| name: "fetchFromRepo", | ||||||||||||||||||||||||||||||||||||||||||||
| description: "Fetch a file from a remote repository that has been indexed", | ||||||||||||||||||||||||||||||||||||||||||||
| inputSchema: z.object({ | ||||||||||||||||||||||||||||||||||||||||||||
| path: z.string().describe("The file path in the repository"), | ||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||
| handler: async ({ path }: { path: string }) => { | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| handler: async ({ path }: { path: string }) => { | |
| handler: async ({ path }: { path: string }) => { | |
| // Validate path input | |
| if ( | |
| typeof path !== "string" || | |
| path.trim() === "" || | |
| path.includes("..") || | |
| path.includes("\\") || | |
| !path.startsWith("/") && !path.match(/^[a-zA-Z0-9_\-./]+$/) | |
| ) { | |
| return { | |
| content: [ | |
| { | |
| type: "text" as const, | |
| text: `Error: Invalid file path.`, | |
| }, | |
| ], | |
| }; | |
| } |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using any type for the error weakens type safety. Consider using error: unknown and then type-narrowing to check for .message property, or use a more specific error type.
| } catch (error: any) { | |
| return { | |
| content: [ | |
| { | |
| type: "text" as const, | |
| text: `Error fetching file: ${error.message}`, | |
| } catch (error: unknown) { | |
| let errorMessage = "Unknown error"; | |
| if ( | |
| typeof error === "object" && | |
| error !== null && | |
| "message" in error && | |
| typeof (error as { message?: unknown }).message === "string" | |
| ) { | |
| errorMessage = (error as { message: string }).message; | |
| } | |
| return { | |
| content: [ | |
| { | |
| type: "text" as const, | |
| text: `Error fetching file: ${errorMessage}`, |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,74 @@ | ||||||
| // src/tools/remote-repo.ts | ||||||
|
|
||||||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||||||
| import { BrowserStackConfig } from "../lib/types.js"; | ||||||
| import { setupRemoteRepoMCP } from "../repo/repo-setup.js"; | ||||||
| import logger from "../logger.js"; | ||||||
|
|
||||||
| /** | ||||||
| * Adds remote repository tools (if configured) | ||||||
| */ | ||||||
| export default function addRemoteRepoTools( | ||||||
| server: McpServer, | ||||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||||
| _config: BrowserStackConfig, | ||||||
|
Comment on lines
+13
to
+14
|
||||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |
| _config: BrowserStackConfig, |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GitHub token is passed directly from environment variables without any sanitization or security checks. Consider:
- Validating the token format (GitHub tokens have specific prefixes like
ghp_,gho_, etc.) - Ensuring tokens are not logged accidentally
- Adding a warning about token scope requirements in documentation
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing validation for the GitHub token and repository configuration. If the token is invalid or doesn't have the necessary permissions, the error will only surface when setupRemoteRepoMCP fails. Consider validating the token format and checking basic repository access before proceeding with setup.
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The setupRemoteRepoMCP function is called asynchronously with .then()/.catch(), but the addRemoteRepoTools function returns immediately with an empty tools object. This means that when the server tries to use these tools, they won't be registered yet, leading to a race condition. The tools will only be added to the local tools object after the promise resolves, but the returned object is already passed to the caller.
Consider either:
- Making
addRemoteRepoToolsasync and awaiting the setup - Returning a promise that resolves when tools are ready
- Registering tools synchronously and deferring only the indexing operation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
anytype weakens type safety. The GitHub API's tree response has a well-defined structure. Consider using the proper Octokit types instead ofany, such asRestEndpointMethodTypes["git"]["getTree"]["response"]["data"]["tree"][number].