@corbat-tech/coco 2.25.10 → 2.25.12

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/dist/cli/index.js CHANGED
@@ -2568,7 +2568,11 @@ function getDefaultModel(provider) {
2568
2568
  function normalizeConfiguredModel(model) {
2569
2569
  if (typeof model !== "string") return void 0;
2570
2570
  const trimmed = model.trim();
2571
- return trimmed.length > 0 ? trimmed : void 0;
2571
+ if (trimmed.length === 0) return void 0;
2572
+ if (["default", "none", "null", "undefined"].includes(trimmed.toLowerCase())) {
2573
+ return void 0;
2574
+ }
2575
+ return trimmed;
2572
2576
  }
2573
2577
  function getDefaultProvider() {
2574
2578
  const envProvider = process.env["COCO_PROVIDER"]?.toLowerCase();
@@ -4498,7 +4502,9 @@ var init_openai = __esm({
4498
4502
  input,
4499
4503
  instructions: instructions ?? void 0,
4500
4504
  max_output_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
4501
- ...supportsTemp && { temperature: options?.temperature ?? this.config.temperature ?? 0 },
4505
+ ...supportsTemp && {
4506
+ temperature: options?.temperature ?? this.config.temperature ?? 0
4507
+ },
4502
4508
  store: false
4503
4509
  });
4504
4510
  return {
@@ -4533,7 +4539,9 @@ var init_openai = __esm({
4533
4539
  instructions: instructions ?? void 0,
4534
4540
  tools,
4535
4541
  max_output_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
4536
- ...supportsTemp && { temperature: options?.temperature ?? this.config.temperature ?? 0 },
4542
+ ...supportsTemp && {
4543
+ temperature: options?.temperature ?? this.config.temperature ?? 0
4544
+ },
4537
4545
  store: false
4538
4546
  });
4539
4547
  let content = "";
@@ -10261,6 +10269,7 @@ Rules:
10261
10269
  - If you need real-time data, CALL web_search. NEVER say "I don't have access to real-time data."
10262
10270
  - If an MCP tool exists for a service (tool names like \`mcp_<service>_...\`), prefer that MCP tool over generic \`web_fetch\` or \`http_fetch\`.
10263
10271
  - Use \`mcp_list_servers\` to inspect configured or connected MCP services. Do NOT use \`bash_exec\` to run \`coco mcp ...\` unless the user explicitly asked for that CLI command.
10272
+ - If the user asks you to use an MCP service and it is configured but disconnected, call \`mcp_connect_server\` first. This built-in flow may open a browser for OAuth. Do NOT ask the user for raw tokens when MCP OAuth is supported.
10264
10273
  - Before answering "I can't do that", check your full tool catalog below \u2014 you likely have a tool for it.
10265
10274
  - NEVER claim you cannot run a command because you lack credentials, access, or connectivity. bash_exec runs in the user's own shell environment and inherits their full PATH, kubeconfig, gcloud auth, AWS profiles, SSH keys, and every other tool installed on their machine. kubectl, gcloud, aws, docker, and any other CLI available to the user are available to you. ALWAYS attempt the command with bash_exec; report failure only if it actually returns a non-zero exit code.
10266
10275
 
@@ -21252,7 +21261,9 @@ function createProtectedMetadataCandidates(resourceUrl, headerUrl) {
21252
21261
  candidates.push(`${origin}/.well-known/oauth-protected-resource`);
21253
21262
  if (pathPart && pathPart !== "/") {
21254
21263
  candidates.push(`${origin}/.well-known/oauth-protected-resource${pathPart}`);
21255
- candidates.push(`${origin}/.well-known/oauth-protected-resource/${pathPart.replace(/^\//, "")}`);
21264
+ candidates.push(
21265
+ `${origin}/.well-known/oauth-protected-resource/${pathPart.replace(/^\//, "")}`
21266
+ );
21256
21267
  }
21257
21268
  return Array.from(new Set(candidates));
21258
21269
  }
@@ -21651,14 +21662,6 @@ var init_http = __esm({
21651
21662
  if (this.shouldAttemptOAuth()) {
21652
21663
  this.oauthToken = await getStoredMcpOAuthToken(this.config.url);
21653
21664
  }
21654
- const response = await this.sendRequestWithOAuthRetry(
21655
- "GET",
21656
- void 0,
21657
- this.abortController.signal
21658
- );
21659
- if (!response.ok && response.status !== 404) {
21660
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
21661
- }
21662
21665
  this.connected = true;
21663
21666
  } catch (error) {
21664
21667
  if (error instanceof MCPError) {
@@ -22367,6 +22370,263 @@ var init_full_power_risk_mode = __esm({
22367
22370
  }
22368
22371
  });
22369
22372
 
22373
+ // src/mcp/tools.ts
22374
+ var tools_exports = {};
22375
+ __export(tools_exports, {
22376
+ createToolsFromMCPServer: () => createToolsFromMCPServer,
22377
+ extractOriginalToolName: () => extractOriginalToolName,
22378
+ getMCPToolInfo: () => getMCPToolInfo,
22379
+ jsonSchemaToZod: () => jsonSchemaToZod,
22380
+ registerMCPTools: () => registerMCPTools,
22381
+ wrapMCPTool: () => wrapMCPTool,
22382
+ wrapMCPTools: () => wrapMCPTools
22383
+ });
22384
+ function buildMcpToolDescription(serverName, tool) {
22385
+ const base = tool.description || `Tool '${tool.name}' exposed by MCP server '${serverName}'`;
22386
+ const lowerServer = serverName.toLowerCase();
22387
+ if (lowerServer.includes("atlassian") || lowerServer.includes("jira") || lowerServer.includes("confluence")) {
22388
+ return `${base}. Use this MCP tool for Atlassian/Jira/Confluence data. Prefer it over direct web_fetch or http_fetch for Atlassian content.`;
22389
+ }
22390
+ return `${base}. Exposed by MCP server '${serverName}'. Prefer this MCP tool over generic web/http fetch when accessing data from that connected service.`;
22391
+ }
22392
+ function jsonSchemaToZod(schema) {
22393
+ if (schema.enum && Array.isArray(schema.enum)) {
22394
+ const values = schema.enum;
22395
+ if (values.length > 0 && values.every((v) => typeof v === "string")) {
22396
+ return z.enum(values);
22397
+ }
22398
+ const literals = values.map((v) => z.literal(v));
22399
+ if (literals.length < 2) {
22400
+ return literals[0] ?? z.any();
22401
+ }
22402
+ return z.union(literals);
22403
+ }
22404
+ if (schema.const !== void 0) {
22405
+ return z.literal(schema.const);
22406
+ }
22407
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
22408
+ const schemas = schema.oneOf.map(jsonSchemaToZod);
22409
+ if (schemas.length >= 2) {
22410
+ return z.union(schemas);
22411
+ }
22412
+ return schemas[0] ?? z.unknown();
22413
+ }
22414
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
22415
+ const schemas = schema.anyOf.map(jsonSchemaToZod);
22416
+ if (schemas.length >= 2) {
22417
+ return z.union(schemas);
22418
+ }
22419
+ return schemas[0] ?? z.unknown();
22420
+ }
22421
+ if (schema.allOf && Array.isArray(schema.allOf)) {
22422
+ const schemas = schema.allOf.map(jsonSchemaToZod);
22423
+ return schemas.reduce((acc, s) => z.intersection(acc, s));
22424
+ }
22425
+ const type = schema.type;
22426
+ const makeNullable = (s) => {
22427
+ if (schema.nullable === true) return s.nullable();
22428
+ return s;
22429
+ };
22430
+ switch (type) {
22431
+ case "string": {
22432
+ let s = z.string();
22433
+ if (schema.format) {
22434
+ switch (schema.format) {
22435
+ case "uri":
22436
+ case "url":
22437
+ s = z.string().url();
22438
+ break;
22439
+ case "email":
22440
+ s = z.string().email();
22441
+ break;
22442
+ case "date-time":
22443
+ case "datetime":
22444
+ s = z.string().datetime();
22445
+ break;
22446
+ }
22447
+ }
22448
+ if (typeof schema.minLength === "number") s = s.min(schema.minLength);
22449
+ if (typeof schema.maxLength === "number") s = s.max(schema.maxLength);
22450
+ return makeNullable(s);
22451
+ }
22452
+ case "number": {
22453
+ let n = z.number();
22454
+ if (typeof schema.minimum === "number") n = n.min(schema.minimum);
22455
+ if (typeof schema.maximum === "number") n = n.max(schema.maximum);
22456
+ if (typeof schema.exclusiveMinimum === "number") n = n.gt(schema.exclusiveMinimum);
22457
+ if (typeof schema.exclusiveMaximum === "number") n = n.lt(schema.exclusiveMaximum);
22458
+ return makeNullable(n);
22459
+ }
22460
+ case "integer": {
22461
+ let n = z.number().int();
22462
+ if (typeof schema.minimum === "number") n = n.min(schema.minimum);
22463
+ if (typeof schema.maximum === "number") n = n.max(schema.maximum);
22464
+ return makeNullable(n);
22465
+ }
22466
+ case "boolean":
22467
+ return makeNullable(z.boolean());
22468
+ case "null":
22469
+ return z.null();
22470
+ case "array":
22471
+ if (schema.items) {
22472
+ const itemSchema = jsonSchemaToZod(schema.items);
22473
+ let arr = z.array(itemSchema);
22474
+ if (typeof schema.minItems === "number") arr = arr.min(schema.minItems);
22475
+ if (typeof schema.maxItems === "number") arr = arr.max(schema.maxItems);
22476
+ return makeNullable(arr);
22477
+ }
22478
+ return makeNullable(z.array(z.unknown()));
22479
+ case "object": {
22480
+ const properties = schema.properties;
22481
+ const required = schema.required;
22482
+ if (!properties) {
22483
+ return makeNullable(z.record(z.string(), z.unknown()));
22484
+ }
22485
+ const shape = {};
22486
+ for (const [key, propSchema] of Object.entries(properties)) {
22487
+ let fieldSchema = jsonSchemaToZod(propSchema);
22488
+ if (!required?.includes(key)) {
22489
+ fieldSchema = fieldSchema.optional();
22490
+ }
22491
+ if (propSchema.description && typeof propSchema.description === "string") {
22492
+ fieldSchema = fieldSchema.describe(propSchema.description);
22493
+ }
22494
+ shape[key] = fieldSchema;
22495
+ }
22496
+ return makeNullable(z.object(shape));
22497
+ }
22498
+ default:
22499
+ if (Array.isArray(schema.type)) {
22500
+ const types = schema.type;
22501
+ if (types.includes("null")) {
22502
+ const nonNullType = types.find((t) => t !== "null");
22503
+ if (nonNullType) {
22504
+ return jsonSchemaToZod({ ...schema, type: nonNullType }).nullable();
22505
+ }
22506
+ }
22507
+ }
22508
+ return z.unknown();
22509
+ }
22510
+ }
22511
+ function createToolParametersSchema(tool) {
22512
+ const schema = tool.inputSchema;
22513
+ if (!schema || schema.type !== "object") {
22514
+ return z.object({});
22515
+ }
22516
+ return jsonSchemaToZod(schema);
22517
+ }
22518
+ function formatToolResult(result) {
22519
+ if (result.isError) {
22520
+ throw new Error(result.content.map((c) => c.text || "").join("\n"));
22521
+ }
22522
+ return result.content.map((item) => {
22523
+ switch (item.type) {
22524
+ case "text":
22525
+ return item.text || "";
22526
+ case "image":
22527
+ return `[Image: ${item.mimeType || "unknown"}]`;
22528
+ case "resource":
22529
+ return `[Resource: ${item.resource?.uri || "unknown"}]`;
22530
+ default:
22531
+ return "";
22532
+ }
22533
+ }).filter(Boolean).join("\n");
22534
+ }
22535
+ function createToolName(serverName, toolName, prefix) {
22536
+ return `${prefix}_${serverName}_${toolName}`.replace(/[^a-zA-Z0-9_]/g, "_");
22537
+ }
22538
+ function wrapMCPTool(tool, serverName, client, options = {}) {
22539
+ const opts = { ...DEFAULT_OPTIONS2, ...options };
22540
+ const wrappedName = createToolName(serverName, tool.name, opts.namePrefix);
22541
+ const parametersSchema = createToolParametersSchema(tool);
22542
+ const cocoTool = {
22543
+ name: wrappedName,
22544
+ description: buildMcpToolDescription(serverName, tool),
22545
+ category: opts.category,
22546
+ parameters: parametersSchema,
22547
+ execute: async (params) => {
22548
+ const timeout = opts.requestTimeout;
22549
+ try {
22550
+ const result = await Promise.race([
22551
+ client.callTool({
22552
+ name: tool.name,
22553
+ arguments: params
22554
+ }),
22555
+ new Promise((_, reject) => {
22556
+ setTimeout(() => {
22557
+ reject(new MCPTimeoutError(`Tool '${tool.name}' timed out after ${timeout}ms`));
22558
+ }, timeout);
22559
+ })
22560
+ ]);
22561
+ return formatToolResult(result);
22562
+ } catch (error) {
22563
+ if (error instanceof MCPError) {
22564
+ throw error;
22565
+ }
22566
+ throw new MCPError(
22567
+ -32603,
22568
+ `Tool execution failed: ${error instanceof Error ? error.message : "Unknown error"}`
22569
+ );
22570
+ }
22571
+ }
22572
+ };
22573
+ const wrapped = {
22574
+ originalTool: tool,
22575
+ serverName,
22576
+ wrappedName
22577
+ };
22578
+ return { tool: cocoTool, wrapped };
22579
+ }
22580
+ function wrapMCPTools(tools, serverName, client, options = {}) {
22581
+ const cocoTools = [];
22582
+ const wrappedTools = [];
22583
+ for (const tool of tools) {
22584
+ const { tool: cocoTool, wrapped } = wrapMCPTool(tool, serverName, client, options);
22585
+ cocoTools.push(cocoTool);
22586
+ wrappedTools.push(wrapped);
22587
+ }
22588
+ return { tools: cocoTools, wrapped: wrappedTools };
22589
+ }
22590
+ async function createToolsFromMCPServer(serverName, client, options = {}) {
22591
+ if (!client.isConnected()) {
22592
+ await client.initialize({
22593
+ protocolVersion: "2024-11-05",
22594
+ capabilities: {},
22595
+ clientInfo: { name: "coco-mcp-client", version: "0.2.0" }
22596
+ });
22597
+ }
22598
+ const { tools } = await client.listTools();
22599
+ return wrapMCPTools(tools, serverName, client, options);
22600
+ }
22601
+ async function registerMCPTools(registry, serverName, client, options = {}) {
22602
+ const { tools, wrapped } = await createToolsFromMCPServer(serverName, client, options);
22603
+ for (const tool of tools) {
22604
+ registry.register(tool);
22605
+ }
22606
+ return wrapped;
22607
+ }
22608
+ function getMCPToolInfo(wrappedName, wrappedTools) {
22609
+ return wrappedTools.find((t) => t.wrappedName === wrappedName);
22610
+ }
22611
+ function extractOriginalToolName(wrappedName, serverName, prefix = "mcp") {
22612
+ const prefix_pattern = `${prefix}_${serverName}_`;
22613
+ if (wrappedName.startsWith(prefix_pattern)) {
22614
+ return wrappedName.slice(prefix_pattern.length);
22615
+ }
22616
+ return null;
22617
+ }
22618
+ var DEFAULT_OPTIONS2;
22619
+ var init_tools = __esm({
22620
+ "src/mcp/tools.ts"() {
22621
+ init_errors2();
22622
+ DEFAULT_OPTIONS2 = {
22623
+ namePrefix: "mcp",
22624
+ category: "deploy",
22625
+ requestTimeout: 6e4
22626
+ };
22627
+ }
22628
+ });
22629
+
22370
22630
  // src/cli/repl/allow-path-prompt.ts
22371
22631
  var allow_path_prompt_exports = {};
22372
22632
  __export(allow_path_prompt_exports, {
@@ -22815,263 +23075,6 @@ var init_stack_detector = __esm({
22815
23075
  }
22816
23076
  });
22817
23077
 
22818
- // src/mcp/tools.ts
22819
- var tools_exports = {};
22820
- __export(tools_exports, {
22821
- createToolsFromMCPServer: () => createToolsFromMCPServer,
22822
- extractOriginalToolName: () => extractOriginalToolName,
22823
- getMCPToolInfo: () => getMCPToolInfo,
22824
- jsonSchemaToZod: () => jsonSchemaToZod,
22825
- registerMCPTools: () => registerMCPTools,
22826
- wrapMCPTool: () => wrapMCPTool,
22827
- wrapMCPTools: () => wrapMCPTools
22828
- });
22829
- function buildMcpToolDescription(serverName, tool) {
22830
- const base = tool.description || `Tool '${tool.name}' exposed by MCP server '${serverName}'`;
22831
- const lowerServer = serverName.toLowerCase();
22832
- if (lowerServer.includes("atlassian") || lowerServer.includes("jira") || lowerServer.includes("confluence")) {
22833
- return `${base}. Use this MCP tool for Atlassian/Jira/Confluence data. Prefer it over direct web_fetch or http_fetch for Atlassian content.`;
22834
- }
22835
- return `${base}. Exposed by MCP server '${serverName}'. Prefer this MCP tool over generic web/http fetch when accessing data from that connected service.`;
22836
- }
22837
- function jsonSchemaToZod(schema) {
22838
- if (schema.enum && Array.isArray(schema.enum)) {
22839
- const values = schema.enum;
22840
- if (values.length > 0 && values.every((v) => typeof v === "string")) {
22841
- return z.enum(values);
22842
- }
22843
- const literals = values.map((v) => z.literal(v));
22844
- if (literals.length < 2) {
22845
- return literals[0] ?? z.any();
22846
- }
22847
- return z.union(literals);
22848
- }
22849
- if (schema.const !== void 0) {
22850
- return z.literal(schema.const);
22851
- }
22852
- if (schema.oneOf && Array.isArray(schema.oneOf)) {
22853
- const schemas = schema.oneOf.map(jsonSchemaToZod);
22854
- if (schemas.length >= 2) {
22855
- return z.union(schemas);
22856
- }
22857
- return schemas[0] ?? z.unknown();
22858
- }
22859
- if (schema.anyOf && Array.isArray(schema.anyOf)) {
22860
- const schemas = schema.anyOf.map(jsonSchemaToZod);
22861
- if (schemas.length >= 2) {
22862
- return z.union(schemas);
22863
- }
22864
- return schemas[0] ?? z.unknown();
22865
- }
22866
- if (schema.allOf && Array.isArray(schema.allOf)) {
22867
- const schemas = schema.allOf.map(jsonSchemaToZod);
22868
- return schemas.reduce((acc, s) => z.intersection(acc, s));
22869
- }
22870
- const type = schema.type;
22871
- const makeNullable = (s) => {
22872
- if (schema.nullable === true) return s.nullable();
22873
- return s;
22874
- };
22875
- switch (type) {
22876
- case "string": {
22877
- let s = z.string();
22878
- if (schema.format) {
22879
- switch (schema.format) {
22880
- case "uri":
22881
- case "url":
22882
- s = z.string().url();
22883
- break;
22884
- case "email":
22885
- s = z.string().email();
22886
- break;
22887
- case "date-time":
22888
- case "datetime":
22889
- s = z.string().datetime();
22890
- break;
22891
- }
22892
- }
22893
- if (typeof schema.minLength === "number") s = s.min(schema.minLength);
22894
- if (typeof schema.maxLength === "number") s = s.max(schema.maxLength);
22895
- return makeNullable(s);
22896
- }
22897
- case "number": {
22898
- let n = z.number();
22899
- if (typeof schema.minimum === "number") n = n.min(schema.minimum);
22900
- if (typeof schema.maximum === "number") n = n.max(schema.maximum);
22901
- if (typeof schema.exclusiveMinimum === "number") n = n.gt(schema.exclusiveMinimum);
22902
- if (typeof schema.exclusiveMaximum === "number") n = n.lt(schema.exclusiveMaximum);
22903
- return makeNullable(n);
22904
- }
22905
- case "integer": {
22906
- let n = z.number().int();
22907
- if (typeof schema.minimum === "number") n = n.min(schema.minimum);
22908
- if (typeof schema.maximum === "number") n = n.max(schema.maximum);
22909
- return makeNullable(n);
22910
- }
22911
- case "boolean":
22912
- return makeNullable(z.boolean());
22913
- case "null":
22914
- return z.null();
22915
- case "array":
22916
- if (schema.items) {
22917
- const itemSchema = jsonSchemaToZod(schema.items);
22918
- let arr = z.array(itemSchema);
22919
- if (typeof schema.minItems === "number") arr = arr.min(schema.minItems);
22920
- if (typeof schema.maxItems === "number") arr = arr.max(schema.maxItems);
22921
- return makeNullable(arr);
22922
- }
22923
- return makeNullable(z.array(z.unknown()));
22924
- case "object": {
22925
- const properties = schema.properties;
22926
- const required = schema.required;
22927
- if (!properties) {
22928
- return makeNullable(z.record(z.string(), z.unknown()));
22929
- }
22930
- const shape = {};
22931
- for (const [key, propSchema] of Object.entries(properties)) {
22932
- let fieldSchema = jsonSchemaToZod(propSchema);
22933
- if (!required?.includes(key)) {
22934
- fieldSchema = fieldSchema.optional();
22935
- }
22936
- if (propSchema.description && typeof propSchema.description === "string") {
22937
- fieldSchema = fieldSchema.describe(propSchema.description);
22938
- }
22939
- shape[key] = fieldSchema;
22940
- }
22941
- return makeNullable(z.object(shape));
22942
- }
22943
- default:
22944
- if (Array.isArray(schema.type)) {
22945
- const types = schema.type;
22946
- if (types.includes("null")) {
22947
- const nonNullType = types.find((t) => t !== "null");
22948
- if (nonNullType) {
22949
- return jsonSchemaToZod({ ...schema, type: nonNullType }).nullable();
22950
- }
22951
- }
22952
- }
22953
- return z.unknown();
22954
- }
22955
- }
22956
- function createToolParametersSchema(tool) {
22957
- const schema = tool.inputSchema;
22958
- if (!schema || schema.type !== "object") {
22959
- return z.object({});
22960
- }
22961
- return jsonSchemaToZod(schema);
22962
- }
22963
- function formatToolResult(result) {
22964
- if (result.isError) {
22965
- throw new Error(result.content.map((c) => c.text || "").join("\n"));
22966
- }
22967
- return result.content.map((item) => {
22968
- switch (item.type) {
22969
- case "text":
22970
- return item.text || "";
22971
- case "image":
22972
- return `[Image: ${item.mimeType || "unknown"}]`;
22973
- case "resource":
22974
- return `[Resource: ${item.resource?.uri || "unknown"}]`;
22975
- default:
22976
- return "";
22977
- }
22978
- }).filter(Boolean).join("\n");
22979
- }
22980
- function createToolName(serverName, toolName, prefix) {
22981
- return `${prefix}_${serverName}_${toolName}`.replace(/[^a-zA-Z0-9_]/g, "_");
22982
- }
22983
- function wrapMCPTool(tool, serverName, client, options = {}) {
22984
- const opts = { ...DEFAULT_OPTIONS2, ...options };
22985
- const wrappedName = createToolName(serverName, tool.name, opts.namePrefix);
22986
- const parametersSchema = createToolParametersSchema(tool);
22987
- const cocoTool = {
22988
- name: wrappedName,
22989
- description: buildMcpToolDescription(serverName, tool),
22990
- category: opts.category,
22991
- parameters: parametersSchema,
22992
- execute: async (params) => {
22993
- const timeout = opts.requestTimeout;
22994
- try {
22995
- const result = await Promise.race([
22996
- client.callTool({
22997
- name: tool.name,
22998
- arguments: params
22999
- }),
23000
- new Promise((_, reject) => {
23001
- setTimeout(() => {
23002
- reject(new MCPTimeoutError(`Tool '${tool.name}' timed out after ${timeout}ms`));
23003
- }, timeout);
23004
- })
23005
- ]);
23006
- return formatToolResult(result);
23007
- } catch (error) {
23008
- if (error instanceof MCPError) {
23009
- throw error;
23010
- }
23011
- throw new MCPError(
23012
- -32603,
23013
- `Tool execution failed: ${error instanceof Error ? error.message : "Unknown error"}`
23014
- );
23015
- }
23016
- }
23017
- };
23018
- const wrapped = {
23019
- originalTool: tool,
23020
- serverName,
23021
- wrappedName
23022
- };
23023
- return { tool: cocoTool, wrapped };
23024
- }
23025
- function wrapMCPTools(tools, serverName, client, options = {}) {
23026
- const cocoTools = [];
23027
- const wrappedTools = [];
23028
- for (const tool of tools) {
23029
- const { tool: cocoTool, wrapped } = wrapMCPTool(tool, serverName, client, options);
23030
- cocoTools.push(cocoTool);
23031
- wrappedTools.push(wrapped);
23032
- }
23033
- return { tools: cocoTools, wrapped: wrappedTools };
23034
- }
23035
- async function createToolsFromMCPServer(serverName, client, options = {}) {
23036
- if (!client.isConnected()) {
23037
- await client.initialize({
23038
- protocolVersion: "2024-11-05",
23039
- capabilities: {},
23040
- clientInfo: { name: "coco-mcp-client", version: "0.2.0" }
23041
- });
23042
- }
23043
- const { tools } = await client.listTools();
23044
- return wrapMCPTools(tools, serverName, client, options);
23045
- }
23046
- async function registerMCPTools(registry, serverName, client, options = {}) {
23047
- const { tools, wrapped } = await createToolsFromMCPServer(serverName, client, options);
23048
- for (const tool of tools) {
23049
- registry.register(tool);
23050
- }
23051
- return wrapped;
23052
- }
23053
- function getMCPToolInfo(wrappedName, wrappedTools) {
23054
- return wrappedTools.find((t) => t.wrappedName === wrappedName);
23055
- }
23056
- function extractOriginalToolName(wrappedName, serverName, prefix = "mcp") {
23057
- const prefix_pattern = `${prefix}_${serverName}_`;
23058
- if (wrappedName.startsWith(prefix_pattern)) {
23059
- return wrappedName.slice(prefix_pattern.length);
23060
- }
23061
- return null;
23062
- }
23063
- var DEFAULT_OPTIONS2;
23064
- var init_tools = __esm({
23065
- "src/mcp/tools.ts"() {
23066
- init_errors2();
23067
- DEFAULT_OPTIONS2 = {
23068
- namePrefix: "mcp",
23069
- category: "deploy",
23070
- requestTimeout: 6e4
23071
- };
23072
- }
23073
- });
23074
-
23075
23078
  // src/cli/repl/hooks/types.ts
23076
23079
  function isHookEvent(value) {
23077
23080
  return typeof value === "string" && HOOK_EVENTS.includes(value);
@@ -38297,6 +38300,9 @@ var RECOMMENDED_DENY = [
38297
38300
  "bash:eval",
38298
38301
  "bash:source"
38299
38302
  ];
38303
+ function getProjectPreferenceKey(projectPath) {
38304
+ return path39__default.resolve(projectPath);
38305
+ }
38300
38306
  async function loadPermissionPreferences() {
38301
38307
  try {
38302
38308
  const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
@@ -38304,7 +38310,8 @@ async function loadPermissionPreferences() {
38304
38310
  return {
38305
38311
  recommendedAllowlistApplied: config.recommendedAllowlistApplied,
38306
38312
  recommendedAllowlistDismissed: config.recommendedAllowlistDismissed,
38307
- recommendedAllowlistPrompted: config.recommendedAllowlistPrompted
38313
+ recommendedAllowlistPrompted: config.recommendedAllowlistPrompted,
38314
+ recommendedAllowlistPromptedProjects: config.recommendedAllowlistPromptedProjects
38308
38315
  };
38309
38316
  } catch {
38310
38317
  return {};
@@ -38324,7 +38331,26 @@ async function savePermissionPreference(key, value) {
38324
38331
  } catch {
38325
38332
  }
38326
38333
  }
38327
- async function shouldShowPermissionSuggestion() {
38334
+ async function markPermissionSuggestionShownForProject(projectPath) {
38335
+ try {
38336
+ let config = {};
38337
+ try {
38338
+ const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
38339
+ config = JSON.parse(content);
38340
+ } catch {
38341
+ }
38342
+ const promptedProjects = {
38343
+ ...config.recommendedAllowlistPromptedProjects,
38344
+ [getProjectPreferenceKey(projectPath)]: true
38345
+ };
38346
+ config.recommendedAllowlistPromptedProjects = promptedProjects;
38347
+ config.recommendedAllowlistPrompted = true;
38348
+ await fs35__default.mkdir(path39__default.dirname(CONFIG_PATHS.config), { recursive: true });
38349
+ await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2), "utf-8");
38350
+ } catch {
38351
+ }
38352
+ }
38353
+ async function shouldShowPermissionSuggestion(projectPath = process.cwd()) {
38328
38354
  const prefs = await loadPermissionPreferences();
38329
38355
  if (prefs.recommendedAllowlistDismissed) {
38330
38356
  return false;
@@ -38332,6 +38358,10 @@ async function shouldShowPermissionSuggestion() {
38332
38358
  if (prefs.recommendedAllowlistApplied) {
38333
38359
  return false;
38334
38360
  }
38361
+ const projectKey = getProjectPreferenceKey(projectPath);
38362
+ if (prefs.recommendedAllowlistPromptedProjects?.[projectKey]) {
38363
+ return false;
38364
+ }
38335
38365
  return true;
38336
38366
  }
38337
38367
  async function applyRecommendedPermissions() {
@@ -38340,7 +38370,8 @@ async function applyRecommendedPermissions() {
38340
38370
  }
38341
38371
  await savePermissionPreference("recommendedAllowlistApplied", true);
38342
38372
  }
38343
- async function showPermissionSuggestion() {
38373
+ async function showPermissionSuggestion(projectPath = process.cwd()) {
38374
+ await markPermissionSuggestionShownForProject(projectPath);
38344
38375
  console.log();
38345
38376
  console.log(chalk.magenta.bold(" \u{1F4CB} Recommended Permissions"));
38346
38377
  console.log();
@@ -47396,6 +47427,27 @@ init_registry4();
47396
47427
  init_registry();
47397
47428
  init_config_loader();
47398
47429
  init_lifecycle();
47430
+ init_tools();
47431
+ async function loadConfiguredServers(projectPath) {
47432
+ const registry = new MCPRegistryImpl();
47433
+ await registry.load();
47434
+ const resolvedProjectPath = projectPath || process.cwd();
47435
+ return mergeMCPConfigs(
47436
+ registry.listServers(),
47437
+ await loadMCPServersFromCOCOConfig(),
47438
+ await loadProjectMCPFile(resolvedProjectPath)
47439
+ );
47440
+ }
47441
+ function findConfiguredServer(servers, requestedServer) {
47442
+ const normalized = requestedServer.trim().toLowerCase();
47443
+ return servers.find((server) => {
47444
+ const name = server.name.toLowerCase();
47445
+ if (name === normalized) return true;
47446
+ if (name.includes(normalized) || normalized.includes(name)) return true;
47447
+ if (name.includes("atlassian") && /^(atlassian|jira|confluence)$/.test(normalized)) return true;
47448
+ return false;
47449
+ });
47450
+ }
47399
47451
  var mcpListServersTool = defineTool({
47400
47452
  name: "mcp_list_servers",
47401
47453
  description: `Inspect Coco's MCP configuration and current runtime connections.
@@ -47409,14 +47461,9 @@ when you need to know which MCP servers are configured, connected, healthy, or w
47409
47461
  projectPath: z.string().optional().describe("Project path whose .mcp.json should be merged. Defaults to process.cwd()")
47410
47462
  }),
47411
47463
  async execute({ includeDisabled, includeTools, projectPath }) {
47412
- const registry = new MCPRegistryImpl();
47413
- await registry.load();
47414
- const resolvedProjectPath = projectPath || process.cwd();
47415
- const configuredServers = mergeMCPConfigs(
47416
- registry.listServers(),
47417
- await loadMCPServersFromCOCOConfig(),
47418
- await loadProjectMCPFile(resolvedProjectPath)
47419
- ).filter((server) => includeDisabled || server.enabled !== false);
47464
+ const configuredServers = (await loadConfiguredServers(projectPath)).filter(
47465
+ (server) => includeDisabled || server.enabled !== false
47466
+ );
47420
47467
  const manager = getMCPServerManager();
47421
47468
  const servers = [];
47422
47469
  for (const server of configuredServers) {
@@ -47447,7 +47494,59 @@ when you need to know which MCP servers are configured, connected, healthy, or w
47447
47494
  };
47448
47495
  }
47449
47496
  });
47450
- var mcpTools = [mcpListServersTool];
47497
+ var mcpConnectServerTool = defineTool({
47498
+ name: "mcp_connect_server",
47499
+ description: `Connect or reconnect a configured MCP server in the current Coco session.
47500
+
47501
+ Use this when mcp_list_servers shows a service as configured but disconnected, or when
47502
+ the user explicitly asks you to use a specific MCP service. This tool can trigger the
47503
+ built-in MCP OAuth browser login flow. Do not ask the user for raw tokens when this exists.`,
47504
+ category: "config",
47505
+ parameters: z.object({
47506
+ server: z.string().describe("Configured MCP server name, or a common alias like 'jira' or 'atlassian'"),
47507
+ includeTools: z.boolean().optional().default(true).describe("Include discovered MCP tool names after connecting"),
47508
+ projectPath: z.string().optional().describe("Project path whose .mcp.json should be merged. Defaults to process.cwd()")
47509
+ }),
47510
+ async execute({ server, includeTools, projectPath }) {
47511
+ const configuredServers = await loadConfiguredServers(projectPath);
47512
+ const target = findConfiguredServer(
47513
+ configuredServers.filter((configuredServer) => configuredServer.enabled !== false),
47514
+ server
47515
+ );
47516
+ if (!target) {
47517
+ throw new Error(`MCP server '${server}' is not configured`);
47518
+ }
47519
+ const manager = getMCPServerManager();
47520
+ const existingConnection = manager.getConnection(target.name);
47521
+ if (existingConnection && existingConnection.healthy === false) {
47522
+ await manager.stopServer(target.name);
47523
+ }
47524
+ const connection = await manager.startServer(target);
47525
+ const toolRegistry = getAgentToolRegistry();
47526
+ if (toolRegistry) {
47527
+ await registerMCPTools(toolRegistry, connection.name, connection.client);
47528
+ }
47529
+ let tools;
47530
+ if (includeTools) {
47531
+ try {
47532
+ const listed = await connection.client.listTools();
47533
+ tools = listed.tools.map((tool) => tool.name);
47534
+ } catch {
47535
+ tools = [];
47536
+ }
47537
+ }
47538
+ return {
47539
+ requestedServer: server,
47540
+ connected: true,
47541
+ healthy: true,
47542
+ toolCount: connection.toolCount,
47543
+ ...includeTools ? { tools: tools ?? [] } : {},
47544
+ authTriggered: target.transport === "http",
47545
+ message: `MCP server '${target.name}' is connected for this session.`
47546
+ };
47547
+ }
47548
+ });
47549
+ var mcpTools = [mcpListServersTool, mcpConnectServerTool];
47451
47550
 
47452
47551
  // src/tools/index.ts
47453
47552
  init_registry4();
@@ -53064,8 +53163,8 @@ async function startRepl(options = {}) {
53064
53163
  ]);
53065
53164
  session.projectContext = projectContext;
53066
53165
  await initializeSessionMemory(session);
53067
- if (await shouldShowPermissionSuggestion()) {
53068
- await showPermissionSuggestion();
53166
+ if (await shouldShowPermissionSuggestion(projectPath)) {
53167
+ await showPermissionSuggestion(projectPath);
53069
53168
  const updatedTrust = await loadTrustedTools(projectPath);
53070
53169
  for (const tool of updatedTrust) {
53071
53170
  session.trustedTools.add(tool);
@@ -53169,7 +53268,11 @@ async function startRepl(options = {}) {
53169
53268
  );
53170
53269
  }
53171
53270
  async function ensureRequestedMcpConnections(message) {
53172
- if (!mcpManager || configuredMcpServers.length === 0) return;
53271
+ if (configuredMcpServers.length === 0) return;
53272
+ if (!mcpManager) {
53273
+ const { getMCPServerManager: getMCPServerManager2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
53274
+ mcpManager = getMCPServerManager2();
53275
+ }
53173
53276
  const normalizedMessage = message.toLowerCase();
53174
53277
  const explicitlyRequestsMcp = /\bmcp\b/.test(normalizedMessage) || /\b(use|using|usa|usar|utiliza|utilizar)\b.{0,24}\bmcp\b/.test(normalizedMessage);
53175
53278
  const matchingServers = configuredMcpServers.filter((server) => {
@@ -53184,13 +53287,13 @@ async function startRepl(options = {}) {
53184
53287
  });
53185
53288
  for (const server of matchingServers) {
53186
53289
  try {
53290
+ const existingConnection = mcpManager.getConnection(server.name);
53291
+ if (existingConnection?.healthy === false) {
53292
+ await mcpManager.stopServer(server.name);
53293
+ }
53187
53294
  const connection = await mcpManager.startServer(server);
53188
53295
  if (!registeredMcpServers.has(connection.name)) {
53189
- await (await Promise.resolve().then(() => (init_tools(), tools_exports))).registerMCPTools(
53190
- toolRegistry,
53191
- connection.name,
53192
- connection.client
53193
- );
53296
+ await (await Promise.resolve().then(() => (init_tools(), tools_exports))).registerMCPTools(toolRegistry, connection.name, connection.client);
53194
53297
  registeredMcpServers.add(connection.name);
53195
53298
  }
53196
53299
  } catch (error) {