From 8b408e554e0d8cd95d238558fcf2b901fcae1951 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Sun, 23 Aug 2026 01:36:06 +0200 Subject: [PATCH 1/4] fix: resume sessions with MCP configuration Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- doc/api/API.html | 2 +- doc/api/github.copilot-sdk.client.html | 2 +- doc/api/github.copilot-sdk.html | 2 +- doc/api/overview.html | 5 +- doc/mcp/overview.md | 15 ++-- doc/reference/API.md | 11 +-- src/github/copilot_sdk.clj | 5 +- src/github/copilot_sdk/client.clj | 71 +++-------------- .../integration/tools_config_test.clj | 79 +++---------------- 9 files changed, 35 insertions(+), 157 deletions(-) diff --git a/doc/api/API.html b/doc/api/API.html index 575b16f..675b7d2 100644 --- a/doc/api/API.html +++ b/doc/api/API.html @@ -326,7 +326,7 @@

resume-session :large-output map Tool output handling config. Forwarded on session.resume as the official SDK’s largeOutput field. -

When :mcp-servers is present, the SDK first resumes the session and then calls session.mcp.reloadWithConfig with the same converted server configuration. This applies to blocking and async resume, and therefore to join-session. Reload errors propagate, and the partially registered local session is removed; the SDK does not silently fall back for older runtimes. Reload requests use a bounded timeout of at least 60 seconds, extended to the largest configured :mcp-timeout plus a five-second transport margin.

+

When :mcp-servers is present, the SDK sends the converted server configuration as mcpServers in the session.resume request. This applies to blocking and async resume, and therefore to join-session. Omitting the key omits mcpServers; an empty map sends an empty configuration.

When :on-permission-request is set to default-join-session-permission-handler, the SDK sends requestPermission: false on the wire, telling the CLI that this client does not handle permission requests. Any other handler sends requestPermission: true.

;; Resume with a different model and reasoning effort
 (copilot/resume-session client "session-123"
diff --git a/doc/api/github.copilot-sdk.client.html b/doc/api/github.copilot-sdk.client.html
index a808b66..03624ce 100644
--- a/doc/api/github.copilot-sdk.client.html
+++ b/doc/api/github.copilot-sdk.client.html
@@ -87,7 +87,7 @@
 

ping

(ping client)(ping client message)

Ping the server to check connectivity. Returns {:message :timestamp :protocol-version}.

:timestamp is either an ISO 8601 date-time string (e.g. “2026-05-21T08:00:00Z”; CLI ≥ 1.0.51, upstream PR #1340) or a numeric epoch-millis value, depending on the CLI version; the SDK forwards whatever the server sends.

resume-session

(resume-session client session-id config)

Resume an existing session by ID.

-

Config options (:on-permission-request is optional since upstream PR #1308 — omit it to leave permission requests pending for manual resolution via copilot/handle-pending-permission-request!): - :on-permission-request - Permission handler function (optional, e.g. approve-all). When omitted, permission requests are surfaced as :copilot/permission.requested events and remain pending until resolved by the application. - :client-name - Client name to identify the application (included in User-Agent header) - :model - Change the model for the resumed session - :tools - Tools exposed to the CLI server - :system-message - System message configuration {:mode :content} - :available-tools - List of tool names to allow - :excluded-tools - List of tool names to disable - :tool-search - Tool discovery config {:enabled :defer-threshold} - :provider - Custom provider configuration (BYOK) - :streaming? - Enable streaming responses - :mcp-servers - MCP server configurations. When present, resume applies the same config with session.mcp.reloadWithConfig after session.resume; reload failures fail the resume and clean up the local session. - :custom-agents - Custom agent configurations - :default-agent - Built-in agent config, e.g. {:excluded-tools “private_tool”} - :config-directory - Override configuration directory. :config-dir remains a deprecated alias. - :skill-directories - Directories to load skills from - :disabled-skills - Skills to disable - :infinite-sessions - Infinite session configuration - :memory - Persistent memory config {:enabled boolean} (upstream PR #1617). Parity with create-session; omitted when unset. - :reasoning-effort - Reasoning effort level: “low”, “medium”, “high”, or “xhigh” - :github-token - GitHub token for this session (sent as gitHubToken) - :on-user-input-request - Handler for ask_user requests - :on-elicitation-request - Handler for elicitation requests (upstream PRs #908, #960). Single-arg handler receives an ElicitationContext map with :session-id, :message, :requested-schema, :mode, :elicitation-source, :url. Returns an ElicitationResult map; omit :content for decline/cancel results. - :on-mcp-auth-request - Handler for interactive MCP OAuth requests (upstream PR #1669). Same shape as create-session. On resume, interest in mcp.oauth_required is registered before the resume RPC so OAuth needed while the runtime replays state reaches the handler. - :hooks - Lifecycle hooks map - :on-event - Event handler (1-arg fn) registered before the RPC call. Guarantees early events like session.start are not missed. - :enable-config-discovery - Boolean. Auto-discover .mcp.json, skills, etc. (upstream PR #1044) - :enable-mcp-apps - Boolean (@experimental). See create-session; explicit true sends requestMcpApps: true on resume, while false and omission send nothing. (https://github.com/github/copilot-sdk/pull/1335) - :model-capabilities - Model capabilities override map (upstream PR #1029). Same shape as create-session; :adaptive-thinking and :max-output-tokens are experimental CLI-protocol extras. - :include-sub-agent-streaming-events? - Boolean. When true (default), streaming events from sub-agents are forwarded to this session’s event stream. (upstream PR #1108) - :enable-session-telemetry? - Boolean. See create-session (upstream PR #1224). - :excluded-builtin-agents - Vector of strings. See create-session (upstream PR #1865). - :enable-citations - Boolean (@experimental). See create-session (upstream PR #1865). - :enable-file-change-tracking? - Boolean. See create-session. On resume, tracking can start only when the runtime still has a valid baseline; earlier untracked turns cannot be reconstructed. - :session-limits - Map (@experimental). See create-session (upstream PR #1865). - :enable-managed-settings? - Boolean. See create-session (upstream PR #1925). - :request-extensions? - Boolean. See create-session; explicit false is forwarded. - :extension-sdk-path - String path override for extension subprocesses. See create-session. - :extension-info - Stable extension identity {:source string :name string}. - :canvas-provider - Map {:id .. :name ..}. See create-session (upstream PR #1847). - :on-exit-plan-mode - Handler for exitPlanMode.request RPCs. See create-session (upstream PR #1228). - :on-auto-mode-switch - Handler for autoModeSwitch.request RPCs. See create-session (upstream PR #1228).

+

Config options (:on-permission-request is optional since upstream PR #1308 — omit it to leave permission requests pending for manual resolution via copilot/handle-pending-permission-request!): - :on-permission-request - Permission handler function (optional, e.g. approve-all). When omitted, permission requests are surfaced as :copilot/permission.requested events and remain pending until resolved by the application. - :client-name - Client name to identify the application (included in User-Agent header) - :model - Change the model for the resumed session - :tools - Tools exposed to the CLI server - :system-message - System message configuration {:mode :content} - :available-tools - List of tool names to allow - :excluded-tools - List of tool names to disable - :tool-search - Tool discovery config {:enabled :defer-threshold} - :provider - Custom provider configuration (BYOK) - :streaming? - Enable streaming responses - :mcp-servers - MCP server configurations, applied as part of session.resume. - :custom-agents - Custom agent configurations - :default-agent - Built-in agent config, e.g. {:excluded-tools “private_tool”} - :config-directory - Override configuration directory. :config-dir remains a deprecated alias. - :skill-directories - Directories to load skills from - :disabled-skills - Skills to disable - :infinite-sessions - Infinite session configuration - :memory - Persistent memory config {:enabled boolean} (upstream PR #1617). Parity with create-session; omitted when unset. - :reasoning-effort - Reasoning effort level: “low”, “medium”, “high”, or “xhigh” - :github-token - GitHub token for this session (sent as gitHubToken) - :on-user-input-request - Handler for ask_user requests - :on-elicitation-request - Handler for elicitation requests (upstream PRs #908, #960). Single-arg handler receives an ElicitationContext map with :session-id, :message, :requested-schema, :mode, :elicitation-source, :url. Returns an ElicitationResult map; omit :content for decline/cancel results. - :on-mcp-auth-request - Handler for interactive MCP OAuth requests (upstream PR #1669). Same shape as create-session. On resume, interest in mcp.oauth_required is registered before the resume RPC so OAuth needed while the runtime replays state reaches the handler. - :hooks - Lifecycle hooks map - :on-event - Event handler (1-arg fn) registered before the RPC call. Guarantees early events like session.start are not missed. - :enable-config-discovery - Boolean. Auto-discover .mcp.json, skills, etc. (upstream PR #1044) - :enable-mcp-apps - Boolean (@experimental). See create-session; explicit true sends requestMcpApps: true on resume, while false and omission send nothing. (https://github.com/github/copilot-sdk/pull/1335) - :model-capabilities - Model capabilities override map (upstream PR #1029). Same shape as create-session; :adaptive-thinking and :max-output-tokens are experimental CLI-protocol extras. - :include-sub-agent-streaming-events? - Boolean. When true (default), streaming events from sub-agents are forwarded to this session’s event stream. (upstream PR #1108) - :enable-session-telemetry? - Boolean. See create-session (upstream PR #1224). - :excluded-builtin-agents - Vector of strings. See create-session (upstream PR #1865). - :enable-citations - Boolean (@experimental). See create-session (upstream PR #1865). - :enable-file-change-tracking? - Boolean. See create-session. On resume, tracking can start only when the runtime still has a valid baseline; earlier untracked turns cannot be reconstructed. - :session-limits - Map (@experimental). See create-session (upstream PR #1865). - :enable-managed-settings? - Boolean. See create-session (upstream PR #1925). - :request-extensions? - Boolean. See create-session; explicit false is forwarded. - :extension-sdk-path - String path override for extension subprocesses. See create-session. - :extension-info - Stable extension identity {:source string :name string}. - :canvas-provider - Map {:id .. :name ..}. See create-session (upstream PR #1847). - :on-exit-plan-mode - Handler for exitPlanMode.request RPCs. See create-session (upstream PR #1228). - :on-auto-mode-switch - Handler for autoModeSwitch.request RPCs. See create-session (upstream PR #1228).

Returns a CopilotSession.

set-foreground-session-id!

(set-foreground-session-id! client session-id)

Set the foreground session (TUI+server mode). Requests the TUI to switch to displaying the specified session.

start!

(start! client)

Start the CLI server and establish connection. Blocks until connected or throws on error.

diff --git a/doc/api/github.copilot-sdk.html b/doc/api/github.copilot-sdk.html index 3e9e468..7abf337 100644 --- a/doc/api/github.copilot-sdk.html +++ b/doc/api/github.copilot-sdk.html @@ -296,7 +296,7 @@

telemetry is a map of string bucket names to JSON object maps.

resume-factory!

(resume-factory! session run-id)(resume-factory! session run-id options)

resume-session

(resume-session client session-id config)

Resume an existing session by ID. Accepts the same config options as create-session (except :session-id and :cloud), including the experimental :enable-mcp-apps host opt-in, plus: - :disable-resume? - When true, skip emitting the session.resume event (default: false)

:on-permission-request is optional (since upstream PR #1308) — omit it to leave permission requests pending for manual resolution.

-

When :mcp-servers is present, resume applies the same configuration with session.mcp.reloadWithConfig after session.resume. A reload failure fails the resume and cleans up the local session.

+

When :mcp-servers is present, the configuration is applied by session.resume.

Example:

(def session (copilot/resume-session client "session-123"
                {:on-permission-request copilot/approve-all}))
diff --git a/doc/api/overview.html b/doc/api/overview.html
index 5be7246..28432ee 100644
--- a/doc/api/overview.html
+++ b/doc/api/overview.html
@@ -118,9 +118,8 @@ 

Remote Serve :mcp-headers map No HTTP headers (e.g., for authentication) -

Resume-Time Reload

-

When :mcp-servers is supplied to resume-session or <resume-session, the SDK sends the configuration with session.resume and then applies the same converted map through session.mcp.reloadWithConfig. join-session inherits this behavior because it resumes the parent session internally.

-

The reload is part of the resume contract: an RPC error or closed response fails the operation and removes the partially registered local session. There is no silent fallback for runtimes that do not support the reload method. Omitting :mcp-servers sends neither a reload request nor an empty configuration.

+

Resume-Time Configuration

+

When :mcp-servers is supplied to resume-session or <resume-session, the SDK sends the converted configuration as mcpServers in the session.resume request. join-session inherits this behavior because it resumes the parent session internally. Omitting :mcp-servers omits the wire key; an empty map sends an empty configuration.

Disabling MCP Servers

Disable specific servers for a session without removing their :mcp-servers entry:

(copilot/with-client-session [session
diff --git a/doc/mcp/overview.md b/doc/mcp/overview.md
index 8615294..8a4ea1d 100644
--- a/doc/mcp/overview.md
+++ b/doc/mcp/overview.md
@@ -126,18 +126,13 @@ Here's a complete working example using the official [`@modelcontextprotocol/ser
 | `:mcp-defer-tools` | keyword | No | **Experimental, CLI-only:** tool-deferral policy `:auto` or `:never`. Wire-encoded as `deferTools` (upstream schema 1.0.63); this field is not exposed by the official Node SDK's `MCPServerConfig`. |
 | `:mcp-headers` | map | No | HTTP headers (e.g., for authentication) |
 
-### Resume-Time Reload
+### Resume-Time Configuration
 
 When `:mcp-servers` is supplied to `resume-session` or `wire mcp-servers)}})
-
-(def ^:private default-mcp-reload-timeout-ms 60000)
-(def ^:private mcp-reload-transport-margin-ms 5000)
-
-(defn- mcp-reload-timeout-ms
-  [mcp-servers]
-  (reduce
-   max
-   default-mcp-reload-timeout-ms
-   (keep (fn [{:keys [mcp-timeout]}]
-           (when mcp-timeout
-             (+ mcp-timeout mcp-reload-transport-margin-ms)))
-         (vals mcp-servers))))
-
 (defn- resume-session-result*
   [client session-id config]
   (validate-provider-config! config)
@@ -3038,12 +3020,6 @@
       (install-session-fs-handler! client session-id session config)
       (register-mcp-auth-interest! client session-id config)
       (let [result (proto/send-request! connection-io "session.resume" params)]
-        (when (contains? config :mcp-servers)
-          (proto/send-request!
-           connection-io
-           "session.mcp.reloadWithConfig"
-           (mcp-reload-with-config-params session-id (:mcp-servers config))
-           (mcp-reload-timeout-ms (:mcp-servers config))))
         (session/set-workspace-path! client session-id (:workspace-path result))
         (session/set-capabilities! client session-id (:capabilities result))
         (session/set-open-canvases! client session-id (:open-canvases result))
@@ -3077,9 +3053,7 @@
    - :tool-search        - Tool discovery config {:enabled :defer-threshold}
    - :provider           - Custom provider configuration (BYOK)
    - :streaming?         - Enable streaming responses
-   - :mcp-servers        - MCP server configurations. When present, resume applies the same
-                           config with session.mcp.reloadWithConfig after session.resume;
-                           reload failures fail the resume and clean up the local session.
+   - :mcp-servers        - MCP server configurations, applied as part of session.resume.
    - :custom-agents      - Custom agent configurations
    - :default-agent      - Built-in agent config, e.g. {:excluded-tools [\"private_tool\"]}
    - :config-directory   - Override configuration directory.
@@ -3340,41 +3314,14 @@
                     (session/remove-session! client session-id)
                     (ex-info (str "Failed to resume session: " (:message err))
                              {:error err :session-id session-id}))
-                (let [result (:result response)
-                      reload? (contains? config :mcp-servers)
-                      reload-response
-                      (when reload?
-                        (
Date: Sun, 23 Aug 2026 01:36:13 +0200
Subject: [PATCH 2/4] fix(examples): isolate resumed manual tool

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---
 examples/manual_tool_resume.clj | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/examples/manual_tool_resume.clj b/examples/manual_tool_resume.clj
index 8ade2d0..4d5a7c3 100644
--- a/examples/manual_tool_resume.clj
+++ b/examples/manual_tool_resume.clj
@@ -43,7 +43,8 @@
    the upstream Node/Python/Rust samples exactly. `run` executes the flow once
    and fails loudly if a pending request is lost; there is no retry or fallback."
   (:require [clojure.core.async :refer [alts!! timeout]]
-            [github.copilot-sdk :as copilot :refer [evt]]))
+            [github.copilot-sdk :as copilot :refer [evt]]
+            [github.copilot-sdk.tool-set :as tool-set]))
 
 ;; See examples/README.md for usage
 
@@ -154,7 +155,9 @@
    1-second `pause!` before the next client mirrors the upstream samples'
    lifecycle."
   [{:keys [model] :or {model "claude-haiku-4.5"}}]
-  (let [config {:model model :tools [tool]}
+  (let [config {:model model
+                :tools [tool]
+                :available-tools [(tool-set/custom (:tool-name tool))]}
         ;; Lifecycle 1: ask for the tool, capture the pending permission, suspend.
         {:keys [session-id permission-id]}
         (with-suspended-client

From b9072057dce4172f27771e923848231aa83f7f7f Mon Sep 17 00:00:00 2001
From: Karl Krukow 
Date: Sun, 23 Aug 2026 01:36:52 +0200
Subject: [PATCH 3/4] chore: certify upstream through cc0438d

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---
 .copilot-schema-version                       |   2 +-
 .github/skills/update-upstream/SKILL.md       |   5 +
 CHANGELOG.md                                  |  16 +-
 schemas/README.md                             |   2 +-
 schemas/api.schema.json                       |  13 +-
 .../integration/stable_sync_cc0438d_test.clj  | 129 +++++++++++++
 .../integration/stable_sync_ea41d_test.clj    |  16 +-
 .../stable_upstream_delta_cc0438d.edn         | 178 ++++++++++++++++++
 8 files changed, 333 insertions(+), 28 deletions(-)
 create mode 100644 test/github/copilot_sdk/integration/stable_sync_cc0438d_test.clj
 create mode 100644 test/resources/stable_upstream_delta_cc0438d.edn

diff --git a/.copilot-schema-version b/.copilot-schema-version
index a528c8a..0c161af 100644
--- a/.copilot-schema-version
+++ b/.copilot-schema-version
@@ -1 +1 @@
-1.0.81-5
+1.0.81-6
diff --git a/.github/skills/update-upstream/SKILL.md b/.github/skills/update-upstream/SKILL.md
index dce58fc..9ede113 100644
--- a/.github/skills/update-upstream/SKILL.md
+++ b/.github/skills/update-upstream/SKILL.md
@@ -141,9 +141,14 @@ without copying temporary audit notes into evergreen docs. Add a concise
 Use the smallest targeted gates during iteration, then run:
 
 ```bash
+COPILOT_UPSTREAM_VALIDATION=true bb test
 bb ci:full
 ```
 
+The first command validates committed exact-pin evidence against the resolved
+local upstream checkout. Normal `bb test` and CI remain hermetic when that
+external checkout is unavailable.
+
 If authenticated end-to-end prerequisites are unavailable, run `bb ci` and
 state the limitation. Review example output, generated diffs, and the
 machine-readable parity inventory.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef130e6..46a6606 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,9 +28,9 @@ All notable changes to this project will be documented in this file. This change
   [upstream PR #2364](https://github.com/github/copilot-sdk/pull/2364))
 
 ### Changed (post-v1.0.12-preview.0 sync)
-- Updated the runtime schema pin from `1.0.80` to `1.0.81-5` and recertified
+- Updated the runtime schema pin from `1.0.80` to `1.0.81-6` and recertified
   the complete stable Node SDK public surface through upstream commit
-  [`ea41dadb199725766d5097f4592c17be3200035f`](https://github.com/github/copilot-sdk/commit/ea41dadb199725766d5097f4592c17be3200035f).
+  [`cc0438d66e3e68c333537cb935d9425d4e4ed8d5`](https://github.com/github/copilot-sdk/commit/cc0438d66e3e68c333537cb935d9425d4e4ed8d5).
   Experimental assisted-approval controls, ephemeral UI queries, factory
   lifecycle events, generated-only RPC declarations, and Node-specific package
   resolution remain intentionally excluded.
@@ -40,16 +40,20 @@ All notable changes to this project will be documented in this file. This change
   `:assisted-approval-model`. This replaces the removed aggregate allow-all
   booleans and `"off"` / `"auto"` / `"on"` mode fields.
 - `resume-session`, ` report-resource io/resource slurp edn/read-string))
+
+(defn- resolve-upstream
+  []
+  (let [{:keys [exit out err]}
+        (sh/sh "bash"
+               ".github/skills/update-upstream/scripts/resolve-upstream.sh")]
+    (when-not (zero? exit)
+      (throw (ex-info "Could not resolve the upstream checkout"
+                      {:exit exit :stderr err})))
+    (str/trim out)))
+
+(def ^:private upstream-repo
+  (delay
+    (when upstream-validation-enabled?
+      (resolve-upstream))))
+
+(defn- git-lines
+  [upstream & args]
+  (let [{:keys [exit out err]}
+        (apply sh/sh (concat ["git" "-C" upstream] args))]
+    (when-not (zero? exit)
+      (throw (ex-info "Could not inspect the upstream checkout"
+                      {:args args :exit exit :stderr err})))
+    (->> (str/split-lines out)
+         (remove str/blank?)
+         vec)))
+
+(defn- sha256-file
+  [path]
+  (let [digest (MessageDigest/getInstance "SHA-256")
+        bytes (Files/readAllBytes (Paths/get path (make-array String 0)))]
+    (format "%064x" (BigInteger. 1 (.digest digest bytes)))))
+
+(defn- evidence-references
+  [report]
+  (set (concat
+        (mapcat :evidence (:ported-internal-deltas report))
+        (mapcat :evidence (:intentional-exclusions report)))))
+
+(deftest upstream-range-and-classification-are-exact
+  (let [{:keys [base target exact-commits classified-files]} (read-report)
+        expected-commits (mapv :sha exact-commits)
+        expected-files (mapcat :paths classified-files)]
+    (is (= "ea41dadb199725766d5097f4592c17be3200035f" base))
+    (is (= "cc0438d66e3e68c333537cb935d9425d4e4ed8d5" target))
+    (is (= ["21d738ea2cad2b49bb40b125046a29c0880f1504"
+            "cc0438d66e3e68c333537cb935d9425d4e4ed8d5"]
+           expected-commits))
+    (is (= 31 (count expected-files)))
+    (is (= (count expected-files) (count (distinct expected-files))))
+    (is (every? allowed-classifications
+                (map :classification classified-files)))
+    (when-let [upstream @upstream-repo]
+      (let [actual-commits (git-lines upstream "rev-list" "--reverse"
+                                      (str base ".." target))
+            actual-files (git-lines upstream "diff" "--name-only"
+                                    (str base ".." target))]
+        (is (= expected-commits actual-commits))
+        (is (= (set expected-files) (set actual-files)))
+        (is (= (count actual-files) (count (distinct actual-files))))))))
+
+(deftest public-surface-and-evidence-are-complete
+  (let [{:keys [target public-surface-audit ported-internal-deltas
+                source-evidence] :as report}
+        (read-report)
+        evidence-ids (set (keys source-evidence))]
+    (is (empty? (:stable-public-deltas public-surface-audit)))
+    (is (empty? (:unclassified-deltas public-surface-audit)))
+    (is (= #{:resume/mcp-config-in-resume-only}
+           (set (map :id ported-internal-deltas))))
+    (is (= evidence-ids (evidence-references report)))
+    (when-let [upstream @upstream-repo]
+      (doseq [[evidence-id {:keys [path symbol]}] source-evidence]
+        (testing (name evidence-id)
+          (let [{:keys [exit out]}
+                (sh/sh "git" "-C" upstream "grep" "-F"
+                       symbol target "--" path)]
+            (is (zero? exit))
+            (is (str/includes? out symbol))))))))
+
+(deftest current-runtime-schema-is-exact
+  (let [{:keys [schema version]} (read-report)
+        api-schema (json/read-str (slurp "schemas/api.schema.json"))
+        definitions (get api-schema "definitions")
+        account-login (get definitions "AccountLoginRequest")
+        permission-mode-source (get definitions "PermissionModeSource")
+        approve-all-source (get definitions "PermissionsSetApproveAllSource")]
+    (is (= (:runtime-pin schema)
+           (str/trim (slurp ".copilot-schema-version"))))
+    (is (= (get-in schema [:api :sha256])
+           (sha256-file "schemas/api.schema.json")))
+    (is (= (get-in schema [:session-events :sha256])
+           (sha256-file "schemas/session-events.schema.json")))
+    (is (= #{"host" "token"} (set (get account-login "required"))))
+    (is (contains? (get account-login "properties") "login"))
+    (is (= false (get account-login "additionalProperties")))
+    (is (= "experimental" (get permission-mode-source "stability")))
+    (is (contains? (set (get permission-mode-source "enum"))
+                   "user_setting"))
+    (is (contains? (set (get approve-all-source "enum"))
+                   "user_setting"))
+    (is (= {:sdk "1.0.11.0"
+            :changed? false
+            :release-required? false}
+           version))))
diff --git a/test/github/copilot_sdk/integration/stable_sync_ea41d_test.clj b/test/github/copilot_sdk/integration/stable_sync_ea41d_test.clj
index cb3a761..5ffad04 100644
--- a/test/github/copilot_sdk/integration/stable_sync_ea41d_test.clj
+++ b/test/github/copilot_sdk/integration/stable_sync_ea41d_test.clj
@@ -8,20 +8,11 @@
             [clojure.test :refer [deftest is testing]]
             [github.copilot-sdk :as sdk]
             [github.copilot-sdk.generated.event-specs :as generated-events]
-            [github.copilot-sdk.specs])
-  (:import [java.nio.file Files Paths]
-           [java.security MessageDigest]))
+            [github.copilot-sdk.specs]))
 
 (def ^:private stable-delta-resource
   "resources/stable_upstream_delta_ea41d.edn")
 
-(defn- sha256
-  [path]
-  (let [digest (.digest (MessageDigest/getInstance "SHA-256")
-                        (Files/readAllBytes
-                         (Paths/get path (make-array String 0))))]
-    (apply str (map #(format "%02x" (bit-and % 0xff)) digest))))
-
 (defn- read-schema
   [path]
   (with-open [reader (io/reader path)]
@@ -59,11 +50,6 @@
                "session-events.schema.json"
                "9fd414f5020c317a234da6d7a06a4d0ef02ddad227ddc9962dced49302e5e8ec"}
                (get-in report [:upstream :schema-sha256])))
-        (is (= (get-in report [:upstream :schema-sha256])
-               {"api.schema.json" (sha256 "schemas/api.schema.json")
-               "session-events.schema.json"
-               (sha256 "schemas/session-events.schema.json")}))
-        (is (= "1.0.81-5\n" (slurp ".copilot-schema-version")))
         (is (= (:stable-delta-ids report)
                (set (map :id stable-deltas))))
         (is (empty? (:unclassified-stable report)))
diff --git a/test/resources/stable_upstream_delta_cc0438d.edn b/test/resources/stable_upstream_delta_cc0438d.edn
new file mode 100644
index 0000000..2d70551
--- /dev/null
+++ b/test/resources/stable_upstream_delta_cc0438d.edn
@@ -0,0 +1,178 @@
+{:base "ea41dadb199725766d5097f4592c17be3200035f"
+ :target "cc0438d66e3e68c333537cb935d9425d4e4ed8d5"
+ :target-position :post-v1.0.12-preview.0-main
+ :exact-commits
+ [{:sha "21d738ea2cad2b49bb40b125046a29c0880f1504"
+   :pull-request "https://github.com/github/copilot-sdk/pull/2367"
+   :classification :mixed
+   :status :internal-subset-ported
+   :reason "The Node resume implementation and runtime pin affect compatibility; generated RPC, language-specific, and test changes do not add stable package-root API."}
+  {:sha "cc0438d66e3e68c333537cb935d9425d4e4ed8d5"
+   :pull-request "https://github.com/github/copilot-sdk/pull/2370"
+   :classification :test-harness
+   :status :reviewed-no-port
+   :reason "Replay-cassette normalization is confined to the upstream shared test harness."}]
+ :public-surface-audit
+ {:package-root "nodejs/src/index.ts"
+  :inspected
+  ["nodejs/src/index.ts"
+   "nodejs/src/types.ts"
+   "nodejs/src/client.ts"
+   "nodejs/src/session.ts"
+   "nodejs/src/extension.ts"
+   "nodejs/src/toolSet.ts"
+   "nodejs/src/factory.ts"]
+  :stable-public-deltas []
+  :unclassified-deltas []}
+ :ported-internal-deltas
+ [{:id :resume/mcp-config-in-resume-only
+   :classification :internal
+   :status :ported
+   :upstream-paths ["nodejs/package.json" "nodejs/src/client.ts"]
+   :clojure-paths
+   [".copilot-schema-version"
+    "src/github/copilot_sdk.clj"
+    "src/github/copilot_sdk/client.clj"
+    "test/github/copilot_sdk/integration/tools_config_test.clj"
+    "doc/reference/API.md"
+    "doc/mcp/overview.md"
+    "CHANGELOG.md"]
+   :evidence
+   [:node-package-pin
+    :node-package-root
+    :resume-config-type
+    :resume-session-implementation
+    :resume-mcp-wire]
+   :contract
+   {:public-export :unchanged
+    :public-type :unchanged
+    :builders [:resume-session :
Date: Sun, 23 Aug 2026 01:44:50 +0200
Subject: [PATCH 4/4] test: cover resume MCP omission semantics

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---
 .../integration/tools_config_test.clj         | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/test/github/copilot_sdk/integration/tools_config_test.clj b/test/github/copilot_sdk/integration/tools_config_test.clj
index 0527b32..317db2f 100644
--- a/test/github/copilot_sdk/integration/tools_config_test.clj
+++ b/test/github/copilot_sdk/integration/tools_config_test.clj
@@ -118,6 +118,26 @@
            (select-keys (second resume-request) [:sessionId :mcpServers])))
     (is (empty? reloads))))
 
+(deftest test-resume-mcp-server-omission-and-empty-map-wire-shape
+  (doseq [[label config expected-present?]
+          [["omitted input omits mcpServers" {} false]
+           ["empty input sends an empty mcpServers object"
+            {:mcp-servers {}}
+            true]]]
+    (testing label
+      (let [seed (sdk/create-session *test-client* {})
+            session-id (sdk/session-id seed)
+            resume-params (atom nil)
+            _ (mock/set-request-hook!
+               *mock-server*
+               (fn [method params]
+                 (when (= "session.resume" method)
+                   (reset! resume-params params))))
+            _ (sdk/resume-session *test-client* session-id config)]
+        (is (= expected-present? (contains? @resume-params :mcpServers)))
+        (when expected-present?
+          (is (= {} (:mcpServers @resume-params))))))))
+
 (deftest test-custom-agent-mcp-server-ids-on-wire
   (testing "custom-agent MCP server IDs and config shapes survive create and resume"
     (let [seen (atom {})