Compare commits

...
Author SHA1 Message Date
Nandini-Inuguru
d73f99efbf
fix(memory): serialize graph mutations to prevent concurrent write race (#4555)
Some checks failed
Python / detect-packages (push) Has been cancelled
TypeScript / detect-packages (push) Has been cancelled
Python / Test (push) Has been cancelled
Python / Build (push) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
createEntities, createRelations, addObservations, deleteEntities,
deleteObservations, and deleteRelations each independently did
load -> mutate -> save with no synchronization. Concurrent tool
calls (e.g. multiple mutations dispatched from one LLM turn) could
race: both read the same starting state, both write back their own
copy, and whichever write landed last silently discarded the other's
changes. Interleaved writes could also corrupt the file outright.

Adds an in-process async mutex (KnowledgeGraphManager.withLock) that
serializes all six mutation methods through a single queue. Read-only
methods (readGraph, searchNodes, openNodes) are unaffected.

Verified: reverting the fix and re-running the new concurrency tests
reproduces the bug exactly (lost entities, lost relations, malformed
JSONL lines). With the fix, all 39 tests pass.

Fixes #1819

Co-authored-by: olaservo <olahungerford@gmail.com>
2026-09-02 18:42:26 -07:00
JSap0914
1ec570c27d
fix(memory): skip duplicate entities and relations within a single batch (#4383)
create_entities and create_relations only de-duplicated against the existing
graph, so passing the same entity name (or identical relation) twice in one
call persisted duplicate records. This contradicts the documented behavior
("Ignores entities with existing names" / "Skips duplicate relations") and
breaks the implicit name-uniqueness invariant the rest of the manager relies
on (e.g. addObservations/deleteEntities key on name).

De-duplicate within the input batch as well, keeping the first occurrence.

Co-authored-by: JSap0914 <JSap0914@users.noreply.github.com>
Co-authored-by: olaservo <olahungerford@gmail.com>
2026-09-02 18:38:22 -07:00
fei
f41b666edf
fix(memory): constrain search_nodes query length (#4662)
The search_nodes tool accepted an unbounded query string (per
modelcontextprotocol/servers#3537, official servers should constrain
string parameters). An oversized query costs an O(graph) scan per
call with no value. Cap it at 2048 chars via an exported
SearchNodesQuerySchema, and add vitest coverage for the boundary
(at-limit accepted, over-limit rejected, non-string still rejected).

Signed-off-by: fei <204683769+feiiiiii5@users.noreply.github.com>
2026-09-02 18:35:03 -07:00
Yiheng Zhao
c3d8e43d1f
fix(memory): validate knowledge graph entries when loading from disk (#4717)
loadGraph() trusted the persisted memory file and pushed entities and
relations without validating their fields. A corrupted or legacy entry
(e.g. an entity missing entityType, or an observation that is not a string)
would reach searchNodes and crash with "Cannot read properties of undefined
(reading 'toLowerCase')".

Validate each line against the existing EntitySchema/RelationSchema and skip
malformed entries with a warning, so the in-memory graph only ever contains
well-formed data. Malformed JSON lines are skipped as well.

Fixes #2044
2026-09-02 18:34:58 -07:00
Connor Moss
abbbddb2e3
fix(memory): stop reporting deletions that did not happen (#4738)
delete_entities, delete_observations and delete_relations returned
success: true with a hardcoded "deleted successfully" message regardless
of what matched. An agent that mistypes an entity name is told its memory
is clean while the data is still on disk, and nothing in the response
contradicts that. addObservations throws for the same condition ten lines
above, so the file already disagreed with itself.

Staying quiet is deliberate and documented, so nothing throws and the
output schema is unchanged. The three manager methods now return what
they matched, and the handlers say so. A delete where everything is found
returns the same message it always did.

README updated: the three "Silent operation" bullets described the
absence of an error, which is still true, but read as if the response
said nothing either.
2026-09-02 18:34:53 -07:00
Amey Pawar
649af5856e
fix(memory): expand leading ~ in MEMORY_FILE_PATH to the home directory (#4447)
* fix(memory): expand leading ~ in MEMORY_FILE_PATH to the home directory

MCP clients pass MEMORY_FILE_PATH from JSON config, where no shell
expands a leading "~". Because path.isAbsolute("~/memory.jsonl") is
false, the value was joined onto the package directory, so the server
persisted to a literal "~" folder inside the install instead of the
user's intended location.

Add an expandHome() helper that mirrors the one already used by the
filesystem server (src/filesystem/path-utils.ts) and apply it in
ensureMemoryFilePath() before the existing absolute/relative resolution.
Absolute paths, relative paths, and a "~" not at the start are
unaffected. Adds unit tests for expandHome plus an end-to-end test
through ensureMemoryFilePath.

Addresses #1600

* test(memory): use a literal ~/ path in the tilde expansion test

path.join('~', ...) produces a backslash on Windows, which expandHome does
not match, so the test failed there.

---------

Co-authored-by: olaservo <olahungerford@gmail.com>
2026-09-02 18:15:44 -07:00
Nick Veenhof
242751e8ee
fix(memory): add trailing newline to JSONL output (#3653)
Co-authored-by: Nick Veenhof <nveenhof@gitlab.com>
Co-authored-by: olaservo <olahungerford@gmail.com>
2026-09-02 18:15:35 -07:00
Yufeng He
ba167cef5b
fix: declare zod runtime dependencies (#4289)
Co-authored-by: olaservo <olahungerford@gmail.com>
2026-09-02 18:15:26 -07:00
King Star
48723c95dc
fix(memory): reject dangling relations (#4477)
Signed-off-by: King Star <mcxin.y@gmail.com>
2026-09-02 08:04:34 -07:00
石岳峰
96c49c7364
fix(memory): read serverInfo.version from package.json (#4407)
Fixes #4406. The memory server previously hardcoded serverInfo.version as
"0.6.3", which no longer matched published calendar versions. Resolve
package.json from both source and dist layouts so Docker and npm installs
report the installed package version.

Co-authored-by: syf2211 <syf2211@users.noreply.github.com>
2026-09-02 08:04:28 -07:00
Yusufhan Sacak
cce08de417
fix(memory): resolve Vitest false positive for expected rejection (#3893)
Wrap the addObservations call in an async closure before asserting with
.rejects.toThrow(). This prevents Vitest's global unhandled rejection
handler from capturing the expected error before the assertion can
intercept it, eliminating the false-positive "Unhandled Errors" warning
that was failing CI.

Fixes #3073
2026-09-02 08:03:51 -07:00
Ola Hungerford
2e3e4c7ab7
ci: run readme-pr-check on pull_request_target so fork PRs can be labeled (#4739)
Observed on #4714 and #4726: addLabels returned 403 "Resource not
accessible by integration" because pull_request gives fork PRs a
read-only token. The job only calls the GitHub API and never checks
out PR code, so pull_request_target is safe here.


Claude-Session: https://claude.ai/code/session_01CK8vvB229h6orvkokG8YxU

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 20:01:29 -07:00
Abhinav Prakash
3aa19f5574
fix(git): unify git_log output schema and remove raise_exceptions from server.run (#4658) 2026-09-01 19:25:31 -07:00
Abhinav Prakash
579c3903f3
docs(brave-search): point to official @brave/brave-search-mcp-server package (#4714)
Fixes #4694
2026-08-31 09:22:36 -07:00
Yohanes
a6cdbf4dea
fix(filesystem): reject Windows paths on POSIX (#4704)
Co-authored-by: Yohanes <CryoThrust@users.noreply.github.com>
2026-08-30 14:43:38 -07:00
Trent Apple
ebaccfd623
fix: Containers - Bind Mounts - Security and Compatibility (Docker / Podman / SELinux) for Fetch, Git, and Time Servers (#2205)
* Update Dockerfile for fetch

Initial patch for podman and SELinux contexts.

* Update Dockerfile for time

podman and SELinux

* Update Dockerfile

* Also include app user / group (securely) such that the build succeeds

* Container app user optional add (#1)

* Update Dockerfile git

* Update Dockerfile fetch

* Update Dockerfile time

* Update Dockerfile

* Update Dockerfile

* Update Dockerfile

---------

Co-authored-by: Ola Hungerford <olahungerford@gmail.com>
2026-08-30 13:07:02 -07:00
Riley
cda92bdaac
fix(filesystem): create_directory doesn't create parent directories (#4631)
validatePath()'s ENOENT fallback checked only the immediate parent
directory before allowing a new path through. Creating a path with
more than one missing level (e.g. a/b/c when none of a, b, c exist)
made the immediate parent check fail with "Parent directory does not
exist", even though fs.mkdir was already called with recursive: true
one level up in the create_directory handler and an existing allowed
ancestor was available further up the tree.

Walk up through missing ancestors until an existing one is found (or
the filesystem root is reached), verifying that ancestor still
resolves inside an allowed directory via realpath. The top-level
lexical containment check already ran against the full target path,
so this only extends how far the existence/symlink check climbs.

Fixes #4629

Co-authored-by: Riley <313779891+rileybuilds@users.noreply.github.com>
Co-authored-by: olaservo <olahungerford@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 10:58:19 -07:00
Amir Fathi
d6402efee6
fix(sequentialthinking): restore nextThoughtNeeded in the advertised inputSchema required array (#4652)
* 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>
2026-08-28 10:57:34 -07:00
karl4c
40a8de87ed
fix(sequentialthinking): read server version from package.json (#4576)
* 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.
2026-08-28 10:01:24 -07:00
Excelius
11845d51cb
fix(filesystem): preserve UTF-8 across head and tail chunks (#4667) 2026-08-28 10:01:16 -07:00
KXH
cf05b37694
fix(filesystem): resolve Unicode-equivalent paths (#4638)
Signed-off-by: KXH <shepherdlaurie238@gmail.com>
2026-08-28 10:01:08 -07:00
Teddy100x
642a9112e7
fix(memory): write the knowledge graph atomically (#4642)
* 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>
2026-08-28 10:00:33 -07:00
eeshsaxena
18ce197639
fix(filesystem): make move_file fail instead of overwriting the destination (#4630)
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
2026-08-28 09:58:51 -07:00
drudiger
562feeb281
fix(filesystem): preserve file permissions during write and edit operations (#4115)
* 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>
2026-08-27 19:36:45 -07:00
Ryan Lopopolo
71e3cfbd1e
test(filesystem): add MCP SDK regression coverage for directory_tree (#3245)
* test(filesystem): add directory_tree MCP SDK regression coverage

* docs(filesystem): drop troubleshooting note from README
2026-08-27 19:17:32 -07:00
1060996408
781a93db14
fix(everything): remove dead code in template URI validation and subscription cleanup (#4104)
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>
2026-08-27 19:17:27 -07:00
nulone
8102fbe27a
fix(filesystem): handle negative values in formatSize() (#3231)
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>
2026-08-27 19:17:22 -07:00
Ola Hungerford
599dafc105
Merge pull request #4663 from olaservo/fix/python-cap-mcp-below-2
fix(python): require mcp>=1.29.0,<2 in fetch, git, and time
2026-08-18 09:02:46 -07:00
olaservo
d811ba11f1 docs(python): note the mcp 1.x requirement in fetch, git, and time
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>
2026-08-18 08:57:03 -07:00
olaservo
53dbe83b7a fix(python): require mcp>=1.29.0,<2 in fetch, git, and time
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 #4560
Closes #4570
Closes #4580

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 08:26:30 -07:00
Den Delimarsky
76d64c822f
Merge pull request #4527 from Joosboy/docs-everything-4096-references-formatting
docs(everything): fix markdown formatting and instructions file references
2026-07-29 16:09:46 -07:00
Den Delimarsky
1cd1096d29
Merge pull request #4591 from modelcontextprotocol/dependabot/npm_and_yarn/npm_and_yarn-f26b5a2147
chore(deps): bump @hono/node-server from 1.19.14 to 2.0.12 in the npm_and_yarn group across 1 directory
2026-07-29 15:09:50 -07:00
dependabot[bot]
d8cff7f016
chore(deps): bump @hono/node-server
Bumps the npm_and_yarn group with 1 update in the / directory: [@hono/node-server](https://github.com/honojs/node-server).


Updates `@hono/node-server` from 1.19.14 to 2.0.12
- [Release notes](https://github.com/honojs/node-server/releases)
- [Commits](https://github.com/honojs/node-server/compare/v1.19.14...v2.0.12)

---
updated-dependencies:
- dependency-name: "@hono/node-server"
  dependency-version: 2.0.12
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 22:07:16 +00:00
Den Delimarsky
7112282ce2
Merge pull request #4589 from modelcontextprotocol/dependabot/npm_and_yarn/npm_and_yarn-49aacb125d
chore(deps): bump the npm_and_yarn group across 1 directory with 6 updates
2026-07-29 15:06:23 -07:00
Den Delimarsky
76639ead0c
Merge pull request #4590 from modelcontextprotocol/dependabot/uv/src/git/uv-3eb4f0e641
chore(deps): bump the uv group across 3 directories with 4 updates
2026-07-29 15:05:57 -07:00
dependabot[bot]
3bcfedd8e4
chore(deps): bump the uv group across 3 directories with 4 updates
Bumps the uv group with 3 updates in the /src/git directory: [gitpython](https://github.com/gitpython-developers/GitPython), [mcp](https://github.com/modelcontextprotocol/python-sdk) and [pydantic-settings](https://github.com/pydantic/pydantic-settings).
Bumps the uv group with 2 updates in the /src/time directory: [mcp](https://github.com/modelcontextprotocol/python-sdk) and [pydantic-settings](https://github.com/pydantic/pydantic-settings).
Bumps the uv group with 2 updates in the /src/fetch directory: [mcp](https://github.com/modelcontextprotocol/python-sdk) and [soupsieve](https://github.com/facelessuser/soupsieve).


Updates `gitpython` from 3.1.50 to 3.1.54
- [Release notes](https://github.com/gitpython-developers/GitPython/releases)
- [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES)
- [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.50...3.1.54)

Updates `mcp` from 1.23.0 to 1.28.1
- [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases)
- [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md)
- [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.23.0...v1.28.1)

Updates `pydantic-settings` from 2.12.0 to 2.14.2
- [Release notes](https://github.com/pydantic/pydantic-settings/releases)
- [Commits](https://github.com/pydantic/pydantic-settings/compare/v2.12.0...v2.14.2)

Updates `mcp` from 1.25.0 to 1.28.1
- [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases)
- [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md)
- [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.23.0...v1.28.1)

Updates `pydantic-settings` from 2.12.0 to 2.14.2
- [Release notes](https://github.com/pydantic/pydantic-settings/releases)
- [Commits](https://github.com/pydantic/pydantic-settings/compare/v2.12.0...v2.14.2)

Updates `mcp` from 1.23.0 to 1.28.1
- [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases)
- [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md)
- [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.23.0...v1.28.1)

Updates `soupsieve` from 2.6 to 2.8.4
- [Release notes](https://github.com/facelessuser/soupsieve/releases)
- [Commits](https://github.com/facelessuser/soupsieve/compare/2.6...2.8.4)

---
updated-dependencies:
- dependency-name: gitpython
  dependency-version: 3.1.54
  dependency-type: direct:production
  dependency-group: uv
- dependency-name: mcp
  dependency-version: 1.28.1
  dependency-type: direct:production
  dependency-group: uv
- dependency-name: pydantic-settings
  dependency-version: 2.14.2
  dependency-type: indirect
  dependency-group: uv
- dependency-name: mcp
  dependency-version: 1.28.1
  dependency-type: direct:production
  dependency-group: uv
- dependency-name: pydantic-settings
  dependency-version: 2.14.2
  dependency-type: indirect
  dependency-group: uv
- dependency-name: mcp
  dependency-version: 1.28.1
  dependency-type: direct:production
  dependency-group: uv
- dependency-name: soupsieve
  dependency-version: 2.8.4
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 22:05:20 +00:00
dependabot[bot]
fd8248e56b
chore(deps): bump the npm_and_yarn group across 1 directory with 6 updates
Bumps the npm_and_yarn group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@hono/node-server](https://github.com/honojs/node-server) | `1.19.14` | `2.0.12` |
| [body-parser](https://github.com/expressjs/body-parser) | `2.2.2` | `2.3.0` |
| [brace-expansion](https://github.com/juliangruber/brace-expansion) | `5.0.6` | `5.0.8` |
| [fast-uri](https://github.com/fastify/fast-uri) | `3.1.2` | `3.1.4` |
| [hono](https://github.com/honojs/hono) | `4.12.25` | `4.12.32` |
| [postcss](https://github.com/postcss/postcss) | `8.5.15` | `8.5.25` |



Updates `@hono/node-server` from 1.19.14 to 2.0.12
- [Release notes](https://github.com/honojs/node-server/releases)
- [Commits](https://github.com/honojs/node-server/compare/v1.19.14...v2.0.12)

Updates `body-parser` from 2.2.2 to 2.3.0
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0)

Updates `brace-expansion` from 5.0.6 to 5.0.8
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.6...v5.0.8)

Updates `fast-uri` from 3.1.2 to 3.1.4
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.4)

Updates `hono` from 4.12.25 to 4.12.32
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.25...v4.12.32)

Updates `postcss` from 8.5.15 to 8.5.25
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.15...8.5.25)

---
updated-dependencies:
- dependency-name: "@hono/node-server"
  dependency-version: 2.0.12
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: body-parser
  dependency-version: 2.3.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: brace-expansion
  dependency-version: 5.0.8
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: fast-uri
  dependency-version: 3.1.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: hono
  dependency-version: 4.12.32
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: postcss
  dependency-version: 8.5.25
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 22:04:42 +00:00
Den Delimarsky
ced1408d3a
Merge pull request #4587 from modelcontextprotocol/dependabot/github_actions/astral-sh/setup-uv-7
chore(deps): bump astral-sh/setup-uv from 3 to 7
2026-07-29 15:04:23 -07:00
Den Delimarsky
20b5783d3b
Merge pull request #4586 from modelcontextprotocol/dependabot/github_actions/actions/download-artifact-8
chore(deps): bump actions/download-artifact from 7 to 8
2026-07-29 15:04:09 -07:00
Den Delimarsky
4852bb3fc4
Merge pull request #4585 from modelcontextprotocol/dependabot/github_actions/actions/upload-artifact-7
chore(deps): bump actions/upload-artifact from 6 to 7
2026-07-29 15:03:38 -07:00
Den Delimarsky
10962d3114
Merge pull request #4588 from modelcontextprotocol/dependabot/github_actions/actions/setup-node-7
chore(deps): bump actions/setup-node from 6 to 7
2026-07-29 15:03:19 -07:00
Den Delimarsky
7bdcdb6a85
Merge pull request #4584 from modelcontextprotocol/dependabot/github_actions/actions/setup-python-7
chore(deps): bump actions/setup-python from 6 to 7
2026-07-29 15:03:08 -07:00
dependabot[bot]
fab6abed58
chore(deps): bump actions/setup-node from 6 to 7
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 22:02:15 +00:00
dependabot[bot]
2e15194d4e
chore(deps): bump astral-sh/setup-uv from 3 to 7
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 3 to 7.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/v3...v7)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 22:02:12 +00:00
dependabot[bot]
42a1d825b1
chore(deps): bump actions/download-artifact from 7 to 8
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 22:02:08 +00:00
dependabot[bot]
40ce2f784e
chore(deps): bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 22:02:06 +00:00
dependabot[bot]
31bd12967f
chore(deps): bump actions/setup-python from 6 to 7
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 22:02:03 +00:00
Den Delimarsky
86b9438e0e
Merge pull request #3243 from koic/enable_dependabot
Enable Dependabot
2026-07-29 15:01:28 -07:00
Mahadev Annabhimoju
63df72cb15 docs(everything): fix markdown formatting and instructions file references 2026-07-14 20:05:30 +05:30
Bryan Thompson
d31124c982
fix(filesystem): declare openWorldHint on all tools (#4480)
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>
2026-07-06 10:15:49 -07:00
Bryan Thompson
97b70ee252
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) <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>
2026-07-06 10:15:03 -07:00
Alex Akimov
0e30ad18c7
Merge pull request #4173 from modelcontextprotocol/dependabot/uv/src/fetch/uv-c30c77f42d
chore(deps): bump urllib3 from 2.6.3 to 2.7.0 in /src/fetch in the uv group across 1 directory
2026-07-06 16:24:09 +02:00
Alex Akimov
f7221c4d02
Merge pull request #4429 from orbisai0security/fix-cve-2026-48526-pyjwt
fix: upgrade pyjwt to 2.13.0 (CVE-2026-48526)
2026-07-06 16:22:27 +02:00
Alex Akimov
55d256293f
Merge pull request #4455 from kingrubic/add-pypi-repo-links-3336
Link Python packages to their source directories
2026-07-06 16:20:00 +02:00
Cliff Hall
7097923966
ci(release): switch npm publishing to OIDC trusted publishing (#4466)
ci(release): 4463 / Phase 1 - Switch npm publishing to OIDC trusted publishing
2026-07-04 19:03:24 -04:00
cliffhall
5850690363 ci: don't cancel in-progress CI runs on main
Per-ref cancellation is for superseded branch amend-pushes; on main,
every merge keeps its own CI run so failures stay attributable to a
specific commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 18:44:47 -04:00
cliffhall
3b9d38fbac ci: run CI on pushes to any branch, cancel superseded runs
Previously push-triggered CI ran only on main, so same-repo branches
got no CI until a PR was opened (fork contributions were and remain
covered by pull_request, which fires on open and on every subsequent
push). Run on all pushes, and add a per-ref concurrency group so rapid
amend-pushes cancel superseded runs. Same-repo branches with an open
PR will run under both events; acceptable at this repo's scale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 18:36:43 -04:00
cliffhall
752985790e ci(release): remove the daily release cron; dispatch-only
Releases are now triggered deliberately via workflow_dispatch. The
schedule produced a year of unattended runs stuck awaiting environment
approval, every real release was manually dispatched anyway, and two
runs on the same UTC day collide on the date-based tag. Rename the
workflow to "Release" accordingly and update RELEASING.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 17:22:02 -04:00
cliffhall
18f09adae3 docs(releasing): note per-registry guard placement precisely
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 16:47:52 -04:00
cliffhall
06a8169e6f ci: remove dead release-triggered publish jobs; make RELEASING.md claims true
Review findings: RELEASING.md claimed publishing happens exclusively
from release.yml with no registry tokens, but typescript.yml and
python.yml still carried release:[published] publish jobs — never
fired (releases are created with GITHUB_TOKEN, which suppresses
workflow triggers) and unable to succeed under OIDC (typescript.yml's
still referenced the deleted NPM_TOKEN). Remove both jobs and their
release triggers; the workflows are now pure CI.

Also:
- add skip-existing to the PyPI publish action so re-runs tolerate
  already-uploaded files, and describe the per-registry guard
  accurately in RELEASING.md (npm aborts; PyPI skips)
- correct the fresh-dispatch note: same-day dispatches collide on the
  date-granular tag rather than minting a new version

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 16:45:25 -04:00
cliffhall
a714b63b7f docs(releasing): describe the process as it stands after this PR
RELEASING.md documents the pipeline this PR ships (scheduled/dispatch
CalVer releases, OIDC publishing, retry runbook), with a footer noting
the planned Phase 2 changes tracked in #4463. The doc gets updated
when that work merges, not before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 16:36:41 -04:00
cliffhall
f2450d7fa4 docs(releasing): describe the go-forward release process
Rewrite RELEASING.md as the reference for the target process rather
than a snapshot of the interim state: semver via changesets for
TypeScript / CalVer for Python, versions changed only by PRs on main,
publishing triggered by creating a GitHub Release, per-package
registry-diff guard, OIDC trusted publishing with no registry tokens,
and the failed-publish retry runbook. Implementation tracked in #4463.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 16:32:43 -04:00
cliffhall
9e9dfb95b5 docs(releasing): clarify CalVer is current state, semver for TS is planned
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 16:31:27 -04:00
cliffhall
1c0fdc3063 docs: add RELEASING.md documenting OIDC publishing and retry process
Describes how packages publish (release.yml + release environment,
trusted publishing on npm and PyPI, no registry tokens, provenance),
what a release run does, and how to recover from a failed publish leg
(rerun --failed within the window, or ride the next release). Linked
from the root README alongside the other maintainer docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 16:08:50 -04:00
cliffhall
f4c86ff964 fix(release): count .md changes in package matrix; use --frozen for pytest
Review findings from the PR:
- release.py's has_changes() only counted .py/.ts files, so the README
  touches meant to enroll the four TS packages in the npm matrix would
  have been ignored. Include .md — READMEs ship inside the published
  package (npm tarball / PyPI long_description), so doc changes are
  release-relevant.
- uv run --frozen pytest for consistency with the job's other uv calls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 15:29:44 -04:00
cliffhall
dab3489b73 ci(release): run tests before publishing
Gate both publish jobs on the package's test suite: npm test
--if-present before the npm build/publish, and pytest (when a tests/
or test/ directory exists, matching python.yml's guard) before the
PyPI build/publish. Previously nothing between tagging and publishing
ran a single test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 10:01:31 -04:00
cliffhall
4307b95aee ci(release): enable provenance and pin npm version for OIDC publish
Align with modelcontextprotocol/inspector#1199:
- set NPM_CONFIG_PROVENANCE so published packages get provenance
  attestations
- pin the npm upgrade to ^11.5.1 instead of latest

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AViME1R9ES2UXT1uQDx1TG
2026-07-04 09:43:12 -04:00
cliffhall
1a4ecb7575 ci(release): switch npm publishing to OIDC trusted publishing
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
2026-07-03 20:16:55 -04:00
Bao
a72e93e503 docs: link Python packages to source 2026-07-02 06:39:31 +07:00
Ola Hungerford
b2a94a21a5
fix(deps): bump python-multipart, cryptography, pyjwt to clear HIGH alerts (#4398)
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>
2026-06-29 09:13:57 +02:00
orbisai0security
aa8d20609c fix: CVE-2026-48526 security vulnerability
Automated dependency upgrade by OrbisAI Security
2026-06-28 12:12:32 +00:00
Lazreg Raed
7b1170d1da
feat(memory): expose knowledge graph as MCP Resource (#3323)
* 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>
2026-06-16 18:40:51 -07:00
Cliff Hall
a96189b2af
Merge pull request #4376 from olaservo/fix/server-vulns-2026-06
fix(security): resolve open dependency alerts; harden git_add
2026-06-16 10:45:17 -04:00
olaservo
0588ec098e fix(security): bump vulnerable deps; harden git_add (defense-in-depth)
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>
2026-06-14 13:46:02 -07:00
Cliff Hall
275175cda1
Merge pull request #3816 from galagaevdc/fix/everything-elicitation-http-transport
fix(everything): use relatedTask option to enable elicitation over HTTP (#3228)
2026-06-07 18:29:29 -04:00
Cliff Hall
25e5f3a6e8
fix(everything): prevent URL elicitation error-path infinite loop (#4285)
* 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>
2026-06-06 18:47:20 -07:00
Ola Hungerford
f5054df1fb
fix(deps): bump gitpython and urllib3 to resolve HIGH security alerts (#4283)
- git: gitpython >=3.1.45 -> >=3.1.50 (lock 3.1.49 -> 3.1.50)
  Fixes GHSA-mv93-w799-cj2w: newline injection in config_writer()
  bypasses the CVE-2026-42215 patch, enabling RCE via core.hooksPath.
- fetch: urllib3 2.6.3 -> 2.7.0 (transitive via requests)
  Fixes GHSA-qccp-gfcp-xxvc (sensitive headers forwarded across origins
  on proxied redirects) and GHSA-mf9v-mfxr-j63j (decompression-bomb
  safeguards bypassed in the streaming API).

Resolves Dependabot alerts #129, #131, #132.
Tests pass (fetch: 20 passed; git: all test bodies pass, only
pre-existing Windows tmpdir-teardown errors remain).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 11:24:07 -04:00
shaun smith
3a32d1024b
Add URL Elicitation cases (#3334)
* 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>
2026-06-03 18:10:07 -07:00
dependabot[bot]
040b5a96db
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] <support@github.com>
2026-05-16 15:23:32 +00:00
dhalahayeu
67a71f1b60 fix(everything): use relatedTask option to enable elicitation over HTTP (#3228) 2026-04-22 14:33:01 +02:00
Koichi ITO
e73b9a2f9a Enable Dependabot
Follow-up to https://github.com/modelcontextprotocol/servers/pull/3233.

Configure Dependabot to automatically detect updates to the actions used in
GitHub Actions (e.g., `actions/checkout`) and create pull requests for them.

Based on experience, checking for updates on a weekly interval should work well.
2026-01-24 13:15:29 +09:00
62 changed files with 5348 additions and 3267 deletions

6
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'

View file

@ -2,11 +2,13 @@ name: Python
on:
push:
branches:
- main
pull_request:
release:
types: [published]
# Superseded runs on the same ref are cancelled (e.g. rapid amend-pushes),
# except on main, where every merge keeps its own CI run
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
detect-packages:
@ -34,10 +36,10 @@ jobs:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v7
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version-file: "src/${{ matrix.package }}/.python-version"
@ -71,10 +73,10 @@ jobs:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v7
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version-file: "src/${{ matrix.package }}/.python-version"
@ -91,31 +93,7 @@ jobs:
run: uv build
- name: Upload artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: dist-${{ matrix.package }}
path: src/${{ matrix.package }}/dist/
publish:
runs-on: ubuntu-latest
needs: [build, detect-packages]
if: github.event_name == 'release'
strategy:
matrix:
package: ${{ fromJson(needs.detect-packages.outputs.packages) }}
name: Publish ${{ matrix.package }}
environment: release
permissions:
id-token: write # Required for trusted publishing
steps:
- name: Download artifacts
uses: actions/download-artifact@v7
with:
name: dist-${{ matrix.package }}
path: dist/
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

View file

@ -1,7 +1,10 @@
name: README PR Check
on:
pull_request:
# pull_request_target, not pull_request: fork PRs get a read-only token under
# pull_request, so the label and comment calls fail with 403. This job only
# calls the API and never checks out PR code, so the write token is safe.
pull_request_target:
types: [opened]
paths:
- 'README.md'
@ -10,7 +13,7 @@ on:
jobs:
check-readme-only:
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
permissions:
contents: read

View file

@ -1,9 +1,7 @@
name: Automatic Release Creation
name: Release
on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *'
jobs:
create-metadata:
@ -27,7 +25,7 @@ jobs:
echo "Using last release hash: ${HASH}"
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7
- name: Create version name
id: create-version
@ -43,7 +41,7 @@ jobs:
cat RELEASE_NOTES.md
- name: Release notes
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: release-notes
path: RELEASE_NOTES.md
@ -79,7 +77,7 @@ jobs:
fetch-depth: 0
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7
- name: Update packages
run: |
@ -123,10 +121,10 @@ jobs:
ref: ${{ needs.create-metadata.outputs.version }}
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version-file: "src/${{ matrix.package }}/.python-version"
@ -138,6 +136,15 @@ jobs:
working-directory: src/${{ matrix.package }}
run: uv run --frozen pyright
- name: Run tests
working-directory: src/${{ matrix.package }}
run: |
if [ -d "tests" ] || [ -d "test" ]; then
uv run --frozen pytest
else
echo "No tests directory, skipping"
fi
- name: Build package
working-directory: src/${{ matrix.package }}
run: uv build
@ -146,6 +153,7 @@ jobs:
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: src/${{ matrix.package }}/dist
skip-existing: true # re-runs tolerate already-uploaded files
publish-npm:
needs: [update-packages, create-metadata]
@ -156,18 +164,25 @@ jobs:
package: ${{ fromJson(needs.create-metadata.outputs.npm_packages) }}
name: Build ${{ matrix.package }}
environment: release
permissions:
contents: read
id-token: write # Required for npm trusted publishing (OIDC)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.create-metadata.outputs.version }}
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
registry-url: 'https://registry.npmjs.org'
# OIDC trusted publishing requires npm >=11.5.1; Node 22's bundled npm is 10.x.
- name: Ensure npm CLI supports OIDC trusted publishing
run: npm install -g npm@^11.5.1
- name: Install dependencies
working-directory: src/${{ matrix.package }}
run: npm ci
@ -182,16 +197,22 @@ jobs:
fi
echo "Version $VERSION is new, proceeding with publish"
- name: Run tests
working-directory: src/${{ matrix.package }}
run: npm test --if-present
- name: Build package
working-directory: src/${{ matrix.package }}
run: npm run build
# Authenticates via OIDC trusted publishing (no token) — each package's
# npm trusted publisher is bound to this workflow + the release environment
- name: Publish package
working-directory: src/${{ matrix.package }}
run: |
npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: "true"
create-release:
needs: [update-packages, create-metadata, publish-pypi, publish-npm]
@ -207,7 +228,7 @@ jobs:
- uses: actions/checkout@v6
- name: Download release notes
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: release-notes

View file

@ -2,11 +2,13 @@ name: TypeScript
on:
push:
branches:
- main
pull_request:
release:
types: [published]
# Superseded runs on the same ref are cancelled (e.g. rapid amend-pushes),
# except on main, where every merge keeps its own CI run
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
detect-packages:
@ -32,7 +34,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
@ -55,7 +57,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
@ -68,35 +70,3 @@ jobs:
working-directory: src/${{ matrix.package }}
run: npm run build
publish:
runs-on: ubuntu-latest
needs: [build, detect-packages]
if: github.event_name == 'release'
environment: release
strategy:
matrix:
package: ${{ fromJson(needs.detect-packages.outputs.packages) }}
name: Publish ${{ matrix.package }}
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
working-directory: src/${{ matrix.package }}
run: npm ci
- name: Publish package
working-directory: src/${{ matrix.package }}
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View file

@ -39,7 +39,7 @@ These servers aim to demonstrate MCP features and the official SDKs.
The following reference servers are now archived and can be found at [servers-archived](https://github.com/modelcontextprotocol/servers-archived).
- **[AWS KB Retrieval](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/aws-kb-retrieval-server)** - Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.
- **[Brave Search](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search)** - Web and local search using Brave's Search API. Has been replaced by the [official server](https://github.com/brave/brave-search-mcp-server).
- **[Brave Search](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search)** - Web and local search using Brave's Search API. Has been replaced by the [official server](https://github.com/brave/brave-search-mcp-server) ([`@brave/brave-search-mcp-server`](https://www.npmjs.com/package/@brave/brave-search-mcp-server)).
- **[EverArt](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/everart)** - AI image generation using various models.
- **[GitHub](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/github)** - Repository management, file operations, and GitHub API integration.
- **[GitLab](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/gitlab)** - GitLab API, enabling project management.
@ -145,6 +145,10 @@ See [ADDITIONAL.md](ADDITIONAL.md) for a curated list of frameworks and resource
See [CONTRIBUTING.md](CONTRIBUTING.md) for information about contributing to this repository.
## 📦 Releasing
See [RELEASING.md](RELEASING.md) for how packages are published (OIDC trusted publishing from CI — no registry tokens) and how to retry a failed publish.
## 🔒 Security
See [SECURITY.md](SECURITY.md) for reporting security vulnerabilities.

49
RELEASING.md Normal file
View file

@ -0,0 +1,49 @@
# Releasing
How the packages in this repository are published, and what to do when a publish fails.
## How publishing works
All packages publish exclusively from the [`release.yml`](.github/workflows/release.yml) GitHub Actions workflow, gated by the `release` environment (a required reviewer must approve each deployment). Releases are triggered deliberately by a maintainer via **workflow_dispatch** (Actions → Release → Run workflow, or `gh workflow run release.yml`) — there is no scheduled/automatic release.
**Authentication is OIDC trusted publishing on both registries — there are no registry tokens.**
- **npm** (TypeScript servers): each `@modelcontextprotocol/*` package is registered on npmjs.com with a [trusted publisher](https://docs.npmjs.com/trusted-publishers) bound to this repository, workflow filename `release.yml`, and environment `release` (the binding is case-sensitive). Packages publish with [provenance attestations](https://docs.npmjs.com/generating-provenance-statements).
- **PyPI** (Python servers): published via [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) using `pypa/gh-action-pypi-publish`.
A release run:
1. **Detects changed packages** since the last release tag — a package counts as changed if any `.py`, `.ts`, or `.md` file in its directory changed (READMEs ship inside the published artifacts).
2. **Stamps versions and pushes the release tag** — versions are date-based (CalVer, e.g. `2026.7.4`).
3. **Publishes each changed package as an independent matrix job** (`fail-fast: false` — one package's failure never blocks another). Each job: checkout at the release tag → install → double-publish guard → **run the package's tests** (plus `pyright` for Python) → build → publish. The guard differs by registry: the npm job aborts before tests if the version already exists; for PyPI the skip happens at the publish step itself (`skip-existing` on the upload action).
4. **Creates the GitHub release** with generated notes.
## When a publish fails
A failed matrix leg means that one package didn't publish; everything that succeeded stays published.
**Preferred: re-run the failed jobs on the same run.**
```bash
gh run rerun <run-id> --failed --repo modelcontextprotocol/servers
```
- A re-run is still a `release.yml` run in the `release` environment, so it satisfies the trusted-publisher binding.
- It re-runs only the failed legs, checked out at the original release tag — it publishes exactly the tagged code, and the double-publish guard keeps already-published packages safe.
- It needs a fresh `release` environment approval, and the run must be complete first (approve or reject any pending deployments).
- GitHub's re-run window is ~30 days from the original run, and re-runs execute the *original* workflow snapshot — workflow fixes on `main` don't apply to a re-run.
**Otherwise: let the next release pick it up.** If the re-run window has closed (or the fix required a workflow change), the failed version simply never exists on that registry — that's benign; npm and PyPI version histories don't need to match. The package publishes at the next version, provided it has a qualifying change (`.py`, `.ts`, or `.md`) since the last release tag.
**Never:**
- Publish manually with an npm token or from a laptop — there are no registry tokens, and manual publishes would break the provenance/trust chain.
- Dispatch a fresh `release.yml` run expecting it to retry a failed version — versions are date-granular, so a same-day dispatch collides with the existing tag, and a later dispatch mints a *new* version. Neither retries the failed one.
## Environment approvals
The `release` environment's required-reviewer list is configured in the repository settings (Settings → Environments → `release`). Reviewer rights come only from that list — repository admin does not confer deployment approval.
---
Planned changes to this process — semver via changesets for the TypeScript packages, publishing triggered by manually-created GitHub Releases — are tracked in [#4463](https://github.com/modelcontextprotocol/servers/issues/4463). This document will be updated when that work merges.

3754
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -23,5 +23,9 @@
"@modelcontextprotocol/server-memory": "*",
"@modelcontextprotocol/server-filesystem": "*",
"@modelcontextprotocol/server-sequential-thinking": "*"
},
"overrides": {
"qs": ">=6.15.2",
"hono": ">=4.12.21"
}
}

View file

@ -113,7 +113,9 @@ def has_changes(path: Path, git_hash: GitHash) -> bool:
)
changed_files = [Path(f) for f in output.stdout.splitlines()]
relevant_files = [f for f in changed_files if f.suffix in [".py", ".ts"]]
# .md counts as a change: READMEs ship inside the published package
# (npm tarball / PyPI long_description)
relevant_files = [f for f in changed_files if f.suffix in [".py", ".ts", ".md"]]
return len(relevant_files) >= 1
except subprocess.CalledProcessError:
return False

View file

@ -113,7 +113,7 @@ npm run start:streamableHttp
### Install
```shell
npm install -g @modelcontextprotocol/server-everything@latest
````
```
### Run the default (stdio) server
```shell
@ -134,3 +134,7 @@ npx @modelcontextprotocol/server-everything sse
```shell
npx @modelcontextprotocol/server-everything streamableHttp
```
## License
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.

View file

@ -54,7 +54,7 @@ describe('Registration Index Files', () => {
server: {
getClientCapabilities: vi.fn(() => ({
roots: {},
elicitation: {},
elicitation: { url: {} },
sampling: {},
})),
},
@ -67,14 +67,17 @@ describe('Registration Index Files', () => {
registerConditionalTools(mockServerWithCapabilities);
// Should register 3 conditional tools + 3 task-based tools when all capabilities present
expect(mockServerWithCapabilities.registerTool).toHaveBeenCalledTimes(3);
// Should register 4 conditional tools via registerTool when all capabilities
// are present. Task-based tools register via registerToolTask (counted separately),
// so they are not included in this registerTool count.
expect(mockServerWithCapabilities.registerTool).toHaveBeenCalledTimes(4);
const registeredTools = (
mockServerWithCapabilities.registerTool as any
).mock.calls.map((call: any[]) => call[0]);
expect(registeredTools).toContain('get-roots-list');
expect(registeredTools).toContain('trigger-elicitation-request');
expect(registeredTools).toContain('trigger-url-elicitation');
expect(registeredTools).toContain('trigger-sampling-request');
// Task-based tools are registered via experimental.tasks.registerToolTask

View file

@ -1,4 +1,4 @@
import { describe, it, expect, vi } from 'vitest';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { registerEchoTool, EchoSchema } from '../tools/echo.js';
import { registerGetSumTool } from '../tools/get-sum.js';
@ -13,8 +13,13 @@ import { registerToggleSimulatedLoggingTool } from '../tools/toggle-simulated-lo
import { registerToggleSubscriberUpdatesTool } from '../tools/toggle-subscriber-updates.js';
import { registerTriggerSamplingRequestTool } from '../tools/trigger-sampling-request.js';
import { registerTriggerElicitationRequestTool } from '../tools/trigger-elicitation-request.js';
import {
registerTriggerUrlElicitationTool,
__resetIssuedErrorPathElicitations,
} from '../tools/trigger-url-elicitation.js';
import { registerGetRootsListTool } from '../tools/get-roots-list.js';
import { registerGZipFileAsResourceTool } from '../tools/gzip-file-as-resource.js';
import { registerSimulateResearchQueryTool } from '../tools/simulate-research-query.js';
// Helper to capture registered tool handlers
function createMockServer() {
@ -706,6 +711,318 @@ describe('Tools', () => {
});
});
describe('trigger-url-elicitation', () => {
// The error-path marker is module-level state shared across cases; reset it
// so tests are independent of order and of each other's leftover keys.
beforeEach(() => {
__resetIssuedErrorPathElicitations();
});
it('should not register when client does not support URL elicitation', () => {
const handlers: Map<string, Function> = new Map();
const mockServer = {
registerTool: vi.fn((name: string, config: any, handler: Function) => {
handlers.set(name, handler);
}),
server: {
getClientCapabilities: vi.fn(() => ({ elicitation: { form: {} } })),
},
} as unknown as McpServer;
registerTriggerUrlElicitationTool(mockServer);
expect(mockServer.registerTool).not.toHaveBeenCalled();
});
it('should register when client supports URL elicitation', () => {
const handlers: Map<string, Function> = new Map();
const mockServer = {
registerTool: vi.fn((name: string, config: any, handler: Function) => {
handlers.set(name, handler);
}),
server: {
getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })),
},
} as unknown as McpServer;
registerTriggerUrlElicitationTool(mockServer);
expect(mockServer.registerTool).toHaveBeenCalledWith(
'trigger-url-elicitation',
expect.objectContaining({
title: 'Trigger URL Elicitation Tool',
description: expect.stringContaining('URL elicitation'),
}),
expect.any(Function)
);
});
it('should send URL-mode elicitation request when errorPath is false', async () => {
const handlers: Map<string, Function> = new Map();
const mockSendRequest = vi.fn().mockResolvedValue({
action: 'accept',
});
const mockServer = {
registerTool: vi.fn((name: string, config: any, handler: Function) => {
handlers.set(name, handler);
}),
server: {
getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })),
},
} as unknown as McpServer;
registerTriggerUrlElicitationTool(mockServer);
const handler = handlers.get('trigger-url-elicitation')!;
const result = await handler(
{
url: 'https://example.com/verify',
message: 'Open this page to verify your identity',
elicitationId: 'elicitation-123',
errorPath: false,
},
{ sendRequest: mockSendRequest }
);
expect(mockSendRequest).toHaveBeenCalledWith(
expect.objectContaining({
method: 'elicitation/create',
params: expect.objectContaining({
mode: 'url',
url: 'https://example.com/verify',
message: 'Open this page to verify your identity',
elicitationId: 'elicitation-123',
}),
}),
expect.anything(),
expect.anything()
);
expect(result.content[0].text).toContain(
'✅ User completed the URL elicitation flow.'
);
});
it('should not register when client has no elicitation capability at all', () => {
const mockServer = {
registerTool: vi.fn(),
server: {
getClientCapabilities: vi.fn(() => ({})),
},
} as unknown as McpServer;
registerTriggerUrlElicitationTool(mockServer);
expect(mockServer.registerTool).not.toHaveBeenCalled();
});
it('should not register when client capabilities are undefined', () => {
const mockServer = {
registerTool: vi.fn(),
server: {
getClientCapabilities: vi.fn(() => undefined),
},
} as unknown as McpServer;
registerTriggerUrlElicitationTool(mockServer);
expect(mockServer.registerTool).not.toHaveBeenCalled();
});
it('should default the elicitationId to a random UUID when omitted', async () => {
const handlers: Map<string, Function> = new Map();
const mockSendRequest = vi.fn().mockResolvedValue({
action: 'accept',
});
const mockServer = {
registerTool: vi.fn((name: string, config: any, handler: Function) => {
handlers.set(name, handler);
}),
server: {
getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })),
},
} as unknown as McpServer;
registerTriggerUrlElicitationTool(mockServer);
const handler = handlers.get('trigger-url-elicitation')!;
await handler(
{
url: 'https://example.com/verify',
message: 'Open this page to verify your identity',
errorPath: false,
},
{ sendRequest: mockSendRequest }
);
const sentParams = mockSendRequest.mock.calls[0][0].params;
expect(sentParams.elicitationId).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
);
});
it('should report a declined URL elicitation', async () => {
const handlers: Map<string, Function> = new Map();
const mockSendRequest = vi.fn().mockResolvedValue({ action: 'decline' });
const mockServer = {
registerTool: vi.fn((name: string, config: any, handler: Function) => {
handlers.set(name, handler);
}),
server: {
getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })),
},
} as unknown as McpServer;
registerTriggerUrlElicitationTool(mockServer);
const handler = handlers.get('trigger-url-elicitation')!;
const result = await handler(
{
url: 'https://example.com/verify',
message: 'Open this page to verify your identity',
elicitationId: 'elicitation-123',
errorPath: false,
},
{ sendRequest: mockSendRequest }
);
expect(result.content[0].text).toContain('❌ User declined to open the URL');
});
it('should report a cancelled URL elicitation', async () => {
const handlers: Map<string, Function> = new Map();
const mockSendRequest = vi.fn().mockResolvedValue({ action: 'cancel' });
const mockServer = {
registerTool: vi.fn((name: string, config: any, handler: Function) => {
handlers.set(name, handler);
}),
server: {
getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })),
},
} as unknown as McpServer;
registerTriggerUrlElicitationTool(mockServer);
const handler = handlers.get('trigger-url-elicitation')!;
const result = await handler(
{
url: 'https://example.com/verify',
message: 'Open this page to verify your identity',
elicitationId: 'elicitation-123',
errorPath: false,
},
{ sendRequest: mockSendRequest }
);
expect(result.content[0].text).toContain(
'⚠️ User cancelled the URL elicitation'
);
});
it('should throw MCP error -32042 with a prerequisite elicitation pointing at a different URL when errorPath is true', async () => {
const handlers: Map<string, Function> = new Map();
const mockServer = {
registerTool: vi.fn((name: string, config: any, handler: Function) => {
handlers.set(name, handler);
}),
server: {
getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })),
},
} as unknown as McpServer;
registerTriggerUrlElicitationTool(mockServer);
const handler = handlers.get('trigger-url-elicitation')!;
expect.assertions(5);
try {
await handler(
{
url: 'https://example.com/connect',
message: 'Authorization is required to continue.',
elicitationId: 'elicitation-xyz',
errorPath: true,
},
{}
);
} catch (error: any) {
expect(error.code).toBe(-32042);
const prerequisite = error.data.elicitations[0];
expect(prerequisite.mode).toBe('url');
// The prerequisite must NOT reuse the failing URL, otherwise the client
// would complete it, retry, hit the same error, and loop forever.
expect(prerequisite.url).toBe('https://modelcontextprotocol.io');
expect(prerequisite.url).not.toBe('https://example.com/connect');
// It carries its own elicitation id for the prerequisite itself.
expect(typeof prerequisite.elicitationId).toBe('string');
}
});
it('should ignore errorPath and take the request path when the same call is retried after the prerequisite', async () => {
const handlers: Map<string, Function> = new Map();
const mockSendRequest = vi.fn().mockResolvedValue({ action: 'accept' });
const mockServer = {
registerTool: vi.fn((name: string, config: any, handler: Function) => {
handlers.set(name, handler);
}),
server: {
getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })),
},
} as unknown as McpServer;
registerTriggerUrlElicitationTool(mockServer);
const handler = handlers.get('trigger-url-elicitation')!;
// A real client retries with the *same* arguments and does not echo the
// prerequisite's elicitationId. Note these args omit elicitationId, so the
// correlation must rely on stable inputs (session + url), not a per-call
// random id.
const args = {
url: 'https://example.com/connect',
message: 'Authorization is required to continue.',
errorPath: true,
};
const extra = { sessionId: 'session-1', sendRequest: mockSendRequest };
// First call: error path issues the prerequisite and throws -32042.
let prerequisiteUrl: string | undefined;
try {
await handler(args, extra);
throw new Error('expected first call to throw');
} catch (error: any) {
expect(error.code).toBe(-32042);
prerequisiteUrl = error.data.elicitations[0].url;
expect(prerequisiteUrl).toBe('https://modelcontextprotocol.io');
expect(mockSendRequest).not.toHaveBeenCalled();
}
// Plain retry with identical arguments: errorPath is ignored and the call
// proceeds via the request path instead of throwing the prerequisite again.
const result = await handler({ ...args }, extra);
expect(mockSendRequest).toHaveBeenCalledWith(
expect.objectContaining({
method: 'elicitation/create',
params: expect.objectContaining({
mode: 'url',
url: 'https://example.com/connect',
}),
}),
expect.anything(),
expect.anything()
);
expect(result.content[0].text).toContain(
'✅ User completed the URL elicitation flow.'
);
});
});
describe('get-roots-list', () => {
it('should not register when client does not support roots', () => {
const { mockServer } = createMockServer();
@ -738,6 +1055,90 @@ describe('Tools', () => {
});
});
describe('simulate-research-query', () => {
function createMockServerWithTasks() {
const taskHandlers: Record<string, any> = {};
const mockServer = {
experimental: {
tasks: {
registerToolTask: vi.fn((_name: string, _config: any, handler: any) => {
Object.assign(taskHandlers, handler);
}),
},
},
server: { getClientCapabilities: vi.fn(() => ({ elicitation: {} })) },
} as unknown as McpServer;
return { mockServer, taskHandlers };
}
function createMockTaskStore(taskId: string) {
return {
createTask: vi.fn().mockResolvedValue({
taskId,
status: 'working',
createdAt: new Date().toISOString(),
lastUpdatedAt: new Date().toISOString(),
ttl: 300000,
pollInterval: 1000,
}),
updateTaskStatus: vi.fn().mockResolvedValue(undefined),
storeTaskResult: vi.fn().mockResolvedValue(undefined),
getTask: vi.fn(),
getTaskResult: vi.fn(),
};
}
it('should pass relatedTask to sendRequest when elicitation is triggered', async () => {
vi.useFakeTimers();
const { mockServer, taskHandlers } = createMockServerWithTasks();
registerSimulateResearchQueryTool(mockServer);
const mockTaskStore = createMockTaskStore('task-abc');
const mockSendRequest = vi.fn().mockResolvedValue({
action: 'accept',
content: { interpretation: 'technical' },
});
await taskHandlers.createTask(
{ topic: 'python', ambiguous: true },
{ taskStore: mockTaskStore, sendRequest: mockSendRequest }
);
await vi.runAllTimersAsync();
vi.useRealTimers();
expect(mockSendRequest).toHaveBeenCalledWith(
expect.objectContaining({ method: 'elicitation/create' }),
expect.anything(),
expect.objectContaining({ relatedTask: { taskId: 'task-abc' } })
);
});
it('should complete without elicitation for non-ambiguous query', async () => {
vi.useFakeTimers();
const { mockServer, taskHandlers } = createMockServerWithTasks();
registerSimulateResearchQueryTool(mockServer);
const mockTaskStore = createMockTaskStore('task-def');
const mockSendRequest = vi.fn();
await taskHandlers.createTask(
{ topic: 'python', ambiguous: false },
{ taskStore: mockTaskStore, sendRequest: mockSendRequest }
);
await vi.runAllTimersAsync();
vi.useRealTimers();
expect(mockSendRequest).not.toHaveBeenCalled();
expect(mockTaskStore.storeTaskResult).toHaveBeenCalledWith(
'task-def', 'completed', expect.anything()
);
});
});
describe('gzip-file-as-resource', () => {
it('should compress data URI and return resource link', async () => {
const registeredResources: any[] = [];

View file

@ -19,9 +19,11 @@
- `get-structured-content` (tools/get-structured-content.ts): Demonstrates structured responses. Accepts `location` input and returns both backward‑compatible `content` (a `text` block containing JSON) and `structuredContent` validated by an `outputSchema` (temperature, conditions, humidity).
- `get-sum` (tools/get-sum.ts): For two numbers `a` and `b` calculates and returns their sum. Uses Zod to validate inputs.
- `get-tiny-image` (tools/get-tiny-image.ts): Returns a tiny PNG MCP logo as an `image` content item with brief descriptive text before and after.
- `trigger-long-running-operation` (tools/trigger-trigger-long-running-operation.ts): Simulates a multi-step operation over a given `duration` and number of `steps`; reports progress via `notifications/progress` when a `progressToken` is provided by the client.
- `trigger-long-running-operation` (tools/trigger-long-running-operation.ts): Simulates a multi-step operation over a given `duration` and number of `steps`; reports progress via `notifications/progress` when a `progressToken` is provided by the client.
- `toggle-simulated-logging` (tools/toggle-simulated-logging.ts): Starts or stops simulated, random‑leveled logging for the invoking session. Respects the client’s selected minimum logging level.
- `toggle-subscriber-updates` (tools/toggle-subscriber-updates.ts): Starts or stops simulated resource update notifications for URIs the invoking session has subscribed to.
- `trigger-elicitation-request` (tools/trigger-elicitation-request.ts): Issues an `elicitation/create` request using form-mode fields (strings, numbers, booleans, enums, and format validation) and returns the resulting action/content.
- `trigger-url-elicitation` (tools/trigger-url-elicitation.ts): Issues an `elicitation/create` request in URL mode (`mode: "url"`) with an `elicitationId`, or throws MCP error `-32042` (`UrlElicitationRequiredError`) when `errorPath=true`. On the error path the prerequisite elicitation it returns points at a different URL than the failing request (`https://modelcontextprotocol.io`); when the client satisfies it and retries the same call, the retry ignores `errorPath` and proceeds via the request path, so the client does not loop on the same error. The retry marker is one-shot per `(session, url, elicitationId)`: it is cleared on the recognized retry, so re-running the error path with identical arguments without an intervening prerequisite is treated as a retry and proceeds. Requires client capability `elicitation.url`.
- `trigger-sampling-request` (tools/trigger-sampling-request.ts): Issues a `sampling/createMessage` request to the client/LLM using provided `prompt` and optional generation controls; returns the LLM's response payload.
- `simulate-research-query` (tools/simulate-research-query.ts): Demonstrates MCP Tasks (SEP-1686) with a simulated multi-stage research operation. Accepts `topic` and `ambiguous` parameters. Returns a task that progresses through stages with status updates. If `ambiguous` is true and client supports elicitation, sends an elicitation request directly to gather clarification before completing.
- `trigger-sampling-request-async` (tools/trigger-sampling-request-async.ts): Demonstrates bidirectional tasks where the server sends a sampling request that the client executes as a background task. Server polls for status and retrieves the LLM result when complete. Requires client to support `tasks.requests.sampling.createMessage`.

View file

@ -54,7 +54,7 @@
- `prompts: {}`
- `resources: { subscribe: true }`
- **Server Instructions**
- Loaded from the docs folder (`server-instructions.md`).
- Loaded from the docs folder (`instructions.md`).
- **Registrations**
- Registers **tools** via `registerTools(server)`.
- Registers **resources** via `registerResources(server)`.

View file

@ -55,7 +55,8 @@ src/everything
│ ├── trigger-elicitation-request-async.ts
│ ├── trigger-long-running-operation.ts
│ ├── trigger-sampling-request.ts
│ └── trigger-sampling-request-async.ts
│ ├── trigger-sampling-request-async.ts
│ └── trigger-url-elicitation.ts
└── transports
├── sse.ts
├── stdio.ts
@ -84,9 +85,19 @@ src/everything
### `docs/`
- `architecture.md`
- Describes the server architecture, including transport layers, session initialization, and primitive registration.
- `extension.md`
- Explains how to extend the server with new tools, prompts, resources, or transports.
- `features.md`
- A comprehensive reference listing all implemented prompts, resources, tools, and protocol behaviors.
- `how-it-works.md`
- Explains how SSE and Streamable HTTP transports are set up and how sessions are managed.
- `instructions.md`
- Human‑readable instructions intended to be passed to the client/LLM as guidance on server use. Loaded by the server at startup and returned in the initialize exchange.
- `startup.md`
- Describes the server startup sequence, environment checks, and initialization.
- `structure.md`
- This document.
- `server-instructions.md`
- Human‑readable instructions intended to be passed to the client/LLM as for guidance on server use. Loaded by the server at startup and returned in the "initialize" exchange.
### `prompts/`
@ -154,6 +165,8 @@ src/everything
- Registers a `simulate-research-query` task-based tool that demonstrates the MCP Tasks feature (SEP-1686). Simulates a multi-stage research operation with progress updates. If the query is marked as ambiguous and the client supports elicitation, it pauses mid-execution to request clarification via `elicitation/create`. Uses `server.experimental.tasks.registerToolTask()` with `execution: { taskSupport: "required" }`.
- `trigger-elicitation-request.ts`
- Registers a `trigger-elicitation-request` tool that sends an `elicitation/create` request to the client/LLM and returns the elicitation result.
- `trigger-url-elicitation.ts`
- Registers a `trigger-url-elicitation` tool that either sends an out-of-band URL-mode `elicitation/create` request (`mode: "url"`) including an `elicitationId` (request path) or throws `UrlElicitationRequiredError` (`-32042`) for client-handled URL elicitation (error path). On the error path the carried prerequisite elicitation points at a different URL than the failing one (`https://modelcontextprotocol.io`), and when the client satisfies it and retries the same call, the retry ignores `errorPath` and proceeds via the request path — so the client does not loop on the same error.
- `trigger-elicitation-request-async.ts`
- Registers a `trigger-elicitation-request-async` tool that demonstrates bidirectional MCP tasks for elicitation. Sends an elicitation request with task metadata, then polls the client's `tasks/get` endpoint for completion status before fetching the final result.
- `trigger-sampling-request.ts`

View file

@ -30,7 +30,7 @@
"test": "vitest run --coverage"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"@modelcontextprotocol/sdk": "^1.30.0",
"cors": "^2.8.5",
"express": "^5.2.1",
"jszip": "^3.10.1",
@ -39,10 +39,10 @@
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@vitest/coverage-v8": "^2.1.8",
"@vitest/coverage-v8": "^4.1.8",
"prettier": "^2.8.8",
"shx": "^0.3.4",
"shx": "^0.4.0",
"typescript": "^5.6.2",
"vitest": "^2.1.8"
"vitest": "^4.1.8"
}
}

View file

@ -99,10 +99,9 @@ export const setSubscriptionHandlers = (server: McpServer) => {
/**
* Sends simulated resource update notifications to the subscribed client.
*
* This function iterates through all resource URIs stored in the subscriptions
* and checks if the specified session ID is subscribed to them. If so, it sends
* a notification through the provided server. If the session ID is no longer valid
* (disconnected), it removes the session ID from the list of subscribers.
* Iterates the URIs in `subscriptions` and emits a
* `notifications/resources/updated` notification for each URI the given
* sessionId is subscribed to.
*
* @param {McpServer} server - The server instance used to send notifications.
* @param {string | undefined} sessionId - The session ID of the client to check for subscriptions.
@ -122,8 +121,6 @@ const sendSimulatedResourceUpdates = async (
method: "notifications/resources/updated",
params: { uri },
});
} else {
subscribers.delete(sessionId); // subscriber has disconnected
}
}
};

View file

@ -127,31 +127,26 @@ export const blobResourceUri = (resourceId: number) =>
new URL(`${blobUriBase}/${resourceId}`);
/**
* Parses the resource identifier from the provided URI and validates it
* against the given variables. Throws an error if the URI corresponds
* to an unknown resource or if the resource identifier is invalid.
* Parses the resource identifier from the provided variables and validates
* that it is a positive integer.
*
* @param {URL} uri - The URI of the resource to be parsed.
* @param {Record<string, unknown>} variables - A record containing context-specific variables that include the resourceId.
* @returns {number} The parsed and validated resource identifier as an integer.
* @throws {Error} Throws an error if the URI matches unsupported base URIs or if the resourceId is invalid.
* The SDK only routes URIs that match the registered template to the
* resource handler, so by the time we get here the URI prefix is already
* known to be one of `textUriBase` / `blobUriBase`. Only the resourceId
* variable still needs validating.
*
* @param {URL} uri - The URI of the resource (used in the error message).
* @param {Record<string, unknown>} variables - Context variables including resourceId.
* @returns {number} The parsed and validated resource identifier as a positive integer.
* @throws {Error} If the resourceId is not a finite positive integer.
*/
const parseResourceId = (uri: URL, variables: Record<string, unknown>) => {
const uriError = `Unknown resource: ${uri.toString()}`;
if (
uri.toString().startsWith(textUriBase) &&
uri.toString().startsWith(blobUriBase)
) {
throw new Error(uriError);
} else {
const idxStr = String((variables as any).resourceId ?? "");
const idx = Number(idxStr);
if (Number.isFinite(idx) && Number.isInteger(idx) && idx > 0) {
return idx;
} else {
throw new Error(uriError);
}
const idxStr = String((variables as any).resourceId ?? "");
const idx = Number(idxStr);
if (Number.isFinite(idx) && Number.isInteger(idx) && idx > 0) {
return idx;
}
throw new Error(`Unknown resource: ${uri.toString()}`);
};
/**

View file

@ -17,6 +17,7 @@ import { registerTriggerSamplingRequestTool } from "./trigger-sampling-request.j
import { registerTriggerSamplingRequestAsyncTool } from "./trigger-sampling-request-async.js";
import { registerTriggerElicitationRequestAsyncTool } from "./trigger-elicitation-request-async.js";
import { registerSimulateResearchQueryTool } from "./simulate-research-query.js";
import { registerTriggerUrlElicitationTool } from "./trigger-url-elicitation.js";
/**
* Register the tools with the MCP server.
@ -44,6 +45,7 @@ export const registerTools = (server: McpServer) => {
export const registerConditionalTools = (server: McpServer) => {
registerGetRootsListTool(server);
registerTriggerElicitationRequestTool(server);
registerTriggerUrlElicitationTool(server);
registerTriggerSamplingRequestTool(server);
// Task-based research tool (uses experimental tasks API)
registerSimulateResearchQueryTool(server);

Some files were not shown because too many files have changed in this diff Show more