cliskill 1.0.3 → 1.0.5

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/README.md CHANGED
@@ -24,6 +24,7 @@
24
24
  - [Использование](#-использование)
25
25
  - [Конфигурация](#-конфигурация)
26
26
  - [Инструменты](#-инструменты)
27
+ - [MCP серверы](#-mcp-серверы)
27
28
  - [Агентская система](#-агентская-система)
28
29
  - [Архитектура](#-архитектура)
29
30
  - [API провайдеры](#-api-провайдеры)
@@ -345,6 +346,71 @@ cliskill включает 15 встроенных инструментов дл
345
346
 
346
347
  ---
347
348
 
349
+ ## 🔌 MCP серверы
350
+
351
+ **MCP (Model Context Protocol)** — открытый протокол для подключения внешних инструментов к AI-ассистентам. cliskill автоматически запускает MCP-серверы при старте и регистрирует их инструменты как доступные для модели.
352
+
353
+ ### Как это работает
354
+
355
+ 1. cliskill читает `mcpServers` из конфига
356
+ 2. Запускает каждый сервер как дочерний процесс (stdio transport)
357
+ 3. Получает список инструментов через `tools/list`
358
+ 4. Регистрирует их в ToolRegistry с префиксом `mcp_{serverName}_`
359
+ 5. Модель может вызывать MCP-инструменты наравне со встроенными
360
+
361
+ ### Настройка
362
+
363
+ Добавьте массив `mcpServers` в конфиг (`~/.cliskill/config.json` или `.cliskillrc.json`):
364
+
365
+ ```json
366
+ {
367
+ "mcpServers": [
368
+ {
369
+ "name": "filesystem",
370
+ "command": "npx",
371
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"],
372
+ "transport": "stdio"
373
+ },
374
+ {
375
+ "name": "github",
376
+ "command": "npx",
377
+ "args": ["-y", "@modelcontextprotocol/server-github"],
378
+ "env": {
379
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_your_token"
380
+ },
381
+ "transport": "stdio"
382
+ }
383
+ ]
384
+ }
385
+ ```
386
+
387
+ ### Поля конфигурации MCP-сервера
388
+
389
+ | Поле | Тип | Обязательное | Описание |
390
+ |------|-----|:---:|----------|
391
+ | `name` | string | ✅ | Уникальное имя сервера |
392
+ | `command` | string | ✅ | Команда запуска процесса |
393
+ | `args` | string[] | — | Аргументы команды (по умолчанию `[]`) |
394
+ | `env` | object | — | Переменные окружения для процесса |
395
+ | `transport` | string | — | Транспорт: `"stdio"` (по умолчанию) |
396
+
397
+ ### Популярные MCP-серверы
398
+
399
+ | Сервер | Пакет | Назначение |
400
+ |--------|-------|------------|
401
+ | Filesystem | `@modelcontextprotocol/server-filesystem` | Доступ к файловой системе |
402
+ | GitHub | `@modelcontextprotocol/server-github` | Работа с GitHub API |
403
+ | PostgreSQL | `@modelcontextprotocol/server-postgres` | Запросы к PostgreSQL |
404
+ | Brave Search | `@modelcontextprotocol/server-brave-search` | Веб-поиск через Brave |
405
+ | Memory | `@modelcontextprotocol/server-memory` | Память между сессиями |
406
+ | Puppeteer | `@modelcontextprotocol/server-puppeteer` | Управление браузером |
407
+
408
+ ### Безопасность
409
+
410
+ - MCP-инструменты наследуют `riskLevel = 'safe'` — требуют разрешения в режиме `ask`
411
+ - Серверы запускаются с переменными окружения из конфига (не из системы)
412
+ - При ошибке подключения сервер пропускается, остальные продолжают работу
413
+
348
414
  ## 🤖 Агентская система
349
415
 
350
416
  ### Auto Mode
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "../chunk-PBLJ6557.js";
4
+ } from "../chunk-ETGUAK34.js";
5
5
  import "../chunk-AJENHWD3.js";
6
6
  export {
7
7
  runCli
@@ -90,6 +90,18 @@ var modelsSchema = z.object({
90
90
  fallbackModel: z.string().default("glm-5.1"),
91
91
  maxContextTokens: z.number().default(5e5)
92
92
  });
93
+ var mcpServerSchema = z.object({
94
+ /** Unique name for this MCP server */
95
+ name: z.string().min(1),
96
+ /** Command to start the MCP server process */
97
+ command: z.string().min(1),
98
+ /** Arguments passed to the command */
99
+ args: z.array(z.string()).default([]),
100
+ /** Environment variables for the server process */
101
+ env: z.record(z.string()).optional(),
102
+ /** Transport type (currently only stdio supported) */
103
+ transport: z.enum(["stdio"]).default("stdio")
104
+ });
93
105
  var appConfigSchema = z.object({
94
106
  /** Default provider name to use */
95
107
  defaultProvider: z.string().optional(),
@@ -136,7 +148,9 @@ var appConfigSchema = z.object({
136
148
  maxHistoryAge: z.number().default(60),
137
149
  maxMemorySize: z.number().default(50),
138
150
  idleThreshold: z.number().default(3e5)
139
- }).default({})
151
+ }).default({}),
152
+ /** MCP servers — external tool providers via Model Context Protocol */
153
+ mcpServers: z.array(mcpServerSchema).default([])
140
154
  });
141
155
 
142
156
  // src/config/constants.ts
@@ -1189,12 +1203,22 @@ function zodToJsonSchema(schema) {
1189
1203
  return result2;
1190
1204
  }
1191
1205
  if (schema instanceof z2.ZodTuple) {
1192
- const items = schema.items.map((item) => zodToJsonSchema(item));
1206
+ const itemSchemas = schema.items.map((item) => zodToJsonSchema(item));
1207
+ const allTypes = itemSchemas.map((s) => s.type).filter(Boolean);
1208
+ const uniqueTypes = [...new Set(allTypes)];
1209
+ let mergedItems;
1210
+ if (uniqueTypes.length === 1) {
1211
+ mergedItems = { type: uniqueTypes[0] };
1212
+ } else if (uniqueTypes.length > 1) {
1213
+ mergedItems = { anyOf: uniqueTypes.map((t) => ({ type: t })) };
1214
+ } else {
1215
+ mergedItems = {};
1216
+ }
1193
1217
  const result2 = {
1194
1218
  type: "array",
1195
- items,
1196
- minItems: items.length,
1197
- maxItems: items.length
1219
+ items: mergedItems,
1220
+ minItems: itemSchemas.length,
1221
+ maxItems: itemSchemas.length
1198
1222
  };
1199
1223
  if (schema.description) result2.description = schema.description;
1200
1224
  return result2;
@@ -6801,6 +6825,312 @@ function buildSystemPrompt() {
6801
6825
  ].join("\n\n");
6802
6826
  }
6803
6827
 
6828
+ // src/mcp/client.ts
6829
+ import { spawn } from "child_process";
6830
+ import { createInterface } from "readline";
6831
+ var REQUEST_TIMEOUT = 3e4;
6832
+ var MCPClient = class {
6833
+ config;
6834
+ process = null;
6835
+ rl = null;
6836
+ connected = false;
6837
+ nextId = 1;
6838
+ pendingRequests = /* @__PURE__ */ new Map();
6839
+ buffer = "";
6840
+ constructor(config) {
6841
+ this.config = config;
6842
+ }
6843
+ async connect() {
6844
+ if (this.connected) return;
6845
+ const env = {
6846
+ ...process.env,
6847
+ ...this.config.env
6848
+ };
6849
+ this.process = spawn(this.config.command, this.config.args, {
6850
+ stdio: ["pipe", "pipe", "pipe"],
6851
+ env
6852
+ });
6853
+ this.process.on("error", (err) => {
6854
+ this.cleanup();
6855
+ throw err;
6856
+ });
6857
+ this.process.on("exit", () => {
6858
+ this.cleanup();
6859
+ });
6860
+ if (!this.process.stdout) {
6861
+ throw new Error("MCP server stdout is not available");
6862
+ }
6863
+ this.rl = createInterface({ input: this.process.stdout });
6864
+ this.rl.on("line", (line) => {
6865
+ this.handleLine(line);
6866
+ });
6867
+ if (this.process.stderr) {
6868
+ this.process.stderr.on("data", () => {
6869
+ });
6870
+ }
6871
+ await this.sendRequest("initialize", {
6872
+ protocolVersion: "2024-11-05",
6873
+ capabilities: {},
6874
+ clientInfo: { name: "cliskill", version: "1.0.0" }
6875
+ });
6876
+ this.sendNotification("notifications/initialized", {});
6877
+ this.connected = true;
6878
+ }
6879
+ async disconnect() {
6880
+ if (!this.process) return;
6881
+ for (const [, pending] of this.pendingRequests) {
6882
+ clearTimeout(pending.timer);
6883
+ pending.reject(new Error("Connection closed"));
6884
+ }
6885
+ this.pendingRequests.clear();
6886
+ this.process.kill("SIGTERM");
6887
+ this.cleanup();
6888
+ }
6889
+ async listTools() {
6890
+ const response = await this.sendRequest("tools/list", {});
6891
+ const result = response.result;
6892
+ return result?.tools ?? [];
6893
+ }
6894
+ async callTool(name, args) {
6895
+ const response = await this.sendRequest("tools/call", { name, arguments: args });
6896
+ return response.result;
6897
+ }
6898
+ async listResources() {
6899
+ const response = await this.sendRequest("resources/list", {});
6900
+ const result = response.result;
6901
+ return result?.resources ?? [];
6902
+ }
6903
+ async readResource(uri) {
6904
+ const response = await this.sendRequest("resources/read", { uri });
6905
+ return response.result;
6906
+ }
6907
+ async listPrompts() {
6908
+ const response = await this.sendRequest("prompts/list", {});
6909
+ const result = response.result;
6910
+ return result?.prompts ?? [];
6911
+ }
6912
+ isConnected() {
6913
+ return this.connected;
6914
+ }
6915
+ sendRequest(method, params) {
6916
+ return new Promise((resolve9, reject) => {
6917
+ if (!this.process?.stdin) {
6918
+ reject(new Error("MCP server not connected"));
6919
+ return;
6920
+ }
6921
+ const id = this.nextId++;
6922
+ const request = { jsonrpc: "2.0", id, method, params };
6923
+ const timer = setTimeout(() => {
6924
+ this.pendingRequests.delete(id);
6925
+ reject(new Error(`Request timeout: ${method} (id=${id})`));
6926
+ }, REQUEST_TIMEOUT);
6927
+ this.pendingRequests.set(id, { resolve: resolve9, reject, timer });
6928
+ const data = JSON.stringify(request) + "\n";
6929
+ this.process.stdin.write(data, (err) => {
6930
+ if (err) {
6931
+ clearTimeout(timer);
6932
+ this.pendingRequests.delete(id);
6933
+ reject(err);
6934
+ }
6935
+ });
6936
+ });
6937
+ }
6938
+ sendNotification(method, params) {
6939
+ if (!this.process?.stdin) return;
6940
+ const notification = { jsonrpc: "2.0", method, params };
6941
+ const data = JSON.stringify(notification) + "\n";
6942
+ this.process.stdin.write(data);
6943
+ }
6944
+ handleLine(line) {
6945
+ this.buffer += line;
6946
+ let response;
6947
+ try {
6948
+ response = JSON.parse(this.buffer);
6949
+ } catch {
6950
+ return;
6951
+ } finally {
6952
+ this.buffer = "";
6953
+ }
6954
+ if (response.id !== void 0 && response.id !== null) {
6955
+ const pending = this.pendingRequests.get(response.id);
6956
+ if (pending) {
6957
+ clearTimeout(pending.timer);
6958
+ this.pendingRequests.delete(response.id);
6959
+ pending.resolve(response);
6960
+ }
6961
+ }
6962
+ }
6963
+ cleanup() {
6964
+ this.connected = false;
6965
+ this.rl?.close();
6966
+ this.rl = null;
6967
+ this.process = null;
6968
+ this.buffer = "";
6969
+ }
6970
+ };
6971
+
6972
+ // src/mcp/manager.ts
6973
+ var MCPConnectionManager = class {
6974
+ clients = /* @__PURE__ */ new Map();
6975
+ async addServer(config) {
6976
+ const client = new MCPClient(config);
6977
+ await client.connect();
6978
+ this.clients.set(config.name, client);
6979
+ }
6980
+ async removeServer(name) {
6981
+ const client = this.clients.get(name);
6982
+ if (client) {
6983
+ await client.disconnect();
6984
+ this.clients.delete(name);
6985
+ }
6986
+ }
6987
+ getConnectedServers() {
6988
+ return Array.from(this.clients.entries()).filter(([, client]) => client.isConnected()).map(([name]) => name);
6989
+ }
6990
+ async getAllTools() {
6991
+ const allTools = [];
6992
+ for (const [, client] of this.clients) {
6993
+ if (!client.isConnected()) continue;
6994
+ try {
6995
+ const tools = await client.listTools();
6996
+ allTools.push(...tools);
6997
+ } catch {
6998
+ }
6999
+ }
7000
+ return allTools;
7001
+ }
7002
+ async callTool(serverName, toolName, args) {
7003
+ const client = this.clients.get(serverName);
7004
+ if (!client) {
7005
+ throw new Error(`MCP server "${serverName}" not found`);
7006
+ }
7007
+ if (!client.isConnected()) {
7008
+ throw new Error(`MCP server "${serverName}" is not connected`);
7009
+ }
7010
+ return client.callTool(toolName, args);
7011
+ }
7012
+ async getToolsForServer(serverName) {
7013
+ const client = this.clients.get(serverName);
7014
+ if (!client || !client.isConnected()) {
7015
+ return [];
7016
+ }
7017
+ return client.listTools();
7018
+ }
7019
+ async disconnectAll() {
7020
+ for (const [, client] of this.clients) {
7021
+ try {
7022
+ await client.disconnect();
7023
+ } catch {
7024
+ }
7025
+ }
7026
+ this.clients.clear();
7027
+ }
7028
+ };
7029
+
7030
+ // src/mcp/mcp-tool-adapter.ts
7031
+ import { z as z20 } from "zod";
7032
+ var MCPToolAdapter = class {
7033
+ name;
7034
+ description;
7035
+ inputSchema;
7036
+ riskLevel = "safe";
7037
+ concurrencySafe = true;
7038
+ readOnly = true;
7039
+ serverName;
7040
+ mcpManager;
7041
+ rawInputSchema;
7042
+ constructor(serverName, toolName, description, inputSchema, mcpManager) {
7043
+ this.name = `mcp_${serverName}_${toolName}`;
7044
+ this.description = description;
7045
+ this.serverName = serverName;
7046
+ this.mcpManager = mcpManager;
7047
+ this.rawInputSchema = inputSchema;
7048
+ this.inputSchema = this.buildZodSchema(inputSchema);
7049
+ }
7050
+ async execute(input, context) {
7051
+ const allowed = await context.checkPermission(this.name, JSON.stringify(input));
7052
+ if (!allowed) {
7053
+ return `Error: Permission denied for MCP tool: ${this.name}`;
7054
+ }
7055
+ try {
7056
+ const result = await this.mcpManager.callTool(this.serverName, this.getOriginalName(), input);
7057
+ return typeof result === "string" ? result : JSON.stringify(result, null, 2);
7058
+ } catch (err) {
7059
+ return `Error calling MCP tool ${this.name}: ${err.message}`;
7060
+ }
7061
+ }
7062
+ toToolDefinition() {
7063
+ return {
7064
+ name: this.name,
7065
+ description: this.description,
7066
+ inputSchema: this.rawInputSchema
7067
+ };
7068
+ }
7069
+ getOriginalName() {
7070
+ const parts = this.name.split("_");
7071
+ return parts.slice(2).join("_");
7072
+ }
7073
+ getServerName() {
7074
+ return this.serverName;
7075
+ }
7076
+ buildZodSchema(jsonSchema) {
7077
+ const properties = jsonSchema.properties ?? {};
7078
+ const required = new Set(jsonSchema.required ?? []);
7079
+ const shape = {};
7080
+ for (const [key, propSchema] of Object.entries(properties)) {
7081
+ const zodType = this.jsonSchemaPropertyToZod(propSchema);
7082
+ shape[key] = required.has(key) ? zodType : zodType.optional();
7083
+ }
7084
+ return z20.object(shape);
7085
+ }
7086
+ jsonSchemaPropertyToZod(prop) {
7087
+ const type = prop.type;
7088
+ switch (type) {
7089
+ case "string":
7090
+ if (prop.enum) return z20.enum(prop.enum);
7091
+ return z20.string();
7092
+ case "number":
7093
+ case "integer":
7094
+ return z20.number();
7095
+ case "boolean":
7096
+ return z20.boolean();
7097
+ case "array":
7098
+ if (prop.items && typeof prop.items === "object") {
7099
+ return z20.array(this.jsonSchemaPropertyToZod(prop.items));
7100
+ }
7101
+ return z20.array(z20.unknown());
7102
+ case "object":
7103
+ if (prop.properties && typeof prop.properties === "object") {
7104
+ return this.buildZodSchema(prop);
7105
+ }
7106
+ return z20.record(z20.unknown());
7107
+ default:
7108
+ return z20.unknown();
7109
+ }
7110
+ }
7111
+ };
7112
+ async function registerMCPTools(mcpManager, registerFn) {
7113
+ const registered = [];
7114
+ for (const serverName of mcpManager.getConnectedServers()) {
7115
+ try {
7116
+ const tools = await mcpManager.getToolsForServer(serverName);
7117
+ for (const tool of tools) {
7118
+ const adapter = new MCPToolAdapter(
7119
+ serverName,
7120
+ tool.name,
7121
+ tool.description ?? `MCP tool: ${tool.name}`,
7122
+ tool.inputSchema,
7123
+ mcpManager
7124
+ );
7125
+ registerFn(adapter);
7126
+ registered.push(adapter.name);
7127
+ }
7128
+ } catch {
7129
+ }
7130
+ }
7131
+ return registered;
7132
+ }
7133
+
6804
7134
  // src/ui/repl.ts
6805
7135
  var SessionSaver = class {
6806
7136
  filePath;
@@ -6847,6 +7177,36 @@ function buildModelRouter(adapterRegistry, config) {
6847
7177
  defaultModel: config.models.defaultModel
6848
7178
  });
6849
7179
  }
7180
+ async function connectMcpServers(config, toolRegistry) {
7181
+ if (config.mcpServers.length === 0) return null;
7182
+ const mcpManager = new MCPConnectionManager();
7183
+ let connectedCount = 0;
7184
+ for (const serverConfig of config.mcpServers) {
7185
+ try {
7186
+ await mcpManager.addServer({
7187
+ name: serverConfig.name,
7188
+ command: serverConfig.command,
7189
+ args: serverConfig.args,
7190
+ env: serverConfig.env,
7191
+ transport: serverConfig.transport
7192
+ });
7193
+ connectedCount++;
7194
+ } catch (err) {
7195
+ console.error(`MCP server "${serverConfig.name}" connection failed: ${err.message}`);
7196
+ }
7197
+ }
7198
+ if (connectedCount === 0) return mcpManager;
7199
+ const registeredTools = await registerMCPTools(mcpManager, (tool) => {
7200
+ try {
7201
+ toolRegistry.register(tool);
7202
+ } catch {
7203
+ }
7204
+ });
7205
+ if (registeredTools.length > 0) {
7206
+ console.log(` MCP: ${connectedCount} server(s) connected, ${registeredTools.length} tool(s) loaded`);
7207
+ }
7208
+ return mcpManager;
7209
+ }
6850
7210
  async function runTuiRepl(config) {
6851
7211
  const adapterRegistry = new AdapterRegistry();
6852
7212
  for (const provider of config.providers) {
@@ -6859,6 +7219,7 @@ async function runTuiRepl(config) {
6859
7219
  const adapter = config.defaultProvider ? adapterRegistry.get(config.defaultProvider) : adapterRegistry.getAll()[0];
6860
7220
  const modelRouter = buildModelRouter(adapterRegistry, config);
6861
7221
  const toolRegistry = createDefaultToolRegistry(modelRouter);
7222
+ const mcpManager = await connectMcpServers(config, toolRegistry);
6862
7223
  ensureDataDir();
6863
7224
  const sessionSaver = new SessionSaver();
6864
7225
  await sessionSaver.init();
@@ -6953,13 +7314,14 @@ async function runBasicRepl(config) {
6953
7314
  `);
6954
7315
  }
6955
7316
  const toolRegistry = createDefaultToolRegistry(modelRouter);
7317
+ await connectMcpServers(config, toolRegistry);
6956
7318
  const abortController = new AbortController();
6957
7319
  ensureDataDir();
6958
7320
  const sessionSaver = new SessionSaver();
6959
7321
  await sessionSaver.init();
6960
7322
  let sessionMessages = [];
6961
- const { createInterface } = await import("readline");
6962
- const rl = createInterface({
7323
+ const { createInterface: createInterface2 } = await import("readline");
7324
+ const rl = createInterface2({
6963
7325
  input: process.stdin,
6964
7326
  output: process.stdout,
6965
7327
  prompt: "> "
@@ -10028,6 +10390,8 @@ export {
10028
10390
  renderInkApp,
10029
10391
  formatAnsiMessage,
10030
10392
  MessageList,
10393
+ MCPClient,
10394
+ MCPConnectionManager,
10031
10395
  runCli
10032
10396
  };
10033
- //# sourceMappingURL=chunk-PBLJ6557.js.map
10397
+ //# sourceMappingURL=chunk-ETGUAK34.js.map