cairn-mcp 0.1.0__tar.gz

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.
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "cairn",
3
+ "displayName": "Cairn",
4
+ "version": "0.1.0",
5
+ "description": "Log Claude Code work sessions, file edits, and tasks to your Cairn account. Bundles the Cairn MCP server (11 tools) and the session + file-edit auto-logging hooks.",
6
+ "author": {
7
+ "name": "cybort360",
8
+ "url": "https://github.com/cybort360"
9
+ },
10
+ "homepage": "https://github.com/cybort360/cairn",
11
+ "repository": "https://github.com/cybort360/cairn",
12
+ "keywords": ["work-tracking", "productivity", "mcp", "logging", "tasks"]
13
+ }
@@ -0,0 +1,28 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ .venv/
5
+
6
+ # Vercel
7
+ .vercel
8
+ .env*.local
9
+
10
+ # Superseded legacy code — kept on disk, not part of the product repo.
11
+ # webapp.py: pre-serverless SQLite monolith (replaced by lib/ + api/ + dev.py)
12
+ # server.py: old local-SQLite MCP (replaced by the cloud client in agent/)
13
+ /webapp.py
14
+ /server.py
15
+
16
+ # OS
17
+ .DS_Store
18
+
19
+ # Playwright test artifacts
20
+ .playwright-mcp/
21
+ *.png
22
+
23
+ # Subagent-driven dev scratch (ledger, briefs, diffs)
24
+ .superpowers/
25
+
26
+ # Python build artifacts
27
+ dist/
28
+ *.egg-info/
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "cairn": {
4
+ "command": "uv",
5
+ "args": ["run", "--directory", "${CLAUDE_PLUGIN_ROOT}", "cairn-mcp"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adedeji Olamide
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: cairn-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server that logs Claude Code work sessions, file edits, and tasks to your Cairn account.
5
+ Project-URL: Homepage, https://github.com/cybort360/cairn
6
+ Project-URL: Repository, https://github.com/cybort360/cairn
7
+ Author: Adedeji Olamide
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: claude-code,mcp,model-context-protocol,productivity,work-tracking
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
14
+ Requires-Python: >=3.10
15
+ Requires-Dist: mcp[cli]>=1.0.0
16
+ Description-Content-Type: text/markdown
17
+
18
+ # The Cairn agent (MCP server + hooks)
19
+
20
+ This folder is the piece that runs on *your* machine, not on Vercel. It gives Claude Code a set of Cairn tools (log work, list tasks, weekly summaries, and so on) and, if you want, quietly records what you did after every session. Everything here is a thin HTTPS client over your Cairn cloud account — no local database, no second copy of your data.
21
+
22
+ What's in here:
23
+
24
+ - `cairn_mcp/` — the Python package, published to PyPI as [`cairn-mcp`](https://pypi.org/project/cairn-mcp/):
25
+ - `server.py` — the MCP server. Eleven tools (`work_log_add`, `task_create`, `daily_summary`, …).
26
+ - `client.py` — the tiny stdlib HTTP client the server and hooks share. Reads your token, talks to the API.
27
+ - `session_logger.py` — a SessionEnd hook. Turns a finished Claude Code session into one work-log entry.
28
+ - `file_logger.py` — a PostToolUse hook. Records a file-activity event whenever Claude writes or edits a file.
29
+ - `pyproject.toml` — packaging metadata. One dependency, `mcp[cli]`; one console script, `cairn-mcp`.
30
+ - `.mcp.json`, `hooks/hooks.json`, `.claude-plugin/` — the Claude Code plugin wiring.
31
+
32
+ ## Easiest: install as a plugin
33
+
34
+ If you're on Claude Code, skip the manual steps below — this repo is also a plugin marketplace. Two commands from inside Claude Code:
35
+
36
+ ```
37
+ /plugin marketplace add cybort360/cairn
38
+ /plugin install cairn@cairn
39
+ ```
40
+
41
+ That registers the `cairn` MCP server and both auto-logging hooks for you; there's no clone, no venv, and no `claude mcp add`. One prerequisite: the plugin runs the server through [`uv`](https://docs.astral.sh/uv/), so you need `uv` on your PATH (`curl -LsSf https://astral.sh/uv/install.sh | sh`). `uv` pulls `mcp[cli]` on its own, so you never manage a Python env.
42
+
43
+ You still need to drop your token in once — see step 2 of the manual install for where to get it:
44
+
45
+ ```bash
46
+ printf '%s' 'PASTE_YOUR_TOKEN' > ~/.cairn_token && chmod 600 ~/.cairn_token
47
+ ```
48
+
49
+ ## From PyPI (any MCP client)
50
+
51
+ Not on Claude Code, or want the server without the plugin? The package is on PyPI, so [`uv`](https://docs.astral.sh/uv/) can run it with nothing to clone or install:
52
+
53
+ ```bash
54
+ claude mcp add cairn --scope user -- uvx cairn-mcp
55
+ ```
56
+
57
+ `uvx cairn-mcp` is the command any MCP client can point at; `uvx` fetches the package and its deps on first run. Save your token first (see step 2 below). For a different client, use its own config — the command is the same.
58
+
59
+ ## Manual, from source
60
+
61
+ For when you'd rather not use `uv` at all. You need Python 3.10+ and the Claude Code CLI.
62
+
63
+ **1. Clone and install the package into a venv.**
64
+
65
+ ```bash
66
+ git clone https://github.com/cybort360/cairn.git
67
+ cd cairn/agent
68
+ python3 -m venv .venv
69
+ .venv/bin/pip install .
70
+ ```
71
+
72
+ That puts a `cairn-mcp` console script in `.venv/bin/`.
73
+
74
+ **2. Get your API token.** Sign in at the Cairn web app, open the account menu, and copy your API token. Save it where the client looks for it:
75
+
76
+ ```bash
77
+ printf '%s' 'PASTE_YOUR_TOKEN_HERE' > ~/.cairn_token
78
+ chmod 600 ~/.cairn_token
79
+ ```
80
+
81
+ (You can set `CAIRN_TOKEN` in the environment instead if you'd rather not write a file. The client checks `CAIRN_TOKEN` first, then `~/.cairn_token`.)
82
+
83
+ **3. Register the server with Claude Code.** Point it at the console script. `--scope user` makes the `cairn` tools available in every project, not just this one:
84
+
85
+ ```bash
86
+ claude mcp add cairn --scope user -- "$(pwd)/.venv/bin/cairn-mcp"
87
+ ```
88
+
89
+ **4. Check it connected.**
90
+
91
+ ```bash
92
+ claude mcp list
93
+ ```
94
+
95
+ You want to see `cairn: … ✔ Connected`. Inside a session the tools show up as `mcp__cairn__work_log_add`, `mcp__cairn__task_list`, and the rest.
96
+
97
+ That's the whole install. The hooks below are optional.
98
+
99
+ ## Optional: auto-logging hooks
100
+
101
+ The plugin install already wires these up. Set them by hand only if you went the manual route. The two hooks make Cairn fill itself in without you calling a tool: `session_logger` writes one entry summarizing each session when it ends; `file_logger` notes every file Claude touches. Both fail silently — a dropped network call or a missing token never interrupts your work. They use only the standard library, so plain `python3` runs them; no `mcp` needed.
102
+
103
+ Wire them into Claude Code's settings (`~/.claude/settings.json` for all projects). The commands run the package modules, so they need `cairn_mcp` on the path — `cd` into wherever you cloned (`$HOME/cairn/agent` assumes `~/cairn`; adjust it):
104
+
105
+ ```json
106
+ {
107
+ "hooks": {
108
+ "SessionEnd": [
109
+ {
110
+ "hooks": [
111
+ {
112
+ "type": "command",
113
+ "command": "cd \"$HOME/cairn/agent\" && python3 -m cairn_mcp.session_logger 2>/dev/null || true",
114
+ "timeout": 15,
115
+ "statusMessage": "Logging session to Cairn"
116
+ }
117
+ ]
118
+ }
119
+ ],
120
+ "PostToolUse": [
121
+ {
122
+ "matcher": "Write|Edit|MultiEdit",
123
+ "hooks": [
124
+ {
125
+ "type": "command",
126
+ "command": "cd \"$HOME/cairn/agent\" && python3 -m cairn_mcp.file_logger 2>/dev/null || true",
127
+ "timeout": 10
128
+ }
129
+ ]
130
+ }
131
+ ]
132
+ }
133
+ }
134
+ ```
135
+
136
+ A hard kill (crash, power loss, `kill -9`) skips the SessionEnd hook, so that one session won't be logged. Everything else — the normal exit, `/clear`, `/logout`, Ctrl-D — fires it.
137
+
138
+ ## Pointing at a different backend
139
+
140
+ By default the client talks to production. Override it with `CAIRN_API_URL` if you're running the web app locally or against a staging deploy:
141
+
142
+ ```bash
143
+ export CAIRN_API_URL="http://127.0.0.1:8765"
144
+ ```
145
+
146
+ The MCP server reads it the same way, so set it in the `claude mcp add` environment (`-e CAIRN_API_URL=…`) if you want the tools pointed somewhere other than prod.
@@ -0,0 +1,129 @@
1
+ # The Cairn agent (MCP server + hooks)
2
+
3
+ This folder is the piece that runs on *your* machine, not on Vercel. It gives Claude Code a set of Cairn tools (log work, list tasks, weekly summaries, and so on) and, if you want, quietly records what you did after every session. Everything here is a thin HTTPS client over your Cairn cloud account — no local database, no second copy of your data.
4
+
5
+ What's in here:
6
+
7
+ - `cairn_mcp/` — the Python package, published to PyPI as [`cairn-mcp`](https://pypi.org/project/cairn-mcp/):
8
+ - `server.py` — the MCP server. Eleven tools (`work_log_add`, `task_create`, `daily_summary`, …).
9
+ - `client.py` — the tiny stdlib HTTP client the server and hooks share. Reads your token, talks to the API.
10
+ - `session_logger.py` — a SessionEnd hook. Turns a finished Claude Code session into one work-log entry.
11
+ - `file_logger.py` — a PostToolUse hook. Records a file-activity event whenever Claude writes or edits a file.
12
+ - `pyproject.toml` — packaging metadata. One dependency, `mcp[cli]`; one console script, `cairn-mcp`.
13
+ - `.mcp.json`, `hooks/hooks.json`, `.claude-plugin/` — the Claude Code plugin wiring.
14
+
15
+ ## Easiest: install as a plugin
16
+
17
+ If you're on Claude Code, skip the manual steps below — this repo is also a plugin marketplace. Two commands from inside Claude Code:
18
+
19
+ ```
20
+ /plugin marketplace add cybort360/cairn
21
+ /plugin install cairn@cairn
22
+ ```
23
+
24
+ That registers the `cairn` MCP server and both auto-logging hooks for you; there's no clone, no venv, and no `claude mcp add`. One prerequisite: the plugin runs the server through [`uv`](https://docs.astral.sh/uv/), so you need `uv` on your PATH (`curl -LsSf https://astral.sh/uv/install.sh | sh`). `uv` pulls `mcp[cli]` on its own, so you never manage a Python env.
25
+
26
+ You still need to drop your token in once — see step 2 of the manual install for where to get it:
27
+
28
+ ```bash
29
+ printf '%s' 'PASTE_YOUR_TOKEN' > ~/.cairn_token && chmod 600 ~/.cairn_token
30
+ ```
31
+
32
+ ## From PyPI (any MCP client)
33
+
34
+ Not on Claude Code, or want the server without the plugin? The package is on PyPI, so [`uv`](https://docs.astral.sh/uv/) can run it with nothing to clone or install:
35
+
36
+ ```bash
37
+ claude mcp add cairn --scope user -- uvx cairn-mcp
38
+ ```
39
+
40
+ `uvx cairn-mcp` is the command any MCP client can point at; `uvx` fetches the package and its deps on first run. Save your token first (see step 2 below). For a different client, use its own config — the command is the same.
41
+
42
+ ## Manual, from source
43
+
44
+ For when you'd rather not use `uv` at all. You need Python 3.10+ and the Claude Code CLI.
45
+
46
+ **1. Clone and install the package into a venv.**
47
+
48
+ ```bash
49
+ git clone https://github.com/cybort360/cairn.git
50
+ cd cairn/agent
51
+ python3 -m venv .venv
52
+ .venv/bin/pip install .
53
+ ```
54
+
55
+ That puts a `cairn-mcp` console script in `.venv/bin/`.
56
+
57
+ **2. Get your API token.** Sign in at the Cairn web app, open the account menu, and copy your API token. Save it where the client looks for it:
58
+
59
+ ```bash
60
+ printf '%s' 'PASTE_YOUR_TOKEN_HERE' > ~/.cairn_token
61
+ chmod 600 ~/.cairn_token
62
+ ```
63
+
64
+ (You can set `CAIRN_TOKEN` in the environment instead if you'd rather not write a file. The client checks `CAIRN_TOKEN` first, then `~/.cairn_token`.)
65
+
66
+ **3. Register the server with Claude Code.** Point it at the console script. `--scope user` makes the `cairn` tools available in every project, not just this one:
67
+
68
+ ```bash
69
+ claude mcp add cairn --scope user -- "$(pwd)/.venv/bin/cairn-mcp"
70
+ ```
71
+
72
+ **4. Check it connected.**
73
+
74
+ ```bash
75
+ claude mcp list
76
+ ```
77
+
78
+ You want to see `cairn: … ✔ Connected`. Inside a session the tools show up as `mcp__cairn__work_log_add`, `mcp__cairn__task_list`, and the rest.
79
+
80
+ That's the whole install. The hooks below are optional.
81
+
82
+ ## Optional: auto-logging hooks
83
+
84
+ The plugin install already wires these up. Set them by hand only if you went the manual route. The two hooks make Cairn fill itself in without you calling a tool: `session_logger` writes one entry summarizing each session when it ends; `file_logger` notes every file Claude touches. Both fail silently — a dropped network call or a missing token never interrupts your work. They use only the standard library, so plain `python3` runs them; no `mcp` needed.
85
+
86
+ Wire them into Claude Code's settings (`~/.claude/settings.json` for all projects). The commands run the package modules, so they need `cairn_mcp` on the path — `cd` into wherever you cloned (`$HOME/cairn/agent` assumes `~/cairn`; adjust it):
87
+
88
+ ```json
89
+ {
90
+ "hooks": {
91
+ "SessionEnd": [
92
+ {
93
+ "hooks": [
94
+ {
95
+ "type": "command",
96
+ "command": "cd \"$HOME/cairn/agent\" && python3 -m cairn_mcp.session_logger 2>/dev/null || true",
97
+ "timeout": 15,
98
+ "statusMessage": "Logging session to Cairn"
99
+ }
100
+ ]
101
+ }
102
+ ],
103
+ "PostToolUse": [
104
+ {
105
+ "matcher": "Write|Edit|MultiEdit",
106
+ "hooks": [
107
+ {
108
+ "type": "command",
109
+ "command": "cd \"$HOME/cairn/agent\" && python3 -m cairn_mcp.file_logger 2>/dev/null || true",
110
+ "timeout": 10
111
+ }
112
+ ]
113
+ }
114
+ ]
115
+ }
116
+ }
117
+ ```
118
+
119
+ A hard kill (crash, power loss, `kill -9`) skips the SessionEnd hook, so that one session won't be logged. Everything else — the normal exit, `/clear`, `/logout`, Ctrl-D — fires it.
120
+
121
+ ## Pointing at a different backend
122
+
123
+ By default the client talks to production. Override it with `CAIRN_API_URL` if you're running the web app locally or against a staging deploy:
124
+
125
+ ```bash
126
+ export CAIRN_API_URL="http://127.0.0.1:8765"
127
+ ```
128
+
129
+ The MCP server reads it the same way, so set it in the `claude mcp add` environment (`-e CAIRN_API_URL=…`) if you want the tools pointed somewhere other than prod.
@@ -0,0 +1,8 @@
1
+ """Cairn MCP server and Claude Code hooks — a thin client over the Cairn cloud API.
2
+
3
+ Kept import-light on purpose: the hooks (session_logger, file_logger) run under a
4
+ bare ``python3`` with only the standard library, so importing this package must not
5
+ pull in ``mcp`` or ``pydantic``. The server lives in :mod:`cairn_mcp.server`.
6
+ """
7
+
8
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Run the Cairn MCP server with ``python -m cairn_mcp``."""
2
+
3
+ from .server import main
4
+
5
+ main()
@@ -0,0 +1,88 @@
1
+ """Tiny stdlib HTTP client for the Cairn cloud API.
2
+
3
+ The MCP server and the Claude Code hooks use this to talk to the user's Cairn
4
+ account over HTTPS, authenticating with their API token. No third-party deps.
5
+
6
+ Config:
7
+ CAIRN_API_URL base URL (defaults to production)
8
+ CAIRN_TOKEN API token, else read from ~/.cairn_token
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import urllib.error
14
+ import urllib.request
15
+ from urllib.parse import urlencode
16
+
17
+ DEFAULT_BASE = "https://cairn-octanes-projects.vercel.app"
18
+ TOKEN_PATH = os.path.expanduser("~/.cairn_token")
19
+ # Older installs saved the token here; read it as a fallback during the rename.
20
+ LEGACY_TOKEN_PATH = os.path.expanduser("~/.work_tracker_token")
21
+ DEFAULT_TIMEOUT = 5.0
22
+
23
+
24
+ class CairnError(Exception):
25
+ """Raised on missing config, network failure, or a non-2xx response."""
26
+
27
+
28
+ def base_url() -> str:
29
+ return os.environ.get("CAIRN_API_URL", DEFAULT_BASE).rstrip("/")
30
+
31
+
32
+ def token() -> str:
33
+ t = os.environ.get("CAIRN_TOKEN")
34
+ if t:
35
+ return t.strip()
36
+ for path in (TOKEN_PATH, LEGACY_TOKEN_PATH):
37
+ try:
38
+ with open(path) as f:
39
+ return f.read().strip()
40
+ except OSError:
41
+ continue
42
+ return ""
43
+
44
+
45
+ def _request(method, path, params=None, payload=None, timeout=DEFAULT_TIMEOUT):
46
+ tok = token()
47
+ if not tok:
48
+ raise CairnError("not configured — save your token to ~/.cairn_token "
49
+ "or set CAIRN_TOKEN")
50
+ url = base_url() + path
51
+ if params:
52
+ q = urlencode({k: v for k, v in params.items() if v is not None})
53
+ if q:
54
+ url += "?" + q
55
+ body = json.dumps(payload).encode() if payload is not None else None
56
+ req = urllib.request.Request(url, data=body, method=method)
57
+ req.add_header("Authorization", f"Bearer {tok}")
58
+ if body is not None:
59
+ req.add_header("Content-Type", "application/json")
60
+ try:
61
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
62
+ text = resp.read().decode()
63
+ return json.loads(text) if text else {}
64
+ except urllib.error.HTTPError as e:
65
+ detail = ""
66
+ try:
67
+ detail = json.loads(e.read().decode()).get("error", "")
68
+ except Exception:
69
+ pass
70
+ raise CairnError(f"HTTP {e.code}: {detail or e.reason}")
71
+ except urllib.error.URLError as e:
72
+ raise CairnError(f"network error: {e.reason}")
73
+
74
+
75
+ def get(path, params=None, timeout=DEFAULT_TIMEOUT):
76
+ return _request("GET", path, params=params, timeout=timeout)
77
+
78
+
79
+ def post(path, payload, timeout=DEFAULT_TIMEOUT):
80
+ return _request("POST", path, payload=payload, timeout=timeout)
81
+
82
+
83
+ def patch(path, payload, timeout=DEFAULT_TIMEOUT):
84
+ return _request("PATCH", path, payload=payload, timeout=timeout)
85
+
86
+
87
+ def delete(path, timeout=DEFAULT_TIMEOUT):
88
+ return _request("DELETE", path, timeout=timeout)
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PostToolUse(Edit|Write|MultiEdit) hook for Cairn.
4
+
5
+ After Claude Code writes or edits a file, this records one file-activity event
6
+ in the user's Cairn account over HTTPS. Dedup (collapsing rapid repeats of the
7
+ same file+action) and language detection now happen server-side.
8
+
9
+ Fail-soft: any network/config error is swallowed so editing never breaks.
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import sys
15
+
16
+ from . import client as cairn_client
17
+
18
+ POST_TIMEOUT = 3.0
19
+
20
+ # Paths we never want cluttering the activity log.
21
+ IGNORE_SUBSTRINGS = ("/.git/", "/node_modules/", "/.venv/", "/__pycache__/",
22
+ "/dist/", "/build/", "/.next/", ".work_tracker.db")
23
+
24
+
25
+ def main() -> None:
26
+ try:
27
+ payload = json.load(sys.stdin)
28
+ except (json.JSONDecodeError, ValueError):
29
+ return
30
+
31
+ tool = payload.get("tool_name", "")
32
+ fp = (payload.get("tool_input") or {}).get("file_path", "")
33
+ if not fp or any(s in fp for s in IGNORE_SUBSTRINGS):
34
+ return
35
+
36
+ action = "created" if tool == "Write" else "edited"
37
+ cwd = payload.get("cwd") or os.getcwd()
38
+ project = os.path.basename(cwd.rstrip("/")) or None
39
+ display = os.path.relpath(fp, cwd) if fp.startswith(cwd) else fp
40
+
41
+ try:
42
+ cairn_client.post("/api/files", {
43
+ "file_path": display, "action": action, "project": project,
44
+ }, timeout=POST_TIMEOUT)
45
+ except cairn_client.CairnError:
46
+ pass # fail-soft
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()