@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.
Files changed (48) hide show
  1. package/dist/__tests__/advanced.test.d.ts +6 -0
  2. package/dist/__tests__/advanced.test.d.ts.map +1 -0
  3. package/dist/__tests__/advanced.test.js +354 -0
  4. package/dist/__tests__/advanced.test.js.map +1 -0
  5. package/dist/advanced.d.ts +109 -0
  6. package/dist/advanced.d.ts.map +1 -0
  7. package/dist/advanced.js +427 -0
  8. package/dist/advanced.js.map +1 -0
  9. package/dist/cli-wrapper.d.ts +202 -0
  10. package/dist/cli-wrapper.d.ts.map +1 -0
  11. package/dist/cli-wrapper.js +347 -0
  12. package/dist/cli-wrapper.js.map +1 -0
  13. package/dist/cli-wrapper.test.d.ts +12 -0
  14. package/dist/cli-wrapper.test.d.ts.map +1 -0
  15. package/dist/cli-wrapper.test.js +354 -0
  16. package/dist/cli-wrapper.test.js.map +1 -0
  17. package/dist/cli.d.ts +16 -0
  18. package/dist/cli.d.ts.map +1 -0
  19. package/dist/cli.js +354 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/index.d.ts +10 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +561 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/integration.test.d.ts +12 -0
  26. package/dist/integration.test.d.ts.map +1 -0
  27. package/dist/integration.test.js +716 -0
  28. package/dist/integration.test.js.map +1 -0
  29. package/dist/queue.d.ts +87 -0
  30. package/dist/queue.d.ts.map +1 -0
  31. package/dist/queue.js +273 -0
  32. package/dist/queue.js.map +1 -0
  33. package/dist/teammates-integration.d.ts +128 -0
  34. package/dist/teammates-integration.d.ts.map +1 -0
  35. package/dist/teammates-integration.js +353 -0
  36. package/dist/teammates-integration.js.map +1 -0
  37. package/dist/test-config.d.ts +104 -0
  38. package/dist/test-config.d.ts.map +1 -0
  39. package/dist/test-config.js +439 -0
  40. package/dist/test-config.js.map +1 -0
  41. package/dist/tools.d.ts +97 -0
  42. package/dist/tools.d.ts.map +1 -0
  43. package/dist/tools.js +627 -0
  44. package/dist/tools.js.map +1 -0
  45. package/package.json +7 -1
  46. package/ARCHITECTURE.md +0 -1802
  47. package/DOCUMENTATION.md +0 -747
  48. 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