@dsh-cc/mcp-client 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +201 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +149 -0
  4. package/README.zh.md +150 -0
  5. package/lib/auth.d.ts +66 -0
  6. package/lib/auth.d.ts.map +1 -0
  7. package/lib/auth.js +121 -0
  8. package/lib/auth.js.map +1 -0
  9. package/lib/connection.d.ts +98 -0
  10. package/lib/connection.d.ts.map +1 -0
  11. package/lib/connection.js +409 -0
  12. package/lib/connection.js.map +1 -0
  13. package/lib/defer.d.ts +39 -0
  14. package/lib/defer.d.ts.map +1 -0
  15. package/lib/defer.js +40 -0
  16. package/lib/defer.js.map +1 -0
  17. package/lib/index.d.ts +129 -0
  18. package/lib/index.d.ts.map +1 -0
  19. package/lib/index.js +198 -0
  20. package/lib/index.js.map +1 -0
  21. package/lib/invariant.d.ts +16 -0
  22. package/lib/invariant.d.ts.map +1 -0
  23. package/lib/invariant.js +22 -0
  24. package/lib/invariant.js.map +1 -0
  25. package/lib/prompts.d.ts +44 -0
  26. package/lib/prompts.d.ts.map +1 -0
  27. package/lib/prompts.js +166 -0
  28. package/lib/prompts.js.map +1 -0
  29. package/lib/registry.d.ts +94 -0
  30. package/lib/registry.d.ts.map +1 -0
  31. package/lib/registry.js +101 -0
  32. package/lib/registry.js.map +1 -0
  33. package/lib/resources.d.ts +42 -0
  34. package/lib/resources.d.ts.map +1 -0
  35. package/lib/resources.js +136 -0
  36. package/lib/resources.js.map +1 -0
  37. package/lib/stdio-stderr.d.ts +66 -0
  38. package/lib/stdio-stderr.d.ts.map +1 -0
  39. package/lib/stdio-stderr.js +187 -0
  40. package/lib/stdio-stderr.js.map +1 -0
  41. package/lib/tools.d.ts +161 -0
  42. package/lib/tools.d.ts.map +1 -0
  43. package/lib/tools.js +373 -0
  44. package/lib/tools.js.map +1 -0
  45. package/lib/transport.d.ts +50 -0
  46. package/lib/transport.d.ts.map +1 -0
  47. package/lib/transport.js +79 -0
  48. package/lib/transport.js.map +1 -0
  49. package/package.json +62 -0
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/mcp/mcp-client/README.md
5
+ README.md: 5c2ae4c5c608554a6c3b5ea02c41a80a4e36a08b
6
+ README.zh.md: 655256c6931361b80d269bb37f2e5f83c9e1dc34
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # @dsh-cc/mcp-client
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ MCP client bridge plugin: connects to external [Model Context Protocol](https://modelcontextprotocol.io/) servers and registers their tools on `ctx.tools`, making them available to the model as native tools under server-qualified names (`mcp__<serverName>__<rawName>`).
6
+
7
+ ## Usage
8
+
9
+ One plugin instance per MCP server in `cordis.yml`:
10
+
11
+ ```yaml
12
+ - id: mcp-github
13
+ name: '@dsh-cc/mcp-client'
14
+ config:
15
+ serverName: github
16
+ transport: stdio
17
+ command: npx
18
+ args: ['-y', '@modelcontextprotocol/server-github']
19
+ env:
20
+ GITHUB_TOKEN: !!js process.env.GITHUB_TOKEN
21
+
22
+ - id: mcp-web
23
+ name: '@dsh-cc/mcp-client'
24
+ config:
25
+ serverName: web
26
+ transport: streamable-http
27
+ url: http://localhost:3000/mcp
28
+ headers:
29
+ Authorization: !!js '`Bearer ${process.env.MCP_TOKEN}`'
30
+
31
+ - id: mcp-feed
32
+ name: '@dsh-cc/mcp-client'
33
+ config:
34
+ serverName: feed
35
+ transport: sse
36
+ url: http://localhost:3001/sse
37
+ oauth:
38
+ redirectUrl: http://localhost:8787/callback
39
+ ```
40
+
41
+ The model sees `mcp__github__create_issue`, `mcp__web__search`, … — the same server-qualified shape Claude Code and Codex use. HMR hot-swaps: editing the entry triggers disconnect + reconnect without process restart; an unchanged `serverName` reproduces identical tool names.
42
+
43
+ ## Config
44
+
45
+ | Field | Transport | Required | Description |
46
+ |---|---|---|---|
47
+ | `transport` | both | yes | `"stdio"`, `"streamable-http"`, or `"sse"` |
48
+ | `serverName` | both | yes | Namespace for this server's model-facing tool names; `[A-Za-z0-9_-]{1,32}`, unique across live instances |
49
+ | `command` | stdio | yes | Executable to spawn |
50
+ | `args` | stdio | no | Arguments passed to the command |
51
+ | `env` | stdio | no | Extra env vars merged on top of scrubbed ambient env |
52
+ | `cwd` | stdio | no | Working directory for the child process |
53
+ | `url` | http | yes | MCP server URL |
54
+ | `headers` | http | no | Extra headers (e.g. auth tokens) |
55
+ | `oauth` | http | no | OAuth flow options (see below) |
56
+ | `toolCallTimeoutMs` | both | no | Timeout per `callTool` invocation (default 60000) |
57
+ | `failOnStartupError` | both | no | Reject plugin activation when initial connection or tool synchronization fails (default `false`) |
58
+ | `reconnect.enabled` | both | no | Reconnect automatically after a lost connection (default `true`) |
59
+ | `reconnect.initialDelayMs` | both | no | First reconnect delay in ms; doubles per consecutive failed attempt (default 500) |
60
+ | `reconnect.maxDelayMs` | both | no | Backoff ceiling in ms; also the uptime after which the attempt budget resets (default 30000) |
61
+ | `reconnect.maxAttempts` | both | no | Consecutive failed attempts per outage before giving up for good (default 10) |
62
+
63
+ ## Tool naming
64
+
65
+ Every MCP tool has two names: the raw MCP name (sent on the wire in `tools/call`) and the public name `mcp__<serverName>__<rawName>` registered on `ctx.tools`. Public names are normalized to the DeepSeek function-name contract (64 chars, `[A-Za-z0-9_-]`); when replacement or truncation changes the name, a deterministic 12-hex-char hash of `(serverName, rawName)` is appended so distinct tools never collapse into one name. Names are pure functions of `(serverName, rawName)` — connection order, re-syncs, and other servers never rename a tool.
66
+
67
+ - Two servers publishing the same raw name (e.g. `search`) coexist under their namespaces.
68
+ - A duplicate `serverName` across live instances fails the later plugin instance at load.
69
+ - A server listing the same tool name twice is rejected as an invalid tool list.
70
+ - A foreign registration squatting on this server's namespace rolls back the whole generation (never a partial set), with a loud error.
71
+
72
+ ## Behavior
73
+
74
+ - On connect: plugin activation awaits `listTools()` and publishes each tool under its public name before the composition starts its first turn — eagerly via `ctx.tools.register()`, or deferred through `ctx.toolSearch.registerDeferred` when that seam is mounted and the server lists at least `deferToolThreshold` tools (default 8). Initial connection, discovery, or registration failure is always logged; it rejects activation when `failOnStartupError` is true and otherwise activates with no tools.
75
+ - Listens for `notifications/tools/list_changed` → re-syncs; a fetch-phase failure keeps the previous generation registered, while a registration conflict rolls back the attempted generation and leaves no tools from that server.
76
+ - Tool execute: `client.callTool({ name: rawName, arguments }, { signal })` with timeout + abort support—the public name is never sent to the server.
77
+ - Canonical success is `{ content: JsonValue[], structuredContent? }`; complete JSON MCP blocks survive for programmatic callers. A supported advertised `outputSchema` validates `structuredContent`; unsupported schema vocabulary falls back to unconstrained `JsonValue`.
78
+ - Native/model rendering keeps the existing text projection: text blocks join with newlines while image, audio, resource, and unsupported blocks become placeholders.
79
+ - On disconnect/crash: the supervisor restarts the original server config with exponential backoff (`reconnect.initialDelayMs` doubling up to `reconnect.maxDelayMs`) and re-runs discovery on success — the recovered generation replaces the previous one, so tools neither duplicate nor leak. During the outage the last good generation stays registered; calls against it fail until recovery.
80
+ - Reconnection is budgeted per outage: after `reconnect.maxAttempts` consecutive failures the server's tools are unregistered and reconnection stops until an HMR reload or Host restart. A connection that survives past `maxDelayMs` resets the budget, so an occasionally-crashing server recovers indefinitely while a crash-looping one — even with briefly successful connects — still exhausts the cap instead of restarting forever.
81
+ - Reconnect states are user-visible in logs: reconnecting (warn, with attempt count and delay), recovered (info), final failure and disabled-loss (error). Disposal cancels any pending reconnect. With `reconnect.enabled: false`, a lost connection keeps tools registered but failing until a reload — the manual-recovery behavior.
82
+ - Stdio server stderr is piped, never inherited, so it cannot paint a TUI. The stream is appended to `$DSH_HOME/mcp-logs/<serverName>.log`, rotated at 4 MiB with one `.log.1` backup generation and a trimmed tail is attached to connection-failure / connection-lost warns. Headless users no longer see live server stderr on the terminal.
83
+
84
+ ## Capabilities
85
+
86
+ Beyond tools, the bridge exposes a server's other MCP capabilities when the server declares them:
87
+
88
+ - **Resources** — exposed as two server-qualified model tools, `mcp__<serverName>__list_mcp_resources` and `mcp__<serverName>__read_mcp_resource`, which call `resources/list` and `resources/read`. The `ctx.fs` seam (a real-filesystem abstraction over `FsTarget`/`readBytes`) cannot represent virtual MCP resources, so these bridge tools are the fallback the capability seam prescribes. Resource-list-change notifications re-register the bridge.
89
+ - **Prompts** — each MCP prompt is registered as a skill on `ctx.skills` when the prompt capability is declared. Skill names map the `mcp__<server>__<prompt>` shape onto the registry's lowercase-kebab grammar (`mcp-<server>-<prompt>`). Argumentless prompts are resolved via `prompts/get` and their rendered text becomes the skill body; prompts that require arguments are documented with their argument contract instead. MCP-sourced skills are inert prose — never executable shell. Prompt-list-change notifications re-register the skills. Prompt bridging requires the `ctx.skills` service; without it the bridge is a no-op.
90
+ - **OAuth** — network transports (`streamable-http`, `sse`) accept an `oauth` block. Tokens, registered-client information, the PKCE code verifier, and discovery state are persisted through the `ctx.credentials` reference seam (stored under derived credential references, never inline). The MCP SDK drives RFC 9728 → RFC 8414 metadata discovery, dynamic client registration, scheme-code PKCE, and token refresh through the provider; a mid-session `401` is retried once after dropping the stale token state. OAuth requires the `ctx.credentials` service.
91
+
92
+ The supervisor keeps each capability's registrations live across an outage (last-good wins) and removes them on give-up or disposal, all through one serialized swap chain.
93
+
94
+ ## Services consumed
95
+
96
+ | Service | Usage |
97
+ |---|---|
98
+ | `ctx.tools` | Register/unregister MCP tools and the resource bridge |
99
+ | `ctx.toolSearch` | Optional: defer over-threshold listed tools (duck-typed; never a required inject) |
100
+ | `ctx.skills` | Optional: register MCP prompt skills |
101
+ | `ctx.credentials` | Optional: persist OAuth tokens and related state |
102
+
103
+ ## Model Experience
104
+
105
+ ### Discovered MCP tools
106
+
107
+ #### What the model sees
108
+
109
+ After initial discovery succeeds, each advertised MCP tool appears as a native tool named `mcp__<serverName>__<rawName>` (or its deterministic normalized form), with the server-provided description and input schema. A successful re-sync — including the one after an automatic reconnect — replaces the generation; plugin disposal or an exhausted reconnect budget removes it.
110
+
111
+ When the `ctx.toolSearch` seam is mounted and this server lists at least **8 tools** (the default `deferToolThreshold`; counts the `tools/list` length including alwaysLoad tools), deferrable tools register **deferred** instead: they contribute name + description + a server-qualified search hint to the ToolSearch pool and stay out of the model-visible schema until a ToolSearch hit activates them (`activate` performs the real registration, callable exactly like any top-of-prompt tool afterward). A tool advertising `_meta['anthropic/alwaysLoad'] === true` registers eagerly even on a deferred server. Resource-bridge tools (`mcp__<server>__list_mcp_resources` / `read_mcp_resource`) always stay eager. Without the toolSearch seam — standalone deployments, other presets — every tool registers eagerly at any threshold.
112
+
113
+ A generation swap (reconnect or `tools/list_changed`) disposes the previous generation first, which also unloads tools the model had activated via ToolSearch: after the swap those names are searchable again, and the model must search (or the session must re-activate) before calling them. Identical re-syncs on the same client are skipped by fingerprint and re-publish nothing.
114
+
115
+ #### Token effect
116
+
117
+ Data-dependent schema cost is paid on every request while the tools are registered — deferred tools pay it only after activation, replacing their schema cost with one ToolSearch tool plus per-hit activations. Re-sync replaces rather than accumulates schemas, and the server-qualified name adds tokens to every tool definition and call.
118
+
119
+ #### KV Cache effect
120
+
121
+ Prefix-stable while the discovered tool set and schemas are unchanged. A re-sync that adds, removes, renames, or changes a tool replaces definitions and may invalidate reuse from the first changed schema token; a reconnect that recovers an unchanged list reproduces identical definitions and stays prefix-stable. The first deferred registration also inserts the ToolSearch tool into the model-visible set (one-time prefix change; it stays registered afterward — hysteresis).
122
+
123
+ ### Tool-call history and results
124
+
125
+ #### What the model sees
126
+
127
+ The public tool name and JSON arguments remain in assistant history. Text result blocks are joined with newlines into one retained Native text result; image, audio, resource, and unsupported blocks become short placeholders there. Their full JSON blocks and optional structured content remain in the execution-local canonical value, and MCP `isError` rejects the call through the registry's error path.
128
+
129
+ #### Token effect
130
+
131
+ Arguments and mapped text are retained until compaction. Binary and resource payloads are discarded rather than added to context.
132
+
133
+ #### KV Cache effect
134
+
135
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
136
+
137
+ ## Known Limitations and Deferred Work
138
+
139
+ - **The defer threshold is per-server tool count, not context size** — a percentage-of-context or cross-server aggregate rule (Claude Code's ~10%) would defer more aggressively; three small servers of five tools each all stay eager. The threshold is also not configurable via plugin Config yet (tests pass it into `syncTools` directly).
140
+ - **ToolSearch activation is process-global** — loading a deferred MCP tool makes it visible to every agent whose tool restriction admits the name, and there is no per-conversation discovery history (see the `dsh-tool-search` README).
141
+ - **Programmatic `ctx.tools.execute` on a reserved-but-unactivated MCP name fails as unknown** — the definition does not exist until a ToolSearch hit activates it; no in-repo caller relies on this.
142
+ - **Exact frontmatter MCP names must match the public (possibly hashed) name** — server-prefix wildcards sidestep most of that.
143
+
144
+ - **Dynamic OAuth client registration is delegated to the MCP SDK** — the SDK owns RFC 9728/8414 metadata discovery, PKCE, dynamic client registration, and token refresh; this package supplies the credentials-backed persistence half of the `OAuthClientProvider` seam and the single 401 retry. An interactive `redirectToAuthorization` logs the URL for a headless host rather than driving a browser; completing the flow still requires an operator.
145
+ - **Startup timeout is inherited from the MCP SDK** — DSH does not yet expose a connection/discovery timeout. Each initialize or paginated `tools/list` request uses the SDK's 60-second default, so an unresponsive server or cursor chain can delay both activation and teardown while the initial synchronization settles.
146
+ - **Reconnect triggers on transport close** — a crashed stdio child fires it; Streamable HTTP failures surface per request and through the SDK transport's own SSE-stream recovery, so an unreachable HTTP server is retried per call rather than respawned by the supervisor.
147
+ - **Native non-text rendering is lossy** — image, audio, and resource payloads become placeholders in model context even though the execution-local canonical value preserves their JSON blocks. Richer Native multimedia projection is deferred.
148
+ - **Unsupported MCP output schemas are not enforced** — `structuredContent` falls back to `JsonValue` when the advertised schema uses vocabulary outside the harness subset.
149
+ - **Resource bridging uses the two-tool fallback, not a filesystem provider** — the `ctx.fs` seam cannot represent virtual MCP resources; the ListMcpResources/ReadMcpResource model tools are the current surface.
package/README.zh.md ADDED
@@ -0,0 +1,150 @@
1
+ # @dsh-cc/mcp-client
2
+
3
+ [English](README.md) | 中文
4
+
5
+ MCP 客户端桥接插件:连接外部 [Model Context Protocol](https://modelcontextprotocol.io/) 服务器,把它们的工具注册到 `ctx.tools`,使模型能够通过服务器限定名称(`mcp__<serverName>__<rawName>`)将其作为原生工具使用。
6
+
7
+ ## 用法
8
+
9
+ `cordis.yml` 中每个 MCP 服务器使用一个插件实例:
10
+
11
+ ```yaml
12
+ - id: mcp-github
13
+ name: '@dsh-cc/mcp-client'
14
+ config:
15
+ serverName: github
16
+ transport: stdio
17
+ command: npx
18
+ args: ['-y', '@modelcontextprotocol/server-github']
19
+ env:
20
+ GITHUB_TOKEN: !!js process.env.GITHUB_TOKEN
21
+
22
+ - id: mcp-web
23
+ name: '@dsh-cc/mcp-client'
24
+ config:
25
+ serverName: web
26
+ transport: streamable-http
27
+ url: http://localhost:3000/mcp
28
+ headers:
29
+ Authorization: !!js '`Bearer ${process.env.MCP_TOKEN}`'
30
+
31
+ - id: mcp-feed
32
+ name: '@dsh-cc/mcp-client'
33
+ config:
34
+ serverName: feed
35
+ transport: sse
36
+ url: http://localhost:3001/sse
37
+ oauth:
38
+ redirectUrl: http://localhost:8787/callback
39
+ ```
40
+
41
+ 模型会看到 `mcp__github__create_issue`、`mcp__web__search` 等工具,这与 Claude Code 和 Codex 使用的服务器限定形状相同。HMR(热模块替换)支持热替换:编辑配置项会触发断开 + 重新连接,无需重启进程;`serverName` 不变时会生成完全相同的工具名称。
42
+
43
+ ## 配置
44
+
45
+ | 字段 | 传输 | 必填 | 描述 |
46
+ |---|---|---|---|
47
+ | `transport` | 两者 | 是 | `"stdio"`、`"streamable-http"` 或 `"sse"` |
48
+ | `serverName` | 两者 | 是 | 该服务器面向模型工具名称的 namespace;`[A-Za-z0-9_-]{1,32}`,在存活实例中唯一 |
49
+ | `command` | stdio | 是 | 要 spawn 的可执行文件 |
50
+ | `args` | stdio | 否 | 传给命令的参数 |
51
+ | `env` | stdio | 否 | 合并到已清理环境中的额外环境变量 |
52
+ | `cwd` | stdio | 否 | 子进程工作目录 |
53
+ | `url` | http | 是 | MCP 服务器 URL |
54
+ | `headers` | http | 否 | 额外标头(例如认证 token) |
55
+ | `oauth` | http | 否 | OAuth 流程选项(见下文) |
56
+ | `toolCallTimeoutMs` | 两者 | 否 | 每次 `callTool` 调用的超时(默认 60000) |
57
+ | `failOnStartupError` | 两者 | 否 | 初始连接或工具同步失败时拒绝插件激活(默认 `false`) |
58
+ | `reconnect.enabled` | 两者 | 否 | 连接丢失后自动重新连接(默认 `true`) |
59
+ | `reconnect.initialDelayMs` | 两者 | 否 | 首次重连延迟(毫秒);每次连续失败尝试翻倍(默认 500) |
60
+ | `reconnect.maxDelayMs` | 两者 | 否 | 退避上限(毫秒);同时也是重置尝试预算所需的正常运行时长(默认 30000) |
61
+ | `reconnect.maxAttempts` | 两者 | 否 | 每次中断期间连续失败尝试次数上限,超出后彻底放弃(默认 10) |
62
+
63
+ ## 工具命名
64
+
65
+ 每个 MCP 工具都有两个名称:通过 `tools/call` 在协议上传送的原始 MCP 名称,以及公开名称 `mcp__<serverName>__<rawName>`,后者注册到 `ctx.tools`。公开名称会规范化为 DeepSeek 函数名称约定(64 个字符、`[A-Za-z0-9_-]`);如果替换或截断改变名称,就会追加 `(serverName, rawName)` 的确定性 12 位十六进制 hash,确保不同工具绝不会折叠为同一个名称。名称是 `(serverName, rawName)` 的纯函数:连接顺序、重新同步和其他服务器永远不会重命名工具。
66
+
67
+ - 发布相同原始名称(例如 `search`)的两个服务器会在各自 namespace 下共存。
68
+ - 存活实例中的重复 `serverName` 会使后加载的插件实例失败。
69
+ - 服务器在工具列表中两次列出同一工具名称时,该列表会作为无效工具列表被拒绝。
70
+ - 外部注册抢占该服务器 namespace 时,会回滚整个世代(绝不保留部分集合),并明确报错。
71
+
72
+ ## 行为
73
+
74
+ - 连接时:插件激活会等待 `listTools()`,并在组合开始首个轮次前以公开名称发布每个工具——默认通过 `ctx.tools.register()` 即时注册;当 `ctx.toolSearch` seam 已挂载且该服务器列出的工具数达到 `deferToolThreshold`(默认 8)时,改为经 `registerDeferred` 延迟注册。初始连接、发现或注册失败始终会记录日志;`failOnStartupError` 为 true 时拒绝激活,否则插件仍会激活但不注册工具。
75
+ - 监听 `notifications/tools/list_changed` → 重新同步;获取阶段失败时保留上一世代的注册,注册冲突则会回滚本次尝试的世代,并且不保留该服务器的任何工具。
76
+ - 工具执行:`client.callTool({ name: rawName, arguments }, { signal })`,支持超时 + 中止;公开名称绝不会发给服务器。
77
+ - 规范成功值是 `{ content: JsonValue[], structuredContent? }`;完整的 JSON MCP 块会保留给编程调用方。受支持且已声明的 `outputSchema` 会验证 `structuredContent`;不受支持的 schema 词汇会回退为不受约束的 `JsonValue`。
78
+ - Native/模型渲染保留现有文本投影:文本块以换行连接,图片、音频、资源和不受支持的块会变成占位符。
79
+ - 断开/崩溃时:supervisor 以指数退避(`reconnect.initialDelayMs` 逐次翻倍,上限 `reconnect.maxDelayMs`)重启原始服务器配置,成功后重新执行发现——恢复的世代会替换前一个,因此工具既不会重复也不会泄漏。中断期间最后一个正常世代保持注册;针对它的调用在恢复前会失败。
80
+ - 重连按中断预算控制:连续失败达到 `reconnect.maxAttempts` 次后,该服务器的工具会被注销,重连停止,直到 HMR 重载或重启 Host。连接存活超过 `maxDelayMs` 会重置预算,因此偶尔崩溃的服务器可以无限恢复,而崩溃循环的服务器——即使短暂连接成功——仍会耗尽上限而非永远重启。
81
+ - 重连状态在日志中对用户可见:reconnecting(warn,含尝试次数和延迟)、recovered(info)、最终失败和 disabled-loss(error)。dispose(资源释放)会取消任何待执行的重连。设置 `reconnect.enabled: false` 时,连接丢失后工具保持注册但调用失败,直到重载——即手动恢复行为。
82
+ - stdio 服务器的 stderr 会被管道捕获、不再继承父进程,因此不会刷到 TUI。内容追加到 `$DSH_HOME/mcp-logs/<serverName>.log`,达到 4 MiB 时轮转并保留一份 `.log.1` 备份,截断后的尾部会附在连接失败 / 连接丢失的 warn 上。无界面运行时终端上也不再出现服务器的实时 stderr。
83
+
84
+ ## 消费的服务
85
+
86
+ | 服务 | 用途 |
87
+ |---|---|
88
+ | `ctx.tools` | 注册/注销 MCP 工具及资源桥 |
89
+ | `ctx.toolSearch` | 可选:把超过阈值的列出工具延迟注册(duck-type;不是必选 inject) |
90
+ | `ctx.skills` | 可选:注册 MCP 提示词技能 |
91
+ | `ctx.credentials` | 可选:持久化 OAuth token 及相关状态 |
92
+
93
+ ## 能力
94
+
95
+ 除工具外,当服务器声明其他 MCP 能力时,桥也会将其暴露出来:
96
+
97
+ - **资源(Resources)**——以两个服务器限定的模型工具 `mcp__<serverName>__list_mcp_resources` 与 `mcp__<serverName>__read_mcp_resource` 暴露,分别调用 `resources/list` 与 `resources/read`。`ctx.fs` seam(针对真实文件系统的抽象,基于 `FsTarget`/`readBytes`)无法表达虚拟 MCP 资源,因此这两个桥接工具正是能力 seam 规定的回退方案。资源列表变更通知会重新注册桥。
98
+ - **提示词(Prompts)**——当声明提示词能力时,每个 MCP 提示词都会作为技能注册到 `ctx.skills`。技能名把 `mcp__<server>__<prompt>` 形状映射到注册表的 lowercase-kebab 语法(`mcp-<server>-<prompt>`)。无参数提示词通过 `prompts/get` 解析,其渲染文本成为技能正文;需要参数的提示词则以参数约定作为文档。MCP 来源的技能是惰性纯文本——绝不包含可执行的 shell。提示词列表变更通知会重新注册技能。提示词桥需要 `ctx.skills` 服务;缺失时该桥为空操作。
99
+ - **OAuth**——网络传输(`streamable-http`、`sse`)接受 `oauth` 配置块。token、已注册客户端信息、PKCE code verifier 与发现状态通过 `ctx.credentials` 引用 seam 持久化(存储在派生的凭据引用下,绝不内联)。MCP SDK 通过 provider 驱动 RFC 9728 → RFC 8414 元数据发现、动态客户端注册、授权码 PKCE 与 token 刷新;会话中途的 `401` 会在丢弃过期 token 状态后重试一次。OAuth 需要 `ctx.credentials` 服务。
100
+
101
+ supervisor 在中断期间保持每个能力的注册存活(保留最后一个正常世代),并在放弃或 dispose 时移除,全部经由一条串行化的交换链。
102
+
103
+
104
+ ## 模型体验
105
+
106
+ ### 已发现的 MCP 工具
107
+
108
+ #### 模型看到的内容
109
+
110
+ 初始发现成功后,每个已声明的 MCP 工具都会显示为名为 `mcp__<serverName>__<rawName>`(或其确定性规范化形式)的原生工具,并携带服务器提供的描述和输入 schema。成功的重新同步——包括自动重连后的同步——会替换整个世代;对插件执行 dispose(资源释放)或重连预算耗尽会移除该世代。
111
+
112
+ 当 `ctx.toolSearch` seam 已挂载、且该服务器列出的工具数达到 **8 个**(默认 `deferToolThreshold`;按 `tools/list` 数组长度计,含 alwaysLoad 工具)时,可延迟的工具改为**延迟注册**:它们向 ToolSearch 池贡献名称 + 描述 + 服务器限定的搜索提示,在 ToolSearch 命中激活之前不进入模型可见 schema(激活执行真正的注册,此后与任何提示词顶部的工具无异)。声明 `_meta['anthropic/alwaysLoad'] === true` 的工具即使在延迟服务器上也立即注册。资源桥工具(`mcp__<server>__list_mcp_resources` / `read_mcp_resource`)始终保持即时注册。没有 toolSearch seam 时——独立部署、其他 preset——任何阈值下全部工具都即时注册。
113
+
114
+ 世代替换(重连或 `tools/list_changed`)会先注销上一世代,这同时会卸载模型此前通过 ToolSearch 激活的工具:替换之后这些名字重新变为可搜索,模型必须再次搜索(或由会话重新激活)才能调用。同一客户端上的相同重新同步由 fingerprint 跳过,不会重新发布任何内容。
115
+
116
+ #### Token 影响
117
+
118
+ 工具注册期间,每次请求都会承担数据相关的 schema 成本——延迟工具只在激活后才承担该成本,其 schema 成本被一个 ToolSearch 工具加每次命中的激活所取代。重新同步会替换而非累积 schema,服务器限定名称也会为每个工具定义和调用增加 token。
119
+
120
+ #### KV Cache 影响
121
+
122
+ 只要已发现工具集合及其 schema 不变,前缀就保持稳定。增加、移除、重命名或更改工具的重新同步会替换定义,并可能使从第一个变化的 schema token 起的复用失效;恢复了未变列表的重连会生成完全相同的定义,前缀保持稳定。首个延迟注册还会把 ToolSearch 工具插入模型可见集合(一次性前缀变化;此后保持注册——迟滞)。
123
+
124
+ ### 工具调用历史与结果
125
+
126
+ #### 模型看到的内容
127
+
128
+ 公开工具名称和 JSON 参数会保留在 assistant 历史中。文本结果块会以换行连接为一个保留的 Native 文本结果;图片、音频、资源和不受支持的块在其中变为简短占位符。它们的完整 JSON 块及可选结构化内容保留在执行局部的规范值中;MCP `isError` 会通过注册表的错误路径拒绝调用。
129
+
130
+ #### Token 影响
131
+
132
+ 参数和映射后的文本会保留到压缩(compaction)发生时。二进制与资源载荷会被丢弃,而不会加入上下文。
133
+
134
+ #### KV Cache 影响
135
+
136
+ 仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV-cache 条目失效。
137
+
138
+ ## 已知限制与暂缓事项
139
+
140
+ - **延迟阈值按服务器工具数计,而非上下文占比**:按上下文百分比或跨服务器聚合的规则(Claude Code 的约 10%)会更激进地延迟;三个各 5 个工具的小服务器全部保持即时注册。阈值也尚不能通过插件 Config 配置(测试直接把选项传入 `syncTools`)。
141
+ - **ToolSearch 激活是进程全局的**:加载一个延迟 MCP 工具后,凡工具限制放行该名字的 agent 都能看到它,且没有按会话的发现历史(见 `dsh-tool-search` README)。
142
+ - **对已预留但未激活的 MCP 名字执行程序化 `ctx.tools.execute` 会以 unknown 失败**:定义在 ToolSearch 命中激活之前并不存在;仓库内没有调用方依赖此路径。
143
+ - **frontmatter 里的精确 MCP 名字必须与公开名(可能带 hash 后缀)一致**:服务器前缀通配符可以绕开大多数此类问题。
144
+
145
+ - **动态 OAuth 客户端注册委托给 MCP SDK**:SDK 负责 RFC 9728/8414 元数据发现、PKCE、动态客户端注册与 token 刷新;本包只提供基于凭据的 `OAuthClientProvider` seam 持久化半边与单次 401 重试。交互式 `redirectToAuthorization` 在 headless 主机上只记录 URL 而不会驱动浏览器;完成流程仍需操作者介入。
146
+ - **启动超时继承自 MCP SDK**:DSH 尚未公开连接/发现超时。每次 initialize 请求或分页 `tools/list` 请求都使用 SDK 默认的 60 秒,因此在初始同步完成期间,无响应的 server 或 cursor chain 可能同时延迟激活与 teardown。
147
+ - **重连在传输关闭时触发**:崩溃的 stdio 子进程会触发重连;Streamable HTTP 失败通过每次请求以及 SDK 传输自身的 SSE(Server-Sent Events)流恢复机制暴露,因此不可达的 HTTP 服务器会按调用重试,而非由 supervisor 重新 spawn。
148
+ - **Native 非文本渲染有损**:图片、音频与资源载荷在模型上下文中会变成占位符,即使执行局部的规范值保留了其 JSON 块。更丰富的 Native 多媒体投影暂缓实现。
149
+ - **不强制执行不受支持的 MCP 输出 schema**:已声明 schema 使用 harness 子集之外的词汇时,`structuredContent` 会回退到 `JsonValue`。
150
+ - **资源桥使用两个工具的回退方案,而非文件系统 provider**:`ctx.fs` seam 无法表达虚拟 MCP 资源;当前表面是 ListMcpResources/ReadMcpResource 两个模型工具。
package/lib/auth.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * OAuth support: a `ctx.credentials`-backed `OAuthClientProvider` for the MCP
3
+ * SDK's streamable-HTTP and SSE transports.
4
+ *
5
+ * The SDK implements RFC 9728 → RFC 8414 metadata discovery, PKCE, dynamic
6
+ * client registration, and token refresh behind its `OAuthClientProvider`
7
+ * interface (driven by the transport's `authProvider` option). This module
8
+ * supplies the durable half of that seam: OAuth tokens, registered-client
9
+ * information, the PKCE code verifier, and discovery state are persisted through
10
+ * the `ctx.credentials` reference capability — values are stored under
11
+ * server-derived credential references rather than inline, so configuration
12
+ * surfaces never see the secret material.
13
+ *
14
+ * 401 handling: the transport auto-refreshes an expired access token against a
15
+ * stored refresh token before a request; a mid-session `401` surfaces as an
16
+ * `UnauthorizedError`, which the tool bridge retries once after invalidating
17
+ * and re-running the token flow ({@link retryUnauthorizedOnce}).
18
+ *
19
+ * @module
20
+ */
21
+ import type { Context } from '@deepseek-ai/cordis';
22
+ import type { OAuthClientProvider, OAuthDiscoveryState } from '@modelcontextprotocol/sdk/client/auth.js';
23
+ import type { OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
24
+ /** Config selecting an interactive OAuth flow for a network transport. */
25
+ export interface OAuthConfig {
26
+ /** Redirect URL the user agent returns to after authorizing. */
27
+ redirectUrl?: string;
28
+ /** Stable OAuth client name reported to the authorization server. */
29
+ clientName?: string;
30
+ /**
31
+ * Credential-reference prefix (defaults to `MCP_OAUTH_<SERVER>`); the provider
32
+ * derives `_TOKENS`, `_CLIENT`, `_VERIFIER`, and `_DISCOVERY` refs from it.
33
+ */
34
+ credentialPrefix?: string;
35
+ }
36
+ /**
37
+ * `ctx.credentials`-backed {@link OAuthClientProvider}. Every artifact is a
38
+ * JSON value persisted under a derived credential reference; absent refs mean
39
+ * "no state" (unconfigured), and an empty stored value counts as unset.
40
+ */
41
+ export declare class CredentialsOAuthClientProvider implements OAuthClientProvider {
42
+ private readonly ctx;
43
+ private readonly tokensRef;
44
+ private readonly clientRef;
45
+ private readonly verifierRef;
46
+ private readonly discoveryRef;
47
+ private readonly redirectUrlValue;
48
+ constructor(ctx: Context, serverName: string, config: OAuthConfig);
49
+ get redirectUrl(): string | URL | undefined;
50
+ get clientMetadata(): OAuthClientMetadata;
51
+ clientInformation(): Promise<OAuthClientInformationMixed | undefined>;
52
+ saveClientInformation(clientInformation: OAuthClientInformationMixed): Promise<void>;
53
+ tokens(): Promise<OAuthTokens | undefined>;
54
+ saveTokens(tokens: OAuthTokens): Promise<void>;
55
+ redirectToAuthorization(authorizationUrl: URL): void;
56
+ saveCodeVerifier(codeVerifier: string): Promise<void>;
57
+ codeVerifier(): Promise<string>;
58
+ saveDiscoveryState(state: OAuthDiscoveryState): Promise<void>;
59
+ discoveryState(): Promise<OAuthDiscoveryState | undefined>;
60
+ invalidateCredentials(scope: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'): Promise<void>;
61
+ /** Read a credential value, treating an empty value as unset. */
62
+ private read;
63
+ /** Persist a value; an empty value is removed rather than stored. */
64
+ private write;
65
+ }
66
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAA;AACxG,OAAO,KAAK,EACV,2BAA2B,EAC3B,mBAAmB,EACnB,WAAW,EACZ,MAAM,0CAA0C,CAAA;AAGjD,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAQD;;;;GAIG;AACH,qBAAa,8BAA+B,YAAW,mBAAmB;IAQtE,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAe;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoB;gBAGlC,GAAG,EAAE,OAAO,EAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,WAAW;IAWrB,IAAI,WAAW,IAAI,MAAM,GAAG,GAAG,GAAG,SAAS,CAE1C;IAED,IAAI,cAAc,IAAI,mBAAmB,CAQxC;IAEK,iBAAiB,IAAI,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC;IAKrE,qBAAqB,CAAC,iBAAiB,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpF,MAAM,IAAI,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAK1C,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpD,uBAAuB,CAAC,gBAAgB,EAAE,GAAG,GAAG,IAAI;IAO9C,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAK/B,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D,cAAc,IAAI,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAK1D,qBAAqB,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAazG,iEAAiE;YACnD,IAAI;IAKlB,qEAAqE;YACvD,KAAK;CAOpB"}