@sideboard-ai/core 0.1.311 → 0.1.313

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.
@@ -3,7 +3,7 @@ import {
3
3
  brightsyMcpServerName,
4
4
  ensureCliTeamTracked,
5
5
  ensureConnectedBrightsyTeamTokens
6
- } from "./chunk-VEVFHO6K.js";
6
+ } from "./chunk-HH3VVTEC.js";
7
7
  import {
8
8
  looksLikeConversationSummary,
9
9
  looksLikeSkillBody,
@@ -21,7 +21,7 @@ import {
21
21
  } from "./chunk-BIPXOHZI.js";
22
22
  import {
23
23
  loadBrightsyConfig
24
- } from "./chunk-33KDPEK4.js";
24
+ } from "./chunk-RSA47QYR.js";
25
25
  import {
26
26
  httpFetch
27
27
  } from "./chunk-JAT3JFOS.js";
@@ -513,6 +513,9 @@ var brightsyAdapter = {
513
513
 
514
514
  // src/agents/claude.ts
515
515
  import { existsSync as existsSync7 } from "fs";
516
+
517
+ // src/mcp-ui/host.ts
518
+ import { createHash } from "crypto";
516
519
  import { homedir as homedir4 } from "os";
517
520
 
518
521
  // src/agents/orch-mcp-isolation.ts
@@ -608,18 +611,18 @@ function mcpUiContentSecurityPolicy(csp) {
608
611
  const connect = (csp?.connectDomains ?? []).join(" ");
609
612
  const frames = csp?.frameDomains?.length ? csp.frameDomains.join(" ") : "'none'";
610
613
  const base = csp?.baseUriDomains?.length ? csp.baseUriDomains.join(" ") : "'none'";
614
+ const connectSrc = connect ? `'self' ${connect}`.trim() : "'none'";
611
615
  return [
612
616
  "default-src 'none'",
613
- `script-src 'self' 'unsafe-inline' 'unsafe-eval' ${resource}`.trim(),
617
+ `script-src 'self' 'unsafe-inline' ${resource}`.trim(),
614
618
  `style-src 'self' 'unsafe-inline' ${resource}`.trim(),
615
- `img-src 'self' data: blob: ${resource}`.trim(),
616
- `font-src 'self' data: ${resource}`.trim(),
617
- `connect-src 'self' ${connect}`.trim(),
618
- `media-src 'self' data: blob: ${resource}`.trim(),
619
+ `img-src 'self' data: ${resource}`.trim(),
620
+ `font-src 'self' ${resource}`.trim(),
621
+ `connect-src ${connectSrc}`,
622
+ `media-src 'self' data: ${resource}`.trim(),
619
623
  `frame-src ${frames}`,
620
624
  `base-uri ${base}`,
621
- "object-src 'none'",
622
- "form-action 'self'"
625
+ "object-src 'none'"
623
626
  ].join("; ");
624
627
  }
625
628
  function htmlText(text, blob) {
@@ -656,8 +659,10 @@ function originList(value) {
656
659
  return out;
657
660
  }
658
661
  function originToken(value) {
662
+ const trimmed = value.trim();
663
+ if (/^(https?|wss?):\/\/\*\.[a-z0-9.-]+(?::\d+)?$/i.test(trimmed)) return trimmed;
659
664
  try {
660
- const url = new URL(value.trim());
665
+ const url = new URL(trimmed);
661
666
  if (url.protocol !== "https:" && url.protocol !== "http:" && url.protocol !== "wss:" && url.protocol !== "ws:") {
662
667
  return null;
663
668
  }
@@ -1493,6 +1498,497 @@ function toCodexUnattendedAppsArgs() {
1493
1498
  ];
1494
1499
  }
1495
1500
 
1501
+ // src/mcp-ui/proxy-http.ts
1502
+ import { randomBytes, randomUUID } from "crypto";
1503
+ import { createServer } from "http";
1504
+ import { Server as Server2 } from "@modelcontextprotocol/sdk/server/index.js";
1505
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1506
+
1507
+ // src/mcp-ui/proxy-server.ts
1508
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1509
+ import {
1510
+ CallToolRequestSchema,
1511
+ GetPromptRequestSchema,
1512
+ ListPromptsRequestSchema,
1513
+ ListResourcesRequestSchema,
1514
+ ListResourceTemplatesRequestSchema,
1515
+ ListToolsRequestSchema,
1516
+ ReadResourceRequestSchema
1517
+ } from "@modelcontextprotocol/sdk/types.js";
1518
+
1519
+ // src/mcp-ui/visibility.ts
1520
+ function visibilityFromMeta(meta) {
1521
+ const rec = asRecord2(meta);
1522
+ const ui = asRecord2(rec.ui);
1523
+ const raw = ui.visibility;
1524
+ if (!Array.isArray(raw)) return ["model", "app"];
1525
+ const out = [];
1526
+ for (const item of raw) {
1527
+ if (item !== "model" && item !== "app") continue;
1528
+ if (!out.includes(item)) out.push(item);
1529
+ }
1530
+ return out.length ? out : ["model", "app"];
1531
+ }
1532
+ function toolVisibleTo(visibility, audience) {
1533
+ const vis = visibility?.length ? visibility : ["model", "app"];
1534
+ return vis.includes(audience);
1535
+ }
1536
+ function asRecord2(value) {
1537
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
1538
+ return value;
1539
+ }
1540
+
1541
+ // src/mcp-ui/proxy-server.ts
1542
+ function attachMcpUiProxyHandlers(server, session) {
1543
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
1544
+ const tools = await session.listTools();
1545
+ return { tools: tools.filter((tool) => toolVisibleTo(tool.visibility, "model")).map(listedTool) };
1546
+ });
1547
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1548
+ const name = request.params.name;
1549
+ const tools = await session.listTools();
1550
+ const tool = tools.find((item) => item.name === name);
1551
+ if (!tool || !toolVisibleTo(tool.visibility, "model")) {
1552
+ throw new Error(`Tool ${name} is not available to the model`);
1553
+ }
1554
+ return await session.callTool(name, request.params.arguments ?? {});
1555
+ });
1556
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1557
+ const resource = await session.readResource(request.params.uri);
1558
+ if (!resource) throw new Error(`Resource ${request.params.uri} was empty`);
1559
+ return {
1560
+ contents: [
1561
+ {
1562
+ uri: resource.uri,
1563
+ ...resource.mimeType ? { mimeType: resource.mimeType } : {},
1564
+ ...resource.text != null ? { text: resource.text } : {},
1565
+ ...resource.blob != null ? { blob: resource.blob } : {}
1566
+ }
1567
+ ]
1568
+ };
1569
+ });
1570
+ server.setRequestHandler(ListResourcesRequestSchema, async () => session.listResources());
1571
+ server.setRequestHandler(
1572
+ ListResourceTemplatesRequestSchema,
1573
+ async () => session.listResourceTemplates()
1574
+ );
1575
+ server.setRequestHandler(ListPromptsRequestSchema, async () => session.listPrompts());
1576
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1577
+ return await session.getPrompt(request.params.name, request.params.arguments);
1578
+ });
1579
+ server.fallbackRequestHandler = async (request) => {
1580
+ return await session.forward(request.method, request.params);
1581
+ };
1582
+ }
1583
+ function listedTool(tool) {
1584
+ return {
1585
+ name: tool.name,
1586
+ ...tool.description ? { description: tool.description } : {},
1587
+ inputSchema: tool.inputSchema ?? { type: "object", properties: {} },
1588
+ _meta: {
1589
+ ui: {
1590
+ ...tool.resourceUri ? { resourceUri: tool.resourceUri } : {},
1591
+ visibility: tool.visibility
1592
+ }
1593
+ }
1594
+ };
1595
+ }
1596
+
1597
+ // src/mcp-ui/proxy-http.ts
1598
+ async function listenMcpUiProxy(opts) {
1599
+ const token = randomBytes(24).toString("hex");
1600
+ const transports = /* @__PURE__ */ new Map();
1601
+ const httpServer = createServer((req, res) => {
1602
+ void handle(req, res);
1603
+ });
1604
+ await new Promise((resolve2, reject) => {
1605
+ httpServer.once("error", reject);
1606
+ httpServer.listen(0, "127.0.0.1", () => resolve2());
1607
+ });
1608
+ const addr = httpServer.address();
1609
+ if (!addr || typeof addr === "string") throw new Error("mcp-ui proxy failed to bind");
1610
+ const origin = `http://127.0.0.1:${addr.port}`;
1611
+ async function handle(req, res) {
1612
+ if (req.headers.authorization !== `Bearer ${token}`) {
1613
+ res.writeHead(401).end();
1614
+ return;
1615
+ }
1616
+ const target = parseMcpUiProxyPath(req.url);
1617
+ if (!target) {
1618
+ res.writeHead(404).end();
1619
+ return;
1620
+ }
1621
+ try {
1622
+ const session = await opts.sessionFor(target.server, target.dirsKey);
1623
+ if (!session) {
1624
+ res.writeHead(404).end();
1625
+ return;
1626
+ }
1627
+ const header = req.headers["mcp-session-id"];
1628
+ const sid = typeof header === "string" ? header : "";
1629
+ const sessionKey = `${target.server}:${target.dirsKey}`;
1630
+ let transport = sid ? transports.get(`${sessionKey}:${sid}`) : void 0;
1631
+ if (!transport) {
1632
+ if (req.method !== "POST") {
1633
+ res.writeHead(400).end();
1634
+ return;
1635
+ }
1636
+ const created = new StreamableHTTPServerTransport({
1637
+ sessionIdGenerator: () => randomUUID(),
1638
+ allowedHosts: ["127.0.0.1", "localhost"],
1639
+ onsessioninitialized: (id) => {
1640
+ transports.set(`${sessionKey}:${id}`, created);
1641
+ },
1642
+ onsessionclosed: (id) => {
1643
+ transports.delete(`${sessionKey}:${id}`);
1644
+ }
1645
+ });
1646
+ transport = created;
1647
+ const mcp = new Server2(
1648
+ { name: `sideboard-ui-proxy-${target.server}`, version: "1.0.0" },
1649
+ { capabilities: session.capabilities() }
1650
+ );
1651
+ attachMcpUiProxyHandlers(mcp, session);
1652
+ await mcp.connect(created);
1653
+ }
1654
+ await transport.handleRequest(req, res);
1655
+ } catch {
1656
+ if (!res.headersSent) res.writeHead(500).end();
1657
+ }
1658
+ }
1659
+ return {
1660
+ urlFor(server, dirsKey) {
1661
+ const base = `${origin}/s/${encodeURIComponent(server)}`;
1662
+ return dirsKey ? `${base}/d/${dirsKey}` : base;
1663
+ },
1664
+ headers: { Authorization: `Bearer ${token}` },
1665
+ async close() {
1666
+ await Promise.all([...transports.values()].map((item) => item.close().catch(() => void 0)));
1667
+ transports.clear();
1668
+ await new Promise((resolve2) => httpServer.close(() => resolve2()));
1669
+ }
1670
+ };
1671
+ }
1672
+ function parseMcpUiProxyPath(url) {
1673
+ const path = (url ?? "").split("?")[0] ?? "";
1674
+ if (!path.startsWith("/s/")) return null;
1675
+ const rest = path.slice(3).replace(/\/$/, "");
1676
+ const split = rest.indexOf("/d/");
1677
+ try {
1678
+ if (split < 0) {
1679
+ const server2 = decodeURIComponent(rest).trim();
1680
+ return server2 ? { server: server2, dirsKey: "" } : null;
1681
+ }
1682
+ const server = decodeURIComponent(rest.slice(0, split)).trim();
1683
+ const dirsKey = rest.slice(split + 3);
1684
+ if (!server || !/^[a-f0-9]{16}$/.test(dirsKey)) return null;
1685
+ return { server, dirsKey };
1686
+ } catch {
1687
+ return null;
1688
+ }
1689
+ }
1690
+
1691
+ // src/mcp-ui/session.ts
1692
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1693
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
1694
+ import { StdioClientTransport, getDefaultEnvironment } from "@modelcontextprotocol/sdk/client/stdio.js";
1695
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1696
+ import { ResultSchema } from "@modelcontextprotocol/sdk/types.js";
1697
+ var REQUEST_TIMEOUT_MS = 2e4;
1698
+ async function orEmpty(run2, fallback) {
1699
+ try {
1700
+ return await run2();
1701
+ } catch {
1702
+ return fallback;
1703
+ }
1704
+ }
1705
+ var UI_CAPABILITIES = {
1706
+ extensions: {
1707
+ "io.modelcontextprotocol/ui": {
1708
+ mimeTypes: ["text/html;profile=mcp-app"]
1709
+ }
1710
+ }
1711
+ };
1712
+ async function openMcpUiSession(launch) {
1713
+ return openMcpUiSessionFromTransport(transportForLaunch(launch));
1714
+ }
1715
+ async function openMcpUiSessionFromTransport(transport) {
1716
+ const client = new Client(
1717
+ { name: "sideboard", version: "1.0.0" },
1718
+ { capabilities: UI_CAPABILITIES }
1719
+ );
1720
+ await client.connect(transport, { timeout: REQUEST_TIMEOUT_MS });
1721
+ return {
1722
+ async listTools() {
1723
+ const listed = await client.listTools(void 0, { timeout: REQUEST_TIMEOUT_MS });
1724
+ return listed.tools.map((tool) => {
1725
+ const meta = tool._meta;
1726
+ return {
1727
+ name: tool.name,
1728
+ ...tool.description ? { description: tool.description } : {},
1729
+ ...tool.inputSchema ? { inputSchema: tool.inputSchema } : {},
1730
+ resourceUri: uiResourceUriFromMeta(meta),
1731
+ visibility: visibilityFromMeta(meta),
1732
+ ...cspFromMeta(meta) ? { csp: cspFromMeta(meta) } : {}
1733
+ };
1734
+ });
1735
+ },
1736
+ listResources: () => orEmpty(
1737
+ () => client.listResources(void 0, { timeout: REQUEST_TIMEOUT_MS }),
1738
+ { resources: [] }
1739
+ ),
1740
+ listResourceTemplates: () => orEmpty(
1741
+ () => client.listResourceTemplates(void 0, { timeout: REQUEST_TIMEOUT_MS }),
1742
+ { resourceTemplates: [] }
1743
+ ),
1744
+ listPrompts: () => orEmpty(
1745
+ () => client.listPrompts(void 0, { timeout: REQUEST_TIMEOUT_MS }),
1746
+ { prompts: [] }
1747
+ ),
1748
+ getPrompt(name, args) {
1749
+ return client.getPrompt({ name, arguments: args }, { timeout: REQUEST_TIMEOUT_MS });
1750
+ },
1751
+ async readResource(uri) {
1752
+ const result = await client.readResource({ uri }, { timeout: REQUEST_TIMEOUT_MS });
1753
+ const content = result.contents?.[0];
1754
+ if (!content) return null;
1755
+ const text = "text" in content && typeof content.text === "string" ? content.text : void 0;
1756
+ const blob = "blob" in content && typeof content.blob === "string" ? content.blob : void 0;
1757
+ return {
1758
+ uri: content.uri,
1759
+ ...content.mimeType ? { mimeType: content.mimeType } : {},
1760
+ ...text != null ? { text } : {},
1761
+ ...blob != null ? { blob } : {},
1762
+ ...cspFromMeta(content._meta) ? { csp: cspFromMeta(content._meta) } : {}
1763
+ };
1764
+ },
1765
+ async callTool(name, args) {
1766
+ return client.callTool({ name, arguments: args }, void 0, { timeout: REQUEST_TIMEOUT_MS });
1767
+ },
1768
+ capabilities() {
1769
+ const caps = client.getServerCapabilities() ?? {};
1770
+ return {
1771
+ tools: caps.tools ?? {},
1772
+ resources: caps.resources ?? {},
1773
+ prompts: caps.prompts ?? {},
1774
+ completions: caps.completions ?? {},
1775
+ ...caps.logging ? { logging: caps.logging } : {}
1776
+ };
1777
+ },
1778
+ forward(method, params) {
1779
+ return client.request(
1780
+ { method, params },
1781
+ ResultSchema,
1782
+ { timeout: REQUEST_TIMEOUT_MS }
1783
+ );
1784
+ },
1785
+ async close() {
1786
+ await client.close();
1787
+ }
1788
+ };
1789
+ }
1790
+ function transportForLaunch(launch) {
1791
+ if (launch.kind === "stdio") {
1792
+ return new StdioClientTransport({
1793
+ command: launch.command,
1794
+ args: launch.args,
1795
+ env: { ...getDefaultEnvironment(), ...launch.env },
1796
+ stderr: "ignore",
1797
+ ...launch.cwd ? { cwd: launch.cwd } : {}
1798
+ });
1799
+ }
1800
+ const url = new URL(launch.url);
1801
+ const requestInit = launch.headers ? { headers: launch.headers } : void 0;
1802
+ if (launch.kind === "sse") {
1803
+ return new SSEClientTransport(url, requestInit ? { requestInit } : void 0);
1804
+ }
1805
+ return new StreamableHTTPClientTransport(url, requestInit ? { requestInit } : void 0);
1806
+ }
1807
+
1808
+ // src/mcp-ui/host.ts
1809
+ var TOOL_TTL_MS = 5 * 60 * 1e3;
1810
+ var McpUiHost = class {
1811
+ constructor(opts) {
1812
+ this.opts = opts;
1813
+ }
1814
+ opts;
1815
+ sessions = /* @__PURE__ */ new Map();
1816
+ toolLists = /* @__PURE__ */ new Map();
1817
+ proxy;
1818
+ proxyDirs = /* @__PURE__ */ new Map();
1819
+ async viewForClaudeTool(toolName, projectDirs) {
1820
+ const entries = this.userEntries(projectDirs);
1821
+ const match = matchClaudeMcpTool(toolName, Object.keys(entries));
1822
+ if (!match) return null;
1823
+ const launch = parseMcpServerLaunch(entries[match.server] ?? {});
1824
+ if (!launch) return null;
1825
+ try {
1826
+ const session = await this.sessionFor(match.server, launch);
1827
+ const tools = await this.toolsFor(match.server, launch, session);
1828
+ const tool = tools.find((item) => item.name === match.tool);
1829
+ if (!tool?.resourceUri) return null;
1830
+ const resource = await session.readResource(tool.resourceUri);
1831
+ if (!resource) return null;
1832
+ const html = htmlFromResourceContent(resource);
1833
+ if (!html) return null;
1834
+ const csp = resource.csp ?? tool.csp;
1835
+ return {
1836
+ server: match.server,
1837
+ tool: match.tool,
1838
+ uri: html.uri,
1839
+ mimeType: html.mimeType,
1840
+ html: html.html,
1841
+ csp: mcpUiContentSecurityPolicy(csp)
1842
+ };
1843
+ } catch (err) {
1844
+ console.error(
1845
+ `[mcp-ui] ${match.server}/${match.tool}:`,
1846
+ err instanceof Error ? err.message : err
1847
+ );
1848
+ return null;
1849
+ }
1850
+ }
1851
+ /**
1852
+ * Point Claude at loopback HTTP so it shares this host's upstream session
1853
+ * and never sees `visibility: ["app"]` tools.
1854
+ */
1855
+ async proxyEntries(entries, projectDirs) {
1856
+ const names = Object.keys(entries).filter((name) => name.trim());
1857
+ if (names.length === 0) return {};
1858
+ const dirsKey = dirsKeyFor(projectDirs);
1859
+ this.proxyDirs.set(dirsKey, projectDirs);
1860
+ try {
1861
+ const listen = await this.ensureProxy();
1862
+ const out = {};
1863
+ for (const name of names) {
1864
+ out[name] = {
1865
+ type: "http",
1866
+ url: listen.urlFor(name, dirsKey),
1867
+ headers: listen.headers
1868
+ };
1869
+ }
1870
+ return out;
1871
+ } catch (err) {
1872
+ console.error("[mcp-ui] proxy:", err instanceof Error ? err.message : err);
1873
+ return { ...entries };
1874
+ }
1875
+ }
1876
+ async callTool(server, tool, args, projectDirs) {
1877
+ const entry = this.userEntries(projectDirs)[server];
1878
+ if (!entry) throw new Error(`MCP server ${server} is not available`);
1879
+ const launch = parseMcpServerLaunch(entry);
1880
+ if (!launch) throw new Error(`MCP server ${server} is not available`);
1881
+ const session = await this.sessionFor(server, launch);
1882
+ const tools = await this.toolsFor(server, launch, session);
1883
+ const info = tools.find((item) => item.name === tool);
1884
+ if (!info || !toolVisibleTo(info.visibility, "app")) {
1885
+ throw new Error(`Tool ${tool} is not available to the app`);
1886
+ }
1887
+ return session.callTool(tool, args);
1888
+ }
1889
+ async readResource(server, uri, projectDirs) {
1890
+ const session = await this.sessionForName(server, projectDirs);
1891
+ if (!session) throw new Error(`MCP server ${server} is not available`);
1892
+ const resource = await session.readResource(uri);
1893
+ if (!resource) throw new Error(`Resource ${uri} was empty`);
1894
+ return {
1895
+ contents: [
1896
+ {
1897
+ uri: resource.uri,
1898
+ ...resource.mimeType ? { mimeType: resource.mimeType } : {},
1899
+ ...resource.text != null ? { text: resource.text } : {},
1900
+ ...resource.blob != null ? { blob: resource.blob } : {}
1901
+ }
1902
+ ]
1903
+ };
1904
+ }
1905
+ async closeAll() {
1906
+ const pending = [...this.sessions.values()];
1907
+ const proxy = this.proxy;
1908
+ this.sessions.clear();
1909
+ this.toolLists.clear();
1910
+ this.proxyDirs.clear();
1911
+ this.proxy = void 0;
1912
+ await Promise.all([
1913
+ ...pending.map((session) => session.then((open) => open.close()).catch(() => void 0)),
1914
+ proxy ? proxy.then((listen) => listen.close()).catch(() => void 0) : Promise.resolve()
1915
+ ]);
1916
+ }
1917
+ ensureProxy() {
1918
+ if (this.proxy) return this.proxy;
1919
+ this.proxy = listenMcpUiProxy({
1920
+ sessionFor: (server, dirsKey) => {
1921
+ if (dirsKey && !this.proxyDirs.has(dirsKey)) return Promise.resolve(null);
1922
+ return this.sessionForName(server, this.proxyDirs.get(dirsKey));
1923
+ }
1924
+ }).catch((err) => {
1925
+ this.proxy = void 0;
1926
+ throw err;
1927
+ });
1928
+ return this.proxy;
1929
+ }
1930
+ userEntries(projectDirs) {
1931
+ const out = {};
1932
+ for (const [name, entry] of Object.entries(this.opts.entries(projectDirs))) {
1933
+ if (!name.trim() || isInjectedOrchMcpName(name) || !entry) continue;
1934
+ out[name] = entry;
1935
+ }
1936
+ return out;
1937
+ }
1938
+ async sessionForName(server, projectDirs) {
1939
+ const entry = this.userEntries(projectDirs)[server];
1940
+ if (!entry) return null;
1941
+ const launch = parseMcpServerLaunch(entry);
1942
+ if (!launch) return null;
1943
+ return this.sessionFor(server, launch);
1944
+ }
1945
+ sessionFor(server, launch) {
1946
+ const key = `${server}
1947
+ ${JSON.stringify(launch)}`;
1948
+ const existing = this.sessions.get(key);
1949
+ if (existing) return existing;
1950
+ const open = this.opts.openSession ?? openMcpUiSession;
1951
+ const pending = open(launch).catch((err) => {
1952
+ this.sessions.delete(key);
1953
+ throw err;
1954
+ });
1955
+ this.sessions.set(key, pending);
1956
+ return pending;
1957
+ }
1958
+ async toolsFor(server, launch, session) {
1959
+ const key = `${server}
1960
+ ${JSON.stringify(launch)}`;
1961
+ const cached = this.toolLists.get(key);
1962
+ if (cached && cached.key === key && Date.now() - cached.at < TOOL_TTL_MS) return cached.tools;
1963
+ const tools = await session.listTools();
1964
+ this.toolLists.set(key, { at: Date.now(), key, tools });
1965
+ return tools;
1966
+ }
1967
+ };
1968
+ var singleton;
1969
+ function getMcpUiHost() {
1970
+ if (!singleton) {
1971
+ singleton = new McpUiHost({
1972
+ entries: (projectDirs) => listUserClaudeMcpServerEntries(homedir4(), projectDirs)
1973
+ });
1974
+ }
1975
+ return singleton;
1976
+ }
1977
+ async function proxyClaudeUserMcp(orchestrator, projectDirs) {
1978
+ if (orchestrator) return {};
1979
+ const dirs = projectDirs.filter((d) => typeof d === "string" && d.length > 0);
1980
+ return getMcpUiHost().proxyEntries(listUserClaudeMcpServerEntries(homedir4(), dirs), dirs);
1981
+ }
1982
+ function closeMcpUiHost() {
1983
+ const host = singleton;
1984
+ singleton = void 0;
1985
+ if (host) void host.closeAll();
1986
+ }
1987
+ function dirsKeyFor(projectDirs) {
1988
+ if (!projectDirs?.length) return "";
1989
+ return createHash("sha256").update(projectDirs.join("\0")).digest("hex").slice(0, 16);
1990
+ }
1991
+
1496
1992
  // src/agents/types.ts
1497
1993
  var PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or Approves / Hands off the plan). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any project files except via Sideboard MCP present_plan (writes .context/attachments/plan.md). When you need a clarifying decision (approach forks, auth choice, scope) \u2014 not greetings, check-ins, or an invented task menu: (1) first write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it) \u2014 do not leave the user staring at bare labels; (2) then call Sideboard MCP ask_user with the same options, including a description on every option. If one option is the obvious default, proceed without asking. Sideboard shows questions in the composer and mirrors them in chat. After ask_user, wait for the user's next message with their answers before finalizing the plan. When the plan is ready for approval: (1) call present_plan with the full markdown plan (title + content) so Sideboard saves .context/attachments/plan.md and shows it in chat for Approve / Hand off / Copy; (2) Claude should also call ExitPlanMode after present_plan. Do not skip present_plan \u2014 the plan must be a markdown file, not only chat prose.";
1498
1994
  var PLAN_MODE_OFF_INSTRUCTION = "Plan mode is off. Do not call present_plan or ExitPlanMode \u2014 those only work when the user turns Plan on in the composer. If you need to propose an approach, write it in chat.";
@@ -2063,7 +2559,7 @@ var claudeAdapter = {
2063
2559
  });
2064
2560
  const injectedBrightsyNames = injectedServers.filter((s) => s.name === "brightsy" || s.name.startsWith("brightsy_")).map((s) => s.name);
2065
2561
  const chromeOn = claudeChromeEnabled();
2066
- const userMcpEntries = isOrchestrator ? {} : listUserClaudeMcpServerEntries(homedir4(), [thread.repoPath, thread.worktreePath]);
2562
+ const userMcpEntries = await proxyClaudeUserMcp(isOrchestrator, [thread.repoPath, thread.worktreePath]);
2067
2563
  let allowedTools;
2068
2564
  if (isOrchestrator) {
2069
2565
  allowedTools = [
@@ -2401,7 +2897,7 @@ function unwrapCodexMcpResult(result) {
2401
2897
  return String(result);
2402
2898
  }
2403
2899
  }
2404
- function asRecord2(value) {
2900
+ function asRecord3(value) {
2405
2901
  if (value && typeof value === "object" && !Array.isArray(value)) {
2406
2902
  return value;
2407
2903
  }
@@ -2554,7 +3050,7 @@ var codexAdapter = {
2554
3050
  }
2555
3051
  if (itemType === "mcp_tool_call" && item.id && item.server && item.tool) {
2556
3052
  const name = `mcp__${item.server}__${item.tool}`;
2557
- const input = asRecord2(item.arguments);
3053
+ const input = asRecord3(item.arguments);
2558
3054
  const events = [
2559
3055
  { type: "tool_use", id: item.id, name, input }
2560
3056
  ];
@@ -3039,7 +3535,7 @@ var FALLBACK_OPENCODE_MODELS = [
3039
3535
  ];
3040
3536
  var cachedOpencodeModels = null;
3041
3537
  var OPENCODE_MODEL_CACHE_MS = 5 * 60 * 1e3;
3042
- function asRecord3(value) {
3538
+ function asRecord4(value) {
3043
3539
  if (value && typeof value === "object" && !Array.isArray(value)) {
3044
3540
  return value;
3045
3541
  }
@@ -3210,7 +3706,7 @@ var opencodeAdapter = {
3210
3706
  const name = part?.tool ?? part?.name ?? obj.tool ?? obj.name ?? "tool";
3211
3707
  const state = part?.state;
3212
3708
  const input = (state?.input && typeof state.input === "object" ? state.input : void 0) ?? part?.input ?? obj.input;
3213
- const metadata = asRecord3(state?.metadata);
3709
+ const metadata = asRecord4(state?.metadata);
3214
3710
  const id = opencodeToolId(
3215
3711
  name,
3216
3712
  part?.callID ?? part?.id ?? obj.id,
@@ -3245,7 +3741,7 @@ var opencodeAdapter = {
3245
3741
  }
3246
3742
  if (obj.type === "subtask_event") {
3247
3743
  const parentId = str(obj.childSessionID) ?? str(obj.childSessionId);
3248
- const part = asRecord3(obj.part);
3744
+ const part = asRecord4(obj.part);
3249
3745
  if (!parentId || !part) return null;
3250
3746
  if (part.type === "text" && str(part.text)) {
3251
3747
  return withTaskParent(
@@ -3256,8 +3752,8 @@ var opencodeAdapter = {
3256
3752
  if (part.type === "tool" || str(part.tool)) {
3257
3753
  const name = str(part.tool) ?? str(part.name) ?? "tool";
3258
3754
  const id = str(part.callID) ?? str(part.id) ?? `${parentId}-${name}`;
3259
- const state = asRecord3(part.state);
3260
- const input = asRecord3(state?.input) ?? asRecord3(part.input);
3755
+ const state = asRecord4(part.state);
3756
+ const input = asRecord4(state?.input) ?? asRecord4(part.input);
3261
3757
  const nested = [
3262
3758
  withEventParentId({ type: "tool_use", id, name, input }, parentId)
3263
3759
  ];
@@ -3976,11 +4472,6 @@ export {
3976
4472
  adjustBrightsyTurnExit,
3977
4473
  listBrightsyChatTargets,
3978
4474
  brightsyAdapter,
3979
- matchClaudeMcpTool,
3980
- parseMcpServerLaunch,
3981
- uiResourceUriFromMeta,
3982
- cspFromMeta,
3983
- htmlFromResourceContent,
3984
4475
  mcpUiContentSecurityPolicy,
3985
4476
  SIDEBOARD_MCP_PROFILE_ENV,
3986
4477
  SIDEBOARD_THREAD_ID_ENV,
@@ -4005,7 +4496,8 @@ export {
4005
4496
  registerPackagedUserMcpClients,
4006
4497
  isInjectedOrchMcpName,
4007
4498
  userMcpNamesToDisable,
4008
- listUserClaudeMcpServerEntries,
4499
+ getMcpUiHost,
4500
+ closeMcpUiHost,
4009
4501
  PLAN_MODE_INSTRUCTION,
4010
4502
  PLAN_MODE_OFF_INSTRUCTION,
4011
4503
  planModeTurnInstruction,