Extracted from alkhub_ts packages/core/operations/ and packages/core/mcp/. - Runtime-agnostic (injected fs/env deps, no Deno globals) - Direct @logtape/logtape import instead of logger wrapper - PendingRequestMap with pubsub-wired call protocol - Peer-dep isolation for MCP adapter (sub-path export) - Schema const naming convention (XSchema + X type alias) - 68 tests passing, build + lint + test all green
2.6 KiB
ADR-003: Peer Dependencies for Adapter Isolation
Status: Accepted Date: 2026-04-30
Context
The MCP adapter (from_mcp.ts) depends on @modelcontextprotocol/sdk for Client, StdioClientTransport, and StreamableHTTPClientTransport. This dependency is heavy (transitive deps) and only needed by consumers that connect to MCP servers. Other adapters may have similar heavy dependencies in the future (e.g., gRPC, GraphQL).
Two approaches:
- Regular dependency — list
@modelcontextprotocol/sdkas a direct dependency. All consumers install it. - Optional peer dependency + sub-path export — list it as an optional peer dependency, import dynamically, and expose via a separate
./from-mcpsub-path export.
Decision
Use optional peer dependency with sub-path export.
{
"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.0.0"
},
"peerDependenciesMeta": {
"@modelcontextprotocol/sdk": { "optional": true }
},
"exports": {
".": { ... },
"./from-mcp": { ... }
}
}
Rationale
-
Zero-cost for non-MCP consumers —
npm install @alkdev/operationsdoes not install@modelcontextprotocol/sdk. Only consumers thatimport { createMCPClient } from "@alkdev/operations/from-mcp"need to install it. -
Dynamic import —
from_mcp.tsusesawait import("@modelcontextprotocol/sdk/client/index.js")andawait import("@modelcontextprotocol/sdk/client/stdio.js"). The MCP SDK is loaded only whencreateMCPClientis actually called, not at module parse time. -
Explicit dependency declaration — the sub-path import makes it clear at the import site that this code needs the MCP SDK. A barrel-only import doesn't communicate this.
-
No bundler reliance — sub-path exports don't depend on the consumer's bundler correctly tree-shaking. Not all consumers use bundlers (Deno, Node with
--experimental-strip-types). -
Follows established pattern —
@alkdev/pubsubuses the same approach for its Redis adapter (sub-path export with optional ioredis peer dep). -
Incremental — future adapters (gRPC, GraphQL) will follow the same pattern. Each adds one peer dep entry and one sub-path export.
Consequences
package.jsonhas a peer dep entry for each adapter's external dependency- Both barrel and sub-path work — barrel re-exports everything for convenience, sub-path for explicitness
tsupmust list each adapter as a separate entry point- Consumer docs should recommend sub-path imports for adapter-specific code
- The
from_mcp.tsmodule also imports fromfrom_schema.tsandtypes.ts(core), which are bundled into the sub-path output by tsup's code splitting