feat(streamable_http): add spec resumability (Last-Event-ID replay) - #216
Closed
BobbieBarker wants to merge 124 commits into
Closed
BobbieBarker wants to merge 124 commits into
BobbieBarker wants to merge 124 commits into
Conversation
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 /> [](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 |> Enum.map(fun) |> Enum.intersperse(separator)</code> => <code>Enum.map_intersperse(enum, separator, fun)</code></li> <li><code>enum |> Enum.sort() |> Enum.reverse()</code> => <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 "execute the request" 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 |> Req.new() |> Req.merge(bar) |> Req.post!()</code></li> <li><code>Req.post!(foo, bar)</code></li> </ul> <p>Styler now rewrites the former to the latter, since "less is more" or "code is a liability".</p> <p>It also rewrites <code>|> Keyword.merge(bar) |> Req.foo()</code> to <code>|> 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 |> Enum.map(fun) |> Enum.intersperse(separator)</code> => <code>Enum.map_intersperse(enum, separator, fun)</code></li> <li><code>enum |> Enum.sort() |> Enum.reverse()</code> => <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 "execute the request" 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 |> Req.new() |> Req.merge(bar) |> Req.post!()</code></li> <li><code>Req.post!(foo, bar)</code></li> </ul> <p>Styler now rewrites the former to the latter, since "less is more" or "code is a liability".</p> <p>It also rewrites <code>|> Keyword.merge(bar) |> Req.foo()</code> to <code>|> 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 |> Enum.intersperse => 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 |> reverse => 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 /> [](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 "Search Hexdocs" link to "Go to package docs"</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 /> [](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 /> [](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 /> [](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 "since" 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 /> [](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 & 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 & 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 /> [](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 -->
Bumps [cowboy](https://github.com/ninenines/cowboy) from 2.16.1 to 2.17.0. <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/ninenines/cowboy/commit/3de77f8f08f57d6a6d5f0e646b7e8ecd4ccd525b"><code>3de77f8</code></a> Cowboy 2.17.0</li> <li><a href="https://github.com/ninenines/cowboy/commit/98d97c02f651c67247142ee59d68f697df07c7c4"><code>98d97c0</code></a> Clarify cowboy_decompress_h behavior in manual</li> <li><a href="https://github.com/ninenines/cowboy/commit/19f3a5bad5342601cd4a28f5976adb825150b84e"><code>19f3a5b</code></a> HTTP/1: Skip request trailers if any</li> <li><a href="https://github.com/ninenines/cowboy/commit/c8cd061c509c5b63ee8bbc4c2fb4fcab1e8215d0"><code>c8cd061</code></a> Warn about cowboy_compress_h in the security checklist</li> <li><a href="https://github.com/ninenines/cowboy/commit/ad44cbf0a9052849652fbcf049494d6514cae8b8"><code>ad44cbf</code></a> HTTP/1: Fix rejecting header lines with missing colon</li> <li><a href="https://github.com/ninenines/cowboy/commit/3cab5a3a4ee14a835e8beb581dbb7cdeb54b45a6"><code>3cab5a3</code></a> Add max_keys option to cowboy_req qs functions</li> <li><a href="https://github.com/ninenines/cowboy/commit/6e3609c38dc3d5b1aa6b420ededaefa98963f1a3"><code>6e3609c</code></a> Clarify static handler behavior with symlinks</li> <li><a href="https://github.com/ninenines/cowboy/commit/c19a177276ae14afdb73bf60977263008ece0587"><code>c19a177</code></a> Don't use 'catch' in tests</li> <li><a href="https://github.com/ninenines/cowboy/commit/a8541a2d74c561a320d474c50af7d6c0658ba9a0"><code>a8541a2</code></a> Temporarily use Cowlib master</li> <li><a href="https://github.com/ninenines/cowboy/commit/db5cc07249eeba831a2f1aec992c6bbd6ab25e43"><code>db5cc07</code></a> Extend invalid_response_headers to early_error responses</li> <li>Additional commits viewable in <a href="https://github.com/ninenines/cowboy/compare/2.16.1...2.17.0">compare view</a></li> </ul> </details> <br /> [](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 [plug](https://github.com/elixir-plug/plug) from 1.20.1 to 1.20.3. <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.20.3 (2026-07-09)</h2> <h3>Security</h3> <ul> <li>[Plug.Parsers.MULTIPART] Count files and skipped multipart parts towards the length limit (CVE-2026-56814)</li> <li>[Plug.Conn.Cookies] Raise if <code>;</code> is present in cookie attributes (CVE-2026-56813)</li> </ul> <h2>v1.20.2 (2026-06-30)</h2> <h3>Bug fixes</h3> <ul> <li>[Plug.Conn] Set state to <code>:set_upgrade</code> and status to 101 when running <code>before_send</code> callbacks for upgrades</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/elixir-plug/plug/commit/9fa11c8ebedbe68531eba25d8f81b9282e0514da"><code>9fa11c8</code></a> Release v1.20.3</li> <li><a href="https://github.com/elixir-plug/plug/commit/eceb8315ce9a31ef784943a95a8624ebd1bc7e06"><code>eceb831</code></a> Raise if ; is present in cookie attributes</li> <li><a href="https://github.com/elixir-plug/plug/commit/56edca2ce35fe5589cd581644d8a4493fa5f484e"><code>56edca2</code></a> Count files and skipped multiparts towards length</li> <li><a href="https://github.com/elixir-plug/plug/commit/cf15f0a5d9ce4390adff1cb4071a44013793ddd4"><code>cf15f0a</code></a> Release v1.20.2</li> <li><a href="https://github.com/elixir-plug/plug/commit/5d0fe881537531c760d7e4a54bb93fa9d346ac4c"><code>5d0fe88</code></a> Fix register_before_send with upgrade_adapter (<a href="https://github.com/elixir-plug/plug/issues/1320">#1320</a>)</li> <li>See full diff in <a href="https://github.com/elixir-plug/plug/compare/v1.20.1...v1.20.3">compare view</a></li> </ul> </details> <br /> [](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>
…stion DoS (zoedsoupe#188) ## Summary `Anubis.Server.Registry.resolve_session_name/3` named each session process by interpolating the session id into an atom. For HTTP transports the session id comes from the client-supplied `mcp-session-id` header, and atoms are never garbage collected, so a client sending many distinct session ids grows the global atom table without bound until the BEAM hits its atom limit (default ~1,048,576) and the node crashes with `system_limit`. That is a remote denial-of-service reachable in a default production deployment. This PR routes the default session naming through an Elixir `Registry` (`:via` tuple keyed by the session-id string) so the client-supplied id stays ordinary term data and no atom is minted per session. ## The vulnerability Two code paths built a fresh atom per session id: - The fallback in `resolve_session_name/3`, used when a registry adapter does not implement the optional `session_name/2` callback (`lib/anubis/server/registry.ex`): ```elixir defp session_name_from_registry_name(registry_name, session_id) when is_atom(registry_name) do :"#{registry_name}.session.#{session_id}" end ``` - The shipped `Registry.Local` adapter (`lib/anubis/server/registry/local.ex`), the default for HTTP transports, which implemented `session_name/2` the same way: ```elixir def session_name(registry_name, session_id), do: :"#{registry_name}.session.#{session_id}" ``` `Registry.PG` does not implement `session_name/2`, so it hits the atom-minting fallback. Both shipped HTTP-capable registries are therefore affected — no custom adapter required. STDIO is unaffected because it uses a single, fixed `"stdio"` session id. The name is used only as the `name:` passed to `start_session/2`; after start, lookups go through the adapter's `lookup_session/2` (ETS or `:pg`) by pid. So the atom only ever served as the process registration name, which makes it safe to replace with a `:via` name. ## The fix `resolve_session_name/3` now returns: ```elixir {:via, Registry, {naming_registry_name(registry_name), session_id}} ``` - A per-server Elixir `Registry` (`keys: :unique`) is started in the HTTP supervision tree (`build_http_children/8`). Its name is derived from the compile-time bounded `registry_name` via the new `Registry.naming_registry_name/1`, so it does not itself mint per-session atoms. - The client-supplied `session_id` is the registry key (binary term data), never converted to an atom. - `:via` tuples are drop-in replacements for atom names in `GenServer.start_link/3` / `start_session/2`, and they preserve the existing `{:error, {:already_started, pid}}` semantics that `plug.ex` and `sse.ex` rely on for concurrent requests to the same session. - The optional `session_name/2` callback is kept for adapters that want their own `:via` naming (e.g. Horde). Only the shipped atom-minting `Registry.Local.session_name/2` is removed, so it falls through to the safe default. Internal, compile-time bounded names (transports, supervisors, task stores) keep their atom naming — they are not influenced by client input. The public `Registry.session_name/2` helper still builds an atom and is now documented as test-only / trusted-id-only. ## Changes - `lib/anubis/server/registry.ex` — `resolve_session_name/3` fallback now returns a `:via` `Registry` tuple; added `naming_registry_name/1`; documented why session names must not be atoms and marked `session_name/2` as trusted-id-only. - `lib/anubis/server/supervisor.ex` — start the per-server naming `Registry` in `build_http_children/8`. - `lib/anubis/server/registry/local.ex` — removed the atom-minting `session_name/2`; uses the safe default. - `test/anubis/server/registry_test.exs` — regression test asserting `:erlang.system_info(:atom_count)` stays stable across 50,000 distinct session ids, plus round-trip tests that a process is reachable by its resolved `:via` name and that duplicate starts return `{:already_started, pid}`. - `test/anubis/server/transport/streamable_http/plug_test.exs` — start the naming `Registry` in the session-handling setup (mirrors production wiring). ## Verification ``` mix test # registry/transport/session suites: 63 tests, 0 failures mix format --check-formatted mix credo # no issues on changed files ``` The regression test fails against the previous atom-based naming and passes with the fix. --------- Co-authored-by: zoey <zoey.spessanha@zeetech.io>
…e#198) ## Problem On Streamable HTTP, the client sends `notifications/initialized` and its first request (e.g. `tools/list`) as two separate, near-simultaneous HTTP requests. The client sends them in order, but the transport doesn't guarantee order, so `tools/list` sometimes lands before the notification. When it does, the session rejects it with `"Server not initialized"` ([gate](https://github.com/zoedsoupe/anubis-mcp/blob/main/lib/anubis/server/session.ex#L603-L605), [error](https://github.com/zoedsoupe/anubis-mcp/blob/main/lib/anubis/server/session.ex#L635-L645)). A client that doesn't retry then stalls. In my case, the Claude Desktop client hangs ~30s, then sends `notifications/cancelled`. The spec lets a client send requests as soon as the server responds to `initialize`, so this is stricter than the spec requires. ## Solution Mark the session initialized on the `initialize` response, not only in the `notifications/initialized` handler. Adds a test for a request arriving after `initialize` but before the notification. ## Rationale Matches the official SDKs, which mark the session ready on the `initialize` response: - TypeScript ([streamableHttp.ts#L501](https://github.com/modelcontextprotocol/typescript-sdk/blob/caa25503cdfc449d116c204e866bccc2617d7037/src/server/streamableHttp.ts#L501)) - Python ([#1478](modelcontextprotocol/python-sdk#1478)) - Rust ([#788](modelcontextprotocol/rust-sdk#788)) The spec says **SHOULD NOT**, not **MUST NOT**, so the notification isn't a hard gate.
…ssion auto-recovery (zoedsoupe#208) **Addresses behaviour 1 in zoedsoupe#204.** **Follows up zoedsoupe#125**, which added transparent auto-recovery so a client caching a stale session id keeps working instead of erroring out. This completes that path for servers that authenticate per-request. ### Problem On the StreamableHTTP transport, when a non-`initialize` request arrives for an expired/unknown session, the recovery path (`find_or_create_session` -> `start_and_auto_initialize_session` -> `Session.auto_initialize/1`) runs the recovered `init/2` (and the optional `handle_session_expired/2`) with an **empty `frame.assigns` and empty `frame.context`**. `auto_initialize/1` takes no transport context, so the triggering request's `conn.assigns`, headers, remote IP, and auth claims are dropped. `Anubis.Server.Frame`'s moduledoc says assigns inherit from `Plug.Conn.assigns` for HTTP transports. That holds on the normal request path but not on recovery: a server that authenticates in a Plug (setting `conn.assigns.current_user`) and reads it back in `init/2` sees an *unauthenticated* frame on any request that triggers recovery, even though that request carried valid credentials. ### Change - Add `Session.auto_initialize/2 (session, transport_context)`; keep `auto_initialize/1` delegating with `nil` (backwards-compatible). - Thread the request's transport context down the plug's unknown-session recovery branch into `auto_initialize/2`. Only that branch is affected; `initialize` and existing-session branches are unchanged. The notification/response paths (which already return 404 for unknown sessions) are untouched. - In the recovery handler, apply the live request's assigns to the recovered frame and pass the context to `prepare_frame/2` so `frame.context` (headers/remote_ip/auth) is populated. Both `handle_session_expired/2` and `init/2` now see the populated frame, consistent with the normal request path. ### Rationale for replace assigns instead of merging On the recovery path the live request's assigns **replace** the frame's assigns (only when the request actually carries assigns. An empty/absent set leaves any store-restored assigns untouched). This is deliberate rather than a merge: The session store round-trips assigns through serialization - `Frame.to_saved`/ `from_saved` do no key normalization - so a JSON-backed store returns assigns with **string keys** (`"current_user"`) while live `conn.assigns` are **atom keys** (`:current_user`). A key-wise merge would leave *both* present: a host reading the atom key gets the live value, but a stale string-keyed value silently survives alongside it, leaving a latent auth bug. Atom-normalizing untrusted stored strings is an atom-table-exhaustion risk, so the recovery path treats the live per-request assigns as authoritative and replaces rather than reconciling. ### Tests - Recovered frame carries live request assigns (no store). - Recovered `frame.context` carries headers / remote_ip / auth. - Live assigns replace stale store assigns (the string-vs-atom key case above); the stale key does not survive. - Store-only recovery preserved when the request carries no assigns. - `auto_initialize/1` backwards-compat unchanged. Verified end-to-end by driving a `conn` with `conn.assigns` set through the plug to the recovery branch and confirming the recovered session's frame carries them. `mix lint` (format + credo --strict + dialyzer) and `mix test` pass. --------- Co-authored-by: zoey <zoey.spessanha@zeetech.io>
…oedsoupe#209) ## Summary Enable `Process.flag(:trap_exit, true)` in `Session.init/1` so explicit supervisor shutdown (e.g. StreamableHTTP client `DELETE`) runs `terminate/2` and emits `[:anubis_mcp, :server, :terminate]` telemetry. ## Motivation `Anubis.Server.Session` did not trap exits while the transport processes (`streamable_http`, `sse`, `stdio`) already do. When a session is stopped via `DynamicSupervisor.terminate_child/2` (the DELETE path), OTP delivers a `:shutdown` exit to a non-trapping GenServer, which terminates immediately **without** calling `terminate/2`. That silently skips: - library `[:anubis_mcp, :server, :terminate]` telemetry - optional server-module `terminate/2` cleanup hooks Idle-expiry shutdown already worked because it uses callback-initiated `{:stop, ...}`. Fixes the outstanding part 4 of zoedsoupe#204 (session idle expiry and assigns recovery are tracked separately in zoedsoupe#208). ## Changes - `lib/anubis/server/session.ex`: set `trap_exit` at the top of `init/1` - `test/anubis/server/session_test.exs`: regression test stopping a session via `DynamicSupervisor.terminate_child/2` and asserting terminate telemetry ## Tests - `mix test test/anubis/server/session_test.exs` — 21 tests, 0 failures - `mix test test/anubis/server/session_test.exs:537` — new regression test passes ## Notes Session task work already uses `Task.Supervisor.async_nolink/2` + monitors, matching the existing transport `trap_exit` pattern. No new `handle_info` for `{:EXIT, ...}` is required. --------- Co-authored-by: syf2211 <syf2211@users.noreply.github.com> Co-authored-by: zoey <zoey.spessanha@zeetech.io>
zoedsoupe#210) Addresses behaviour 4 in zoedsoupe#204. ### Problem `Anubis.Server.Session` is `use GenServer` and its `init/1` returns `{:ok, state, :hibernate}` without `Process.flag(:trap_exit, true)`. The library already traps exits on its transport processes (`server/transport/streamable_http.ex`, `server/transport/stdio.ex`), just not on `Session`. Because of that, when a session is stopped via the supervisor — `Supervisor.stop_session/3` -> `DynamicSupervisor.terminate_child/2`, a `:shutdown` exit — the non-trapping process dies immediately without running `terminate/2` (standard OTP: a non-trapping process receiving `:shutdown` terminates without invoking `terminate/2`). This is the path a spec-compliant client `DELETE /mcp` takes (`handle_delete -> delete_session_from_store -> stop_session_process -> Supervisor.stop_session`). On it: - the library's own `[:anubis_mcp, :server, :terminate]` telemetry (emitted before the `exported?(module, :terminate, 2)` gate) never fires; - the server module's optional `terminate/2` never fires — so any cleanup/observability a host hangs off `terminate/2` is silently skipped on explicit disconnect. The idle-expiry path already works, because `handle_info(:session_expired, ...)` returns `{:stop, {:shutdown, :session_expired}, ...}`, and a callback-initiated `{:stop, …}` always runs `terminate/2`. The gap is specifically the explicit-stop (DELETE) path — arguably the more common close for short-lived tool sessions. ### Change - `Process.flag(:trap_exit, true)` in `Session.init/1`. With trapping, the supervisor's `:shutdown` is delivered through the `gen_server` loop and `terminate/2` (+ the `[:anubis_mcp, :server, :terminate]` telemetry) runs on every stop path — explicit DELETE, idle expiry, and supervisor shutdown alike — consistent with how the transports already behave. - An explicit `handle_info({:EXIT, _pid, _reason}, state)` clause that ignores the signal. Session's general `handle_info` catch-all forwards unmatched messages to the host's `module.handle_info/2`; this clause keeps a stray `{:EXIT, …}` (from a process a host links to the session pid) from leaking into the host callback. ### Trapping rationale Trapping was deemed safe due to: - Tasks are spawned with `Task.Supervisor.async_nolink`, so they are not linked to the session — task crashes deliver `{:DOWN, …}` (already handled) / `{ref, result}`, never `{:EXIT, …}`. Trapping changes nothing for tasks. - The session links only to its supervisor parent; a parent exit signal to a trapping GenServer is intercepted by the `gen_server` loop and triggers normal termination (which runs `terminate/2`) rather than being dispatched to `handle_info`. That interception is exactly what makes the fix work. - So in normal operation the session receives no `{:EXIT, …}` at `handle_info`; the explicit clause is defense-in-depth for the exotic host-link case. ### Tests - Host `terminate/2` fires when the session is stopped via the supervisor (`DynamicSupervisor.terminate_child`, the mechanism `Supervisor.stop_session/3` uses), asserted via telemetry; the process is confirmed dead. - The library `[:anubis_mcp, :server, :terminate]` telemetry fires on the same path, with the session id in metadata. Both fail before the change (the session dies on `:shutdown` without running `terminate/2`) and pass after. `mix lint` (format + credo --strict + dialyzer) and `mix test` pass. --------- Co-authored-by: zoey <zoey.spessanha@zeetech.io>
…st (follow-up to zoedsoupe#180) (zoedsoupe#213) **Follow-up to zoedsoupe#180 (issue zoedsoupe#177).** zoedsoupe#180 fixed the client transport dropping the configured `:headers` (e.g. `Authorization`) on the SSE **GET** request. The same bug is still present on the **DELETE** session-teardown leg — this completes the pair. ### Problem `Anubis.Transport.StreamableHTTP.delete_session/1` builds its DELETE request headers from a bare `%{}`: ```elixir defp delete_session(state) do headers = put_session_header(%{}, state.session_id) ... ``` So a spec-compliant client `DELETE /mcp` reaches the server without the configured headers — an authenticated client's session close arrives **unauthenticated**. A server that authorizes the DELETE (or just logs the caller) sees no `Authorization`. This is the exact shape of the bug zoedsoupe#180 already fixed for the SSE GET. Every other request leg bases its headers on `state.headers`: - POST — `send_http_request/3`: `state.headers |> Map.put("accept", ...) |> ...` - SSE GET — `build_sse_headers/1` (the zoedsoupe#180 fix): `state.headers |> Map.put("accept", "text/event-stream") |> ...` DELETE was the one remaining leg still starting from `%{}`. zoedsoupe#180 fixed GET but didn't touch DELETE; this is the natural completion of that change. ### Solution Base the DELETE headers on `state.headers`, exactly like the POST and GET legs: ```elixir - headers = put_session_header(%{}, state.session_id) + headers = put_session_header(state.headers, state.session_id) ``` `put_session_header/2` just adds the session id (or no-ops when absent), so this preserves all configured headers and still attaches `mcp-session-id`. `state.headers` defaults to `%{}` when none are configured, so the no-headers case is unchanged. DELETE carries no body and expects none, so it correctly does **not** take the `accept`/`content-type` headers that `build_sse_headers`/the POST path add — only the configured headers plus the session id. ### Test Added alongside the zoedsoupe#180 GET-headers test in the "headers and options" describe: establish a session (a POST that returns `mcp-session-id`), then `shutdown/1` (which issues the DELETE), and assert the DELETE carried the configured `Authorization` header. Fails before the change (the header arrives as `nil`) and passes after. `mix lint` and `mix test` pass. Co-authored-by: zoey <zoey.spessanha@zeetech.io>
…edsoupe#218) ## Problem The Streamable HTTP transport can push a message to a single session's SSE stream (`route_to_session/3`) or broadcast to every connected handler (`send_message/3`), but there is no way to deliver to a *selected subset* of connected subscribers, and no way for a host to attach application context to a subscriber or observe connected-handler counts. This is the single-node building block behind the cross-node delivery discussed in zoedsoupe#189 / zoedsoupe#190. ## Solution Add opaque per-subscriber metadata plus selector-based delivery: - `register_sse_handler/3` stores an opaque `metadata` map verbatim; the 2-arity form delegates with `%{}`. The transport never interprets the map. Handlers stay keyed by `session_id`, so `get`/`route`/`unregister` remain O(1); the stored value widens from `{pid, ref}` to `{pid, ref, metadata}`. - `handler_count/1` (total) and `handler_count/2` (a predicate over metadata) expose connected-handler gauges. - `send_message_to_subscribers/4` fans a message out to every handler whose metadata satisfies a caller-supplied selector -- filling the gap between `route_to_session/3` (one session) and `send_message/3` (all handlers). - The Plug gains a `:subscriber_metadata` option -- a `(Plug.Conn.t() -> map())` callback invoked when an SSE stream opens -- so hosts can tag subscribers from the request (tenant, user, feature scope). Defaults to `%{}`, so existing behavior is unchanged. ## Rationale Server-initiated delivery to an application-defined subset of subscribers is a general MCP-server need (see zoedsoupe#189 / zoedsoupe#190). The library cannot know a host's routing dimensions, so metadata is kept fully opaque and host-populated via the plug callback; the transport provides only storage + selector primitives. Keeping the `session_id` key (rather than re-keying by `{session_id, pid}`) preserves the existing O(1) lookups. This is not spec-mandated -- it is a capability extension layered on the existing per-session SSE registry -- and I'm happy to adjust the shape (e.g. the callback signature) to your preference. ## Tests - Transport: `register_sse_handler/3` metadata round-trip + `handler_count/1,2`; 2-arg default of `%{}`; `send_message_to_subscribers/4` delivers only to matching subscribers. - Plug: `init/1` stores and defaults the `:subscriber_metadata` callback; end-to-end GET SSE registration attaches the configured metadata (verified via `handler_count/2`). Co-authored-by: zoey <zoey.spessanha@zeetech.io>
…econnect flap (zoedsoupe#215) ## Problem When a new GET/SSE stream registers for a session that already has a live SSE handler, the Streamable HTTP transport demonitors the incumbent and sends it `:close_sse`. A spec-compliant MCP client holding a standalone GET stream (e.g. via the MCP SDK / `mcp-remote`) treats a server-initiated close as a signal to immediately reconnect. That reconnect races the next registration and produces an unbounded register/close flap (`sse_handler_registered` / `sse_closing` churn). During each no-handler window, server-to-client notifications broadcast via `send_message` are silently dropped, because there is no registered handler to receive them. ## Solution Keep the takeover semantics (the newly registered handler becomes the active handler for the session) but stop proactively closing the superseded handler in `handle_call({:register_sse_handler, ...})`. The two prior branches collapse into one that only calls `Process.demonitor(old_ref, [:flush])`. The superseded handler is reaped by its own connection lifecycle: `Anubis.SSE.Streaming.start/4` wraps the receive loop in `try/after on_close.()`, and the Plug installs `on_close: unregister_sse_handler(transport, session_id, handler_pid)`. The `expected_pid` guard added in zoedsoupe#109 ensures the superseded handler's eventual unregister cannot evict the handler that took over. ## Rationale MCP Streamable HTTP permits a client to hold a standalone GET/SSE stream for server-initiated messages, and a server-initiated close is a legitimate reconnect trigger for the client -- which is exactly why proactively closing on takeover produced the flap. Letting the old connection close on its own lifecycle preserves takeover without the churn. ## Tests Adds a transport test: two handlers register on the same session; the second becomes the active handler (asserted via `get_sse_handler`), and the first receives no `:close_sse` (`refute_receive`). Existing transport suite unchanged. Co-authored-by: zoey <zoey.spessanha@zeetech.io>
Owner
|
hey, do you mind to solve ci issues + open comments? |
Owner
|
hey, i've messed with the history but i've fixed and manually merged your contribution. sorry for this mess up and thanks again for your contribution!! |
Merged
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).
Contributor
Author
No worries and thank you for getting this merged into the project! |
This was referenced Jul 16, 2026
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Streamable HTTP transport does not implement the MCP spec's "Resumability and Redelivery" feature. Clients that drop and reconnect the standalone GET SSE stream lose any server-to-client messages sent during the gap, and the transport never honors the
Last-Event-IDheader the spec defines for replay.Solution
Add opt-in resumability for the standalone SSE stream:
EventStorebehaviour (append/replay/latest_id/delete/child_spec, optionalresolve_name/2), wired via the transport's:event_storeoption using the same{module, opts}shape as:task_store.EventStore.InMemory): session-scoped monotonic ids, per-session ring (:history_size), LRU session cap (:max_sessions), validated bounds.retry:field.Last-Event-IDparsing on GET and in-order replay of recorded events before live delivery. The dedupe floor is seeded from the ids actually replayed (not the client cursor), so a stale/reset cursor never drops live events.:stream_graceclose timer so dropped sessions cannot leak or thrash the store.:event_storekeeps the exact legacy per-connection id behavior; the deprecated 2024-11-05 SSE transport is unaffected.Rationale
Resumability and Redelivery via
Last-Event-IDis an explicit, named part of the MCP Streamable HTTP transport spec (2025-03-26 / 2025-11-25). The design mirrors the existing:task_storepattern (pluggable behaviour + batteries-included in-memory default + opt-in wiring) so it stays idiomatic and preserves legacy behavior byte-for-byte when unconfigured. Relates to the session-recovery pain in #204 / #175.Coordination
The supervisor child-assembly overlaps #188 (both add to
build_http_children). The two are additive and compose cleanly; if #188 lands first I'll rebase the event-store child on top.Tests
event_store/in_memory_test.exs: id monotonicity/isolation, replay ordering and bounded-history gaps, delete idempotency, LRU session eviction, bound validation.resumability_test.exs: priming (fresh / high-water / retry), replay-then-live ordering, exactly-once dedupe across the register/replay race, stale-cursor safety, transport recording incl. reconnect-gap and append-failure fail-closed, grace-timer close/keep, and end-to-end PlugLast-Event-IDwiring.