{"version":3,"file":"mcp-config-C1K-IzzB.mjs","names":[],"sources":["../src/mcp-config.ts"],"sourcesContent":["// Pi MCP configuration -> DSH mcp-client patch entries.\n//\n// Pi (via pi-mcp-adapter) reads the standard `mcpServers` files. DSH ships an\n// official MCP client (`@deepseek-ai/dsh-mcp-client`) configured through\n// cordis patch layers — so migration is config translation, not running Pi's\n// adapter code. This module reads Pi's documented six-layer precedence,\n// resolves `disabled` flags, and emits one patch entry per enabled server.\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { getAgentDir } from './compat/vendor/pi-config-shim.js'\n\ntype UnknownRecord = Record<string, unknown>\n\nexport interface PiMcpServer {\n  name: string\n  sourcePath: string\n  transport: 'stdio' | 'streamable-http'\n  command?: string\n  args?: string[]\n  env?: Record<string, string>\n  cwd?: string\n  url?: string\n  headers?: Record<string, string>\n  disabled: boolean\n}\n\nexport interface McpConversionWarning {\n  server: string\n  message: string\n}\n\nexport interface McpConversionResult {\n  servers: PiMcpServer[]\n  entries: UnknownRecord[]\n  warnings: McpConversionWarning[]\n  sources: string[]\n}\n\n/** Pi's documented config precedence, lowest to highest. */\nexport function piMcpConfigPaths(cwd: string): string[] {\n  const home = homedir()\n  return [\n    join(home, '.config', 'mcp', 'mcp.json'),\n    join(home, '.agents', 'mcp.json'),\n    join(home, '.agents', 'mcp', 'mcp.json'),\n    join(getAgentDir(), 'mcp.json'),\n    join(cwd, '.mcp.json'),\n    join(cwd, '.pi', 'mcp.json'),\n  ]\n}\n\nfunction stringRecord(value: unknown): Record<string, string> | undefined {\n  if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined\n  const entries = Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string')\n  return entries.length === 0 ? undefined : Object.fromEntries(entries)\n}\n\nfunction parseServer(name: string, raw: unknown, sourcePath: string): PiMcpServer | undefined {\n  if (typeof raw !== 'object' || raw === null) return undefined\n  const record = raw as UnknownRecord\n  const disabled = record.disabled === true\n  if (typeof record.url === 'string') {\n    return {\n      name, sourcePath, transport: 'streamable-http', url: record.url,\n      ...(stringRecord(record.headers) === undefined ? {} : { headers: stringRecord(record.headers)! }),\n      disabled,\n    }\n  }\n  if (typeof record.command === 'string') {\n    return {\n      name, sourcePath, transport: 'stdio', command: record.command,\n      ...(Array.isArray(record.args) ? { args: record.args.map(String) } : {}),\n      ...(stringRecord(record.env) === undefined ? {} : { env: stringRecord(record.env)! }),\n      ...(typeof record.cwd === 'string' ? { cwd: record.cwd } : {}),\n      disabled,\n    }\n  }\n  return undefined\n}\n\nexport function collectPiMcpServers(cwd: string, extraPaths: string[] = []): { servers: Map<string, PiMcpServer>; sources: string[] } {\n  const servers = new Map<string, PiMcpServer>()\n  const sources: string[] = []\n  for (const path of [...piMcpConfigPaths(cwd), ...extraPaths]) {\n    if (!existsSync(path)) continue\n    let parsed: unknown\n    try {\n      parsed = JSON.parse(readFileSync(path, 'utf8'))\n    } catch {\n      continue\n    }\n    const mcpServers = (parsed as UnknownRecord | undefined)?.mcpServers\n    if (typeof mcpServers !== 'object' || mcpServers === null) continue\n    sources.push(path)\n    for (const [name, raw] of Object.entries(mcpServers)) {\n      const record = raw as UnknownRecord | null\n      const existing = servers.get(name)\n      // Later (higher-precedence) layers override whole entries; a bare\n      // `disabled` toggle updates the effective server from a lower layer.\n      // The toggle keeps the DEFINING file's sourcePath — a project-local\n      // disable of a global server must not reclassify it as project-defined\n      // (the Settings view groups by where the definition lives).\n      if (existing !== undefined && typeof record === 'object' && record !== null\n        && record.command === undefined && record.url === undefined && typeof record.disabled === 'boolean') {\n        servers.set(name, { ...existing, disabled: record.disabled })\n        continue\n      }\n      const server = parseServer(name, raw, path)\n      if (server !== undefined) servers.set(name, server)\n    }\n  }\n  return { servers, sources }\n}\n\nconst SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]{1,32}$/u\nconst ENV_REFERENCE_PATTERN = /^\\$\\{?([A-Za-z_][A-Za-z0-9_]*)\\}?$/u\nconst SECRETISH_KEY_PATTERN = /token|secret|key|password|credential/iu\n\ninterface JsExpression {\n  __pi2dshJs: string\n}\n\nfunction js(expression: string): JsExpression {\n  return { __pi2dshJs: expression }\n}\n\nfunction convertValueMap(\n  server: string,\n  kind: 'env' | 'headers',\n  values: Record<string, string> | undefined,\n  warnings: McpConversionWarning[],\n): Record<string, string | JsExpression> | undefined {\n  if (values === undefined) return undefined\n  const output: Record<string, string | JsExpression> = {}\n  for (const [key, value] of Object.entries(values)) {\n    const reference = ENV_REFERENCE_PATTERN.exec(value)\n    if (reference !== null) {\n      output[key] = js(`process.env.${reference[1]}`)\n      continue\n    }\n    if (SECRETISH_KEY_PATTERN.test(key)) {\n      warnings.push({\n        server,\n        message: `${kind}.${key} carries a literal value; move the secret to an environment variable and reference it as $NAME so the patch stays credential-free`,\n      })\n    }\n    output[key] = value\n  }\n  return output\n}\n\nexport function convertPiMcpConfig(cwd: string, extraPaths: string[] = []): McpConversionResult {\n  const { servers, sources } = collectPiMcpServers(cwd, extraPaths)\n  const warnings: McpConversionWarning[] = []\n  const entries: UnknownRecord[] = []\n  for (const server of servers.values()) {\n    if (server.disabled) {\n      warnings.push({ server: server.name, message: 'skipped: disabled in Pi configuration' })\n      continue\n    }\n    if (!SERVER_NAME_PATTERN.test(server.name)) {\n      warnings.push({\n        server: server.name,\n        message: 'skipped: DSH serverName must match [A-Za-z0-9_-]{1,32}; rename the server in the Pi config',\n      })\n      continue\n    }\n    const config: UnknownRecord = { serverName: server.name, transport: server.transport }\n    if (server.transport === 'stdio') {\n      config.command = server.command\n      if (server.args !== undefined) config.args = server.args\n      const env = convertValueMap(server.name, 'env', server.env, warnings)\n      if (env !== undefined) config.env = env\n      if (server.cwd !== undefined) config.cwd = server.cwd\n    } else {\n      config.url = server.url\n      const headers = convertValueMap(server.name, 'headers', server.headers, warnings)\n      if (headers !== undefined) config.headers = headers\n    }\n    entries.push({ id: `mcp-${server.name}`, name: '@deepseek-ai/dsh-mcp-client', config })\n  }\n  return { servers: [...servers.values()], entries, warnings, sources }\n}\n\nfunction yamlScalar(value: unknown, indent: string): string {\n  if (typeof value === 'object' && value !== null && '__pi2dshJs' in (value as JsExpression)) {\n    return `!!js ${(value as JsExpression).__pi2dshJs}`\n  }\n  if (typeof value === 'string') {\n    return /^[A-Za-z0-9@._\\/:-]+$/u.test(value) ? value : JSON.stringify(value)\n  }\n  if (Array.isArray(value)) {\n    if (value.length === 0) return '[]'\n    return `\\n${value.map(item => `${indent}  - ${yamlScalar(item, `${indent}  `)}`).join('\\n')}`\n  }\n  if (typeof value === 'object' && value !== null) {\n    const record = value as UnknownRecord\n    const keys = Object.keys(record)\n    if (keys.length === 0) return '{}'\n    return `\\n${keys.map(key => `${indent}  ${key}: ${yamlScalar(record[key], `${indent}  `)}`).join('\\n')}`\n  }\n  return String(value)\n}\n\n/** Render the converted entries as a cordis.patch.yml `insert` block. */\nexport function renderMcpPatch(result: McpConversionResult): string {\n  if (result.entries.length === 0) return '# no enabled MCP servers found in Pi configuration\\n'\n  const lines: string[] = ['- insert:']\n  for (const entry of result.entries) {\n    lines.push(`    - id: ${String(entry.id)}`)\n    lines.push(`      name: '@deepseek-ai/dsh-mcp-client'`)\n    lines.push(`      inject: [tools]`)\n    lines.push(`      config:${yamlScalar(entry.config, '      ')}`)\n  }\n  return `${lines.join('\\n')}\\n`\n}\n"],"mappings":"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"}