<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Extension-System on ICE-ICE-BEAR-BLOG</title><link>https://ice-ice-bear.github.io/tags/extension-system/</link><description>Recent content in Extension-System on ICE-ICE-BEAR-BLOG</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 06 Apr 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://ice-ice-bear.github.io/tags/extension-system/index.xml" rel="self" type="application/rss+xml"/><item><title>Claude Code Harness Anatomy #5 — MCP Services and the Plugin-Skill Extension Ecosystem</title><link>https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-5/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-5/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Claude Code Harness Anatomy #5 — MCP Services and the Plugin-Skill Extension Ecosystem" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;Beyond its 42 built-in tools, Claude Code can extend with unlimited external tools via MCP (Model Context Protocol). This post analyzes the connection management architecture of &lt;code&gt;client.ts&lt;/code&gt; (3,348 lines), the OAuth authentication system of &lt;code&gt;auth.ts&lt;/code&gt; (2,465 lines), the 4-layer security model, and config deduplication. We then dissect the structural differences between plugins and skills, the 5-layer skill discovery engine, and the circular reference resolution pattern in &lt;code&gt;mcpSkillBuilders.ts&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="1-mcp-client--connection-management-is-harder-than-the-protocol"&gt;1. MCP Client &amp;ndash; Connection Management Is Harder Than the Protocol
&lt;/h2&gt;&lt;h3 id="memoization-based-connection-pool"&gt;Memoization-Based Connection Pool
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;connectToServer&lt;/code&gt; is wrapped with &lt;code&gt;lodash.memoize&lt;/code&gt;. The cache key is &lt;code&gt;name + JSON(config)&lt;/code&gt;. Since MCP servers are stateful (stdio processes, WebSocket connections), creating a new connection for every tool call would be catastrophically bad for performance.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onclose&lt;/code&gt; handler invalidates the cache -&amp;gt; next call automatically reconnects&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetchToolsForClient&lt;/code&gt; and &lt;code&gt;fetchResourcesForClient&lt;/code&gt; each have their own LRU cache (20 entries)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tool-proxy-pattern"&gt;Tool Proxy Pattern
&lt;/h3&gt;&lt;p&gt;MCP tools are converted to native &lt;code&gt;Tool&lt;/code&gt; interfaces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;: Format &lt;code&gt;mcp__&amp;lt;normalized_server&amp;gt;__&amp;lt;normalized_tool&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;call()&lt;/code&gt;: &lt;code&gt;ensureConnectedClient&lt;/code&gt; -&amp;gt; &lt;code&gt;callMCPToolWithUrlElicitationRetry&lt;/code&gt; -&amp;gt; &lt;code&gt;callMCPTool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkPermissions()&lt;/code&gt;: Always &lt;code&gt;passthrough&lt;/code&gt; — MCP tools use a separate permission system&lt;/li&gt;
&lt;li&gt;&lt;code&gt;annotations&lt;/code&gt;: Maps MCP annotations like &lt;code&gt;readOnlyHint&lt;/code&gt;, &lt;code&gt;destructiveHint&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;URL Elicitation Retry&lt;/strong&gt;: OAuth-based MCP servers can require authentication mid-tool-call (error code -32042). A retry loop shows the user the URL, waits for authentication to complete, and retries.&lt;/p&gt;
&lt;h3 id="connection-state-machine-and-3-strike-terminal-error"&gt;Connection State Machine and 3-Strike Terminal Error
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;stateDiagram-v2
 [*] --&gt; Pending: Config loaded
 Pending --&gt; Connected: connectToServer success
 Pending --&gt; Failed: Connection timeout
 Pending --&gt; NeedsAuth: 401 UnauthorizedError
 Pending --&gt; Disabled: isMcpServerDisabled()

 Connected --&gt; Connected: Tool call success
 Connected --&gt; Failed: 3 consecutive terminal errors
 Connected --&gt; NeedsAuth: 401 during callMCPTool
 Connected --&gt; Pending: onclose cache invalidation

 NeedsAuth --&gt; Pending: Auth completed
 NeedsAuth --&gt; NeedsAuth: 15-min TTL cache

 Failed --&gt; Pending: reconnectMcpServer()
 Disabled --&gt; Pending: toggleMcpServer()

 note right of Connected
 Exists in memoize cache
 fetchTools/Resources also cached
 end note&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;3-strike rule&lt;/strong&gt;: 3 consecutive terminal errors force a transition to &lt;code&gt;Failed&lt;/code&gt; state. This prevents endlessly retrying against dead servers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15-minute needs-auth cache&lt;/strong&gt;: Retrying a server that returned 401 every time would cause 30+ connectors to fire simultaneous network requests. The TTL cache prevents unnecessary retries.&lt;/p&gt;
&lt;h2 id="2-oauth--the-reality-of-2465-lines"&gt;2. OAuth &amp;ndash; The Reality of 2,465 Lines
&lt;/h2&gt;&lt;p&gt;The reason &lt;code&gt;auth.ts&lt;/code&gt; is 2,465 lines is that &lt;strong&gt;real-world OAuth servers don&amp;rsquo;t consistently implement the RFCs&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Component&lt;/th&gt;
 &lt;th&gt;Description&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;RFC 9728 + 8414 discovery&lt;/td&gt;
 &lt;td&gt;Server can run AS on a separate host -&amp;gt; discover AS URL via PRM&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PKCE&lt;/td&gt;
 &lt;td&gt;Public client — code_verifier/code_challenge required&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;XAA (Cross-App Access)&lt;/td&gt;
 &lt;td&gt;Exchange IdP id_token for access_token at the MCP server&amp;rsquo;s AS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Non-standard error normalization&lt;/td&gt;
 &lt;td&gt;Slack returns HTTP 200 with &lt;code&gt;{&amp;quot;error&amp;quot;:&amp;quot;invalid_grant&amp;quot;}&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Keychain storage&lt;/td&gt;
 &lt;td&gt;macOS Keychain integration (&lt;code&gt;getSecureStorage()&lt;/code&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Rust porting implications: OAuth is not an SDK dependency but a &lt;strong&gt;complex async state machine&lt;/strong&gt;. Discovery (2 stages) -&amp;gt; PKCE -&amp;gt; callback server -&amp;gt; token storage -&amp;gt; refresh -&amp;gt; revocation -&amp;gt; XAA. Porting the whole thing is impractical, so starting with stdio MCP + API key authentication is realistic.&lt;/p&gt;
&lt;h2 id="3-4-layer-security-model"&gt;3. 4-Layer Security Model
&lt;/h2&gt;&lt;p&gt;MCP security is not a single gate but a &lt;strong&gt;composition of trust levels&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 subgraph L1["1. Enterprise"]
 E1["managed-mcp.json&amp;lt;br/&amp;gt;If present, blocks all other sources"]
 E2["denylist / allowlist&amp;lt;br/&amp;gt;name, command, URL patterns"]
 end

 subgraph L2["2. Project"]
 P1[".mcp.json loaded"]
 P2["pending -&gt; user approval -&gt; approved"]
 end

 subgraph L3["3. Server"]
 S1["Independent OAuth tokens per server"]
 S2["Keychain storage"]
 end

 subgraph L4["4. Channel"]
 C1["GrowthBook allowlist&amp;lt;br/&amp;gt;tengu_harbor_ledger"]
 C2["Structured events&amp;lt;br/&amp;gt;not plain text matching"]
 end

 L1 --&gt; L2 --&gt; L3 --&gt; L4

 style L1 fill:#ffcdd2
 style L2 fill:#fff9c4
 style L3 fill:#c8e6c9
 style L4 fill:#e1f5fe&lt;/pre&gt;&lt;p&gt;Each layer operates independently, and &lt;strong&gt;Enterprise takes highest priority&lt;/strong&gt;. Even if &lt;code&gt;.mcp.json&lt;/code&gt; exists in the project, it&amp;rsquo;s blocked if it hits the enterprise denylist.&lt;/p&gt;
&lt;h3 id="config-sources-and-deduplication-configts-1578-lines"&gt;Config Sources and Deduplication (config.ts 1,578 lines)
&lt;/h3&gt;&lt;p&gt;Config source priority (higher wins):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enterprise managed (&lt;code&gt;managed-mcp.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Local (per-user project settings)&lt;/li&gt;
&lt;li&gt;User (global &lt;code&gt;~/.claude.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Project (&lt;code&gt;.mcp.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Plugin (dynamic)&lt;/li&gt;
&lt;li&gt;claude.ai connectors (lowest)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why is deduplication needed?&lt;/strong&gt; The same MCP server can exist in both &lt;code&gt;.mcp.json&lt;/code&gt; and claude.ai connectors. &lt;code&gt;getMcpServerSignature&lt;/code&gt; creates &lt;code&gt;stdio:[command|args]&lt;/code&gt; or &lt;code&gt;url:&amp;lt;base&amp;gt;&lt;/code&gt; signatures, unwrapping CCR proxy URLs to original vendor URLs before comparison.&lt;/p&gt;
&lt;p&gt;Environment variable expansion: Supports &lt;code&gt;${VAR}&lt;/code&gt; and &lt;code&gt;${VAR:-default}&lt;/code&gt; syntax. Missing variables are reported as warnings rather than errors to prevent partial connection failures.&lt;/p&gt;
&lt;h2 id="4-plugins-vs-skills--structural-differences"&gt;4. Plugins vs Skills &amp;ndash; Structural Differences
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Dimension&lt;/th&gt;
 &lt;th&gt;Skills&lt;/th&gt;
 &lt;th&gt;Plugins&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Essence&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Prompt extension (SKILL.md = text)&lt;/td&gt;
 &lt;td&gt;System extension (skills + hooks + MCP)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Drop a single file&lt;/td&gt;
 &lt;td&gt;Marketplace git clone&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Runtime code&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;None (pure text)&lt;/td&gt;
 &lt;td&gt;Yes (MCP servers, hook scripts)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Toggle&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Implicit (file existence)&lt;/td&gt;
 &lt;td&gt;Explicit (&lt;code&gt;/plugin&lt;/code&gt; UI)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;ID scheme&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;File path&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;{name}@builtin&lt;/code&gt; or &lt;code&gt;{name}@marketplace&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Skills are the embodiment of the &amp;ldquo;file = extension&amp;rdquo; principle. A single &lt;code&gt;SKILL.md&lt;/code&gt; works as an extension immediately without installation or building.&lt;/p&gt;
&lt;h3 id="plugin-service-separation-of-concerns"&gt;Plugin Service Separation of Concerns
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;File&lt;/th&gt;
 &lt;th&gt;Role&lt;/th&gt;
 &lt;th&gt;Side Effects&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pluginOperations.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Pure library functions&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pluginCliCommands.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;CLI wrappers&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;process.exit&lt;/code&gt;, console output&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;PluginInstallationManager.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Background coordinator&lt;/td&gt;
 &lt;td&gt;AppState updates&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The pure functions in &lt;code&gt;pluginOperations&lt;/code&gt; are reused by both CLI and interactive UI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Marketplace coordination&lt;/strong&gt;: &lt;code&gt;diffMarketplaces()&lt;/code&gt; compares declared marketplaces against actual installations. New installs trigger auto-refresh; existing updates only set a &lt;code&gt;needsRefresh&lt;/code&gt; flag. New installs need auto-refresh to prevent &amp;ldquo;plugin not found&amp;rdquo; errors, while updates let users choose when to apply.&lt;/p&gt;
&lt;h2 id="5-5-layer-skill-discovery-engine"&gt;5. 5-Layer Skill Discovery Engine
&lt;/h2&gt;&lt;p&gt;Loading source priority in &lt;code&gt;loadSkillsDir.ts&lt;/code&gt; (1,086 lines):&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 subgraph Discovery["Skill Discovery"]
 A["1. policySettings&amp;lt;br/&amp;gt;managed-settings.json"]
 B["2. userSettings&amp;lt;br/&amp;gt;~/.claude/skills/"]
 C["3. projectSettings&amp;lt;br/&amp;gt;.claude/skills/&amp;lt;br/&amp;gt;project root to home"]
 D["4. --add-dir&amp;lt;br/&amp;gt;additional directories"]
 E["5. legacy&amp;lt;br/&amp;gt;/commands/ directory"]
 end

 subgraph Dedup["Deduplication"]
 F["realpath() symlink resolution"]
 G["File ID based first-wins"]
 end

 subgraph Parse["Frontmatter Parsing"]
 H["description, when_to_use"]
 I["allowed-tools"]
 J["model, context, hooks"]
 K["paths, shell"]
 end

 A --&gt; B --&gt; C --&gt; D --&gt; E
 E --&gt; F --&gt; G
 G --&gt; H &amp; I &amp; J &amp; K

 style Discovery fill:#e1f5fe
 style Parse fill:#fff3e0&lt;/pre&gt;&lt;h3 id="frontmatter-system"&gt;Frontmatter System
&lt;/h3&gt;&lt;p&gt;15+ fields are extracted from &lt;code&gt;SKILL.md&lt;/code&gt;&amp;rsquo;s YAML frontmatter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;description&lt;/code&gt;, &lt;code&gt;when_to_use&lt;/code&gt;: Used by the model for skill selection&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allowed-tools&lt;/code&gt;: List of tools permitted during skill execution&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;: Force a specific model&lt;/li&gt;
&lt;li&gt;&lt;code&gt;context: fork&lt;/code&gt;: Execute in a separate context&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hooks&lt;/code&gt;: Skill-specific hook configuration&lt;/li&gt;
&lt;li&gt;&lt;code&gt;paths&lt;/code&gt;: Path-based activation filter&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shell&lt;/code&gt;: Inline shell command execution&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="lazy-disk-extraction-of-bundled-skills"&gt;Lazy Disk Extraction of Bundled Skills
&lt;/h3&gt;&lt;p&gt;17 bundled skills compiled into the CLI binary (&lt;code&gt;skills/bundled/&lt;/code&gt;) are &lt;strong&gt;extracted to disk on first invocation&lt;/strong&gt; if they have a &lt;code&gt;files&lt;/code&gt; field:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;O_NOFOLLOW | O_EXCL&lt;/code&gt; flags prevent symlink attacks&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0o600&lt;/code&gt; permissions restrict access&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resolveSkillFilePath()&lt;/code&gt; rejects &lt;code&gt;..&lt;/code&gt; paths to prevent directory escape&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why extract to disk?&lt;/strong&gt; So the model can read reference files using the &lt;code&gt;Read&lt;/code&gt;/&lt;code&gt;Grep&lt;/code&gt; tools. Keeping them only in memory would make them inaccessible to the model.&lt;/p&gt;
&lt;h3 id="mcpskillbuilders--a-44-line-circular-reference-solution"&gt;mcpSkillBuilders &amp;ndash; A 44-Line Circular Reference Solution
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;mcpSkillBuilders.ts&lt;/code&gt; (44 lines) is small but architecturally significant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: &lt;code&gt;mcpSkills.ts&lt;/code&gt; needs functions from &lt;code&gt;loadSkillsDir.ts&lt;/code&gt;, but a direct import creates a circular reference (&lt;code&gt;client.ts -&amp;gt; mcpSkills.ts -&amp;gt; loadSkillsDir.ts -&amp;gt; ... -&amp;gt; client.ts&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: A write-once registry. &lt;code&gt;loadSkillsDir.ts&lt;/code&gt; registers functions at module initialization time, and &lt;code&gt;mcpSkills.ts&lt;/code&gt; retrieves them when needed. Dynamic imports fail in the Bun bundler, and literal dynamic imports trigger dependency-cruiser&amp;rsquo;s circular dependency check, making &lt;strong&gt;this approach the only viable solution&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Leaf modules in the dependency graph import only types, and runtime registration happens exactly once at startup.&lt;/p&gt;
&lt;h2 id="rust-comparison"&gt;Rust Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Area&lt;/th&gt;
 &lt;th&gt;TS (Complete)&lt;/th&gt;
 &lt;th&gt;Rust (Current)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Name normalization&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;normalization.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;mcp.rs&lt;/code&gt; — same logic&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Server signature&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;getMcpServerSignature&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;mcp_server_signature&lt;/code&gt; — includes CCR proxy unwrap&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;stdio JSON-RPC&lt;/td&gt;
 &lt;td&gt;SDK-dependent&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;mcp_stdio.rs&lt;/code&gt; — direct implementation (initialize, tools/list, tools/call)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;OAuth&lt;/td&gt;
 &lt;td&gt;2,465-line full implementation&lt;/td&gt;
 &lt;td&gt;None — types only&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Connection management&lt;/td&gt;
 &lt;td&gt;memoize + onclose reconnection&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Skill loading&lt;/td&gt;
 &lt;td&gt;5-layer + 15-field frontmatter&lt;/td&gt;
 &lt;td&gt;2 directories, SKILL.md only&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Bundled skills&lt;/td&gt;
 &lt;td&gt;17 built-in&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Plugins&lt;/td&gt;
 &lt;td&gt;Built-in + marketplace&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Security&lt;/td&gt;
 &lt;td&gt;4-layer (Enterprise-&amp;gt;Channel)&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Key gap&lt;/strong&gt;: Rust has implemented bootstrap (config -&amp;gt; transport) and stdio JSON-RPC. The SDK-less JSON-RPC implementation in &lt;code&gt;mcp_stdio.rs&lt;/code&gt; is meaningful progress. However, OAuth, connection lifecycle, channel security, and the full skill discovery system are all absent.&lt;/p&gt;
&lt;h2 id="insights"&gt;Insights
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MCP is not a &amp;ldquo;protocol&amp;rdquo; but an &amp;ldquo;integration framework&amp;rdquo;&lt;/strong&gt; &amp;ndash; What &lt;code&gt;client.ts&lt;/code&gt;&amp;rsquo;s 3,348 lines tell us is that the hard part is not JSON-RPC but &lt;strong&gt;connection lifecycle management&lt;/strong&gt;. Memoization, auto-reconnect, session expiry detection, 401 retry, 3-strike terminal errors, needs-auth caching. External processes (stdio) and remote services (HTTP/SSE) die unpredictably, OAuth tokens expire, and networks drop. This is code that reflects the reality that &amp;ldquo;connect once and done&amp;rdquo; doesn&amp;rsquo;t exist.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Skills embody the &amp;ldquo;file = extension&amp;rdquo; principle&lt;/strong&gt; &amp;ndash; A single SKILL.md works as an extension immediately without installation or building. This simplicity, combined with incremental complexity via frontmatter (model specification, hooks, path filters), accommodates both beginners and power users. Plugins are the organizational layer above skills, packaging &amp;ldquo;skills + hooks + MCP servers&amp;rdquo; together.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mcpSkillBuilders.ts&lt;/code&gt; is a 44-line architecture lesson&lt;/strong&gt; &amp;ndash; The only solution that simultaneously satisfies Bun bundler&amp;rsquo;s dynamic import constraints and dependency-cruiser&amp;rsquo;s circular dependency check was a &amp;ldquo;write-once registry.&amp;rdquo; The pattern where leaf modules import only types and runtime registration happens once at startup is a broadly applicable approach to resolving circular references in complex module systems — worth remembering.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Next post: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-6/" &gt;#6 &amp;ndash; Beyond Claude Code: A Retrospective on Building an Independent 7-Crate Harness&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>