cliskill 1.0.4 → 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-UJMUL64T.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
@@ -6811,6 +6825,312 @@ function buildSystemPrompt() {
6811
6825
  ].join("\n\n");
6812
6826
  }
6813
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
+
6814
7134
  // src/ui/repl.ts
6815
7135
  var SessionSaver = class {
6816
7136
  filePath;
@@ -6857,6 +7177,36 @@ function buildModelRouter(adapterRegistry, config) {
6857
7177
  defaultModel: config.models.defaultModel
6858
7178
  });
6859
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
+ }
6860
7210
  async function runTuiRepl(config) {
6861
7211
  const adapterRegistry = new AdapterRegistry();
6862
7212
  for (const provider of config.providers) {
@@ -6869,6 +7219,7 @@ async function runTuiRepl(config) {
6869
7219
  const adapter = config.defaultProvider ? adapterRegistry.get(config.defaultProvider) : adapterRegistry.getAll()[0];
6870
7220
  const modelRouter = buildModelRouter(adapterRegistry, config);
6871
7221
  const toolRegistry = createDefaultToolRegistry(modelRouter);
7222
+ const mcpManager = await connectMcpServers(config, toolRegistry);
6872
7223
  ensureDataDir();
6873
7224
  const sessionSaver = new SessionSaver();
6874
7225
  await sessionSaver.init();
@@ -6963,13 +7314,14 @@ async function runBasicRepl(config) {
6963
7314
  `);
6964
7315
  }
6965
7316
  const toolRegistry = createDefaultToolRegistry(modelRouter);
7317
+ await connectMcpServers(config, toolRegistry);
6966
7318
  const abortController = new AbortController();
6967
7319
  ensureDataDir();
6968
7320
  const sessionSaver = new SessionSaver();
6969
7321
  await sessionSaver.init();
6970
7322
  let sessionMessages = [];
6971
- const { createInterface } = await import("readline");
6972
- const rl = createInterface({
7323
+ const { createInterface: createInterface2 } = await import("readline");
7324
+ const rl = createInterface2({
6973
7325
  input: process.stdin,
6974
7326
  output: process.stdout,
6975
7327
  prompt: "> "
@@ -10038,6 +10390,8 @@ export {
10038
10390
  renderInkApp,
10039
10391
  formatAnsiMessage,
10040
10392
  MessageList,
10393
+ MCPClient,
10394
+ MCPConnectionManager,
10041
10395
  runCli
10042
10396
  };
10043
- //# sourceMappingURL=chunk-UJMUL64T.js.map
10397
+ //# sourceMappingURL=chunk-ETGUAK34.js.map