9f6d0d2281
- Bump root AGENTS.md header: 2026-05-14 → 2026-05-15, commit5ffbe0e24→53a740636, release v4.1.1 → v4.1.2 - Update file counts: 2034 (1337+697) → 2041 (1340+701), LOC ~292k → ~294k - Fix STRUCTURE: openclaw lives at src/openclaw/ (not src/features/); list more accurate feature modules in the parenthetical - Clarify interactive_bash gate: tmux binary on PATH via isInteractiveBashEnabled() (not 'tmux enabled') - Fix docs/reference/features.md hook counts: Tool Guard 14→16, Total base 52→54, total with team-mode 59→61 - Bump 'Generated' date on all 43 subdir AGENTS.md files to 2026-05-15 - Preserve promptAsync injection cautions verbatim (per request)
112 lines
4.4 KiB
Markdown
112 lines
4.4 KiB
Markdown
# src/features/skill-mcp-manager/ — Skill-Embedded MCP Client Lifecycle
|
|
|
|
**Generated:** 2026-05-15
|
|
|
|
## OVERVIEW
|
|
|
|
18 files. Manages **tier 3** of the MCP system: skill-embedded MCP servers declared in SKILL.md YAML frontmatter. Per-session client isolation, dual transport (stdio + HTTP), OAuth 2.0 with step-up authentication, idle cleanup.
|
|
|
|
## THREE-TIER MCP CONTEXT
|
|
|
|
| Tier | Manager | Scope |
|
|
|------|---------|-------|
|
|
| 1. Built-in | `createBuiltinMcps()` (src/mcp/) | Global, 3 remote HTTP |
|
|
| 2. Claude Code | `claude-code-mcp-loader` (src/features/) | From `.mcp.json` |
|
|
| 3. **Skill-embedded** | **`SkillMcpManager` (this module)** | **Per-session, from SKILL.md YAML** |
|
|
|
|
## CLIENT KEY FORMAT
|
|
|
|
```
|
|
${sessionID}:${skillName}:${serverName}
|
|
```
|
|
|
|
Enables: per-session isolation, same skill usable in multiple sessions concurrently, multiple servers per skill.
|
|
|
|
## DUAL TRANSPORT
|
|
|
|
| Type | File | Backend |
|
|
|------|------|---------|
|
|
| **stdio** | `stdio-client.ts` | `StdioClientTransport` (local process) |
|
|
| **http** | `http-client.ts` | `StreamableHTTPClientTransport` (remote) |
|
|
|
|
**Detection** (connection-type.ts): explicit `type` field → URL presence → command presence. Legacy `"sse"` mapped to http.
|
|
|
|
## STATE
|
|
|
|
```typescript
|
|
interface SkillMcpManagerState {
|
|
clients: Map<clientKey, ManagedClient> // Active connections
|
|
pendingConnections: Map<clientKey, Promise<Client>> // Race prevention
|
|
disconnectedSessions: Map<sessionID, generation> // Stale connection detection
|
|
authProviders: Map<url, OAuthProvider> // OAuth state per server
|
|
inFlightConnections: Map<sessionID, count> // Connection counting
|
|
}
|
|
```
|
|
|
|
## KEY FILES
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `manager.ts` | `SkillMcpManager` class — main API (getOrCreateClient, disconnectSession, listTools, callTool, etc.) |
|
|
| `types.ts` | `ManagedStdioClient`, `ManagedHttpClient`, `SkillMcpManagerState`, `ConnectionType` |
|
|
| `connection.ts` | Client factory with race prevention, retry, env var expansion |
|
|
| `connection-type.ts` | Detect stdio vs http from config (legacy sse → http) |
|
|
| `stdio-client.ts` | Stdio transport factory |
|
|
| `http-client.ts` | HTTP transport factory |
|
|
| `cleanup.ts` | SIGINT/SIGTERM handlers, idle timer (60s interval, 5min TTL) |
|
|
| `oauth-handler.ts` | OAuth token management, refresh, step-up (403 scope escalation) |
|
|
| `env-cleaner.ts` | Filter npm/pnpm/yarn config + 25+ secret patterns (_KEY, _SECRET, _TOKEN) |
|
|
| `error-redaction.ts` | Redact sensitive data from error messages before logging |
|
|
|
|
## LIFECYCLE INTEGRATION
|
|
|
|
**Hook**: `src/plugin/event.ts` on `session.deleted`:
|
|
```typescript
|
|
await managers.skillMcpManager.disconnectSession(sessionInfo.id)
|
|
```
|
|
|
|
## LIFECYCLE FLOW
|
|
|
|
```
|
|
1. session.created → No action (lazy connection)
|
|
2. First MCP tool call → getOrCreateClient() creates + caches
|
|
3. Ongoing use → lastUsedAt timestamp updated
|
|
4. Idle >5min → cleanup timer removes
|
|
5. session.deleted → disconnectSession() closes session clients
|
|
6. Process exit → disconnectAll() via SIGINT/SIGTERM handlers
|
|
```
|
|
|
|
## RACE CONDITION PREVENTION
|
|
|
|
- **pendingConnections**: Deduplicates concurrent connection attempts for same key
|
|
- **inFlightConnections**: Per-session counter, prevents premature cleanup during connection setup
|
|
- **shutdownGeneration**: Counter-based stale connection detection after disconnect
|
|
|
|
## PUBLIC API
|
|
|
|
```typescript
|
|
class SkillMcpManager {
|
|
constructor(options?: { createOAuthProvider? })
|
|
getOrCreateClient(info, config): Promise<Client>
|
|
disconnectSession(sessionID): Promise<void>
|
|
disconnectAll(): Promise<void>
|
|
listTools/Resources/Prompts(info, context): Promise<...[]>
|
|
callTool(info, context, name, args): Promise<unknown>
|
|
readResource(info, context, uri): Promise<unknown>
|
|
getPrompt(info, context, name, args): Promise<unknown>
|
|
getConnectedServers(): string[]
|
|
isConnected(info): boolean
|
|
}
|
|
```
|
|
|
|
## RETRY SEMANTICS
|
|
|
|
- `getOrCreateClientWithRetry()` — 3 attempts with force reconnect on failure
|
|
- `withOperationRetry()` — OAuth-aware wrapper: step-up on 403, token refresh on 401
|
|
|
|
## SECURITY
|
|
|
|
- **env-cleaner.ts** — strips npm/pnpm config vars (prevents pnpm project isolation issues) and secret patterns before stdio spawn
|
|
- **error-redaction.ts** — masks tokens/secrets in error messages before logger.log
|
|
- **OAuth isolation** — auth providers keyed by server URL, tokens never cross servers
|