{"version":3,"file":"mcp.cjs","names":["join","existsSync","readFile","parseJson","validatePolicy","sync","defaultOmpAgentDir","watch","McpServer","createPermissionPrompt","loadPolicy","PermissionPromptRequest","promptToolContent","resolve","StdioServerTransport"],"sources":["../src/mcp.ts"],"sourcesContent":["/**\n * MCP server for agent-perms: a sync daemon, and optionally a permission-prompt tool.\n *\n * Sits as a background MCP server that keeps native agent config files bidirectionally synced with\n * `.agents/permissions.json`. By default it exposes no tools. With the permission-prompt mode on it\n * exposes one, `permission_prompt`, which answers a host's permission prompts from the policy (see\n * `prompt-tool.ts`).\n *\n * Modes (configured via `.agents/permissions.json` → `sync.mode`):\n *\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 the server's working directory.\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 {\n  defaultOmpAgentDir,\n  parseJson,\n  validatePolicy,\n} from \"./agent-files.ts\";\nimport { loadPolicy } from \"./loader.ts\";\nimport {\n  createPermissionPrompt,\n  PermissionPromptRequest,\n  promptToolContent,\n  type AskHandler,\n} from \"./prompt-tool.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    ompAgentDir: defaultOmpAgentDir(),\n    ompGlobal: false,\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\nexport interface McpServerOptions {\n  /**\n   * Expose the `permission_prompt` tool. `onAsk` settles calls the policy asks about; without it\n   * they are denied.\n   */\n  permissionPrompt?: { onAsk?: AskHandler };\n}\n\n/** The permission-prompt tool's name; a host refers to it as `mcp__<server name>__permission_prompt`. */\nconst PERMISSION_PROMPT_TOOL = \"permission_prompt\";\n\n/**\n * Build the server for a project root without connecting it. It exposes no tools unless the\n * permission-prompt mode is on.\n */\nexport function createMcpServer(\n  root: string,\n  options: McpServerOptions,\n): McpServer {\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  const { permissionPrompt } = options;\n  if (permissionPrompt !== undefined) {\n    const answer = createPermissionPrompt({\n      loadPolicy: () => loadPolicy({ cwd: root }),\n      root,\n      ...(permissionPrompt.onAsk === undefined\n        ? {}\n        : { onAsk: permissionPrompt.onAsk }),\n    });\n    server.registerTool(\n      PERMISSION_PROMPT_TOOL,\n      {\n        description:\n          \"Answers a permission prompt from the agent-perms policy: allow or deny the tool call described by tool_name and input.\",\n        inputSchema: PermissionPromptRequest.shape,\n      },\n      async (request, extra) =>\n        promptToolContent(await answer(request, extra.signal)),\n    );\n  }\n\n  return server;\n}\n\n/**\n * Serve on stdio from the current directory, running the sync the policy's `sync.mode` asks for.\n */\nexport async function startMcpServer(options: McpServerOptions): Promise<void> {\n  const projectRoot = resolve(process.cwd());\n  const server = createMcpServer(projectRoot, options);\n\n  // Connect transport\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n\n  process.stderr.write(\n    `[agent-perms-mcp] Started (root: ${projectRoot}, permission prompt: ${options.permissionPrompt === undefined ? \"off\" : \"on\"})\\n`,\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"],"mappings":"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"}