@bluehawks/cli 1.0.0
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/.eslintrc.json +36 -0
- package/.prettierrc +8 -0
- package/README.md +288 -0
- package/dist/cli/app.d.ts +12 -0
- package/dist/cli/app.d.ts.map +1 -0
- package/dist/cli/app.js +201 -0
- package/dist/cli/app.js.map +1 -0
- package/dist/cli/commands/index.d.ts +56 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +201 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/config/constants.d.ts +32 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +39 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +56 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +28 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/settings.d.ts +20 -0
- package/dist/config/settings.d.ts.map +1 -0
- package/dist/config/settings.js +102 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/core/agents/agent.d.ts +33 -0
- package/dist/core/agents/agent.d.ts.map +1 -0
- package/dist/core/agents/agent.js +156 -0
- package/dist/core/agents/agent.js.map +1 -0
- package/dist/core/agents/index.d.ts +3 -0
- package/dist/core/agents/index.d.ts.map +1 -0
- package/dist/core/agents/index.js +3 -0
- package/dist/core/agents/index.js.map +1 -0
- package/dist/core/agents/orchestrator.d.ts +56 -0
- package/dist/core/agents/orchestrator.d.ts.map +1 -0
- package/dist/core/agents/orchestrator.js +151 -0
- package/dist/core/agents/orchestrator.js.map +1 -0
- package/dist/core/api/client.d.ts +46 -0
- package/dist/core/api/client.d.ts.map +1 -0
- package/dist/core/api/client.js +223 -0
- package/dist/core/api/client.js.map +1 -0
- package/dist/core/api/index.d.ts +3 -0
- package/dist/core/api/index.d.ts.map +1 -0
- package/dist/core/api/index.js +3 -0
- package/dist/core/api/index.js.map +1 -0
- package/dist/core/api/types.d.ts +126 -0
- package/dist/core/api/types.d.ts.map +1 -0
- package/dist/core/api/types.js +16 -0
- package/dist/core/api/types.js.map +1 -0
- package/dist/core/hooks/index.d.ts +3 -0
- package/dist/core/hooks/index.d.ts.map +1 -0
- package/dist/core/hooks/index.js +3 -0
- package/dist/core/hooks/index.js.map +1 -0
- package/dist/core/hooks/manager.d.ts +43 -0
- package/dist/core/hooks/manager.d.ts.map +1 -0
- package/dist/core/hooks/manager.js +178 -0
- package/dist/core/hooks/manager.js.map +1 -0
- package/dist/core/hooks/types.d.ts +68 -0
- package/dist/core/hooks/types.d.ts.map +1 -0
- package/dist/core/hooks/types.js +6 -0
- package/dist/core/hooks/types.js.map +1 -0
- package/dist/core/mcp/client.d.ts +48 -0
- package/dist/core/mcp/client.d.ts.map +1 -0
- package/dist/core/mcp/client.js +139 -0
- package/dist/core/mcp/client.js.map +1 -0
- package/dist/core/mcp/index.d.ts +3 -0
- package/dist/core/mcp/index.d.ts.map +1 -0
- package/dist/core/mcp/index.js +3 -0
- package/dist/core/mcp/index.js.map +1 -0
- package/dist/core/mcp/manager.d.ts +46 -0
- package/dist/core/mcp/manager.d.ts.map +1 -0
- package/dist/core/mcp/manager.js +133 -0
- package/dist/core/mcp/manager.js.map +1 -0
- package/dist/core/plugins/index.d.ts +3 -0
- package/dist/core/plugins/index.d.ts.map +1 -0
- package/dist/core/plugins/index.js +3 -0
- package/dist/core/plugins/index.js.map +1 -0
- package/dist/core/plugins/loader.d.ts +63 -0
- package/dist/core/plugins/loader.d.ts.map +1 -0
- package/dist/core/plugins/loader.js +258 -0
- package/dist/core/plugins/loader.js.map +1 -0
- package/dist/core/plugins/types.d.ts +95 -0
- package/dist/core/plugins/types.d.ts.map +1 -0
- package/dist/core/plugins/types.js +6 -0
- package/dist/core/plugins/types.js.map +1 -0
- package/dist/core/session/index.d.ts +3 -0
- package/dist/core/session/index.d.ts.map +1 -0
- package/dist/core/session/index.js +3 -0
- package/dist/core/session/index.js.map +1 -0
- package/dist/core/session/manager.d.ts +57 -0
- package/dist/core/session/manager.d.ts.map +1 -0
- package/dist/core/session/manager.js +182 -0
- package/dist/core/session/manager.js.map +1 -0
- package/dist/core/session/storage.d.ts +42 -0
- package/dist/core/session/storage.d.ts.map +1 -0
- package/dist/core/session/storage.js +138 -0
- package/dist/core/session/storage.js.map +1 -0
- package/dist/core/tools/definitions/file.d.ts +6 -0
- package/dist/core/tools/definitions/file.d.ts.map +1 -0
- package/dist/core/tools/definitions/file.js +276 -0
- package/dist/core/tools/definitions/file.js.map +1 -0
- package/dist/core/tools/definitions/git.d.ts +6 -0
- package/dist/core/tools/definitions/git.d.ts.map +1 -0
- package/dist/core/tools/definitions/git.js +294 -0
- package/dist/core/tools/definitions/git.js.map +1 -0
- package/dist/core/tools/definitions/index.d.ts +11 -0
- package/dist/core/tools/definitions/index.d.ts.map +1 -0
- package/dist/core/tools/definitions/index.js +22 -0
- package/dist/core/tools/definitions/index.js.map +1 -0
- package/dist/core/tools/definitions/search.d.ts +6 -0
- package/dist/core/tools/definitions/search.d.ts.map +1 -0
- package/dist/core/tools/definitions/search.js +223 -0
- package/dist/core/tools/definitions/search.js.map +1 -0
- package/dist/core/tools/definitions/shell.d.ts +6 -0
- package/dist/core/tools/definitions/shell.d.ts.map +1 -0
- package/dist/core/tools/definitions/shell.js +190 -0
- package/dist/core/tools/definitions/shell.js.map +1 -0
- package/dist/core/tools/definitions/web.d.ts +6 -0
- package/dist/core/tools/definitions/web.d.ts.map +1 -0
- package/dist/core/tools/definitions/web.js +104 -0
- package/dist/core/tools/definitions/web.js.map +1 -0
- package/dist/core/tools/executor.d.ts +24 -0
- package/dist/core/tools/executor.d.ts.map +1 -0
- package/dist/core/tools/executor.js +111 -0
- package/dist/core/tools/executor.js.map +1 -0
- package/dist/core/tools/index.d.ts +4 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js +4 -0
- package/dist/core/tools/index.js.map +1 -0
- package/dist/core/tools/registry.d.ts +23 -0
- package/dist/core/tools/registry.d.ts.map +1 -0
- package/dist/core/tools/registry.js +28 -0
- package/dist/core/tools/registry.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +352 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
- package/src/cli/app.tsx +319 -0
- package/src/cli/commands/index.ts +261 -0
- package/src/config/constants.ts +45 -0
- package/src/config/index.ts +3 -0
- package/src/config/schema.ts +36 -0
- package/src/config/settings.ts +121 -0
- package/src/core/agents/agent.ts +205 -0
- package/src/core/agents/index.ts +2 -0
- package/src/core/agents/orchestrator.ts +223 -0
- package/src/core/api/client.ts +300 -0
- package/src/core/api/index.ts +2 -0
- package/src/core/api/types.ts +149 -0
- package/src/core/hooks/index.ts +2 -0
- package/src/core/hooks/manager.ts +212 -0
- package/src/core/hooks/types.ts +116 -0
- package/src/core/mcp/client.ts +198 -0
- package/src/core/mcp/index.ts +2 -0
- package/src/core/mcp/manager.ts +153 -0
- package/src/core/plugins/index.ts +2 -0
- package/src/core/plugins/loader.ts +312 -0
- package/src/core/plugins/types.ts +111 -0
- package/src/core/session/index.ts +2 -0
- package/src/core/session/manager.ts +246 -0
- package/src/core/session/storage.ts +184 -0
- package/src/core/tools/definitions/file.ts +312 -0
- package/src/core/tools/definitions/git.ts +326 -0
- package/src/core/tools/definitions/index.ts +24 -0
- package/src/core/tools/definitions/search.ts +266 -0
- package/src/core/tools/definitions/shell.ts +228 -0
- package/src/core/tools/definitions/web.ts +113 -0
- package/src/core/tools/executor.ts +145 -0
- package/src/core/tools/index.ts +3 -0
- package/src/core/tools/registry.ts +44 -0
- package/src/index.ts +407 -0
- package/tsconfig.json +40 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bluehawks CLI - Hooks Types
|
|
3
|
+
* Type definitions for the hooks system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type HookEvent =
|
|
7
|
+
| 'SessionStart'
|
|
8
|
+
| 'UserPromptSubmit'
|
|
9
|
+
| 'PreToolUse'
|
|
10
|
+
| 'PostToolUse'
|
|
11
|
+
| 'PostToolUseFailure'
|
|
12
|
+
| 'Stop'
|
|
13
|
+
| 'SessionEnd';
|
|
14
|
+
|
|
15
|
+
export interface HookContext {
|
|
16
|
+
sessionId: string;
|
|
17
|
+
projectPath: string;
|
|
18
|
+
model: string;
|
|
19
|
+
timestamp: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SessionStartInput extends HookContext {
|
|
23
|
+
cwd: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UserPromptSubmitInput extends HookContext {
|
|
27
|
+
prompt: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PreToolUseInput extends HookContext {
|
|
31
|
+
toolName: string;
|
|
32
|
+
toolInput: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PostToolUseInput extends HookContext {
|
|
36
|
+
toolName: string;
|
|
37
|
+
toolInput: Record<string, unknown>;
|
|
38
|
+
toolOutput: string;
|
|
39
|
+
duration: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PostToolUseFailureInput extends HookContext {
|
|
43
|
+
toolName: string;
|
|
44
|
+
toolInput: Record<string, unknown>;
|
|
45
|
+
error: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface StopInput extends HookContext {
|
|
49
|
+
reason: 'completed' | 'cancelled' | 'error';
|
|
50
|
+
messageCount: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SessionEndInput extends HookContext {
|
|
54
|
+
duration: number;
|
|
55
|
+
tokensUsed: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type HookInput =
|
|
59
|
+
| SessionStartInput
|
|
60
|
+
| UserPromptSubmitInput
|
|
61
|
+
| PreToolUseInput
|
|
62
|
+
| PostToolUseInput
|
|
63
|
+
| PostToolUseFailureInput
|
|
64
|
+
| StopInput
|
|
65
|
+
| SessionEndInput;
|
|
66
|
+
|
|
67
|
+
export interface HookOutput {
|
|
68
|
+
// Block the action (for Pre* hooks)
|
|
69
|
+
block?: boolean;
|
|
70
|
+
blockReason?: string;
|
|
71
|
+
|
|
72
|
+
// Modify the input (for Pre* hooks)
|
|
73
|
+
modifiedInput?: Record<string, unknown>;
|
|
74
|
+
|
|
75
|
+
// Add content to the conversation
|
|
76
|
+
addContent?: string;
|
|
77
|
+
|
|
78
|
+
// Custom data to pass to the next hook
|
|
79
|
+
data?: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface HookHandler {
|
|
83
|
+
// Unique identifier for this handler
|
|
84
|
+
id: string;
|
|
85
|
+
|
|
86
|
+
// Human-readable name
|
|
87
|
+
name: string;
|
|
88
|
+
|
|
89
|
+
// Which event this hook handles
|
|
90
|
+
event: HookEvent;
|
|
91
|
+
|
|
92
|
+
// Command to execute (shell command)
|
|
93
|
+
command?: string;
|
|
94
|
+
|
|
95
|
+
// Inline handler function
|
|
96
|
+
handler?: (input: HookInput) => Promise<HookOutput | void>;
|
|
97
|
+
|
|
98
|
+
// Whether to run asynchronously (don't block)
|
|
99
|
+
async?: boolean;
|
|
100
|
+
|
|
101
|
+
// Timeout in milliseconds
|
|
102
|
+
timeout?: number;
|
|
103
|
+
|
|
104
|
+
// Matcher pattern (for tool-specific hooks)
|
|
105
|
+
matcher?: string | RegExp;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface HooksConfig {
|
|
109
|
+
SessionStart?: HookHandler[];
|
|
110
|
+
UserPromptSubmit?: HookHandler[];
|
|
111
|
+
PreToolUse?: HookHandler[];
|
|
112
|
+
PostToolUse?: HookHandler[];
|
|
113
|
+
PostToolUseFailure?: HookHandler[];
|
|
114
|
+
Stop?: HookHandler[];
|
|
115
|
+
SessionEnd?: HookHandler[];
|
|
116
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bluehawks CLI - MCP Client
|
|
3
|
+
* Model Context Protocol client for connecting to external data sources
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
7
|
+
import * as readline from 'node:readline';
|
|
8
|
+
|
|
9
|
+
export interface MCPServerConfig {
|
|
10
|
+
command: string;
|
|
11
|
+
args?: string[];
|
|
12
|
+
env?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MCPTool {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: string;
|
|
20
|
+
properties?: Record<string, unknown>;
|
|
21
|
+
required?: string[];
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface MCPResource {
|
|
26
|
+
uri: string;
|
|
27
|
+
name: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
mimeType?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface JsonRpcRequest {
|
|
33
|
+
jsonrpc: '2.0';
|
|
34
|
+
id: number;
|
|
35
|
+
method: string;
|
|
36
|
+
params?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface JsonRpcResponse {
|
|
40
|
+
jsonrpc: '2.0';
|
|
41
|
+
id: number;
|
|
42
|
+
result?: unknown;
|
|
43
|
+
error?: { code: number; message: string };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class MCPClient {
|
|
47
|
+
private serverName: string;
|
|
48
|
+
private process: ChildProcess | null = null;
|
|
49
|
+
private requestId = 0;
|
|
50
|
+
private pendingRequests = new Map<number, {
|
|
51
|
+
resolve: (value: unknown) => void;
|
|
52
|
+
reject: (error: Error) => void;
|
|
53
|
+
}>();
|
|
54
|
+
private tools: MCPTool[] = [];
|
|
55
|
+
private resources: MCPResource[] = [];
|
|
56
|
+
private reader: readline.Interface | null = null;
|
|
57
|
+
|
|
58
|
+
constructor(serverName: string) {
|
|
59
|
+
this.serverName = serverName;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async connect(config: MCPServerConfig): Promise<void> {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
this.process = spawn(config.command, config.args || [], {
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
+
env: { ...process.env, ...config.env },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!this.process.stdout || !this.process.stdin) {
|
|
70
|
+
reject(new Error('Failed to create MCP process'));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.reader = readline.createInterface({
|
|
75
|
+
input: this.process.stdout,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.reader.on('line', (line) => {
|
|
79
|
+
try {
|
|
80
|
+
const response = JSON.parse(line) as JsonRpcResponse;
|
|
81
|
+
const pending = this.pendingRequests.get(response.id);
|
|
82
|
+
if (pending) {
|
|
83
|
+
this.pendingRequests.delete(response.id);
|
|
84
|
+
if (response.error) {
|
|
85
|
+
pending.reject(new Error(response.error.message));
|
|
86
|
+
} else {
|
|
87
|
+
pending.resolve(response.result);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
// Ignore non-JSON output
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this.process.on('error', (err) => {
|
|
96
|
+
reject(err);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Initialize the MCP connection
|
|
100
|
+
this.sendRequest('initialize', {
|
|
101
|
+
protocolVersion: '2024-11-05',
|
|
102
|
+
capabilities: {},
|
|
103
|
+
clientInfo: { name: 'bluehawks-cli', version: '1.0.0' },
|
|
104
|
+
}).then(() => {
|
|
105
|
+
// Send initialized notification
|
|
106
|
+
this.sendNotification('notifications/initialized');
|
|
107
|
+
resolve();
|
|
108
|
+
}).catch(reject);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private sendRequest(method: string, params?: Record<string, unknown>): Promise<unknown> {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
if (!this.process?.stdin) {
|
|
115
|
+
reject(new Error('MCP process not connected'));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const id = ++this.requestId;
|
|
120
|
+
const request: JsonRpcRequest = {
|
|
121
|
+
jsonrpc: '2.0',
|
|
122
|
+
id,
|
|
123
|
+
method,
|
|
124
|
+
params,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
128
|
+
this.process.stdin.write(JSON.stringify(request) + '\n');
|
|
129
|
+
|
|
130
|
+
// Timeout after 30 seconds
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
if (this.pendingRequests.has(id)) {
|
|
133
|
+
this.pendingRequests.delete(id);
|
|
134
|
+
reject(new Error(`Request ${method} timed out`));
|
|
135
|
+
}
|
|
136
|
+
}, 30000);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private sendNotification(method: string, params?: Record<string, unknown>): void {
|
|
141
|
+
if (!this.process?.stdin) return;
|
|
142
|
+
|
|
143
|
+
const notification = {
|
|
144
|
+
jsonrpc: '2.0',
|
|
145
|
+
method,
|
|
146
|
+
params,
|
|
147
|
+
};
|
|
148
|
+
this.process.stdin.write(JSON.stringify(notification) + '\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async listTools(): Promise<MCPTool[]> {
|
|
152
|
+
const result = await this.sendRequest('tools/list') as { tools: MCPTool[] };
|
|
153
|
+
this.tools = result.tools || [];
|
|
154
|
+
return this.tools;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async listResources(): Promise<MCPResource[]> {
|
|
158
|
+
const result = await this.sendRequest('resources/list') as { resources: MCPResource[] };
|
|
159
|
+
this.resources = result.resources || [];
|
|
160
|
+
return this.resources;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
164
|
+
const result = await this.sendRequest('tools/call', {
|
|
165
|
+
name,
|
|
166
|
+
arguments: args,
|
|
167
|
+
});
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async readResource(uri: string): Promise<{ contents: unknown[] }> {
|
|
172
|
+
const result = await this.sendRequest('resources/read', { uri });
|
|
173
|
+
return result as { contents: unknown[] };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
getTools(): MCPTool[] {
|
|
177
|
+
return this.tools;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
getResources(): MCPResource[] {
|
|
181
|
+
return this.resources;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getServerName(): string {
|
|
185
|
+
return this.serverName;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async disconnect(): Promise<void> {
|
|
189
|
+
if (this.reader) {
|
|
190
|
+
this.reader.close();
|
|
191
|
+
}
|
|
192
|
+
if (this.process) {
|
|
193
|
+
this.process.kill();
|
|
194
|
+
this.process = null;
|
|
195
|
+
}
|
|
196
|
+
this.pendingRequests.clear();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bluehawks CLI - MCP Manager
|
|
3
|
+
* Manages MCP server connections and integrates tools
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'node:fs/promises';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import * as os from 'node:os';
|
|
9
|
+
import { MCPClient, type MCPServerConfig, type MCPTool } from './client.js';
|
|
10
|
+
import { toolRegistry } from '../tools/index.js';
|
|
11
|
+
import type { ToolDefinition } from '../api/types.js';
|
|
12
|
+
|
|
13
|
+
export interface MCPConfig {
|
|
14
|
+
mcpServers?: Record<string, MCPServerConfig>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class MCPManager {
|
|
18
|
+
private clients: Map<string, MCPClient> = new Map();
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
// Manager initialized - config will be loaded on connectAll
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Load MCP configuration from file
|
|
26
|
+
*/
|
|
27
|
+
async loadConfig(): Promise<MCPConfig | null> {
|
|
28
|
+
const paths = [
|
|
29
|
+
path.join(process.cwd(), '.mcp.json'),
|
|
30
|
+
path.join(os.homedir(), '.bluehawks', '.mcp.json'),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const configPath of paths) {
|
|
34
|
+
try {
|
|
35
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
36
|
+
return JSON.parse(content) as MCPConfig;
|
|
37
|
+
} catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Connect to all configured MCP servers
|
|
46
|
+
*/
|
|
47
|
+
async connectAll(): Promise<void> {
|
|
48
|
+
const config = await this.loadConfig();
|
|
49
|
+
if (!config?.mcpServers) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
54
|
+
try {
|
|
55
|
+
await this.connect(name, serverConfig);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Failed to connect to MCP server ${name}:`, error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Connect to a specific MCP server
|
|
64
|
+
*/
|
|
65
|
+
async connect(name: string, config: MCPServerConfig): Promise<MCPClient> {
|
|
66
|
+
const client = new MCPClient(name);
|
|
67
|
+
await client.connect(config);
|
|
68
|
+
|
|
69
|
+
// List and register tools
|
|
70
|
+
const tools = await client.listTools();
|
|
71
|
+
for (const tool of tools) {
|
|
72
|
+
this.registerMCPTool(name, tool);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.clients.set(name, client);
|
|
76
|
+
return client;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Register an MCP tool as a Bluehawks tool
|
|
81
|
+
*/
|
|
82
|
+
private registerMCPTool(serverName: string, mcpTool: MCPTool): void {
|
|
83
|
+
const toolName = `mcp_${serverName}_${mcpTool.name}`;
|
|
84
|
+
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
const definition: ToolDefinition = {
|
|
87
|
+
type: 'function',
|
|
88
|
+
function: {
|
|
89
|
+
name: toolName,
|
|
90
|
+
description: `[MCP:${serverName}] ${mcpTool.description}`,
|
|
91
|
+
parameters: mcpTool.inputSchema as ToolDefinition['function']['parameters'],
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
const clients = this.clients;
|
|
97
|
+
toolRegistry.register({
|
|
98
|
+
name: toolName,
|
|
99
|
+
definition,
|
|
100
|
+
execute: async (args: Record<string, unknown>) => {
|
|
101
|
+
const client = clients.get(serverName);
|
|
102
|
+
if (!client) {
|
|
103
|
+
return `MCP server ${serverName} not connected`;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const result = await client.callTool(mcpTool.name, args);
|
|
107
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return `MCP tool error: ${error instanceof Error ? error.message : String(error)}`;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
safeToAutoRun: false, // MCP tools require approval by default
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get all connected clients
|
|
118
|
+
*/
|
|
119
|
+
getClients(): Map<string, MCPClient> {
|
|
120
|
+
return this.clients;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get a specific client
|
|
125
|
+
*/
|
|
126
|
+
getClient(name: string): MCPClient | undefined {
|
|
127
|
+
return this.clients.get(name);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Disconnect all MCP servers
|
|
132
|
+
*/
|
|
133
|
+
async disconnectAll(): Promise<void> {
|
|
134
|
+
for (const client of this.clients.values()) {
|
|
135
|
+
await client.disconnect();
|
|
136
|
+
}
|
|
137
|
+
this.clients.clear();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Disconnect a specific server
|
|
142
|
+
*/
|
|
143
|
+
async disconnect(name: string): Promise<void> {
|
|
144
|
+
const client = this.clients.get(name);
|
|
145
|
+
if (client) {
|
|
146
|
+
await client.disconnect();
|
|
147
|
+
this.clients.delete(name);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Singleton instance
|
|
153
|
+
export const mcpManager = new MCPManager();
|