Skip to content

Implement search_code handler with tests #106

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

Merged
merged 1 commit into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified .husky/pre-commit
100644 → 100755
Empty file.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ For repository-specific tool documentation, see the [Repositories Tools Guide](d
- `list_work_items`: List work items in a project
- `manage_work_item_link`: Add, remove, or update links between work items

### Search Tools

- `search_code`: Search for code across repositories in a project

For search-specific tool documentation, see the [Search Tools Guide](docs/tools/search.md).

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
Expand Down
94 changes: 94 additions & 0 deletions docs/tools/search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Search Tools

This document describes the search tools available in the Azure DevOps MCP server.

## search_code

The `search_code` tool allows you to search for code across repositories in an Azure DevOps project. It uses the Azure DevOps Search API to find code matching your search criteria and can optionally include the full content of the files in the results.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| searchText | string | Yes | The text to search for in the code |
| projectId | string | Yes | The ID or name of the project to search in |
| filters | object | No | Optional filters to narrow search results |
| filters.Repository | string[] | No | Filter by repository names |
| filters.Path | string[] | No | Filter by file paths |
| filters.Branch | string[] | No | Filter by branch names |
| filters.CodeElement | string[] | No | Filter by code element types (function, class, etc.) |
| top | number | No | Number of results to return (default: 100, max: 1000) |
| skip | number | No | Number of results to skip for pagination (default: 0) |
| includeSnippet | boolean | No | Whether to include code snippets in results (default: true) |
| includeContent | boolean | No | Whether to include full file content in results (default: true) |

### Response

The response includes:

- `count`: The total number of matching files
- `results`: An array of search results, each containing:
- `fileName`: The name of the file
- `path`: The path to the file
- `content`: The full content of the file (if `includeContent` is true)
- `matches`: Information about where the search text was found in the file
- `collection`: Information about the collection
- `project`: Information about the project
- `repository`: Information about the repository
- `versions`: Information about the versions of the file
- `facets`: Aggregated information about the search results, such as counts by repository, path, etc.

### Examples

#### Basic Search

```json
{
"searchText": "function searchCode",
"projectId": "MyProject"
}
```

#### Search with Filters

```json
{
"searchText": "function searchCode",
"projectId": "MyProject",
"filters": {
"Repository": ["MyRepo"],
"Path": ["/src"],
"Branch": ["main"],
"CodeElement": ["function", "class"]
}
}
```

#### Search with Pagination

```json
{
"searchText": "function",
"projectId": "MyProject",
"top": 10,
"skip": 20
}
```

#### Search without File Content

```json
{
"searchText": "function",
"projectId": "MyProject",
"includeContent": false
}
```

### Notes

- The search is performed using the Azure DevOps Search API, which is separate from the core Azure DevOps API.
- The search API uses a different base URL (`almsearch.dev.azure.com`) than the regular Azure DevOps API.
- When `includeContent` is true, the tool makes additional API calls to fetch the full content of each file in the search results.
- The search API supports a variety of search syntax, including wildcards, exact phrases, and boolean operators. See the [Azure DevOps Search documentation](https://learn.microsoft.com/en-us/azure/devops/project/search/get-started-search?view=azure-devops) for more information.
- The `CodeElement` filter allows you to filter by code element types such as `function`, `class`, `method`, `property`, `variable`, `comment`, etc.
3 changes: 3 additions & 0 deletions src/features/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './schemas';
export * from './types';
export * from './search-code';
47 changes: 47 additions & 0 deletions src/features/search/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { z } from 'zod';

/**
* Schema for searching code in Azure DevOps repositories
*/
export const SearchCodeSchema = z.object({
searchText: z.string().describe('The text to search for'),
projectId: z.string().describe('The ID or name of the project to search in'),
filters: z
.object({
Repository: z
.array(z.string())
.optional()
.describe('Filter by repository names'),
Path: z.array(z.string()).optional().describe('Filter by file paths'),
Branch: z.array(z.string()).optional().describe('Filter by branch names'),
CodeElement: z
.array(z.string())
.optional()
.describe('Filter by code element types (function, class, etc.)'),
})
.optional()
.describe('Optional filters to narrow search results'),
top: z
.number()
.int()
.min(1)
.max(1000)
.default(100)
.describe('Number of results to return (default: 100, max: 1000)'),
skip: z
.number()
.int()
.min(0)
.default(0)
.describe('Number of results to skip for pagination (default: 0)'),
includeSnippet: z
.boolean()
.default(true)
.describe('Whether to include code snippets in results (default: true)'),
includeContent: z
.boolean()
.default(true)
.describe(
'Whether to include full file content in results (default: true)',
),
});
Loading