oricore 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.
Files changed (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/dist/agent/agent/agentManager.d.ts +38 -0
  4. package/dist/agent/agent/builtin/common.d.ts +5 -0
  5. package/dist/agent/agent/builtin/explore.d.ts +5 -0
  6. package/dist/agent/agent/builtin/general-purpose.d.ts +5 -0
  7. package/dist/agent/agent/builtin/index.d.ts +5 -0
  8. package/dist/agent/agent/executor.d.ts +2 -0
  9. package/dist/agent/agent/types.d.ts +98 -0
  10. package/dist/api/engine.d.ts +213 -0
  11. package/dist/communication/index.d.ts +4 -0
  12. package/dist/communication/messageBus.d.ts +71 -0
  13. package/dist/core/at.d.ts +26 -0
  14. package/dist/core/backgroundTaskManager.d.ts +27 -0
  15. package/dist/core/compact.d.ts +9 -0
  16. package/dist/core/config.d.ts +103 -0
  17. package/dist/core/constants.d.ts +32 -0
  18. package/dist/core/context.d.ts +57 -0
  19. package/dist/core/globalData.d.ts +21 -0
  20. package/dist/core/history.d.ts +24 -0
  21. package/dist/core/ide.d.ts +103 -0
  22. package/dist/core/jsonl.d.ts +37 -0
  23. package/dist/core/llmsContext.d.ts +14 -0
  24. package/dist/core/loop.d.ts +82 -0
  25. package/dist/core/message.d.ts +132 -0
  26. package/dist/core/model.d.ts +79 -0
  27. package/dist/core/output-style/builtin/default.d.ts +2 -0
  28. package/dist/core/output-style/builtin/explanatory.d.ts +2 -0
  29. package/dist/core/output-style/builtin/index.d.ts +6 -0
  30. package/dist/core/output-style/builtin/miao.d.ts +2 -0
  31. package/dist/core/output-style/builtin/minimal.d.ts +2 -0
  32. package/dist/core/output-style/types.d.ts +6 -0
  33. package/dist/core/outputFormat.d.ts +29 -0
  34. package/dist/core/outputStyle.d.ts +43 -0
  35. package/dist/core/paths.d.ts +20 -0
  36. package/dist/core/planSystemPrompt.d.ts +5 -0
  37. package/dist/core/plugin.d.ts +138 -0
  38. package/dist/core/project.d.ts +64 -0
  39. package/dist/core/promptCache.d.ts +3 -0
  40. package/dist/core/query.d.ts +14 -0
  41. package/dist/core/rules.d.ts +8 -0
  42. package/dist/core/systemPrompt.d.ts +9 -0
  43. package/dist/core/thinking-config.d.ts +3 -0
  44. package/dist/core/usage.d.ts +14 -0
  45. package/dist/index.d.ts +16 -0
  46. package/dist/index.js +144432 -0
  47. package/dist/mcp/mcp.d.ts +49 -0
  48. package/dist/modes/builtin.d.ts +34 -0
  49. package/dist/modes/index.d.ts +8 -0
  50. package/dist/modes/registry.d.ts +18 -0
  51. package/dist/modes/types.d.ts +51 -0
  52. package/dist/platform/index.d.ts +5 -0
  53. package/dist/platform/node.d.ts +28 -0
  54. package/dist/platform/types.d.ts +41 -0
  55. package/dist/session/session.d.ts +43 -0
  56. package/dist/skill/skill.d.ts +79 -0
  57. package/dist/tools/tool.d.ts +119 -0
  58. package/dist/tools/tools/askUserQuestion.d.ts +48 -0
  59. package/dist/tools/tools/bash.d.ts +43 -0
  60. package/dist/tools/tools/edit.d.ts +9 -0
  61. package/dist/tools/tools/fetch.d.ts +9 -0
  62. package/dist/tools/tools/glob.d.ts +7 -0
  63. package/dist/tools/tools/grep.d.ts +22 -0
  64. package/dist/tools/tools/ls.d.ts +6 -0
  65. package/dist/tools/tools/read.d.ts +9 -0
  66. package/dist/tools/tools/skill.d.ts +7 -0
  67. package/dist/tools/tools/task.d.ts +14 -0
  68. package/dist/tools/tools/todo.d.ts +37 -0
  69. package/dist/tools/tools/write.d.ts +7 -0
  70. package/dist/utils/apiKeyRotation.d.ts +2 -0
  71. package/dist/utils/applyEdit.d.ts +17 -0
  72. package/dist/utils/background-detection.d.ts +2 -0
  73. package/dist/utils/dotenv.d.ts +9 -0
  74. package/dist/utils/env.d.ts +6 -0
  75. package/dist/utils/error.d.ts +11 -0
  76. package/dist/utils/execFileNoThrow.d.ts +8 -0
  77. package/dist/utils/files.d.ts +10 -0
  78. package/dist/utils/git.d.ts +163 -0
  79. package/dist/utils/ide.d.ts +27 -0
  80. package/dist/utils/ignore.d.ts +6 -0
  81. package/dist/utils/isLocal.d.ts +1 -0
  82. package/dist/utils/language.d.ts +9 -0
  83. package/dist/utils/list.d.ts +20 -0
  84. package/dist/utils/mergeSystemMessagesMiddleware.d.ts +2 -0
  85. package/dist/utils/messageNormalization.d.ts +22 -0
  86. package/dist/utils/path.d.ts +34 -0
  87. package/dist/utils/prependSystemMessageMiddleware.d.ts +2 -0
  88. package/dist/utils/project.d.ts +1 -0
  89. package/dist/utils/proxy.d.ts +18 -0
  90. package/dist/utils/randomUUID.d.ts +5 -0
  91. package/dist/utils/renderSessionMarkdown.d.ts +10 -0
  92. package/dist/utils/ripgrep.d.ts +16 -0
  93. package/dist/utils/safeFrontMatter.d.ts +11 -0
  94. package/dist/utils/safeParseJson.d.ts +1 -0
  95. package/dist/utils/safeStringify.d.ts +1 -0
  96. package/dist/utils/sanitizeAIResponse.d.ts +30 -0
  97. package/dist/utils/setTerminalTitle.d.ts +1 -0
  98. package/dist/utils/shell-execution.d.ts +44 -0
  99. package/dist/utils/string.d.ts +8 -0
  100. package/dist/utils/symbols.d.ts +14 -0
  101. package/dist/utils/system-encoding.d.ts +40 -0
  102. package/dist/utils/tokenCounter.d.ts +8 -0
  103. package/dist/utils/username.d.ts +1 -0
  104. package/package.json +106 -0
  105. package/src/agent/agent/agentManager.test.ts +124 -0
  106. package/src/agent/agent/agentManager.ts +372 -0
  107. package/src/agent/agent/builtin/common.ts +20 -0
  108. package/src/agent/agent/builtin/explore.ts +53 -0
  109. package/src/agent/agent/builtin/general-purpose.ts +38 -0
  110. package/src/agent/agent/builtin/index.ts +13 -0
  111. package/src/agent/agent/executor.test.ts +339 -0
  112. package/src/agent/agent/executor.ts +224 -0
  113. package/src/agent/agent/types.ts +119 -0
  114. package/src/api/engine.ts +466 -0
  115. package/src/communication/index.ts +18 -0
  116. package/src/communication/messageBus.ts +393 -0
  117. package/src/core/at.ts +315 -0
  118. package/src/core/backgroundTaskManager.ts +129 -0
  119. package/src/core/compact.ts +95 -0
  120. package/src/core/config.ts +441 -0
  121. package/src/core/constants.ts +82 -0
  122. package/src/core/context.ts +214 -0
  123. package/src/core/globalData.ts +77 -0
  124. package/src/core/history.ts +323 -0
  125. package/src/core/ide.ts +325 -0
  126. package/src/core/jsonl.ts +100 -0
  127. package/src/core/llmsContext.ts +117 -0
  128. package/src/core/loop.ts +638 -0
  129. package/src/core/message.ts +304 -0
  130. package/src/core/model.ts +2198 -0
  131. package/src/core/output-style/builtin/default.ts +9 -0
  132. package/src/core/output-style/builtin/explanatory.ts +22 -0
  133. package/src/core/output-style/builtin/index.ts +19 -0
  134. package/src/core/output-style/builtin/miao.ts +22 -0
  135. package/src/core/output-style/builtin/minimal.ts +8 -0
  136. package/src/core/output-style/types.ts +6 -0
  137. package/src/core/outputFormat.ts +93 -0
  138. package/src/core/outputStyle.ts +255 -0
  139. package/src/core/paths.ts +161 -0
  140. package/src/core/planSystemPrompt.ts +46 -0
  141. package/src/core/plugin.ts +299 -0
  142. package/src/core/project.ts +492 -0
  143. package/src/core/promptCache.ts +32 -0
  144. package/src/core/query.ts +46 -0
  145. package/src/core/rules.ts +56 -0
  146. package/src/core/systemPrompt.ts +176 -0
  147. package/src/core/thinking-config.ts +98 -0
  148. package/src/core/usage.ts +68 -0
  149. package/src/index.ts +39 -0
  150. package/src/mcp/mcp.ts +637 -0
  151. package/src/modes/builtin.ts +305 -0
  152. package/src/modes/index.ts +22 -0
  153. package/src/modes/registry.ts +39 -0
  154. package/src/modes/types.ts +56 -0
  155. package/src/platform/index.ts +6 -0
  156. package/src/platform/node.ts +108 -0
  157. package/src/platform/types.ts +54 -0
  158. package/src/plugins/index.ts +15 -0
  159. package/src/session/session.ts +187 -0
  160. package/src/skill/skill.ts +702 -0
  161. package/src/tools/tool.ts +378 -0
  162. package/src/tools/tools/askUserQuestion.ts +134 -0
  163. package/src/tools/tools/bash.test.ts +425 -0
  164. package/src/tools/tools/bash.ts +999 -0
  165. package/src/tools/tools/edit.ts +86 -0
  166. package/src/tools/tools/fetch.ts +129 -0
  167. package/src/tools/tools/glob.ts +69 -0
  168. package/src/tools/tools/grep.test.ts +194 -0
  169. package/src/tools/tools/grep.ts +358 -0
  170. package/src/tools/tools/ls.ts +51 -0
  171. package/src/tools/tools/read.test.ts +169 -0
  172. package/src/tools/tools/read.ts +284 -0
  173. package/src/tools/tools/skill.ts +73 -0
  174. package/src/tools/tools/task.test.ts +262 -0
  175. package/src/tools/tools/task.ts +284 -0
  176. package/src/tools/tools/todo.ts +269 -0
  177. package/src/tools/tools/write.ts +71 -0
  178. package/src/types.d.ts +18 -0
  179. package/src/utils/apiKeyRotation.test.ts +70 -0
  180. package/src/utils/apiKeyRotation.ts +24 -0
  181. package/src/utils/applyEdit.test.ts +388 -0
  182. package/src/utils/applyEdit.ts +547 -0
  183. package/src/utils/background-detection.test.ts +61 -0
  184. package/src/utils/background-detection.ts +58 -0
  185. package/src/utils/dotenv.ts +26 -0
  186. package/src/utils/env.ts +90 -0
  187. package/src/utils/error.ts +38 -0
  188. package/src/utils/execFileNoThrow.ts +49 -0
  189. package/src/utils/files.ts +93 -0
  190. package/src/utils/git.ts +1152 -0
  191. package/src/utils/ide.ts +279 -0
  192. package/src/utils/ignore.ts +275 -0
  193. package/src/utils/isLocal.ts +6 -0
  194. package/src/utils/language.ts +33 -0
  195. package/src/utils/list.ts +200 -0
  196. package/src/utils/mergeSystemMessagesMiddleware.ts +32 -0
  197. package/src/utils/messageNormalization.test.ts +401 -0
  198. package/src/utils/messageNormalization.ts +168 -0
  199. package/src/utils/path.ts +98 -0
  200. package/src/utils/prependSystemMessageMiddleware.ts +16 -0
  201. package/src/utils/project.ts +32 -0
  202. package/src/utils/proxy.ts +102 -0
  203. package/src/utils/randomUUID.ts +11 -0
  204. package/src/utils/renderSessionMarkdown.ts +175 -0
  205. package/src/utils/ripgrep.ts +189 -0
  206. package/src/utils/safeFrontMatter.test.ts +118 -0
  207. package/src/utils/safeFrontMatter.ts +68 -0
  208. package/src/utils/safeParseJson.ts +7 -0
  209. package/src/utils/safeStringify.ts +10 -0
  210. package/src/utils/sanitizeAIResponse.test.ts +135 -0
  211. package/src/utils/sanitizeAIResponse.ts +55 -0
  212. package/src/utils/setTerminalTitle.ts +7 -0
  213. package/src/utils/shell-execution.test.ts +237 -0
  214. package/src/utils/shell-execution.ts +279 -0
  215. package/src/utils/string.ts +13 -0
  216. package/src/utils/symbols.ts +18 -0
  217. package/src/utils/system-encoding.test.ts +164 -0
  218. package/src/utils/system-encoding.ts +296 -0
  219. package/src/utils/tokenCounter.test.ts +38 -0
  220. package/src/utils/tokenCounter.ts +19 -0
  221. package/src/utils/username.ts +21 -0
package/src/mcp/mcp.ts ADDED
@@ -0,0 +1,637 @@
1
+ import { experimental_createMCPClient } from '@ai-sdk/mcp';
2
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
+ import createDebug from 'debug';
4
+ import { existsSync, readFileSync } from 'fs';
5
+ import { resolve } from 'pathe';
6
+ import type { ImagePart, TextPart } from '../core/message';
7
+ import type { Tool } from '../tools/tool';
8
+ import { safeStringify } from '../utils/safeStringify';
9
+
10
+ export interface MCPConfig {
11
+ type?: 'stdio' | 'sse' | 'http';
12
+ command?: string;
13
+ args?: string[];
14
+ env?: Record<string, string>;
15
+ url?: string;
16
+ disable?: boolean;
17
+ /**
18
+ * The timeout for tool calls in milliseconds.
19
+ */
20
+ timeout?: number;
21
+ headers?: Record<string, string>;
22
+ }
23
+
24
+ const debug = createDebug('oricore:mcp');
25
+
26
+ type MCPServerStatus =
27
+ | 'pending'
28
+ | 'connecting'
29
+ | 'connected'
30
+ | 'failed'
31
+ | 'disconnected';
32
+
33
+ interface ServerState {
34
+ config: MCPConfig;
35
+ status: MCPServerStatus;
36
+ error?: string;
37
+ tools?: Record<string, any>;
38
+ client?: any; // Store client for cleanup
39
+ retryCount: number;
40
+ isTemporaryError?: boolean;
41
+ }
42
+
43
+ export class MCPManager {
44
+ private servers: Map<string, ServerState> = new Map();
45
+ private configs: Record<string, MCPConfig> = {};
46
+ private isInitialized: boolean = false;
47
+ private initPromise?: Promise<void>;
48
+ private initLock: boolean = false;
49
+
50
+ static create(mcpServers: Record<string, MCPConfig>): MCPManager {
51
+ debug('create MCPManager', mcpServers);
52
+ const manager = new MCPManager();
53
+ manager.configs = mcpServers || {};
54
+
55
+ // Initialize servers state without connecting
56
+ for (const [key, config] of Object.entries(mcpServers || {})) {
57
+ if (config.disable) {
58
+ debug(`Skipping disabled MCP server: ${key}`);
59
+ continue;
60
+ }
61
+ manager.servers.set(key, {
62
+ config,
63
+ status: 'pending',
64
+ retryCount: 0,
65
+ });
66
+ }
67
+
68
+ return manager;
69
+ }
70
+
71
+ async initAsync(): Promise<void> {
72
+ // Return existing promise if initialization is already in progress
73
+ if (this.initPromise) {
74
+ return this.initPromise;
75
+ }
76
+ // Double-check locking pattern for thread safety
77
+ if (this.initLock) {
78
+ // Wait for lock to be released and check if initialization completed
79
+ while (this.initLock) {
80
+ await new Promise((resolve) => setTimeout(resolve, 10));
81
+ }
82
+ if (this.isInitialized) {
83
+ return;
84
+ }
85
+ }
86
+ // Acquire lock
87
+ this.initLock = true;
88
+ try {
89
+ // Check again in case another thread completed initialization
90
+ if (this.isInitialized) {
91
+ return;
92
+ }
93
+ this.initPromise = this._performInit();
94
+ await this.initPromise;
95
+ } finally {
96
+ // Release lock
97
+ this.initLock = false;
98
+ }
99
+ }
100
+
101
+ private async _performInit(): Promise<void> {
102
+ debug('Starting async MCP initialization');
103
+ const connectionPromises: Promise<void>[] = [];
104
+
105
+ for (const [key, config] of Object.entries(this.configs)) {
106
+ if (config.disable) {
107
+ continue;
108
+ }
109
+
110
+ const connectionPromise = this._connectServer(key, config);
111
+ connectionPromises.push(connectionPromise);
112
+ }
113
+
114
+ // Wait for all connections to complete (success or failure)
115
+ await Promise.allSettled(connectionPromises);
116
+ this.isInitialized = true;
117
+ debug('MCP initialization completed');
118
+ }
119
+
120
+ private async _connectServer(key: string, config: MCPConfig): Promise<void> {
121
+ const serverState = this.servers.get(key);
122
+ if (!serverState) return;
123
+
124
+ try {
125
+ debug(`Connecting MCP server: ${key}`);
126
+ serverState.status = 'connecting';
127
+
128
+ // Test connection and fetch tools
129
+ const { client, tools } = await this._testConnectionAndFetchTools(config);
130
+
131
+ serverState.status = 'connected';
132
+ serverState.client = client;
133
+ serverState.tools = tools;
134
+ serverState.error = undefined;
135
+
136
+ debug(
137
+ `MCP server connected successfully: ${key}, tools: ${Object.keys(tools).length}`,
138
+ );
139
+ } catch (error) {
140
+ const errorMessage =
141
+ error instanceof Error ? error.message : String(error);
142
+ debug(`Failed to connect MCP server ${key}: ${errorMessage}`);
143
+
144
+ // Classify error types for better handling
145
+ const isTemporaryError = this._isTemporaryError(error);
146
+
147
+ serverState.status = 'failed';
148
+ serverState.error = errorMessage;
149
+ serverState.retryCount += 1;
150
+ serverState.isTemporaryError = isTemporaryError;
151
+
152
+ // Ensure no client reference is left on failure
153
+ serverState.client = undefined;
154
+ serverState.tools = undefined;
155
+ }
156
+ }
157
+
158
+ async getAllTools(): Promise<Tool[]> {
159
+ const allTools: Tool[] = [];
160
+ const toolNames = new Set<string>();
161
+
162
+ for (const [serverName, serverState] of this.servers.entries()) {
163
+ if (serverState.status !== 'connected' || !serverState.tools) {
164
+ continue;
165
+ }
166
+
167
+ for (const [toolName, toolDef] of Object.entries(serverState.tools)) {
168
+ const fullToolName = `mcp__${serverName}__${toolName}`;
169
+
170
+ if (toolNames.has(fullToolName)) {
171
+ throw new Error(`Duplicate tool name found: ${fullToolName}`);
172
+ }
173
+
174
+ toolNames.add(fullToolName);
175
+ allTools.push(
176
+ this.#convertAiSdkToolToLocal(
177
+ toolName,
178
+ toolDef,
179
+ serverName,
180
+ serverState.config,
181
+ ),
182
+ );
183
+ }
184
+ }
185
+
186
+ return allTools;
187
+ }
188
+
189
+ async getTools(keys: string[]): Promise<Tool[]> {
190
+ const allTools: Tool[] = [];
191
+ const toolNames = new Set<string>();
192
+
193
+ for (const key of keys) {
194
+ const serverState = this.servers.get(key);
195
+ if (
196
+ !serverState ||
197
+ serverState.status !== 'connected' ||
198
+ !serverState.tools
199
+ ) {
200
+ continue;
201
+ }
202
+
203
+ for (const [toolName, toolDef] of Object.entries(serverState.tools)) {
204
+ const fullToolName = `mcp__${key}__${toolName}`;
205
+
206
+ if (toolNames.has(fullToolName)) {
207
+ throw new Error(`Duplicate tool name found: ${fullToolName}`);
208
+ }
209
+
210
+ toolNames.add(fullToolName);
211
+ allTools.push(
212
+ this.#convertAiSdkToolToLocal(
213
+ toolName,
214
+ toolDef,
215
+ key,
216
+ serverState.config,
217
+ ),
218
+ );
219
+ }
220
+ }
221
+
222
+ return allTools;
223
+ }
224
+
225
+ async destroy() {
226
+ // Close all client connections
227
+ const closePromises = Array.from(this.servers.values())
228
+ .filter((state) => state.client)
229
+ .map((state) =>
230
+ state.client.close().catch((err: Error) => {
231
+ debug('Error closing client during destroy:', err);
232
+ }),
233
+ );
234
+
235
+ await Promise.allSettled(closePromises);
236
+ this.servers.clear();
237
+ this.isInitialized = false;
238
+ this.initPromise = undefined;
239
+ }
240
+
241
+ getServerNames(): string[] {
242
+ return Array.from(this.servers.keys());
243
+ }
244
+
245
+ hasServer(name: string): boolean {
246
+ return this.servers.has(name);
247
+ }
248
+
249
+ getServerStatus(name: string): MCPServerStatus | undefined {
250
+ return this.servers.get(name)?.status;
251
+ }
252
+
253
+ getServerError(name: string): string | undefined {
254
+ return this.servers.get(name)?.error;
255
+ }
256
+
257
+ async getAllServerStatus(): Promise<
258
+ Record<
259
+ string,
260
+ { status: MCPServerStatus; error?: string; toolCount: number }
261
+ >
262
+ > {
263
+ await this.initAsync();
264
+
265
+ const result: Record<
266
+ string,
267
+ { status: MCPServerStatus; error?: string; toolCount: number }
268
+ > = {};
269
+ for (const [name, state] of this.servers.entries()) {
270
+ result[name] = {
271
+ status: state.status,
272
+ error: state.error,
273
+ toolCount: state.tools ? Object.keys(state.tools).length : 0,
274
+ };
275
+ }
276
+ return result;
277
+ }
278
+
279
+ isReady(): boolean {
280
+ return this.isInitialized;
281
+ }
282
+
283
+ isLoading(): boolean {
284
+ return !!this.initPromise && !this.isInitialized;
285
+ }
286
+
287
+ async retryConnection(serverName: string): Promise<void> {
288
+ const config = this.configs[serverName];
289
+ if (!config) {
290
+ throw new Error(`Server ${serverName} not found in configuration`);
291
+ }
292
+
293
+ const serverState = this.servers.get(serverName);
294
+ if (!serverState) {
295
+ throw new Error(`Server ${serverName} state not found`);
296
+ }
297
+
298
+ // Log reconnection attempt
299
+ debug(`Attempting to reconnect MCP server: ${serverName}`);
300
+
301
+ // Close existing client if any
302
+ if (serverState.client) {
303
+ try {
304
+ await serverState.client.close();
305
+ } catch (error) {
306
+ debug(`Error closing existing client for ${serverName}:`, error);
307
+ }
308
+ }
309
+
310
+ // Reset state and retry
311
+ serverState.client = undefined;
312
+ serverState.tools = undefined;
313
+ serverState.error = undefined;
314
+ serverState.status = 'connecting';
315
+
316
+ await this._connectServer(serverName, config);
317
+
318
+ // Verify reconnection result
319
+ const newState = this.servers.get(serverName);
320
+ if (newState?.status !== 'connected') {
321
+ throw new Error(newState?.error || 'Reconnection failed');
322
+ }
323
+
324
+ debug(`Successfully reconnected MCP server: ${serverName}`);
325
+ }
326
+
327
+ private async _createClient(config: MCPConfig) {
328
+ if (config.command) {
329
+ // Stdio transport (for local servers only)
330
+ const env = config.env
331
+ ? { ...config.env, PATH: process.env.PATH || '' }
332
+ : undefined;
333
+
334
+ const { Experimental_StdioMCPTransport } = await import(
335
+ '@ai-sdk/mcp/mcp-stdio'
336
+ );
337
+
338
+ return experimental_createMCPClient({
339
+ transport: new Experimental_StdioMCPTransport({
340
+ command: config.command,
341
+ args: config.args,
342
+ stderr: 'ignore',
343
+ env,
344
+ }),
345
+ });
346
+ } else if (config.url) {
347
+ // HTTP or SSE transport
348
+ const transportType = config.type || 'http'; // Default to HTTP
349
+ if (transportType === 'sse') {
350
+ // SSE transport
351
+ return experimental_createMCPClient({
352
+ transport: {
353
+ type: 'sse',
354
+ url: config.url,
355
+ headers: config.headers,
356
+ },
357
+ });
358
+ } else {
359
+ // HTTP transport
360
+ return experimental_createMCPClient({
361
+ transport: new StreamableHTTPClientTransport(new URL(config.url), {
362
+ requestInit: {
363
+ headers: config.headers,
364
+ },
365
+ }),
366
+ });
367
+ }
368
+ } else {
369
+ throw new Error('MCP config must have either command or url configured');
370
+ }
371
+ }
372
+
373
+ private async _testConnectionAndFetchTools(
374
+ config: MCPConfig,
375
+ ): Promise<{ client: any; tools: Record<string, any> }> {
376
+ const client = await this._createClient(config);
377
+ try {
378
+ const tools = await client.tools();
379
+ return { client, tools };
380
+ } catch (error) {
381
+ // Close client on error
382
+ await client.close().catch((err) => {
383
+ debug('Error closing client after connection failure:', err);
384
+ });
385
+ throw error;
386
+ }
387
+ }
388
+
389
+ private _isTemporaryError(error: unknown): boolean {
390
+ if (!(error instanceof Error)) {
391
+ return false;
392
+ }
393
+
394
+ const message = error.message.toLowerCase();
395
+
396
+ // Network-related temporary errors
397
+ const temporaryErrors = [
398
+ 'timeout',
399
+ 'connection refused',
400
+ 'network error',
401
+ 'temporary',
402
+ 'try again',
403
+ 'rate limit',
404
+ 'too many requests',
405
+ 'service unavailable',
406
+ 'socket hang up',
407
+ 'econnreset',
408
+ 'enotfound',
409
+ 'econnrefused',
410
+ 'etimedout',
411
+ ];
412
+
413
+ // Configuration or permanent errors
414
+ const permanentErrors = [
415
+ 'command not found',
416
+ 'no such file',
417
+ 'permission denied',
418
+ 'invalid configuration',
419
+ 'malformed',
420
+ 'syntax error',
421
+ 'authentication failed',
422
+ 'unauthorized',
423
+ ];
424
+
425
+ // Check for permanent errors first (higher priority)
426
+ if (permanentErrors.some((permanent) => message.includes(permanent))) {
427
+ return false;
428
+ }
429
+
430
+ // Check for temporary errors
431
+ if (temporaryErrors.some((temporary) => message.includes(temporary))) {
432
+ return true;
433
+ }
434
+
435
+ // Default to temporary for unknown errors (safer for retries)
436
+ return true;
437
+ }
438
+
439
+ #convertAiSdkToolToLocal(
440
+ toolName: string,
441
+ toolDef: any,
442
+ serverName: string,
443
+ config: MCPConfig,
444
+ ): Tool {
445
+ return {
446
+ name: `mcp__${serverName.replace(/[^a-zA-Z0-9_-]/g, '')}__${toolName}`,
447
+ description: toolDef.description,
448
+ getDescription: ({ params }) => {
449
+ return formatParamsDescription(params as Record<string, any>);
450
+ },
451
+ // Why? Some models do not support null values, so null values need to be removed.
452
+ parameters: removeNullValues(toolDef.inputSchema.jsonSchema),
453
+ execute: async (params) => {
454
+ try {
455
+ // toolDef is already a Tool from AI SDK with an execute method
456
+ const result = await toolDef.execute(params || {});
457
+
458
+ const returnDisplay = `Tool ${toolName} executed successfully${params ? `, parameters: ${JSON.stringify(params)}` : ''}`;
459
+ const llmContent = convertMcpResultToLlmContent(result);
460
+
461
+ return {
462
+ llmContent,
463
+ returnDisplay,
464
+ };
465
+ } catch (error) {
466
+ return {
467
+ isError: true,
468
+ llmContent: error instanceof Error ? error.message : String(error),
469
+ };
470
+ }
471
+ },
472
+ approval: {
473
+ category: 'network',
474
+ },
475
+ };
476
+ }
477
+ }
478
+
479
+ export function parseMcpConfig(
480
+ mcpConfigArgs: string[],
481
+ cwd: string,
482
+ ): Record<string, MCPConfig> {
483
+ const mcpServers: Record<string, MCPConfig> = {};
484
+ for (const configItem of mcpConfigArgs) {
485
+ let configData: unknown;
486
+ try {
487
+ // Try to parse as JSON string first
488
+ configData = JSON.parse(configItem);
489
+ } catch (e) {
490
+ // If JSON parsing fails, treat as file path
491
+ const configPath = resolve(cwd, configItem);
492
+ if (!existsSync(configPath)) {
493
+ throw new Error(`MCP config file not found: ${configPath}`);
494
+ }
495
+ try {
496
+ const fileContent = readFileSync(configPath, 'utf-8');
497
+ configData = JSON.parse(fileContent);
498
+ } catch (error) {
499
+ throw new Error(
500
+ `Failed to parse MCP config file ${configPath}: ${error instanceof Error ? error.message : String(error)}`,
501
+ );
502
+ }
503
+ }
504
+ // Extract mcpServer object from the config data
505
+ if (!configData || typeof configData !== 'object') {
506
+ throw new Error('MCP config must be a valid JSON object');
507
+ }
508
+ const configObj = configData as Record<string, unknown>;
509
+ if (!configObj.mcpServers || typeof configObj.mcpServers !== 'object') {
510
+ throw new Error('MCP config must contain an "mcpServers" object');
511
+ }
512
+ Object.assign(
513
+ mcpServers,
514
+ configObj.mcpServers as Record<string, MCPConfig>,
515
+ );
516
+ }
517
+
518
+ return mcpServers;
519
+ }
520
+
521
+ function removeNullValues(obj: unknown): any {
522
+ if (obj === null || obj === undefined) {
523
+ return undefined;
524
+ }
525
+ if (Array.isArray(obj)) {
526
+ return obj.map(removeNullValues).filter((v) => v !== undefined);
527
+ }
528
+ if (typeof obj === 'object') {
529
+ const result: Record<string, unknown> = {};
530
+ for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
531
+ const cleaned = removeNullValues(v);
532
+ if (cleaned !== undefined) {
533
+ result[k] = cleaned;
534
+ }
535
+ }
536
+ return result;
537
+ }
538
+ return obj;
539
+ }
540
+
541
+ function formatParamsDescription(params: Record<string, any>): string {
542
+ if (!params || typeof params !== 'object') {
543
+ return '';
544
+ }
545
+ const entries = Object.entries(params);
546
+ if (entries.length === 0) {
547
+ return '';
548
+ }
549
+ return entries
550
+ .filter(([key, value]) => value !== null && value !== undefined)
551
+ .map(([key, value]) => {
552
+ return `${key}: ${safeStringify(value)}`;
553
+ })
554
+ .join(', ');
555
+ }
556
+
557
+ export function convertMcpResultToLlmContent(
558
+ result: any,
559
+ ): string | (TextPart | ImagePart)[] {
560
+ // Support mcp spec data types
561
+ // ref: https://modelcontextprotocol.io/specification/2025-06-18/server/tools#data-types
562
+
563
+ // Step 1: Unpack MCP result format
564
+ let actualContent: any;
565
+
566
+ if (result && typeof result === 'object' && !Array.isArray(result)) {
567
+ // why? https://github.com/vercel/ai/blob/main/packages/mcp/src/tool/types.ts#L201
568
+ if ('content' in result && Array.isArray(result.content)) {
569
+ // Format 1: { content: [...], isError?: boolean }
570
+ actualContent = result.content;
571
+ } else if ('toolResult' in result) {
572
+ // Format 2: { toolResult: unknown }
573
+ return safeStringify(result.toolResult);
574
+ } else {
575
+ // Fallback: treat as regular object
576
+ actualContent = result;
577
+ }
578
+ } else {
579
+ actualContent = result;
580
+ }
581
+
582
+ // Step 2: Type detection functions
583
+ const isTextPart = (part: object) => {
584
+ return 'type' in part && part.type === 'text' && 'text' in part;
585
+ };
586
+ const isImagePart = (part: object) => {
587
+ return (
588
+ 'type' in part &&
589
+ part.type === 'image' &&
590
+ 'data' in part &&
591
+ 'mimeType' in part
592
+ );
593
+ };
594
+ const isResourcePart = (part: object) => {
595
+ return 'type' in part && part.type === 'resource' && 'resource' in part;
596
+ };
597
+ const isPart = (part: object) => {
598
+ return isTextPart(part) || isImagePart(part) || isResourcePart(part);
599
+ };
600
+
601
+ // Step 3: Process actualContent based on type
602
+ let llmContent: any = actualContent;
603
+
604
+ if (typeof llmContent === 'object' && !Array.isArray(llmContent)) {
605
+ // Single object: wrap in array if it's a part, otherwise stringify
606
+ if (isPart(llmContent as object)) {
607
+ llmContent = [llmContent];
608
+ } else {
609
+ llmContent = safeStringify(llmContent);
610
+ }
611
+ } else if (Array.isArray(llmContent)) {
612
+ // Array: check if it contains parts
613
+ const hasPart = llmContent.some(
614
+ (item) => typeof item === 'object' && item !== null && isPart(item),
615
+ );
616
+ if (hasPart) {
617
+ // Mixed array: convert non-part elements to text parts
618
+ llmContent = llmContent.map((part) => {
619
+ if (typeof part === 'object' && part !== null && isPart(part)) {
620
+ return part;
621
+ } else {
622
+ return { type: 'text', text: safeStringify(part) };
623
+ }
624
+ });
625
+ } else {
626
+ // Pure data array: stringify
627
+ llmContent = safeStringify(llmContent);
628
+ }
629
+ } else if (typeof llmContent === 'string') {
630
+ // Keep llmContent as string
631
+ } else {
632
+ // Other types: convert to string
633
+ llmContent = String(llmContent);
634
+ }
635
+
636
+ return llmContent;
637
+ }