@poncho-ai/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-W7J5XM2X.js";
4
+ } from "./chunk-HDKWXGXT.js";
5
5
  import "./chunk-3BEWSRFW.js";
6
6
 
7
7
  // src/cli.ts
package/dist/index.d.ts CHANGED
@@ -37,11 +37,17 @@ declare const mcpAdd: (workingDir: string, options: {
37
37
  url?: string;
38
38
  name?: string;
39
39
  envVars?: string[];
40
+ authBearerEnv?: string;
40
41
  }) => Promise<void>;
41
42
  declare const mcpList: (workingDir: string) => Promise<void>;
42
43
  declare const mcpRemove: (workingDir: string, name: string) => Promise<void>;
44
+ declare const mcpToolsList: (workingDir: string, serverName: string) => Promise<void>;
45
+ declare const mcpToolsSelect: (workingDir: string, serverName: string, options: {
46
+ all?: boolean;
47
+ toolsCsv?: string;
48
+ }) => Promise<void>;
43
49
  declare const buildCli: () => Command;
44
50
  declare const main: (argv?: string[]) => Promise<void>;
45
51
  declare const packageRoot: string;
46
52
 
47
- export { type RequestHandler, addSkill, buildCli, buildTarget, createRequestHandler, initProject, listTools, main, mcpAdd, mcpList, mcpRemove, packageRoot, runInteractive, runOnce, runTests, startDevServer, updateAgentGuidance };
53
+ export { type RequestHandler, addSkill, buildCli, buildTarget, createRequestHandler, initProject, listTools, main, mcpAdd, mcpList, mcpRemove, mcpToolsList, mcpToolsSelect, packageRoot, runInteractive, runOnce, runTests, startDevServer, updateAgentGuidance };
package/dist/index.js CHANGED
@@ -9,13 +9,15 @@ import {
9
9
  mcpAdd,
10
10
  mcpList,
11
11
  mcpRemove,
12
+ mcpToolsList,
13
+ mcpToolsSelect,
12
14
  packageRoot,
13
15
  runInteractive,
14
16
  runOnce,
15
17
  runTests,
16
18
  startDevServer,
17
19
  updateAgentGuidance
18
- } from "./chunk-W7J5XM2X.js";
20
+ } from "./chunk-HDKWXGXT.js";
19
21
  import "./chunk-3BEWSRFW.js";
20
22
  export {
21
23
  addSkill,
@@ -28,6 +30,8 @@ export {
28
30
  mcpAdd,
29
31
  mcpList,
30
32
  mcpRemove,
33
+ mcpToolsList,
34
+ mcpToolsSelect,
31
35
  packageRoot,
32
36
  runInteractive,
33
37
  runOnce,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for building and deploying AI agents",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "react": "^19.2.4",
26
26
  "react-devtools-core": "^6.1.5",
27
27
  "yaml": "^2.8.1",
28
- "@poncho-ai/harness": "0.2.0",
28
+ "@poncho-ai/harness": "0.3.0",
29
29
  "@poncho-ai/sdk": "0.2.0"
30
30
  },
31
31
  "devDependencies": {
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ import { createRequire } from "node:module";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import {
14
14
  AgentHarness,
15
+ LocalMcpBridge,
15
16
  TelemetryEmitter,
16
17
  createConversationStore,
17
18
  loadPonchoConfig,
@@ -36,6 +37,7 @@ import {
36
37
  setCookie,
37
38
  verifyPassphrase,
38
39
  } from "./web-ui.js";
40
+ import { createInterface } from "node:readline/promises";
39
41
  import {
40
42
  runInitOnboarding,
41
43
  type InitOnboardingOptions,
@@ -299,17 +301,48 @@ Connect remote MCP servers and expose their tools to the agent:
299
301
 
300
302
  \`\`\`bash
301
303
  # Add remote MCP server
302
- poncho mcp add --url wss://mcp.example.com/github --name github --env GITHUB_TOKEN
304
+ poncho mcp add --url https://mcp.example.com/github --name github --auth-bearer-env GITHUB_TOKEN
303
305
 
304
306
  # List configured servers
305
307
  poncho mcp list
306
308
 
309
+ # Discover and select MCP tools into config allowlist
310
+ poncho mcp tools list github
311
+ poncho mcp tools select github
312
+
307
313
  # Remove a server
308
314
  poncho mcp remove github
309
315
  \`\`\`
310
316
 
311
317
  Set required secrets in \`.env\` (for example, \`GITHUB_TOKEN=...\`).
312
318
 
319
+ ## Tool Intent in Frontmatter
320
+
321
+ Declare tool intent directly in \`AGENT.md\` and \`SKILL.md\` frontmatter:
322
+
323
+ \`\`\`yaml
324
+ tools:
325
+ mcp:
326
+ - github/list_issues
327
+ - github/*
328
+ scripts:
329
+ - starter/scripts/*
330
+ \`\`\`
331
+
332
+ How it works:
333
+
334
+ - \`AGENT.md\` provides fallback MCP intent when no skill is active.
335
+ - \`SKILL.md\` intent applies when you activate that skill (\`activate_skill\`).
336
+ - Skill scripts are accessible by default from each skill's \`scripts/\` directory.
337
+ - \`AGENT.md\` \`tools.scripts\` can still be used to narrow script access when active skills do not set script intent.
338
+ - Active skills are unioned, then filtered by policy in \`poncho.config.js\`.
339
+ - Deactivating a skill (\`deactivate_skill\`) removes its MCP tools from runtime registration.
340
+
341
+ Pattern format is strict slash-only:
342
+
343
+ - MCP: \`server/tool\`, \`server/*\`
344
+ - Scripts: \`skill/scripts/file.ts\`, \`skill/scripts/*\`
345
+
313
346
  ## Configuration
314
347
 
315
348
  Core files:
@@ -335,6 +368,21 @@ export default {
335
368
  telemetry: {
336
369
  enabled: true,
337
370
  },
371
+ mcp: [
372
+ {
373
+ name: "github",
374
+ url: "https://mcp.example.com/github",
375
+ auth: { type: "bearer", tokenEnv: "GITHUB_TOKEN" },
376
+ tools: {
377
+ mode: "allowlist",
378
+ include: ["github/list_issues", "github/get_issue"],
379
+ },
380
+ },
381
+ ],
382
+ scripts: {
383
+ mode: "allowlist",
384
+ include: ["starter/scripts/*"],
385
+ },
338
386
  tools: {
339
387
  defaults: {
340
388
  list_directory: true,
@@ -396,7 +444,6 @@ const VERCEL_RUNTIME_DEPENDENCIES: Record<string, string> = {
396
444
  mustache: "^4.2.0",
397
445
  openai: "^6.3.0",
398
446
  redis: "^5.10.0",
399
- ws: "^8.18.0",
400
447
  yaml: "^2.8.1",
401
448
  };
402
449
  const TEST_TEMPLATE = `tests:
@@ -548,6 +595,52 @@ const writeConfigFile = async (workingDir: string, config: PonchoConfig): Promis
548
595
  await writeFile(resolve(workingDir, "poncho.config.js"), serialized, "utf8");
549
596
  };
550
597
 
598
+ const ensureEnvPlaceholder = async (filePath: string, key: string): Promise<boolean> => {
599
+ const normalizedKey = key.trim();
600
+ if (!normalizedKey) {
601
+ return false;
602
+ }
603
+ let content = "";
604
+ try {
605
+ content = await readFile(filePath, "utf8");
606
+ } catch {
607
+ await writeFile(filePath, `${normalizedKey}=\n`, "utf8");
608
+ return true;
609
+ }
610
+ const present = content
611
+ .split(/\r?\n/)
612
+ .some((line) => line.trimStart().startsWith(`${normalizedKey}=`));
613
+ if (present) {
614
+ return false;
615
+ }
616
+ const withTrailingNewline = content.length === 0 || content.endsWith("\n")
617
+ ? content
618
+ : `${content}\n`;
619
+ await writeFile(filePath, `${withTrailingNewline}${normalizedKey}=\n`, "utf8");
620
+ return true;
621
+ };
622
+
623
+ const removeEnvPlaceholder = async (filePath: string, key: string): Promise<boolean> => {
624
+ const normalizedKey = key.trim();
625
+ if (!normalizedKey) {
626
+ return false;
627
+ }
628
+ let content = "";
629
+ try {
630
+ content = await readFile(filePath, "utf8");
631
+ } catch {
632
+ return false;
633
+ }
634
+ const lines = content.split(/\r?\n/);
635
+ const filtered = lines.filter((line) => !line.trimStart().startsWith(`${normalizedKey}=`));
636
+ if (filtered.length === lines.length) {
637
+ return false;
638
+ }
639
+ const nextContent = filtered.join("\n").replace(/\n+$/, "");
640
+ await writeFile(filePath, nextContent.length > 0 ? `${nextContent}\n` : "", "utf8");
641
+ return true;
642
+ };
643
+
551
644
  const gitInit = (cwd: string): Promise<boolean> =>
552
645
  new Promise((resolve) => {
553
646
  const child = spawn("git", ["init"], { cwd, stdio: "ignore" });
@@ -1588,6 +1681,7 @@ export const mcpAdd = async (
1588
1681
  url?: string;
1589
1682
  name?: string;
1590
1683
  envVars?: string[];
1684
+ authBearerEnv?: string;
1591
1685
  },
1592
1686
  ): Promise<void> => {
1593
1687
  const config = (await loadPonchoConfig(workingDir)) ?? { mcp: [] };
@@ -1595,14 +1689,56 @@ export const mcpAdd = async (
1595
1689
  if (!options.url) {
1596
1690
  throw new Error("Remote MCP only: provide --url for a remote MCP server.");
1597
1691
  }
1692
+ if (options.url.startsWith("ws://") || options.url.startsWith("wss://")) {
1693
+ throw new Error("WebSocket MCP URLs are no longer supported. Use an HTTP MCP endpoint.");
1694
+ }
1695
+ if (!options.url.startsWith("http://") && !options.url.startsWith("https://")) {
1696
+ throw new Error("Invalid MCP URL. Expected http:// or https://.");
1697
+ }
1698
+ const serverName = options.name ?? normalizeMcpName({ url: options.url });
1598
1699
  mcp.push({
1599
- name: options.name ?? normalizeMcpName({ url: options.url }),
1700
+ name: serverName,
1600
1701
  url: options.url,
1601
1702
  env: options.envVars ?? [],
1703
+ auth: options.authBearerEnv
1704
+ ? {
1705
+ type: "bearer",
1706
+ tokenEnv: options.authBearerEnv,
1707
+ }
1708
+ : undefined,
1602
1709
  });
1603
1710
 
1604
1711
  await writeConfigFile(workingDir, { ...config, mcp });
1605
- process.stdout.write("MCP server added.\n");
1712
+ let envSeedMessage: string | undefined;
1713
+ if (options.authBearerEnv) {
1714
+ const envPath = resolve(workingDir, ".env");
1715
+ const envExamplePath = resolve(workingDir, ".env.example");
1716
+ const addedEnv = await ensureEnvPlaceholder(envPath, options.authBearerEnv);
1717
+ const addedEnvExample = await ensureEnvPlaceholder(envExamplePath, options.authBearerEnv);
1718
+ if (addedEnv || addedEnvExample) {
1719
+ envSeedMessage = `Added ${options.authBearerEnv}= to ${addedEnv ? ".env" : ""}${addedEnv && addedEnvExample ? " and " : ""}${addedEnvExample ? ".env.example" : ""}.`;
1720
+ }
1721
+ }
1722
+ const nextSteps: string[] = [];
1723
+ let step = 1;
1724
+ if (options.authBearerEnv) {
1725
+ nextSteps.push(` ${step}) Set token in .env: ${options.authBearerEnv}=...`);
1726
+ step += 1;
1727
+ }
1728
+ nextSteps.push(` ${step}) Discover tools: poncho mcp tools list ${serverName}`);
1729
+ step += 1;
1730
+ nextSteps.push(` ${step}) Select tools: poncho mcp tools select ${serverName}`);
1731
+ step += 1;
1732
+ nextSteps.push(` ${step}) Verify config: poncho mcp list`);
1733
+ process.stdout.write(
1734
+ [
1735
+ `MCP server added: ${serverName}`,
1736
+ ...(envSeedMessage ? [envSeedMessage] : []),
1737
+ "Next steps:",
1738
+ ...nextSteps,
1739
+ "",
1740
+ ].join("\n"),
1741
+ );
1606
1742
  };
1607
1743
 
1608
1744
  export const mcpList = async (workingDir: string): Promise<void> => {
@@ -1610,20 +1746,204 @@ export const mcpList = async (workingDir: string): Promise<void> => {
1610
1746
  const mcp = config?.mcp ?? [];
1611
1747
  if (mcp.length === 0) {
1612
1748
  process.stdout.write("No MCP servers configured.\n");
1749
+ if (config?.scripts) {
1750
+ process.stdout.write(
1751
+ `Script policy: mode=${config.scripts.mode ?? "all"} include=${config.scripts.include?.length ?? 0} exclude=${config.scripts.exclude?.length ?? 0}\n`,
1752
+ );
1753
+ }
1613
1754
  return;
1614
1755
  }
1615
1756
  process.stdout.write("Configured MCP servers:\n");
1616
1757
  for (const entry of mcp) {
1617
- process.stdout.write(`- ${entry.name ?? entry.url} (remote: ${entry.url})\n`);
1758
+ const auth =
1759
+ entry.auth?.type === "bearer" ? `auth=bearer:${entry.auth.tokenEnv}` : "auth=none";
1760
+ const mode = entry.tools?.mode ?? "all";
1761
+ process.stdout.write(
1762
+ `- ${entry.name ?? entry.url} (remote: ${entry.url}, ${auth}, mode=${mode})\n`,
1763
+ );
1764
+ }
1765
+ if (config?.scripts) {
1766
+ process.stdout.write(
1767
+ `Script policy: mode=${config.scripts.mode ?? "all"} include=${config.scripts.include?.length ?? 0} exclude=${config.scripts.exclude?.length ?? 0}\n`,
1768
+ );
1618
1769
  }
1619
1770
  };
1620
1771
 
1621
1772
  export const mcpRemove = async (workingDir: string, name: string): Promise<void> => {
1622
1773
  const config = (await loadPonchoConfig(workingDir)) ?? { mcp: [] };
1623
1774
  const before = config.mcp ?? [];
1775
+ const removed = before.filter((entry) => normalizeMcpName(entry) === name);
1624
1776
  const filtered = before.filter((entry) => normalizeMcpName(entry) !== name);
1625
1777
  await writeConfigFile(workingDir, { ...config, mcp: filtered });
1778
+ const removedTokenEnvNames = new Set(
1779
+ removed
1780
+ .map((entry) =>
1781
+ entry.auth?.type === "bearer" ? entry.auth.tokenEnv?.trim() ?? "" : "",
1782
+ )
1783
+ .filter((value) => value.length > 0),
1784
+ );
1785
+ const stillUsedTokenEnvNames = new Set(
1786
+ filtered
1787
+ .map((entry) =>
1788
+ entry.auth?.type === "bearer" ? entry.auth.tokenEnv?.trim() ?? "" : "",
1789
+ )
1790
+ .filter((value) => value.length > 0),
1791
+ );
1792
+ const removedFromExample: string[] = [];
1793
+ for (const tokenEnv of removedTokenEnvNames) {
1794
+ if (stillUsedTokenEnvNames.has(tokenEnv)) {
1795
+ continue;
1796
+ }
1797
+ const changed = await removeEnvPlaceholder(resolve(workingDir, ".env.example"), tokenEnv);
1798
+ if (changed) {
1799
+ removedFromExample.push(tokenEnv);
1800
+ }
1801
+ }
1626
1802
  process.stdout.write(`Removed MCP server: ${name}\n`);
1803
+ if (removedFromExample.length > 0) {
1804
+ process.stdout.write(
1805
+ `Removed unused token placeholder(s) from .env.example: ${removedFromExample.join(", ")}\n`,
1806
+ );
1807
+ }
1808
+ };
1809
+
1810
+ const resolveMcpEntry = async (
1811
+ workingDir: string,
1812
+ serverName: string,
1813
+ ): Promise<{ config: PonchoConfig; index: number }> => {
1814
+ const config = (await loadPonchoConfig(workingDir)) ?? { mcp: [] };
1815
+ const entries = config.mcp ?? [];
1816
+ const index = entries.findIndex((entry) => normalizeMcpName(entry) === serverName);
1817
+ if (index < 0) {
1818
+ throw new Error(`MCP server "${serverName}" is not configured.`);
1819
+ }
1820
+ return { config, index };
1821
+ };
1822
+
1823
+ const discoverMcpTools = async (
1824
+ workingDir: string,
1825
+ serverName: string,
1826
+ ): Promise<string[]> => {
1827
+ dotenv.config({ path: resolve(workingDir, ".env") });
1828
+ const { config, index } = await resolveMcpEntry(workingDir, serverName);
1829
+ const entry = (config.mcp ?? [])[index];
1830
+ const bridge = new LocalMcpBridge({ mcp: [entry] });
1831
+ try {
1832
+ await bridge.startLocalServers();
1833
+ await bridge.discoverTools();
1834
+ return bridge.listDiscoveredTools(normalizeMcpName(entry));
1835
+ } finally {
1836
+ await bridge.stopLocalServers();
1837
+ }
1838
+ };
1839
+
1840
+ export const mcpToolsList = async (
1841
+ workingDir: string,
1842
+ serverName: string,
1843
+ ): Promise<void> => {
1844
+ const discovered = await discoverMcpTools(workingDir, serverName);
1845
+ if (discovered.length === 0) {
1846
+ process.stdout.write(`No tools discovered for MCP server "${serverName}".\n`);
1847
+ return;
1848
+ }
1849
+ process.stdout.write(`Discovered tools for "${serverName}":\n`);
1850
+ for (const tool of discovered) {
1851
+ process.stdout.write(`- ${tool}\n`);
1852
+ }
1853
+ };
1854
+
1855
+ export const mcpToolsSelect = async (
1856
+ workingDir: string,
1857
+ serverName: string,
1858
+ options: {
1859
+ all?: boolean;
1860
+ toolsCsv?: string;
1861
+ },
1862
+ ): Promise<void> => {
1863
+ const discovered = await discoverMcpTools(workingDir, serverName);
1864
+ if (discovered.length === 0) {
1865
+ process.stdout.write(`No tools discovered for MCP server "${serverName}".\n`);
1866
+ return;
1867
+ }
1868
+ let selected: string[] = [];
1869
+ if (options.all) {
1870
+ selected = [...discovered];
1871
+ } else if (options.toolsCsv && options.toolsCsv.trim().length > 0) {
1872
+ const requested = options.toolsCsv
1873
+ .split(",")
1874
+ .map((part) => part.trim())
1875
+ .filter((part) => part.length > 0);
1876
+ selected = discovered.filter((tool) => requested.includes(tool));
1877
+ } else {
1878
+ process.stdout.write(`Discovered tools for "${serverName}":\n`);
1879
+ discovered.forEach((tool, idx) => {
1880
+ process.stdout.write(`${idx + 1}. ${tool}\n`);
1881
+ });
1882
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1883
+ const answer = await rl.question(
1884
+ "Enter comma-separated tool numbers/names to allow (or * for all): ",
1885
+ );
1886
+ rl.close();
1887
+ const raw = answer.trim();
1888
+ if (raw === "*") {
1889
+ selected = [...discovered];
1890
+ } else {
1891
+ const tokens = raw
1892
+ .split(",")
1893
+ .map((part) => part.trim())
1894
+ .filter((part) => part.length > 0);
1895
+ const fromIndex = tokens
1896
+ .map((token) => Number.parseInt(token, 10))
1897
+ .filter((value) => !Number.isNaN(value))
1898
+ .map((index) => discovered[index - 1])
1899
+ .filter((value): value is string => typeof value === "string");
1900
+ const byName = discovered.filter((tool) => tokens.includes(tool));
1901
+ selected = [...new Set([...fromIndex, ...byName])];
1902
+ }
1903
+ }
1904
+ if (selected.length === 0) {
1905
+ throw new Error("No valid tools selected.");
1906
+ }
1907
+ const includePatterns =
1908
+ selected.length === discovered.length
1909
+ ? [`${serverName}/*`]
1910
+ : selected.sort();
1911
+ const { config, index } = await resolveMcpEntry(workingDir, serverName);
1912
+ const mcp = [...(config.mcp ?? [])];
1913
+ const existing = mcp[index];
1914
+ mcp[index] = {
1915
+ ...existing,
1916
+ tools: {
1917
+ ...(existing.tools ?? {}),
1918
+ mode: "allowlist",
1919
+ include: includePatterns,
1920
+ },
1921
+ };
1922
+ await writeConfigFile(workingDir, { ...config, mcp });
1923
+ process.stdout.write(
1924
+ `Updated ${serverName} to allowlist ${includePatterns.join(", ")} in poncho.config.js.\n`,
1925
+ );
1926
+ process.stdout.write(
1927
+ "\nRequired next step: add MCP intent in AGENT.md or SKILL.md. Without this, these MCP tools will not be registered for the model.\n",
1928
+ );
1929
+ process.stdout.write(
1930
+ "\nOption A: AGENT.md (global fallback intent)\n" +
1931
+ "Paste this into AGENT.md frontmatter:\n" +
1932
+ "---\n" +
1933
+ "tools:\n" +
1934
+ " mcp:\n" +
1935
+ includePatterns.map((tool) => ` - ${tool}`).join("\n") +
1936
+ "\n---\n",
1937
+ );
1938
+ process.stdout.write(
1939
+ "\nOption B: SKILL.md (only when that skill is activated)\n" +
1940
+ "Paste this into SKILL.md frontmatter:\n" +
1941
+ "---\n" +
1942
+ "tools:\n" +
1943
+ " mcp:\n" +
1944
+ includePatterns.map((tool) => ` - ${tool}`).join("\n") +
1945
+ "\n---\n",
1946
+ );
1627
1947
  };
1628
1948
 
1629
1949
  export const buildCli = (): Command => {
@@ -1739,6 +2059,10 @@ export const buildCli = (): Command => {
1739
2059
  .command("add")
1740
2060
  .requiredOption("--url <url>", "remote MCP url")
1741
2061
  .option("--name <name>", "server name")
2062
+ .option(
2063
+ "--auth-bearer-env <name>",
2064
+ "env var name containing bearer token for this MCP server",
2065
+ )
1742
2066
  .option("--env <name>", "env variable (repeatable)", (value, all: string[]) => {
1743
2067
  all.push(value);
1744
2068
  return all;
@@ -1748,6 +2072,7 @@ export const buildCli = (): Command => {
1748
2072
  options: {
1749
2073
  url?: string;
1750
2074
  name?: string;
2075
+ authBearerEnv?: string;
1751
2076
  env: string[];
1752
2077
  },
1753
2078
  ) => {
@@ -1755,6 +2080,7 @@ export const buildCli = (): Command => {
1755
2080
  url: options.url,
1756
2081
  name: options.name,
1757
2082
  envVars: options.env,
2083
+ authBearerEnv: options.authBearerEnv,
1758
2084
  });
1759
2085
  },
1760
2086
  );
@@ -1774,6 +2100,39 @@ export const buildCli = (): Command => {
1774
2100
  await mcpRemove(process.cwd(), name);
1775
2101
  });
1776
2102
 
2103
+ const mcpToolsCommand = mcpCommand
2104
+ .command("tools")
2105
+ .description("Discover and curate tools for a configured MCP server");
2106
+
2107
+ mcpToolsCommand
2108
+ .command("list")
2109
+ .argument("<name>", "server name")
2110
+ .description("Discover and list tools from a configured MCP server")
2111
+ .action(async (name: string) => {
2112
+ await mcpToolsList(process.cwd(), name);
2113
+ });
2114
+
2115
+ mcpToolsCommand
2116
+ .command("select")
2117
+ .argument("<name>", "server name")
2118
+ .description("Select MCP tools and store as config allowlist")
2119
+ .option("--all", "select all discovered tools", false)
2120
+ .option("--tools <csv>", "comma-separated discovered tool names")
2121
+ .action(
2122
+ async (
2123
+ name: string,
2124
+ options: {
2125
+ all: boolean;
2126
+ tools?: string;
2127
+ },
2128
+ ) => {
2129
+ await mcpToolsSelect(process.cwd(), name, {
2130
+ all: options.all,
2131
+ toolsCsv: options.tools,
2132
+ });
2133
+ },
2134
+ );
2135
+
1777
2136
  return program;
1778
2137
  };
1779
2138
 
package/test/cli.test.ts CHANGED
@@ -482,7 +482,7 @@ describe("cli", () => {
482
482
  const projectDir = join(tempDir, "aux-agent");
483
483
 
484
484
  await listTools(projectDir);
485
- await mcpAdd(projectDir, { url: "wss://example.com/mcp", name: "remote-mcp" });
485
+ await mcpAdd(projectDir, { url: "https://example.com/mcp", name: "remote-mcp" });
486
486
  await mcpList(projectDir);
487
487
  await mcpRemove(projectDir, "remote-mcp");
488
488
 
@@ -525,6 +525,20 @@ describe("cli", () => {
525
525
  expect(result.failed).toBe(0);
526
526
  });
527
527
 
528
+ it("seeds bearer token placeholders in env files when adding mcp auth", async () => {
529
+ await initProject("mcp-env-seed-agent", { workingDir: tempDir });
530
+ const projectDir = join(tempDir, "mcp-env-seed-agent");
531
+ await mcpAdd(projectDir, {
532
+ url: "https://example.com/mcp",
533
+ name: "remote-mcp",
534
+ authBearerEnv: "LINEAR_TOKEN",
535
+ });
536
+ const envFile = await readFile(join(projectDir, ".env"), "utf8");
537
+ const envExampleFile = await readFile(join(projectDir, ".env.example"), "utf8");
538
+ expect(envFile).toContain("LINEAR_TOKEN=");
539
+ expect(envExampleFile).toContain("LINEAR_TOKEN=");
540
+ });
541
+
528
542
  it("does not modify AGENT.md when no deprecated embedded guidance exists", async () => {
529
543
  const projectDir = join(tempDir, "legacy-agent");
530
544
  await mkdir(projectDir, { recursive: true });