feat(mcp): Add pagination and filtering to list_repos tool (#614)

* feat(mcp): Add pagination and filtering to list_repos tool

Fixes #566

  - Add query parameter to filter repositories by name
  - Add pageNumber and limit parameters for pagination
  - Include pagination info in response when applicable
  - Add listReposRequestSchema for request validation
  - Update README with new list_repos parameters

* feat(mcp): Sort repositories alphabetically for consistent pagination

Fixes #566
- Updated CHANGELOG.md with pagination and filtering changes

---------

Co-authored-by: Brendan Kellam <bshizzle1234@gmail.com>
This commit is contained in:
Teddy Reinert 2025-11-17 19:08:20 -06:00 committed by GitHub
parent e20d514569
commit 9bee8c2c59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 75 additions and 4 deletions

View file

@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Added pagination and filtering to `list_repos` tool to handle large repository lists efficiently and prevent oversized responses that waste token context. [#614](https://github.com/sourcebot-dev/sourcebot/pull/614)
## [1.0.8] - 2025-11-10
### Fixed

View file

@ -182,7 +182,18 @@ Fetches code that matches the provided regex pattern in `query`.
### list_repos
Lists all repositories indexed by Sourcebot.
Lists repositories indexed by Sourcebot with optional filtering and pagination.
<details>
<summary>Parameters</summary>
| Name | Required | Description |
|:-------------|:---------|:--------------------------------------------------------------------|
| `query` | no | Filter repositories by name (case-insensitive). |
| `pageNumber` | no | Page number (1-indexed, default: 1). |
| `limit` | no | Number of repositories per page (default: 50). |
</details>
### get_file_source

View file

@ -7,6 +7,7 @@ import escapeStringRegexp from 'escape-string-regexp';
import { z } from 'zod';
import { listRepos, search, getFileSource } from './client.js';
import { env, numberSchema } from './env.js';
import { listReposRequestSchema } from './schemas.js';
import { TextContent } from './types.js';
import { isServiceError } from './utils.js';
@ -165,8 +166,13 @@ server.tool(
server.tool(
"list_repos",
"Lists all repositories in the organization. If you receive an error that indicates that you're not authenticated, please inform the user to set the SOURCEBOT_API_KEY environment variable.",
async () => {
"Lists repositories in the organization with optional filtering and pagination. If you receive an error that indicates that you're not authenticated, please inform the user to set the SOURCEBOT_API_KEY environment variable.",
listReposRequestSchema.shape,
async ({ query, pageNumber = 1, limit = 50 }: {
query?: string;
pageNumber?: number;
limit?: number;
}) => {
const response = await listRepos();
if (isServiceError(response)) {
return {
@ -177,13 +183,45 @@ server.tool(
};
}
const content: TextContent[] = response.map(repo => {
// Apply query filter if provided
let filtered = response;
if (query) {
const lowerQuery = query.toLowerCase();
filtered = response.filter(repo =>
repo.repoName.toLowerCase().includes(lowerQuery) ||
repo.repoDisplayName?.toLowerCase().includes(lowerQuery)
);
}
// Sort alphabetically for consistent pagination
filtered.sort((a, b) => a.repoName.localeCompare(b.repoName));
// Apply pagination
const startIndex = (pageNumber - 1) * limit;
const endIndex = startIndex + limit;
const paginated = filtered.slice(startIndex, endIndex);
// Format output
const content: TextContent[] = paginated.map(repo => {
return {
type: "text",
text: `id: ${repo.repoName}\nurl: ${repo.webUrl}`,
}
});
// Add pagination info
if (content.length === 0 && filtered.length > 0) {
content.push({
type: "text",
text: `No results on page ${pageNumber}. Total matching repositories: ${filtered.length}`,
});
} else if (filtered.length > endIndex) {
content.push({
type: "text",
text: `Showing ${paginated.length} repositories (page ${pageNumber}). Total matching: ${filtered.length}. Use pageNumber ${pageNumber + 1} to see more.`,
});
}
return {
content,
};

View file

@ -156,6 +156,25 @@ export const repositoryQuerySchema = z.object({
export const listRepositoriesResponseSchema = repositoryQuerySchema.array();
export const listReposRequestSchema = z.object({
query: z
.string()
.describe("Filter repositories by name or displayName (case-insensitive)")
.optional(),
pageNumber: z
.number()
.int()
.positive()
.describe("Page number (1-indexed, default: 1)")
.default(1),
limit: z
.number()
.int()
.positive()
.describe("Number of repositories per page (default: 50)")
.default(50),
});
export const fileSourceRequestSchema = z.object({
fileName: z.string(),
repository: z.string(),