@minko-ai/minko 0.1.0 → 0.1.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 (189) hide show
  1. package/README.md +577 -2
  2. package/assets/mascot-preview.png +0 -0
  3. package/assets/mascot-preview.txt +46 -0
  4. package/assets/mascot-source.png +0 -0
  5. package/assets/skills/README.md +21 -0
  6. package/assets/skills/brainstorming/LICENSE +21 -0
  7. package/assets/skills/brainstorming/SKILL.md +316 -0
  8. package/assets/skills/buddhist-method/LICENSE +21 -0
  9. package/assets/skills/buddhist-method/SKILL.md +137 -0
  10. package/assets/skills/buddhist-method/references/ariyasacca-debug.md +57 -0
  11. package/assets/skills/buddhist-method/references/extended-principles.md +100 -0
  12. package/assets/skills/code-humanizer/LICENSE +21 -0
  13. package/assets/skills/code-humanizer/SKILL.md +174 -0
  14. package/assets/skills/code-review/LICENSE +21 -0
  15. package/assets/skills/code-review/SKILL.md +232 -0
  16. package/assets/skills/code-review/reference/angular.md +768 -0
  17. package/assets/skills/code-review/reference/architecture-review-guide.md +472 -0
  18. package/assets/skills/code-review/reference/c.md +890 -0
  19. package/assets/skills/code-review/reference/code-quality-universal.md +488 -0
  20. package/assets/skills/code-review/reference/code-review-best-practices.md +136 -0
  21. package/assets/skills/code-review/reference/common-bugs-checklist.md +302 -0
  22. package/assets/skills/code-review/reference/cpp.md +893 -0
  23. package/assets/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
  24. package/assets/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
  25. package/assets/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
  26. package/assets/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
  27. package/assets/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
  28. package/assets/skills/code-review/reference/csharp.md +525 -0
  29. package/assets/skills/code-review/reference/css-less-sass.md +661 -0
  30. package/assets/skills/code-review/reference/dart.md +670 -0
  31. package/assets/skills/code-review/reference/django.md +985 -0
  32. package/assets/skills/code-review/reference/fastapi.md +580 -0
  33. package/assets/skills/code-review/reference/go.md +993 -0
  34. package/assets/skills/code-review/reference/java.md +409 -0
  35. package/assets/skills/code-review/reference/java8.md +586 -0
  36. package/assets/skills/code-review/reference/kotlin.md +1018 -0
  37. package/assets/skills/code-review/reference/nestjs.md +593 -0
  38. package/assets/skills/code-review/reference/performance-review-guide.md +816 -0
  39. package/assets/skills/code-review/reference/php.md +684 -0
  40. package/assets/skills/code-review/reference/python.md +1073 -0
  41. package/assets/skills/code-review/reference/qt.md +757 -0
  42. package/assets/skills/code-review/reference/react.md +871 -0
  43. package/assets/skills/code-review/reference/ruby.md +964 -0
  44. package/assets/skills/code-review/reference/rust.md +846 -0
  45. package/assets/skills/code-review/reference/security-review-guide.md +494 -0
  46. package/assets/skills/code-review/reference/svelte.md +1064 -0
  47. package/assets/skills/code-review/reference/swift.md +936 -0
  48. package/assets/skills/code-review/reference/typescript.md +1016 -0
  49. package/assets/skills/code-review/reference/vue.md +924 -0
  50. package/assets/skills/code-review/reference/zig.md +440 -0
  51. package/assets/skills/cyclomatic-complexity/LICENSE +201 -0
  52. package/assets/skills/cyclomatic-complexity/SKILL.md +69 -0
  53. package/assets/skills/five-whys/LICENSE +21 -0
  54. package/assets/skills/five-whys/SKILL.md +247 -0
  55. package/assets/skills/five-whys/examples/performance-regression.md +161 -0
  56. package/assets/skills/five-whys/examples/production-incident.md +134 -0
  57. package/assets/skills/five-whys/references/software-patterns.md +284 -0
  58. package/assets/skills/five-whys/references/toyota-origins.md +257 -0
  59. package/assets/skills/systematic-debugging/LICENSE +21 -0
  60. package/assets/skills/systematic-debugging/SKILL.md +283 -0
  61. package/assets/skills/test-driven-development/LICENSE +21 -0
  62. package/assets/skills/test-driven-development/SKILL.md +330 -0
  63. package/assets/skills/verification-before-completion/LICENSE +21 -0
  64. package/assets/skills/verification-before-completion/SKILL.md +120 -0
  65. package/assets/skills/vibe-sec/LICENSE +201 -0
  66. package/assets/skills/vibe-sec/SKILL.md +758 -0
  67. package/assets/skills/writing-plans/LICENSE +21 -0
  68. package/assets/skills/writing-plans/SKILL.md +204 -0
  69. package/bin/minko.js +2 -2
  70. package/dist/agent/approvalTokens.js +224 -0
  71. package/dist/agent/branchLock.js +315 -0
  72. package/dist/agent/compact.js +182 -0
  73. package/dist/agent/context.js +183 -0
  74. package/dist/agent/cron.js +326 -0
  75. package/dist/agent/hooks.js +211 -0
  76. package/dist/agent/lanes.js +511 -0
  77. package/dist/agent/loop.js +337 -0
  78. package/dist/agent/memory.js +159 -0
  79. package/dist/agent/permissions.js +71 -0
  80. package/dist/agent/plan.js +118 -0
  81. package/dist/agent/prompt.js +120 -0
  82. package/dist/agent/subagent.js +67 -0
  83. package/dist/agent/tasks.js +449 -0
  84. package/dist/agent/transcript.js +62 -0
  85. package/dist/agent/workerStore.js +172 -0
  86. package/dist/agent/workerTypes.js +1 -0
  87. package/dist/agent/workers.js +503 -0
  88. package/dist/cli/app.js +261 -0
  89. package/dist/cli/argv.js +206 -0
  90. package/dist/cli/commandsCommand.js +12 -0
  91. package/dist/cli/configCommand.js +170 -0
  92. package/dist/cli/doctorCommand.js +112 -0
  93. package/dist/cli/exportCommand.js +85 -0
  94. package/dist/cli/forkCommand.js +24 -0
  95. package/dist/cli/linkCommand.js +272 -0
  96. package/dist/cli/loginCommand.js +69 -0
  97. package/dist/cli/mcpLoginCommand.js +68 -0
  98. package/dist/cli/pluginCommand.js +113 -0
  99. package/dist/cli/powerCommand.js +174 -0
  100. package/dist/cli/promptFooter.js +74 -0
  101. package/dist/cli/relayCommand.js +38 -0
  102. package/dist/cli/sandboxCommand.js +31 -0
  103. package/dist/cli/serveCommand.js +104 -0
  104. package/dist/cli/statusCommand.js +120 -0
  105. package/dist/cli/tui.js +1104 -0
  106. package/dist/commands/registry.js +99 -0
  107. package/dist/config/paths.js +81 -0
  108. package/dist/config/store.js +605 -0
  109. package/dist/index.js +199 -0
  110. package/dist/link/protocol.js +159 -0
  111. package/dist/link/relay.js +184 -0
  112. package/dist/link/server.js +566 -0
  113. package/dist/link/tunnel.js +90 -0
  114. package/dist/lsp/client.js +340 -0
  115. package/dist/lsp/tool.js +177 -0
  116. package/dist/mcp/client.js +469 -0
  117. package/dist/mcp/httpClient.js +191 -0
  118. package/dist/mcp/server.js +206 -0
  119. package/dist/mcp/tools.js +173 -0
  120. package/dist/oauth/device.js +111 -0
  121. package/dist/oauth/store.js +56 -0
  122. package/dist/plugins/loader.js +291 -0
  123. package/dist/plugins/market.js +98 -0
  124. package/dist/providers/anthropic.js +279 -0
  125. package/dist/providers/factory.js +109 -0
  126. package/dist/providers/openai.js +182 -0
  127. package/dist/providers/retry.js +95 -0
  128. package/dist/providers/sse.js +67 -0
  129. package/dist/providers/types.js +10 -0
  130. package/dist/rag/embeddings.js +171 -0
  131. package/dist/rag/index.js +373 -0
  132. package/dist/rag/server.js +167 -0
  133. package/dist/rag/tool.js +148 -0
  134. package/dist/sandbox/index.js +55 -0
  135. package/dist/sdk/server.js +150 -0
  136. package/dist/sdk/session.js +104 -0
  137. package/dist/session/liveness.js +148 -0
  138. package/dist/session/recorder.js +49 -0
  139. package/dist/session/store.js +171 -0
  140. package/dist/skills/index.js +428 -0
  141. package/dist/theme.js +30 -0
  142. package/dist/tools/fs.js +195 -0
  143. package/dist/tools/notebook.js +109 -0
  144. package/dist/tools/registry.js +32 -0
  145. package/dist/tools/search.js +130 -0
  146. package/dist/tools/shell.js +200 -0
  147. package/dist/tools/structured.js +83 -0
  148. package/dist/tools/types.js +1 -0
  149. package/dist/ui/ansi.js +135 -0
  150. package/dist/ui/banner.js +103 -0
  151. package/dist/ui/input.js +49 -0
  152. package/dist/ui/inputModel.js +113 -0
  153. package/dist/ui/keyboard.js +142 -0
  154. package/dist/ui/mascot.js +34 -0
  155. package/dist/ui/mascotData.js +96 -0
  156. package/dist/ui/md.js +183 -0
  157. package/dist/ui/screen.js +49 -0
  158. package/dist/ui/spinner.js +52 -0
  159. package/dist/ui/transcript.js +287 -0
  160. package/dist/util/clipboard.js +53 -0
  161. package/dist/util/errors.js +50 -0
  162. package/dist/util/version.js +12 -0
  163. package/dist/web/fetch.js +168 -0
  164. package/dist/web/search.js +97 -0
  165. package/package.json +45 -36
  166. package/src/agent/loop.ts +0 -159
  167. package/src/agent/prompt.ts +0 -45
  168. package/src/cli/app.ts +0 -396
  169. package/src/cli/argv.ts +0 -139
  170. package/src/cli/configCommand.ts +0 -175
  171. package/src/config/paths.ts +0 -20
  172. package/src/config/store.ts +0 -149
  173. package/src/index.ts +0 -120
  174. package/src/providers/anthropic.ts +0 -215
  175. package/src/providers/openai.ts +0 -158
  176. package/src/providers/sse.ts +0 -57
  177. package/src/providers/types.ts +0 -59
  178. package/src/session/store.ts +0 -125
  179. package/src/theme.ts +0 -41
  180. package/src/tools/fs.ts +0 -143
  181. package/src/tools/registry.ts +0 -19
  182. package/src/tools/shell.ts +0 -120
  183. package/src/tools/types.ts +0 -26
  184. package/src/ui/ansi.ts +0 -110
  185. package/src/ui/banner.ts +0 -39
  186. package/src/ui/input.ts +0 -54
  187. package/src/ui/md.ts +0 -160
  188. package/src/ui/spinner.ts +0 -58
  189. package/src/util/errors.ts +0 -48
@@ -0,0 +1,469 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { readVersion } from "../util/version.js";
3
+ const INIT_TIMEOUT_MS = 15_000;
4
+ const CALL_TIMEOUT_MS = 120_000;
5
+ /** Minimal MCP client over stdio, using newline-delimited JSON-RPC 2.0. */
6
+ export class McpStdioClient {
7
+ name;
8
+ config;
9
+ child = null;
10
+ buffer = '';
11
+ nextId = 0;
12
+ pending = new Map();
13
+ toolDefs = [];
14
+ resourceDefs = [];
15
+ promptDefs = [];
16
+ templateDefs = [];
17
+ stderr = '';
18
+ closed = false;
19
+ constructor(name, config) {
20
+ this.name = name;
21
+ this.config = config;
22
+ }
23
+ tools() {
24
+ return this.toolDefs.slice();
25
+ }
26
+ resources() {
27
+ return this.resourceDefs.slice();
28
+ }
29
+ async listResources() {
30
+ try {
31
+ const listed = await this.request('resources/list', {}, CALL_TIMEOUT_MS);
32
+ this.resourceDefs = parseResourceList(listed);
33
+ }
34
+ catch {
35
+ // Servers without resource support keep the cached/empty list.
36
+ }
37
+ return this.resourceDefs.slice();
38
+ }
39
+ async readResource(uri) {
40
+ const result = await this.request('resources/read', { uri }, CALL_TIMEOUT_MS);
41
+ return renderResourceContents(result);
42
+ }
43
+ prompts() {
44
+ return this.promptDefs.slice();
45
+ }
46
+ async listPrompts() {
47
+ try {
48
+ const listed = await this.request('prompts/list', {}, CALL_TIMEOUT_MS);
49
+ this.promptDefs = parsePromptList(listed);
50
+ }
51
+ catch {
52
+ // Prompts are optional.
53
+ }
54
+ return this.promptDefs.slice();
55
+ }
56
+ async getPrompt(name, args) {
57
+ const result = await this.request('prompts/get', { name, arguments: args }, CALL_TIMEOUT_MS);
58
+ return renderPrompt(result);
59
+ }
60
+ resourceTemplates() {
61
+ return this.templateDefs.slice();
62
+ }
63
+ async listResourceTemplates() {
64
+ try {
65
+ const listed = await this.request('resources/templates/list', {}, CALL_TIMEOUT_MS);
66
+ this.templateDefs = parseResourceTemplateList(listed);
67
+ }
68
+ catch {
69
+ // Templates are optional.
70
+ }
71
+ return this.templateDefs.slice();
72
+ }
73
+ async start() {
74
+ const command = this.config.command;
75
+ if (!command) {
76
+ throw new Error('MCP stdio server 缺少 command');
77
+ }
78
+ const launch = chooseSpawn(command);
79
+ const child = spawn(launch.command, this.config.args ?? [], {
80
+ cwd: this.config.cwd,
81
+ env: { ...process.env, ...(this.config.env ?? {}) },
82
+ stdio: ['pipe', 'pipe', 'pipe'],
83
+ windowsHide: true,
84
+ shell: launch.shell,
85
+ });
86
+ this.child = child;
87
+ child.stdout.setEncoding('utf8');
88
+ child.stderr.setEncoding('utf8');
89
+ child.stdout.on('data', (chunk) => this.onData(chunk));
90
+ child.stdin.on('error', () => {
91
+ // A server may exit while a request is still being written.
92
+ });
93
+ child.stderr.on('data', (chunk) => {
94
+ this.stderr = (this.stderr + chunk).slice(-4_000);
95
+ });
96
+ child.on('error', (error) => {
97
+ this.failAll('MCP 进程启动失败:' + error.message);
98
+ });
99
+ child.on('exit', (code) => {
100
+ if (!this.closed) {
101
+ const detail = this.stderr.trim();
102
+ this.failAll('MCP 进程退出(code ' + String(code) + ')' + (detail ? ':' + detail : ''));
103
+ }
104
+ });
105
+ try {
106
+ await this.request('initialize', {
107
+ protocolVersion: '2024-11-05',
108
+ capabilities: {},
109
+ clientInfo: { name: 'minko', version: readVersion() },
110
+ }, INIT_TIMEOUT_MS);
111
+ this.notify('notifications/initialized', {});
112
+ const listed = await this.request('tools/list', {}, INIT_TIMEOUT_MS);
113
+ this.toolDefs = parseToolList(listed);
114
+ try {
115
+ const resources = await this.request('resources/list', {}, INIT_TIMEOUT_MS);
116
+ this.resourceDefs = parseResourceList(resources);
117
+ }
118
+ catch {
119
+ // Resource support is optional.
120
+ }
121
+ try {
122
+ const prompts = await this.request('prompts/list', {}, INIT_TIMEOUT_MS);
123
+ this.promptDefs = parsePromptList(prompts);
124
+ }
125
+ catch {
126
+ // Prompt support is optional.
127
+ }
128
+ try {
129
+ const templates = await this.request('resources/templates/list', {}, INIT_TIMEOUT_MS);
130
+ this.templateDefs = parseResourceTemplateList(templates);
131
+ }
132
+ catch {
133
+ // Resource templates are optional.
134
+ }
135
+ }
136
+ catch (error) {
137
+ await this.close();
138
+ throw error;
139
+ }
140
+ }
141
+ async callTool(name, args) {
142
+ const result = await this.request('tools/call', {
143
+ name,
144
+ arguments: args,
145
+ }, CALL_TIMEOUT_MS);
146
+ const text = renderContent(result.content);
147
+ if (result.isError === true) {
148
+ throw new Error(text.length > 0 ? text : 'MCP 工具返回错误');
149
+ }
150
+ return text;
151
+ }
152
+ async close() {
153
+ this.closed = true;
154
+ this.failAll('MCP 连接已关闭');
155
+ const child = this.child;
156
+ this.child = null;
157
+ if (!child || child.exitCode !== null) {
158
+ return;
159
+ }
160
+ await new Promise((resolve) => {
161
+ const timer = setTimeout(() => {
162
+ try {
163
+ child.kill('SIGKILL');
164
+ }
165
+ catch { /* already gone */ }
166
+ resolve();
167
+ }, 1_000);
168
+ child.once('exit', () => {
169
+ clearTimeout(timer);
170
+ resolve();
171
+ });
172
+ try {
173
+ child.kill();
174
+ }
175
+ catch {
176
+ clearTimeout(timer);
177
+ resolve();
178
+ }
179
+ });
180
+ }
181
+ request(method, params, timeoutMs) {
182
+ if (!this.child || this.closed) {
183
+ return Promise.reject(new Error('MCP 未连接:' + this.name));
184
+ }
185
+ const id = this.nextId + 1;
186
+ this.nextId = id;
187
+ return new Promise((resolve, reject) => {
188
+ const timer = setTimeout(() => {
189
+ this.pending.delete(id);
190
+ reject(new Error('MCP 请求超时:' + method));
191
+ }, timeoutMs);
192
+ this.pending.set(id, { resolve, reject, timer });
193
+ this.write({ jsonrpc: '2.0', id, method, params });
194
+ });
195
+ }
196
+ notify(method, params) {
197
+ this.write({ jsonrpc: '2.0', method, params });
198
+ }
199
+ write(message) {
200
+ const child = this.child;
201
+ if (!child || this.closed) {
202
+ return;
203
+ }
204
+ child.stdin.write(JSON.stringify(message) + '\n');
205
+ }
206
+ onData(chunk) {
207
+ this.buffer += chunk;
208
+ for (;;) {
209
+ const index = this.buffer.indexOf('\n');
210
+ if (index < 0) {
211
+ break;
212
+ }
213
+ const line = this.buffer.slice(0, index).trim();
214
+ this.buffer = this.buffer.slice(index + 1);
215
+ if (line.length > 0) {
216
+ this.onMessage(line);
217
+ }
218
+ }
219
+ }
220
+ onMessage(line) {
221
+ let message;
222
+ try {
223
+ message = JSON.parse(line);
224
+ }
225
+ catch {
226
+ return;
227
+ }
228
+ if (typeof message.id === 'number' && this.pending.has(message.id)) {
229
+ const entry = this.pending.get(message.id);
230
+ if (!entry) {
231
+ return;
232
+ }
233
+ this.pending.delete(message.id);
234
+ clearTimeout(entry.timer);
235
+ if (message.error) {
236
+ entry.reject(new Error('MCP 错误:' + message.error.message));
237
+ }
238
+ else {
239
+ entry.resolve(message.result);
240
+ }
241
+ return;
242
+ }
243
+ // A server-to-client request must get a response or it may hang.
244
+ if (message.method !== undefined && message.id !== undefined) {
245
+ this.write({
246
+ jsonrpc: '2.0',
247
+ id: message.id,
248
+ error: { code: -32601, message: 'method not found' },
249
+ });
250
+ }
251
+ }
252
+ failAll(message) {
253
+ for (const [id, entry] of this.pending) {
254
+ clearTimeout(entry.timer);
255
+ entry.reject(new Error(message));
256
+ this.pending.delete(id);
257
+ }
258
+ }
259
+ }
260
+ export function parseResourceList(value) {
261
+ if (value === null || typeof value !== 'object') {
262
+ return [];
263
+ }
264
+ const resources = value.resources;
265
+ if (!Array.isArray(resources)) {
266
+ return [];
267
+ }
268
+ const out = [];
269
+ for (const raw of resources) {
270
+ if (raw === null || typeof raw !== 'object') {
271
+ continue;
272
+ }
273
+ const record = raw;
274
+ if (typeof record.uri !== 'string' || record.uri.length === 0) {
275
+ continue;
276
+ }
277
+ out.push({
278
+ uri: record.uri,
279
+ name: typeof record.name === 'string' ? record.name : undefined,
280
+ description: typeof record.description === 'string' ? record.description : undefined,
281
+ mimeType: typeof record.mimeType === 'string' ? record.mimeType : undefined,
282
+ });
283
+ }
284
+ return out;
285
+ }
286
+ export function parsePromptList(value) {
287
+ if (value === null || typeof value !== 'object') {
288
+ return [];
289
+ }
290
+ const prompts = value.prompts;
291
+ if (!Array.isArray(prompts)) {
292
+ return [];
293
+ }
294
+ const out = [];
295
+ for (const raw of prompts) {
296
+ if (raw === null || typeof raw !== 'object') {
297
+ continue;
298
+ }
299
+ const record = raw;
300
+ if (typeof record.name !== 'string' || record.name.length === 0) {
301
+ continue;
302
+ }
303
+ const args = [];
304
+ if (Array.isArray(record.arguments)) {
305
+ for (const rawArg of record.arguments) {
306
+ if (rawArg === null || typeof rawArg !== 'object') {
307
+ continue;
308
+ }
309
+ const arg = rawArg;
310
+ if (typeof arg.name !== 'string' || arg.name.length === 0) {
311
+ continue;
312
+ }
313
+ args.push({
314
+ name: arg.name,
315
+ description: typeof arg.description === 'string' ? arg.description : undefined,
316
+ required: arg.required === true,
317
+ });
318
+ }
319
+ }
320
+ out.push({
321
+ name: record.name,
322
+ description: typeof record.description === 'string' ? record.description : undefined,
323
+ ...(args.length > 0 ? { arguments: args } : {}),
324
+ });
325
+ }
326
+ return out;
327
+ }
328
+ export function renderPrompt(value) {
329
+ if (value === null || typeof value !== 'object') {
330
+ return '';
331
+ }
332
+ const record = value;
333
+ const parts = [];
334
+ if (typeof record.description === 'string' && record.description.length > 0) {
335
+ parts.push(record.description);
336
+ }
337
+ const messages = record.messages;
338
+ if (Array.isArray(messages)) {
339
+ for (const raw of messages) {
340
+ if (raw === null || typeof raw !== 'object') {
341
+ continue;
342
+ }
343
+ const message = raw;
344
+ const role = typeof message.role === 'string' ? message.role : 'user';
345
+ const content = message.content;
346
+ let text = '';
347
+ if (content !== null && typeof content === 'object') {
348
+ const block = content;
349
+ text = typeof block.text === 'string' ? block.text : JSON.stringify(block);
350
+ }
351
+ else if (typeof content === 'string') {
352
+ text = content;
353
+ }
354
+ parts.push('[' + role + '] ' + text);
355
+ }
356
+ }
357
+ return parts.join('\n\n');
358
+ }
359
+ export function parseResourceTemplateList(value) {
360
+ if (value === null || typeof value !== 'object') {
361
+ return [];
362
+ }
363
+ const templates = value.resourceTemplates;
364
+ if (!Array.isArray(templates)) {
365
+ return [];
366
+ }
367
+ const out = [];
368
+ for (const raw of templates) {
369
+ if (raw === null || typeof raw !== 'object') {
370
+ continue;
371
+ }
372
+ const record = raw;
373
+ if (typeof record.uriTemplate !== 'string' || record.uriTemplate.length === 0) {
374
+ continue;
375
+ }
376
+ out.push({
377
+ uriTemplate: record.uriTemplate,
378
+ name: typeof record.name === 'string' ? record.name : undefined,
379
+ description: typeof record.description === 'string' ? record.description : undefined,
380
+ mimeType: typeof record.mimeType === 'string' ? record.mimeType : undefined,
381
+ });
382
+ }
383
+ return out;
384
+ }
385
+ export function renderResourceContents(value) {
386
+ if (value === null || typeof value !== 'object') {
387
+ return '';
388
+ }
389
+ const contents = value.contents;
390
+ if (!Array.isArray(contents)) {
391
+ return '';
392
+ }
393
+ const parts = [];
394
+ for (const raw of contents) {
395
+ if (raw === null || typeof raw !== 'object') {
396
+ continue;
397
+ }
398
+ const record = raw;
399
+ const uri = typeof record.uri === 'string' ? record.uri : '';
400
+ const mime = typeof record.mimeType === 'string' ? record.mimeType : '';
401
+ if (typeof record.text === 'string') {
402
+ parts.push('URI: ' + uri + (mime ? '\nMIME: ' + mime : '') + '\n' + record.text);
403
+ }
404
+ else if (typeof record.blob === 'string') {
405
+ parts.push('URI: ' + uri + (mime ? '\nMIME: ' + mime : '')
406
+ + '\n(二进制内容,base64 长度 ' + record.blob.length + ')');
407
+ }
408
+ }
409
+ return parts.join('\n\n');
410
+ }
411
+ export function parseToolList(value) {
412
+ if (value === null || typeof value !== 'object') {
413
+ return [];
414
+ }
415
+ const tools = value.tools;
416
+ if (!Array.isArray(tools)) {
417
+ return [];
418
+ }
419
+ const out = [];
420
+ for (const raw of tools) {
421
+ if (raw === null || typeof raw !== 'object') {
422
+ continue;
423
+ }
424
+ const record = raw;
425
+ if (typeof record.name !== 'string' || record.name.length === 0) {
426
+ continue;
427
+ }
428
+ out.push({
429
+ name: record.name,
430
+ description: typeof record.description === 'string' ? record.description : undefined,
431
+ inputSchema: record.inputSchema,
432
+ });
433
+ }
434
+ return out;
435
+ }
436
+ export function renderContent(content) {
437
+ if (!Array.isArray(content)) {
438
+ return '';
439
+ }
440
+ const parts = [];
441
+ for (const block of content) {
442
+ if (block !== null && typeof block === 'object') {
443
+ const record = block;
444
+ if (record.type === 'text' && typeof record.text === 'string') {
445
+ parts.push(record.text);
446
+ }
447
+ else {
448
+ parts.push(JSON.stringify(record));
449
+ }
450
+ }
451
+ else if (typeof block === 'string') {
452
+ parts.push(block);
453
+ }
454
+ }
455
+ return parts.join('\n');
456
+ }
457
+ function chooseSpawn(command) {
458
+ if (process.platform !== 'win32') {
459
+ return { command, shell: false };
460
+ }
461
+ // `.cmd` shims and bare PATH commands (npx, uvx) need cmd.exe on Windows.
462
+ if (/\.(cmd|bat)$/i.test(command)) {
463
+ return { command, shell: true };
464
+ }
465
+ if (!command.includes('/') && !command.includes('\\')) {
466
+ return { command, shell: true };
467
+ }
468
+ return { command, shell: false };
469
+ }
@@ -0,0 +1,191 @@
1
+ import { HttpError } from "../util/errors.js";
2
+ import { readVersion } from "../util/version.js";
3
+ import { sseEvents } from "../providers/sse.js";
4
+ import { parsePromptList, parseResourceList, parseResourceTemplateList, parseToolList, renderContent, renderPrompt, renderResourceContents, } from "./client.js";
5
+ const REQUEST_TIMEOUT_MS = 30_000;
6
+ /** Minimal MCP Streamable HTTP client with optional Bearer auth. */
7
+ export class McpHttpClient {
8
+ name;
9
+ config;
10
+ url;
11
+ token;
12
+ nextId = 0;
13
+ sessionId = null;
14
+ toolDefs = [];
15
+ resourceDefs = [];
16
+ promptDefs = [];
17
+ templateDefs = [];
18
+ closed = false;
19
+ constructor(name, config) {
20
+ this.name = name;
21
+ this.config = config;
22
+ this.url = config.url ?? '';
23
+ this.token = config.token ?? '';
24
+ }
25
+ tools() {
26
+ return this.toolDefs.slice();
27
+ }
28
+ resources() {
29
+ return this.resourceDefs.slice();
30
+ }
31
+ async start() {
32
+ if (!this.url) {
33
+ throw new Error('MCP HTTP server 缺少 url');
34
+ }
35
+ await this.request('initialize', {
36
+ protocolVersion: '2024-11-05',
37
+ capabilities: {},
38
+ clientInfo: { name: 'minko', version: readVersion() },
39
+ });
40
+ await this.notify('notifications/initialized', {});
41
+ const listed = await this.request('tools/list', {});
42
+ this.toolDefs = parseToolList(listed);
43
+ try {
44
+ const resources = await this.request('resources/list', {});
45
+ this.resourceDefs = parseResourceList(resources);
46
+ }
47
+ catch {
48
+ // Resource support is optional.
49
+ }
50
+ try {
51
+ const prompts = await this.request('prompts/list', {});
52
+ this.promptDefs = parsePromptList(prompts);
53
+ }
54
+ catch {
55
+ // Prompt support is optional.
56
+ }
57
+ try {
58
+ const templates = await this.request('resources/templates/list', {});
59
+ this.templateDefs = parseResourceTemplateList(templates);
60
+ }
61
+ catch {
62
+ // Resource templates are optional.
63
+ }
64
+ }
65
+ async callTool(name, args) {
66
+ const result = await this.request('tools/call', { name, arguments: args });
67
+ const text = renderContent(result.content);
68
+ if (result.isError === true) {
69
+ throw new Error(text.length > 0 ? text : 'MCP 工具返回错误');
70
+ }
71
+ return text;
72
+ }
73
+ async listResources() {
74
+ try {
75
+ const listed = await this.request('resources/list', {});
76
+ this.resourceDefs = parseResourceList(listed);
77
+ }
78
+ catch {
79
+ // Keep cached/empty.
80
+ }
81
+ return this.resourceDefs.slice();
82
+ }
83
+ async readResource(uri) {
84
+ const result = await this.request('resources/read', { uri });
85
+ return renderResourceContents(result);
86
+ }
87
+ prompts() {
88
+ return this.promptDefs.slice();
89
+ }
90
+ async listPrompts() {
91
+ try {
92
+ const listed = await this.request('prompts/list', {});
93
+ this.promptDefs = parsePromptList(listed);
94
+ }
95
+ catch {
96
+ // Optional.
97
+ }
98
+ return this.promptDefs.slice();
99
+ }
100
+ async getPrompt(name, args) {
101
+ const result = await this.request('prompts/get', { name, arguments: args });
102
+ return renderPrompt(result);
103
+ }
104
+ resourceTemplates() {
105
+ return this.templateDefs.slice();
106
+ }
107
+ async listResourceTemplates() {
108
+ try {
109
+ const listed = await this.request('resources/templates/list', {});
110
+ this.templateDefs = parseResourceTemplateList(listed);
111
+ }
112
+ catch {
113
+ // Optional.
114
+ }
115
+ return this.templateDefs.slice();
116
+ }
117
+ async close() {
118
+ this.closed = true;
119
+ }
120
+ async request(method, params) {
121
+ if (this.closed) {
122
+ throw new Error('MCP 连接已关闭');
123
+ }
124
+ const id = this.nextId + 1;
125
+ this.nextId = id;
126
+ const response = await this.post({ jsonrpc: '2.0', id, method, params });
127
+ if (!response) {
128
+ throw new Error('MCP HTTP 返回为空');
129
+ }
130
+ if (response.error) {
131
+ throw new Error('MCP 错误:' + response.error.message);
132
+ }
133
+ return response.result;
134
+ }
135
+ async notify(method, params) {
136
+ await this.post({ jsonrpc: '2.0', method, params });
137
+ }
138
+ async post(message) {
139
+ const headers = {
140
+ 'content-type': 'application/json',
141
+ accept: 'application/json, text/event-stream',
142
+ };
143
+ if (this.token.length > 0) {
144
+ headers.authorization = 'Bearer ' + this.token;
145
+ }
146
+ if (this.sessionId) {
147
+ headers['mcp-session-id'] = this.sessionId;
148
+ }
149
+ const response = await fetch(this.url, {
150
+ method: 'POST',
151
+ headers,
152
+ body: JSON.stringify(message),
153
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
154
+ });
155
+ const session = response.headers.get('mcp-session-id');
156
+ if (session) {
157
+ this.sessionId = session;
158
+ }
159
+ if (!response.ok) {
160
+ throw new HttpError(response.status, await response.text());
161
+ }
162
+ if (message.id === undefined || message.id === null) {
163
+ await response.body?.cancel().catch(() => { });
164
+ return null;
165
+ }
166
+ const contentType = response.headers.get('content-type') ?? '';
167
+ if (contentType.includes('text/event-stream')) {
168
+ if (!response.body) {
169
+ throw new Error('MCP HTTP 没有返回可读的流');
170
+ }
171
+ for await (const event of sseEvents(response.body)) {
172
+ let payload;
173
+ try {
174
+ payload = JSON.parse(event.data);
175
+ }
176
+ catch {
177
+ continue;
178
+ }
179
+ if (payload.id === message.id) {
180
+ return payload;
181
+ }
182
+ }
183
+ throw new Error('MCP HTTP 流没有返回匹配的响应');
184
+ }
185
+ const payload = await response.json().catch(() => null);
186
+ if (!payload) {
187
+ throw new Error('MCP HTTP 响应不是合法 JSON');
188
+ }
189
+ return payload;
190
+ }
191
+ }