From 040b5a96dba7ad586b7b74960b4cbcd0d7681350 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 May 2026 15:23:32 +0000 Subject: [PATCH 1/5] chore(deps): bump urllib3 Bumps the uv group with 1 update in the /src/fetch directory: [urllib3](https://github.com/urllib3/urllib3). Updates `urllib3` from 2.6.3 to 2.7.0 - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.7.0 dependency-type: indirect dependency-group: uv ... Signed-off-by: dependabot[bot] --- src/fetch/uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index 0396b24c48..cde68d0aa4 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -1293,11 +1293,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] From aa8d20609c981265b7963e6f5ac279af1d46c042 Mon Sep 17 00:00:00 2001 From: orbisai0security Date: Sun, 28 Jun 2026 12:12:32 +0000 Subject: [PATCH 2/5] fix: CVE-2026-48526 security vulnerability Automated dependency upgrade by OrbisAI Security --- src/fetch/uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index da02c96b1e..7579563885 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -802,14 +802,14 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.12.1" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, ] [package.optional-dependencies] From a72e93e5030241a8f717604765170b8c9f4da728 Mon Sep 17 00:00:00 2001 From: Bao Date: Thu, 2 Jul 2026 06:39:31 +0700 Subject: [PATCH 3/5] docs: link Python packages to source --- src/fetch/README.md | 2 ++ src/fetch/pyproject.toml | 4 ++++ src/git/README.md | 2 ++ src/git/pyproject.toml | 4 ++++ src/time/README.md | 2 ++ src/time/pyproject.toml | 4 ++++ 6 files changed, 18 insertions(+) diff --git a/src/fetch/README.md b/src/fetch/README.md index 2c3e048927..ed6d2262f4 100644 --- a/src/fetch/README.md +++ b/src/fetch/README.md @@ -4,6 +4,8 @@ A Model Context Protocol server that provides web content fetching capabilities. This server enables LLMs to retrieve and process content from web pages, converting HTML to markdown for easier consumption. +Source: https://github.com/modelcontextprotocol/servers/tree/main/src/fetch + > [!CAUTION] > This server can access local/internal IP addresses and may represent a security risk. Exercise caution when using this MCP server to ensure this does not expose any sensitive data. diff --git a/src/fetch/pyproject.toml b/src/fetch/pyproject.toml index e2d0d38d0c..84735f278a 100644 --- a/src/fetch/pyproject.toml +++ b/src/fetch/pyproject.toml @@ -25,6 +25,10 @@ dependencies = [ "requests>=2.32.3", ] +[project.urls] +Repository = "https://github.com/modelcontextprotocol/servers/tree/main/src/fetch" +Issues = "https://github.com/modelcontextprotocol/servers/issues" + [project.scripts] mcp-server-fetch = "mcp_server_fetch:main" diff --git a/src/git/README.md b/src/git/README.md index c9ec3140be..a252a4a7ba 100644 --- a/src/git/README.md +++ b/src/git/README.md @@ -6,6 +6,8 @@ A Model Context Protocol server for Git repository interaction and automation. This server provides tools to read, search, and manipulate Git repositories via Large Language Models. +Source: https://github.com/modelcontextprotocol/servers/tree/main/src/git + Please note that mcp-server-git is currently in early development. The functionality and available tools are subject to change and expansion as we continue to develop and improve the server. ### Tools diff --git a/src/git/pyproject.toml b/src/git/pyproject.toml index ea46b1cfb2..c5113ae7d4 100644 --- a/src/git/pyproject.toml +++ b/src/git/pyproject.toml @@ -22,6 +22,10 @@ dependencies = [ "pydantic>=2.0.0", ] +[project.urls] +Repository = "https://github.com/modelcontextprotocol/servers/tree/main/src/git" +Issues = "https://github.com/modelcontextprotocol/servers/issues" + [project.scripts] mcp-server-git = "mcp_server_git:main" diff --git a/src/time/README.md b/src/time/README.md index 51107f51de..9adbe19483 100644 --- a/src/time/README.md +++ b/src/time/README.md @@ -4,6 +4,8 @@ A Model Context Protocol server that provides time and timezone conversion capabilities. This server enables LLMs to get current time information and perform timezone conversions using IANA timezone names, with automatic system timezone detection. +Source: https://github.com/modelcontextprotocol/servers/tree/main/src/time + ### Available Tools - `get_current_time` - Get current time in a specific timezone or system timezone. diff --git a/src/time/pyproject.toml b/src/time/pyproject.toml index b498a07b4a..f9be050452 100644 --- a/src/time/pyproject.toml +++ b/src/time/pyproject.toml @@ -23,6 +23,10 @@ dependencies = [ "tzlocal>=5.3.1", ] +[project.urls] +Repository = "https://github.com/modelcontextprotocol/servers/tree/main/src/time" +Issues = "https://github.com/modelcontextprotocol/servers/issues" + [project.scripts] mcp-server-time = "mcp_server_time:main" From 97b70ee25209382fb6a859527dbb5f4cf36eb318 Mon Sep 17 00:00:00 2001 From: Bryan Thompson Date: Mon, 6 Jul 2026 12:15:03 -0500 Subject: [PATCH 4/5] fix(filesystem): return valid MCP content type from read_media_file (#4029) (#4479) * fix(filesystem): return valid MCP content type from read_media_file (#4029) read_media_file returned a content block with type: "blob" for non-image/ non-audio files. "blob" is not a valid MCP content type (the union is text | image | audio | resource_link | resource), so a strict client rejects the result on schema validation. Map the MIME type to a valid block: image/* -> image, audio/* -> audio, and any other file type -> an embedded resource (the spec-correct carrier for arbitrary bytes) with a pathToFileURL-encoded uri. Widen the tool's outputSchema to the matching union and drop the now-unnecessary `as unknown as CallToolResult` cast so tsc enforces the returned shape. Tests cover the image, audio, and resource content types, verify the base64 blob round-trips to the original bytes, and assert the resource uri is percent-encoded (space + non-ASCII) rather than a raw file:// concatenation. Co-Authored-By: Claude Opus 4.8 (1M context) * test(filesystem): assert structuredContent mirrors content in read_media_file tests The SDK validates content and structuredContent independently against the outputSchema union, so a refactor that lets the two drift apart (e.g. a stale image block in structuredContent while content carries the resource) stays schema-valid and every test stays green. Mirror-equality assertions on all four callTool cases close that gap (mutation-verified: a schema-valid drifted structuredContent fails exactly these four assertions). Also drops a vacuous .not.toBe('blob') assertion that could never add signal, noting where the real "never blob" guard actually lives (the SDK client's CallToolResultSchema rejection, upstream of any local assertion). Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Opus 4.8 (1M context) --- src/filesystem/README.md | 6 +- .../__tests__/structured-content.test.ts | 123 ++++++++++++++++++ src/filesystem/index.ts | 50 ++++--- 3 files changed, 161 insertions(+), 18 deletions(-) diff --git a/src/filesystem/README.md b/src/filesystem/README.md index 76cabd0cce..219f39b9be 100644 --- a/src/filesystem/README.md +++ b/src/filesystem/README.md @@ -78,10 +78,12 @@ The server's directory access control follows this flow: - Cannot specify both `head` and `tail` simultaneously - **read_media_file** - - Read an image or audio file + - Read a file and return it as a base64-encoded content block with its MIME type - Inputs: - `path` (string) - - Streams the file and returns base64 data with the corresponding MIME type + - Streams the file and returns base64 data with the corresponding MIME type. Image and + audio files are returned as `image`/`audio` content; any other file type is returned as + an embedded `resource` (a valid MCP content block for arbitrary binary data) - **read_multiple_files** - Read multiple files simultaneously diff --git a/src/filesystem/__tests__/structured-content.test.ts b/src/filesystem/__tests__/structured-content.test.ts index 4b8f92b0a3..4605b72a8f 100644 --- a/src/filesystem/__tests__/structured-content.test.ts +++ b/src/filesystem/__tests__/structured-content.test.ts @@ -5,6 +5,7 @@ import * as os from 'os'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { spawn } from 'child_process'; +import { fileURLToPath } from 'url'; /** * Integration tests to verify that tool handlers return structuredContent @@ -155,4 +156,126 @@ describe('structuredContent schema compliance', () => { expect(Array.isArray(structuredContent.content)).toBe(false); }); }); + + // read_media_file must return a VALID MCP content block. Previously it emitted + // type: "blob" for non-image/audio files, which is not in the MCP content-block + // union (text | image | audio | resource_link | resource) and a strict client + // rejects on schema validation. See issue #4029. + describe('read_media_file (issue #4029)', () => { + // Image/audio inputs already returned valid content types before #4029 (only the + // non-media fallback was the invalid "blob"), so these two cases are "still-works" + // coverage — they round-trip the bytes to catch an encoding regression on the media + // paths. The resource + café cases below are the actual #4029 regression guards + // (they fail against the pre-fix "blob" build). + it('returns type "image" for image files, round-tripping the bytes', async () => { + const pngBytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); + const pngPath = path.join(testDir, 'pixel.png'); + // Contents aren't validated by the tool (MIME is by extension), so arbitrary bytes are fine. + await fs.writeFile(pngPath, pngBytes); + + const result = await client.callTool({ + name: 'read_media_file', + arguments: { path: pngPath } + }); + + const content = result.content as Array<{ type: string; data?: string; mimeType?: string }>; + expect(Array.isArray(content)).toBe(true); + expect(content).toHaveLength(1); + expect(content[0].type).toBe('image'); + expect(content[0].mimeType).toBe('image/png'); + expect(Buffer.from(content[0].data!, 'base64').equals(pngBytes)).toBe(true); + // structuredContent must MIRROR content. The SDK validates each field independently + // against the outputSchema union (either arm is valid for either field), so schema + // validation alone cannot catch the two drifting apart — only this equality can. + expect(result.structuredContent).toEqual({ content }); + }); + + it('returns type "audio" for audio files, round-tripping the bytes', async () => { + const mp3Bytes = Buffer.from([0x49, 0x44, 0x33, 0x04]); + const mp3Path = path.join(testDir, 'clip.mp3'); + await fs.writeFile(mp3Path, mp3Bytes); + + const result = await client.callTool({ + name: 'read_media_file', + arguments: { path: mp3Path } + }); + + const content = result.content as Array<{ type: string; data?: string; mimeType?: string }>; + expect(content[0].type).toBe('audio'); + expect(content[0].mimeType).toBe('audio/mpeg'); + expect(Buffer.from(content[0].data!, 'base64').equals(mp3Bytes)).toBe(true); + expect(result.structuredContent).toEqual({ content }); // must mirror content (see image case) + }); + + it('returns an embedded resource (never "blob") for non-media binaries, round-tripping the bytes', async () => { + const binBytes = Buffer.from([0x00, 0x01, 0x02, 0x03, 0xff, 0xfe]); + const binPath = path.join(testDir, 'data.bin'); + await fs.writeFile(binPath, binBytes); + + const result = await client.callTool({ + name: 'read_media_file', + arguments: { path: binPath } + }); + + const content = result.content as Array<{ + type: string; + resource?: { uri: string; mimeType: string; blob: string }; + }>; + expect(content).toHaveLength(1); + // ("never blob" is enforced upstream of any assertion here: the SDK client's + // CallToolResultSchema parse rejects a blob block before content is even inspected, + // failing the callTool above — which is exactly how this test fails pre-fix.) + expect(content[0].type).toBe('resource'); + expect(content[0].resource).toBeDefined(); + expect(content[0].resource!.uri.startsWith('file://')).toBe(true); + // Decode the uri back to a path and confirm it is the file actually read — guards a + // refactor that builds the uri from the wrong variable while still emitting a + // well-formed file:// string. (realpath handles the /tmp -> /private/tmp macOS alias.) + expect(fileURLToPath(content[0].resource!.uri)).toBe(await fs.realpath(binPath)); + expect(content[0].resource!.mimeType).toBe('application/octet-stream'); + // The base64 blob must round-trip to the original bytes (data integrity, not just a valid + // shape) — this also subsumes a non-empty check. + expect(Buffer.from(content[0].resource!.blob, 'base64').equals(binBytes)).toBe(true); + expect(result.structuredContent).toEqual({ content }); // must mirror content (see image case) + }); + + it('percent-encodes spaces and non-ASCII chars in the resource uri (pathToFileURL, not a raw file:// concatenation)', async () => { + const fancyPath = path.join(testDir, 'my café.bin'); + await fs.writeFile(fancyPath, Buffer.from([0x10, 0x20, 0x30])); + + const result = await client.callTool({ + name: 'read_media_file', + arguments: { path: fancyPath } + }); + + const content = result.content as Array<{ type: string; resource?: { uri: string } }>; + expect(content[0].type).toBe('resource'); + const uri = content[0].resource!.uri; + // pathToFileURL percent-encodes the space (%20) and the non-ASCII "é" (%C3%A9 for NFC, + // or the base "e" + %CC%81 for the NFD form some filesystems store); a raw + // `file://${validPath}` (as in PR #4044) would leave both literal. + expect(uri).toContain('%20'); + expect(uri).not.toContain(' '); + // The non-ASCII "é" must be percent-encoded: %C3%A9 (NFC) or base-"e" + %CC%81 (the NFD + // form some filesystems decompose to). This regex — not a `.not.toContain('é')`, which is + // vacuous on an NFD runner where the precomposed char never appears — is the load-bearing + // assertion that distinguishes pathToFileURL from a raw `file://${path}` concatenation. + expect(uri).toMatch(/%C3%A9|%CC%81/); + expect(result.structuredContent).toEqual({ content }); // must mirror content (see image case) + }); + + it('advertises the widened outputSchema union via tools/list (both branches serialize)', async () => { + const { tools } = await client.listTools(); + const tool = tools.find((t) => t.name === 'read_media_file'); + expect(tool).toBeDefined(); + // The union outputSchema must serialize to JSON Schema — a DIFFERENT SDK path + // (toJsonSchemaCompat / tools-list) than the callTool structuredContent validation the + // other cases exercise — with BOTH branches present. Guards a nested-union-in-array + // serialization quirk that could drop the resource arm from the advertised schema. + const schemaStr = JSON.stringify(tool!.outputSchema); + expect(schemaStr).toMatch(/resource/); + expect(schemaStr).toMatch(/image/); + expect(schemaStr).toMatch(/audio/); + }); + }); }); diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 7b67e63e58..25ccfc7c36 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -3,13 +3,13 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { - CallToolResult, RootsListChangedNotificationSchema, type Root, } from "@modelcontextprotocol/sdk/types.js"; import fs from "fs/promises"; import { createReadStream } from "fs"; import path from "path"; +import { pathToFileURL } from "url"; import { z } from "zod"; import { minimatch } from "minimatch"; import { normalizePath, expandHome } from './path-utils.js'; @@ -250,17 +250,29 @@ server.registerTool( { title: "Read Media File", description: - "Read an image or audio file. Returns the base64 encoded data and MIME type. " + - "Only works within allowed directories.", + "Read a file and return it as a base64-encoded content block with its MIME type. " + + "Image and audio files are returned as image/audio content; any other file type is " + + "returned as an embedded resource. Only works within allowed directories.", inputSchema: { path: z.string() }, outputSchema: { - content: z.array(z.object({ - type: z.enum(["image", "audio", "blob"]), - data: z.string(), - mimeType: z.string() - })) + content: z.array(z.union([ + z.object({ + type: z.enum(["image", "audio"]), + data: z.string(), + mimeType: z.string() + }), + z.object({ + type: z.literal("resource"), + resource: z.object({ + uri: z.string(), + // Optional, matching the SDK's BlobResourceContents shape (the handler always sets it). + mimeType: z.string().optional(), + blob: z.string() + }) + }) + ])) }, annotations: { readOnlyHint: true } }, @@ -283,17 +295,23 @@ server.registerTool( const mimeType = mimeTypes[extension] || "application/octet-stream"; const data = await readFileAsBase64Stream(validPath); - const type = mimeType.startsWith("image/") - ? "image" - : mimeType.startsWith("audio/") - ? "audio" - // Fallback for other binary types, not officially supported by the spec but has been used for some time - : "blob"; - const contentItem = { type: type as 'image' | 'audio' | 'blob', data, mimeType }; + // Map the MIME type to a valid MCP content block. The spec only allows + // text, image, audio, resource_link, and resource — so non-image/audio + // binaries are returned as an embedded resource (NOT type:"blob", which the + // SDK content-block union rejects on schema validation). + const contentItem = + mimeType.startsWith("image/") + ? { type: "image" as const, data, mimeType } + : mimeType.startsWith("audio/") + ? { type: "audio" as const, data, mimeType } + : { + type: "resource" as const, + resource: { uri: pathToFileURL(validPath).href, mimeType, blob: data } + }; return { content: [contentItem], structuredContent: { content: [contentItem] } - } as unknown as CallToolResult; + }; } ); From d31124c982401739917fd817c2a59db344529c16 Mon Sep 17 00:00:00 2001 From: Bryan Thompson Date: Mon, 6 Jul 2026 12:15:49 -0500 Subject: [PATCH 5/5] fix(filesystem): declare openWorldHint on all tools (#4480) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every filesystem tool operates only on the local filesystem within its allowed directories — none reaches an open or external world — but none declared the MCP `openWorldHint` annotation, leaving open-world-hint coverage at 0/14. Set `openWorldHint: false` on all 14 tools and document it in the README's tool annotations section. Co-authored-by: Claude Opus 4.8 (1M context) --- src/filesystem/README.md | 3 ++- src/filesystem/index.ts | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/filesystem/README.md b/src/filesystem/README.md index 219f39b9be..5a7ffe791d 100644 --- a/src/filesystem/README.md +++ b/src/filesystem/README.md @@ -187,6 +187,7 @@ on each tool so clients can: - Distinguish **read‑only** tools from write‑capable tools. - Understand which write operations are **idempotent** (safe to retry with the same arguments). - Highlight operations that may be **destructive** (overwriting or heavily mutating data). +- Signal that a tool does **not** reach an open or external world (every filesystem tool sets `openWorldHint: false`). The mapping for filesystem tools is: @@ -206,7 +207,7 @@ The mapping for filesystem tools is: | `edit_file` | `false` | `false` | `true` | Re‑applying edits can fail or double‑apply | | `move_file` | `false` | `false` | `true` | Deletes source file | -> Note: `idempotentHint` and `destructiveHint` are meaningful only when `readOnlyHint` is `false`, as defined by the MCP spec. +> Note: `idempotentHint` and `destructiveHint` are meaningful only when `readOnlyHint` is `false`, as defined by the MCP spec. Every tool also sets `openWorldHint: false` — this server only accesses the local filesystem within its allowed directories, never an open or external world. ## Usage with Claude Desktop Add this to your `claude_desktop_config.json`: diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 25ccfc7c36..234605bb13 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -217,7 +217,7 @@ server.registerTool( description: "Read the complete contents of a file as text. DEPRECATED: Use read_text_file instead.", inputSchema: ReadTextFileArgsSchema.shape, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, openWorldHint: false } }, readTextFileHandler ); @@ -240,7 +240,7 @@ server.registerTool( head: z.number().optional().describe("If provided, returns only the first N lines of the file") }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, openWorldHint: false } }, readTextFileHandler ); @@ -274,7 +274,7 @@ server.registerTool( }) ])) }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -331,7 +331,7 @@ server.registerTool( .describe("Array of file paths to read. Each path must be a string pointing to a valid file within allowed directories.") }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, openWorldHint: false } }, async (args: z.infer) => { const results = await Promise.all( @@ -367,7 +367,7 @@ server.registerTool( content: z.string() }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: true } + annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -397,7 +397,7 @@ server.registerTool( dryRun: z.boolean().default(false).describe("Preview changes using git-style diff format") }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true } + annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -422,7 +422,7 @@ server.registerTool( path: z.string() }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false } + annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -448,7 +448,7 @@ server.registerTool( path: z.string() }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -477,7 +477,7 @@ server.registerTool( sortBy: z.enum(["name", "size"]).optional().default("name").describe("Sort entries by name or size") }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -556,7 +556,7 @@ server.registerTool( excludePatterns: z.array(z.string()).optional().default([]) }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, openWorldHint: false } }, async (args: z.infer) => { interface TreeEntry { @@ -626,7 +626,7 @@ server.registerTool( destination: z.string() }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true } + annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false } }, async (args: z.infer) => { const validSourcePath = await validatePath(args.source); @@ -657,7 +657,7 @@ server.registerTool( excludePatterns: z.array(z.string()).optional().default([]) }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -683,7 +683,7 @@ server.registerTool( path: z.string() }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -709,7 +709,7 @@ server.registerTool( "before trying to access files.", inputSchema: {}, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, openWorldHint: false } }, async () => { const text = `Allowed directories:\n${allowedDirectories.join('\n')}`;