@ebowwa/claude-code-mcp 1.0.0 → 1.0.2
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.
- package/dist/__tests__/advanced.test.d.ts +6 -0
- package/dist/__tests__/advanced.test.d.ts.map +1 -0
- package/dist/__tests__/advanced.test.js +354 -0
- package/dist/__tests__/advanced.test.js.map +1 -0
- package/dist/advanced.d.ts +109 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +427 -0
- package/dist/advanced.js.map +1 -0
- package/dist/cli-wrapper.d.ts +202 -0
- package/dist/cli-wrapper.d.ts.map +1 -0
- package/dist/cli-wrapper.js +347 -0
- package/dist/cli-wrapper.js.map +1 -0
- package/dist/cli-wrapper.test.d.ts +12 -0
- package/dist/cli-wrapper.test.d.ts.map +1 -0
- package/dist/cli-wrapper.test.js +354 -0
- package/dist/cli-wrapper.test.js.map +1 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +354 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +561 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.test.d.ts +12 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +716 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/queue.d.ts +87 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +273 -0
- package/dist/queue.js.map +1 -0
- package/dist/teammates-integration.d.ts +128 -0
- package/dist/teammates-integration.d.ts.map +1 -0
- package/dist/teammates-integration.js +353 -0
- package/dist/teammates-integration.js.map +1 -0
- package/dist/test-config.d.ts +104 -0
- package/dist/test-config.d.ts.map +1 -0
- package/dist/test-config.js +439 -0
- package/dist/test-config.js.map +1 -0
- package/dist/tools.d.ts +97 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +627 -0
- package/dist/tools.js.map +1 -0
- package/package.json +7 -1
- package/ARCHITECTURE.md +0 -1802
- package/DOCUMENTATION.md +0 -747
- package/TESTING.md +0 -318
package/ARCHITECTURE.md
DELETED
|
@@ -1,1802 +0,0 @@
|
|
|
1
|
-
# Claude Code MCP Server Architecture
|
|
2
|
-
|
|
3
|
-
**Version:** 1.0.0
|
|
4
|
-
**Date:** 2026-02-05
|
|
5
|
-
**Status:** Design Document
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Table of Contents
|
|
10
|
-
|
|
11
|
-
1. [Overview](#overview)
|
|
12
|
-
2. [Design Philosophy](#design-philosophy)
|
|
13
|
-
3. [Architecture Diagram](#architecture-diagram)
|
|
14
|
-
4. [Core Components](#core-components)
|
|
15
|
-
5. [Tool Schemas](#tool-schemas)
|
|
16
|
-
6. [Session State Management](#session-state-management)
|
|
17
|
-
7. [Doppler Integration](#doppler-integration)
|
|
18
|
-
8. [Message Pass-Through Protocol](#message-pass-through-protocol)
|
|
19
|
-
9. [Inter-Agent Communication](#inter-agent-communication)
|
|
20
|
-
10. [Type System](#type-system)
|
|
21
|
-
11. [Integration Patterns](#integration-patterns)
|
|
22
|
-
12. [Security Considerations](#security-considerations)
|
|
23
|
-
13. [Error Handling](#error-handling)
|
|
24
|
-
14. [Performance Considerations](#performance-considerations)
|
|
25
|
-
15. [Testing Strategy](#testing-strategy)
|
|
26
|
-
16. [Future Enhancements](#future-enhancements)
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Overview
|
|
31
|
-
|
|
32
|
-
The Claude Code MCP Server provides programmatic control over Claude Code sessions via the Model Context Protocol (MCP). It complements the existing `claude-code-history` MCP by adding session lifecycle management capabilities.
|
|
33
|
-
|
|
34
|
-
### Key Capabilities
|
|
35
|
-
|
|
36
|
-
- **Session Management**: Start, resume, list, and kill Claude Code sessions
|
|
37
|
-
- **Message Pass-Through**: Send messages to active sessions and receive responses
|
|
38
|
-
- **Context Synchronization**: Sync project context and working directory state
|
|
39
|
-
- **Doppler Integration**: Automatic secrets injection via `doppler run`
|
|
40
|
-
- **Multi-Agent Coordination**: Built-in support for teammate messaging via `@ebowwa/teammates`
|
|
41
|
-
- **Persistent State**: Session metadata tracking for recovery and monitoring
|
|
42
|
-
|
|
43
|
-
### Relationship to Other MCPs
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
claude-code-history (READ-ONLY)
|
|
47
|
-
├── List conversations
|
|
48
|
-
├── Get conversation history
|
|
49
|
-
└── Search conversations
|
|
50
|
-
|
|
51
|
-
claude-code (READ-WRITE) ← This MCP
|
|
52
|
-
├── Start new sessions
|
|
53
|
-
├── Resume existing sessions
|
|
54
|
-
├── Kill sessions
|
|
55
|
-
├── Send messages to sessions
|
|
56
|
-
└── Sync context
|
|
57
|
-
|
|
58
|
-
Complementary: Together provide full lifecycle management
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## Design Philosophy
|
|
64
|
-
|
|
65
|
-
### Core Principles
|
|
66
|
-
|
|
67
|
-
1. **Composability**: Every component is modular and reusable
|
|
68
|
-
2. **Type Safety**: Full TypeScript coverage with Zod validation
|
|
69
|
-
3. **Idempotency**: Operations are safe to retry
|
|
70
|
-
4. **Observability**: All actions are logged and traceable
|
|
71
|
-
5. **Graceful Degradation**: Failures don't crash sessions
|
|
72
|
-
6. **Doppler-First**: Secrets management via environment variables
|
|
73
|
-
|
|
74
|
-
### Architectural Decisions
|
|
75
|
-
|
|
76
|
-
| Decision | Rationale |
|
|
77
|
-
|----------|-----------|
|
|
78
|
-
| **stdio Transport** | Local execution, no network overhead |
|
|
79
|
-
| **JSON-RPC 2.0** | Standard MCP protocol (2024-11-05) |
|
|
80
|
-
| **Zod Schemas** | Runtime validation + TypeScript inference |
|
|
81
|
-
| **Doppler Wrapping** | Secure secrets injection without .env files |
|
|
82
|
-
| **Teammates Integration** | Native multi-agent coordination |
|
|
83
|
-
| **File-Based State** | Simple persistence, no database required |
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Architecture Diagram
|
|
88
|
-
|
|
89
|
-
```
|
|
90
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
91
|
-
│ Claude Code MCP │
|
|
92
|
-
│ (stdio:// transport) │
|
|
93
|
-
└────────────────────────────┬────────────────────────────────────┘
|
|
94
|
-
│
|
|
95
|
-
┌────────────┴────────────┐
|
|
96
|
-
│ │
|
|
97
|
-
┌───────▼────────┐ ┌────────▼────────┐
|
|
98
|
-
│ Tool Router │ │ State Manager │
|
|
99
|
-
└───────┬────────┘ └────────┬────────┘
|
|
100
|
-
│ │
|
|
101
|
-
┌───────┴─────────────────────────┴────────┐
|
|
102
|
-
│ │
|
|
103
|
-
┌───────▼────────┐ ┌────────────┐ ┌────────────▼──────┐
|
|
104
|
-
│ start_session │ │ resume_ │ │ sync_context │
|
|
105
|
-
│ kill_session │ │ session │ │ send_message │
|
|
106
|
-
│ list_sessions │ │ wait_for_ │ │ stream_output │
|
|
107
|
-
└───────┬────────┘ │ completion │ └──────────┬───────┘
|
|
108
|
-
│ └────────────┘ │
|
|
109
|
-
│ │
|
|
110
|
-
┌───────▼───────────────────────────────────────▼────────┐
|
|
111
|
-
│ Claude Code CLI Wrapper │
|
|
112
|
-
│ (doppler run --command "claude-code ...") │
|
|
113
|
-
└──────────────────────┬──────────────────────────────────┘
|
|
114
|
-
│
|
|
115
|
-
┌─────────────┼─────────────┐
|
|
116
|
-
│ │ │
|
|
117
|
-
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
|
|
118
|
-
│ Doppler │ │ Tmux │ │ Team │
|
|
119
|
-
│ Secrets │ │ Session │ │ Mates │
|
|
120
|
-
└─────────┘ └─────────┘ └─────────┘
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## Core Components
|
|
126
|
-
|
|
127
|
-
### 1. MCP Server (stdio)
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
// src/server.ts
|
|
131
|
-
interface MCPServer {
|
|
132
|
-
start(): Promise<void>;
|
|
133
|
-
stop(): Promise<void>;
|
|
134
|
-
handleRequest(request: JSONRPCMessage): Promise<JSONRPCResponse>;
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
**Responsibilities:**
|
|
139
|
-
- JSON-RPC 2.0 message handling
|
|
140
|
-
- Tool routing and dispatch
|
|
141
|
-
- Error handling and logging
|
|
142
|
-
- Protocol negotiation (2024-11-05)
|
|
143
|
-
|
|
144
|
-
### 2. Tool Router
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
// src/tools/router.ts
|
|
148
|
-
interface ToolRouter {
|
|
149
|
-
routes: Map<string, ToolHandler>;
|
|
150
|
-
register(name: string, handler: ToolHandler): void;
|
|
151
|
-
dispatch(name: string, args: unknown): Promise<ToolResult>;
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
**Responsibilities:**
|
|
156
|
-
- Tool registration and discovery
|
|
157
|
-
- Input validation via Zod schemas
|
|
158
|
-
- Handler execution
|
|
159
|
-
- Response formatting
|
|
160
|
-
|
|
161
|
-
### 3. Claude Code Wrapper
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
// src/wrapper/claude-code.ts
|
|
165
|
-
interface ClaudeCodeWrapper {
|
|
166
|
-
startSession(options: StartSessionOptions): Promise<ClaudeSession>;
|
|
167
|
-
resumeSession(sessionId: string): Promise<ClaudeSession>;
|
|
168
|
-
killSession(sessionId: string): Promise<void>;
|
|
169
|
-
sendMessage(sessionId: string, message: string): Promise<void>;
|
|
170
|
-
streamOutput(sessionId: string): AsyncIterable<string>;
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
**Responsibilities:**
|
|
175
|
-
- Spawn Claude Code processes
|
|
176
|
-
- Wrap with `doppler run`
|
|
177
|
-
- Manage process lifecycle
|
|
178
|
-
- Handle stdio streams
|
|
179
|
-
|
|
180
|
-
### 4. State Manager
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
// src/state/manager.ts
|
|
184
|
-
interface StateManager {
|
|
185
|
-
sessions: Map<string, SessionState>;
|
|
186
|
-
create(sessionId: string, state: SessionState): void;
|
|
187
|
-
get(sessionId: string): SessionState | undefined;
|
|
188
|
-
update(sessionId: string, updates: Partial<SessionState>): void;
|
|
189
|
-
delete(sessionId: string): void;
|
|
190
|
-
list(): SessionState[];
|
|
191
|
-
persist(): Promise<void>;
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
**Responsibilities:**
|
|
196
|
-
- Session metadata tracking
|
|
197
|
-
- State persistence to disk
|
|
198
|
-
- Concurrent access safety
|
|
199
|
-
- Cleanup of stale sessions
|
|
200
|
-
|
|
201
|
-
### 5. Doppler Integration
|
|
202
|
-
|
|
203
|
-
```typescript
|
|
204
|
-
// src/integrations/doppler.ts
|
|
205
|
-
interface DopplerIntegration {
|
|
206
|
-
wrapCommand(command: string): string;
|
|
207
|
-
injectSecrets(): Promise<void>;
|
|
208
|
-
getSecret(key: string): string | undefined;
|
|
209
|
-
}
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
**Responsibilities:**
|
|
213
|
-
- Generate `doppler run` wrappers
|
|
214
|
-
- Load secrets from Doppler config
|
|
215
|
-
- Handle `--resume` flag workaround
|
|
216
|
-
- Validate secret availability
|
|
217
|
-
|
|
218
|
-
### 6. Teammates Messenger
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
// src/integrations/teammates.ts
|
|
222
|
-
interface TeammatesMessenger {
|
|
223
|
-
sendToTeammate(teammateName: string, message: string): Promise<void>;
|
|
224
|
-
broadcast(message: string): Promise<void>;
|
|
225
|
-
requestShutdown(teammateName: string): Promise<void>;
|
|
226
|
-
notifyIdle(reason: string): Promise<void>;
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
**Responsibilities:**
|
|
231
|
-
- Inter-agent communication
|
|
232
|
-
- Teammate discovery via config
|
|
233
|
-
- Message serialization
|
|
234
|
-
- Response handling
|
|
235
|
-
|
|
236
|
-
---
|
|
237
|
-
|
|
238
|
-
## Tool Schemas
|
|
239
|
-
|
|
240
|
-
### Tool: start_session
|
|
241
|
-
|
|
242
|
-
Start a new Claude Code session with optional context.
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
import { z } from "zod";
|
|
246
|
-
|
|
247
|
-
export const StartSessionSchema = z.object({
|
|
248
|
-
// Session identifier (auto-generated if not provided)
|
|
249
|
-
session_id: z.string().optional().describe(
|
|
250
|
-
"Unique session identifier. Auto-generated UUID if not provided."
|
|
251
|
-
),
|
|
252
|
-
|
|
253
|
-
// Working directory for the session
|
|
254
|
-
cwd: z.string().optional().describe(
|
|
255
|
-
"Working directory for Claude Code session. Defaults to current directory."
|
|
256
|
-
),
|
|
257
|
-
|
|
258
|
-
// Doppler project configuration
|
|
259
|
-
doppler_project: z.string().optional().describe(
|
|
260
|
-
"Doppler project name for secrets injection."
|
|
261
|
-
),
|
|
262
|
-
|
|
263
|
-
doppler_config: z.string().optional().describe(
|
|
264
|
-
"Doppler configuration name (dev, prod, etc.). Defaults to 'dev'."
|
|
265
|
-
),
|
|
266
|
-
|
|
267
|
-
// Initial prompt to send
|
|
268
|
-
prompt: z.string().optional().describe(
|
|
269
|
-
"Initial prompt/message to send to the session."
|
|
270
|
-
),
|
|
271
|
-
|
|
272
|
-
// Teammate integration
|
|
273
|
-
team_name: z.string().optional().describe(
|
|
274
|
-
"Team name for teammates integration. Session will join this team."
|
|
275
|
-
),
|
|
276
|
-
|
|
277
|
-
teammate_name: z.string().optional().describe(
|
|
278
|
-
"Name for this session as a teammate. Auto-generated if not provided."
|
|
279
|
-
),
|
|
280
|
-
|
|
281
|
-
// Session options
|
|
282
|
-
model: z.string().optional().describe(
|
|
283
|
-
"Claude model to use (e.g., 'claude-sonnet-4-5-20250929')."
|
|
284
|
-
),
|
|
285
|
-
|
|
286
|
-
max_turns: z.number().optional().describe(
|
|
287
|
-
"Maximum number of conversation turns. Optional for unlimited."
|
|
288
|
-
),
|
|
289
|
-
|
|
290
|
-
// Output options
|
|
291
|
-
stream_output: z.boolean().default(false).describe(
|
|
292
|
-
"Whether to stream session output to stdout."
|
|
293
|
-
),
|
|
294
|
-
|
|
295
|
-
log_file: z.string().optional().describe(
|
|
296
|
-
"Path to file for session logging. Optional."
|
|
297
|
-
),
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
export type StartSessionInput = z.infer<typeof StartSessionSchema>;
|
|
301
|
-
|
|
302
|
-
// Response schema
|
|
303
|
-
export const StartSessionResponseSchema = z.object({
|
|
304
|
-
session_id: z.string().describe("Unique session identifier"),
|
|
305
|
-
status: z.enum(["starting", "ready", "error"]).describe("Session status"),
|
|
306
|
-
pid: z.number().optional().describe("Process ID if running"),
|
|
307
|
-
tmux_pane: z.string().optional().describe("Tmux pane ID if using tmux"),
|
|
308
|
-
started_at: z.string().datetime().describe("ISO 8601 timestamp"),
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
export type StartSessionResponse = z.infer<typeof StartSessionResponseSchema>;
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
---
|
|
315
|
-
|
|
316
|
-
### Tool: resume_session
|
|
317
|
-
|
|
318
|
-
Resume an existing Claude Code session.
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
export const ResumeSessionSchema = z.object({
|
|
322
|
-
session_id: z.string().describe(
|
|
323
|
-
"Session ID to resume. Must be an existing but inactive session."
|
|
324
|
-
),
|
|
325
|
-
|
|
326
|
-
cwd: z.string().optional().describe(
|
|
327
|
-
"Override working directory. Defaults to original session directory."
|
|
328
|
-
),
|
|
329
|
-
|
|
330
|
-
doppler_project: z.string().optional().describe(
|
|
331
|
-
"Override Doppler project. Defaults to original project."
|
|
332
|
-
),
|
|
333
|
-
|
|
334
|
-
doppler_config: z.string().optional().describe(
|
|
335
|
-
"Override Doppler config. Defaults to original config."
|
|
336
|
-
),
|
|
337
|
-
|
|
338
|
-
message: z.string().optional().describe(
|
|
339
|
-
"Message to send upon resuming."
|
|
340
|
-
),
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
export type ResumeSessionInput = z.infer<typeof ResumeSessionSchema>;
|
|
344
|
-
|
|
345
|
-
export const ResumeSessionResponseSchema = z.object({
|
|
346
|
-
session_id: z.string(),
|
|
347
|
-
status: z.enum(["resuming", "ready", "not_found", "error"]),
|
|
348
|
-
resumed_at: z.string().datetime(),
|
|
349
|
-
previous_state: z.object({
|
|
350
|
-
created_at: z.string().datetime(),
|
|
351
|
-
last_active: z.string().datetime().optional(),
|
|
352
|
-
}).optional(),
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
export type ResumeSessionResponse = z.infer<typeof ResumeSessionResponseSchema>;
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
**Resume Flag Workaround:**
|
|
359
|
-
|
|
360
|
-
Due to Claude Code CLI limitations, resume is implemented via:
|
|
361
|
-
```bash
|
|
362
|
-
doppler run --command "claude-code --resume <session-id>"
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
### Tool: kill_session
|
|
368
|
-
|
|
369
|
-
Terminate a running Claude Code session.
|
|
370
|
-
|
|
371
|
-
```typescript
|
|
372
|
-
export const KillSessionSchema = z.object({
|
|
373
|
-
session_id: z.string().describe(
|
|
374
|
-
"Session ID to terminate."
|
|
375
|
-
),
|
|
376
|
-
|
|
377
|
-
force: z.boolean().default(false).describe(
|
|
378
|
-
"Force kill if graceful shutdown fails. Uses SIGKILL instead of SIGTERM."
|
|
379
|
-
),
|
|
380
|
-
|
|
381
|
-
save_state: z.boolean().default(true).describe(
|
|
382
|
-
"Whether to save session state before killing. Preserves conversation history."
|
|
383
|
-
),
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
export type KillSessionInput = z.infer<typeof KillSessionSchema>;
|
|
387
|
-
|
|
388
|
-
export const KillSessionResponseSchema = z.object({
|
|
389
|
-
session_id: z.string(),
|
|
390
|
-
status: z.enum(["killed", "not_found", "already_dead", "error"]),
|
|
391
|
-
killed_at: z.string().datetime(),
|
|
392
|
-
exit_code: z.number().optional(),
|
|
393
|
-
state_saved: z.boolean(),
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
export type KillSessionResponse = z.infer<typeof KillSessionResponseSchema>;
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
---
|
|
400
|
-
|
|
401
|
-
### Tool: list_sessions
|
|
402
|
-
|
|
403
|
-
List all Claude Code sessions with optional filtering.
|
|
404
|
-
|
|
405
|
-
```typescript
|
|
406
|
-
export const ListSessionsSchema = z.object({
|
|
407
|
-
status_filter: z.enum(["all", "active", "inactive", "error"]).default("all").describe(
|
|
408
|
-
"Filter sessions by status."
|
|
409
|
-
),
|
|
410
|
-
|
|
411
|
-
team_filter: z.string().optional().describe(
|
|
412
|
-
"Filter sessions by team name."
|
|
413
|
-
),
|
|
414
|
-
|
|
415
|
-
limit: z.number().optional().describe(
|
|
416
|
-
"Maximum number of sessions to return. Optional for unlimited."
|
|
417
|
-
),
|
|
418
|
-
|
|
419
|
-
include_metadata: z.boolean().default(false).describe(
|
|
420
|
-
"Include full session metadata. Increases response size."
|
|
421
|
-
),
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
export type ListSessionsInput = z.infer<typeof ListSessionsSchema>;
|
|
425
|
-
|
|
426
|
-
export const ListSessionsResponseSchema = z.object({
|
|
427
|
-
sessions: z.array(z.object({
|
|
428
|
-
session_id: z.string(),
|
|
429
|
-
status: z.enum(["active", "inactive", "error", "zombie"]),
|
|
430
|
-
pid: z.number().optional(),
|
|
431
|
-
cwd: z.string().optional(),
|
|
432
|
-
created_at: z.string().datetime(),
|
|
433
|
-
last_active: z.string().datetime().optional(),
|
|
434
|
-
team_name: z.string().optional(),
|
|
435
|
-
teammate_name: z.string().optional(),
|
|
436
|
-
model: z.string().optional(),
|
|
437
|
-
})),
|
|
438
|
-
total_count: z.number(),
|
|
439
|
-
filtered_count: z.number(),
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
export type ListSessionsResponse = z.infer<typeof ListSessionsResponseSchema>;
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
---
|
|
446
|
-
|
|
447
|
-
### Tool: send_message
|
|
448
|
-
|
|
449
|
-
Send a message to an active Claude Code session.
|
|
450
|
-
|
|
451
|
-
```typescript
|
|
452
|
-
export const SendMessageSchema = z.object({
|
|
453
|
-
session_id: z.string().describe(
|
|
454
|
-
"Target session ID. Must be active."
|
|
455
|
-
),
|
|
456
|
-
|
|
457
|
-
message: z.string().describe(
|
|
458
|
-
"Message content to send to the session."
|
|
459
|
-
),
|
|
460
|
-
|
|
461
|
-
wait_for_response: z.boolean().default(true).describe(
|
|
462
|
-
"Whether to wait for Claude's response before returning."
|
|
463
|
-
),
|
|
464
|
-
|
|
465
|
-
timeout_ms: z.number().default(30000).describe(
|
|
466
|
-
"Maximum time to wait for response in milliseconds."
|
|
467
|
-
),
|
|
468
|
-
|
|
469
|
-
include_context: z.boolean().default(false).describe(
|
|
470
|
-
"Include current file/context in message."
|
|
471
|
-
),
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
export type SendMessageInput = z.infer<typeof SendMessageSchema>;
|
|
475
|
-
|
|
476
|
-
export const SendMessageResponseSchema = z.object({
|
|
477
|
-
session_id: z.string(),
|
|
478
|
-
message_sent: z.boolean(),
|
|
479
|
-
response: z.string().optional().describe("Claude's response if wait_for_response=true"),
|
|
480
|
-
error: z.string().optional(),
|
|
481
|
-
sent_at: z.string().datetime(),
|
|
482
|
-
responded_at: z.string().datetime().optional(),
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
export type SendMessageResponse = z.infer<typeof SendMessageResponseSchema>;
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
---
|
|
489
|
-
|
|
490
|
-
### Tool: sync_context
|
|
491
|
-
|
|
492
|
-
Synchronize project context between sessions.
|
|
493
|
-
|
|
494
|
-
```typescript
|
|
495
|
-
export const SyncContextSchema = z.object({
|
|
496
|
-
session_id: z.string().describe(
|
|
497
|
-
"Target session ID."
|
|
498
|
-
),
|
|
499
|
-
|
|
500
|
-
context_type: z.enum(["files", "env", "git", "all"]).default("all").describe(
|
|
501
|
-
"Type of context to sync."
|
|
502
|
-
),
|
|
503
|
-
|
|
504
|
-
paths: z.array(z.string()).optional().describe(
|
|
505
|
-
"Specific file/directory paths to sync. Optional for all."
|
|
506
|
-
),
|
|
507
|
-
|
|
508
|
-
force: z.boolean().default(false).describe(
|
|
509
|
-
"Force sync even if context appears unchanged."
|
|
510
|
-
),
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
export type SyncContextInput = z.infer<typeof SyncContextSchema>;
|
|
514
|
-
|
|
515
|
-
export const SyncContextResponseSchema = z.object({
|
|
516
|
-
session_id: z.string(),
|
|
517
|
-
synced_at: z.string().datetime(),
|
|
518
|
-
context_type: z.string(),
|
|
519
|
-
files_synced: z.number().optional(),
|
|
520
|
-
env_vars_synced: z.number().optional(),
|
|
521
|
-
git_status_synced: z.boolean().optional(),
|
|
522
|
-
warnings: z.array(z.string()).optional(),
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
export type SyncContextResponse = z.infer<typeof SyncContextResponseSchema>;
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
---
|
|
529
|
-
|
|
530
|
-
### Tool: wait_for_completion
|
|
531
|
-
|
|
532
|
-
Wait for a session to complete its current task.
|
|
533
|
-
|
|
534
|
-
```typescript
|
|
535
|
-
export const WaitForCompletionSchema = z.object({
|
|
536
|
-
session_id: z.string().describe(
|
|
537
|
-
"Session ID to wait for."
|
|
538
|
-
),
|
|
539
|
-
|
|
540
|
-
timeout_ms: z.number().default(300000).describe(
|
|
541
|
-
"Maximum wait time in milliseconds. Default: 5 minutes."
|
|
542
|
-
),
|
|
543
|
-
|
|
544
|
-
poll_interval_ms: z.number().default(1000).describe(
|
|
545
|
-
"Polling interval in milliseconds. Default: 1 second."
|
|
546
|
-
),
|
|
547
|
-
|
|
548
|
-
check_idle: z.boolean().default(true).describe(
|
|
549
|
-
"Wait for session to become idle (not just responsive)."
|
|
550
|
-
),
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
export type WaitForCompletionInput = z.infer<typeof WaitForCompletionSchema>;
|
|
554
|
-
|
|
555
|
-
export const WaitForCompletionResponseSchema = z.object({
|
|
556
|
-
session_id: z.string(),
|
|
557
|
-
completed: z.boolean(),
|
|
558
|
-
timed_out: z.boolean(),
|
|
559
|
-
waited_ms: z.number(),
|
|
560
|
-
final_state: z.enum(["idle", "active", "error", "dead"]),
|
|
561
|
-
completed_at: z.string().datetime().optional(),
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
export type WaitForCompletionResponse = z.infer<typeof WaitForCompletionResponseSchema>;
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
---
|
|
568
|
-
|
|
569
|
-
### Tool: stream_output
|
|
570
|
-
|
|
571
|
-
Stream real-time output from a session.
|
|
572
|
-
|
|
573
|
-
```typescript
|
|
574
|
-
export const StreamOutputSchema = z.object({
|
|
575
|
-
session_id: z.string().describe(
|
|
576
|
-
"Session ID to stream from."
|
|
577
|
-
),
|
|
578
|
-
|
|
579
|
-
lines: z.number().optional().describe(
|
|
580
|
-
"Number of lines to stream. Optional for continuous stream."
|
|
581
|
-
),
|
|
582
|
-
|
|
583
|
-
follow: z.boolean().default(false).describe(
|
|
584
|
-
"Continue streaming new output as it arrives."
|
|
585
|
-
),
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
export type StreamOutputInput = z.infer<typeof StreamOutputSchema>;
|
|
589
|
-
|
|
590
|
-
// Note: This tool uses Server-Sent Events (SSE) for streaming
|
|
591
|
-
// The response is a continuous stream of text chunks
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
---
|
|
595
|
-
|
|
596
|
-
## Session State Management
|
|
597
|
-
|
|
598
|
-
### Session State Schema
|
|
599
|
-
|
|
600
|
-
```typescript
|
|
601
|
-
// src/state/schema.ts
|
|
602
|
-
import { z } from "zod";
|
|
603
|
-
|
|
604
|
-
export const SessionStateSchema = z.object({
|
|
605
|
-
// Identity
|
|
606
|
-
session_id: z.string().describe("UUID v4 identifier"),
|
|
607
|
-
|
|
608
|
-
// Lifecycle
|
|
609
|
-
status: z.enum([
|
|
610
|
-
"starting", // Process spawning
|
|
611
|
-
"ready", // Accepting messages
|
|
612
|
-
"busy", // Processing task
|
|
613
|
-
"idle", // Waiting for work
|
|
614
|
-
"error", // Error state
|
|
615
|
-
"dead", // Process terminated
|
|
616
|
-
"zombie", // Orphaned process
|
|
617
|
-
]).describe("Current session status"),
|
|
618
|
-
|
|
619
|
-
// Process info
|
|
620
|
-
pid: z.number().optional().describe("Process ID"),
|
|
621
|
-
tmux_pane_id: z.string().optional().describe("Tmux pane ID"),
|
|
622
|
-
|
|
623
|
-
// Timing
|
|
624
|
-
created_at: z.string().datetime().describe("Session creation time"),
|
|
625
|
-
started_at: z.string().datetime().optional().describe("Ready time"),
|
|
626
|
-
last_active: z.string().datetime().optional().describe("Last activity"),
|
|
627
|
-
killed_at: z.string().datetime().optional().describe("Termination time"),
|
|
628
|
-
|
|
629
|
-
// Configuration
|
|
630
|
-
cwd: z.string().describe("Working directory"),
|
|
631
|
-
command: z.string().describe("Full command used to start"),
|
|
632
|
-
args: z.array(z.string()).describe("Command arguments"),
|
|
633
|
-
|
|
634
|
-
// Doppler
|
|
635
|
-
doppler_project: z.string().optional(),
|
|
636
|
-
doppler_config: z.string().optional(),
|
|
637
|
-
|
|
638
|
-
// Teammates
|
|
639
|
-
team_name: z.string().optional().describe("Team membership"),
|
|
640
|
-
teammate_name: z.string().optional().describe("Teammate identifier"),
|
|
641
|
-
|
|
642
|
-
// Model
|
|
643
|
-
model: z.string().optional().describe("Claude model"),
|
|
644
|
-
max_turns: z.number().optional().describe("Turn limit"),
|
|
645
|
-
|
|
646
|
-
// State
|
|
647
|
-
turn_count: z.number().default(0).describe("Messages exchanged"),
|
|
648
|
-
last_message: z.string().optional().describe("Last message sent"),
|
|
649
|
-
last_response: z.string().optional().describe("Last Claude response"),
|
|
650
|
-
|
|
651
|
-
// Logging
|
|
652
|
-
log_file: z.string().optional().describe("Session log path"),
|
|
653
|
-
error_log: z.array(z.string()).describe("Error history"),
|
|
654
|
-
|
|
655
|
-
// Metrics
|
|
656
|
-
messages_sent: z.number().default(0),
|
|
657
|
-
responses_received: z.number().default(0),
|
|
658
|
-
total_chars_sent: z.number().default(0),
|
|
659
|
-
total_chars_received: z.number().default(0),
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
export type SessionState = z.infer<typeof SessionStateSchema>;
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
### State Persistence
|
|
666
|
-
|
|
667
|
-
```typescript
|
|
668
|
-
// src/state/persistence.ts
|
|
669
|
-
interface StatePersistence {
|
|
670
|
-
savePath: string; // ~/.claude/sessions/state.json
|
|
671
|
-
|
|
672
|
-
load(): Promise<Map<string, SessionState>>;
|
|
673
|
-
save(sessions: Map<string, SessionState>): Promise<void>;
|
|
674
|
-
|
|
675
|
-
// Atomic write to prevent corruption
|
|
676
|
-
atomicWrite(sessions: Map<string, SessionState>): Promise<void>;
|
|
677
|
-
|
|
678
|
-
// Backup before writes
|
|
679
|
-
backup(): Promise<void>;
|
|
680
|
-
}
|
|
681
|
-
```
|
|
682
|
-
|
|
683
|
-
**Storage Format:**
|
|
684
|
-
|
|
685
|
-
```json
|
|
686
|
-
{
|
|
687
|
-
"version": "1.0.0",
|
|
688
|
-
"updated_at": "2026-02-05T12:00:00Z",
|
|
689
|
-
"sessions": {
|
|
690
|
-
"uuid-1": { /* SessionState */ },
|
|
691
|
-
"uuid-2": { /* SessionState */ }
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
### State Transitions
|
|
697
|
-
|
|
698
|
-
```
|
|
699
|
-
start_session()
|
|
700
|
-
│
|
|
701
|
-
▼
|
|
702
|
-
[starting]
|
|
703
|
-
│
|
|
704
|
-
process ready
|
|
705
|
-
│
|
|
706
|
-
▼
|
|
707
|
-
[ready] ◄─────────────┐
|
|
708
|
-
│ │
|
|
709
|
-
send_message() │
|
|
710
|
-
│ │
|
|
711
|
-
▼ │
|
|
712
|
-
[busy] ───complete──▶ [idle]
|
|
713
|
-
│ │
|
|
714
|
-
error timeout
|
|
715
|
-
│ │
|
|
716
|
-
▼ │
|
|
717
|
-
[error] │
|
|
718
|
-
│ │
|
|
719
|
-
recover/cleanup │
|
|
720
|
-
│ │
|
|
721
|
-
└────────────────▴──────▶ [dead]
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
---
|
|
725
|
-
|
|
726
|
-
## Doppler Integration
|
|
727
|
-
|
|
728
|
-
### Doppler Command Wrapper
|
|
729
|
-
|
|
730
|
-
```typescript
|
|
731
|
-
// src/integrations/doppler.ts
|
|
732
|
-
export class DopplerWrapper {
|
|
733
|
-
/**
|
|
734
|
-
* Wrap a Claude Code command with Doppler secrets injection
|
|
735
|
-
*/
|
|
736
|
-
wrapCommand(command: string, options: {
|
|
737
|
-
project?: string;
|
|
738
|
-
config?: string;
|
|
739
|
-
}): string {
|
|
740
|
-
const { project, config = "dev" } = options;
|
|
741
|
-
|
|
742
|
-
if (!project) {
|
|
743
|
-
return command; // No wrapping if no project
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Use doppler run for automatic secrets injection
|
|
747
|
-
return `doppler run --command "${command}"`;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
* Generate Claude Code start command with Doppler
|
|
752
|
-
*/
|
|
753
|
-
generateStartCommand(options: StartSessionInput): string {
|
|
754
|
-
const baseCommand = this.buildClaudeCodeCommand(options);
|
|
755
|
-
return this.wrapCommand(baseCommand, options);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
/**
|
|
759
|
-
* Generate Claude Code resume command with Doppler
|
|
760
|
-
*
|
|
761
|
-
* WORKAROUND: Claude Code CLI doesn't natively support --resume flag
|
|
762
|
-
* We use doppler run --command to inject environment variables
|
|
763
|
-
*/
|
|
764
|
-
generateResumeCommand(sessionId: string, options: ResumeSessionInput): string {
|
|
765
|
-
const resumeCmd = `claude-code --resume ${sessionId}`;
|
|
766
|
-
return this.wrapCommand(resumeCmd, options);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
/**
|
|
770
|
-
* Build base Claude Code command
|
|
771
|
-
*/
|
|
772
|
-
private buildClaudeCodeCommand(options: StartSessionInput): string {
|
|
773
|
-
const parts = ["claude-code"];
|
|
774
|
-
|
|
775
|
-
if (options.model) {
|
|
776
|
-
parts.push("--model", options.model);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
if (options.max_turns) {
|
|
780
|
-
parts.push("--max-turns", String(options.max_turns));
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
if (options.cwd) {
|
|
784
|
-
parts.push("--cwd", options.cwd);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
return parts.join(" ");
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
```
|
|
791
|
-
|
|
792
|
-
### Environment Variables
|
|
793
|
-
|
|
794
|
-
Doppler automatically injects these environment variables:
|
|
795
|
-
|
|
796
|
-
```bash
|
|
797
|
-
# From Doppler config
|
|
798
|
-
ANTHROPIC_API_KEY=sk-ant-...
|
|
799
|
-
OPENAI_API_KEY=sk-...
|
|
800
|
-
GITHUB_TOKEN=ghp_...
|
|
801
|
-
|
|
802
|
-
# Session-specific (set by MCP server)
|
|
803
|
-
CLAUDE_SESSION_ID=<uuid>
|
|
804
|
-
CLAUDE_TEAM_NAME=<team>
|
|
805
|
-
CLAUDE_TEAMMATE_NAME=<name>
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
### Configuration
|
|
809
|
-
|
|
810
|
-
```typescript
|
|
811
|
-
// .doppler/doppler.yaml (example)
|
|
812
|
-
project: claude-code-mcp
|
|
813
|
-
config:
|
|
814
|
-
dev:
|
|
815
|
-
ANTHROPIC_API_KEY: sk-ant-...
|
|
816
|
-
prod:
|
|
817
|
-
ANTHROPIC_API_KEY: sk-ant-...
|
|
818
|
-
```
|
|
819
|
-
|
|
820
|
-
---
|
|
821
|
-
|
|
822
|
-
## Message Pass-Through Protocol
|
|
823
|
-
|
|
824
|
-
### Protocol Design
|
|
825
|
-
|
|
826
|
-
Messages flow through the MCP server to Claude Code sessions via stdio:
|
|
827
|
-
|
|
828
|
-
```
|
|
829
|
-
MCP Client → MCP Server → Claude Code Process → Claude
|
|
830
|
-
↑ ↓
|
|
831
|
-
└──────────────────────── response ───────────────────┘
|
|
832
|
-
```
|
|
833
|
-
|
|
834
|
-
### Message Format
|
|
835
|
-
|
|
836
|
-
```typescript
|
|
837
|
-
// src/messaging/protocol.ts
|
|
838
|
-
interface PassThroughMessage {
|
|
839
|
-
version: "1.0";
|
|
840
|
-
session_id: string;
|
|
841
|
-
timestamp: string;
|
|
842
|
-
|
|
843
|
-
// Request
|
|
844
|
-
request: {
|
|
845
|
-
type: "message" | "context_sync" | "status_check";
|
|
846
|
-
payload: unknown;
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
// Response (async)
|
|
850
|
-
response?: {
|
|
851
|
-
type: "success" | "error" | "partial";
|
|
852
|
-
payload: unknown;
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
```
|
|
856
|
-
|
|
857
|
-
### Send Message Flow
|
|
858
|
-
|
|
859
|
-
```typescript
|
|
860
|
-
// src/tools/send-message.ts
|
|
861
|
-
async function sendMessage(input: SendMessageInput): Promise<SendMessageResponse> {
|
|
862
|
-
const session = stateManager.get(input.session_id);
|
|
863
|
-
if (!session) {
|
|
864
|
-
throw new Error(`Session ${input.session_id} not found`);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
if (session.status !== "ready" && session.status !== "idle") {
|
|
868
|
-
throw new Error(`Session ${input.session_id} is not ready`);
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
// Write to process stdin
|
|
872
|
-
const process = processManager.get(input.session_id);
|
|
873
|
-
process.stdin.write(input.message + "\n");
|
|
874
|
-
|
|
875
|
-
// Update state
|
|
876
|
-
stateManager.update(input.session_id, {
|
|
877
|
-
last_message: input.message,
|
|
878
|
-
last_active: new Date().toISOString(),
|
|
879
|
-
status: "busy",
|
|
880
|
-
messages_sent: session.messages_sent + 1,
|
|
881
|
-
total_chars_sent: session.total_chars_sent + input.message.length,
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
// Wait for response if requested
|
|
885
|
-
if (input.wait_for_response) {
|
|
886
|
-
const response = await waitForResponse(session.pid, input.timeout_ms);
|
|
887
|
-
|
|
888
|
-
stateManager.update(input.session_id, {
|
|
889
|
-
last_response: response,
|
|
890
|
-
status: "idle",
|
|
891
|
-
responses_received: session.responses_received + 1,
|
|
892
|
-
});
|
|
893
|
-
|
|
894
|
-
return {
|
|
895
|
-
session_id: input.session_id,
|
|
896
|
-
message_sent: true,
|
|
897
|
-
response,
|
|
898
|
-
sent_at: new Date().toISOString(),
|
|
899
|
-
responded_at: new Date().toISOString(),
|
|
900
|
-
};
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
return {
|
|
904
|
-
session_id: input.session_id,
|
|
905
|
-
message_sent: true,
|
|
906
|
-
sent_at: new Date().toISOString(),
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
```
|
|
910
|
-
|
|
911
|
-
### Response Parsing
|
|
912
|
-
|
|
913
|
-
Claude Code outputs are captured from stdout:
|
|
914
|
-
|
|
915
|
-
```typescript
|
|
916
|
-
// src/messaging/response-parser.ts
|
|
917
|
-
async function* readResponse(process: ChildProcess): AsyncIterable<string> {
|
|
918
|
-
const decoder = new TextDecoder();
|
|
919
|
-
|
|
920
|
-
for await (const chunk of process.stdout) {
|
|
921
|
-
const text = decoder.decode(chunk);
|
|
922
|
-
|
|
923
|
-
// Parse Claude Code output format
|
|
924
|
-
// (depends on actual CLI output format)
|
|
925
|
-
if (text.startsWith("claude: ")) {
|
|
926
|
-
yield text.slice(8);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
```
|
|
931
|
-
|
|
932
|
-
---
|
|
933
|
-
|
|
934
|
-
## Inter-Agent Communication
|
|
935
|
-
|
|
936
|
-
### Teammates Integration
|
|
937
|
-
|
|
938
|
-
The MCP server integrates with `@ebowwa/teammates` for multi-agent coordination:
|
|
939
|
-
|
|
940
|
-
```typescript
|
|
941
|
-
// src/integrations/teammates.ts
|
|
942
|
-
import { Team, Teammate, sendMessage } from "@ebowwa/teammates";
|
|
943
|
-
|
|
944
|
-
export class TeammatesIntegration {
|
|
945
|
-
/**
|
|
946
|
-
* Start a session as a teammate
|
|
947
|
-
*/
|
|
948
|
-
async startAsTeammate(sessionId: string, options: StartSessionInput): Promise<void> {
|
|
949
|
-
if (!options.team_name) {
|
|
950
|
-
return; // No team integration
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
const teammate = await Teammate.create(options.team_name, {
|
|
954
|
-
teamName: options.team_name,
|
|
955
|
-
name: options.teammate_name || `session-${sessionId.slice(0, 8)}`,
|
|
956
|
-
subagentType: "general-purpose",
|
|
957
|
-
prompt: `Claude Code session ${sessionId}`,
|
|
958
|
-
backendType: "tmux",
|
|
959
|
-
mode: "delegate",
|
|
960
|
-
});
|
|
961
|
-
|
|
962
|
-
// Store teammate reference in session state
|
|
963
|
-
stateManager.update(sessionId, {
|
|
964
|
-
team_name: options.team_name,
|
|
965
|
-
teammate_name: teammate.name,
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
/**
|
|
970
|
-
* Send message to teammate
|
|
971
|
-
*/
|
|
972
|
-
async sendToTeammate(sessionId: string, message: string): Promise<void> {
|
|
973
|
-
const session = stateManager.get(sessionId);
|
|
974
|
-
if (!session?.teammate_name) {
|
|
975
|
-
throw new Error(`Session ${sessionId} is not a teammate`);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
await sendMessage({
|
|
979
|
-
type: "message",
|
|
980
|
-
recipient: session.teammate_name,
|
|
981
|
-
content: message,
|
|
982
|
-
summary: message.slice(0, 50),
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
/**
|
|
987
|
-
* Broadcast to team
|
|
988
|
-
*/
|
|
989
|
-
async broadcastToTeam(sessionId: string, message: string): Promise<void> {
|
|
990
|
-
const session = stateManager.get(sessionId);
|
|
991
|
-
if (!session?.team_name) {
|
|
992
|
-
throw new Error(`Session ${sessionId} has no team`);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
const team = await Team.load(session.team_name);
|
|
996
|
-
await team.broadcast(message);
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
/**
|
|
1000
|
-
* Notify idle status
|
|
1001
|
-
*/
|
|
1002
|
-
async notifyIdle(sessionId: string, reason: string): Promise<void> {
|
|
1003
|
-
const session = stateManager.get(sessionId);
|
|
1004
|
-
if (!session?.teammate_name) {
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
await sendIdleNotification(session.teammate_name, reason);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
```
|
|
1012
|
-
|
|
1013
|
-
### Coordination Patterns
|
|
1014
|
-
|
|
1015
|
-
**Pattern 1: Delegation**
|
|
1016
|
-
```
|
|
1017
|
-
User → MCP Server → Session A
|
|
1018
|
-
↓
|
|
1019
|
-
Session B (teammate)
|
|
1020
|
-
```
|
|
1021
|
-
|
|
1022
|
-
**Pattern 2: Broadcast**
|
|
1023
|
-
```
|
|
1024
|
-
MCP Server → broadcast("Update available")
|
|
1025
|
-
↓
|
|
1026
|
-
┌────────┼────────┐
|
|
1027
|
-
▼ ▼ ▼
|
|
1028
|
-
Session A Session B Session C
|
|
1029
|
-
```
|
|
1030
|
-
|
|
1031
|
-
**Pattern 3: Request/Response**
|
|
1032
|
-
```
|
|
1033
|
-
Session A → requestShutdown(Session B)
|
|
1034
|
-
Session B ──────────────────► approve: true
|
|
1035
|
-
Session A ──────────────────► terminate B
|
|
1036
|
-
```
|
|
1037
|
-
|
|
1038
|
-
---
|
|
1039
|
-
|
|
1040
|
-
## Type System
|
|
1041
|
-
|
|
1042
|
-
### Core Types
|
|
1043
|
-
|
|
1044
|
-
```typescript
|
|
1045
|
-
// src/types/index.ts
|
|
1046
|
-
import { z } from "zod";
|
|
1047
|
-
|
|
1048
|
-
// ============================================================================
|
|
1049
|
-
// SESSION TYPES
|
|
1050
|
-
// ============================================================================
|
|
1051
|
-
|
|
1052
|
-
export type SessionStatus =
|
|
1053
|
-
| "starting"
|
|
1054
|
-
| "ready"
|
|
1055
|
-
| "busy"
|
|
1056
|
-
| "idle"
|
|
1057
|
-
| "error"
|
|
1058
|
-
| "dead"
|
|
1059
|
-
| "zombie";
|
|
1060
|
-
|
|
1061
|
-
export interface SessionMetadata {
|
|
1062
|
-
session_id: string;
|
|
1063
|
-
status: SessionStatus;
|
|
1064
|
-
pid?: number;
|
|
1065
|
-
tmux_pane_id?: string;
|
|
1066
|
-
cwd: string;
|
|
1067
|
-
created_at: string;
|
|
1068
|
-
started_at?: string;
|
|
1069
|
-
last_active?: string;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
// ============================================================================
|
|
1073
|
-
// MESSAGE TYPES
|
|
1074
|
-
// ============================================================================
|
|
1075
|
-
|
|
1076
|
-
export interface MessageRequest {
|
|
1077
|
-
session_id: string;
|
|
1078
|
-
content: string;
|
|
1079
|
-
wait_for_response?: boolean;
|
|
1080
|
-
timeout_ms?: number;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
export interface MessageResponse {
|
|
1084
|
-
session_id: string;
|
|
1085
|
-
success: boolean;
|
|
1086
|
-
response?: string;
|
|
1087
|
-
error?: string;
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
// ============================================================================
|
|
1091
|
-
// CONTEXT TYPES
|
|
1092
|
-
// ============================================================================
|
|
1093
|
-
|
|
1094
|
-
export type ContextType = "files" | "env" | "git" | "all";
|
|
1095
|
-
|
|
1096
|
-
export interface ContextSyncRequest {
|
|
1097
|
-
session_id: string;
|
|
1098
|
-
context_type: ContextType;
|
|
1099
|
-
paths?: string[];
|
|
1100
|
-
force?: boolean;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
// ============================================================================
|
|
1104
|
-
// PROCESS TYPES
|
|
1105
|
-
// ============================================================================
|
|
1106
|
-
|
|
1107
|
-
export interface ProcessInfo {
|
|
1108
|
-
pid: number;
|
|
1109
|
-
ppid: number;
|
|
1110
|
-
command: string;
|
|
1111
|
-
args: string[];
|
|
1112
|
-
cwd: string;
|
|
1113
|
-
status: "running" | "stopped" | "zombie";
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// ============================================================================
|
|
1117
|
-
// MCP TYPES
|
|
1118
|
-
// ============================================================================
|
|
1119
|
-
|
|
1120
|
-
export interface MCPToolCall {
|
|
1121
|
-
name: string;
|
|
1122
|
-
arguments: Record<string, unknown>;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
export interface MCPToolResponse {
|
|
1126
|
-
content: Array<{
|
|
1127
|
-
type: "text" | "image" | "resource";
|
|
1128
|
-
text?: string;
|
|
1129
|
-
data?: string;
|
|
1130
|
-
uri?: string;
|
|
1131
|
-
}>;
|
|
1132
|
-
isError?: boolean;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// ============================================================================
|
|
1136
|
-
// ERROR TYPES
|
|
1137
|
-
// ============================================================================
|
|
1138
|
-
|
|
1139
|
-
export class SessionNotFoundError extends Error {
|
|
1140
|
-
constructor(public sessionId: string) {
|
|
1141
|
-
super(`Session ${sessionId} not found`);
|
|
1142
|
-
this.name = "SessionNotFoundError";
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
export class SessionNotReadyError extends Error {
|
|
1147
|
-
constructor(
|
|
1148
|
-
public sessionId: string,
|
|
1149
|
-
public status: SessionStatus
|
|
1150
|
-
) {
|
|
1151
|
-
super(`Session ${sessionId} is not ready (status: ${status})`);
|
|
1152
|
-
this.name = "SessionNotReadyError";
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
export class DopplerError extends Error {
|
|
1157
|
-
constructor(message: string, public project?: string) {
|
|
1158
|
-
super(`Doppler error: ${message}`);
|
|
1159
|
-
this.name = "DopplerError";
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
// ============================================================================
|
|
1164
|
-
// ZOD SCHEMAS (re-exported)
|
|
1165
|
-
// ============================================================================
|
|
1166
|
-
|
|
1167
|
-
export * from "./schemas/session.js";
|
|
1168
|
-
export * from "./schemas/tools.js";
|
|
1169
|
-
export * from "./schemas/messages.js";
|
|
1170
|
-
```
|
|
1171
|
-
|
|
1172
|
-
---
|
|
1173
|
-
|
|
1174
|
-
## Integration Patterns
|
|
1175
|
-
|
|
1176
|
-
### Pattern 1: Complement claude-code-history MCP
|
|
1177
|
-
|
|
1178
|
-
```typescript
|
|
1179
|
-
// Use both MCPs together
|
|
1180
|
-
import { ClaudeCodeMCP } from "@claude-code/mcp";
|
|
1181
|
-
import { ClaudeCodeHistoryMCP } from "@claude-code-history/mcp";
|
|
1182
|
-
|
|
1183
|
-
// 1. List historical conversations
|
|
1184
|
-
const history = await historyMCP.listConversations();
|
|
1185
|
-
|
|
1186
|
-
// 2. Resume specific conversation
|
|
1187
|
-
const session = await claudeCodeMCP.resumeSession(history[0].id);
|
|
1188
|
-
|
|
1189
|
-
// 3. Send message
|
|
1190
|
-
await claudeCodeMCP.sendMessage(session.session_id, "Continue from here");
|
|
1191
|
-
```
|
|
1192
|
-
|
|
1193
|
-
### Pattern 2: Coordinate with terminal MCP
|
|
1194
|
-
|
|
1195
|
-
```typescript
|
|
1196
|
-
// Use terminal MCP for tmux + claude-code MCP for sessions
|
|
1197
|
-
import { TerminalMCP } from "@terminal/mcp";
|
|
1198
|
-
import { ClaudeCodeMCP } from "@claude-code/mcp";
|
|
1199
|
-
|
|
1200
|
-
// 1. Create tmux session
|
|
1201
|
-
const tmux = await terminalMCP.createSession("host", "tmux");
|
|
1202
|
-
|
|
1203
|
-
// 2. Start Claude Code in tmux
|
|
1204
|
-
const session = await claudeCodeMCP.startSession({
|
|
1205
|
-
cwd: "/project",
|
|
1206
|
-
team_name: "dev-team",
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
// 3. Split pane for monitoring
|
|
1210
|
-
await terminalMCP.splitPane(tmux.session_id, "htop");
|
|
1211
|
-
```
|
|
1212
|
-
|
|
1213
|
-
### Pattern 3: Multi-Agent Orchestration
|
|
1214
|
-
|
|
1215
|
-
```typescript
|
|
1216
|
-
import { Team } from "@ebowwa/teammates";
|
|
1217
|
-
import { ClaudeCodeMCP } from "@claude-code/mcp";
|
|
1218
|
-
|
|
1219
|
-
// 1. Create team
|
|
1220
|
-
const team = await Team.create({
|
|
1221
|
-
name: "code-review",
|
|
1222
|
-
description: "Automated code review team",
|
|
1223
|
-
});
|
|
1224
|
-
|
|
1225
|
-
// 2. Start multiple sessions
|
|
1226
|
-
const reviewer1 = await claudeCodeMCP.startSession({
|
|
1227
|
-
team_name: "code-review",
|
|
1228
|
-
teammate_name: "reviewer-1",
|
|
1229
|
-
prompt: "Review for security issues",
|
|
1230
|
-
});
|
|
1231
|
-
|
|
1232
|
-
const reviewer2 = await claudeCodeMCP.startSession({
|
|
1233
|
-
team_name: "code-review",
|
|
1234
|
-
teammate_name: "reviewer-2",
|
|
1235
|
-
prompt: "Review for performance",
|
|
1236
|
-
});
|
|
1237
|
-
|
|
1238
|
-
// 3. Send work to team
|
|
1239
|
-
await claudeCodeMCP.sendMessage(reviewer1.session_id, "Review auth.ts");
|
|
1240
|
-
await claudeCodeMCP.sendMessage(reviewer2.session_id, "Review api.ts");
|
|
1241
|
-
```
|
|
1242
|
-
|
|
1243
|
-
---
|
|
1244
|
-
|
|
1245
|
-
## Security Considerations
|
|
1246
|
-
|
|
1247
|
-
### 1. Secrets Management
|
|
1248
|
-
|
|
1249
|
-
- **Never log secrets**: Doppler secrets are never written to logs
|
|
1250
|
-
- **Ephemeral injection**: Secrets only exist in process environment
|
|
1251
|
-
- **No .env files**: Avoids accidental commits
|
|
1252
|
-
|
|
1253
|
-
### 2. Process Isolation
|
|
1254
|
-
|
|
1255
|
-
```typescript
|
|
1256
|
-
// Spawn with restricted permissions
|
|
1257
|
-
const spawnOptions = {
|
|
1258
|
-
env: { ...process.env, ...dopplerSecrets },
|
|
1259
|
-
cwd: session.cwd,
|
|
1260
|
-
uid: process.getuid(), // Run as current user
|
|
1261
|
-
gid: process.getgid(),
|
|
1262
|
-
detached: false, // No detached processes
|
|
1263
|
-
};
|
|
1264
|
-
```
|
|
1265
|
-
|
|
1266
|
-
### 3. Input Validation
|
|
1267
|
-
|
|
1268
|
-
- All tool inputs validated via Zod schemas
|
|
1269
|
-
- Path sanitization to prevent directory traversal
|
|
1270
|
-
- Command injection prevention via shell escaping
|
|
1271
|
-
|
|
1272
|
-
### 4. Resource Limits
|
|
1273
|
-
|
|
1274
|
-
```typescript
|
|
1275
|
-
// Limit concurrent sessions
|
|
1276
|
-
const MAX_CONCURRENT_SESSIONS = 10;
|
|
1277
|
-
|
|
1278
|
-
// Limit session lifetime
|
|
1279
|
-
const MAX_SESSION_DURATION = 24 * 60 * 60 * 1000; // 24 hours
|
|
1280
|
-
|
|
1281
|
-
// Limit message size
|
|
1282
|
-
const MAX_MESSAGE_SIZE = 100_000; // 100k characters
|
|
1283
|
-
```
|
|
1284
|
-
|
|
1285
|
-
### 5. Audit Logging
|
|
1286
|
-
|
|
1287
|
-
```typescript
|
|
1288
|
-
// Audit log for all operations
|
|
1289
|
-
interface AuditLog {
|
|
1290
|
-
timestamp: string;
|
|
1291
|
-
operation: string;
|
|
1292
|
-
session_id?: string;
|
|
1293
|
-
user?: string;
|
|
1294
|
-
success: boolean;
|
|
1295
|
-
error?: string;
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
// Write to audit log
|
|
1299
|
-
await auditLogger.write({
|
|
1300
|
-
timestamp: new Date().toISOString(),
|
|
1301
|
-
operation: "start_session",
|
|
1302
|
-
session_id: sessionId,
|
|
1303
|
-
user: process.env.USER,
|
|
1304
|
-
success: true,
|
|
1305
|
-
});
|
|
1306
|
-
```
|
|
1307
|
-
|
|
1308
|
-
---
|
|
1309
|
-
|
|
1310
|
-
## Error Handling
|
|
1311
|
-
|
|
1312
|
-
### Error Categories
|
|
1313
|
-
|
|
1314
|
-
```typescript
|
|
1315
|
-
// src/errors/index.ts
|
|
1316
|
-
export enum ErrorCategory {
|
|
1317
|
-
SESSION_NOT_FOUND = "SESSION_NOT_FOUND",
|
|
1318
|
-
SESSION_NOT_READY = "SESSION_NOT_READY",
|
|
1319
|
-
DOPPLER_ERROR = "DOPPLER_ERROR",
|
|
1320
|
-
PROCESS_ERROR = "PROCESS_ERROR",
|
|
1321
|
-
VALIDATION_ERROR = "VALIDATION_ERROR",
|
|
1322
|
-
TIMEOUT_ERROR = "TIMEOUT_ERROR",
|
|
1323
|
-
PERMISSION_ERROR = "PERMISSION_ERROR",
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
export class MCPError extends Error {
|
|
1327
|
-
constructor(
|
|
1328
|
-
public category: ErrorCategory,
|
|
1329
|
-
message: string,
|
|
1330
|
-
public details?: unknown
|
|
1331
|
-
) {
|
|
1332
|
-
super(message);
|
|
1333
|
-
this.name = "MCPError";
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
toJSON() {
|
|
1337
|
-
return {
|
|
1338
|
-
name: this.name,
|
|
1339
|
-
category: this.category,
|
|
1340
|
-
message: this.message,
|
|
1341
|
-
details: this.details,
|
|
1342
|
-
};
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
```
|
|
1346
|
-
|
|
1347
|
-
### Error Response Format
|
|
1348
|
-
|
|
1349
|
-
```typescript
|
|
1350
|
-
// MCP tool error response
|
|
1351
|
-
interface ErrorResponse {
|
|
1352
|
-
error: {
|
|
1353
|
-
code: string; // ErrorCategory
|
|
1354
|
-
message: string;
|
|
1355
|
-
details?: unknown;
|
|
1356
|
-
retryable: boolean;
|
|
1357
|
-
suggestion?: string;
|
|
1358
|
-
};
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
// Example
|
|
1362
|
-
{
|
|
1363
|
-
error: {
|
|
1364
|
-
code: "SESSION_NOT_FOUND",
|
|
1365
|
-
message: "Session abc123 not found",
|
|
1366
|
-
retryable: false,
|
|
1367
|
-
suggestion: "Check session ID with list_sessions tool",
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
```
|
|
1371
|
-
|
|
1372
|
-
### Retry Strategy
|
|
1373
|
-
|
|
1374
|
-
```typescript
|
|
1375
|
-
// src/utils/retry.ts
|
|
1376
|
-
export async function retry<T>(
|
|
1377
|
-
fn: () => Promise<T>,
|
|
1378
|
-
options: {
|
|
1379
|
-
maxAttempts: number;
|
|
1380
|
-
baseDelay: number;
|
|
1381
|
-
maxDelay: number;
|
|
1382
|
-
retryableErrors: ErrorCategory[];
|
|
1383
|
-
}
|
|
1384
|
-
): Promise<T> {
|
|
1385
|
-
let lastError: Error;
|
|
1386
|
-
|
|
1387
|
-
for (let attempt = 0; attempt < options.maxAttempts; attempt++) {
|
|
1388
|
-
try {
|
|
1389
|
-
return await fn();
|
|
1390
|
-
} catch (error) {
|
|
1391
|
-
lastError = error;
|
|
1392
|
-
|
|
1393
|
-
if (!isRetryable(error, options.retryableErrors)) {
|
|
1394
|
-
throw error;
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
const delay = Math.min(
|
|
1398
|
-
options.baseDelay * Math.pow(2, attempt),
|
|
1399
|
-
options.maxDelay
|
|
1400
|
-
);
|
|
1401
|
-
|
|
1402
|
-
await sleep(delay);
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
throw lastError;
|
|
1407
|
-
}
|
|
1408
|
-
```
|
|
1409
|
-
|
|
1410
|
-
---
|
|
1411
|
-
|
|
1412
|
-
## Performance Considerations
|
|
1413
|
-
|
|
1414
|
-
### 1. State Management
|
|
1415
|
-
|
|
1416
|
-
- **In-memory cache**: Fast lookups for active sessions
|
|
1417
|
-
- **Lazy persistence**: Write to disk on state changes, not every tick
|
|
1418
|
-
- **Batch updates**: Aggregate multiple updates before persisting
|
|
1419
|
-
|
|
1420
|
-
### 2. Process Pool
|
|
1421
|
-
|
|
1422
|
-
```typescript
|
|
1423
|
-
// Reuse processes when possible
|
|
1424
|
-
class ProcessPool {
|
|
1425
|
-
private pool: Map<string, ChildProcess> = new Map();
|
|
1426
|
-
|
|
1427
|
-
acquire(sessionId: string): ChildProcess {
|
|
1428
|
-
return this.pool.get(sessionId) || this.spawn(sessionId);
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
release(sessionId: string): void {
|
|
1432
|
-
// Keep process alive for reuse
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
```
|
|
1436
|
-
|
|
1437
|
-
### 3. Streaming
|
|
1438
|
-
|
|
1439
|
-
```typescript
|
|
1440
|
-
// Stream large outputs instead of buffering
|
|
1441
|
-
async function* streamOutput(sessionId: string): AsyncIterable<string> {
|
|
1442
|
-
const process = getProcess(sessionId);
|
|
1443
|
-
|
|
1444
|
-
for await (const chunk of process.stdout) {
|
|
1445
|
-
yield chunk.toString();
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
```
|
|
1449
|
-
|
|
1450
|
-
### 4. Connection Pooling
|
|
1451
|
-
|
|
1452
|
-
```typescript
|
|
1453
|
-
// Pool connections to Doppler API
|
|
1454
|
-
class DopplerConnectionPool {
|
|
1455
|
-
private connections: Array<DopplerClient> = [];
|
|
1456
|
-
|
|
1457
|
-
acquire(): DopplerClient {
|
|
1458
|
-
return this.connections.pop() || this.create();
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
release(client: DopplerClient): void {
|
|
1462
|
-
this.connections.push(client);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
```
|
|
1466
|
-
|
|
1467
|
-
---
|
|
1468
|
-
|
|
1469
|
-
## Testing Strategy
|
|
1470
|
-
|
|
1471
|
-
### Unit Tests
|
|
1472
|
-
|
|
1473
|
-
```typescript
|
|
1474
|
-
// src/tools/__tests__/start-session.test.ts
|
|
1475
|
-
import { describe, it, expect, vi } from "vitest";
|
|
1476
|
-
import { startSession } from "../start-session.js";
|
|
1477
|
-
import { z } from "zod";
|
|
1478
|
-
|
|
1479
|
-
describe("start_session tool", () => {
|
|
1480
|
-
it("should validate input schema", () => {
|
|
1481
|
-
const input = {
|
|
1482
|
-
cwd: "/project",
|
|
1483
|
-
doppler_project: "my-project",
|
|
1484
|
-
};
|
|
1485
|
-
|
|
1486
|
-
expect(() => StartSessionSchema.parse(input)).not.toThrow();
|
|
1487
|
-
});
|
|
1488
|
-
|
|
1489
|
-
it("should generate unique session ID if not provided", async () => {
|
|
1490
|
-
const result = await startSession({ cwd: "/project" });
|
|
1491
|
-
|
|
1492
|
-
expect(result.session_id).toMatch(
|
|
1493
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
|
1494
|
-
);
|
|
1495
|
-
});
|
|
1496
|
-
|
|
1497
|
-
it("should wrap command with doppler run", async () => {
|
|
1498
|
-
const spy = vi.spyOn(DopplerWrapper, "wrapCommand");
|
|
1499
|
-
|
|
1500
|
-
await startSession({
|
|
1501
|
-
cwd: "/project",
|
|
1502
|
-
doppler_project: "my-project",
|
|
1503
|
-
});
|
|
1504
|
-
|
|
1505
|
-
expect(spy).toHaveBeenCalledWith(
|
|
1506
|
-
expect.stringContaining("claude-code"),
|
|
1507
|
-
{ project: "my-project", config: "dev" }
|
|
1508
|
-
);
|
|
1509
|
-
});
|
|
1510
|
-
});
|
|
1511
|
-
```
|
|
1512
|
-
|
|
1513
|
-
### Integration Tests
|
|
1514
|
-
|
|
1515
|
-
```typescript
|
|
1516
|
-
// tests/integration/session-lifecycle.test.ts
|
|
1517
|
-
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
1518
|
-
import { ClaudeCodeMCP } from "@claude-code/mcp";
|
|
1519
|
-
|
|
1520
|
-
describe("Session lifecycle", () => {
|
|
1521
|
-
const mcp = new ClaudeCodeMCP();
|
|
1522
|
-
let sessionId: string;
|
|
1523
|
-
|
|
1524
|
-
it("should start a new session", async () => {
|
|
1525
|
-
const result = await mcp.callTool("start_session", {
|
|
1526
|
-
cwd: "/tmp/test-project",
|
|
1527
|
-
});
|
|
1528
|
-
|
|
1529
|
-
expect(result.status).toBe("ready");
|
|
1530
|
-
sessionId = result.session_id;
|
|
1531
|
-
});
|
|
1532
|
-
|
|
1533
|
-
it("should list the session", async () => {
|
|
1534
|
-
const result = await mcp.callTool("list_sessions", {
|
|
1535
|
-
status_filter: "active",
|
|
1536
|
-
});
|
|
1537
|
-
|
|
1538
|
-
expect(result.sessions).toContainEqual(
|
|
1539
|
-
expect.objectContaining({ session_id: sessionId })
|
|
1540
|
-
);
|
|
1541
|
-
});
|
|
1542
|
-
|
|
1543
|
-
it("should send a message", async () => {
|
|
1544
|
-
const result = await mcp.callTool("send_message", {
|
|
1545
|
-
session_id: sessionId,
|
|
1546
|
-
message: "Hello, Claude!",
|
|
1547
|
-
wait_for_response: true,
|
|
1548
|
-
});
|
|
1549
|
-
|
|
1550
|
-
expect(result.message_sent).toBe(true);
|
|
1551
|
-
expect(result.response).toBeDefined();
|
|
1552
|
-
});
|
|
1553
|
-
|
|
1554
|
-
it("should kill the session", async () => {
|
|
1555
|
-
const result = await mcp.callTool("kill_session", {
|
|
1556
|
-
session_id: sessionId,
|
|
1557
|
-
});
|
|
1558
|
-
|
|
1559
|
-
expect(result.status).toBe("killed");
|
|
1560
|
-
});
|
|
1561
|
-
});
|
|
1562
|
-
```
|
|
1563
|
-
|
|
1564
|
-
### Load Tests
|
|
1565
|
-
|
|
1566
|
-
```typescript
|
|
1567
|
-
// tests/load/concurrent-sessions.test.ts
|
|
1568
|
-
import { describe, it, expect } from "vitest";
|
|
1569
|
-
import { ClaudeCodeMCP } from "@claude-code/mcp";
|
|
1570
|
-
|
|
1571
|
-
describe("Concurrent sessions", () => {
|
|
1572
|
-
it("should handle 10 concurrent sessions", async () => {
|
|
1573
|
-
const mcp = new ClaudeCodeMCP();
|
|
1574
|
-
const sessionIds: string[] = [];
|
|
1575
|
-
|
|
1576
|
-
// Start 10 sessions concurrently
|
|
1577
|
-
const promises = Array.from({ length: 10 }, (_, i) =>
|
|
1578
|
-
mcp.callTool("start_session", {
|
|
1579
|
-
cwd: `/tmp/test-project-${i}`,
|
|
1580
|
-
})
|
|
1581
|
-
);
|
|
1582
|
-
|
|
1583
|
-
const results = await Promise.all(promises);
|
|
1584
|
-
|
|
1585
|
-
results.forEach((result) => {
|
|
1586
|
-
expect(result.status).toBe("ready");
|
|
1587
|
-
sessionIds.push(result.session_id);
|
|
1588
|
-
});
|
|
1589
|
-
|
|
1590
|
-
// Cleanup
|
|
1591
|
-
await Promise.all(
|
|
1592
|
-
sessionIds.map((id) =>
|
|
1593
|
-
mcp.callTool("kill_session", { session_id: id })
|
|
1594
|
-
)
|
|
1595
|
-
);
|
|
1596
|
-
});
|
|
1597
|
-
});
|
|
1598
|
-
```
|
|
1599
|
-
|
|
1600
|
-
---
|
|
1601
|
-
|
|
1602
|
-
## Future Enhancements
|
|
1603
|
-
|
|
1604
|
-
### Phase 2 Features
|
|
1605
|
-
|
|
1606
|
-
1. **Session Templates**
|
|
1607
|
-
```typescript
|
|
1608
|
-
const template = await mcp.createTemplate("code-review", {
|
|
1609
|
-
prompt: "Review for security and performance",
|
|
1610
|
-
model: "claude-sonnet-4-5",
|
|
1611
|
-
max_turns: 50,
|
|
1612
|
-
});
|
|
1613
|
-
|
|
1614
|
-
const session = await mcp.startFromTemplate("code-review", {
|
|
1615
|
-
cwd: "/project",
|
|
1616
|
-
});
|
|
1617
|
-
```
|
|
1618
|
-
|
|
1619
|
-
2. **Session Cloning**
|
|
1620
|
-
```typescript
|
|
1621
|
-
const clone = await mcp.cloneSession(originalSessionId, {
|
|
1622
|
-
cwd: "/new-location",
|
|
1623
|
-
});
|
|
1624
|
-
```
|
|
1625
|
-
|
|
1626
|
-
3. **Batch Operations**
|
|
1627
|
-
```typescript
|
|
1628
|
-
await mcp.batchOperation({
|
|
1629
|
-
operation: "send_message",
|
|
1630
|
-
session_ids: [id1, id2, id3],
|
|
1631
|
-
message: "Update available",
|
|
1632
|
-
});
|
|
1633
|
-
```
|
|
1634
|
-
|
|
1635
|
-
4. **Session Metrics**
|
|
1636
|
-
```typescript
|
|
1637
|
-
const metrics = await mcp.getSessionMetrics(sessionId);
|
|
1638
|
-
// {
|
|
1639
|
-
// avg_response_time: 1.2,
|
|
1640
|
-
// total_tokens: 50000,
|
|
1641
|
-
// error_rate: 0.01,
|
|
1642
|
-
// }
|
|
1643
|
-
```
|
|
1644
|
-
|
|
1645
|
-
5. **Webhooks**
|
|
1646
|
-
```typescript
|
|
1647
|
-
await mcp.registerWebhook({
|
|
1648
|
-
events: ["session.ready", "session.error"],
|
|
1649
|
-
url: "https://hook.example.com/events",
|
|
1650
|
-
});
|
|
1651
|
-
```
|
|
1652
|
-
|
|
1653
|
-
### Phase 3: AI Coordination
|
|
1654
|
-
|
|
1655
|
-
1. **Auto-scaling Teams**
|
|
1656
|
-
- Automatically spawn teammates based on workload
|
|
1657
|
-
- Load balancing across sessions
|
|
1658
|
-
|
|
1659
|
-
2. **Task Orchestration**
|
|
1660
|
-
- DAG-based task dependencies
|
|
1661
|
-
- Parallel execution with coordination
|
|
1662
|
-
|
|
1663
|
-
3. **Context Sharing**
|
|
1664
|
-
- Shared memory between sessions
|
|
1665
|
-
- Distributed state management
|
|
1666
|
-
|
|
1667
|
-
---
|
|
1668
|
-
|
|
1669
|
-
## Appendix
|
|
1670
|
-
|
|
1671
|
-
### A. Configuration Files
|
|
1672
|
-
|
|
1673
|
-
**package.json**
|
|
1674
|
-
```json
|
|
1675
|
-
{
|
|
1676
|
-
"name": "@claude-code/mcp",
|
|
1677
|
-
"version": "1.0.0",
|
|
1678
|
-
"type": "module",
|
|
1679
|
-
"main": "./dist/index.js",
|
|
1680
|
-
"types": "./dist/index.d.ts",
|
|
1681
|
-
"bin": {
|
|
1682
|
-
"claude-code-mcp": "./dist/cli.js"
|
|
1683
|
-
},
|
|
1684
|
-
"dependencies": {
|
|
1685
|
-
"@ebowwa/teammates": "workspace:*",
|
|
1686
|
-
"zod": "^3.22.0"
|
|
1687
|
-
},
|
|
1688
|
-
"devDependencies": {
|
|
1689
|
-
"@types/node": "^20.0.0",
|
|
1690
|
-
"typescript": "^5.0.0",
|
|
1691
|
-
"vitest": "^1.0.0"
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
```
|
|
1695
|
-
|
|
1696
|
-
**tsconfig.json**
|
|
1697
|
-
```json
|
|
1698
|
-
{
|
|
1699
|
-
"compilerOptions": {
|
|
1700
|
-
"target": "ES2022",
|
|
1701
|
-
"module": "ESNext",
|
|
1702
|
-
"moduleResolution": "bundler",
|
|
1703
|
-
"strict": true,
|
|
1704
|
-
"esModuleInterop": true,
|
|
1705
|
-
"skipLibCheck": true,
|
|
1706
|
-
"outDir": "./dist",
|
|
1707
|
-
"rootDir": "./src"
|
|
1708
|
-
},
|
|
1709
|
-
"include": ["src/**/*"],
|
|
1710
|
-
"exclude": ["node_modules", "dist"]
|
|
1711
|
-
}
|
|
1712
|
-
```
|
|
1713
|
-
|
|
1714
|
-
### B. Environment Variables
|
|
1715
|
-
|
|
1716
|
-
```bash
|
|
1717
|
-
# Doppler Configuration
|
|
1718
|
-
DOPPLER_PROJECT=claude-code-mcp
|
|
1719
|
-
DOPPLER_CONFIG=dev
|
|
1720
|
-
|
|
1721
|
-
# MCP Server Configuration
|
|
1722
|
-
MCP_PORT=8914
|
|
1723
|
-
MCP_LOG_LEVEL=info
|
|
1724
|
-
|
|
1725
|
-
# Session Limits
|
|
1726
|
-
MAX_CONCURRENT_SESSIONS=10
|
|
1727
|
-
MAX_SESSION_DURATION=86400000
|
|
1728
|
-
|
|
1729
|
-
# State Persistence
|
|
1730
|
-
STATE_DIR=~/.claude/sessions
|
|
1731
|
-
BACKUP_ENABLED=true
|
|
1732
|
-
```
|
|
1733
|
-
|
|
1734
|
-
### C. MCP Server Manifest
|
|
1735
|
-
|
|
1736
|
-
```json
|
|
1737
|
-
{
|
|
1738
|
-
"name": "claude-code-mcp",
|
|
1739
|
-
"version": "1.0.0",
|
|
1740
|
-
"description": "Claude Code session management via MCP",
|
|
1741
|
-
"protocolVersion": "2024-11-05",
|
|
1742
|
-
"capabilities": {
|
|
1743
|
-
"tools": {}
|
|
1744
|
-
},
|
|
1745
|
-
"tools": [
|
|
1746
|
-
{
|
|
1747
|
-
"name": "start_session",
|
|
1748
|
-
"description": "Start a new Claude Code session"
|
|
1749
|
-
},
|
|
1750
|
-
{
|
|
1751
|
-
"name": "resume_session",
|
|
1752
|
-
"description": "Resume an existing Claude Code session"
|
|
1753
|
-
},
|
|
1754
|
-
{
|
|
1755
|
-
"name": "kill_session",
|
|
1756
|
-
"description": "Terminate a Claude Code session"
|
|
1757
|
-
},
|
|
1758
|
-
{
|
|
1759
|
-
"name": "list_sessions",
|
|
1760
|
-
"description": "List all Claude Code sessions"
|
|
1761
|
-
},
|
|
1762
|
-
{
|
|
1763
|
-
"name": "send_message",
|
|
1764
|
-
"description": "Send a message to a Claude Code session"
|
|
1765
|
-
},
|
|
1766
|
-
{
|
|
1767
|
-
"name": "sync_context",
|
|
1768
|
-
"description": "Sync context to a Claude Code session"
|
|
1769
|
-
},
|
|
1770
|
-
{
|
|
1771
|
-
"name": "wait_for_completion",
|
|
1772
|
-
"description": "Wait for session to complete current task"
|
|
1773
|
-
},
|
|
1774
|
-
{
|
|
1775
|
-
"name": "stream_output",
|
|
1776
|
-
"description": "Stream real-time output from a session"
|
|
1777
|
-
}
|
|
1778
|
-
]
|
|
1779
|
-
}
|
|
1780
|
-
```
|
|
1781
|
-
|
|
1782
|
-
---
|
|
1783
|
-
|
|
1784
|
-
## Sources
|
|
1785
|
-
|
|
1786
|
-
This architecture document references the following resources:
|
|
1787
|
-
|
|
1788
|
-
- [Model Context Protocol Architecture Documentation](https://modelcontextprotocol.info/docs/concepts/architecture/)
|
|
1789
|
-
- [MCP TypeScript SDK (mcp-auth)](https://github.com/mcp-auth/mcp-typescript-sdk)
|
|
1790
|
-
- [@mcpbay/easy-mcp-server](https://jsr.io/@mcpbay/easy-mcp-server)
|
|
1791
|
-
- [MCP Server Development Guide](https://github.com/cyanheads/model-context-protocol-resources/blob/main/guides/mcp-server-development-guide.md)
|
|
1792
|
-
- [详解MCP 传输机制](https://blog.csdn.net/sinat_37574187/article/details/147185240)
|
|
1793
|
-
- [@enth/mcp-sdk](https://www.npmjs.com/package/%40enth/mcp-sdk)
|
|
1794
|
-
- [Chinese MCP Architecture Guide](https://mcpcn.com/docs/concepts/architecture/)
|
|
1795
|
-
- [MCP Server and Client with SSE](https://levelup.gitconnected.com/mcp-server-and-client-with-sse-the-new-streamable-http-d860850d9d9d)
|
|
1796
|
-
- [Tencent Cloud MCP TypeScript SDK](https://cloud.tencent.com/developer/mcp/server/11591)
|
|
1797
|
-
- [The Model Context Protocol — Complete Tutorial](https://medium.com/@nimritakoul01/the-model-context-protocol-mcp-a-complete-tutorial-a3abe8a7f4ef)
|
|
1798
|
-
|
|
1799
|
-
---
|
|
1800
|
-
|
|
1801
|
-
**Document Status:** Ready for Implementation
|
|
1802
|
-
**Next Steps:** Create package.json, implement tool handlers, write tests
|