{"version":3,"file":"mcp.mjs","names":[],"sources":["../src/mcp.ts"],"sourcesContent":["/**\n * MCP server for agent-perms — sync daemon.\n *\n * Sits as a background MCP server that keeps native agent config files\n * bidirectionally synced with `.agents/permissions.json`. Exposes no tools.\n *\n * Modes (configured via `.agents/permissions.json` → `sync.mode`):\n *   - `\"sync\"`: One-shot sync at startup, then stay alive (passive).\n *   - `\"watch\"`: Continuous sync via filesystem watching.\n *   - `false` / absent: No sync (just a passive MCP server).\n *\n * The project directory is discovered via `roots/list` from the MCP client,\n * or falls back to `process.cwd()`.\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\n\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync, watch } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { AgentPermissionPolicy } from \"./schema.ts\";\nimport { parseJson, validatePolicy } from \"./agent-files.ts\";\nimport { sync } from \"./sync.ts\";\n\n// ---------------------------------------------------------------------------\n// Config resolution\n// ---------------------------------------------------------------------------\n\ninterface SyncConfig {\n  mode: \"sync\" | \"watch\" | false;\n  backup: boolean;\n}\n\nfunction resolveSyncConfig(\n  policy: AgentPermissionPolicy | undefined,\n): SyncConfig {\n  if (policy === undefined) return { mode: false, backup: false };\n  const s = policy.sync;\n  return {\n    mode: s?.mode ?? false,\n    backup: s?.backup ?? false,\n  };\n}\n\nasync function loadRootPolicy(\n  rootDir: string,\n): Promise<AgentPermissionPolicy | undefined> {\n  const filePath = join(rootDir, \".agents\", \"permissions.json\");\n  if (!existsSync(filePath)) return undefined;\n  const content = await readFile(filePath, \"utf-8\").catch(() => undefined);\n  if (content === undefined) return undefined;\n  const parsed = parseJson(content, filePath);\n  if (!parsed.ok) return undefined;\n  const validated = validatePolicy(parsed.value);\n  if (!validated.ok) return undefined;\n  return validated.value;\n}\n\n// ---------------------------------------------------------------------------\n// Sync operations\n// ---------------------------------------------------------------------------\n\nasync function performSync(cwd: string, config: SyncConfig): Promise<void> {\n  if (config.mode === false) return;\n\n  await sync({\n    cwd,\n    up: Infinity,\n    with: [],\n    without: [],\n    yes: true,\n    dryRun: false,\n    create: true,\n    verbose: false,\n    backup: config.backup,\n  });\n}\n\nfunction startWatcher(cwd: string, config: SyncConfig): void {\n  if (config.mode !== \"watch\") return;\n\n  const watchedDirs = new Set<string>();\n\n  // Watch .agents/ and native config locations\n  const watchPaths = [\n    join(cwd, \".agents\"),\n    cwd, // native configs live at project root\n  ];\n\n  for (const dir of watchPaths) {\n    if (!existsSync(dir)) continue;\n    if (watchedDirs.has(dir)) continue;\n    watchedDirs.add(dir);\n\n    try {\n      const watcher = watch(dir, { recursive: false }, (_event, filename) => {\n        if (filename === null) return;\n        // Only react to relevant config files\n        if (\n          filename === \"permissions.json\" ||\n          filename === \"permissions.local.json\" ||\n          filename === \"settings.json\" ||\n          filename === \"settings.local.json\" ||\n          filename === \"opencode.json\" ||\n          filename === \".crush.json\" ||\n          filename === \"codex.toml\"\n        ) {\n          void performSync(cwd, config);\n        }\n      });\n      watcher.on(\"error\", () => {\n        // Silently ignore watch errors\n      });\n    } catch {\n      // fs.watch may throw on some platforms\n    }\n  }\n\n  process.stderr.write(\n    `[agent-perms-mcp] Watching ${cwd} for config changes (mode: watch)\\n`,\n  );\n}\n\n// ---------------------------------------------------------------------------\n// MCP server\n// ---------------------------------------------------------------------------\n\nasync function main(): Promise<void> {\n  const server = new McpServer(\n    { name: \"agent-perms\", version: \"0.1.0\" },\n    {\n      instructions:\n        \"agent-perms sync daemon. Keeps native agent config files \" +\n        \"bidirectionally synced with .agents/permissions.json. \" +\n        \"Configured via sync.mode in .agents/permissions.json.\",\n    },\n  );\n\n  // Connect transport\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n\n  // After connection, use cwd as project root\n  const projectRoot = resolve(process.cwd());\n\n  process.stderr.write(`[agent-perms-mcp] Started (root: ${projectRoot})\\n`);\n\n  // Load config and perform initial sync\n  const policy = await loadRootPolicy(projectRoot);\n  const config = resolveSyncConfig(policy);\n\n  process.stderr.write(\n    `[agent-perms-mcp] Config: mode=${String(config.mode)}, backup=${String(config.backup)}\\n`,\n  );\n\n  if (config.mode === \"sync\" || config.mode === \"watch\") {\n    await performSync(projectRoot, config);\n    process.stderr.write(\"[agent-perms-mcp] Initial sync complete\\n\");\n  }\n\n  if (config.mode === \"watch\") {\n    startWatcher(projectRoot, config);\n  }\n\n  // Keep alive — MCP server handles the event loop\n}\n\nmain().catch((error: unknown) => {\n  const message = error instanceof Error ? error.message : String(error);\n  process.stderr.write(`[agent-perms-mcp] Fatal: ${message}\\n`);\n  process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAS,kBACP,QACY;CACZ,IAAI,WAAW,QAAW,OAAO;EAAE,MAAM;EAAO,QAAQ;EAAO;CAC/D,MAAM,IAAI,OAAO;CACjB,OAAO;EACL,MAAM,GAAG,QAAQ;EACjB,QAAQ,GAAG,UAAU;EACtB;;AAGH,eAAe,eACb,SAC4C;CAC5C,MAAM,WAAW,KAAK,SAAS,WAAW,mBAAmB;CAC7D,IAAI,CAAC,WAAW,SAAS,EAAE,OAAO;CAClC,MAAM,UAAU,MAAM,SAAS,UAAU,QAAQ,CAAC,YAAY,OAAU;CACxE,IAAI,YAAY,QAAW,OAAO;CAClC,MAAM,SAAS,UAAU,SAAS,SAAS;CAC3C,IAAI,CAAC,OAAO,IAAI,OAAO;CACvB,MAAM,YAAY,eAAe,OAAO,MAAM;CAC9C,IAAI,CAAC,UAAU,IAAI,OAAO;CAC1B,OAAO,UAAU;;AAOnB,eAAe,YAAY,KAAa,QAAmC;CACzE,IAAI,OAAO,SAAS,OAAO;CAE3B,MAAM,KAAK;EACT;EACA,IAAI;EACJ,MAAM,EAAE;EACR,SAAS,EAAE;EACX,KAAK;EACL,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,QAAQ,OAAO;EAChB,CAAC;;AAGJ,SAAS,aAAa,KAAa,QAA0B;CAC3D,IAAI,OAAO,SAAS,SAAS;CAE7B,MAAM,8BAAc,IAAI,KAAa;CAGrC,MAAM,aAAa,CACjB,KAAK,KAAK,UAAU,EACpB,IACD;CAED,KAAK,MAAM,OAAO,YAAY;EAC5B,IAAI,CAAC,WAAW,IAAI,EAAE;EACtB,IAAI,YAAY,IAAI,IAAI,EAAE;EAC1B,YAAY,IAAI,IAAI;EAEpB,IAAI;GAgBF,AAfgB,MAAM,KAAK,EAAE,WAAW,OAAO,GAAG,QAAQ,aAAa;IACrE,IAAI,aAAa,MAAM;IAEvB,IACE,aAAa,sBACb,aAAa,4BACb,aAAa,mBACb,aAAa,yBACb,aAAa,mBACb,aAAa,iBACb,aAAa,cAEb,AAAK,YAAY,KAAK,OAAO;KAG1B,CAAC,GAAG,eAAe,GAExB;UACI;;CAKV,QAAQ,OAAO,MACb,8BAA8B,IAAI,qCACnC;;AAOH,eAAe,OAAsB;CACnC,MAAM,SAAS,IAAI,UACjB;EAAE,MAAM;EAAe,SAAS;EAAS,EACzC,EACE,cACE,wKAGH,CACF;CAGD,MAAM,YAAY,IAAI,sBAAsB;CAC5C,MAAM,OAAO,QAAQ,UAAU;CAG/B,MAAM,cAAc,QAAQ,QAAQ,KAAK,CAAC;CAE1C,QAAQ,OAAO,MAAM,oCAAoC,YAAY,KAAK;CAI1E,MAAM,SAAS,kBAAkB,MADZ,eAAe,YAAY,CACR;CAExC,QAAQ,OAAO,MACb,kCAAkC,OAAO,OAAO,KAAK,CAAC,WAAW,OAAO,OAAO,OAAO,CAAC,IACxF;CAED,IAAI,OAAO,SAAS,UAAU,OAAO,SAAS,SAAS;EACrD,MAAM,YAAY,aAAa,OAAO;EACtC,QAAQ,OAAO,MAAM,4CAA4C;;CAGnE,IAAI,OAAO,SAAS,SAClB,aAAa,aAAa,OAAO;;AAMrC,MAAM,CAAC,OAAO,UAAmB;CAC/B,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;CACtE,QAAQ,OAAO,MAAM,4BAA4B,QAAQ,IAAI;CAC7D,QAAQ,KAAK,EAAE;EACf"}