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) <noreply@anthropic.com>
* 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) <noreply@anthropic.com>
* 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 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The NPM_TOKEN used by publish-npm is dead (npm classic-token sunset) —
both June release attempts failed publishing with a disguised auth error
(E404 on the publish PUT) after PyPI legs succeeded. Each
@modelcontextprotocol/* package's npm trusted publisher is being bound to
this workflow (release.yml) + the release environment, so publish-npm now
authenticates via OIDC instead of a token:
- add id-token: write permission to publish-npm (required to mint the
OIDC token; contents: read added explicitly since setting permissions
drops the defaults)
- upgrade the npm CLI in the publish job (trusted publishing requires
npm >= 11.5.1; Node 22 bundles an older npm)
- drop NODE_AUTH_TOKEN from the publish step
Also touch each TypeScript server's README (add everything's missing
License section; link each package's npm page) so scripts/release.py
includes all four packages in the next npm publish matrix — they've been
stranded on npm at 2026.1.26 while PyPI advanced to 2026.6.16.
See #4463 for the rollout plan.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
Real fixes (deps):
- python (git/fetch/time): idna 3.10->3.18 (GHSA-65pc), starlette
0.49.1->1.3.1 (GHSA-86qp)
- npm: vitest ^2.1.8->^4.1.8 (critical GHSA-5xrq UI server RCE). vitest 4
pulls vite 8 (rolldown, no esbuild), clearing the esbuild/vite advisories
natively. Remaining root overrides: qs>=6.15.2 (GHSA-q8mj, via express),
hono>=4.12.21 (via @modelcontextprotocol/sdk).
- npm audit: 0 vulnerabilities; all TS (313) + Python (git/fetch/time) suites
and TS builds pass.
Hardening (git_add):
- The repo already mitigates CVE-2026-27735 by using repo.git.add (the git
CLI rejects out-of-tree paths, PR #3164's fix), not repo.index.add (which
did not). Add an explicit path-boundary check in git_add as defense in
depth plus regression tests asserting the security invariant (out-of-tree
paths are never staged), guarding against a regression to repo.index.add.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(filesystem): ensure bare Windows drive letters normalize to root
Appends path.sep to bare drive letters (e.g. "C:") before calling path.normalize(), preventing them from normalizing to "C:." (current directory on drive) instead of "C:\" (drive root). Includes test coverage with platform mocking.
Fixes#3418
The project uses Vitest, not Jest. Replace jest.fn() with vi.fn()
to fix the ReferenceError in the relative path resolution test.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
On macOS, /tmp is a symlink to /private/tmp. When users specify /tmp
as an allowed directory, the server was resolving it to /private/tmp
during startup but then rejecting paths like /tmp/file.txt because
they dont start with /private/tmp.
This fix stores BOTH the original normalized path AND the resolved
path in allowedDirectories, so users can access files through either
form. For example, with /tmp as allowed directory, both /tmp/file.txt
and /private/tmp/file.txt will now be accepted.
Fixes#3253
move_file deletes the source file, which is a destructive operation.
Updated annotations and README documentation to reflect this.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, the server would crash if any configured directory was
unavailable (e.g., unmounted external drive). Now it:
- Filters out inaccessible directories with a warning
- Continues operating with remaining accessible directories
- Only fails if NO directories are accessible
Fixes#2815
* fix(filesystem): return string in structuredContent to match outputSchema
The directory_tree, move_file, and list_directory_with_sizes tools were
returning an array in structuredContent.content, but outputSchema declares
content as z.string(). This caused MCP error -32602 validation failures.
Changed structuredContent from { content: [contentBlock] } to { content: text }
to match the declared outputSchema and be consistent with other filesystem
tools like list_directory and search_files.
Fixes#3110, #3106, #3093🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test(filesystem): add integration tests for structuredContent schema compliance
Adds tests to verify that directory_tree, list_directory_with_sizes, and
move_file tools return structuredContent.content as a string (matching
outputSchema) rather than an array.
These tests prevent regression of issues #3110, #3106, #3093.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: adam jones <domdomegg+git@gmail.com>
Adds metadata required for registering servers in the MCP registry:
- NPM servers (everything, filesystem, memory, sequentialthinking):
Added mcpName and repository fields to package.json
- PyPI servers (fetch, git, time):
Added mcp-name comment to README.md
Part of #3047
Update the SDK from ^1.19.1 to ^1.23.0 in all TypeScript reference servers:
- everything
- filesystem
- memory
- sequentialthinking
Also update everything server for SDK breaking changes:
- Update zod to ^3.25.0 (zod v4 compatibility)
- Fix ToolInput/ToolOutput types to use Tool["inputSchema"] instead of
inferring from zod schemas
- Fix result.content handling to support array format
Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com>
**Files touched**
- [src/filesystem/index.ts](../blob/HEAD/src/filesystem/index.ts) — add `annotations` metadata to each tool definition
- [src/filesystem/README.md](../blob/HEAD/src/filesystem/README.md) — document ToolAnnotations mapping for all filesystem tools
## Description
This change adds MCP `ToolAnnotations` (`readOnlyHint`, `idempotentHint`, `destructiveHint`) to all filesystem tools and documents the mapping in the filesystem README. MCP clients can now accurately distinguish read‑only vs. write tools, understand which operations are safe to retry, and highlight potentially destructive actions.
## Server Details
- **Server**: filesystem
- **Area**: tools (metadata returned via `listTools` / `ListToolsRequest`) and server docs
## Motivation and Context
Previously, the filesystem server did not expose ToolAnnotations, so many clients (e.g. ChatGPT Apps) conservatively treated filesystem tools as generic write operations. This led to:
- READ operations being surfaced with WRITE badges and confirmation prompts.
- No way for clients to know which write tools are idempotent or potentially destructive.
This PR aligns the implementation with `servers#2988` and updates the README to clearly document the semantics of each tool. Read‑only operations no longer need to be treated as writes, and destructive/idempotent behavior is explicit for UI and retry logic.
## How Has This Been Tested?
- `npm run build --workspace @modelcontextprotocol/server-filesystem`
- `npm test --workspaces --if-present`
## Breaking Changes
None.
## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [x] Documentation update
## Checklist
- [x] I have read the [MCP Protocol Documentation](https://modelcontextprotocol.io)
- [x] My changes follows MCP security best practices
- [x] I have updated the server's README accordingly
- [x] I have tested this with an LLM client
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [ ] I have documented all environment variables and configuration options
## Additional context
None.
Co-authored-by: Valeriy_Pavlovich <vp@strato.space>
For text-only tool responses, simplify outputSchemas from complex nested
arrays to simple { content: z.string() } format. All tool responses now
include structuredContent matching their outputSchema, fixing MCP protocol
violations when tools had output schemas but no structured content.
This applies to both filesystem and everything servers.
Address two items from Camila's review:
1. Use blob type for non-image/non-audio media files, restoring the
original behavior. This matches the previous implementation which
used blob as the fallback for unknown binary types. Use type
assertion to satisfy the SDK's type constraints.
2. Reuse ReadTextFileArgsSchema.shape in the deprecated read_file tool
instead of redefining the schema inline.
* fix(filesystem): convert to modern TypeScript SDK APIs
Convert the filesystem server to use the modern McpServer API instead
of the low-level Server API.
Key changes:
- Replace Server with McpServer from @modelcontextprotocol/sdk/server/mcp.js
- Convert all 13 tools to use registerTool() instead of manual request handlers
- Use Zod schemas directly in inputSchema/outputSchema
- Add structuredContent to all tool responses
- Fix type literals to use 'as const' assertions
- Update roots protocol handling to use server.server.* pattern
- Fix tsconfig to exclude vitest.config.ts
Tools converted:
- read_file (deprecated)
- read_text_file
- read_media_file
- read_multiple_files
- write_file
- edit_file
- create_directory
- list_directory
- list_directory_with_sizes
- directory_tree
- move_file
- search_files
- get_file_info
- list_allowed_directories
The modern API provides:
- Less boilerplate code
- Better type safety with Zod
- More declarative tool registration
- Cleaner, more maintainable code
* fix: use default import for minimatch
minimatch v10+ uses default export instead of named export
* fix(filesystem): use named import for minimatch
The minimatch module doesn't have a default export, so we need to use
the named import syntax instead.
Fixes TypeScript compilation error:
error TS2613: Module has no default export. Did you mean to use
'import { minimatch } from "minimatch"' instead?
test files were being compiled into dist during build,
causing issues in docker environments. added exclude
pattern to tsconfig to skip __tests__ directory.
fixes#2928
The test was expecting forward slashes to always be converted to backslashes, but normalizePath() only does this on Windows (process.platform === 'win32'). On Linux/Unix, forward slashes are preserved.
Improved the fix by:
1. Removing relative path assertion from the "as is" test since it doesn't match intent
2. Adding a dedicated test that validates platform-specific behavior naturally without mocking
3. Using the actual platform instead of unreliable Object.defineProperty mocking
This approach is more reliable and clearly documents expected behavior per platform.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The afterEach function was used in the test file but not imported from vitest, causing a build failure. This was introduced in PR #2851.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace Jest dependencies with Vitest and @vitest/coverage-v8
- Update test scripts to use 'vitest run --coverage'
- Create vitest.config.ts for both servers with node environment and coverage settings
- Update all test files:
- Change imports from '@jest/globals' to 'vitest'
- Replace jest.mock() with vi.mock()
- Replace jest.fn() with vi.fn()
- Update mock clearing/restoring to use vi methods
- Remove jest.config.cjs files
- All 151 tests passing (24 in sequentialthinking, 127 in filesystem)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes issue where relative paths were incorrectly resolved against process.cwd(),
causing "Access denied - path outside allowed directories" errors when the MCP
server's working directory was outside the configured allowed directories.
The fix implements intelligent path resolution that:
1. First tries to resolve relative paths against each allowed directory
2. Validates the resulting path is within allowed directories
3. Falls back to the first allowed directory if no valid resolution is found
4. Maintains backward compatibility by falling back to process.cwd() when no allowed directories are configured
Resolves#2526🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Ola Hungerford <olaservo@users.noreply.github.com>