Skip to content

Commit 8d8f922

Browse files
authored
Merge pull request #189 from nikolapeja6/users/nikolapeja6/issue-113-2
Option to filter repos and projects by parts of their names (Issue #113)
2 parents dda8102 + 82b113c commit 8d8f922

2 files changed

Lines changed: 43 additions & 7 deletions

File tree

src/tools/core.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,24 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
66
import { WebApi } from "azure-devops-node-api";
77
import { z } from "zod";
88

9+
import type { ProjectInfo } from "azure-devops-node-api/interfaces/CoreInterfaces.js";
10+
911
const CORE_TOOLS = {
1012
list_project_teams: "core_list_project_teams",
1113
list_projects: "core_list_projects",
1214
};
1315

16+
function filterProjectsByName(
17+
projects: ProjectInfo[],
18+
projectNameFilter: string
19+
): ProjectInfo[]
20+
{
21+
const lowerCaseFilter = projectNameFilter.toLowerCase();
22+
return projects.filter((project) =>
23+
project.name?.toLowerCase().includes(lowerCaseFilter)
24+
);
25+
}
26+
1427
function configureCoreTools(
1528
server: McpServer,
1629
tokenProvider: () => Promise<AccessToken>,
@@ -63,9 +76,10 @@ function configureCoreTools(
6376
stateFilter: z.enum(["all", "wellFormed", "createPending", "deleted"]).default("wellFormed").describe("Filter projects by their state. Defaults to 'wellFormed'."),
6477
top: z.number().optional().describe("The maximum number of projects to return. Defaults to 100."),
6578
skip: z.number().optional().describe("The number of projects to skip for pagination. Defaults to 0."),
66-
continuationToken: z.number().optional().describe("Continuation token for pagination. Used to fetch the next set of results if available."),
79+
continuationToken: z.number().optional().describe("Continuation token for pagination. Used to fetch the next set of results if available."),
80+
projectNameFilter: z.string().optional().describe("Filter projects by name. Supports partial matches."),
6781
},
68-
async ({ stateFilter, top, skip, continuationToken }) => {
82+
async ({ stateFilter, top, skip, continuationToken, projectNameFilter }) => {
6983
try {
7084
const connection = await connectionProvider();
7185
const coreApi = await connection.getCoreApi();
@@ -81,8 +95,12 @@ function configureCoreTools(
8195
return { content: [{ type: "text", text: "No projects found" }], isError: true };
8296
}
8397

98+
const filteredProject = projectNameFilter
99+
? filterProjectsByName(projects, projectNameFilter)
100+
: projects;
101+
84102
return {
85-
content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
103+
content: [{ type: "text", text: JSON.stringify(filteredProject, null, 2) }],
86104
};
87105
} catch (error) {
88106
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';

src/tools/repos.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { WebApi } from "azure-devops-node-api";
77
import { GitRef, PullRequestStatus } from "azure-devops-node-api/interfaces/GitInterfaces.js";
88
import { z } from "zod";
99
import { getCurrentUserDetails } from "./auth.js";
10+
import { GitRepository } from "azure-devops-node-api/interfaces/TfvcInterfaces.js";
1011

1112
const REPO_TOOLS = {
1213
list_repos_by_project: "repo_list_repos_by_project",
@@ -55,6 +56,18 @@ function pullRequestStatusStringToInt(
5556
}
5657
}
5758

59+
function filterReposByName(
60+
repositories: GitRepository[],
61+
repoNameFilter: string
62+
): GitRepository[] {
63+
const lowerCaseFilter = repoNameFilter.toLowerCase();
64+
const filteredByName = repositories?.filter((repo) =>
65+
repo.name?.toLowerCase().includes(lowerCaseFilter)
66+
);
67+
68+
return filteredByName;
69+
}
70+
5871
function configureRepoTools(
5972
server: McpServer,
6073
tokenProvider: () => Promise<AccessToken>,
@@ -137,9 +150,10 @@ function configureRepoTools(
137150
REPO_TOOLS.list_repos_by_project,
138151
"Retrieve a list of repositories for a given project",
139152
{
140-
project: z.string().describe("The name or ID of the Azure DevOps project."),
153+
project: z.string().describe("The name or ID of the Azure DevOps project."),
154+
repoNameFilter: z.string().optional().describe("Optional filter to search for repositories by name. If provided, only repositories with names containing this string will be returned."),
141155
},
142-
async ({ project }) => {
156+
async ({ project, repoNameFilter }) => {
143157
const connection = await connectionProvider();
144158
const gitApi = await connection.getGitApi();
145159
const repositories = await gitApi.getRepositories(
@@ -149,8 +163,12 @@ function configureRepoTools(
149163
false
150164
);
151165

166+
const filteredRepositories = repoNameFilter
167+
? filterReposByName(repositories, repoNameFilter)
168+
: repositories;
169+
152170
// Filter out the irrelevant properties
153-
const filteredRepositories = repositories?.map((repo) => ({
171+
const trimmedRepositories = filteredRepositories?.map((repo) => ({
154172
id: repo.id,
155173
name: repo.name,
156174
isDisabled: repo.isDisabled,
@@ -162,7 +180,7 @@ function configureRepoTools(
162180

163181
return {
164182
content: [
165-
{ type: "text", text: JSON.stringify(filteredRepositories, null, 2) },
183+
{ type: "text", text: JSON.stringify(trimmedRepositories, null, 2) },
166184
],
167185
};
168186
}

0 commit comments

Comments
 (0)