[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-93184":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":11,"openIssues":13,"contributorsCount":13,"subscribersCount":13,"size":13,"stars1d":13,"stars7d":13,"stars30d":13,"stars90d":13,"forks30d":13,"starsTrendScore":13,"compositeScore":14,"rankGlobal":9,"rankLanguage":9,"license":15,"archived":16,"fork":16,"defaultBranch":17,"hasWiki":18,"hasPages":16,"topics":19,"createdAt":9,"pushedAt":9,"updatedAt":30,"readmeContent":31,"aiSummary":32,"trendingCount":13,"starSnapshotCount":13,"syncStatus":33,"lastSyncTime":34,"discoverSource":35},93184,"madcop","linmy666\u002Fmadcop","linmy666","MadCop — local-first AI agent desktop workstation (v0.9). Multi-model chat, tool-use, MCP servers, 12 workflow modes, knowledge base, AI design tool, persistent workspace.",null,"TypeScript",62,1,0,40.9,"Other",false,"main",true,[20,21,22,23,24,25,26,27,28,29],"ai-agent","desktop-app","electron","llm","local-first","mcp","python","typescript","vue","workflow-automation","2026-07-22 04:02:08","# MadCop\n\n**A local-first AI agent desktop workstation.**\n\nMadCop is a cross-platform desktop application that brings the power of modern LLMs into a private, agentic workflow. It runs as a single Electron binary on macOS, Windows, and Linux, talks to any OpenAI-compatible API endpoint, and keeps your conversations, files, and knowledge base entirely on your machine. No cloud lock-in, no per-seat fees, no data leaving the device.\n\nThis document explains the *why* behind the major design decisions — written for product managers and reviewers who want to understand how the system is put together, not just a list of features.\n\n---\n\n## Preview\n\n\u003Ctable>\n  \u003Ctr>\n    \u003Ctd align=\"center\">\u003Cb>Multi-model chat\u003C\u002Fb>\u003Cbr\u002F>Sidebar · Tabs · Workspace · Tool calls\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd>\u003Cimg src=\"docs\u002Fscreenshots\u002F01-chat.png\" alt=\"Chat interface\"\u002F>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd align=\"center\">\u003Cb>Streaming report generation\u003C\u002Fb>\u003Cbr\u002F>LLM streaming output with tool calls\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd>\u003Cimg src=\"docs\u002Fscreenshots\u002F02-generating.png\" alt=\"Generating report\"\u002F>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd align=\"center\">\u003Cb>Rich report with tables\u003C\u002Fb>\u003Cbr\u002F>Markdown + data visualization\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd>\u003Cimg src=\"docs\u002Fscreenshots\u002F03-report.png\" alt=\"Generated report\"\u002F>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n\u003C\u002Ftable>\n\n---\n\n## What problem is MadCop solving?\n\nThe dominant LLM desktop clients (ChatGPT, Claude.ai, Gemini) are excellent chat surfaces but they assume a specific shape of interaction: one human, one model, one conversation at a time, with vendor-managed tools and memory. That works for \"answer this question\" but it does not work for \"I need to (a) search the web, (b) read a local file, (c) summarise the result, (d) save a Markdown report to disk\" — which is a normal afternoon for a product manager, analyst, or engineer.\n\nMadCop is built around three observations:\n\n1. **The work is multi-step.** A single useful task almost always needs several LLM calls with intermediate state. A chat that cannot sequence, branch, persist, or call external tools caps you at \"Q&A on a webpage\".\n2. **The data is local.** A working file directory, a Notion export, a `git` history, screenshots, contracts — the substance of real work sits on the user's disk. A client that can only attach a single file per message (or pays per-token for cloud RAG) punishes the people who already have the answer.\n3. **The model is a choice, not a vendor.** A senior engineer might need different models for different tasks — one for refactoring code, another for Chinese-language analysis. A privacy-sensitive user wants a self-hosted endpoint. A startup wants to A\u002FB-test cost. A single-vendor client forces that decision at sign-up and never lets you revisit it.\n\nSo the design goal is: a thin local shell that lets the user pick their own model, hand it their own files, and let the system orchestrate the rest. Everything else is in service of that.\n\n---\n\n## How is the system structured?\n\n```\n┌─────────────────────────────────────────────────────────┐\n│                    Electron Shell                       │\n│  ┌────────────────────┐   ┌────────────────────────┐  │\n│  │  Vue 3 + Pinia +   │   │   Python Backend       │  │\n│  │  Tailwind v4 UI    │   │   (FastAPI + Uvicorn)  │  │\n│  │  (Renderer Process)│←→│                         │  │\n│  └────────────────────┘   │  ┌──────────────────┐  │  │\n│                            │  │  LLM Client       │  │  │\n│  ┌────────────────────┐   │  │  (OpenAI Compat)  │  │  │\n│  │  Workspace Picker  │   │  └──────────────────┘  │  │\n│  │  Sidebar \u002F Tabs    │   │  ┌──────────────────┐  │  │\n│  │  Chat \u002F Composer   │   │  │  Tool Registry    │  │  │\n│  └────────────────────┘   │  │  + MCP Bridge      │  │  │\n│                            │  └──────────────────┘  │  │\n│                            │  ┌──────────────────┐  │  │\n│                            │  │  Workflow Engine  │  │  │\n│                            │  │  + 12 Modes       │  │  │\n│                            │  └──────────────────┘  │  │\n│                            │  ┌──────────────────┐  │  │\n│                            │  │  Memory Pipeline  │  │  │\n│                            │  │  (5-tier)         │  │  │\n│                            │  └──────────────────┘  │  │\n│                            └────────────────────────┘  │\n└─────────────────────────────────────────────────────────┘\n```\n\nThe architecture is intentionally **two processes talking over HTTP** — not one big monolith — for three reasons:\n\n1. **The Python backend can be deployed headless.** The same `madcop.server` module runs without Electron (over plain HTTP\u002FWebSocket) for CI, scripting, or embedding in other tools.\n2. **The Vue renderer is portable.** Tauri, Web, or a custom shell can reuse the frontend as-is because it never assumes anything about the backend's runtime.\n3. **Process isolation protects the user.** A misbehaving tool call cannot freeze the UI; the renderer survives a hung request and can cancel it.\n\n---\n\n## How does model routing work?\n\nRouting is the single most important design question in any LLM client. MadCop routes on **three axes simultaneously**:\n\n### Axis 1: Per-request model selection\n\nThe user configures one or more model providers in the Settings panel. Each provider is a name + base URL + API key + model id (any OpenAI-compatible endpoint). The frontend exposes the active provider as a `useSettingsStore.currentModel` ref; the backend receives a `model` field on every `POST \u002Fapi\u002Fchat` and forwards it to the OpenAI-compatible client.\n\nThis means **there is no \"default model\" hard-coded in the backend**. If you don't configure one, you get a clear error on the first request. The model lives in user data, not in the product.\n\n### Axis 2: Per-session tool registry\n\nDifferent models from different vendors have wildly different tool-use quality. The backend's tool dispatcher handles this by tuning the system prompt per model family. The backend's tool dispatcher (`madcop\u002Ftools\u002Fregistry.py`) registers the **same** toolset regardless of model, but the system prompt is tuned to nudge the model toward emitting function calls. Specifically, every chat request includes an explicit instruction:\n\n> \"When the user asks you to do anything that requires real-time information, you MUST call the `web_search` tool. Do not make up answers. Call the tool directly — do not output the tool's parameter description.\"\n\nThis works around the most common failure mode of self-hosted Chinese-tuned models (outputting the JSON schema as text instead of as a structured `tool_calls` array).\n\n### Axis 3: Intent classification → workflow mode\n\nA single chat turn can be either \"answer this\" (one LLM call) or \"search the web, write a report, save to disk\" (many LLM calls with tool side effects). The Mode Selector in the composer exposes **12 preset modes** from Google's \"Agent design patterns\" catalog:\n\n- `single_agent` — one LLM + tools, the default\n- `sequential` — Agent A → B → C, each consuming the previous output\n- `parallel` — same input fanned out to N workers, results merged\n- `coordinator` — a router agent classifies input, dispatches to a specialist\n- `hierarchical` — top-level plan, sub-tasks, recurse\n- `swarm` — many agents with shared blackboard\n- `loop` — produce, critique, revise until a quality threshold is met\n- `review_critique` — producer + reviewer\n- `iterative_refine` — feedback loop with a separate evaluator\n- `human_in_loop` — explicit user checkpoint\n- `react` — Reason + Act\n- `custom` — user-defined graph\n\nWhen a mode is selected, the chat endpoint hands the request to the workflow engine (`madcop\u002Fworkflow\u002Fexecutor.py`), which walks the mode's graph of nodes. Each node is a Python class with an `execute(ctx)` method that receives the inputs from upstream nodes and yields a result. The engine emits SSE events at each step so the UI can show progress live.\n\nFor users who don't want to pick a mode, the frontend performs a lightweight keyword-based intent classifier (`_classifyAndPlan` in `chatStore.ts`) that detects multi-step requests (contains words like 调研 \u002F 搜索 \u002F 保存 \u002F 生成 \u002F 写到) and injects an inline task plan into the system prompt:\n\n> \"First, search using web_search. Second, generate the report and save it to ~\u002FDownloads\u002Fmadcop\u002F as report.md using write_file. Third, reply to the user when done.\"\n\nThis gives the user the *experience* of the multi-agent system without the cognitive overhead of picking a mode from a dropdown.\n\n---\n\n## How are tools registered and dispatched?\n\nThe tool system is the surface where \"agent\" stops being marketing and becomes real. MadCop's design is **a single registry that everything reads from**, with three extension points:\n\n1. **Built-in tools** — registered in `madcop\u002Ftools\u002F__init__.py::default_registry()`. These include:\n   - `web_search` (DuckDuckGo, no API key needed)\n   - `web_fetch` (httpx + BeautifulSoup-style HTML→text)\n   - `read_file` \u002F `write_file` \u002F `edit_file` (path-confined to allowlisted dirs)\n   - `weather` (wttr.in, no key)\n   - `clarify` (returns a structured question back to the user)\n\n2. **MCP servers** — any external Model Context Protocol server can be registered at startup via `madcop\u002Ftools\u002Fmcp.py`. The bridge translates MCP `tools\u002Fcall` into internal `Tool.__call__` invocations. This means a user can add a private internal API or a database client without forking MadCop.\n\n3. **User-defined Python tools** — any function decorated with `@tool(\"name\", description=\"...\")` is auto-registered. The decorator captures the signature and produces an OpenAI-compatible JSON schema. This is the path for one-off internal tools.\n\nThe dispatcher is stateless: each tool call is `registry.dispatch(tool_call) -> ToolResult`. The function looks up the tool by name, validates the arguments against the schema, calls the function, and wraps the return value in a `ToolResult(is_error=..., content=...)`. The chat loop serializes the result and appends it to the message history before the second LLM call.\n\nThe frontend's `ToolCallGroup.vue` component reads these results from the SSE stream and renders them as collapsible cards under the assistant message — so the user sees which tools were called, with what inputs, and what was returned.\n\n---\n\n## How does the workspace integration work?\n\nA core anti-pattern in many LLM clients is that the client doesn't know which directory the user is actually working in, so the model writes files to `os.getcwd()` of whatever process spawned it. In MadCop, this is solved with a `WorkspacePanel` component in the sidebar that:\n\n1. Lists the current workspace directory and lets the user pick a new one via a native folder picker (Electron `dialog.showOpenDialog`).\n2. Stores the selected path in `localStorage` as `madcop_workspace_dir` *and* on the backend via `POST \u002Fapi\u002Fworkspace\u002Fdir`.\n3. On every chat request, the frontend reads `madcop_workspace_dir` and prepends a system message: *\"Your current working directory is `\u002FUsers\u002F...\u002Fmadcop`. When the user asks you to save files, generate reports, or write code, save them under that directory.\"*\n\nThe backend's `WriteFileTool.__init__` accepts an `allowed_dirs` list and the chat handler passes `[_WORKSPACE_DIR, os.getcwd(), os.path.expanduser('~')]` to it. This means a user who picks `~\u002FDocuments\u002Fprojects\u002Ffoo` cannot accidentally have the model write into `~\u002FLibrary\u002FApplication Support\u002Fmadcop\u002F`.\n\nThe `DirectoryPicker` component in the composer echoes the same path so the user always sees where files would go.\n\n---\n\n## How does persistence work?\n\nThe frontend is responsible for **local-first persistence of the conversation log** via `chatStore._persistSession()`:\n\n- Every `sendMessage` writes the current messages + title of that session to `localStorage[\"madcop_chat_messages\"]`.\n- On reload, `getSession()` is called the first time a session id is referenced; if the in-memory state has no messages but `localStorage` does, the messages are hydrated.\n- The tab list (`madcop_tabs`) and session metadata (`madcop_sessions`) are persisted the same way.\n\nThe reason this matters: the backend's session store uses UUIDs but the frontend's session ids are `session-${Date.now()}` (e.g. `session-1783…`). These are two different id schemes that would break hydration if you tried to map them through the backend. Instead, the frontend owns the conversation history and the backend is treated as stateless from the LLM's perspective. This is a deliberate trade: the backend can be restarted, the database can be wiped, the model can be swapped, and the user never loses their thread.\n\n---\n\n## How does the multi-agent routing actually work in practice?\n\nThe current v0.9 implementation has two layers:\n\n**Layer 1: Local keyword intent classification.** Before every chat, the frontend's `chatStore._classifyAndPlan` runs a regex over the user's input:\n\n- contains `调研 \u002F 搜索 \u002F 查 \u002F 报告 \u002F 行业` → `needsResearch = true`\n- contains `保存 \u002F 生成 \u002F 写到 \u002F md \u002F 存到` → `needsFileWrite = true`\n- contains `代码 \u002F 实现 \u002F 写个 \u002F 做个` → `needsCode = true`\n\nIf any of these match, a plan prompt is injected into the system message:\n\n> \"## Task plan\n> Step 1: Search the web. Call `web_search` to find relevant information.\n> Step 2: Generate a file. Use `write_file` to save the report to `\u002FUsers\u002F...\u002Fmadcop\u002Freport.md`.\n> Final: Reply to the user.\"\n\nThis is intentionally **not a real LLM call** — it adds no latency to the chat flow. The trade-off is that regex matching misses nuance (e.g. \"查一下明天的天气\" wouldn't match), but the most common multi-step requests (research + write) are caught reliably.\n\n**Layer 2: Per-mode workflow execution.** The 12 preset modes are not \"tricks\" — they are real graph executors. When the user explicitly picks `coordinator` from the Mode Selector, the chat endpoint hands the request to the workflow engine, which:\n\n1. Spins up a \"RequirementAnalysis\" node that uses the LLM to extract structured intent from the user input.\n2. Picks a specialist agent based on the extracted intent (a `coordinator` LLM call).\n3. Dispatches the actual work to that specialist, with a tailored prompt.\n4. A `synthesis` node merges results and streams the final response.\n\nEach node is a Python class implementing `execute(ctx) -> NodeResult`. The engine persists each step to a `workflow_node_runs` table so the trace can be replayed. The UI shows a live graph of which node is currently running with timing.\n\nFor users who don't want to pick a mode, the keyword layer (Layer 1) gives them most of the benefit.\n\n---\n\n## How does the design tool work?\n\nThe `DesignPage.vue` is a separate page reachable from the sidebar. It contains:\n\n- A text prompt on the left.\n- A live canvas on the right showing the generated UI tree as nested Vue components.\n- 11 component types (Button, Input, Card, etc.) plus nested Container.\n- Per-page state, undo\u002Fredo (10 levels), and a `.madcop` file format for save\u002Fload.\n- Multi-page projects with a directory tree.\n- Export to HTML.\n\nThe interesting design decision here is that the canvas is **not** an iframe of a separate renderer. It's a Vue tree generated by an LLM call to `DesignTool` (in `madcop\u002Fdesign\u002F`), which produces a structured `DesignData` JSON that the page then renders as live Vue components. The user can click on any element to select it, drag to move, and tweak properties in an inspector. Because the canvas is \"real\" Vue and not a screenshot, the elements are interactive — you can type into a generated `Input` and see the value update.\n\n---\n\n## How is quality controlled?\n\nA test suite of **1,321 tests** (all passing) covers:\n\n- The memory store, the consolidation \u002F pruning pipeline, the prescreen for sensitive content.\n- The statistics engine (CUSUM, z-score anomaly detection, counterfactual cost).\n- The design tool's component tree compiler.\n- The workflow engine's executor and all 12 modes.\n- The backend's HTTP routes and SSE streaming.\n\nThe key test discipline is that **the backend is exercised as a black box** — the tests build a FastAPI app, make real HTTP requests, and assert on the response shape. This means the same tests catch the kind of regression that a unit test of `madcop.brain.store` would miss (e.g. a routing change that breaks the URL pattern).\n\nThe frontend has lightweight Vitest coverage; most UI behavior is verified manually because the Vue\u002FPinia state is straightforward to inspect in the browser devtools and the failure modes are visual.\n\n---\n\n## What is the intended audience?\n\nMadCop is built for **power users who have a clear sense of \"I have a task\" and want to do it in one window**:\n\n- Product managers writing briefs that pull from internal docs and external research, then save as Markdown to a project folder.\n- Engineers who want a chat interface that can also `read_file` a source file, `edit_file` a function, and explain what changed.\n- Analysts who need a tool that can pull data from the web, structure it, and save the result to disk in a format a colleague can read.\n- Researchers who want a local RAG-style workflow over their own document folder, with the model picked from a dropdown rather than dictated by the vendor.\n\nIt is **not** built for:\n\n- Users who want a \"polished\" SaaS experience. The UI is functional, not pretty.\n- Users who want a cloud sync model. There is no cloud sync. (If you want one, file an issue.)\n- Users who are not comfortable with a config screen full of model API keys. The trade-off for \"any model you want\" is \"you have to bring your own API key\".\n\n---\n\n## What is the deployment story?\n\nThe intended install is:\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Flinmy666\u002Fmadcop.git\ncd madcop\npip install -e .\npython -m madcop.server  # terminal 1: backend on :8765\ncd desktop\nbun install\nbun run build:electron  # terminal 2: build the Electron app\n.\u002Fnode_modules\u002Felectron\u002Fdist\u002FElectron.app\u002FContents\u002FMacOS\u002FElectron \\\n  .\u002Felectron-dist\u002Fmain.cjs --no-sandbox\n```\n\nFor a packaged distribution (DMG \u002F EXE \u002F AppImage), see `desktop\u002Felectron-builder` config. The expected distribution is a single ~150MB binary that bundles Python via a tree-shaken `pymalloc` style or via the user's system Python, depending on platform.\n\nThe model API key is supplied by the user at first launch via the Settings panel. There is no built-in default, no anonymous telemetry, and no required account. The product is the desktop app, not a service.\n\n---\n\n## What is the future direction?\n\nThe roadmap in priority order:\n\n1. **Local inference** — integrate MLX (macOS) and llama.cpp (cross-platform) so a user with a 64GB Mac can run a 70B model entirely offline. This unlocks the \"no API key needed\" path for privacy-sensitive users.\n2. **Visual understanding** — the current API endpoint is text-only; once a multimodal model is wired in, the design tool and the workflow editor both benefit from \"show me a screenshot, generate the component that matches it\".\n3. **Skill marketplace** — a \"skill\" is a named workflow + tool bundle (e.g. \"competitor research\", \"weekly status report\"). The current `\u002Fapi\u002Fskills` endpoint is the local-only seed; the next step is opt-in cloud discovery with a rating system.\n4. **Mobile companion** — a thin iOS\u002FAndroid app that talks to the desktop backend over LAN. The local-first model is a good fit for this; the alternative is \"no mobile at all\".\n\nEach item is independently shippable. None of them require rewriting what already works.\n\n---\n\n## Project structure\n\n```\nmadcop\u002F\n├── desktop\u002F                  Electron desktop (Vue 3 + Pinia + Tailwind v4)\n│   ├── src\u002Fvue\u002F              ~155 .vue files (renderer)\n│   ├── public\u002F               Static assets (mascot, icons)\n│   ├── electron\u002F             Main process scripts\n│   └── theme\u002F                CSS theme system\n│\n├── madcop\u002F                   Python backend\n│   ├── server\u002F               FastAPI app, route handlers, SSE streaming\n│   ├── llm\u002F                  OpenAI-compatible client + model config\n│   ├── tools\u002F                Tool registry, built-in tools, MCP bridge, MCP server\n│   │   ├── mac_ax.py         macOS AXAPI bridge (JXA\u002Fosascript)\n│   │   ├── mac_ax_mcp_server.py  Standalone MCP server wrapping mac_ax\n│   │   ├── mcp.py            MCP client (connect to external servers)\n│   │   ├── registry.py       Tool ABC, FunctionTool, ToolRegistry\n│   │   ├── computer.py       ComputerUseTool (screenshot, click, AXAPI)\n│   │   ├── permissions.py    Permission gating for dangerous actions\n│   │   ├── files.py          Read\u002Fwrite\u002Fedit file tools\n│   │   ├── web.py            Web search + fetch tools\n│   │   ├── memory.py         Agent-managed long-term memory tools\n│   │   ├── cron.py           Cron scheduling tools\n│   │   ├── weather.py        Weather lookup tool\n│   │   ├── clarify.py        Ask-user-for-clarification tool\n│   │   ├── sandbox.py        Bash\u002Fshell execution sandbox\n│   │   ├── eventbus.py       Event bus + webhook subscriptions\n│   │   └── docker_sandbox.py Docker container sandbox\n│   ├── workflow\u002F             Workflow engine + 12 mode presets + node types\n│   ├── agent_network\u002F        Agent network API\n│   ├── memory\u002F               5-tier memory system (episodic \u002F semantic \u002F reflective \u002F scenario \u002F persona \u002F insight)\n│   ├── training\u002F             Continuous learning (local feedback, opt-in)\n│   ├── arena\u002F                Multi-LLM comparison endpoint\n│   ├── design\u002F               AI design tool backend\n│   ├── analysis\u002F             Supply-chain analysis (CUSUM, counterfactual)\n│   └── planning\u002F             Heuristic planners (safety stock, EOQ, ABC)\n│\n├── docs\u002F                     Documentation\n│   ├── screenshots\u002F          README product screenshots\n│   ├── skills\u002F               Reusable skill definitions (SKILL.md format)\n│   ├── img\u002F                  Historical assets\n│   ├── design-tool\u002F          Design tool docs\n│   └── workflow-editor\u002F      Workflow editor docs\n│\n├── tests\u002F                    1,300+ pytest tests (backend-focused)\n├── start.sh                  One-command startup script\n├── README.md\n├── LICENSE\n└── pyproject.toml\n```\n\n---\n\n## Quick Start\n\n```bash\n# 1. Clone & build\ngit clone https:\u002F\u002Fgithub.com\u002Flinmy666\u002Fmadcop.git\ncd madcop\nchmod +x start.sh\n.\u002Fstart.sh\n\n# Or manually:\n# Backend:\npip install -e .\nuvicorn madcop.server.app:app --host 127.0.0.1 --port 8765 --reload\n\n# Frontend (separate terminal):\ncd desktop\nnpm install\nnpx vite build --config vite.vue.config.ts\nnpm run dev\n```\n\n**macOS Computer Use** requires two permissions:\n1. **Accessibility** — System Settings → Privacy & Security → Accessibility → add Terminal\u002FElectron\n2. **Screen Recording** — System Settings → Privacy & Security → Screen Recording → add Terminal\u002FElectron\n\n---\n\n## macOS Computer Use (mac_ax)\n\nMadCop includes a **pure-JXA** macOS Accessibility API bridge — no pyobjc, no ctypes, no visual model. It's available two ways:\n\n### 1. Built-in tool (via MadCop's agent)\n```json\n\u002F\u002F The agent calls computer_use tool when it needs to interact with the screen\n{ \"action\": \"screenshot\", ... }\n{ \"action\": \"find_element\", \"label\": \"搜索\", \"role\": \"AXButton\" }\n{ \"action\": \"click\", \"x\": 400, \"y\": 300 }\n{ \"action\": \"launch_app\", \"name\": \"Calculator\" }\n```\n\n### 2. Standalone MCP Server (for any MCP client)\n```bash\n# Run the MCP server\npython3 -m madcop.tools.mac_ax_mcp_server\n\n# Claude Desktop → Config → MCP Servers → add:\n# {\n#   \"macos-axapi\": {\n#     \"command\": \"python3\",\n#     \"args\": [\"-m\", \"madcop.tools.mac_ax_mcp_server\"]\n#   }\n# }\n```\n\nThis exposes **10 tools**: `check_permission`, `check_screen_recording`, `list_apps`, `list_windows`, `focus_app`, `launch_app`, `find_element`, `click`, `type_text`, `press_key`.\n\nAny MCP-compatible client (Claude Desktop, Cursor, VS Code, etc.) can connect and use macOS UI automation.\n\n### Architecture\n```\n┌──────────────┐     MCP\u002FJSON-RPC      ┌─────────────────┐\n│  MCP Client  │ ←─── over stdio ───→  │  mac_ax MCP     │\n│  (Claude,    │                        │  Server          │\n│   Cursor,    │     tools\u002Flist         │                  │\n│   MadCop...) │     tools\u002Fcall         │  osascript       │\n│              │                        │  → JXA           │\n└──────────────┘                        │  → AXAPI         │\n                                        └─────────────────┘\n```\n\n---\n\n## License\n\nMadCop is licensed under the **GNU Affero General Public License v3.0 (AGPL-3.0)**. The full license text is in [LICENSE](LICENSE).\n\nIn short:\n\n- You may use, modify, and distribute MadCop for personal or internal use freely.\n- If you run a modified MadCop as a **network service** (e.g. a hosted AI agent workstation for your customers), you **must release the full source code** of your modified version to those users, under the same AGPL-3.0.\n- Closed-source SaaS forks are explicitly prohibited by the AGPL. If you want to use MadCop in a commercial product you do not wish to open-source, contact the author for a separate commercial license.\n\nSee the additional notice in [LICENSE](LICENSE) for the full text and rationale.\n\n\n## Author\n\nLin Ruihan (林芮翰) — Product Manager \u002F engineer.\n- GitHub: [@linmy666](https:\u002F\u002Fgithub.com\u002Flinmy666)\n- Email: chuiniu@me.com\n","MadCop 是一款本地优先的 AI 智能体桌面工作站，面向需要多步协作式 AI 工作流的专业用户。它基于 Electron 构建，支持多模型切换、工具调用（MCP 协议）、12 种预设工作流模式、本地知识库与持久化工作区，并提供富文本报告生成（含表格与图表）。所有数据与对话全程离线运行，不上传云端，兼容任意 OpenAI 兼容 API。适用于产品\u002F数据分析、工程文档处理、私有知识管理等需结合本地文件、多阶段推理与模型自主选型的场景。",2,"2026-07-12 02:30:07","CREATED_QUERY"]