groundswell 0.0.1

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 (120) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.claude/system_prompts/task-breakdown.md +100 -0
  3. package/PRPs/001-hierarchical-workflow-engine.md +2438 -0
  4. package/PRPs/PRDs/001-hierarchical-workflow-engine.md +543 -0
  5. package/PRPs/PRDs/002-agent-prompt.md +390 -0
  6. package/PRPs/PRDs/003-agent-prompt.md +943 -0
  7. package/PRPs/PRDs/004-agent-prompt.md +1136 -0
  8. package/PRPs/PRDs/tasks-001.json +492 -0
  9. package/PRPs/README.md +83 -0
  10. package/PRPs/templates/prp_base.md +222 -0
  11. package/README.md +218 -0
  12. package/docs/agent.md +422 -0
  13. package/docs/prompt.md +419 -0
  14. package/docs/workflow.md +600 -0
  15. package/examples/README.md +244 -0
  16. package/examples/examples/01-basic-workflow.ts +100 -0
  17. package/examples/examples/02-decorator-options.ts +217 -0
  18. package/examples/examples/03-parent-child.ts +241 -0
  19. package/examples/examples/04-observers-debugger.ts +340 -0
  20. package/examples/examples/05-error-handling.ts +387 -0
  21. package/examples/examples/06-concurrent-tasks.ts +352 -0
  22. package/examples/examples/07-agent-loops.ts +432 -0
  23. package/examples/examples/08-sdk-features.ts +667 -0
  24. package/examples/examples/09-reflection.ts +573 -0
  25. package/examples/examples/10-introspection.ts +550 -0
  26. package/examples/index.ts +143 -0
  27. package/examples/utils/helpers.ts +57 -0
  28. package/llms_full.txt +5890 -0
  29. package/package.json +63 -0
  30. package/plan/P1P2/PRP.md +527 -0
  31. package/plan/P1P2/research/LRU_CACHE_BEST_PRACTICES.md +1929 -0
  32. package/plan/P1P2/research/LRU_CACHE_CODE_PATTERNS.md +857 -0
  33. package/plan/P1P2/research/LRU_CACHE_INTEGRATION_GUIDE.md +738 -0
  34. package/plan/P1P2/research/LRU_CACHE_RESEARCH_INDEX.md +424 -0
  35. package/plan/P1P2/research/REFLECTION_INDEX.md +291 -0
  36. package/plan/P1P2/research/REFLECTION_RESEARCH_REPORT.md +1342 -0
  37. package/plan/P1P2/research/RESEARCH_SUMMARY.md +342 -0
  38. package/plan/P1P2/research/anthropic-sdk.md +174 -0
  39. package/plan/P1P2/research/async-local-storage.md +200 -0
  40. package/plan/P1P2/research/reflection-code-patterns.md +1205 -0
  41. package/plan/P1P2/research/reflection-decision-matrix.md +421 -0
  42. package/plan/P1P2/research/reflection-implementation-guide.md +1341 -0
  43. package/plan/P1P2/research/reflection-integration-guide.md +834 -0
  44. package/plan/P1P2/research/reflection-patterns.md +1468 -0
  45. package/plan/P1P2/research/reflection-quick-reference.md +558 -0
  46. package/plan/P1P2/research/zod-schema.md +152 -0
  47. package/plan/P3P4/PRP.md +1388 -0
  48. package/plan/P3P4/research/caching-lru.md +116 -0
  49. package/plan/P3P4/research/introspection-tools.md +177 -0
  50. package/plan/P3P4/research/reflection-patterns.md +117 -0
  51. package/plan/P4P5/PRP.md +1136 -0
  52. package/plan/P4P5/research/RESEARCH_SUMMARY.md +151 -0
  53. package/plan/architecture/external_deps.md +358 -0
  54. package/plan/architecture/system_context.md +242 -0
  55. package/plan/backlog.json +867 -0
  56. package/plan/research/INTROSPECTION_RESEARCH_SUMMARY.md +378 -0
  57. package/plan/research/README-INTROSPECTION.md +352 -0
  58. package/plan/research/agent-introspection-patterns.md +1085 -0
  59. package/plan/research/introspection-security-guide.md +928 -0
  60. package/plan/research/introspection-tool-examples.md +875 -0
  61. package/scripts/generate-llms-full.ts +206 -0
  62. package/src/__tests__/integration/agent-workflow.test.ts +256 -0
  63. package/src/__tests__/integration/tree-mirroring.test.ts +114 -0
  64. package/src/__tests__/unit/agent.test.ts +169 -0
  65. package/src/__tests__/unit/cache-key.test.ts +182 -0
  66. package/src/__tests__/unit/cache.test.ts +172 -0
  67. package/src/__tests__/unit/context.test.ts +138 -0
  68. package/src/__tests__/unit/decorators.test.ts +100 -0
  69. package/src/__tests__/unit/introspection-tools.test.ts +277 -0
  70. package/src/__tests__/unit/prompt.test.ts +135 -0
  71. package/src/__tests__/unit/reflection.test.ts +210 -0
  72. package/src/__tests__/unit/tree-debugger.test.ts +85 -0
  73. package/src/__tests__/unit/workflow.test.ts +81 -0
  74. package/src/cache/cache-key.ts +244 -0
  75. package/src/cache/cache.ts +236 -0
  76. package/src/cache/index.ts +8 -0
  77. package/src/core/agent.ts +573 -0
  78. package/src/core/context.ts +119 -0
  79. package/src/core/event-tree.ts +260 -0
  80. package/src/core/factory.ts +123 -0
  81. package/src/core/index.ts +17 -0
  82. package/src/core/logger.ts +87 -0
  83. package/src/core/mcp-handler.ts +184 -0
  84. package/src/core/prompt.ts +150 -0
  85. package/src/core/workflow-context.ts +349 -0
  86. package/src/core/workflow.ts +302 -0
  87. package/src/debugger/index.ts +1 -0
  88. package/src/debugger/tree-debugger.ts +210 -0
  89. package/src/decorators/index.ts +3 -0
  90. package/src/decorators/observed-state.ts +95 -0
  91. package/src/decorators/step.ts +139 -0
  92. package/src/decorators/task.ts +96 -0
  93. package/src/examples/index.ts +2 -0
  94. package/src/examples/tdd-orchestrator.ts +65 -0
  95. package/src/examples/test-cycle-workflow.ts +64 -0
  96. package/src/index.ts +140 -0
  97. package/src/reflection/index.ts +5 -0
  98. package/src/reflection/reflection.ts +407 -0
  99. package/src/tools/index.ts +36 -0
  100. package/src/tools/introspection.ts +464 -0
  101. package/src/types/agent.ts +90 -0
  102. package/src/types/decorators.ts +25 -0
  103. package/src/types/error-strategy.ts +13 -0
  104. package/src/types/error.ts +20 -0
  105. package/src/types/events.ts +74 -0
  106. package/src/types/index.ts +55 -0
  107. package/src/types/logging.ts +24 -0
  108. package/src/types/observer.ts +18 -0
  109. package/src/types/prompt.ts +40 -0
  110. package/src/types/reflection.ts +117 -0
  111. package/src/types/sdk-primitives.ts +128 -0
  112. package/src/types/snapshot.ts +14 -0
  113. package/src/types/workflow-context.ts +163 -0
  114. package/src/types/workflow.ts +37 -0
  115. package/src/utils/id.ts +11 -0
  116. package/src/utils/index.ts +3 -0
  117. package/src/utils/observable.ts +77 -0
  118. package/tasks.json +0 -0
  119. package/tsconfig.json +22 -0
  120. package/vitest.config.ts +16 -0
@@ -0,0 +1,260 @@
1
+ /**
2
+ * EventTreeHandle - Queryable interface for the workflow event tree
3
+ *
4
+ * Provides methods for traversing and querying the event tree
5
+ * that mirrors workflow execution.
6
+ */
7
+
8
+ import type {
9
+ EventTreeHandle,
10
+ EventNode,
11
+ EventMetrics,
12
+ } from '../types/workflow-context.js';
13
+ import type { WorkflowNode, WorkflowEvent } from '../types/index.js';
14
+
15
+ /**
16
+ * Implementation of EventTreeHandle
17
+ */
18
+ export class EventTreeHandleImpl implements EventTreeHandle {
19
+ /** Root node of the tree */
20
+ public readonly root: EventNode;
21
+
22
+ /** Index of nodes by ID for fast lookup */
23
+ private nodeIndex: Map<string, EventNode> = new Map();
24
+
25
+ /**
26
+ * Create a new EventTreeHandle from a workflow node
27
+ * @param workflowNode Root workflow node
28
+ */
29
+ constructor(workflowNode: WorkflowNode) {
30
+ this.root = this.buildEventNode(workflowNode);
31
+ this.buildIndex(this.root);
32
+ }
33
+
34
+ /**
35
+ * Get a node by ID
36
+ * @param id Node ID to find
37
+ */
38
+ public getNode(id: string): EventNode | undefined {
39
+ return this.nodeIndex.get(id);
40
+ }
41
+
42
+ /**
43
+ * Get all children of a node
44
+ * @param id Parent node ID
45
+ */
46
+ public getChildren(id: string): EventNode[] {
47
+ const node = this.nodeIndex.get(id);
48
+ return node?.children ?? [];
49
+ }
50
+
51
+ /**
52
+ * Get all ancestors of a node (from node up to root, excluding the node itself)
53
+ * @param id Node ID
54
+ */
55
+ public getAncestors(id: string): EventNode[] {
56
+ const ancestors: EventNode[] = [];
57
+ const node = this.nodeIndex.get(id);
58
+
59
+ if (!node || !node.parentId) {
60
+ return ancestors;
61
+ }
62
+
63
+ let currentId = node.parentId;
64
+ while (currentId) {
65
+ const parent = this.nodeIndex.get(currentId);
66
+ if (!parent) break;
67
+ ancestors.push(parent);
68
+ currentId = parent.parentId ?? '';
69
+ }
70
+
71
+ return ancestors;
72
+ }
73
+
74
+ /**
75
+ * Export tree as JSON
76
+ */
77
+ public toJSON(): EventNode {
78
+ return this.cloneNode(this.root);
79
+ }
80
+
81
+ /**
82
+ * Rebuild the tree from an updated workflow node
83
+ * @param workflowNode Updated workflow node
84
+ */
85
+ public rebuild(workflowNode: WorkflowNode): void {
86
+ const newRoot = this.buildEventNode(workflowNode);
87
+ (this.root as { children: EventNode[] }).children = newRoot.children;
88
+ Object.assign(this.root, {
89
+ type: newRoot.type,
90
+ name: newRoot.name,
91
+ timestamp: newRoot.timestamp,
92
+ metrics: newRoot.metrics,
93
+ });
94
+
95
+ this.nodeIndex.clear();
96
+ this.buildIndex(this.root);
97
+ }
98
+
99
+ /**
100
+ * Build an EventNode from a WorkflowNode
101
+ */
102
+ private buildEventNode(
103
+ wfNode: WorkflowNode,
104
+ parentId?: string
105
+ ): EventNode {
106
+ const eventNode: EventNode = {
107
+ id: wfNode.id,
108
+ type: 'workflow',
109
+ timestamp: Date.now(),
110
+ name: wfNode.name,
111
+ parentId,
112
+ children: [],
113
+ metrics: this.extractMetrics(wfNode),
114
+ };
115
+
116
+ // Add event nodes from workflow events
117
+ for (const event of wfNode.events) {
118
+ const childNode = this.eventToNode(event, wfNode.id);
119
+ if (childNode) {
120
+ eventNode.children.push(childNode);
121
+ }
122
+ }
123
+
124
+ // Add child workflow nodes
125
+ for (const child of wfNode.children) {
126
+ eventNode.children.push(this.buildEventNode(child, wfNode.id));
127
+ }
128
+
129
+ return eventNode;
130
+ }
131
+
132
+ /**
133
+ * Convert a WorkflowEvent to an EventNode
134
+ */
135
+ private eventToNode(
136
+ event: WorkflowEvent,
137
+ parentId: string
138
+ ): EventNode | null {
139
+ const baseNode: Omit<EventNode, 'type' | 'payload'> = {
140
+ id: `${parentId}-${event.type}-${Date.now()}`,
141
+ timestamp: Date.now(),
142
+ parentId,
143
+ children: [],
144
+ };
145
+
146
+ switch (event.type) {
147
+ case 'stepStart':
148
+ return {
149
+ ...baseNode,
150
+ type: 'step',
151
+ name: event.step,
152
+ };
153
+
154
+ case 'stepEnd':
155
+ return {
156
+ ...baseNode,
157
+ type: 'stepComplete',
158
+ name: event.step,
159
+ metrics: { duration: event.duration },
160
+ };
161
+
162
+ case 'agentPromptStart':
163
+ return {
164
+ ...baseNode,
165
+ id: `${event.agentId}-prompt-${event.promptId}`,
166
+ type: 'agentPrompt',
167
+ name: event.agentName,
168
+ payload: { promptId: event.promptId },
169
+ };
170
+
171
+ case 'agentPromptEnd':
172
+ return {
173
+ ...baseNode,
174
+ id: `${event.agentId}-promptEnd-${event.promptId}`,
175
+ type: 'agentPromptComplete',
176
+ name: event.agentName,
177
+ metrics: {
178
+ duration: event.duration,
179
+ tokenUsage: event.tokenUsage
180
+ ? {
181
+ input: event.tokenUsage.input_tokens,
182
+ output: event.tokenUsage.output_tokens,
183
+ }
184
+ : undefined,
185
+ },
186
+ };
187
+
188
+ case 'toolInvocation':
189
+ return {
190
+ ...baseNode,
191
+ type: 'tool',
192
+ name: event.toolName,
193
+ payload: { input: event.input, output: event.output },
194
+ metrics: { duration: event.duration },
195
+ };
196
+
197
+ case 'error':
198
+ return {
199
+ ...baseNode,
200
+ type: 'error',
201
+ name: event.error.message,
202
+ payload: event.error,
203
+ };
204
+
205
+ default:
206
+ return null;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Extract metrics from a workflow node
212
+ */
213
+ private extractMetrics(wfNode: WorkflowNode): EventMetrics | undefined {
214
+ // Calculate total duration from step events
215
+ const stepEndEvents = wfNode.events.filter(
216
+ (e) => e.type === 'stepEnd'
217
+ ) as Array<{ type: 'stepEnd'; duration: number }>;
218
+
219
+ if (stepEndEvents.length === 0) {
220
+ return undefined;
221
+ }
222
+
223
+ const totalDuration = stepEndEvents.reduce(
224
+ (sum, e) => sum + e.duration,
225
+ 0
226
+ );
227
+
228
+ return { duration: totalDuration };
229
+ }
230
+
231
+ /**
232
+ * Build index of all nodes
233
+ */
234
+ private buildIndex(node: EventNode): void {
235
+ this.nodeIndex.set(node.id, node);
236
+ for (const child of node.children) {
237
+ this.buildIndex(child);
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Deep clone a node for JSON export
243
+ */
244
+ private cloneNode(node: EventNode): EventNode {
245
+ return {
246
+ ...node,
247
+ children: node.children.map((c) => this.cloneNode(c)),
248
+ };
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Create an EventTreeHandle from a workflow node
254
+ * @param workflowNode Root workflow node
255
+ */
256
+ export function createEventTreeHandle(
257
+ workflowNode: WorkflowNode
258
+ ): EventTreeHandle {
259
+ return new EventTreeHandleImpl(workflowNode);
260
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Factory Functions - Convenience functions for dynamic workflow, agent, and prompt creation
3
+ *
4
+ * These functions provide a simple API for creating workflow components dynamically
5
+ * at runtime, useful for metaprogramming and agent-driven workflow construction.
6
+ */
7
+
8
+ import { Workflow, type WorkflowExecutor } from './workflow.js';
9
+ import { Agent } from './agent.js';
10
+ import { Prompt } from './prompt.js';
11
+ import type { WorkflowConfig } from '../types/workflow-context.js';
12
+ import type { AgentConfig, PromptConfig } from '../types/index.js';
13
+
14
+ /**
15
+ * Create a new Workflow instance
16
+ *
17
+ * @param config Workflow configuration
18
+ * @param executor Executor function that receives WorkflowContext
19
+ * @returns Configured Workflow instance
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const workflow = createWorkflow(
24
+ * { name: 'DataPipeline', enableReflection: true },
25
+ * async (ctx) => {
26
+ * const data = await ctx.step('fetch', () => fetchData());
27
+ * const processed = await ctx.step('process', () => processData(data));
28
+ * return processed;
29
+ * }
30
+ * );
31
+ *
32
+ * const result = await workflow.run();
33
+ * ```
34
+ */
35
+ export function createWorkflow<T>(
36
+ config: WorkflowConfig,
37
+ executor: WorkflowExecutor<T>
38
+ ): Workflow<T> {
39
+ return new Workflow(config, executor);
40
+ }
41
+
42
+ /**
43
+ * Create a new Agent instance
44
+ *
45
+ * @param config Agent configuration
46
+ * @returns Configured Agent instance
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const agent = createAgent({
51
+ * name: 'DataAnalyst',
52
+ * system: 'You analyze data and provide insights.',
53
+ * enableCache: true,
54
+ * enableReflection: true,
55
+ * });
56
+ *
57
+ * const result = await agent.prompt(analysisPrompt);
58
+ * ```
59
+ */
60
+ export function createAgent(config: AgentConfig): Agent {
61
+ return new Agent(config);
62
+ }
63
+
64
+ /**
65
+ * Create a new Prompt instance
66
+ *
67
+ * @param config Prompt configuration
68
+ * @returns Configured Prompt instance
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * const prompt = createPrompt({
73
+ * user: 'Analyze this data',
74
+ * data: { values: [1, 2, 3, 4, 5] },
75
+ * responseFormat: z.object({
76
+ * summary: z.string(),
77
+ * insights: z.array(z.string()),
78
+ * }),
79
+ * });
80
+ *
81
+ * const result = await agent.prompt(prompt);
82
+ * ```
83
+ */
84
+ export function createPrompt<T>(config: PromptConfig<T>): Prompt<T> {
85
+ return new Prompt(config);
86
+ }
87
+
88
+ /**
89
+ * Create a workflow with an inline executor (shorthand)
90
+ *
91
+ * @param name Workflow name
92
+ * @param executor Executor function
93
+ * @returns Configured Workflow instance
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * const workflow = quickWorkflow('ProcessData', async (ctx) => {
98
+ * return await ctx.step('process', () => processData());
99
+ * });
100
+ * ```
101
+ */
102
+ export function quickWorkflow<T>(
103
+ name: string,
104
+ executor: WorkflowExecutor<T>
105
+ ): Workflow<T> {
106
+ return new Workflow({ name }, executor);
107
+ }
108
+
109
+ /**
110
+ * Create a simple agent with minimal configuration (shorthand)
111
+ *
112
+ * @param name Agent name
113
+ * @param system System prompt
114
+ * @returns Configured Agent instance
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * const agent = quickAgent('Helper', 'You are a helpful assistant.');
119
+ * ```
120
+ */
121
+ export function quickAgent(name: string, system?: string): Agent {
122
+ return new Agent({ name, system });
123
+ }
@@ -0,0 +1,17 @@
1
+ export { WorkflowLogger } from './logger.js';
2
+ export { Workflow, type WorkflowExecutor } from './workflow.js';
3
+ export { Agent, type PromptResult } from './agent.js';
4
+ export { Prompt } from './prompt.js';
5
+ export { MCPHandler, type ToolExecutor } from './mcp-handler.js';
6
+ export { EventTreeHandleImpl, createEventTreeHandle } from './event-tree.js';
7
+ export { WorkflowContextImpl, createWorkflowContext } from './workflow-context.js';
8
+ export {
9
+ getExecutionContext,
10
+ requireExecutionContext,
11
+ runInContext,
12
+ runInContextSync,
13
+ hasExecutionContext,
14
+ createChildContext,
15
+ agentExecutionStorage,
16
+ type AgentExecutionContext,
17
+ } from './context.js';
@@ -0,0 +1,87 @@
1
+ import type { WorkflowNode, LogEntry, LogLevel, WorkflowObserver } from '../types/index.js';
2
+ import { generateId } from '../utils/id.js';
3
+
4
+ /**
5
+ * Logger that emits log entries to workflow node and observers
6
+ */
7
+ export class WorkflowLogger {
8
+ private readonly parentLogId?: string;
9
+
10
+ constructor(
11
+ private readonly node: WorkflowNode,
12
+ private readonly observers: WorkflowObserver[],
13
+ parentLogId?: string
14
+ ) {
15
+ this.parentLogId = parentLogId;
16
+ }
17
+
18
+ /**
19
+ * Emit a log entry to the node and all observers
20
+ */
21
+ private emit(entry: LogEntry): void {
22
+ this.node.logs.push(entry);
23
+ for (const obs of this.observers) {
24
+ try {
25
+ obs.onLog(entry);
26
+ } catch (err) {
27
+ console.error('Observer onLog error:', err);
28
+ }
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Create a log entry with the given level
34
+ */
35
+ private log(level: LogLevel, message: string, data?: unknown): void {
36
+ const entry: LogEntry = {
37
+ id: generateId(),
38
+ workflowId: this.node.id,
39
+ timestamp: Date.now(),
40
+ level,
41
+ message,
42
+ data,
43
+ };
44
+
45
+ // Add parent log ID if this is a child logger
46
+ if (this.parentLogId) {
47
+ entry.parentLogId = this.parentLogId;
48
+ }
49
+
50
+ this.emit(entry);
51
+ }
52
+
53
+ /**
54
+ * Log a debug message
55
+ */
56
+ debug(message: string, data?: unknown): void {
57
+ this.log('debug', message, data);
58
+ }
59
+
60
+ /**
61
+ * Log an info message
62
+ */
63
+ info(message: string, data?: unknown): void {
64
+ this.log('info', message, data);
65
+ }
66
+
67
+ /**
68
+ * Log a warning message
69
+ */
70
+ warn(message: string, data?: unknown): void {
71
+ this.log('warn', message, data);
72
+ }
73
+
74
+ /**
75
+ * Log an error message
76
+ */
77
+ error(message: string, data?: unknown): void {
78
+ this.log('error', message, data);
79
+ }
80
+
81
+ /**
82
+ * Create a child logger that includes parentLogId
83
+ */
84
+ child(parentLogId: string): WorkflowLogger {
85
+ return new WorkflowLogger(this.node, this.observers, parentLogId);
86
+ }
87
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * MCP Handler - Manages MCP server connections and tool execution
3
+ *
4
+ * Provides integration with MCP (Model Context Protocol) servers,
5
+ * converting MCP tools to Anthropic Tool format.
6
+ */
7
+
8
+ import type { MCPServer, Tool, ToolResult } from '../types/index.js';
9
+
10
+ /**
11
+ * Tool executor function type
12
+ */
13
+ export type ToolExecutor = (input: unknown) => Promise<unknown>;
14
+
15
+ /**
16
+ * Registered tool with its executor
17
+ */
18
+ interface RegisteredTool {
19
+ tool: Tool;
20
+ executor: ToolExecutor;
21
+ serverName: string;
22
+ }
23
+
24
+ /**
25
+ * MCPHandler - Manages MCP server registration and tool execution
26
+ *
27
+ * Currently supports:
28
+ * - inprocess transport: Direct tool registration
29
+ * - stdio transport: Documented for future implementation
30
+ */
31
+ export class MCPHandler {
32
+ /** Registered MCP servers */
33
+ private servers: Map<string, MCPServer> = new Map();
34
+
35
+ /** Registered tools from all servers */
36
+ private registeredTools: Map<string, RegisteredTool> = new Map();
37
+
38
+ /** Custom tool executors for inprocess servers */
39
+ private toolExecutors: Map<string, ToolExecutor> = new Map();
40
+
41
+ /**
42
+ * Register an MCP server
43
+ * @param server MCP server configuration
44
+ */
45
+ public registerServer(server: MCPServer): void {
46
+ if (this.servers.has(server.name)) {
47
+ throw new Error(`MCP server '${server.name}' is already registered`);
48
+ }
49
+
50
+ this.servers.set(server.name, server);
51
+
52
+ // Register tools from the server
53
+ if (server.tools) {
54
+ for (const tool of server.tools) {
55
+ const fullName = `${server.name}__${tool.name}`;
56
+ this.registeredTools.set(fullName, {
57
+ tool: { ...tool, name: fullName },
58
+ executor: this.createToolExecutor(server, tool),
59
+ serverName: server.name,
60
+ });
61
+ }
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Unregister an MCP server
67
+ * @param name Server name to unregister
68
+ */
69
+ public unregisterServer(name: string): void {
70
+ const server = this.servers.get(name);
71
+ if (server?.tools) {
72
+ for (const tool of server.tools) {
73
+ const fullName = `${name}__${tool.name}`;
74
+ this.registeredTools.delete(fullName);
75
+ }
76
+ }
77
+ this.servers.delete(name);
78
+ }
79
+
80
+ /**
81
+ * Register a custom tool executor for an inprocess tool
82
+ * @param serverName Server name
83
+ * @param toolName Tool name
84
+ * @param executor Executor function
85
+ */
86
+ public registerToolExecutor(
87
+ serverName: string,
88
+ toolName: string,
89
+ executor: ToolExecutor
90
+ ): void {
91
+ const fullName = `${serverName}__${toolName}`;
92
+ this.toolExecutors.set(fullName, executor);
93
+ }
94
+
95
+ /**
96
+ * Get all tools from registered servers in Anthropic format
97
+ * @returns Array of Tool definitions
98
+ */
99
+ public getTools(): Tool[] {
100
+ return Array.from(this.registeredTools.values()).map((rt) => rt.tool);
101
+ }
102
+
103
+ /**
104
+ * Execute a tool by name
105
+ * @param toolName Full tool name (serverName__toolName)
106
+ * @param input Tool input
107
+ * @returns Tool result
108
+ */
109
+ public async executeTool(
110
+ toolName: string,
111
+ input: unknown
112
+ ): Promise<ToolResult> {
113
+ const registered = this.registeredTools.get(toolName);
114
+ if (!registered) {
115
+ return {
116
+ type: 'tool_result',
117
+ tool_use_id: '',
118
+ content: `Tool '${toolName}' not found`,
119
+ is_error: true,
120
+ };
121
+ }
122
+
123
+ try {
124
+ const result = await registered.executor(input);
125
+ return {
126
+ type: 'tool_result',
127
+ tool_use_id: '',
128
+ content: typeof result === 'string' ? result : JSON.stringify(result),
129
+ };
130
+ } catch (error) {
131
+ const message = error instanceof Error ? error.message : 'Unknown error';
132
+ return {
133
+ type: 'tool_result',
134
+ tool_use_id: '',
135
+ content: `Tool execution failed: ${message}`,
136
+ is_error: true,
137
+ };
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Check if a tool is registered
143
+ * @param toolName Tool name to check
144
+ */
145
+ public hasTool(toolName: string): boolean {
146
+ return this.registeredTools.has(toolName);
147
+ }
148
+
149
+ /**
150
+ * Get registered server names
151
+ */
152
+ public getServerNames(): string[] {
153
+ return Array.from(this.servers.keys());
154
+ }
155
+
156
+ /**
157
+ * Create a tool executor based on transport type
158
+ */
159
+ private createToolExecutor(server: MCPServer, tool: Tool): ToolExecutor {
160
+ const fullName = `${server.name}__${tool.name}`;
161
+
162
+ if (server.transport === 'inprocess') {
163
+ // For inprocess, look for registered executor
164
+ return async (input: unknown) => {
165
+ const executor = this.toolExecutors.get(fullName);
166
+ if (!executor) {
167
+ throw new Error(
168
+ `No executor registered for inprocess tool '${fullName}'. ` +
169
+ `Use registerToolExecutor() to provide an executor.`
170
+ );
171
+ }
172
+ return executor(input);
173
+ };
174
+ }
175
+
176
+ // stdio transport - placeholder for future implementation
177
+ return async (_input: unknown) => {
178
+ throw new Error(
179
+ `stdio transport for MCP server '${server.name}' is not yet implemented. ` +
180
+ `Use inprocess transport or register a custom executor.`
181
+ );
182
+ };
183
+ }
184
+ }