basecamp-cli-mcp 0.6.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,7 @@
1
+ /.venv/
2
+ /dist/
3
+ /build/
4
+ __pycache__/
5
+ *.egg-info/
6
+ *.pyc
7
+ .pytest_cache/
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: basecamp-cli-mcp
3
+ Version: 0.6.0
4
+ Summary: MCP server that wraps the basecamp CLI.
5
+ Project-URL: Homepage, https://github.com/FutureWorkshops/basecamp-cli-mcp
6
+ Project-URL: Source, https://github.com/FutureWorkshops/basecamp-cli-mcp
7
+ Author-email: Matt Brooke-Smith <matt@futureworkshops.com>
8
+ License: MIT
9
+ Keywords: basecamp,mcp,model-context-protocol
10
+ Requires-Python: >=3.11
11
+ Requires-Dist: mcp>=1.2
12
+ Description-Content-Type: text/markdown
13
+
14
+ # basecamp-cli-mcp
15
+
16
+ A Model Context Protocol (MCP) server that wraps the [`basecamp`](https://github.com/basecamp/cli)
17
+ CLI. Every non-shortcut CLI action (`projects list`, `todos create`, `cards update`, etc.) is
18
+ exposed as an MCP tool, so MCP-compatible clients (Claude Code, Claude Desktop, etc.) can
19
+ drive Basecamp directly.
20
+
21
+ ## Requirements
22
+
23
+ - Python ≥ 3.11
24
+ - The `basecamp` CLI on `PATH`, already authenticated (`basecamp setup`) — see [First-time setup](#first-time-setup) below to do both in one command
25
+ - [`uv`](https://docs.astral.sh/uv/) for the recommended install
26
+
27
+ ## First-time setup
28
+
29
+ If you don't have the `basecamp` CLI installed yet:
30
+
31
+ ```sh
32
+ uvx basecamp-cli-mcp setup
33
+ ```
34
+
35
+ This checks for the `basecamp` binary, runs the official installer (`curl -fsSL https://basecamp.com/install-cli | bash`) after a y/N prompt if it's missing, then runs `basecamp setup` to walk you through OAuth.
36
+
37
+ On macOS, after auth completes it offers to:
38
+
39
+ 1. Add `basecamp` to your `claude_desktop_config.json` (with a timestamped backup of any existing file).
40
+ 2. Restart Claude Desktop.
41
+
42
+ Both steps prompt y/N. macOS and Linux only for the install/auth — on Windows the command prints the PowerShell installer and exits. The Claude Desktop step is macOS-only.
43
+
44
+ ## Run
45
+
46
+ The fastest way — no install step at all:
47
+
48
+ ```sh
49
+ uvx basecamp-cli-mcp
50
+ ```
51
+
52
+ `uvx` resolves the package, runs it in an ephemeral isolated env, and exec's it on stdin/stdout.
53
+
54
+ To install permanently:
55
+
56
+ ```sh
57
+ uv tool install basecamp-cli-mcp
58
+ basecamp-cli-mcp
59
+ ```
60
+
61
+ ### Add to Claude Code
62
+
63
+ ```sh
64
+ claude mcp add basecamp -- uvx basecamp-cli-mcp
65
+ ```
66
+
67
+ ### Add to Claude Desktop (`claude_desktop_config.json`)
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "basecamp": {
73
+ "command": "uvx",
74
+ "args": ["basecamp-cli-mcp"]
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ If `uvx` isn't on Desktop's `PATH` (it strips most of your shell `PATH`), use the absolute path — `which uvx` from your shell.
81
+
82
+ ### Filtering tools
83
+
84
+ By default the server exposes all 250+ tools. Most agents only need a handful, and a smaller
85
+ catalog speeds up tool selection. Filter with `--include` / `--exclude` (fnmatch globs against
86
+ tool names; both flags repeatable):
87
+
88
+ ```sh
89
+ basecamp-cli-mcp --include 'cards_*' --include 'todos_*'
90
+ basecamp-cli-mcp --include '*' --exclude 'webhooks_*' --exclude 'templates_*'
91
+ ```
92
+
93
+ Register multiple profiles in `claude_desktop_config.json` and turn them on per task:
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "basecamp-cards": {
99
+ "command": "uvx",
100
+ "args": ["basecamp-cli-mcp", "--include", "cards_*", "--include", "projects_list"]
101
+ },
102
+ "basecamp-todos": {
103
+ "command": "uvx",
104
+ "args": ["basecamp-cli-mcp", "--include", "todos_*", "--include", "projects_list"]
105
+ },
106
+ "basecamp-full": {
107
+ "command": "uvx",
108
+ "args": ["basecamp-cli-mcp"]
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### `BASECAMP_BIN`
115
+
116
+ If the `basecamp` CLI isn't on the spawned process's `PATH` (a real risk under Claude Desktop), set:
117
+
118
+ ```json
119
+ "env": { "BASECAMP_BIN": "/absolute/path/to/basecamp" }
120
+ ```
121
+
122
+ ## How it works
123
+
124
+ Tool schemas are pre-generated and committed to `src/basecamp_cli_mcp/data/tools.json` (shipped
125
+ inside the wheel as package data). At runtime the server loads that file and registers one MCP
126
+ tool per entry. Each tool:
127
+
128
+ 1. Builds `argv` from positional arguments and flags defined in the schema.
129
+ 2. Shells out: `basecamp <group> <action> <args...> --json`.
130
+ 3. Returns the parsed `data` field from the CLI's `{ok, data}` envelope to the MCP client.
131
+
132
+ Shortcut commands (`todo`, `done`, `card`, `comment`, etc.) are intentionally excluded — their
133
+ underlying actions (`todos_create`, `cards_create`, …) are already exposed.
134
+
135
+ ## Regenerating tool schemas
136
+
137
+ After upgrading the `basecamp` CLI:
138
+
139
+ ```sh
140
+ uv run basecamp-cli-mcp generate
141
+ ```
142
+
143
+ Review the diff in `src/basecamp_cli_mcp/data/tools.json` and commit. The generator reads
144
+ `basecamp commands --json` and parses `basecamp <group> <action> --help` for each action.
145
+
146
+ ## Development
147
+
148
+ ```sh
149
+ uv sync
150
+ uv run pytest
151
+ uv build # wheel + sdist into dist/
152
+ ```
153
+
154
+ Layout:
155
+
156
+ - `src/basecamp_cli_mcp/server.py` — wires up the MCP server from `data/tools.json`
157
+ - `src/basecamp_cli_mcp/runner.py` — builds argv and shells out
158
+ - `src/basecamp_cli_mcp/generator.py` — regenerates `data/tools.json`
159
+ - `src/basecamp_cli_mcp/help_parser.py` — parses `--help` text into a schema
160
+ - `src/basecamp_cli_mcp/cli.py` — entrypoint (`basecamp-cli-mcp`)
161
+ - `src/basecamp_cli_mcp/data/tools.json` — generated tool schemas (committed)
@@ -0,0 +1,148 @@
1
+ # basecamp-cli-mcp
2
+
3
+ A Model Context Protocol (MCP) server that wraps the [`basecamp`](https://github.com/basecamp/cli)
4
+ CLI. Every non-shortcut CLI action (`projects list`, `todos create`, `cards update`, etc.) is
5
+ exposed as an MCP tool, so MCP-compatible clients (Claude Code, Claude Desktop, etc.) can
6
+ drive Basecamp directly.
7
+
8
+ ## Requirements
9
+
10
+ - Python ≥ 3.11
11
+ - The `basecamp` CLI on `PATH`, already authenticated (`basecamp setup`) — see [First-time setup](#first-time-setup) below to do both in one command
12
+ - [`uv`](https://docs.astral.sh/uv/) for the recommended install
13
+
14
+ ## First-time setup
15
+
16
+ If you don't have the `basecamp` CLI installed yet:
17
+
18
+ ```sh
19
+ uvx basecamp-cli-mcp setup
20
+ ```
21
+
22
+ This checks for the `basecamp` binary, runs the official installer (`curl -fsSL https://basecamp.com/install-cli | bash`) after a y/N prompt if it's missing, then runs `basecamp setup` to walk you through OAuth.
23
+
24
+ On macOS, after auth completes it offers to:
25
+
26
+ 1. Add `basecamp` to your `claude_desktop_config.json` (with a timestamped backup of any existing file).
27
+ 2. Restart Claude Desktop.
28
+
29
+ Both steps prompt y/N. macOS and Linux only for the install/auth — on Windows the command prints the PowerShell installer and exits. The Claude Desktop step is macOS-only.
30
+
31
+ ## Run
32
+
33
+ The fastest way — no install step at all:
34
+
35
+ ```sh
36
+ uvx basecamp-cli-mcp
37
+ ```
38
+
39
+ `uvx` resolves the package, runs it in an ephemeral isolated env, and exec's it on stdin/stdout.
40
+
41
+ To install permanently:
42
+
43
+ ```sh
44
+ uv tool install basecamp-cli-mcp
45
+ basecamp-cli-mcp
46
+ ```
47
+
48
+ ### Add to Claude Code
49
+
50
+ ```sh
51
+ claude mcp add basecamp -- uvx basecamp-cli-mcp
52
+ ```
53
+
54
+ ### Add to Claude Desktop (`claude_desktop_config.json`)
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "basecamp": {
60
+ "command": "uvx",
61
+ "args": ["basecamp-cli-mcp"]
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ If `uvx` isn't on Desktop's `PATH` (it strips most of your shell `PATH`), use the absolute path — `which uvx` from your shell.
68
+
69
+ ### Filtering tools
70
+
71
+ By default the server exposes all 250+ tools. Most agents only need a handful, and a smaller
72
+ catalog speeds up tool selection. Filter with `--include` / `--exclude` (fnmatch globs against
73
+ tool names; both flags repeatable):
74
+
75
+ ```sh
76
+ basecamp-cli-mcp --include 'cards_*' --include 'todos_*'
77
+ basecamp-cli-mcp --include '*' --exclude 'webhooks_*' --exclude 'templates_*'
78
+ ```
79
+
80
+ Register multiple profiles in `claude_desktop_config.json` and turn them on per task:
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "basecamp-cards": {
86
+ "command": "uvx",
87
+ "args": ["basecamp-cli-mcp", "--include", "cards_*", "--include", "projects_list"]
88
+ },
89
+ "basecamp-todos": {
90
+ "command": "uvx",
91
+ "args": ["basecamp-cli-mcp", "--include", "todos_*", "--include", "projects_list"]
92
+ },
93
+ "basecamp-full": {
94
+ "command": "uvx",
95
+ "args": ["basecamp-cli-mcp"]
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### `BASECAMP_BIN`
102
+
103
+ If the `basecamp` CLI isn't on the spawned process's `PATH` (a real risk under Claude Desktop), set:
104
+
105
+ ```json
106
+ "env": { "BASECAMP_BIN": "/absolute/path/to/basecamp" }
107
+ ```
108
+
109
+ ## How it works
110
+
111
+ Tool schemas are pre-generated and committed to `src/basecamp_cli_mcp/data/tools.json` (shipped
112
+ inside the wheel as package data). At runtime the server loads that file and registers one MCP
113
+ tool per entry. Each tool:
114
+
115
+ 1. Builds `argv` from positional arguments and flags defined in the schema.
116
+ 2. Shells out: `basecamp <group> <action> <args...> --json`.
117
+ 3. Returns the parsed `data` field from the CLI's `{ok, data}` envelope to the MCP client.
118
+
119
+ Shortcut commands (`todo`, `done`, `card`, `comment`, etc.) are intentionally excluded — their
120
+ underlying actions (`todos_create`, `cards_create`, …) are already exposed.
121
+
122
+ ## Regenerating tool schemas
123
+
124
+ After upgrading the `basecamp` CLI:
125
+
126
+ ```sh
127
+ uv run basecamp-cli-mcp generate
128
+ ```
129
+
130
+ Review the diff in `src/basecamp_cli_mcp/data/tools.json` and commit. The generator reads
131
+ `basecamp commands --json` and parses `basecamp <group> <action> --help` for each action.
132
+
133
+ ## Development
134
+
135
+ ```sh
136
+ uv sync
137
+ uv run pytest
138
+ uv build # wheel + sdist into dist/
139
+ ```
140
+
141
+ Layout:
142
+
143
+ - `src/basecamp_cli_mcp/server.py` — wires up the MCP server from `data/tools.json`
144
+ - `src/basecamp_cli_mcp/runner.py` — builds argv and shells out
145
+ - `src/basecamp_cli_mcp/generator.py` — regenerates `data/tools.json`
146
+ - `src/basecamp_cli_mcp/help_parser.py` — parses `--help` text into a schema
147
+ - `src/basecamp_cli_mcp/cli.py` — entrypoint (`basecamp-cli-mcp`)
148
+ - `src/basecamp_cli_mcp/data/tools.json` — generated tool schemas (committed)
@@ -0,0 +1,38 @@
1
+ [project]
2
+ name = "basecamp-cli-mcp"
3
+ version = "0.6.0"
4
+ description = "MCP server that wraps the basecamp CLI."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "Matt Brooke-Smith", email = "matt@futureworkshops.com" }]
9
+ keywords = ["mcp", "basecamp", "model-context-protocol"]
10
+
11
+ dependencies = [
12
+ "mcp>=1.2",
13
+ ]
14
+
15
+ [project.urls]
16
+ Homepage = "https://github.com/FutureWorkshops/basecamp-cli-mcp"
17
+ Source = "https://github.com/FutureWorkshops/basecamp-cli-mcp"
18
+
19
+ [project.scripts]
20
+ basecamp-cli-mcp = "basecamp_cli_mcp.cli:main"
21
+
22
+ [build-system]
23
+ requires = ["hatchling"]
24
+ build-backend = "hatchling.build"
25
+
26
+ [tool.hatch.build.targets.wheel]
27
+ packages = ["src/basecamp_cli_mcp"]
28
+
29
+ [tool.hatch.build.targets.sdist]
30
+ include = ["src/basecamp_cli_mcp", "tests", "README.md", "LICENSE"]
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "pytest>=8",
35
+ ]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
@@ -0,0 +1,6 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("basecamp-cli-mcp")
5
+ except PackageNotFoundError:
6
+ __version__ = "0.0.0+unknown"
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import asyncio
5
+ import json
6
+ import sys
7
+ from importlib.resources import files
8
+ from pathlib import Path
9
+
10
+ from . import __version__
11
+
12
+
13
+ def main(argv: list[str] | None = None) -> int:
14
+ parser = argparse.ArgumentParser(
15
+ prog="basecamp-cli-mcp",
16
+ description="MCP server that wraps the basecamp CLI.",
17
+ )
18
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
19
+ parser.add_argument(
20
+ "--include",
21
+ action="append",
22
+ default=None,
23
+ metavar="PATTERN",
24
+ help=(
25
+ "Only expose tools whose name matches PATTERN (fnmatch glob). "
26
+ "Repeatable. Example: --include 'cards_*' --include 'todos_*'."
27
+ ),
28
+ )
29
+ parser.add_argument(
30
+ "--exclude",
31
+ action="append",
32
+ default=None,
33
+ metavar="PATTERN",
34
+ help="Hide tools whose name matches PATTERN. Repeatable. Applied after --include.",
35
+ )
36
+ sub = parser.add_subparsers(dest="command")
37
+
38
+ gen = sub.add_parser("generate", help="Regenerate data/tools.json from the basecamp CLI.")
39
+ gen.add_argument(
40
+ "--output",
41
+ type=Path,
42
+ default=None,
43
+ help="Path to write tools.json (default: in-tree data/tools.json next to this package).",
44
+ )
45
+
46
+ sub.add_parser(
47
+ "setup",
48
+ help="Install the basecamp CLI (if missing) and run `basecamp setup` to authenticate.",
49
+ )
50
+
51
+ args = parser.parse_args(argv)
52
+
53
+ if args.command == "generate":
54
+ return _generate(args.output)
55
+
56
+ if args.command == "setup":
57
+ from .setup_cmd import run as run_setup
58
+
59
+ return run_setup()
60
+
61
+ from . import server
62
+
63
+ asyncio.run(server.run(include=args.include, exclude=args.exclude))
64
+ return 0
65
+
66
+
67
+ def _generate(output: Path | None) -> int:
68
+ from .generator import Generator
69
+
70
+ tools = Generator().generate()
71
+ if output is None:
72
+ output = Path(str(files("basecamp_cli_mcp") / "data" / "tools.json"))
73
+ output.parent.mkdir(parents=True, exist_ok=True)
74
+ output.write_text(json.dumps(tools, indent=2) + "\n", encoding="utf-8")
75
+ print(f"Wrote {len(tools)} tools to {output}", file=sys.stderr)
76
+ return 0
77
+
78
+
79
+ if __name__ == "__main__":
80
+ raise SystemExit(main())