Skip to content

fix(streamable_http): restore session from store on notif/resp registry miss (multi-instance) - #221

Closed
mariamaman wants to merge 89 commits into
zoedsoupe:mainfrom
mollowai:mollow/mcp-notif-resp-store-restore
Closed

mariamaman wants to merge 89 commits into
zoedsoupe:mainfrom
mollowai:mollow/mcp-notif-resp-store-restore

Conversation

@mariamaman

Copy link
Copy Markdown
Contributor

Problem

In a horizontally scaled (multi-instance) Streamable-HTTP deployment where nodes are not clustered (no distributed Erlang between instances — common on Cloud Run / serverless), MCP session state held in Registry.Local (node-local ETS) is invisible to other instances.

The request path already handles this: handle_request_message/5 → find_or_create_session/3, which on a registry miss starts a session and, when a Session.Store is configured, restores it via maybe_restore_from_store/1.

But handle_notification_message/5 and handle_response_message/5 call find_session/2 (registry only) and return 404 "Session not found" on a miss — they never consult the configured Session.Store.

Observed failure

  1. initialize (no session id) → instance A creates + persists the session, returns mcp-session-id.
  2. notifications/initialized (session id present) → load-balanced to instance B → registry miss → 404, notification lost.

This is intermittent and instance-correlated, and it breaks clients (e.g. the claude.ai MCP connector) that treat the handshake-notification 404 as a fatal transport error — even though a Session.Store (Redis/DB) is configured.

Fix

Make handle_notification_message and handle_response_message resolve-or-restore from the store on a registry miss, mirroring what the request path already does. When no store is configured (single node), behaviour is unchanged: a registry miss stays a 404.

The patch adds find_or_restore_session/2: on a registry miss, if the session exists in Anubis.get_session_store_adapter(), restore it via the existing start_and_auto_initialize_session/2; otherwise keep the 404 (so genuinely-missing sessions still 404 rather than being masked).

Notes

  • Small, localized change in streamable_http/plug.ex; reuses existing start_and_auto_initialize_session/2.
  • Pairs naturally with the Session.Store docs, which already recommend a store for multi-node deployments — this closes the gap where the store isn't consulted on the notif/resp paths.

Branch/commit: mollowai/anubis-mcp@mollow/mcp-notif-resp-store-restore (based on v1.6.1).

emilwojtaszek and others added 30 commits November 24, 2025 10:57
Co-authored-by: Claude <noreply@anthropic.com>
Bumps [plug](https://github.com/elixir-plug/plug) from 1.18.1 to 1.19.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/elixir-plug/plug/blob/main/CHANGELOG.md">plug's
changelog</a>.</em></p>
<blockquote>
<h2>v1.19.0 (2025-12-08)</h2>
<p>This release requires Elixir v1.14+ and it bumps the recommended
:strong and :compatible SSL/TLS ciphers suite to align with modern
security standards, prioritizing TLS 1.3 and 1.2. Support for the
insecure TLS 1.0 and 1.1 protocols are removed in accordance with RFC
8996.</p>
<h3>Enhancements</h3>
<ul>
<li>[Plug.Router] Allow colon for named segments to be escaped</li>
<li>[Plug.SSL] Prioritize TLS 1.3 and 1.2 ciphers</li>
<li>[Plug.SSL] Allow excluding redirects based on hosts, paths, or the
connection</li>
<li>[Plug.Static] Add <code>:raise_on_missing_only</code></li>
<li>[Plug.Upload] Partition the uploader to improve performance</li>
<li>[Plug.Upload] Add API for deleting files</li>
</ul>
<h3>Deprecations</h3>
<ul>
<li>[Plug.Conn.Adapter] Deprecate <code>:owner</code> field</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/elixir-plug/plug/commits">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=plug&package-manager=hex&previous-version=1.18.1&new-version=1.19.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [styler](https://github.com/adobe/elixir-styler) from 1.9.1 to
1.10.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/adobe/elixir-styler/releases">styler's
releases</a>.</em></p>
<blockquote>
<h2>v1.10.0</h2>
<h3>Improvements</h3>
<p>Two new standard-library pipe optimizations</p>
<ul>
<li><code>enum |&gt; Enum.map(fun) |&gt;
Enum.intersperse(separator)</code> =&gt;
<code>Enum.map_intersperse(enum, separator, fun)</code></li>
<li><code>enum |&gt; Enum.sort() |&gt; Enum.reverse()</code> =&gt;
<code>Enum.sort(enum, :desc)</code></li>
</ul>
<p>And Req (the http client library) pipe optimizations, as detailed
below</p>
<h4>Req pipe optimizations</h4>
<p><a href="https://github.com/wojtekmach/req">Req</a> is a popular HTTP
Client. If you aren't using it, you can just ignore this whole
section!</p>
<p>Reqs 1-arity &quot;execute the request&quot; functions (<code>delete
get head patch post put request run</code>) have a 2-arity version that
takes a superset of the arguments <code>Req.new/1</code> does as its
first argument, and the typical <code>options</code> keyword list as its
second argument. And so, many places developers are calling a 1-arity
function can be replaced with a 2-arity function.</p>
<p>More succinctly, these two statements are equivalent:</p>
<ul>
<li><code>foo |&gt; Req.new() |&gt; Req.merge(bar) |&gt;
Req.post!()</code></li>
<li><code>Req.post!(foo, bar)</code></li>
</ul>
<p>Styler now rewrites the former to the latter, since &quot;less is
more&quot; or &quot;code is a liability&quot;.</p>
<p>It also rewrites <code>|&gt; Keyword.merge(bar) |&gt;
Req.foo()</code> to <code>|&gt; Req.foo(bar)</code>. <strong>This
changes the program's behaviour</strong>, since
<code>Keyword.merge</code> would overwrite existing values in all cases,
whereas <code>Req</code> 2-arity functions intelligently deep-merge
values for some keys, like <code>:headers</code>.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/adobe/elixir-styler/blob/main/CHANGELOG.md">styler's
changelog</a>.</em></p>
<blockquote>
<h2>1.10.0</h2>
<h3>Improvements</h3>
<p>Two new standard-library pipe optimizations</p>
<ul>
<li><code>enum |&gt; Enum.map(fun) |&gt;
Enum.intersperse(separator)</code> =&gt;
<code>Enum.map_intersperse(enum, separator, fun)</code></li>
<li><code>enum |&gt; Enum.sort() |&gt; Enum.reverse()</code> =&gt;
<code>Enum.sort(enum, :desc)</code></li>
</ul>
<p>And Req (the http client library) pipe optimizations, as detailed
below</p>
<h4>Req pipe optimizations</h4>
<p><a href="https://github.com/wojtekmach/req">Req</a> is a popular HTTP
Client. If you aren't using it, you can just ignore this whole
section!</p>
<p>Reqs 1-arity &quot;execute the request&quot; functions (<code>delete
get head patch post put request run</code>) have a 2-arity version that
takes a superset of the arguments <code>Req.new/1</code> does as its
first argument, and the typical <code>options</code> keyword list as its
second argument. And so, many places developers are calling a 1-arity
function can be replaced with a 2-arity function.</p>
<p>More succinctly, these two statements are equivalent:</p>
<ul>
<li><code>foo |&gt; Req.new() |&gt; Req.merge(bar) |&gt;
Req.post!()</code></li>
<li><code>Req.post!(foo, bar)</code></li>
</ul>
<p>Styler now rewrites the former to the latter, since &quot;less is
more&quot; or &quot;code is a liability&quot;.</p>
<p>It also rewrites <code>|&gt; Keyword.merge(bar) |&gt;
Req.foo()</code> to <code>|&gt; Req.foo(bar)</code>. <strong>This
changes the program's behaviour</strong>, since
<code>Keyword.merge</code> would overwrite existing values in all cases,
whereas <code>Req</code> 2-arity functions intelligently deep-merge
values for some keys, like <code>:headers</code>.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/adobe/elixir-styler/commit/70b50451a8ded14ed8364ba26a5958f05220446e"><code>70b5045</code></a>
v1.10.0</li>
<li><a
href="https://github.com/adobe/elixir-styler/commit/2af7d19948f2bfd78780ec38896086daebf048e1"><code>2af7d19</code></a>
Enum.map |&gt; Enum.intersperse =&gt; Enum.map_intersperse</li>
<li><a
href="https://github.com/adobe/elixir-styler/commit/7884561a1294c3fbe36689c40851bab444cda078"><code>7884561</code></a>
allow docs for Styler.string_to_ast</li>
<li><a
href="https://github.com/adobe/elixir-styler/commit/a490ad68bce097da65b0258f850a702035238c87"><code>a490ad6</code></a>
sort |&gt; reverse =&gt; sort(:desc)</li>
<li><a
href="https://github.com/adobe/elixir-styler/commit/78ced6b4dc2c72df34dbb44973555bc8dfcf3e36"><code>78ced6b</code></a>
TIL capital sigils cant be escaped</li>
<li><a
href="https://github.com/adobe/elixir-styler/commit/1a6a375a6fc2148f778d5f9da04b8f5b6bbcc5b1"><code>1a6a375</code></a>
tweak intro sentence</li>
<li><a
href="https://github.com/adobe/elixir-styler/commit/e48ca6cada3d7cd90b624609827ef0af26bf96fe"><code>e48ca6c</code></a>
less is more</li>
<li><a
href="https://github.com/adobe/elixir-styler/commit/793cf27938a99c1492b0ce611519d91fda68311a"><code>793cf27</code></a>
optimize Req pipes</li>
<li>See full diff in <a
href="https://github.com/adobe/elixir-styler/compare/v1.9.1...v1.10.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=styler&package-manager=hex&previous-version=1.9.1&new-version=1.10.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [ex_doc](https://github.com/elixir-lang/ex_doc) from 0.39.1 to
0.39.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md">ex_doc's
changelog</a>.</em></p>
<blockquote>
<h2>v0.39.2 (2025-12-04)</h2>
<ul>
<li>Bug fixes
<ul>
<li>Do not strip hrefs on summaries</li>
<li>Show go to latest for prereleases</li>
<li>Prevent fake italic in autocomplete text</li>
<li>Rename &quot;Search Hexdocs&quot; link to &quot;Go to package
docs&quot;</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/425378e393769a857dd414255cc83be82e5f079e"><code>425378e</code></a>
Release v0.39.2</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/10d8315f7ba2e0e55093580707ff5788fa889922"><code>10d8315</code></a>
Ensure IDs rather than hrefs are stripped, closes <a
href="https://github.com/elixir-lang/ex_doc/issues/2175">#2175</a></li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/72bb755a90c9a98b645e7e923ebd3bb85ead8f32"><code>72bb755</code></a>
Show go to latest for prereleases, closes <a
href="https://github.com/elixir-lang/ex_doc/issues/2173">#2173</a></li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/6db9cab2742a19488376e870060f62a589bec672"><code>6db9cab</code></a>
Fix docs: Move <code>source_url</code> to <code>project</code> (<a
href="https://github.com/elixir-lang/ex_doc/issues/2172">#2172</a>)</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/e7abbaedb88eed88858f85b53862801e5f456a32"><code>e7abbae</code></a>
Add Elixir v1.19 and Erlang/OTP 28 to CI (<a
href="https://github.com/elixir-lang/ex_doc/issues/2166">#2166</a>)</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/60203be6699d00abef9e054bdcd0669fbca7ae43"><code>60203be</code></a>
Update assets</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/a4927a41cd3d6becc3a4e9540fb35ed6c5fb9403"><code>a4927a4</code></a>
Prevent fake italic in autocomplete text (<a
href="https://github.com/elixir-lang/ex_doc/issues/2168">#2168</a>)</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/c8d1e682a4dc7a1355103b858534f46734f5c78c"><code>c8d1e68</code></a>
Rename Search Hexdocs link to Go to package docs</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/33b1ffca53807808ae23fc915f54cd13bd5c5920"><code>33b1ffc</code></a>
Release v0.39.1</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/5bb6c18048c4f7c314fc13e4a424fa6c45e9eebb"><code>5bb6c18</code></a>
Update assets</li>
<li>Additional commits viewable in <a
href="https://github.com/elixir-lang/ex_doc/compare/v0.39.1...v0.39.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ex_doc&package-manager=hex&previous-version=0.39.1&new-version=0.39.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Problem

<!-- what problem is the PR is trying to solve? -->

## Solution

<!-- how is the PR solving the problem? -->

## Rationale

<!-- why was it implemented the way it was? -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Improved conditional dependency handling for session storage to ensure
graceful operation when optional libraries are unavailable. No
behavioral changes for existing deployments.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Bumps [credo](https://github.com/rrrene/credo) from 1.7.13 to 1.7.14.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rrrene/credo/releases">credo's
releases</a>.</em></p>
<blockquote>
<h2>v1.7.14</h2>
<p>Check it out on Hex: <a
href="https://hex.pm/packages/credo/1.7.14">https://hex.pm/packages/credo/1.7.14</a></p>
<ul>
<li>Fixed regression for <code>DuplicatedCode</code></li>
<li>Expanded <code>Credo.Check.Warning.ExpensiveEmptyEnumCheck</code> to
cover less obvious cases</li>
<li>New Check: <code>Credo.Check.Warning.StructFieldAmount</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rrrene/credo/blob/master/CHANGELOG.md">credo's
changelog</a>.</em></p>
<blockquote>
<h2>1.7.14</h2>
<ul>
<li>Fixed regression for <code>DuplicatedCode</code></li>
<li>Expanded <code>Credo.Check.Warning.ExpensiveEmptyEnumCheck</code> to
cover less obvious cases</li>
<li>New Check: <code>Credo.Check.Warning.StructFieldAmount</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/rrrene/credo/commit/b3a8c4ac73b155c21a02683d37dcb523b74f465d"><code>b3a8c4a</code></a>
Bump version to 1.7.14</li>
<li><a
href="https://github.com/rrrene/credo/commit/677f6389a6d38a957811d1df3b71374ea9081cda"><code>677f638</code></a>
Update CHANGELOG</li>
<li><a
href="https://github.com/rrrene/credo/commit/57deb6fdfbb1e1d3b481a7e395ba732c0e757800"><code>57deb6f</code></a>
Fix warnings for slow usage of <code>length/1</code></li>
<li><a
href="https://github.com/rrrene/credo/commit/ace6edda853c9b9163ab0dc55fca843d8e62633d"><code>ace6edd</code></a>
Fix missing dep</li>
<li><a
href="https://github.com/rrrene/credo/commit/1ffd3b235d1f920a8b54b3baf416af7c9b8b89f7"><code>1ffd3b2</code></a>
Inline pipe</li>
<li><a
href="https://github.com/rrrene/credo/commit/d182b847f282c69ac73355526e87a4dfb0980418"><code>d182b84</code></a>
Add inch_ex</li>
<li><a
href="https://github.com/rrrene/credo/commit/a006b49aa56b9bfce0e1e74ef7ea3c445827c681"><code>a006b49</code></a>
Fix housekeeping workflow</li>
<li><a
href="https://github.com/rrrene/credo/commit/cef51ea8b5c8e0586b51e5da02c51e94d49f2362"><code>cef51ea</code></a>
Refactor ExpensiveEmptyEnumCheck</li>
<li><a
href="https://github.com/rrrene/credo/commit/e781dbb93149c2de4671ed9b76b4bca8d15cb85b"><code>e781dbb</code></a>
Merge branch 'comparison-against-1' of github.com:hauleth/credo into
1226-emp...</li>
<li><a
href="https://github.com/rrrene/credo/commit/c20779e6c09c32d250354bf1262aef8759245f16"><code>c20779e</code></a>
Update Elixir to 1.19.3</li>
<li>Additional commits viewable in <a
href="https://github.com/rrrene/credo/compare/v1.7.13...v1.7.14">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=credo&package-manager=hex&previous-version=1.7.13&new-version=1.7.14)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: zoey <zoey.spessanha@zeetech.io>
🚀 Want to release this?
---


##
[0.17.0](zoedsoupe/anubis-mcp@v0.16.0...v0.17.0)
(2025-12-09)


### Features

* **redis:** add redix_opts for SSL/TLS support
([zoedsoupe#59](zoedsoupe#59))
([33658ab](zoedsoupe@33658ab))


### Bug Fixes

* added server component description/0 callback
([zoedsoupe#58](zoedsoupe#58))
([a094473](zoedsoupe@a094473))
* redix should be loaded
([zoedsoupe#71](zoedsoupe#71))
([09b872f](zoedsoupe@09b872f))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added Redis SSL/TLS support via redix options.

* **Bug Fixes**
  * Fixed server component description callback issue.
  * Fixed redix loading issue.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [ex_doc](https://github.com/elixir-lang/ex_doc) from 0.40.0 to
0.40.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md">ex_doc's
changelog</a>.</em></p>
<blockquote>
<h2>v0.40.1 (2026-01-31)</h2>
<ul>
<li>
<p>Enhancements</p>
<ul>
<li>Remove link to source from generated .md files</li>
<li>Improve word-breaking of module names and sizing of main page
titles</li>
<li>Include description in llms.txt</li>
</ul>
</li>
<li>
<p>Bug fixes</p>
<ul>
<li>Fix headers in custom groups</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/20a355b005c31f3ba38c7729d52a02571ea245cd"><code>20a355b</code></a>
Release v0.40.1</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/7a71ddf985ca531cc5ab8e0e3c81812209f31cd9"><code>7a71ddf</code></a>
Update assets</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/f44f6fe6aae77960b100cd08878986208449a960"><code>f44f6fe</code></a>
Turn whitespace minification back on</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/38028674ac14570a700ba8804d6ca4cbba1dc951"><code>3802867</code></a>
Improve distinction between docstring headings (H2-H4)</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/e8a46c6fa7d8af5a24af6caf7a266e7995c51ce2"><code>e8a46c6</code></a>
Change headings' levels to match their context</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/9cd866c84ccdd1afa9eee772d72511c5ef99cbae"><code>9cd866c</code></a>
Fix Summary Types heading size</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/e8e74eec1881dae173e02cf8713ff65e88540d68"><code>e8e74ee</code></a>
More word break tests</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/21ec71f709317bdd2e32b6d4d6055857d3761552"><code>21ec71f</code></a>
Update assets</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/8611a164fe24d7b9f669cdad96dcaf30e8254fc1"><code>8611a16</code></a>
Improve word-breaking of module names and sizing of main page titles (<a
href="https://github.com/elixir-lang/ex_doc/issues/2190">#2190</a>)</li>
<li><a
href="https://github.com/elixir-lang/ex_doc/commit/1b1fe51e479b9bcbf72802e7ef700ed3cedf53ac"><code>1b1fe51</code></a>
Bump lodash from 4.17.21 to 4.17.23 in /assets (<a
href="https://github.com/elixir-lang/ex_doc/issues/2187">#2187</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/elixir-lang/ex_doc/compare/v0.40.0...v0.40.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ex_doc&package-manager=hex&previous-version=0.40.0&new-version=0.40.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Summary

This fixes a race condition where responses could be silently lost when
an SSE handler process died but the transport hadn't yet processed the
`:DOWN` message.

**The issue:**
1. SSE handler process dies (network drop, crash, etc.)
2. `:DOWN` message is queued to transport GenServer
3. Before transport processes `:DOWN`, a new request calls
`get_sse_handler`
4. `get_sse_handler` returns the stale PID
5. `send/2` silently drops the message to the dead process
6. Client receives HTTP 202 but never gets the actual response

**The fix:**
- Add `Process.alive?` check in `route_sse_response` before sending
- If the handler is stale, clean up the entry and establish a new SSE
connection

## Test plan

- [x] All existing tests pass (519 tests, 0 failures)
- [x] Verified fix in production application (Flux MCP server)

🤖 Generated with [Claude Code](https://claude.ai/code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **Improvements**
* Server-Sent Events connections are now more resilient with enhanced
validation and automatic recovery for stale connections, significantly
improving real-time communication reliability and reducing message
delivery failures in active use.

* **Configuration**
* Session logging level is now optional, offering greater configuration
flexibility and enabling simplified setup when explicit specification is
not required.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Problem

The codebase has version-specific protocol logic scattered across
multiple modules (Anubis.Protocol,
Anubis.MCP.Message, Anubis.Server.Base, Anubis.Server). Adding support
for a new MCP spec version means touching many
places, and the server macro module had incorrect hardcoded version
strings (2024-05-11, 2024-10-07) that don't exist
in the supported versions list. This is Phase 1 of the architecture
refactor plan addressing issues hermes#179 and
hermes#141.

## Solution

- Created Anubis.Protocol.Behaviour defining callbacks each version
module must implement (version, features, schemas,
 methods)
- Created per-version modules (V2024_11_05, V2025_03_26, V2025_06_18)
under lib/anubis/protocol/ encoding
version-specific schemas, features, and method lists
- Created Anubis.Protocol.Registry as the central dispatch point mapping
version strings to modules, with negotiation
support
- Refactored Anubis.Protocol to delegate to the Registry while
preserving its entire public API (zero breaking
changes)
- Updated Server.Base to use Registry.negotiate/2 instead of a private
negotiation function
- Fixed Anubis.Server macro to derive @protocol_versions from the
Registry instead of a hardcoded (incorrect) list
- Stored the negotiated protocol module in Session, Client.State, and
Frame.private for downstream use in later phases
- Added 64 new tests covering the Registry, version modules, behaviour
compliance, feature inheritance, and backward
compatibility

## Rationale

Version modules inherit from their predecessor (V2025_03_26 delegates to
V2024_11_05 for unchanged schemas) to avoid
duplication while making differences explicit. The Registry is a
compile-time map (not a GenServer) since version data
is static. Anubis.Protocol's public API was preserved via defdelegate
and thin wrappers so this is a purely internal
refactor — no downstream code changes required. Storing the resolved
protocol module in session/connection state
enables later phases to dispatch version-specific logic without
re-resolving.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added multi-version MCP protocol support with dynamic version
negotiation and fallback handling.
* Introduced version-aware feature discovery to identify capabilities
across supported protocol versions.
* Enabled automatic protocol module resolution during client
initialization.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
…oedsoupe#95)

## Problem

Transport modules are tightly coupled to GenServer processes, making
them impossible to use as pure functions for
message parsing/encoding. This blocks Phase 3/4 of the architecture
refactor where transports need to be called
directly from Session processes and Plug pipelines without a separate
transport process.

## Solution

Implement the functional `Anubis.Transport` behaviour callbacks
(`transport_init/1`, `parse/2`, `encode/2`,
`extract_metadata/2`) across all 6 transport modules:

- `Anubis.Transport.STDIO` — newline-delimited JSON with partial message
buffering
- `Anubis.Transport.StreamableHTTP` — JSON with batch array support
- `Anubis.Transport.SSE` — JSON string/map parsing
- `Anubis.Server.Transport.STDIO` — same as client, with server metadata
- `Anubis.Server.Transport.StreamableHTTP` — JSON with Plug.Conn
metadata extraction
- `Anubis.Server.Transport.SSE` — SSE event format encoding, Plug.Conn
metadata

Added 64 unit tests covering parse/encode round-trips, buffering,
batching, metadata extraction, and cross-transport
consistency.

## Rationale

Functional callbacks are added alongside existing GenServer code (no
breaking changes). The GenServer transport
processes remain for backward compatibility — they will be removed in
Phase 3 (Server) and Phase 4 (Client) when the
architecture shifts to Session-owns-transport. This incremental approach
keeps all existing tests passing while
preparing the foundation for the next phases.
🚀 Want to release this?
---


##
[0.17.1](zoedsoupe/anubis-mcp@v0.17.0...v0.17.1)
(2026-02-28)


### Bug Fixes

* Check Process.alive? before sending to SSE handler
([zoedsoupe#82](zoedsoupe#82))
([e1dc705](zoedsoupe@e1dc705))


### Code Refactoring

* **phase-1:** abstract protocol version negotiation
([zoedsoupe#93](zoedsoupe#93))
([05a2362](zoedsoupe@05a2362))
* **phase-2:** transport layer as functions, backward compatible
([zoedsoupe#95](zoedsoupe#95))
([105d6a9](zoedsoupe@105d6a9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
…soupe#96)

## Problem

The server architecture had a single-GenServer bottleneck
(`Server.Base`) that serialized all requests across all sessions through
one mailbox. The `Frame` struct accumulated internal state (`transport`,
`request`, `private`, `initialized`) that leaked implementation details
to users and created transport-dependent branching in user code.

Related: https://github.com/cloudwalk/hermes-mcp/issues/179,
https://github.com/cloudwalk/hermes-mcp/issues/#141, RFC zoedsoupe#24

## Solution

**Phase 3 — Session-Centric Architecture**

- Each MCP session is now an independent GenServer process under
`Session.Supervisor` (DynamicSupervisor)
- `Server.Base` becomes a coordinator: session lifecycle management,
expiry timers, and delegation — no longer processes MCP requests
directly
- Transport modules (`StreamableHTTP.Plug`, `SSE.Plug`, `STDIO`) route
requests to the correct Session process via `Registry`
- `Registry` is pluggable (`Registry.Adapter` behaviour) with
`Registry.Local` (ETS) as default, supporting future distributed
implementations (Horde, Phoenix.Tracker)
- Heavy tool execution offloaded to `Task.Supervisor` per session,
keeping the session mailbox responsive

**Phase 3.5 — Context Refactor**

- Introduced `Anubis.Server.Context` — a minimal 4-field struct
(`session_id`, `client_info`, `headers`, `remote_ip`) added as a
read-only field on `Frame`
- Removed `Frame.transport`, `Frame.request`, and `Frame.initialized`
fields — these were internal state that polluted the user-facing struct
- Transport-agnostic design: STDIO naturally has `headers: %{},
remote_ip: nil`, no type branching needed
- Added `Frame.to_saved/1` and `Frame.from_saved/1` for `Session.Store`
integration — context is omitted (request-scoped), only user state
persists
- Session recovery flow: `Registry.lookup` → miss → `Store.load` →
`DynamicSupervisor.start_child` → `Registry.register`
- No process dictionary usage — context is a value passed through Frame,
works naturally with Tasks and async code

## Rationale

**Session-per-process over shared GenServer**: the old design forced all
sessions through one mailbox — a slow tool call in session A blocked
unrelated requests in session B. OTP's model is one process per
concurrent unit of work, and an MCP session is exactly that.

**Context as a Frame field over process dictionary**: `Process.put`
doesn't propagate to `Task.async` spawns, creating silent failures. A
struct field on Frame is explicit, testable, and works everywhere Frame
is passed.

**Minimal Context (4 fields) over full request mirror**: the original
plan had 7+ fields including `protocol_version`, `initialized`,
`request.id`, `request.method`, `request.params`, and transport type
unions. Most duplicated data already available through callback
arguments or enforced by Session lifecycle. Users should never branch on
transport type — that defeats transport-agnostic design.

**Registry and Store as orthogonal concerns**: Registry answers "where
is the process?" (ephemeral, dies with process). Store answers "what was
the state?" (durable, survives restarts). Both are optional and
pluggable independently.

---

**Next steps**:
- Phase 4 — Client refactor: decompose `Client.Base` (1634 LOC) into
`Client.Handlers` + `Client.Sampling`
- Phase 5 — Dead code removal, deduplication audit, and documentation
fixes (including `CONTRIBUTING.md` license mismatch, outdated API
examples in hexdocs pages, missing `pages/home.md`)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Error responses now follow JSON‑RPC 2.0 envelope with a top-level
"jsonrpc": "2.0".
* **Refactor**
* Notification API simplified — sending notifications no longer requires
passing per-frame state.
* Sessions moved to a session-centric model with session-based routing
for transports.
* Registry and naming model modernized for deterministic
transport/session names and pluggable registry options.
* Frame model streamlined to public component maps and a read-only
per-request context.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem

`Anubis.Client.Base` module is currently massive, this is a first try to
reduce it

## Solution

Extracted sampling capability and other message handlers (tool calls
responses for ex) to separate modules

## Rationale

N/A


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Updated Zig tooling in release workflows from v0.14.1 to v0.15.2.

* **Refactor**
* Reorganized internal client notification and sampling request handling
into dedicated modules for improved maintainability and separation of
concerns; no changes to external behavior or public interfaces.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Bumps [telemetry](https://github.com/beam-telemetry/telemetry) from
1.3.0 to 1.4.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/beam-telemetry/telemetry/blob/main/CHANGELOG.md">telemetry's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/elixir-telemetry/telemetry/tree/v1.4.1">1.4.1</a></h2>
<h3>Fixed</h3>
<ul>
<li>Avoid crashes when <code>telemetry</code> is invoked before started
(such as during Elixir compile-time)</li>
</ul>
<h2><a
href="https://github.com/elixir-telemetry/telemetry/tree/v1.4.0">1.4.0</a></h2>
<h3>Added</h3>
<ul>
<li>Add <code>telemetry:persist/0</code> which uses persistent term for
faster dispatches (writes are extremely discouraged after persist)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fix the <code>telemetry:span_function/0</code> type</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/beam-telemetry/telemetry/commit/11462db509623be85c7acf3f15d0579d0d3f4a79"><code>11462db</code></a>
Release v1.4.1</li>
<li><a
href="https://github.com/beam-telemetry/telemetry/commit/11210b44b3bb431059a99847ccaec20352b5bac5"><code>11210b4</code></a>
Do not crash on failed persistent term lookup</li>
<li><a
href="https://github.com/beam-telemetry/telemetry/commit/972ff3bb1b0b95f907d93bbb3726b95eee7b725c"><code>972ff3b</code></a>
Release v1.4.0</li>
<li><a
href="https://github.com/beam-telemetry/telemetry/commit/ddc7f13e4f93d103422f98eb481d3bade2cb73f8"><code>ddc7f13</code></a>
Add &quot;since&quot; doc info and spec to persist/0 (<a
href="https://github.com/beam-telemetry/telemetry/issues/146">#146</a>)</li>
<li><a
href="https://github.com/beam-telemetry/telemetry/commit/55d657eb6102b8a81a519e9abca5990506e00ee8"><code>55d657e</code></a>
Remove function allocation and remote call on execution (<a
href="https://github.com/beam-telemetry/telemetry/issues/145">#145</a>)</li>
<li><a
href="https://github.com/beam-telemetry/telemetry/commit/c0aff75703636e93dcc581f3dc84c9a7f4dc5870"><code>c0aff75</code></a>
Implement <code>telemetry:persist/0</code> (<a
href="https://github.com/beam-telemetry/telemetry/issues/144">#144</a>)</li>
<li><a
href="https://github.com/beam-telemetry/telemetry/commit/614bfb91e9e42e305b95860cc64d237c01435d65"><code>614bfb9</code></a>
Use field name instead of magic number (<a
href="https://github.com/beam-telemetry/telemetry/issues/143">#143</a>)</li>
<li><a
href="https://github.com/beam-telemetry/telemetry/commit/13a380ed0214a8f5824c99ef9897db50de84c90c"><code>13a380e</code></a>
Test CI against multiple OTP versions (24-28) (<a
href="https://github.com/beam-telemetry/telemetry/issues/140">#140</a>)</li>
<li><a
href="https://github.com/beam-telemetry/telemetry/commit/2f29d755aaf98d1c49a166cf5372755bb7033e72"><code>2f29d75</code></a>
Improve docs for failure events (<a
href="https://github.com/beam-telemetry/telemetry/issues/139">#139</a>)</li>
<li><a
href="https://github.com/beam-telemetry/telemetry/commit/7b99309898cc827fda3f1956e23d34d349453f16"><code>7b99309</code></a>
Fix the telemetry:span_function/0 type (<a
href="https://github.com/beam-telemetry/telemetry/issues/137">#137</a>)</li>
<li>See full diff in <a
href="https://github.com/beam-telemetry/telemetry/compare/v1.3.0...v1.4.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=telemetry&package-manager=hex&previous-version=1.3.0&new-version=1.4.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [credo](https://github.com/rrrene/credo) from 1.7.15 to 1.7.17.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rrrene/credo/releases">credo's
releases</a>.</em></p>
<blockquote>
<h2>v1.7.17</h2>
<p>Check it out on Hex: <a
href="https://hex.pm/packages/credo/1.7.17">https://hex.pm/packages/credo/1.7.17</a></p>
<ul>
<li><code>Credo.Check.Readability.ModuleDoc</code> add new param
<code>:ignore_modules_using</code> (defaults to <code>[Credo.Check,
Ecto.Schema, Phoenix.LiveView, ~r/\.Web$/]</code>)</li>
<li><code>Credo.Check.Warning.UnusedOperation</code> update
<code>:modules</code> param: instead of a list of functions to check,
<code>:all</code> can be given to check all functions in a module</li>
<li>New Check:
<code>Credo.Check.Refactor.CondInsteadOfIfElse</code></li>
<li>New Check: <code>Credo.Check.Warning.WrongTestFilename</code></li>
</ul>
<h2>v1.7.16</h2>
<p>Check it out on Hex: <a
href="https://hex.pm/packages/credo/1.7.16">https://hex.pm/packages/credo/1.7.16</a></p>
<ul>
<li>Fix compatibility &amp; compiler warnings with Elixir
1.20.0-rc.1</li>
<li><code>Credo.Check.Refactor.PassAsyncInTestCases</code> add new param
<code>:force_comment_on_explicit_false</code> (defaults to
<code>false</code>)</li>
<li><code>Credo.Check.Warning.Dbg</code> add new param
<code>:allow_captures</code> (defaults to <code>false</code>)</li>
<li>New Check: <code>Credo.Check.Warning.UnusedMapOperation</code></li>
<li>New Check: <code>Credo.Check.Warning.UnusedOperation</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rrrene/credo/blob/master/CHANGELOG.md">credo's
changelog</a>.</em></p>
<blockquote>
<h2>1.7.17</h2>
<ul>
<li><code>Credo.Check.Readability.ModuleDoc</code> add new param
<code>:ignore_modules_using</code> (defaults to <code>[Credo.Check,
Ecto.Schema, Phoenix.LiveView, ~r/\.Web$/]</code>)</li>
<li><code>Credo.Check.Warning.UnusedOperation</code> update
<code>:modules</code> param: instead of a list of functions to check,
<code>:all</code> can be given to check all functions in a module</li>
<li>New Check:
<code>Credo.Check.Refactor.CondInsteadOfIfElse</code></li>
<li>New Check: <code>Credo.Check.Warning.WrongTestFilename</code></li>
</ul>
<h2>1.7.16</h2>
<ul>
<li>Fix compatibility &amp; compiler warnings with Elixir
1.20.0-rc.1</li>
<li><code>Credo.Check.Refactor.PassAsyncInTestCases</code> add new param
<code>:force_comment_on_explicit_false</code> (defaults to
<code>false</code>)</li>
<li><code>Credo.Check.Warning.Dbg</code> add new param
<code>:allow_captures</code> (defaults to <code>false</code>)</li>
<li>New Check: <code>Credo.Check.Warning.UnusedMapOperation</code></li>
<li>New Check: <code>Credo.Check.Warning.UnusedOperation</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/rrrene/credo/commit/068fcc7bfb3d35ed35a000bd2a9a02b637114119"><code>068fcc7</code></a>
Bump version to 1.7.17</li>
<li><a
href="https://github.com/rrrene/credo/commit/5eccc0d146291867ec6f1e22425159484185b3f8"><code>5eccc0d</code></a>
Update CHANGELOG</li>
<li><a
href="https://github.com/rrrene/credo/commit/0b087207d614db8c31fd3aac0f659c49d750a382"><code>0b08720</code></a>
Update .credo.exs</li>
<li><a
href="https://github.com/rrrene/credo/commit/122a258459418e4ca1f9b1cffbc78b11be870a3e"><code>122a258</code></a>
Add :excludes to .formatter.exs</li>
<li><a
href="https://github.com/rrrene/credo/commit/09960e67678f5fc8e7e06d715eb3d94702d48590"><code>09960e6</code></a>
Add defaults to :ignore_modules_using in ModuleDoc</li>
<li><a
href="https://github.com/rrrene/credo/commit/472f12e222abed650d95c8b662836b68da114836"><code>472f12e</code></a>
Add repro for <a
href="https://github.com/rrrene/credo/issues/1235">#1235</a> to
prevent regressions</li>
<li><a
href="https://github.com/rrrene/credo/commit/11235d5754a1c93eb321ce2b2e989ed8601c0a0d"><code>11235d5</code></a>
Merge branch '1254-module-doc-new-param'</li>
<li><a
href="https://github.com/rrrene/credo/commit/e73ea15e05a2d2a14083a0b1567b021d9acfb084"><code>e73ea15</code></a>
Integrate new param for ModuleDoc</li>
<li><a
href="https://github.com/rrrene/credo/commit/0cfeba513f76b074a9be9913003115b9df9ada60"><code>0cfeba5</code></a>
Update check id for WrongTestFilename</li>
<li><a
href="https://github.com/rrrene/credo/commit/04cb8633f9bff15557f062b1ca243a962eb53c8c"><code>04cb863</code></a>
Merge branch '1258-new-test-files-check'</li>
<li>Additional commits viewable in <a
href="https://github.com/rrrene/credo/compare/v1.7.15...v1.7.17">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=credo&package-manager=hex&previous-version=1.7.15&new-version=1.7.17)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…andlers (zoedsoupe#109)

## Problem

After adding Anubis 0.17.1 I noticed that memory usage of my Phoenix
application slowly climbed over the hours until it ran out of memory and
died. It's a pre-production app with little usage and when that happened
there were just logs to connect to the MCP server.

## Solution

After adding some tracking, GPT-5.3-Codex diagnosed this as follows:

- In streamable HTTP transport, SSE handlers are keyed by session_id.
- Reconnects on the same `session_id` can overwrite map entries, and old
stream processes can become orphaned (no longer in `sse_handlers`).
- Orphaned stream loops sit in receive forever.
- `expiry_timers` map in Anubis server is not cleaned on
`:session_expired` path.

With this fix:

- adding pid-aware `unregister_sse_handler/3` so only the expected
handler can remove itself
- replacing existing handlers safely on register (demonitor old ref,
close old handler, monitor/store the new handler)
- updating plug on-close and stale-handler cleanup paths to pass the
handler pid

## Rationale

I've tested this and deployed it to my app, and memory usage has been
stable the last few hours.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Bug Fixes**
* Enhanced Server-Sent Events (SSE) handler registration logic to
properly manage concurrent handler instances for the same session,
preventing race conditions and ensuring consistent handler state.

* **Tests**
* Added test case to verify SSE handler lifecycle behavior when multiple
handlers are registered for the same session.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem

I have an Elixir app that provides an MCP server and I wanted to utilize
the new MCP Apps extension
(https://modelcontextprotocol.io/extensions/apps/overview). To do this,
I needed support for adding the _meta field to tool declarations so
clients know which visual resource to render alongside a tool's output.

## Solution

I added a meta field to the Tool struct and wired it through the JSON
encoder so _meta appears in the serialized MCP tool object. Tools can
now declare `meta: %{"ui" => %{"resourceUri" =>
"ui://my-app/dashboard"}}` via `use Anubis.Server.Component`.

## Rationale

This is the minimal change needed to support the MCP spec's _meta field
— I only touched the Tool struct and its serialization path.



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Tool components now support optional metadata that can be provided
during tool definition and is automatically included in exported tool
information and JSON responses.

* **Tests**
* Added comprehensive tests validating metadata functionality, including
JSON encoding and callback optionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Problem

Warning `Session store enabled but adapter not available` when the
session store
is not enabled.

## Solution

Properly check the config's `enabled` boolean param

## Rationale




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Refactored session store initialization to improve configuration
handling and logging.
* **Tests**
* Added test coverage for session store configuration scenarios and
related logging.
* **Note**
  * No user-facing changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
zoedsoupe and others added 13 commits May 9, 2026 12:06
…upe#98) (zoedsoupe#155)

## Problem

Closes zoedsoupe#98. MCP 2025-11-25 introduces Tasks — durable state machines
wrapping long-running JSON-RPC requests so requestors can poll instead
of relying on timeouts or progress notifications.

## Solution

**Phase 1 — server-receiver for `tools/call` only.** Implements:

- New protocol module `V2025_11_25` (latest); registers
`tasks/get|result|cancel|list` + `notifications/tasks/status`
- `:tasks` capability on `use Anubis.Server` (`list?`, `cancel?`,
`requests:`)
- Tool-level `task_support: :forbidden | :optional | :required`; renders
as `execution.taskSupport` in `tools/list`; enforced in `Handlers.Tools`
(required→reject non-augmented, forbidden→reject augmented)
- `Anubis.Server.Task` struct + `Anubis.Server.TaskStore` behaviour with
`Local` adapter; pluggable via `:task_store {mod, opts}` in
`Anubis.Server.Supervisor` (mirrors `:registry`/`:supervisor`); supports
`:via` for distributed adapters via optional `resolve_name/2` callback
- `Frame.task_id` propagated to worker callbacks
- Session: detects `params.task` on `tools/call`, persists `Task`,
spawns worker via existing `Task.Supervisor`, immediately returns
`CreateTaskResult` with `_meta[related-task]`. Routes
`tasks/get|cancel|result|list`. `tasks/result` blocks until terminal via
per-task waiter list. TTL timer + cancel paths release waiters with
proper errors. Terminate cleans up workers + waiters
- `Server.send_task_status/1` API → emits `notifications/tasks/status`
from session

15 deterministic tests (signal-based stub server,
`SyncHelpers.await_state` — no `Process.sleep`). 757 tests + 33 doctests
pass; credo strict clean; dialyzer pass.

## Deferred to follow-up PRs

- **Phase 2** — client-as-requestor for `tools/call`
(`Client.call_tool(task: [ttl: N])`, `get_task/task_result/cancel_task`
helpers; return shape decided when designed)
- **Phase 3** — server-as-requestor for `sampling/createMessage` +
`elicitation/create`
- **`tasks/list`** — needs auth-context binding through
`Anubis.Server.Context` first; Phase 1 returns `-32601`
- **Redis `TaskStore` adapter** — slots into the behaviour without API
change
- **`input_required` status flow** — schema accepts it; Phase 1 worker
path never produces it

## Rationale

Phase 1 ships the highest-value slice (server author exposes a
long-running tool with a single `task_support: :optional`). Pluggable
storage day-one means distributed adapters land later as drop-ins.
Strict `2025-11-25` gate keeps the spec contract clean — older protocol
versions don't see Tasks. Signal-driven tests over `Process.sleep` keep
the suite fast and deterministic, matching the existing async-dispatch
pattern.
🚀 Want to release this?
---


##
[1.5.0](zoedsoupe/anubis-mcp@v1.4.0...v1.5.0)
(2026-05-09)


### Features

* MCP Tasks (2025-11-25) — server-receiver for tools/call
([zoedsoupe#98](zoedsoupe#98))
([zoedsoupe#155](zoedsoupe#155))
([51348f1](zoedsoupe@51348f1))


### Bug Fixes

* drop compile-connected deps from component/1 macro
([zoedsoupe#154](zoedsoupe#154))
([1e368b9](zoedsoupe@1e368b9))


### Continuous Integration

* fix flaky test
([939fd76](zoedsoupe@939fd76))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
## Problem

MCP servers had no built-in way to authenticate clients per the spec's
OAuth 2.1 authorization model.

## Solution

Add `Anubis.Server.Authorization` with pluggable `Validator` behaviour,
`JWTValidator` (JWKS-backed) and `IntrospectionValidator` impls,
`.well-known/oauth-protected-resource` discovery, and 401 +
`WWW-Authenticate` enforcement in the SSE and Streamable HTTP plugs.
Bearer token claims surface on the request `Frame` so handlers can
scope/authorize.

## Rationale

Validator behaviour keeps token verification out of the transport layer
and lets users plug in custom auth servers. JOSE is optional — JWT
validator only compiles when `:jose` is present.
🚀 Want to release this?
---


##
[1.6.0](zoedsoupe/anubis-mcp@v1.5.0...v1.6.0)
(2026-05-18)


### Features

* add OAuth 2.1 authorization for MCP servers
([zoedsoupe#158](zoedsoupe#158))
([a12a8f6](zoedsoupe@a12a8f6))
* add Registry.PG for distributed session tracking via :pg
([zoedsoupe#160](zoedsoupe#160))
([512e103](zoedsoupe@512e103))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
🚀 Want to release this?
---


##
[1.6.1](zoedsoupe/anubis-mcp@v1.6.0...v1.6.1)
(2026-05-23)


### Bug Fixes

* Echo request id in "Server not initialized" error
([zoedsoupe#168](zoedsoupe#168))
([226b71e](zoedsoupe@226b71e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
…ry miss

In multi-instance (horizontally scaled) deployments, notification and response
POSTs can be routed to a node whose local registry never saw the session. The
request path already recovers via find_or_create_session/3 (which restores from
the configured Session.Store), but handle_notification_message and
handle_response_message returned 404 on a registry miss without consulting the
store. This makes them mirror the request path: on a registry miss, restore the
session from the store if it exists there, otherwise keep the 404. With no store
configured (single node), behaviour is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Problem

Session requests can fail on multi-instance deployments when the session is not in the local registry.

Solution

Restore sessions from the configured Session.Store for notification and response requests.

Rationale

Preserves single-node 404 behavior while allowing valid sessions routed across instances to succeed.

Walkthrough

Notification and response message handling now use find_or_restore_session/2. When a local registry lookup misses, the Plug checks the configured session store, starts and auto-initializes a session when stored data exists, and continues processing. If no store is configured or no session is found, it preserves the existing not-found response.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes restoring Streamable HTTP sessions from store on registry misses in multi-instance deployments.
Description check ✅ Passed The description covers the problem, fix, and rationale well, with only minor template heading differences from the required structure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/anubis/server/transport/streamable_http/plug.ex (1)

191-216: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

P1: Handle unexpected restoration errors to prevent a CaseClauseError crash.

Because start_and_auto_initialize_session/2 can return an {:error, reason} tuple (e.g., if auto-initialization fails or the process fails to start), passing that back through find_or_restore_session/2 will trigger a sneaky CaseClauseError here since we only match {:ok, session_pid} and {:error, :not_found}.

Let's add a catch-all error clause to log the failure and gracefully return an HTTP 500 instead of completely crashing the Plug process 😎.

🛠️ Proposed fix to handle restoration errors
     defp handle_notification_message(conn, message, session_id, context, opts) do
       case find_or_restore_session(opts, session_id) do
         {:ok, session_pid} ->
           GenServer.cast(session_pid, {:mcp_notification, message, context})
 
           conn
           |> put_resp_content_type("application/json")
           |> send_resp(202, "{}")
 
         {:error, :not_found} ->
           send_error(conn, 404, "Session not found")
+
+        {:error, reason} ->
+          Logging.transport_event("session_restore_failed", %{reason: inspect(reason)}, level: :error)
+          send_error(conn, 500, "Failed to restore session")
       end
     end
 
     defp handle_response_message(conn, message, session_id, context, opts) do
       case find_or_restore_session(opts, session_id) do
         {:ok, session_pid} ->
           GenServer.cast(session_pid, {:mcp_response, message, context})
 
           conn
           |> put_resp_content_type("application/json")
           |> send_resp(202, "{}")
 
         {:error, :not_found} ->
           send_error(conn, 404, "Session not found")
+
+        {:error, reason} ->
+          Logging.transport_event("session_restore_failed", %{reason: inspect(reason)}, level: :error)
+          send_error(conn, 500, "Failed to restore session")
       end
     end

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 80feb8bb-a7ae-4aca-8903-13c72c5601f9

📥 Commits

Reviewing files that changed from the base of the PR and between 3b636a8 and af4ccdc.

📒 Files selected for processing (1)
  • lib/anubis/server/transport/streamable_http/plug.ex

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 16, 2026
@zoedsoupe

Copy link
Copy Markdown
Owner

please, merge the main to correctly continue from #208

@zoedsoupe
zoedsoupe dismissed coderabbitai[bot]’s stale review July 16, 2026 12:47

The merge-base changed after approval.

@zoedsoupe zoedsoupe mentioned this pull request Jul 16, 2026
@zoedsoupe

Copy link
Copy Markdown
Owner

hey, i've messed with the history but i've fixed it and manually merged your contribution. so sorry for that and thanks again for the contribution

@zoedsoupe zoedsoupe closed this Jul 16, 2026
zoedsoupe added a commit that referenced this pull request Jul 16, 2026
🚀 Want to release this?
---


##
[1.9.0](v1.8.0...v1.9.0)
(2026-07-16)


### Features

* **streamable_http:** add spec resumability (Last-Event-ID replay)
([#216](#216))
([78e33b4](78e33b4))
* support pre_initialized sessions for cross-pod restore
([#187](#187))
([13be0d7](13be0d7))


### Bug Fixes

* **session:** return encodable JSON-RPC errors when init/2 fails
([#211](#211))
([f9af7cc](f9af7cc))
* **streamable_http:** emit telemetry on SSE handler registration
([#217](#217))
([4a4c528](4a4c528))
* **streamable_http:** restore session from store on notif/resp registry
miss ([#221](#221))
([d757b39](d757b39))
* **streamable_http:** return correct JSON-RPC error codes for parse
failures ([#222](#222))
([1867994](1867994))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.