Skip to content

Commit c00c67b

Browse files
feat(web): API & MCP improvements (#795)
1 parent f1b4361 commit c00c67b

File tree

51 files changed

+795
-457
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+795
-457
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- Added ask sidebar to homepage. [#721](https://github.com/sourcebot-dev/sourcebot/pull/721)
1212
- Added endpoint for searching commit history for a git repository. [#625](https://github.com/sourcebot-dev/sourcebot/pull/625)
1313
- Added `pushedAt` field to the Repo table to track when a repository last was committed to across all branches. [#790](https://github.com/sourcebot-dev/sourcebot/pull/790)
14+
- Added offset pagination to the `/api/repos` endpoint. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
15+
- Added offset pagination to the `/api/commits` endpoint. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
1416

1517
### Changed
1618
- Added commit graph generation to improve performance for commit traversal operations. [#791](https://github.com/sourcebot-dev/sourcebot/pull/791)
19+
- Made the code search `lang:` filter case insensitive. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
20+
- Changed the `/api/source` endpoint from a POST request to a GET request. Repo, path, and ref are specified as query params. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
21+
- Changed the `/api/commits` endpoint from a POST request to a GET request. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
22+
- Renamed `webUrl` to `externalWebUrl` for various apis. Moving forward, `webUrl` will be used for URLs that point to Sourcebot, and `externalWebUrl` will be used for URLs that point to external code hosts. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
23+
- Renamed various fields on the `/api/source` endpoint response body. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
1724

1825
### Fixed
1926
- Fixed issue where a file would fail to load when opening it from the /search view and it matched multiple branches. [#797](https://github.com/sourcebot-dev/sourcebot/pull/797)

packages/mcp/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111
- Added `search_commits` tool to search a repos commit history. [#625](https://github.com/sourcebot-dev/sourcebot/pull/625)
1212
- Added `gitRevision` parameter to the `search_code` tool to allow for searching on different branches. [#625](https://github.com/sourcebot-dev/sourcebot/pull/625)
13+
- Added server side pagination support for `list_commits` and `list_repos`. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
14+
- Added `filterByFilepaths` and `useRegex` params to the `search_code` tool. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
15+
16+
### Changed
17+
- Renamed `search_commits` tool to `list_commits`. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
18+
- Renamed `gitRevision` param to `ref` on `search_code` tool. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
19+
- Generally improved tool and tool param descriptions for all tools. [#795](https://github.com/sourcebot-dev/sourcebot/pull/795)
1320

1421
## [1.0.12] - 2026-01-13
1522

packages/mcp/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"dependencies": {
2020
"@modelcontextprotocol/sdk": "^1.10.2",
2121
"@t3-oss/env-core": "^0.13.4",
22+
"dedent": "^1.7.1",
2223
"escape-string-regexp": "^5.0.0",
2324
"express": "^5.1.0",
2425
"zod": "^3.24.3"

packages/mcp/src/client.ts

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,105 @@
11
import { env } from './env.js';
2-
import { listRepositoriesResponseSchema, searchResponseSchema, fileSourceResponseSchema, searchCommitsResponseSchema } from './schemas.js';
3-
import { FileSourceRequest, FileSourceResponse, ListRepositoriesResponse, SearchRequest, SearchResponse, ServiceError, SearchCommitsRequest, SearchCommitsResponse } from './types.js';
4-
import { isServiceError } from './utils.js';
2+
import { listReposResponseSchema, searchResponseSchema, fileSourceResponseSchema, listCommitsResponseSchema } from './schemas.js';
3+
import { FileSourceRequest, ListReposQueryParams, SearchRequest, ListCommitsQueryParamsSchema } from './types.js';
4+
import { isServiceError, ServiceErrorException } from './utils.js';
5+
import { z } from 'zod';
56

6-
export const search = async (request: SearchRequest): Promise<SearchResponse | ServiceError> => {
7-
const result = await fetch(`${env.SOURCEBOT_HOST}/api/search`, {
7+
const parseResponse = async <T extends z.ZodTypeAny>(
8+
response: Response,
9+
schema: T
10+
): Promise<z.infer<T>> => {
11+
const text = await response.text();
12+
13+
let json: unknown;
14+
try {
15+
json = JSON.parse(text);
16+
} catch {
17+
throw new Error(`Invalid JSON response: ${text}`);
18+
}
19+
20+
// Check if the response is already a service error from the API
21+
if (isServiceError(json)) {
22+
throw new ServiceErrorException(json);
23+
}
24+
25+
const parsed = schema.safeParse(json);
26+
if (!parsed.success) {
27+
throw new Error(`Failed to parse response: ${parsed.error.message}`);
28+
}
29+
30+
return parsed.data;
31+
};
32+
33+
export const search = async (request: SearchRequest) => {
34+
const response = await fetch(`${env.SOURCEBOT_HOST}/api/search`, {
835
method: 'POST',
936
headers: {
1037
'Content-Type': 'application/json',
1138
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
1239
},
1340
body: JSON.stringify(request)
14-
}).then(response => response.json());
41+
});
1542

16-
if (isServiceError(result)) {
17-
return result;
18-
}
19-
20-
return searchResponseSchema.parse(result);
43+
return parseResponse(response, searchResponseSchema);
2144
}
2245

23-
export const listRepos = async (): Promise<ListRepositoriesResponse | ServiceError> => {
24-
const result = await fetch(`${env.SOURCEBOT_HOST}/api/repos`, {
46+
export const listRepos = async (queryParams: ListReposQueryParams = {}) => {
47+
const url = new URL(`${env.SOURCEBOT_HOST}/api/repos`);
48+
49+
for (const [key, value] of Object.entries(queryParams)) {
50+
if (value) {
51+
url.searchParams.set(key, value.toString());
52+
}
53+
}
54+
55+
const response = await fetch(url, {
2556
method: 'GET',
2657
headers: {
2758
'Content-Type': 'application/json',
2859
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
2960
},
30-
}).then(response => response.json());
31-
32-
if (isServiceError(result)) {
33-
return result;
34-
}
61+
});
3562

36-
return listRepositoriesResponseSchema.parse(result);
63+
const repos = await parseResponse(response, listReposResponseSchema);
64+
const totalCount = parseInt(response.headers.get('X-Total-Count') ?? '0', 10);
65+
return { repos, totalCount };
3766
}
3867

39-
export const getFileSource = async (request: FileSourceRequest): Promise<FileSourceResponse | ServiceError> => {
40-
const result = await fetch(`${env.SOURCEBOT_HOST}/api/source`, {
41-
method: 'POST',
68+
export const getFileSource = async (request: FileSourceRequest) => {
69+
const url = new URL(`${env.SOURCEBOT_HOST}/api/source`);
70+
for (const [key, value] of Object.entries(request)) {
71+
if (value) {
72+
url.searchParams.set(key, value.toString());
73+
}
74+
}
75+
76+
const response = await fetch(url, {
77+
method: 'GET',
4278
headers: {
43-
'Content-Type': 'application/json',
4479
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
4580
},
46-
body: JSON.stringify(request)
47-
}).then(response => response.json());
48-
49-
if (isServiceError(result)) {
50-
return result;
51-
}
81+
});
5282

53-
return fileSourceResponseSchema.parse(result);
83+
return parseResponse(response, fileSourceResponseSchema);
5484
}
5585

56-
export const searchCommits = async (request: SearchCommitsRequest): Promise<SearchCommitsResponse | ServiceError> => {
57-
const result = await fetch(`${env.SOURCEBOT_HOST}/api/commits`, {
58-
method: 'POST',
86+
export const listCommits = async (queryParams: ListCommitsQueryParamsSchema) => {
87+
const url = new URL(`${env.SOURCEBOT_HOST}/api/commits`);
88+
for (const [key, value] of Object.entries(queryParams)) {
89+
if (value) {
90+
url.searchParams.set(key, value.toString());
91+
}
92+
}
93+
94+
const response = await fetch(url, {
95+
method: 'GET',
5996
headers: {
60-
'Content-Type': 'application/json',
6197
'X-Org-Domain': '~',
6298
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
6399
},
64-
body: JSON.stringify(request)
65-
}).then(response => response.json());
66-
67-
if (isServiceError(result)) {
68-
return result;
69-
}
100+
});
70101

71-
return searchCommitsResponseSchema.parse(result);
102+
const commits = await parseResponse(response, listCommitsResponseSchema);
103+
const totalCount = parseInt(response.headers.get('X-Total-Count') ?? '0', 10);
104+
return { commits, totalCount };
72105
}

0 commit comments

Comments
 (0)