mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 12:25:22 +00:00
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:
parent
e20d514569
commit
9bee8c2c59
4 changed files with 75 additions and 4 deletions
|
|
@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [1.0.8] - 2025-11-10
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,18 @@ Fetches code that matches the provided regex pattern in `query`.
|
||||||
|
|
||||||
### list_repos
|
### 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
|
### get_file_source
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import escapeStringRegexp from 'escape-string-regexp';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { listRepos, search, getFileSource } from './client.js';
|
import { listRepos, search, getFileSource } from './client.js';
|
||||||
import { env, numberSchema } from './env.js';
|
import { env, numberSchema } from './env.js';
|
||||||
|
import { listReposRequestSchema } from './schemas.js';
|
||||||
import { TextContent } from './types.js';
|
import { TextContent } from './types.js';
|
||||||
import { isServiceError } from './utils.js';
|
import { isServiceError } from './utils.js';
|
||||||
|
|
||||||
|
|
@ -165,8 +166,13 @@ server.tool(
|
||||||
|
|
||||||
server.tool(
|
server.tool(
|
||||||
"list_repos",
|
"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.",
|
"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.",
|
||||||
async () => {
|
listReposRequestSchema.shape,
|
||||||
|
async ({ query, pageNumber = 1, limit = 50 }: {
|
||||||
|
query?: string;
|
||||||
|
pageNumber?: number;
|
||||||
|
limit?: number;
|
||||||
|
}) => {
|
||||||
const response = await listRepos();
|
const response = await listRepos();
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
return {
|
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 {
|
return {
|
||||||
type: "text",
|
type: "text",
|
||||||
text: `id: ${repo.repoName}\nurl: ${repo.webUrl}`,
|
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 {
|
return {
|
||||||
content,
|
content,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,25 @@ export const repositoryQuerySchema = z.object({
|
||||||
|
|
||||||
export const listRepositoriesResponseSchema = repositoryQuerySchema.array();
|
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({
|
export const fileSourceRequestSchema = z.object({
|
||||||
fileName: z.string(),
|
fileName: z.string(),
|
||||||
repository: z.string(),
|
repository: z.string(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue