@agntk/agent-harness 0.1.1

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.
Files changed (212) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +41 -0
  3. package/README.md +445 -0
  4. package/defaults/agents/summarizer.md +49 -0
  5. package/defaults/instincts/lead-with-answer.md +24 -0
  6. package/defaults/instincts/qualify-before-recommending.md +40 -0
  7. package/defaults/instincts/read-before-edit.md +23 -0
  8. package/defaults/instincts/search-before-create.md +23 -0
  9. package/defaults/playbooks/ship-feature.md +31 -0
  10. package/defaults/rules/ask-before-assuming.md +35 -0
  11. package/defaults/rules/operations.md +35 -0
  12. package/defaults/rules/respect-the-user.md +39 -0
  13. package/defaults/skills/business-analyst.md +181 -0
  14. package/defaults/skills/content-marketer.md +184 -0
  15. package/defaults/skills/research.md +34 -0
  16. package/defaults/tools/example-web-search.md +60 -0
  17. package/defaults/workflows/daily-reflection.md +54 -0
  18. package/dist/agent-framework-K4GUIICH.js +344 -0
  19. package/dist/agent-framework-K4GUIICH.js.map +1 -0
  20. package/dist/analytics-RPT73WNM.js +12 -0
  21. package/dist/analytics-RPT73WNM.js.map +1 -0
  22. package/dist/auto-processor-OLE45UI3.js +13 -0
  23. package/dist/auto-processor-OLE45UI3.js.map +1 -0
  24. package/dist/chunk-274RV3YO.js +162 -0
  25. package/dist/chunk-274RV3YO.js.map +1 -0
  26. package/dist/chunk-4CWAGBNS.js +168 -0
  27. package/dist/chunk-4CWAGBNS.js.map +1 -0
  28. package/dist/chunk-4FDUOGSZ.js +69 -0
  29. package/dist/chunk-4FDUOGSZ.js.map +1 -0
  30. package/dist/chunk-5H34JPMB.js +199 -0
  31. package/dist/chunk-5H34JPMB.js.map +1 -0
  32. package/dist/chunk-6EMOEYGU.js +102 -0
  33. package/dist/chunk-6EMOEYGU.js.map +1 -0
  34. package/dist/chunk-A7BJPQQ6.js +236 -0
  35. package/dist/chunk-A7BJPQQ6.js.map +1 -0
  36. package/dist/chunk-AGAAFJEO.js +76 -0
  37. package/dist/chunk-AGAAFJEO.js.map +1 -0
  38. package/dist/chunk-BSKDOFRT.js +65 -0
  39. package/dist/chunk-BSKDOFRT.js.map +1 -0
  40. package/dist/chunk-CHJ5GNZC.js +100 -0
  41. package/dist/chunk-CHJ5GNZC.js.map +1 -0
  42. package/dist/chunk-CSL3ERUI.js +307 -0
  43. package/dist/chunk-CSL3ERUI.js.map +1 -0
  44. package/dist/chunk-DA7IKHC4.js +229 -0
  45. package/dist/chunk-DA7IKHC4.js.map +1 -0
  46. package/dist/chunk-DGUM43GV.js +11 -0
  47. package/dist/chunk-DGUM43GV.js.map +1 -0
  48. package/dist/chunk-DTTXPHFW.js +211 -0
  49. package/dist/chunk-DTTXPHFW.js.map +1 -0
  50. package/dist/chunk-FD55B3IO.js +204 -0
  51. package/dist/chunk-FD55B3IO.js.map +1 -0
  52. package/dist/chunk-FLZU44SV.js +230 -0
  53. package/dist/chunk-FLZU44SV.js.map +1 -0
  54. package/dist/chunk-GJNNR2RA.js +200 -0
  55. package/dist/chunk-GJNNR2RA.js.map +1 -0
  56. package/dist/chunk-GNUSHD2Y.js +111 -0
  57. package/dist/chunk-GNUSHD2Y.js.map +1 -0
  58. package/dist/chunk-GUJTBGVS.js +2212 -0
  59. package/dist/chunk-GUJTBGVS.js.map +1 -0
  60. package/dist/chunk-IZ6UZ3ZL.js +207 -0
  61. package/dist/chunk-IZ6UZ3ZL.js.map +1 -0
  62. package/dist/chunk-JKMGYWXB.js +197 -0
  63. package/dist/chunk-JKMGYWXB.js.map +1 -0
  64. package/dist/chunk-KFX54TQM.js +165 -0
  65. package/dist/chunk-KFX54TQM.js.map +1 -0
  66. package/dist/chunk-M7NXUK55.js +199 -0
  67. package/dist/chunk-M7NXUK55.js.map +1 -0
  68. package/dist/chunk-MPZ3BPUI.js +374 -0
  69. package/dist/chunk-MPZ3BPUI.js.map +1 -0
  70. package/dist/chunk-OC6YSTDX.js +119 -0
  71. package/dist/chunk-OC6YSTDX.js.map +1 -0
  72. package/dist/chunk-RC6MEZB6.js +469 -0
  73. package/dist/chunk-RC6MEZB6.js.map +1 -0
  74. package/dist/chunk-RY3ZFII7.js +3440 -0
  75. package/dist/chunk-RY3ZFII7.js.map +1 -0
  76. package/dist/chunk-TAT6JU3X.js +167 -0
  77. package/dist/chunk-TAT6JU3X.js.map +1 -0
  78. package/dist/chunk-UDZIS2AQ.js +79 -0
  79. package/dist/chunk-UDZIS2AQ.js.map +1 -0
  80. package/dist/chunk-UPLBF4RZ.js +115 -0
  81. package/dist/chunk-UPLBF4RZ.js.map +1 -0
  82. package/dist/chunk-UWQTZMNI.js +154 -0
  83. package/dist/chunk-UWQTZMNI.js.map +1 -0
  84. package/dist/chunk-W4T7PGI2.js +346 -0
  85. package/dist/chunk-W4T7PGI2.js.map +1 -0
  86. package/dist/chunk-XTBKL5BI.js +111 -0
  87. package/dist/chunk-XTBKL5BI.js.map +1 -0
  88. package/dist/chunk-YIJY5DBV.js +399 -0
  89. package/dist/chunk-YIJY5DBV.js.map +1 -0
  90. package/dist/chunk-YUFNYN2H.js +242 -0
  91. package/dist/chunk-YUFNYN2H.js.map +1 -0
  92. package/dist/chunk-Z2PUCXTZ.js +94 -0
  93. package/dist/chunk-Z2PUCXTZ.js.map +1 -0
  94. package/dist/chunk-ZZJOFKAT.js +13 -0
  95. package/dist/chunk-ZZJOFKAT.js.map +1 -0
  96. package/dist/cli/index.js +3661 -0
  97. package/dist/cli/index.js.map +1 -0
  98. package/dist/config-WVMRUOCA.js +13 -0
  99. package/dist/config-WVMRUOCA.js.map +1 -0
  100. package/dist/context-loader-3ORBPMHJ.js +13 -0
  101. package/dist/context-loader-3ORBPMHJ.js.map +1 -0
  102. package/dist/conversation-QDEIDQPH.js +22 -0
  103. package/dist/conversation-QDEIDQPH.js.map +1 -0
  104. package/dist/cost-tracker-RS3W7SVY.js +24 -0
  105. package/dist/cost-tracker-RS3W7SVY.js.map +1 -0
  106. package/dist/delegate-VJCJLYEK.js +29 -0
  107. package/dist/delegate-VJCJLYEK.js.map +1 -0
  108. package/dist/emotional-state-VQVRA6ED.js +206 -0
  109. package/dist/emotional-state-VQVRA6ED.js.map +1 -0
  110. package/dist/env-discovery-2BLVMAIM.js +251 -0
  111. package/dist/env-discovery-2BLVMAIM.js.map +1 -0
  112. package/dist/export-6GCYHEHQ.js +165 -0
  113. package/dist/export-6GCYHEHQ.js.map +1 -0
  114. package/dist/graph-YUIPOSOO.js +14 -0
  115. package/dist/graph-YUIPOSOO.js.map +1 -0
  116. package/dist/harness-LCHA3DWP.js +10 -0
  117. package/dist/harness-LCHA3DWP.js.map +1 -0
  118. package/dist/harness-WE4SLCML.js +26 -0
  119. package/dist/harness-WE4SLCML.js.map +1 -0
  120. package/dist/health-NZ6WNIMV.js +23 -0
  121. package/dist/health-NZ6WNIMV.js.map +1 -0
  122. package/dist/index.d.ts +3612 -0
  123. package/dist/index.js +13501 -0
  124. package/dist/index.js.map +1 -0
  125. package/dist/indexer-LONANRRM.js +16 -0
  126. package/dist/indexer-LONANRRM.js.map +1 -0
  127. package/dist/instinct-learner-SRM72DHF.js +20 -0
  128. package/dist/instinct-learner-SRM72DHF.js.map +1 -0
  129. package/dist/intake-4M3HNU43.js +21 -0
  130. package/dist/intake-4M3HNU43.js.map +1 -0
  131. package/dist/intelligence-HJOCA4SJ.js +1081 -0
  132. package/dist/intelligence-HJOCA4SJ.js.map +1 -0
  133. package/dist/journal-WANJL3MI.js +24 -0
  134. package/dist/journal-WANJL3MI.js.map +1 -0
  135. package/dist/loader-C3TKIKZR.js +23 -0
  136. package/dist/loader-C3TKIKZR.js.map +1 -0
  137. package/dist/mcp-WTQJJZAO.js +15 -0
  138. package/dist/mcp-WTQJJZAO.js.map +1 -0
  139. package/dist/mcp-discovery-WPAQFL6S.js +377 -0
  140. package/dist/mcp-discovery-WPAQFL6S.js.map +1 -0
  141. package/dist/mcp-installer-6O2XXD3V.js +394 -0
  142. package/dist/mcp-installer-6O2XXD3V.js.map +1 -0
  143. package/dist/metrics-KXGNFAAB.js +20 -0
  144. package/dist/metrics-KXGNFAAB.js.map +1 -0
  145. package/dist/primitive-registry-I6VTIR4W.js +512 -0
  146. package/dist/primitive-registry-I6VTIR4W.js.map +1 -0
  147. package/dist/project-discovery-C4UMD7JI.js +246 -0
  148. package/dist/project-discovery-C4UMD7JI.js.map +1 -0
  149. package/dist/provider-LQHQX7Z7.js +26 -0
  150. package/dist/provider-LQHQX7Z7.js.map +1 -0
  151. package/dist/provider-SXPQZ74H.js +28 -0
  152. package/dist/provider-SXPQZ74H.js.map +1 -0
  153. package/dist/rate-limiter-RLRVM325.js +22 -0
  154. package/dist/rate-limiter-RLRVM325.js.map +1 -0
  155. package/dist/rule-engine-YGQ3RYZM.js +182 -0
  156. package/dist/rule-engine-YGQ3RYZM.js.map +1 -0
  157. package/dist/scaffold-A3VRRCBV.js +347 -0
  158. package/dist/scaffold-A3VRRCBV.js.map +1 -0
  159. package/dist/scheduler-XHHIVHRI.js +397 -0
  160. package/dist/scheduler-XHHIVHRI.js.map +1 -0
  161. package/dist/search-V3W5JMJG.js +75 -0
  162. package/dist/search-V3W5JMJG.js.map +1 -0
  163. package/dist/semantic-search-2DTOO5UX.js +241 -0
  164. package/dist/semantic-search-2DTOO5UX.js.map +1 -0
  165. package/dist/serve-DTQ3HENY.js +291 -0
  166. package/dist/serve-DTQ3HENY.js.map +1 -0
  167. package/dist/sessions-CZGVXKQE.js +21 -0
  168. package/dist/sessions-CZGVXKQE.js.map +1 -0
  169. package/dist/sources-RW5DT56F.js +32 -0
  170. package/dist/sources-RW5DT56F.js.map +1 -0
  171. package/dist/starter-packs-76YUVHEU.js +893 -0
  172. package/dist/starter-packs-76YUVHEU.js.map +1 -0
  173. package/dist/state-GMXILIHW.js +13 -0
  174. package/dist/state-GMXILIHW.js.map +1 -0
  175. package/dist/state-merge-NKO5FRBA.js +174 -0
  176. package/dist/state-merge-NKO5FRBA.js.map +1 -0
  177. package/dist/telemetry-UC6PBXC7.js +22 -0
  178. package/dist/telemetry-UC6PBXC7.js.map +1 -0
  179. package/dist/tool-executor-MJ7IG7PQ.js +28 -0
  180. package/dist/tool-executor-MJ7IG7PQ.js.map +1 -0
  181. package/dist/tools-DZ4KETET.js +20 -0
  182. package/dist/tools-DZ4KETET.js.map +1 -0
  183. package/dist/types-EW7AIB3R.js +18 -0
  184. package/dist/types-EW7AIB3R.js.map +1 -0
  185. package/dist/types-WGDLSPO6.js +16 -0
  186. package/dist/types-WGDLSPO6.js.map +1 -0
  187. package/dist/universal-installer-QGS4SJGX.js +578 -0
  188. package/dist/universal-installer-QGS4SJGX.js.map +1 -0
  189. package/dist/validator-7WXMDIHH.js +22 -0
  190. package/dist/validator-7WXMDIHH.js.map +1 -0
  191. package/dist/verification-gate-FYXUX6LH.js +246 -0
  192. package/dist/verification-gate-FYXUX6LH.js.map +1 -0
  193. package/dist/versioning-Z3XNE2Q2.js +271 -0
  194. package/dist/versioning-Z3XNE2Q2.js.map +1 -0
  195. package/dist/watcher-ISJC7YKL.js +109 -0
  196. package/dist/watcher-ISJC7YKL.js.map +1 -0
  197. package/dist/web-server-DD7ZOP46.js +28 -0
  198. package/dist/web-server-DD7ZOP46.js.map +1 -0
  199. package/package.json +76 -0
  200. package/sources.yaml +121 -0
  201. package/templates/assistant/CORE.md +24 -0
  202. package/templates/assistant/SYSTEM.md +24 -0
  203. package/templates/assistant/config.yaml +51 -0
  204. package/templates/base/CORE.md +17 -0
  205. package/templates/base/SYSTEM.md +24 -0
  206. package/templates/base/config.yaml +51 -0
  207. package/templates/claude-opus/config.yaml +51 -0
  208. package/templates/code-reviewer/CORE.md +25 -0
  209. package/templates/code-reviewer/SYSTEM.md +30 -0
  210. package/templates/code-reviewer/config.yaml +51 -0
  211. package/templates/gpt4/config.yaml +51 -0
  212. package/templates/local/config.yaml +51 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/mcp-discovery.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir, platform } from 'os';\n\n// --- Types ---\n\n/** A discovered MCP server from an external tool's config */\nexport interface DiscoveredMcpServer {\n /** Server name (key from the source config) */\n name: string;\n /** Transport type inferred from config */\n transport: 'stdio' | 'http' | 'sse';\n /** Command for stdio transport */\n command?: string;\n /** Args for stdio transport */\n args?: string[];\n /** Environment variables */\n env?: Record<string, string>;\n /** Working directory */\n cwd?: string;\n /** URL for http/sse transport */\n url?: string;\n /** Headers for http/sse transport */\n headers?: Record<string, string>;\n}\n\n/** Result of scanning a single tool's config */\nexport interface DiscoverySource {\n /** Tool name (e.g. \"Claude Desktop\", \"Cursor\") */\n tool: string;\n /** Config file path that was scanned */\n configPath: string;\n /** Whether the config file exists */\n found: boolean;\n /** Servers discovered from this tool */\n servers: DiscoveredMcpServer[];\n /** Error encountered while reading/parsing */\n error?: string;\n}\n\n/** Aggregated discovery results */\nexport interface DiscoveryResult {\n /** All sources scanned */\n sources: DiscoverySource[];\n /** Deduplicated servers (by name, preferring first seen) */\n servers: DiscoveredMcpServer[];\n /** Total sources that had config files */\n sourcesFound: number;\n /** Total unique servers discovered */\n totalServers: number;\n}\n\n// --- Known tool config locations ---\n\ninterface ToolConfig {\n tool: string;\n /** Function returning config file path for the current platform */\n path: () => string;\n /** JSON key containing server definitions */\n rootKey: string;\n /** Whether servers use explicit 'type' field instead of inferring transport */\n usesTypeField?: boolean;\n /** Custom extraction for non-standard config structures. Returns merged server map. */\n extractServers?: (root: Record<string, unknown>) => Record<string, unknown>;\n}\n\nfunction vscodeGlobalStoragePath(h: string, mac: boolean, extensionId: string): string {\n if (mac) {\n return join(h, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', extensionId);\n }\n return join(h, '.config', 'Code', 'User', 'globalStorage', extensionId);\n}\n\n/** Claude Code extractServers — handles nested projects.<path>.mcpServers */\nfunction extractClaudeCodeServers(root: Record<string, unknown>): Record<string, unknown> {\n const merged: Record<string, unknown> = {};\n // Check top-level mcpServers\n const topLevel = root.mcpServers;\n if (topLevel && typeof topLevel === 'object' && !Array.isArray(topLevel)) {\n Object.assign(merged, topLevel);\n }\n // Check per-project mcpServers under projects.<path>.mcpServers\n const projects = root.projects;\n if (projects && typeof projects === 'object' && !Array.isArray(projects)) {\n for (const projectData of Object.values(projects as Record<string, unknown>)) {\n if (!projectData || typeof projectData !== 'object' || Array.isArray(projectData)) continue;\n const proj = projectData as Record<string, unknown>;\n const projServers = proj.mcpServers;\n if (projServers && typeof projServers === 'object' && !Array.isArray(projServers)) {\n for (const [name, config] of Object.entries(projServers as Record<string, unknown>)) {\n if (!(name in merged)) {\n merged[name] = config;\n }\n }\n }\n }\n }\n return merged;\n}\n\n/**\n * Build the list of tool configs for the given home directory and platform.\n * Evaluated lazily so tests can control the home/platform values.\n */\nfunction buildToolConfigs(h: string, mac: boolean): ToolConfig[] {\n return [\n {\n tool: 'Claude Desktop',\n path: () => mac\n ? join(h, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')\n : join(h, '.config', 'Claude', 'claude_desktop_config.json'),\n rootKey: 'mcpServers',\n },\n {\n tool: 'Claude Code',\n path: () => join(h, '.claude.json'),\n rootKey: 'mcpServers',\n usesTypeField: true,\n extractServers: extractClaudeCodeServers,\n },\n {\n tool: 'Cursor',\n path: () => join(h, '.cursor', 'mcp.json'),\n rootKey: 'mcpServers',\n },\n {\n tool: 'Windsurf',\n path: () => join(h, '.codeium', 'windsurf', 'mcp_config.json'),\n rootKey: 'mcpServers',\n },\n {\n tool: 'Copilot CLI',\n path: () => join(h, '.copilot', 'mcp-config.json'),\n rootKey: 'mcpServers',\n },\n {\n tool: 'Cline',\n path: () => join(vscodeGlobalStoragePath(h, mac, 'saoudrizwan.claude-dev'), 'settings', 'cline_mcp_settings.json'),\n rootKey: 'mcpServers',\n },\n {\n tool: 'Roo Code',\n path: () => join(vscodeGlobalStoragePath(h, mac, 'rooveterinaryinc.roo-cline'), 'settings', 'mcp_settings.json'),\n rootKey: 'mcpServers',\n },\n {\n tool: 'VS Code',\n path: () => mac\n ? join(h, 'Library', 'Application Support', 'Code', 'User', 'mcp.json')\n : join(h, '.config', 'Code', 'User', 'mcp.json'),\n rootKey: 'servers',\n usesTypeField: true,\n },\n {\n tool: 'Zed',\n path: () => join(h, '.config', 'zed', 'settings.json'),\n rootKey: 'context_servers',\n },\n ];\n}\n\n// --- Parsing ---\n\n/**\n * Infer transport type from a server config object.\n * Most tools use 'command' = stdio, 'url' = http/sse.\n * VS Code uses explicit 'type' field.\n */\nfunction inferTransport(\n serverObj: Record<string, unknown>,\n usesTypeField?: boolean,\n): 'stdio' | 'http' | 'sse' {\n if (usesTypeField && typeof serverObj.type === 'string') {\n const t = serverObj.type.toLowerCase();\n if (t === 'sse') return 'sse';\n if (t === 'http') return 'http';\n if (t === 'stdio') return 'stdio';\n }\n\n if (typeof serverObj.command === 'string') return 'stdio';\n if (typeof serverObj.url === 'string' || typeof serverObj.serverUrl === 'string') {\n // If the URL looks like an SSE endpoint\n const url = (serverObj.url ?? serverObj.serverUrl) as string;\n if (url.includes('/sse') || serverObj.type === 'sse') return 'sse';\n return 'http';\n }\n\n // Default to stdio if we can't determine\n return 'stdio';\n}\n\n/**\n * Parse a server config object into a DiscoveredMcpServer.\n */\nfunction parseServer(\n name: string,\n serverObj: Record<string, unknown>,\n usesTypeField?: boolean,\n): DiscoveredMcpServer {\n const transport = inferTransport(serverObj, usesTypeField);\n const server: DiscoveredMcpServer = { name, transport };\n\n if (transport === 'stdio') {\n if (typeof serverObj.command === 'string') server.command = serverObj.command;\n if (Array.isArray(serverObj.args)) {\n server.args = serverObj.args\n .filter((a): a is string => typeof a === 'string')\n .map((a) => redactArgValue(a));\n }\n if (serverObj.env && typeof serverObj.env === 'object' && !Array.isArray(serverObj.env)) {\n server.env = redactEnv(filterStringRecord(serverObj.env as Record<string, unknown>));\n }\n if (typeof serverObj.cwd === 'string') server.cwd = serverObj.cwd;\n } else {\n const url = serverObj.url ?? serverObj.serverUrl;\n if (typeof url === 'string') server.url = url;\n if (serverObj.headers && typeof serverObj.headers === 'object' && !Array.isArray(serverObj.headers)) {\n // Redact auth headers\n const headers = filterStringRecord(serverObj.headers as Record<string, unknown>);\n for (const [k, v] of Object.entries(headers)) {\n if (SECRET_PATTERNS.test(k) || SECRET_PATTERNS.test(v)) {\n headers[k] = `\\${${k.toUpperCase().replace(/[^A-Z0-9]/g, '_')}}`;\n }\n }\n server.headers = headers;\n }\n }\n\n return server;\n}\n\n/** Filter an object to only string values */\nfunction filterStringRecord(obj: Record<string, unknown>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (typeof v === 'string') result[k] = v;\n }\n return result;\n}\n\n/** Patterns that indicate a secret value in env vars */\nconst SECRET_PATTERNS = /(?:api[_-]?key|secret|token|password|bearer|auth)/i;\n/** Patterns for non-secret env vars that are always safe to copy */\nconst SAFE_ENV_KEYS = /^(?:PATH|HOME|NODE_ENV|NODE_OPTIONS|SHELL|LANG|LC_\\w+|TZ|TERM|EDITOR)$/;\n\n/** Pattern for Bearer tokens or similar auth values in args */\nconst BEARER_PATTERN = /^(Authorization:\\s*Bearer\\s+)\\S+$/i;\nconst TOKEN_ARG_PATTERN = /^(--(?:token|api-key|secret|password)[=:])\\S+$/i;\n\n/**\n * Redact sensitive values that may appear in command args.\n * E.g., \"Authorization: Bearer abc123\" → \"Authorization: Bearer ${BEARER_TOKEN}\"\n */\nfunction redactArgValue(arg: string): string {\n const bearerMatch = BEARER_PATTERN.exec(arg);\n if (bearerMatch) return `${bearerMatch[1]}\\${BEARER_TOKEN}`;\n\n const tokenMatch = TOKEN_ARG_PATTERN.exec(arg);\n if (tokenMatch) return `${tokenMatch[1]}\\${TOKEN}`;\n\n return arg;\n}\n\n/**\n * Redact sensitive env values, replacing them with a placeholder.\n * Safe keys (PATH, HOME, etc.) are kept. Keys matching secret patterns\n * get their values replaced with \"${KEY_NAME}\" for the user to fill in.\n */\nfunction redactEnv(env: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [k, v] of Object.entries(env)) {\n if (SAFE_ENV_KEYS.test(k)) {\n result[k] = v;\n } else if (SECRET_PATTERNS.test(k)) {\n result[k] = `\\${${k}}`;\n } else {\n // For non-secret, non-safe keys, keep the value\n result[k] = v;\n }\n }\n return result;\n}\n\n/**\n * Read and parse a JSON config file safely.\n * Handles JSONC (comments) by stripping them outside of string literals.\n * Also handles control characters that some tools leave in their configs.\n */\nfunction readJsonSafe(filePath: string): unknown {\n let raw = readFileSync(filePath, 'utf-8');\n\n // Strip control characters (except \\n, \\r, \\t) that break JSON.parse\n // Some tools write configs with embedded control chars in string values.\n raw = raw.replace(/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f]/g, '');\n\n // Strip JSONC comments outside of string literals.\n // Walk character by character to avoid stripping // inside strings.\n let result = '';\n let inString = false;\n let escape = false;\n let i = 0;\n while (i < raw.length) {\n const ch = raw[i];\n\n if (escape) {\n result += ch;\n escape = false;\n i++;\n continue;\n }\n\n if (inString) {\n if (ch === '\\\\') {\n escape = true;\n result += ch;\n } else if (ch === '\"') {\n inString = false;\n result += ch;\n } else {\n result += ch;\n }\n i++;\n continue;\n }\n\n // Not in a string\n if (ch === '\"') {\n inString = true;\n result += ch;\n i++;\n } else if (ch === '/' && i + 1 < raw.length && raw[i + 1] === '/') {\n // Single-line comment — skip to end of line\n while (i < raw.length && raw[i] !== '\\n') i++;\n } else if (ch === '/' && i + 1 < raw.length && raw[i + 1] === '*') {\n // Multi-line comment — skip to */\n i += 2;\n while (i + 1 < raw.length && !(raw[i] === '*' && raw[i + 1] === '/')) i++;\n i += 2; // skip */\n } else {\n result += ch;\n i++;\n }\n }\n\n return JSON.parse(result);\n}\n\n// --- Discovery ---\n\n/**\n * Scan a single tool's config file for MCP server definitions.\n */\nfunction scanTool(toolConfig: ToolConfig): DiscoverySource {\n const configPath = toolConfig.path();\n const source: DiscoverySource = {\n tool: toolConfig.tool,\n configPath,\n found: false,\n servers: [],\n };\n\n if (!existsSync(configPath)) {\n return source;\n }\n\n source.found = true;\n\n try {\n const parsed = readJsonSafe(configPath);\n if (!parsed || typeof parsed !== 'object') {\n source.error = 'Config file is not a JSON object';\n return source;\n }\n\n const root = parsed as Record<string, unknown>;\n\n // Use custom extractor if provided, otherwise look up rootKey directly\n const serversObj = toolConfig.extractServers\n ? toolConfig.extractServers(root)\n : root[toolConfig.rootKey];\n\n if (!serversObj || typeof serversObj !== 'object' || Array.isArray(serversObj)) {\n // No servers section found — not an error, just nothing configured\n return source;\n }\n\n const entries = Object.entries(serversObj as Record<string, unknown>);\n for (const [name, value] of entries) {\n if (!value || typeof value !== 'object' || Array.isArray(value)) continue;\n\n const serverObj = value as Record<string, unknown>;\n\n // Skip disabled servers\n if (serverObj.disabled === true || serverObj.enabled === false) continue;\n\n try {\n const server = parseServer(name, serverObj, toolConfig.usesTypeField);\n // Only include servers that have enough info to be useful\n if (server.command || server.url) {\n source.servers.push(server);\n }\n } catch {\n // Skip individual servers that fail to parse\n }\n }\n } catch (err) {\n source.error = err instanceof Error ? err.message : String(err);\n }\n\n return source;\n}\n\n/** Options for discovery — primarily used for testing */\nexport interface DiscoveryOptions {\n /** Override home directory (default: os.homedir()) */\n homeDir?: string;\n /** Override platform detection (default: os.platform() === 'darwin') */\n isMac?: boolean;\n}\n\n/**\n * Scan all known tool config locations for MCP servers.\n * Returns deduplicated results with source tracking.\n */\nexport function discoverMcpServers(options?: DiscoveryOptions): DiscoveryResult {\n const h = options?.homeDir ?? homedir();\n const mac = options?.isMac ?? platform() === 'darwin';\n const toolConfigs = buildToolConfigs(h, mac);\n\n const sources: DiscoverySource[] = [];\n const seenNames = new Set<string>();\n const uniqueServers: DiscoveredMcpServer[] = [];\n\n for (const toolConfig of toolConfigs) {\n const source = scanTool(toolConfig);\n sources.push(source);\n\n // Dedupe by server name (first seen wins)\n for (const server of source.servers) {\n if (!seenNames.has(server.name)) {\n seenNames.add(server.name);\n uniqueServers.push(server);\n }\n }\n }\n\n return {\n sources,\n servers: uniqueServers,\n sourcesFound: sources.filter((s) => s.found).length,\n totalServers: uniqueServers.length,\n };\n}\n\n/** Binary basenames that are safe to strip from absolute paths — PATH will resolve them */\nconst NORMALIZABLE_BINARIES = new Set(['npx', 'node', 'python', 'python3']);\n\n/**\n * If `command` is an absolute path whose basename is a well-known interpreter\n * (npx/node/python/python3), return just the basename. Otherwise return as-is.\n *\n * This prevents leaking user-specific paths like\n * `/Users/foo/.nvm/versions/node/v22/bin/npx` into scaffolds.\n */\nfunction normalizeCommand(command: string): string {\n if (!command.startsWith('/')) return command;\n const base = command.substring(command.lastIndexOf('/') + 1);\n if (NORMALIZABLE_BINARIES.has(base)) return base;\n return command;\n}\n\n/** Check if an http/sse server has any form of Authorization configured */\nfunction hasAuthConfigured(server: DiscoveredMcpServer): boolean {\n if (server.headers) {\n for (const k of Object.keys(server.headers)) {\n if (k.toLowerCase() === 'authorization') return true;\n }\n }\n return false;\n}\n\n/** Find ${VAR} placeholders in a string. Returns var names. */\nfunction findEnvPlaceholders(value: string): string[] {\n const matches = value.matchAll(/\\$\\{([A-Z_][A-Z0-9_]*)\\}/gi);\n return Array.from(matches, (m) => m[1]);\n}\n\n/**\n * Filter out servers that won't work on this machine:\n * - http/sse servers with no Authorization header (will 401 silently)\n * - servers referencing env vars that aren't set in the current process\n *\n * Logs a warning to stderr for each skipped server explaining why.\n */\nexport function filterUnsafeServers(servers: DiscoveredMcpServer[]): DiscoveredMcpServer[] {\n const result: DiscoveredMcpServer[] = [];\n for (const server of servers) {\n // Drop unauth http/sse\n if (server.transport === 'http' || server.transport === 'sse') {\n if (!hasAuthConfigured(server)) {\n console.warn(`[mcp-discovery] skipping ${server.name}: ${server.transport} transport with no Authorization header`);\n continue;\n }\n }\n\n // Drop servers with unresolved env var references\n let missingVar: string | undefined;\n if (server.env) {\n for (const v of Object.values(server.env)) {\n for (const varName of findEnvPlaceholders(v)) {\n if (!(varName in process.env) || !process.env[varName]) {\n missingVar = varName;\n break;\n }\n }\n if (missingVar) break;\n }\n }\n if (!missingVar && server.headers) {\n for (const v of Object.values(server.headers)) {\n for (const varName of findEnvPlaceholders(v)) {\n if (!(varName in process.env) || !process.env[varName]) {\n missingVar = varName;\n break;\n }\n }\n if (missingVar) break;\n }\n }\n if (missingVar) {\n console.warn(`[mcp-discovery] skipping ${server.name}: required env var ${missingVar} not set`);\n continue;\n }\n\n result.push(server);\n }\n return result;\n}\n\n/**\n * Convert discovered servers to the harness config YAML format.\n * Returns a string that can be appended to config.yaml.\n *\n * Normalizes absolute paths to well-known binaries (npx/node/python) to bare\n * names so the YAML is portable across machines. When the command is normalized,\n * any PATH env var entry is dropped — it was only needed to find the absolute\n * binary location.\n */\nexport function discoveredServersToYaml(servers: DiscoveredMcpServer[]): string {\n if (servers.length === 0) return '';\n\n const lines: string[] = ['mcp:', ' servers:'];\n\n for (const server of servers) {\n lines.push(` ${server.name}:`);\n lines.push(` transport: ${server.transport}`);\n\n if (server.transport === 'stdio') {\n let normalizedCommand: string | undefined;\n if (server.command) {\n normalizedCommand = normalizeCommand(server.command);\n lines.push(` command: ${normalizedCommand}`);\n }\n const wasNormalized = !!server.command && normalizedCommand !== server.command;\n if (server.args && server.args.length > 0) {\n lines.push(` args: [${server.args.map((a) => `\"${a}\"`).join(', ')}]`);\n }\n // When the command was normalized to a bare binary name, drop PATH —\n // the system PATH will resolve it. Custom env vars are still kept.\n const filteredEnv = server.env\n ? Object.fromEntries(Object.entries(server.env).filter(([k]) => !(wasNormalized && k === 'PATH')))\n : undefined;\n if (filteredEnv && Object.keys(filteredEnv).length > 0) {\n lines.push(' env:');\n for (const [k, v] of Object.entries(filteredEnv)) {\n lines.push(` ${k}: \"${v}\"`);\n }\n }\n if (server.cwd) lines.push(` cwd: \"${server.cwd}\"`);\n } else {\n if (server.url) lines.push(` url: \"${server.url}\"`);\n if (server.headers && Object.keys(server.headers).length > 0) {\n lines.push(' headers:');\n for (const [k, v] of Object.entries(server.headers)) {\n lines.push(` ${k}: \"${v}\"`);\n }\n }\n }\n }\n\n return lines.join('\\n');\n}\n\n/** Get the list of tools that are scanned (for display purposes) */\nexport function getScannedTools(): string[] {\n // Tool names are the same regardless of home/platform\n return buildToolConfigs('', true).map((t) => t.tool);\n}\n"],"mappings":";;;;;AAAA,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AACrB,SAAS,SAAS,gBAAgB;AAgElC,SAAS,wBAAwB,GAAW,KAAc,aAA6B;AACrF,MAAI,KAAK;AACP,WAAO,KAAK,GAAG,WAAW,uBAAuB,QAAQ,QAAQ,iBAAiB,WAAW;AAAA,EAC/F;AACA,SAAO,KAAK,GAAG,WAAW,QAAQ,QAAQ,iBAAiB,WAAW;AACxE;AAGA,SAAS,yBAAyB,MAAwD;AACxF,QAAM,SAAkC,CAAC;AAEzC,QAAM,WAAW,KAAK;AACtB,MAAI,YAAY,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,QAAQ,GAAG;AACxE,WAAO,OAAO,QAAQ,QAAQ;AAAA,EAChC;AAEA,QAAM,WAAW,KAAK;AACtB,MAAI,YAAY,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,QAAQ,GAAG;AACxE,eAAW,eAAe,OAAO,OAAO,QAAmC,GAAG;AAC5E,UAAI,CAAC,eAAe,OAAO,gBAAgB,YAAY,MAAM,QAAQ,WAAW,EAAG;AACnF,YAAM,OAAO;AACb,YAAM,cAAc,KAAK;AACzB,UAAI,eAAe,OAAO,gBAAgB,YAAY,CAAC,MAAM,QAAQ,WAAW,GAAG;AACjF,mBAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,WAAsC,GAAG;AACnF,cAAI,EAAE,QAAQ,SAAS;AACrB,mBAAO,IAAI,IAAI;AAAA,UACjB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,iBAAiB,GAAW,KAA4B;AAC/D,SAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,MAAM,MAAM,MACR,KAAK,GAAG,WAAW,uBAAuB,UAAU,4BAA4B,IAChF,KAAK,GAAG,WAAW,UAAU,4BAA4B;AAAA,MAC7D,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM,MAAM,KAAK,GAAG,cAAc;AAAA,MAClC,SAAS;AAAA,MACT,eAAe;AAAA,MACf,gBAAgB;AAAA,IAClB;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM,MAAM,KAAK,GAAG,WAAW,UAAU;AAAA,MACzC,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM,MAAM,KAAK,GAAG,YAAY,YAAY,iBAAiB;AAAA,MAC7D,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM,MAAM,KAAK,GAAG,YAAY,iBAAiB;AAAA,MACjD,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM,MAAM,KAAK,wBAAwB,GAAG,KAAK,wBAAwB,GAAG,YAAY,yBAAyB;AAAA,MACjH,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM,MAAM,KAAK,wBAAwB,GAAG,KAAK,4BAA4B,GAAG,YAAY,mBAAmB;AAAA,MAC/G,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM,MAAM,MACR,KAAK,GAAG,WAAW,uBAAuB,QAAQ,QAAQ,UAAU,IACpE,KAAK,GAAG,WAAW,QAAQ,QAAQ,UAAU;AAAA,MACjD,SAAS;AAAA,MACT,eAAe;AAAA,IACjB;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM,MAAM,KAAK,GAAG,WAAW,OAAO,eAAe;AAAA,MACrD,SAAS;AAAA,IACX;AAAA,EACF;AACF;AASA,SAAS,eACP,WACA,eAC0B;AAC1B,MAAI,iBAAiB,OAAO,UAAU,SAAS,UAAU;AACvD,UAAM,IAAI,UAAU,KAAK,YAAY;AACrC,QAAI,MAAM,MAAO,QAAO;AACxB,QAAI,MAAM,OAAQ,QAAO;AACzB,QAAI,MAAM,QAAS,QAAO;AAAA,EAC5B;AAEA,MAAI,OAAO,UAAU,YAAY,SAAU,QAAO;AAClD,MAAI,OAAO,UAAU,QAAQ,YAAY,OAAO,UAAU,cAAc,UAAU;AAEhF,UAAM,MAAO,UAAU,OAAO,UAAU;AACxC,QAAI,IAAI,SAAS,MAAM,KAAK,UAAU,SAAS,MAAO,QAAO;AAC7D,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAKA,SAAS,YACP,MACA,WACA,eACqB;AACrB,QAAM,YAAY,eAAe,WAAW,aAAa;AACzD,QAAM,SAA8B,EAAE,MAAM,UAAU;AAEtD,MAAI,cAAc,SAAS;AACzB,QAAI,OAAO,UAAU,YAAY,SAAU,QAAO,UAAU,UAAU;AACtE,QAAI,MAAM,QAAQ,UAAU,IAAI,GAAG;AACjC,aAAO,OAAO,UAAU,KACrB,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,EAChD,IAAI,CAAC,MAAM,eAAe,CAAC,CAAC;AAAA,IACjC;AACA,QAAI,UAAU,OAAO,OAAO,UAAU,QAAQ,YAAY,CAAC,MAAM,QAAQ,UAAU,GAAG,GAAG;AACvF,aAAO,MAAM,UAAU,mBAAmB,UAAU,GAA8B,CAAC;AAAA,IACrF;AACA,QAAI,OAAO,UAAU,QAAQ,SAAU,QAAO,MAAM,UAAU;AAAA,EAChE,OAAO;AACL,UAAM,MAAM,UAAU,OAAO,UAAU;AACvC,QAAI,OAAO,QAAQ,SAAU,QAAO,MAAM;AAC1C,QAAI,UAAU,WAAW,OAAO,UAAU,YAAY,YAAY,CAAC,MAAM,QAAQ,UAAU,OAAO,GAAG;AAEnG,YAAM,UAAU,mBAAmB,UAAU,OAAkC;AAC/E,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC5C,YAAI,gBAAgB,KAAK,CAAC,KAAK,gBAAgB,KAAK,CAAC,GAAG;AACtD,kBAAQ,CAAC,IAAI,MAAM,EAAE,YAAY,EAAE,QAAQ,cAAc,GAAG,CAAC;AAAA,QAC/D;AAAA,MACF;AACA,aAAO,UAAU;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AACT;AAGA,SAAS,mBAAmB,KAAsD;AAChF,QAAM,SAAiC,CAAC;AACxC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,QAAI,OAAO,MAAM,SAAU,QAAO,CAAC,IAAI;AAAA,EACzC;AACA,SAAO;AACT;AAGA,IAAM,kBAAkB;AAExB,IAAM,gBAAgB;AAGtB,IAAM,iBAAiB;AACvB,IAAM,oBAAoB;AAM1B,SAAS,eAAe,KAAqB;AAC3C,QAAM,cAAc,eAAe,KAAK,GAAG;AAC3C,MAAI,YAAa,QAAO,GAAG,YAAY,CAAC,CAAC;AAEzC,QAAM,aAAa,kBAAkB,KAAK,GAAG;AAC7C,MAAI,WAAY,QAAO,GAAG,WAAW,CAAC,CAAC;AAEvC,SAAO;AACT;AAOA,SAAS,UAAU,KAAqD;AACtE,QAAM,SAAiC,CAAC;AACxC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,QAAI,cAAc,KAAK,CAAC,GAAG;AACzB,aAAO,CAAC,IAAI;AAAA,IACd,WAAW,gBAAgB,KAAK,CAAC,GAAG;AAClC,aAAO,CAAC,IAAI,MAAM,CAAC;AAAA,IACrB,OAAO;AAEL,aAAO,CAAC,IAAI;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,aAAa,UAA2B;AAC/C,MAAI,MAAM,aAAa,UAAU,OAAO;AAIxC,QAAM,IAAI,QAAQ,iCAAiC,EAAE;AAIrD,MAAI,SAAS;AACb,MAAI,WAAW;AACf,MAAI,SAAS;AACb,MAAI,IAAI;AACR,SAAO,IAAI,IAAI,QAAQ;AACrB,UAAM,KAAK,IAAI,CAAC;AAEhB,QAAI,QAAQ;AACV,gBAAU;AACV,eAAS;AACT;AACA;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,UAAI,OAAO,MAAM;AACf,iBAAS;AACT,kBAAU;AAAA,MACZ,WAAW,OAAO,KAAK;AACrB,mBAAW;AACX,kBAAU;AAAA,MACZ,OAAO;AACL,kBAAU;AAAA,MACZ;AACA;AACA;AAAA,IACF;AAGA,QAAI,OAAO,KAAK;AACd,iBAAW;AACX,gBAAU;AACV;AAAA,IACF,WAAW,OAAO,OAAO,IAAI,IAAI,IAAI,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK;AAEjE,aAAO,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,KAAM;AAAA,IAC5C,WAAW,OAAO,OAAO,IAAI,IAAI,IAAI,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK;AAEjE,WAAK;AACL,aAAO,IAAI,IAAI,IAAI,UAAU,EAAE,IAAI,CAAC,MAAM,OAAO,IAAI,IAAI,CAAC,MAAM,KAAM;AACtE,WAAK;AAAA,IACP,OAAO;AACL,gBAAU;AACV;AAAA,IACF;AAAA,EACF;AAEA,SAAO,KAAK,MAAM,MAAM;AAC1B;AAOA,SAAS,SAAS,YAAyC;AACzD,QAAM,aAAa,WAAW,KAAK;AACnC,QAAM,SAA0B;AAAA,IAC9B,MAAM,WAAW;AAAA,IACjB;AAAA,IACA,OAAO;AAAA,IACP,SAAS,CAAC;AAAA,EACZ;AAEA,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,SAAO,QAAQ;AAEf,MAAI;AACF,UAAM,SAAS,aAAa,UAAU;AACtC,QAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,aAAO,QAAQ;AACf,aAAO;AAAA,IACT;AAEA,UAAM,OAAO;AAGb,UAAM,aAAa,WAAW,iBAC1B,WAAW,eAAe,IAAI,IAC9B,KAAK,WAAW,OAAO;AAE3B,QAAI,CAAC,cAAc,OAAO,eAAe,YAAY,MAAM,QAAQ,UAAU,GAAG;AAE9E,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,OAAO,QAAQ,UAAqC;AACpE,eAAW,CAAC,MAAM,KAAK,KAAK,SAAS;AACnC,UAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG;AAEjE,YAAM,YAAY;AAGlB,UAAI,UAAU,aAAa,QAAQ,UAAU,YAAY,MAAO;AAEhE,UAAI;AACF,cAAM,SAAS,YAAY,MAAM,WAAW,WAAW,aAAa;AAEpE,YAAI,OAAO,WAAW,OAAO,KAAK;AAChC,iBAAO,QAAQ,KAAK,MAAM;AAAA,QAC5B;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,WAAO,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,EAChE;AAEA,SAAO;AACT;AAcO,SAAS,mBAAmB,SAA6C;AAC9E,QAAM,IAAI,SAAS,WAAW,QAAQ;AACtC,QAAM,MAAM,SAAS,SAAS,SAAS,MAAM;AAC7C,QAAM,cAAc,iBAAiB,GAAG,GAAG;AAE3C,QAAM,UAA6B,CAAC;AACpC,QAAM,YAAY,oBAAI,IAAY;AAClC,QAAM,gBAAuC,CAAC;AAE9C,aAAW,cAAc,aAAa;AACpC,UAAM,SAAS,SAAS,UAAU;AAClC,YAAQ,KAAK,MAAM;AAGnB,eAAW,UAAU,OAAO,SAAS;AACnC,UAAI,CAAC,UAAU,IAAI,OAAO,IAAI,GAAG;AAC/B,kBAAU,IAAI,OAAO,IAAI;AACzB,sBAAc,KAAK,MAAM;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS;AAAA,IACT,cAAc,QAAQ,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE;AAAA,IAC7C,cAAc,cAAc;AAAA,EAC9B;AACF;AAGA,IAAM,wBAAwB,oBAAI,IAAI,CAAC,OAAO,QAAQ,UAAU,SAAS,CAAC;AAS1E,SAAS,iBAAiB,SAAyB;AACjD,MAAI,CAAC,QAAQ,WAAW,GAAG,EAAG,QAAO;AACrC,QAAM,OAAO,QAAQ,UAAU,QAAQ,YAAY,GAAG,IAAI,CAAC;AAC3D,MAAI,sBAAsB,IAAI,IAAI,EAAG,QAAO;AAC5C,SAAO;AACT;AAGA,SAAS,kBAAkB,QAAsC;AAC/D,MAAI,OAAO,SAAS;AAClB,eAAW,KAAK,OAAO,KAAK,OAAO,OAAO,GAAG;AAC3C,UAAI,EAAE,YAAY,MAAM,gBAAiB,QAAO;AAAA,IAClD;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,oBAAoB,OAAyB;AACpD,QAAM,UAAU,MAAM,SAAS,4BAA4B;AAC3D,SAAO,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;AACxC;AASO,SAAS,oBAAoB,SAAuD;AACzF,QAAM,SAAgC,CAAC;AACvC,aAAW,UAAU,SAAS;AAE5B,QAAI,OAAO,cAAc,UAAU,OAAO,cAAc,OAAO;AAC7D,UAAI,CAAC,kBAAkB,MAAM,GAAG;AAC9B,gBAAQ,KAAK,4BAA4B,OAAO,IAAI,KAAK,OAAO,SAAS,yCAAyC;AAClH;AAAA,MACF;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,OAAO,KAAK;AACd,iBAAW,KAAK,OAAO,OAAO,OAAO,GAAG,GAAG;AACzC,mBAAW,WAAW,oBAAoB,CAAC,GAAG;AAC5C,cAAI,EAAE,WAAW,QAAQ,QAAQ,CAAC,QAAQ,IAAI,OAAO,GAAG;AACtD,yBAAa;AACb;AAAA,UACF;AAAA,QACF;AACA,YAAI,WAAY;AAAA,MAClB;AAAA,IACF;AACA,QAAI,CAAC,cAAc,OAAO,SAAS;AACjC,iBAAW,KAAK,OAAO,OAAO,OAAO,OAAO,GAAG;AAC7C,mBAAW,WAAW,oBAAoB,CAAC,GAAG;AAC5C,cAAI,EAAE,WAAW,QAAQ,QAAQ,CAAC,QAAQ,IAAI,OAAO,GAAG;AACtD,yBAAa;AACb;AAAA,UACF;AAAA,QACF;AACA,YAAI,WAAY;AAAA,MAClB;AAAA,IACF;AACA,QAAI,YAAY;AACd,cAAQ,KAAK,4BAA4B,OAAO,IAAI,sBAAsB,UAAU,UAAU;AAC9F;AAAA,IACF;AAEA,WAAO,KAAK,MAAM;AAAA,EACpB;AACA,SAAO;AACT;AAWO,SAAS,wBAAwB,SAAwC;AAC9E,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,QAAM,QAAkB,CAAC,QAAQ,YAAY;AAE7C,aAAW,UAAU,SAAS;AAC5B,UAAM,KAAK,OAAO,OAAO,IAAI,GAAG;AAChC,UAAM,KAAK,oBAAoB,OAAO,SAAS,EAAE;AAEjD,QAAI,OAAO,cAAc,SAAS;AAChC,UAAI;AACJ,UAAI,OAAO,SAAS;AAClB,4BAAoB,iBAAiB,OAAO,OAAO;AACnD,cAAM,KAAK,kBAAkB,iBAAiB,EAAE;AAAA,MAClD;AACA,YAAM,gBAAgB,CAAC,CAAC,OAAO,WAAW,sBAAsB,OAAO;AACvE,UAAI,OAAO,QAAQ,OAAO,KAAK,SAAS,GAAG;AACzC,cAAM,KAAK,gBAAgB,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,GAAG;AAAA,MAC3E;AAGA,YAAM,cAAc,OAAO,MACvB,OAAO,YAAY,OAAO,QAAQ,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,iBAAiB,MAAM,OAAO,CAAC,IAC/F;AACJ,UAAI,eAAe,OAAO,KAAK,WAAW,EAAE,SAAS,GAAG;AACtD,cAAM,KAAK,YAAY;AACvB,mBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,WAAW,GAAG;AAChD,gBAAM,KAAK,WAAW,CAAC,MAAM,CAAC,GAAG;AAAA,QACnC;AAAA,MACF;AACA,UAAI,OAAO,IAAK,OAAM,KAAK,eAAe,OAAO,GAAG,GAAG;AAAA,IACzD,OAAO;AACL,UAAI,OAAO,IAAK,OAAM,KAAK,eAAe,OAAO,GAAG,GAAG;AACvD,UAAI,OAAO,WAAW,OAAO,KAAK,OAAO,OAAO,EAAE,SAAS,GAAG;AAC5D,cAAM,KAAK,gBAAgB;AAC3B,mBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG;AACnD,gBAAM,KAAK,WAAW,CAAC,MAAM,CAAC,GAAG;AAAA,QACnC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAGO,SAAS,kBAA4B;AAE1C,SAAO,iBAAiB,IAAI,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AACrD;","names":[]}
@@ -0,0 +1,394 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ createMcpManager,
5
+ validateMcpConfig
6
+ } from "./chunk-5H34JPMB.js";
7
+ import {
8
+ log
9
+ } from "./chunk-BSKDOFRT.js";
10
+ import {
11
+ loadConfig
12
+ } from "./chunk-CHJ5GNZC.js";
13
+ import "./chunk-4CWAGBNS.js";
14
+ import "./chunk-ZZJOFKAT.js";
15
+
16
+ // src/runtime/mcp-installer.ts
17
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
18
+ import { join } from "path";
19
+ import YAML from "yaml";
20
+
21
+ // src/runtime/mcp-registry.ts
22
+ var REGISTRY_BASE = "https://registry.modelcontextprotocol.io";
23
+ var REGISTRY_API_VERSION = "v0.1";
24
+ var DEFAULT_SEARCH_LIMIT = 10;
25
+ async function searchRegistry(query, options) {
26
+ const limit = options?.limit ?? DEFAULT_SEARCH_LIMIT;
27
+ const params = new URLSearchParams({
28
+ search: query,
29
+ limit: String(limit),
30
+ version: "latest"
31
+ });
32
+ if (options?.cursor) {
33
+ params.set("cursor", options.cursor);
34
+ }
35
+ const url = `${REGISTRY_BASE}/${REGISTRY_API_VERSION}/servers?${params}`;
36
+ const response = await fetch(url);
37
+ if (!response.ok) {
38
+ throw new Error(`MCP registry search failed (${response.status}): ${response.statusText}`);
39
+ }
40
+ const data = await response.json();
41
+ return data;
42
+ }
43
+ async function getRegistryServer(name, version = "latest") {
44
+ const encodedName = encodeURIComponent(name);
45
+ const encodedVersion = encodeURIComponent(version);
46
+ const url = `${REGISTRY_BASE}/${REGISTRY_API_VERSION}/servers/${encodedName}/versions/${encodedVersion}`;
47
+ const response = await fetch(url);
48
+ if (!response.ok) {
49
+ if (response.status === 404) {
50
+ throw new Error(`MCP server "${name}" not found in registry`);
51
+ }
52
+ throw new Error(`MCP registry lookup failed (${response.status}): ${response.statusText}`);
53
+ }
54
+ const data = await response.json();
55
+ return data;
56
+ }
57
+ var RUNTIME_COMMANDS = {
58
+ npx: { command: "npx", prefix: ["-y"] },
59
+ uvx: { command: "uvx", prefix: [] },
60
+ docker: { command: "docker", prefix: ["run", "-i", "--rm"] },
61
+ dnx: { command: "dnx", prefix: [] }
62
+ };
63
+ function deriveConfigName(registryName) {
64
+ const parts = registryName.split("/");
65
+ return parts[parts.length - 1];
66
+ }
67
+ function resolveServerConfig(server) {
68
+ const name = deriveConfigName(server.name);
69
+ const npmPkg = server.packages?.find((p) => p.registryType === "npm" && p.transport.type === "stdio");
70
+ if (npmPkg) {
71
+ return resolveStdioPackage(name, server, npmPkg, "npx");
72
+ }
73
+ const stdioPkg = server.packages?.find((p) => p.transport.type === "stdio");
74
+ if (stdioPkg) {
75
+ const hint = stdioPkg.runtimeHint ?? (stdioPkg.registryType === "pypi" ? "uvx" : "npx");
76
+ return resolveStdioPackage(name, server, stdioPkg, hint);
77
+ }
78
+ if (server.remotes && server.remotes.length > 0) {
79
+ const remote = server.remotes[0];
80
+ return resolveRemote(name, server, remote);
81
+ }
82
+ const httpPkg = server.packages?.find(
83
+ (p) => p.transport.type === "streamable-http" || p.transport.type === "sse"
84
+ );
85
+ if (httpPkg) {
86
+ return resolveHttpPackage(name, server, httpPkg);
87
+ }
88
+ throw new Error(
89
+ `Cannot resolve MCP server "${server.name}": no supported package or remote configuration found`
90
+ );
91
+ }
92
+ function resolveStdioPackage(name, server, pkg, runtimeHint) {
93
+ const runtime = RUNTIME_COMMANDS[runtimeHint] ?? RUNTIME_COMMANDS["npx"];
94
+ const args = [...runtime.prefix, pkg.identifier];
95
+ const envVars = pkg.environmentVariables ?? [];
96
+ const env = {};
97
+ for (const ev of envVars) {
98
+ env[ev.name] = `\${${ev.name}}`;
99
+ }
100
+ const config = {
101
+ transport: "stdio",
102
+ command: runtime.command,
103
+ args,
104
+ ...Object.keys(env).length > 0 ? { env } : {}
105
+ };
106
+ return {
107
+ name,
108
+ description: server.description,
109
+ registryName: server.name,
110
+ package: pkg,
111
+ config,
112
+ requiredEnv: envVars.filter((e) => e.isRequired),
113
+ allEnv: envVars
114
+ };
115
+ }
116
+ function resolveRemote(name, server, remote) {
117
+ const transport = remote.transportType === "sse" ? "sse" : "http";
118
+ const config = {
119
+ transport,
120
+ url: remote.url,
121
+ ...remote.headers && Object.keys(remote.headers).length > 0 ? { headers: remote.headers } : {}
122
+ };
123
+ return {
124
+ name,
125
+ description: server.description,
126
+ registryName: server.name,
127
+ remote,
128
+ config,
129
+ requiredEnv: [],
130
+ allEnv: []
131
+ };
132
+ }
133
+ function resolveHttpPackage(name, server, pkg) {
134
+ const transport = pkg.transport.type === "sse" ? "sse" : "http";
135
+ const envVars = pkg.environmentVariables ?? [];
136
+ const config = {
137
+ transport,
138
+ url: pkg.identifier
139
+ };
140
+ return {
141
+ name,
142
+ description: server.description,
143
+ registryName: server.name,
144
+ package: pkg,
145
+ config,
146
+ requiredEnv: envVars.filter((e) => e.isRequired),
147
+ allEnv: envVars
148
+ };
149
+ }
150
+ async function findServer(query) {
151
+ if (query.includes("/") || query.includes("io.")) {
152
+ try {
153
+ const server = await getRegistryServer(query);
154
+ return resolveServerConfig(server);
155
+ } catch {
156
+ }
157
+ }
158
+ const results = await searchRegistry(query, { limit: 5 });
159
+ if (results.servers.length === 0) {
160
+ return null;
161
+ }
162
+ return resolveServerConfig(results.servers[0].server);
163
+ }
164
+ async function searchServers(query, options) {
165
+ const results = await searchRegistry(query, { limit: options?.limit ?? DEFAULT_SEARCH_LIMIT });
166
+ const resolved = [];
167
+ for (const result of results.servers) {
168
+ try {
169
+ resolved.push(resolveServerConfig(result.server));
170
+ } catch {
171
+ }
172
+ }
173
+ return resolved;
174
+ }
175
+
176
+ // src/runtime/mcp-installer.ts
177
+ var CONFIG_FILENAMES = ["config.yaml", "config.yml", "harness.yaml", "harness.yml"];
178
+ function findConfigPath(dir) {
179
+ for (const filename of CONFIG_FILENAMES) {
180
+ const configPath = join(dir, filename);
181
+ if (existsSync(configPath)) {
182
+ return configPath;
183
+ }
184
+ }
185
+ return join(dir, "config.yaml");
186
+ }
187
+ function updateConfigWithServer(dir, serverName, serverConfig) {
188
+ const configPath = findConfigPath(dir);
189
+ const content = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
190
+ const doc = YAML.parseDocument(content);
191
+ if (!doc.has("mcp")) {
192
+ doc.set("mcp", doc.createNode({ servers: {} }));
193
+ }
194
+ const mcp = doc.get("mcp");
195
+ if (!mcp.has("servers")) {
196
+ mcp.set("servers", doc.createNode({}));
197
+ }
198
+ const servers = mcp.get("servers");
199
+ const configNode = {
200
+ transport: serverConfig.transport
201
+ };
202
+ if (serverConfig.transport === "stdio") {
203
+ if (serverConfig.command) configNode["command"] = serverConfig.command;
204
+ if (serverConfig.args && serverConfig.args.length > 0) configNode["args"] = serverConfig.args;
205
+ if (serverConfig.env && Object.keys(serverConfig.env).length > 0) configNode["env"] = serverConfig.env;
206
+ if (serverConfig.cwd) configNode["cwd"] = serverConfig.cwd;
207
+ } else {
208
+ if (serverConfig.url) configNode["url"] = serverConfig.url;
209
+ if (serverConfig.headers && Object.keys(serverConfig.headers).length > 0) {
210
+ configNode["headers"] = serverConfig.headers;
211
+ }
212
+ }
213
+ servers.set(serverName, doc.createNode(configNode));
214
+ writeFileSync(configPath, doc.toString(), "utf-8");
215
+ }
216
+ function serverExistsInConfig(dir, serverName) {
217
+ try {
218
+ const config = loadConfig(dir);
219
+ return Boolean(config.mcp?.servers?.[serverName]);
220
+ } catch {
221
+ return false;
222
+ }
223
+ }
224
+ function generateToolDocs(dir, serverName, toolNames, description) {
225
+ const toolsDir = join(dir, "tools");
226
+ if (!existsSync(toolsDir)) {
227
+ mkdirSync(toolsDir, { recursive: true });
228
+ }
229
+ const docPath = join(toolsDir, `${serverName}.md`);
230
+ const lines = [
231
+ "---",
232
+ `id: tool-${serverName}`,
233
+ `created: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
234
+ `tags: [mcp, ${serverName}]`,
235
+ "---",
236
+ "",
237
+ `# ${serverName} MCP Server`,
238
+ ""
239
+ ];
240
+ if (description) {
241
+ lines.push(description, "");
242
+ }
243
+ lines.push("## Available Tools", "");
244
+ for (const toolName of toolNames) {
245
+ lines.push(`- **${toolName}**`);
246
+ }
247
+ lines.push("", `> Auto-generated by \`harness mcp install ${serverName}\``);
248
+ writeFileSync(docPath, lines.join("\n"), "utf-8");
249
+ return [docPath];
250
+ }
251
+ async function testConnection(dir, serverName, serverConfig) {
252
+ const config = loadConfig(dir);
253
+ const testConfig = {
254
+ ...config,
255
+ mcp: { servers: { [serverName]: { ...serverConfig, enabled: serverConfig.enabled ?? true } } }
256
+ };
257
+ const validationErrors = validateMcpConfig(testConfig);
258
+ if (validationErrors.length > 0) {
259
+ return {
260
+ connected: false,
261
+ toolCount: 0,
262
+ toolNames: [],
263
+ error: validationErrors.map((e) => e.error).join("; ")
264
+ };
265
+ }
266
+ const manager = createMcpManager(testConfig);
267
+ try {
268
+ await manager.connect();
269
+ const summaries = manager.getSummaries();
270
+ const summary = summaries.find((s) => s.name === serverName);
271
+ if (summary?.connected) {
272
+ return {
273
+ connected: true,
274
+ toolCount: summary.toolCount,
275
+ toolNames: summary.toolNames
276
+ };
277
+ }
278
+ return {
279
+ connected: false,
280
+ toolCount: 0,
281
+ toolNames: [],
282
+ error: summary?.error ?? "Connection failed"
283
+ };
284
+ } catch (err) {
285
+ return {
286
+ connected: false,
287
+ toolCount: 0,
288
+ toolNames: [],
289
+ error: err instanceof Error ? err.message : String(err)
290
+ };
291
+ } finally {
292
+ await manager.close();
293
+ }
294
+ }
295
+ async function installMcpServer(query, options) {
296
+ const { dir, skipTest, skipDocs, force, name: nameOverride } = options;
297
+ log.info(`Searching MCP registry for "${query}"...`);
298
+ let resolved;
299
+ try {
300
+ resolved = await findServer(query);
301
+ } catch (err) {
302
+ const message = err instanceof Error ? err.message : String(err);
303
+ return {
304
+ installed: false,
305
+ name: query,
306
+ generatedDocs: [],
307
+ pendingEnvVars: [],
308
+ error: `Registry lookup failed: ${message}`
309
+ };
310
+ }
311
+ if (!resolved) {
312
+ return {
313
+ installed: false,
314
+ name: query,
315
+ generatedDocs: [],
316
+ pendingEnvVars: [],
317
+ error: `No MCP server found matching "${query}" in the registry`
318
+ };
319
+ }
320
+ const serverName = nameOverride ?? resolved.name;
321
+ if (!force && serverExistsInConfig(dir, serverName)) {
322
+ return {
323
+ installed: false,
324
+ name: serverName,
325
+ server: resolved,
326
+ generatedDocs: [],
327
+ pendingEnvVars: resolved.requiredEnv,
328
+ error: `Server "${serverName}" already exists in config. Use --force to overwrite.`
329
+ };
330
+ }
331
+ log.info(`Adding "${serverName}" to config.yaml...`);
332
+ updateConfigWithServer(dir, serverName, resolved.config);
333
+ const result = {
334
+ installed: true,
335
+ name: serverName,
336
+ server: resolved,
337
+ generatedDocs: [],
338
+ pendingEnvVars: resolved.requiredEnv
339
+ };
340
+ if (!skipTest) {
341
+ log.info(`Testing connection to "${serverName}"...`);
342
+ result.connectionTest = await testConnection(dir, serverName, resolved.config);
343
+ }
344
+ if (!skipDocs && result.connectionTest?.connected && result.connectionTest.toolNames.length > 0) {
345
+ log.info(`Generating tool docs for "${serverName}"...`);
346
+ result.generatedDocs = generateToolDocs(
347
+ dir,
348
+ serverName,
349
+ result.connectionTest.toolNames,
350
+ resolved.description
351
+ );
352
+ }
353
+ return result;
354
+ }
355
+ async function listRegistryServers(query, options) {
356
+ return searchServers(query, options);
357
+ }
358
+ function formatRegistryServer(entry) {
359
+ const s = entry.server;
360
+ const lines = [];
361
+ lines.push(` ${s.name} (v${s.version})`);
362
+ if (s.title) lines.push(` ${s.title}`);
363
+ if (s.description) {
364
+ const desc = s.description.length > 100 ? s.description.slice(0, 97) + "..." : s.description;
365
+ lines.push(` ${desc}`);
366
+ }
367
+ const npmPkgs = (s.packages ?? []).filter((p) => p.registryType === "npm");
368
+ const pypiPkgs = (s.packages ?? []).filter((p) => p.registryType === "pypi");
369
+ if (npmPkgs.length > 0) {
370
+ lines.push(` npm: ${npmPkgs.map((p) => p.identifier).join(", ")}`);
371
+ }
372
+ if (pypiPkgs.length > 0) {
373
+ lines.push(` pypi: ${pypiPkgs.map((p) => p.identifier).join(", ")}`);
374
+ }
375
+ if (s.remotes && s.remotes.length > 0) {
376
+ lines.push(` remote: ${s.remotes[0].transportType} ${s.remotes[0].url}`);
377
+ }
378
+ const allEnvVars = (s.packages ?? []).flatMap((p) => p.environmentVariables ?? []).filter((v) => v.isRequired);
379
+ if (allEnvVars.length > 0) {
380
+ lines.push(` requires: ${allEnvVars.map((v) => v.name).join(", ")}`);
381
+ }
382
+ return lines.join("\n");
383
+ }
384
+ export {
385
+ formatRegistryServer,
386
+ generateToolDocs,
387
+ getRegistryServer,
388
+ installMcpServer,
389
+ listRegistryServers,
390
+ searchRegistry,
391
+ serverExistsInConfig,
392
+ updateConfigWithServer
393
+ };
394
+ //# sourceMappingURL=mcp-installer-6O2XXD3V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/mcp-installer.ts","../src/runtime/mcp-registry.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport YAML from 'yaml';\nimport type { HarnessConfig } from '../core/types.js';\nimport { loadConfig } from '../core/config.js';\nimport type { McpServerConfig } from './mcp.js';\nimport { createMcpManager, validateMcpConfig } from './mcp.js';\nimport type { ResolvedServer, RegistryEnvVar, RegistryServer, RegistrySearchResponse } from './mcp-registry.js';\nimport { findServer, searchServers, searchRegistry, getRegistryServer } from './mcp-registry.js';\nimport { log } from '../core/logger.js';\n\n// Re-export registry types and functions for consumers\nexport { searchRegistry, getRegistryServer };\nexport type { RegistryServer, RegistrySearchResponse, RegistryEnvVar, ResolvedServer };\nexport type { RegistryPackage, RegistryRemote, RegistrySearchResult } from './mcp-registry.js';\n\n// --- Types ---\n\n/** Result of installing an MCP server */\nexport interface McpInstallResult {\n /** Whether installation succeeded */\n installed: boolean;\n /** Server config name in config.yaml */\n name: string;\n /** The resolved server details */\n server?: ResolvedServer;\n /** Connection test results */\n connectionTest?: {\n connected: boolean;\n toolCount: number;\n toolNames: string[];\n error?: string;\n };\n /** Generated tool doc paths */\n generatedDocs: string[];\n /** Env vars that need user configuration */\n pendingEnvVars: RegistryEnvVar[];\n /** Error message if installation failed */\n error?: string;\n}\n\n/** Options for the install command */\nexport interface McpInstallOptions {\n /** Harness directory */\n dir: string;\n /** Skip connection testing */\n skipTest?: boolean;\n /** Skip tool doc generation */\n skipDocs?: boolean;\n /** Force overwrite if server already exists */\n force?: boolean;\n /** Custom name override for the server in config */\n name?: string;\n}\n\n// --- Config Update ---\n\nconst CONFIG_FILENAMES = ['config.yaml', 'config.yml', 'harness.yaml', 'harness.yml'];\n\n/**\n * Find the config file path in a harness directory.\n */\nfunction findConfigPath(dir: string): string {\n for (const filename of CONFIG_FILENAMES) {\n const configPath = join(dir, filename);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n return join(dir, 'config.yaml');\n}\n\n/**\n * Add or update an MCP server entry in the config file.\n * Preserves existing config structure and comments where possible.\n */\nexport function updateConfigWithServer(\n dir: string,\n serverName: string,\n serverConfig: McpServerConfig,\n): void {\n const configPath = findConfigPath(dir);\n const content = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';\n\n // Parse existing YAML, preserving structure\n const doc = YAML.parseDocument(content);\n\n // Ensure mcp.servers exists\n if (!doc.has('mcp')) {\n doc.set('mcp', doc.createNode({ servers: {} }));\n }\n const mcp = doc.get('mcp') as YAML.YAMLMap;\n if (!mcp.has('servers')) {\n mcp.set('servers', doc.createNode({}));\n }\n const servers = mcp.get('servers') as YAML.YAMLMap;\n\n // Build the server config node\n const configNode: Record<string, unknown> = {\n transport: serverConfig.transport,\n };\n\n if (serverConfig.transport === 'stdio') {\n if (serverConfig.command) configNode['command'] = serverConfig.command;\n if (serverConfig.args && serverConfig.args.length > 0) configNode['args'] = serverConfig.args;\n if (serverConfig.env && Object.keys(serverConfig.env).length > 0) configNode['env'] = serverConfig.env;\n if (serverConfig.cwd) configNode['cwd'] = serverConfig.cwd;\n } else {\n if (serverConfig.url) configNode['url'] = serverConfig.url;\n if (serverConfig.headers && Object.keys(serverConfig.headers).length > 0) {\n configNode['headers'] = serverConfig.headers;\n }\n }\n\n // Set the server entry (add or overwrite)\n servers.set(serverName, doc.createNode(configNode));\n\n // Write back\n writeFileSync(configPath, doc.toString(), 'utf-8');\n}\n\n/**\n * Check if a server name already exists in the config.\n */\nexport function serverExistsInConfig(dir: string, serverName: string): boolean {\n try {\n const config = loadConfig(dir);\n return Boolean(config.mcp?.servers?.[serverName]);\n } catch {\n return false;\n }\n}\n\n// --- Tool Doc Generation ---\n\n/**\n * Generate a tools/*.md knowledge doc from a connected MCP server's tools.\n * Creates one file per MCP server with descriptions of all available tools.\n */\nexport function generateToolDocs(\n dir: string,\n serverName: string,\n toolNames: string[],\n description?: string,\n): string[] {\n const toolsDir = join(dir, 'tools');\n if (!existsSync(toolsDir)) {\n mkdirSync(toolsDir, { recursive: true });\n }\n\n const docPath = join(toolsDir, `${serverName}.md`);\n const lines: string[] = [\n '---',\n `id: tool-${serverName}`,\n `created: ${new Date().toISOString().split('T')[0]}`,\n `tags: [mcp, ${serverName}]`,\n '---',\n '',\n `# ${serverName} MCP Server`,\n '',\n ];\n\n if (description) {\n lines.push(description, '');\n }\n\n lines.push('## Available Tools', '');\n\n for (const toolName of toolNames) {\n lines.push(`- **${toolName}**`);\n }\n\n lines.push('', `> Auto-generated by \\`harness mcp install ${serverName}\\``);\n\n writeFileSync(docPath, lines.join('\\n'), 'utf-8');\n return [docPath];\n}\n\n// --- Connection Test ---\n\n/**\n * Test an MCP server connection and return tool info.\n */\nasync function testConnection(\n dir: string,\n serverName: string,\n serverConfig: McpServerConfig,\n): Promise<{ connected: boolean; toolCount: number; toolNames: string[]; error?: string }> {\n // Build a minimal config for testing just this server\n const config = loadConfig(dir);\n const testConfig: HarnessConfig = {\n ...config,\n mcp: { servers: { [serverName]: { ...serverConfig, enabled: serverConfig.enabled ?? true } } },\n };\n\n // Validate first\n const validationErrors = validateMcpConfig(testConfig);\n if (validationErrors.length > 0) {\n return {\n connected: false,\n toolCount: 0,\n toolNames: [],\n error: validationErrors.map((e) => e.error).join('; '),\n };\n }\n\n const manager = createMcpManager(testConfig);\n try {\n await manager.connect();\n const summaries = manager.getSummaries();\n const summary = summaries.find((s) => s.name === serverName);\n\n if (summary?.connected) {\n return {\n connected: true,\n toolCount: summary.toolCount,\n toolNames: summary.toolNames,\n };\n }\n\n return {\n connected: false,\n toolCount: 0,\n toolNames: [],\n error: summary?.error ?? 'Connection failed',\n };\n } catch (err) {\n return {\n connected: false,\n toolCount: 0,\n toolNames: [],\n error: err instanceof Error ? err.message : String(err),\n };\n } finally {\n await manager.close();\n }\n}\n\n// --- Main Install Flow ---\n\n/**\n * Install an MCP server by name or search query.\n *\n * Flow:\n * 1. Search the MCP registry for the server\n * 2. Resolve the best package/transport configuration\n * 3. Add/update the server entry in config.yaml\n * 4. Optionally test the connection\n * 5. Optionally generate tools/*.md knowledge docs\n */\nexport async function installMcpServer(\n query: string,\n options: McpInstallOptions,\n): Promise<McpInstallResult> {\n const { dir, skipTest, skipDocs, force, name: nameOverride } = options;\n\n // Step 1: Find the server in the registry\n log.info(`Searching MCP registry for \"${query}\"...`);\n let resolved: ResolvedServer | null;\n try {\n resolved = await findServer(query);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n installed: false,\n name: query,\n generatedDocs: [],\n pendingEnvVars: [],\n error: `Registry lookup failed: ${message}`,\n };\n }\n\n if (!resolved) {\n return {\n installed: false,\n name: query,\n generatedDocs: [],\n pendingEnvVars: [],\n error: `No MCP server found matching \"${query}\" in the registry`,\n };\n }\n\n const serverName = nameOverride ?? resolved.name;\n\n // Step 2: Check if already exists\n if (!force && serverExistsInConfig(dir, serverName)) {\n return {\n installed: false,\n name: serverName,\n server: resolved,\n generatedDocs: [],\n pendingEnvVars: resolved.requiredEnv,\n error: `Server \"${serverName}\" already exists in config. Use --force to overwrite.`,\n };\n }\n\n // Step 3: Write to config.yaml\n log.info(`Adding \"${serverName}\" to config.yaml...`);\n updateConfigWithServer(dir, serverName, resolved.config);\n\n const result: McpInstallResult = {\n installed: true,\n name: serverName,\n server: resolved,\n generatedDocs: [],\n pendingEnvVars: resolved.requiredEnv,\n };\n\n // Step 4: Test connection (optional)\n if (!skipTest) {\n log.info(`Testing connection to \"${serverName}\"...`);\n result.connectionTest = await testConnection(dir, serverName, resolved.config);\n }\n\n // Step 5: Generate tool docs (optional)\n if (!skipDocs && result.connectionTest?.connected && result.connectionTest.toolNames.length > 0) {\n log.info(`Generating tool docs for \"${serverName}\"...`);\n result.generatedDocs = generateToolDocs(\n dir,\n serverName,\n result.connectionTest.toolNames,\n resolved.description,\n );\n }\n\n return result;\n}\n\n/**\n * List available servers from the registry for a given query.\n */\nexport async function listRegistryServers(\n query: string,\n options?: { limit?: number },\n): Promise<ResolvedServer[]> {\n return searchServers(query, options);\n}\n\n/**\n * Format a registry search result for CLI display.\n */\nexport function formatRegistryServer(entry: { server: RegistryServer }): string {\n const s = entry.server;\n const lines: string[] = [];\n\n lines.push(` ${s.name} (v${s.version})`);\n if (s.title) lines.push(` ${s.title}`);\n if (s.description) {\n const desc = s.description.length > 100 ? s.description.slice(0, 97) + '...' : s.description;\n lines.push(` ${desc}`);\n }\n\n // Show packages\n const npmPkgs = (s.packages ?? []).filter((p) => p.registryType === 'npm');\n const pypiPkgs = (s.packages ?? []).filter((p) => p.registryType === 'pypi');\n if (npmPkgs.length > 0) {\n lines.push(` npm: ${npmPkgs.map((p) => p.identifier).join(', ')}`);\n }\n if (pypiPkgs.length > 0) {\n lines.push(` pypi: ${pypiPkgs.map((p) => p.identifier).join(', ')}`);\n }\n\n // Show remotes\n if (s.remotes && s.remotes.length > 0) {\n lines.push(` remote: ${s.remotes[0].transportType} ${s.remotes[0].url}`);\n }\n\n // Show required env vars\n const allEnvVars = (s.packages ?? [])\n .flatMap((p) => p.environmentVariables ?? [])\n .filter((v) => v.isRequired);\n if (allEnvVars.length > 0) {\n lines.push(` requires: ${allEnvVars.map((v) => v.name).join(', ')}`);\n }\n\n return lines.join('\\n');\n}\n","import type { McpServerConfig } from './mcp.js';\n\n// --- Types ---\n\n/** Environment variable from registry server entry */\nexport interface RegistryEnvVar {\n name: string;\n description?: string;\n isRequired?: boolean;\n format?: string;\n}\n\n/** Package entry from registry server */\nexport interface RegistryPackage {\n registryType: 'npm' | 'pypi' | 'oci' | 'nuget' | 'mcpb';\n identifier: string;\n version: string;\n transport: { type: 'stdio' | 'streamable-http' | 'sse' };\n environmentVariables?: RegistryEnvVar[];\n runtimeHint?: 'npx' | 'uvx' | 'docker' | 'dnx';\n packageArguments?: unknown[];\n runtimeArguments?: unknown[];\n}\n\n/** Remote endpoint from registry server */\nexport interface RegistryRemote {\n transportType: 'streamable-http' | 'sse';\n url: string;\n headers?: Record<string, string>;\n}\n\n/** A single server entry from the MCP registry API */\nexport interface RegistryServer {\n $schema?: string;\n name: string;\n description?: string;\n title?: string;\n version: string;\n repository?: { url: string; source?: string; subfolder?: string };\n websiteUrl?: string;\n packages?: RegistryPackage[];\n remotes?: RegistryRemote[];\n}\n\n/** A server result from the registry search API */\nexport interface RegistrySearchResult {\n server: RegistryServer;\n _meta?: Record<string, unknown>;\n}\n\n/** Full search response from the registry */\nexport interface RegistrySearchResponse {\n servers: RegistrySearchResult[];\n metadata?: { nextCursor?: string; count?: number };\n}\n\n/** Resolved server config ready for installation */\nexport interface ResolvedServer {\n /** Display name for the server */\n name: string;\n /** Server description from registry */\n description?: string;\n /** Registry name (e.g. \"io.github.foo/bar\") */\n registryName: string;\n /** Source package info */\n package?: RegistryPackage;\n /** Source remote info */\n remote?: RegistryRemote;\n /** Generated harness config */\n config: McpServerConfig;\n /** Environment variables that need to be set */\n requiredEnv: RegistryEnvVar[];\n /** All environment variables (required + optional) */\n allEnv: RegistryEnvVar[];\n}\n\n// --- Constants ---\n\nconst REGISTRY_BASE = 'https://registry.modelcontextprotocol.io';\nconst REGISTRY_API_VERSION = 'v0.1';\nconst DEFAULT_SEARCH_LIMIT = 10;\n\n// --- Registry Client ---\n\n/**\n * Search the MCP registry for servers matching a query.\n */\nexport async function searchRegistry(\n query: string,\n options?: { limit?: number; cursor?: string },\n): Promise<RegistrySearchResponse> {\n const limit = options?.limit ?? DEFAULT_SEARCH_LIMIT;\n const params = new URLSearchParams({\n search: query,\n limit: String(limit),\n version: 'latest',\n });\n if (options?.cursor) {\n params.set('cursor', options.cursor);\n }\n\n const url = `${REGISTRY_BASE}/${REGISTRY_API_VERSION}/servers?${params}`;\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`MCP registry search failed (${response.status}): ${response.statusText}`);\n }\n\n const data = (await response.json()) as RegistrySearchResponse;\n return data;\n}\n\n/**\n * Get a specific server by its registry name and version.\n * Name must be URL-encoded (e.g. \"io.github.foo/bar\" -> \"io.github.foo%2Fbar\").\n */\nexport async function getRegistryServer(\n name: string,\n version: string = 'latest',\n): Promise<RegistryServer> {\n const encodedName = encodeURIComponent(name);\n const encodedVersion = encodeURIComponent(version);\n const url = `${REGISTRY_BASE}/${REGISTRY_API_VERSION}/servers/${encodedName}/versions/${encodedVersion}`;\n const response = await fetch(url);\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new Error(`MCP server \"${name}\" not found in registry`);\n }\n throw new Error(`MCP registry lookup failed (${response.status}): ${response.statusText}`);\n }\n\n const data = (await response.json()) as RegistryServer;\n return data;\n}\n\n// --- Runtime hint mapping ---\n\nconst RUNTIME_COMMANDS: Record<string, { command: string; prefix: string[] }> = {\n npx: { command: 'npx', prefix: ['-y'] },\n uvx: { command: 'uvx', prefix: [] },\n docker: { command: 'docker', prefix: ['run', '-i', '--rm'] },\n dnx: { command: 'dnx', prefix: [] },\n};\n\n/**\n * Derive a short config name from a registry server name.\n * \"io.github.foo/bar-server\" -> \"bar-server\"\n * \"io.github.foo/mcp-something\" -> \"mcp-something\"\n */\nexport function deriveConfigName(registryName: string): string {\n const parts = registryName.split('/');\n return parts[parts.length - 1];\n}\n\n/**\n * Resolve a registry server entry into a harness McpServerConfig.\n * Prefers npm stdio packages, falls back to pypi, then remotes.\n */\nexport function resolveServerConfig(server: RegistryServer): ResolvedServer {\n const name = deriveConfigName(server.name);\n\n // Strategy 1: Look for an npm stdio package\n const npmPkg = server.packages?.find((p) => p.registryType === 'npm' && p.transport.type === 'stdio');\n if (npmPkg) {\n return resolveStdioPackage(name, server, npmPkg, 'npx');\n }\n\n // Strategy 2: Look for any stdio package with runtimeHint\n const stdioPkg = server.packages?.find((p) => p.transport.type === 'stdio');\n if (stdioPkg) {\n const hint = stdioPkg.runtimeHint ?? (stdioPkg.registryType === 'pypi' ? 'uvx' : 'npx');\n return resolveStdioPackage(name, server, stdioPkg, hint);\n }\n\n // Strategy 3: Look for remotes (HTTP/SSE endpoints)\n if (server.remotes && server.remotes.length > 0) {\n const remote = server.remotes[0];\n return resolveRemote(name, server, remote);\n }\n\n // Strategy 4: Look for any package with HTTP transport\n const httpPkg = server.packages?.find(\n (p) => p.transport.type === 'streamable-http' || p.transport.type === 'sse',\n );\n if (httpPkg) {\n return resolveHttpPackage(name, server, httpPkg);\n }\n\n throw new Error(\n `Cannot resolve MCP server \"${server.name}\": no supported package or remote configuration found`,\n );\n}\n\nfunction resolveStdioPackage(\n name: string,\n server: RegistryServer,\n pkg: RegistryPackage,\n runtimeHint: string,\n): ResolvedServer {\n const runtime = RUNTIME_COMMANDS[runtimeHint] ?? RUNTIME_COMMANDS['npx'];\n const args = [...runtime.prefix, pkg.identifier];\n\n const envVars = pkg.environmentVariables ?? [];\n const env: Record<string, string> = {};\n for (const ev of envVars) {\n env[ev.name] = `\\${${ev.name}}`;\n }\n\n const config: McpServerConfig = {\n transport: 'stdio',\n command: runtime.command,\n args,\n ...(Object.keys(env).length > 0 ? { env } : {}),\n };\n\n return {\n name,\n description: server.description,\n registryName: server.name,\n package: pkg,\n config,\n requiredEnv: envVars.filter((e) => e.isRequired),\n allEnv: envVars,\n };\n}\n\nfunction resolveRemote(\n name: string,\n server: RegistryServer,\n remote: RegistryRemote,\n): ResolvedServer {\n const transport = remote.transportType === 'sse' ? 'sse' as const : 'http' as const;\n\n const config: McpServerConfig = {\n transport,\n url: remote.url,\n ...(remote.headers && Object.keys(remote.headers).length > 0 ? { headers: remote.headers } : {}),\n };\n\n return {\n name,\n description: server.description,\n registryName: server.name,\n remote,\n config,\n requiredEnv: [],\n allEnv: [],\n };\n}\n\nfunction resolveHttpPackage(\n name: string,\n server: RegistryServer,\n pkg: RegistryPackage,\n): ResolvedServer {\n // HTTP packages typically have a URL in the package identifier or need a remote\n const transport = pkg.transport.type === 'sse' ? 'sse' as const : 'http' as const;\n\n const envVars = pkg.environmentVariables ?? [];\n const config: McpServerConfig = {\n transport,\n url: pkg.identifier,\n };\n\n return {\n name,\n description: server.description,\n registryName: server.name,\n package: pkg,\n config,\n requiredEnv: envVars.filter((e) => e.isRequired),\n allEnv: envVars,\n };\n}\n\n/**\n * Search the registry and return the best match for a query.\n * If the query looks like a registry name (contains \"/\" or \".\"), try exact lookup first.\n * Otherwise, search and return the first result.\n */\nexport async function findServer(query: string): Promise<ResolvedServer | null> {\n // If it looks like an exact registry name, try direct lookup\n if (query.includes('/') || query.includes('io.')) {\n try {\n const server = await getRegistryServer(query);\n return resolveServerConfig(server);\n } catch {\n // Fall through to search\n }\n }\n\n // Search the registry\n const results = await searchRegistry(query, { limit: 5 });\n if (results.servers.length === 0) {\n return null;\n }\n\n // Return the first match\n return resolveServerConfig(results.servers[0].server);\n}\n\n/**\n * Search the registry and return all matches for display.\n */\nexport async function searchServers(\n query: string,\n options?: { limit?: number },\n): Promise<ResolvedServer[]> {\n const results = await searchRegistry(query, { limit: options?.limit ?? DEFAULT_SEARCH_LIMIT });\n const resolved: ResolvedServer[] = [];\n\n for (const result of results.servers) {\n try {\n resolved.push(resolveServerConfig(result.server));\n } catch {\n // Skip servers that can't be resolved\n }\n }\n\n return resolved;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ clearMetrics,
5
+ getAllWorkflowStats,
6
+ getWorkflowStats,
7
+ loadMetrics,
8
+ recordRun,
9
+ saveMetrics
10
+ } from "./chunk-6EMOEYGU.js";
11
+ import "./chunk-ZZJOFKAT.js";
12
+ export {
13
+ clearMetrics,
14
+ getAllWorkflowStats,
15
+ getWorkflowStats,
16
+ loadMetrics,
17
+ recordRun,
18
+ saveMetrics
19
+ };
20
+ //# sourceMappingURL=metrics-KXGNFAAB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}