* fix(sequentialthinking): restore nextThoughtNeeded in the advertised inputSchema required array
commit 1cdf806d (#3533) wrapped nextThoughtNeeded in a z.preprocess-based
coercedBoolean to fix a real footgun (string "false" coercing to true).
zod's toJSONSchema(..., { io: "input" }) treats a z.preprocess()-wrapped
field's input type as unknown, so it silently drops that field from the
emitted required array, even though it carries no .optional(). A client
that builds its call arguments from the advertised schema then omits
nextThoughtNeeded and gets a -32602 Invalid params from the runtime
validator, which still requires it.
Rebuild coercedBoolean as a transform on an explicit z.union([z.boolean(),
z.string()]) instead of z.preprocess. The union gives toJSONSchema a
concrete input type to report, so nextThoughtNeeded stays in required,
while parse behavior (including the case-insensitive string coercion
#3533 added) is unchanged.
Fixes#4651
* test(sequentialthinking): pin nextThoughtNeeded in required and string coercion
Runs against the built server so it checks the schema the SDK emits.
Skips when dist/ is absent, matching server-version.test.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wo28CHPXyM3DHoLWvKnCK
---------
Co-authored-by: olaservo <olahungerford@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(sequentialthinking): read server version from package.json
Replace the hardcoded 0.2.0 so initialize serverInfo tracks the
package version (0.6.2 in-tree; CalVer after release), matching the
approach used for memory server version sync.
Skip the dist-layout assertion when dist/ is absent so CI's
test-before-build job (and local runs without prepare) still pass.
* test(sequentialthinking): smoke stdio initialize serverInfo.version
Spawn the built server over stdio and assert getServerVersion() matches
package.json (and is not the old hardcoded 0.2.0). Skip when dist/ is
absent so test-before-build CI still passes.
* fix(memory): write the knowledge graph atomically
`saveGraph()` wrote the graph straight to the memory file:
await fs.writeFile(this.memoryFilePath, lines.join("\n"));
`fs.writeFile` opens the target with `'w'`, which truncates it before any
new bytes are written. If the process is interrupted between truncation
and completion — SIGKILL, container or host stop, OOM kill, power loss —
the memory file is left empty or half-written.
That file is the only persistence layer for the knowledge graph, so the
window is small but the loss is total and unrecoverable.
The fix writes to a temporary file in the same directory and renames it
over the target. `rename(2)` is atomic on POSIX filesystems: a reader
sees either the complete old file or the complete new one, never an
intermediate state. Keeping the temp file in the same directory ensures
the rename stays on one filesystem, since cross-device renames fail with
EXDEV. On failure the temp file is removed so no strays accumulate.
Tests added in `__tests__/atomic-save.test.ts`:
- the live memory file is never opened for truncating writes
- a committed graph survives a write that fails midway
- no temporary files remain after a successful write
- the temporary file is cleaned up when the rename fails
- graph contents still round-trip correctly across reloads
The first and fourth fail against the previous implementation.
Full suite: 55 passed (50 existing, 5 new). `tsc --noEmit` clean.
Fixes#4614
* fix(memory): use randomBytes for temp filename to avoid concurrent collision
---------
Co-authored-by: teddiesloco <teddiesloco@users.noreply.github.com>
The move_file tool description and README both state the operation fails if
the destination already exists, but the handler called fs.rename directly,
which silently overwrites the destination. Since this server ships no delete
tool, move_file effectively provided unadvertised, irreversible file
deletion: moving any file onto an existing target destroyed the target.
Add a moveFile helper in lib.ts that rejects when the destination already
exists (checked with lstat, so an existing symlink is detected rather than
followed) and only renames when the target is free, and use it from the
move_file handler. Behaviour now matches the documented contract.
Fixes#4628
* fix(filesystem): preserve file permissions during write and edit operations
The atomic write pattern (write temp file + rename) replaces the original
inode, causing the new file to have default 0644 permissions regardless of
what the original file had. This breaks executable scripts and other files
with non-default permissions.
Fix: capture stat.mode before writing and restore it with chmod after rename.
Fixes both writeFileContent() and applyFileEdits().
* fix(filesystem): mask chmod mode and keep chmod failure from failing the write
Pass origStats.mode & 0o777 to chmod instead of the full st_mode. Move
the chmod out of the try whose catch unlinks the temp file and rethrows,
since rename has already succeeded by then. Add a test that an EPERM
from chmod does not reject the write.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wo28CHPXyM3DHoLWvKnCK
---------
Co-authored-by: Dustin Rudiger <drudiger@apple.com>
Co-authored-by: olaservo <olahungerford@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
resources/templates.ts:
- `parseResourceId` had a guard that compared the URI against both
`textUriBase` and `blobUriBase` with `&&`. Those prefixes are mutually
exclusive, so the condition is always false and the branch is dead.
Drop it; the SDK's template-based routing already guarantees the URI
prefix is one of the two before the handler runs. The remaining
positive-integer check on `resourceId` is preserved.
resources/subscriptions.ts:
- `sendSimulatedResourceUpdates` had an `else` branch that called
`subscribers.delete(sessionId)` whenever the session wasn't in a URI's
subscriber set, with a comment claiming the session had disconnected.
That conclusion doesn't follow — a session not subscribed to URI A
can still be subscribed to URI B — and the delete is a no-op when
the element is absent anyway. Remove the branch.
No behavioral change. Existing 95 / 95 tests in `__tests__` still pass.
Co-authored-by: Jia Xuan <1060996408+jiaxuan@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Return '0 B' for negative byte values instead of 'NaN B'.
File sizes cannot be negative, so this is a safe default.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Users hitting the 2.0.0 startup failure land on the README first. Also makes
the three packages qualify for the next release: the release workflow selects
packages by .py/.ts/.md changes, so a dependency-only fix would merge without
publishing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mcp 2.0.0 renamed McpError to MCPError and replaced the low-level Server
decorators with constructor handlers. The three servers had no upper bound,
so a fresh uvx install has resolved 2.0.0 since 2026-07-28 and failed to
start: fetch and time raise ImportError on the McpError import, git raises
AttributeError on server.list_tools().
The floor moves to 1.29.0, the last 1.x release. The previous floors (1.0.0
for git, 1.1.3 for fetch, 1.23.0 for time) resolved versions affected by
GHSA-vj7q-gjh5-988w, GHSA-jpw9-pfvf-9f58, and GHSA-hvrp-rf83-w775.
Porting to the v2 SDK is tracked separately.
Closes#4560Closes#4570Closes#4580
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
Resolves a fresh batch of Dependabot alerts (disclosed 2026-06-15/16,
after PR #4376 merged) across the fetch, git, and time server lockfiles:
- python-multipart 0.0.27 -> 0.0.32 (CVE-2026-53539 HIGH querystring DoS,
plus CVE-2026-53537/53538/53540 parameter smuggling / buffering)
- cryptography 48.0.0 -> 49.0.0 (GHSA-537c-gmf6-5ccf HIGH, vulnerable
OpenSSL statically linked in wheels < 48.0.1)
- pyjwt 2.12.1 -> 2.13.0 (HIGH + medium JWT advisories)
All three are transitive deps; lockfiles only. Diff scoped to exactly
these 9 version bumps with no other resolution churn. git server tests
unchanged vs base (43 passed; 37 pre-existing Windows tempdir errors).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(memory): expose knowledge graph as MCP Resource
Register the knowledge graph as a readable MCP Resource at
memory://knowledge-graph, enabling clients to discover and read
the graph via the Resources protocol without invoking a tool.
Mutation tools (create_entities, create_relations, add_observations,
delete_entities, delete_observations, delete_relations) now emit
notifications/resources/updated so subscribed clients see live
graph changes.
Resource registration is extracted into the exported
registerKnowledgeGraphResource() function so it can be unit-tested
in isolation, mirroring the everything server's registration
pattern. New tests assert registerResource is called with the
correct kebab-case name, URI, mime type, and that the handler
returns the graph as JSON in the contents array.
Addresses PR #3323 review feedback:
- kebab-case resource name per CLAUDE.md style guide
- single-line comment per CLAUDE.md style guide
- title metadata for client discoverability
- resource update notifications after mutations
- test coverage matching everything/registrations.test.ts pattern
- README documents the new Resource
* feat(memory): support resources/subscribe for the knowledge-graph resource
PR #3323 emitted notifications/resources/updated after every mutation but
the server only advertised resources.listChanged, so resources/subscribe
returned -32601 and a spec-compliant client could never actually subscribe.
Declare resources.subscribe, add subscribe/unsubscribe request handlers that
track subscribed URIs, and only emit notifications/resources/updated when the
client has subscribed to that URI.
Co-authored-by: Lazreg Raed <lazregraed222@gmail.com>
---------
Co-authored-by: olaservo <olahungerford@gmail.com>
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>
* url elicitation
* single tool for normal and error path
* Address review feedback on URL elicitation tool
- Remove src/everything/pnpm-lock.yaml (monorepo uses npm workspaces)
- Drop redundant TriggerUrlElicitationSchema.parse(args) in the handler;
destructure directly from args like the other tools, since the SDK
validates against the registered schema before invoking the handler
- Add explicit accept/decline/cancel messaging in the request path,
mirroring trigger-elicitation-request.ts
- Clarify the registerTool count comment in registrations.test.ts
(task-based tools register via registerToolTask, counted separately)
- Add tests covering the no-url / undefined-capability registration
guards, the randomUUID() elicitationId fallback, and the
decline/cancel response paths (100% coverage of the tool)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add tool annotations to trigger-url-elicitation
Match the annotations block on sibling elicitation tools and the
CLAUDE.md guidance. Uses openWorldHint: true since the tool drives an
external browser flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(everything): prevent URL elicitation error-path infinite loop
The trigger-url-elicitation tool's error path (errorPath=true) threw
UrlElicitationRequiredError (-32042) carrying the same URL that failed and
re-threw it on every retry, so clients looped (and tripped their own loop
detection).
- Point the prerequisite elicitation at a different URL
(https://modelcontextprotocol.io) than the failing request.
- Track issued prerequisites by the inputs a client resends on retry
(session + url + caller-supplied elicitationId); a plain retry is then
recognized, ignores errorPath, and proceeds via the request path instead
of re-throwing the prerequisite.
- Update tests and docs accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(everything): address review on URL elicitation error path
- Replace raw NUL byte delimiters in errorPathKey with the \u0000 escape
sequence so the source stays plain text and the diff is reviewable
(same collision-proof delimiter at runtime).
- Add a one-shot test reset helper + beforeEach so the suite is robust to
the module-level marker state regardless of order.
- Use a dedicated prerequisite message instead of reusing the caller's.
- Document the unbounded-growth tradeoff as a demo simplification and note
the one-shot marker semantics in docs/features.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: evalstate <1936278+evalstate@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* url elicitation
* single tool for normal and error path
* Address review feedback on URL elicitation tool
- Remove src/everything/pnpm-lock.yaml (monorepo uses npm workspaces)
- Drop redundant TriggerUrlElicitationSchema.parse(args) in the handler;
destructure directly from args like the other tools, since the SDK
validates against the registered schema before invoking the handler
- Add explicit accept/decline/cancel messaging in the request path,
mirroring trigger-elicitation-request.ts
- Clarify the registerTool count comment in registrations.test.ts
(task-based tools register via registerToolTask, counted separately)
- Add tests covering the no-url / undefined-capability registration
guards, the randomUUID() elicitationId fallback, and the
decline/cancel response paths (100% coverage of the tool)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add tool annotations to trigger-url-elicitation
Match the annotations block on sibling elicitation tools and the
CLAUDE.md guidance. Uses openWorldHint: true since the tool drives an
external browser flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: cliffhall <cliff@futurescale.com>
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
fix(sequential-thinking): use z.coerce for number and safe preprocess for boolean params
Uses z.coerce.number() for number fields and a z.preprocess() helper for boolean fields to handle string-typed parameters from LLM clients. The preprocess approach correctly handles "false" → false, avoiding the z.coerce.boolean() footgun where Boolean("false") === true.
Fixes#3428