Skip to content

Commit f69b613

Browse files
feat(mcp): add readOnlyHint annotations to all MCP tools (#1013)
* feat(mcp): add readOnlyHint annotations to all MCP tools Mark all MCP tools as read-only using the MCP specification's readOnlyHint annotation. This enables compatibility with Cursor's Ask mode and other MCP clients that restrict tool usage based on whether tools modify their environment. All 7 tools (search_code, list_commits, list_repos, read_file, list_tree, list_language_models, ask_codebase) are now annotated with readOnlyHint: true in both the built-in web server and the standalone npm package. Fixes #SOU-707 Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com> * docs: add CHANGELOG entry for MCP read-only annotations Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com>
1 parent 71f0ffc commit f69b613

File tree

3 files changed

+17
-0
lines changed

3 files changed

+17
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Added read-only annotations to MCP tools for compatibility with Cursor Ask mode and other MCP clients that restrict tool usage based on behavior hints. [#1013](https://github.com/sourcebot-dev/sourcebot/pull/1013)
12+
1013
## [4.15.8] - 2026-03-17
1114

1215
### Added

packages/mcp/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ server.tool(
6969
.transform((val) => (val < env.DEFAULT_MINIMUM_TOKENS ? env.DEFAULT_MINIMUM_TOKENS : val))
7070
.optional(),
7171
},
72+
{ readOnlyHint: true },
7273
async ({
7374
query,
7475
filterByRepos: repos = [],
@@ -186,6 +187,7 @@ server.tool(
186187
"list_commits",
187188
dedent`Get a list of commits for a given repository.`,
188189
listCommitsQueryParamsSchema.shape,
190+
{ readOnlyHint: true },
189191
async (request: ListCommitsQueryParamsSchema) => {
190192
const result = await listCommits(request);
191193

@@ -201,6 +203,7 @@ server.tool(
201203
"list_repos",
202204
dedent`Lists repositories in the organization with optional filtering and pagination.`,
203205
listReposQueryParamsSchema.shape,
206+
{ readOnlyHint: true },
204207
async (request: ListReposQueryParams) => {
205208
const result = await listRepos(request);
206209

@@ -226,6 +229,7 @@ server.tool(
226229
"read_file",
227230
dedent`Reads the source code for a given file.`,
228231
fileSourceRequestSchema.shape,
232+
{ readOnlyHint: true },
229233
async (request: FileSourceRequest) => {
230234
const response = await getFileSource(request);
231235

@@ -249,6 +253,7 @@ server.tool(
249253
Returns a flat list of entries with path metadata and depth relative to the requested path.
250254
`,
251255
listTreeRequestSchema.shape,
256+
{ readOnlyHint: true },
252257
async ({
253258
repo,
254259
path = '',
@@ -395,6 +400,7 @@ server.tool(
395400
"list_language_models",
396401
dedent`Lists the available language models configured on the Sourcebot instance. Use this to discover which models can be specified when calling ask_codebase.`,
397402
{},
403+
{ readOnlyHint: true },
398404
async () => {
399405
const models = await listLanguageModels();
400406

@@ -424,6 +430,7 @@ server.tool(
424430
This is a blocking operation that may take 30-60+ seconds for complex questions as the agent researches the codebase.
425431
`,
426432
askCodebaseRequestSchema.shape,
433+
{ readOnlyHint: true },
427434
async (request: AskCodebaseRequest) => {
428435
const response = await askCodebase(request);
429436

packages/web/src/features/mcp/server.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export function createMcpServer(): McpServer {
7676
"search_code",
7777
{
7878
description: TOOL_DESCRIPTIONS.search_code,
79+
annotations: { readOnlyHint: true },
7980
inputSchema: {
8081
query: z
8182
.string()
@@ -230,6 +231,7 @@ export function createMcpServer(): McpServer {
230231
"list_commits",
231232
{
232233
description: TOOL_DESCRIPTIONS.list_commits,
234+
annotations: { readOnlyHint: true },
233235
inputSchema: z.object({
234236
repo: z.string().describe("The name of the repository to list commits for."),
235237
query: z.string().describe("Search query to filter commits by message content (case-insensitive).").optional(),
@@ -268,6 +270,7 @@ export function createMcpServer(): McpServer {
268270
"list_repos",
269271
{
270272
description: TOOL_DESCRIPTIONS.list_repos,
273+
annotations: { readOnlyHint: true },
271274
inputSchema: z.object({
272275
query: z.string().describe("Filter repositories by name (case-insensitive)").optional(),
273276
page: z.number().int().positive().describe("Page number for pagination (min 1). Default: 1").optional().default(1),
@@ -308,6 +311,7 @@ export function createMcpServer(): McpServer {
308311
"read_file",
309312
{
310313
description: TOOL_DESCRIPTIONS.read_file,
314+
annotations: { readOnlyHint: true },
311315
inputSchema: {
312316
repo: z.string().describe("The repository name."),
313317
path: z.string().describe("The path to the file."),
@@ -341,6 +345,7 @@ export function createMcpServer(): McpServer {
341345
"list_tree",
342346
{
343347
description: TOOL_DESCRIPTIONS.list_tree,
348+
annotations: { readOnlyHint: true },
344349
inputSchema: {
345350
repo: z.string().describe("The name of the repository to list files from."),
346351
path: z.string().describe("Directory path (relative to repo root). If omitted, the repo root is used.").optional().default(''),
@@ -480,6 +485,7 @@ export function createMcpServer(): McpServer {
480485
"list_language_models",
481486
{
482487
description: TOOL_DESCRIPTIONS.list_language_models,
488+
annotations: { readOnlyHint: true },
483489
},
484490
async () => {
485491
const models = await getConfiguredLanguageModelsInfo();
@@ -491,6 +497,7 @@ export function createMcpServer(): McpServer {
491497
"ask_codebase",
492498
{
493499
description: TOOL_DESCRIPTIONS.ask_codebase,
500+
annotations: { readOnlyHint: true },
494501
inputSchema: z.object({
495502
query: z.string().describe("The query to ask about the codebase."),
496503
repos: z.array(z.string()).optional().describe("The repositories accessible to the agent. If not provided, all repositories are accessible."),

0 commit comments

Comments
 (0)