@aexol-studio/basecamp-to-llm
v2.0.5
Published
Basecamp MCP server, CLI, and typed TypeScript SDK for LLM-friendly project data
Readme
@aexol-studio/basecamp-to-llm
Basecamp MCP server and CLI for AI-assisted project management. Connect your IDE (Codex, Cursor, OpenCode) to Basecamp via the Model Context Protocol.
Requires Node.js 20 or newer. CI tests the Node.js 20 and 22 LTS lines; newer compatible Node.js releases are not intentionally excluded.
Features
- MCP Server — 14 tools for managing Basecamp projects, cards, steps, comments, and attachments directly from your IDE
- CLI — Authenticate, list projects, call any Basecamp API endpoint, and run SDK actions from the terminal
- Typed SDK — Modular TypeScript SDK covering projects, card tables, people, comments, steps, messages, and todos
- OAuth2 Authentication — Secure token-based auth with automatic refresh
- Enriched Cards — Fetch cards with full comment history, visual attachments (images), and formatted text output for LLM context
- Kanban Management — Create tasks with checklists, move cards between columns, update assignees and due dates
Quick Start
1. Create a Basecamp OAuth App
- Go to https://launchpad.37signals.com/integrations
- Create a new integration
- Set the redirect URI to
http://localhost:8787/callback - Note your
client_idandclient_secret
2. Environment Variables
export BASECAMP_CLIENT_ID="your_client_id"
export BASECAMP_CLIENT_SECRET="your_client_secret"
export BASECAMP_REDIRECT_URI="http://localhost:8787/callback"
export BASECAMP_USER_AGENT="Your App Name ([email protected])"
# Optional when your authorization includes multiple Basecamp accounts
export BASECAMP_ACCOUNT_ID="your_account_id"These are shell exports: run them in the same shell (or shell startup file) that launches the CLI. The package does not load .env files automatically. MCP clients may instead set the variables in their server configuration.
3. Install
npm install @aexol-studio/basecamp-to-llm4. Authenticate
npx @aexol-studio/basecamp-to-llm auth --openToken cache security
OAuth tokens are cached in .basecamp/basecamp-token.json under the current working
directory. Treat this file as sensitive and do not commit it. If your setup has an older
or root-level token cache such as .basecamp-token, ignore that too:
# Basecamp OAuth token cache
.basecamp/
.basecamp-token*MCP Server Setup
Install as a dev dependency in your project:
npm install --save-dev @aexol-studio/basecamp-to-llmThe package installs two binaries:
basecamp-to-llmprovides the CLI, including themcpsubcommand.basecamp-mcpstarts the MCP stdio server directly.
Then configure your IDE:
Create .codex/config.toml:
[mcp_servers.basecamp]
command = "npx"
args = ["-y", "@aexol-studio/basecamp-to-llm", "mcp"]
env_vars = ["BASECAMP_CLIENT_ID", "BASECAMP_CLIENT_SECRET", "BASECAMP_REDIRECT_URI", "BASECAMP_USER_AGENT"]Create .cursor/mcp.json:
{
"mcpServers": {
"basecamp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@aexol-studio/basecamp-to-llm", "mcp"],
"env": {
"BASECAMP_CLIENT_ID": "${env:BASECAMP_CLIENT_ID}",
"BASECAMP_CLIENT_SECRET": "${env:BASECAMP_CLIENT_SECRET}",
"BASECAMP_REDIRECT_URI": "${env:BASECAMP_REDIRECT_URI}",
"BASECAMP_USER_AGENT": "${env:BASECAMP_USER_AGENT}"
}
}
}
}Add to opencode.jsonc under the top-level mcp key:
{
"mcp": {
"basecamp": {
"type": "local",
"command": ["npx", "-y", "@aexol-studio/basecamp-to-llm", "mcp"],
"environment": {
"BASECAMP_CLIENT_ID": "{env:BASECAMP_CLIENT_ID}",
"BASECAMP_CLIENT_SECRET": "{env:BASECAMP_CLIENT_SECRET}",
"BASECAMP_REDIRECT_URI": "{env:BASECAMP_REDIRECT_URI}",
"BASECAMP_USER_AGENT": "{env:BASECAMP_USER_AGENT}",
},
},
},
}Use stdio transport with the command:
npx -y @aexol-studio/basecamp-to-llm mcpSet the four required environment variables (BASECAMP_CLIENT_ID, BASECAMP_CLIENT_SECRET, BASECAMP_REDIRECT_URI, BASECAMP_USER_AGENT) in your client's MCP config. BASECAMP_ACCOUNT_ID is optional.
The MCP server first connects over stdio, then tries a cached token or silent refresh. If neither is available, it opens a browser and waits for the configured local OAuth callback. In a headless environment, authenticate first with basecamp-to-llm auth (copy the printed URL into a browser) or provide a reusable token cache; a failed automatic browser flow is logged to stderr and does not terminate the MCP server. All MCP logs use stderr so stdout remains reserved for the protocol.
Available MCP Tools
Required arguments are shown without ?; optional arguments include ?.
| Tool | Arguments | Description |
| ------------------------------ | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| authenticate | openBrowser? | Start or refresh OAuth authentication |
| api_request | method, path, query?, body?, absolute? | Make a generic Basecamp API request |
| sdk_projects_list | status?, page? | List projects; status may be archived or trashed |
| sdk_card_tables_get | projectId, tableId | Get a raw board and its column metadata; use list_cards for cards |
| sdk_card_tables_list_cards | projectId, columnId, detail?, page?, limit? | List a bounded page of compact or extended cards |
| sdk_card_tables_get_card | projectId, cardId | Get one raw card |
| sdk_card_tables_get_enriched | projectId, cardId, format?, downloadImages?, imageQuality? | Get card context with comments, creator data, and attachments |
| sdk_card_tables_create_task | projectId, columnId, title, content, due_on?, assignee_ids?, notify?, steps? | Create a card and optional checklist steps |
| sdk_card_tables_update_card | projectId, cardId, title?, content?, due_on?, assignee_ids? | Update supported card fields |
| sdk_card_tables_move_card | projectId, cardId, columnId | Move a card to another column |
| sdk_people_list | None | List people in the account |
| sdk_comments_create | projectId, recordingId, content | Add an HTML comment to a recording |
| sdk_steps_complete | projectId, stepId, completion | Set step completion to on or off |
| sdk_attachments_download | url, filename?, mimeType? | Download and save an attachment; return image content or file metadata |
Compact card listing and pagination
sdk_card_tables_list_cards defaults to detail: "compact", page: 1, and
limit: 25. page is one-based and limit must be from 1 through 100. Compact
cards contain only:
id, normalizedtitle(title, thenname, then an empty string)- optional
status,position,due_on,app_url, andupdated_at - projected
assigneeswithidandnameonly - normalized
comments_count stepscounts withcompletedandtotal
Compact cards do not include card descriptions, content, or comment bodies. Use
detail: "extended" when the full raw Basecamp Card objects are required for
the requested window. Listing cards never fetches comment bodies; use
sdk_card_tables_get_enriched when comment context is explicitly needed.
Compact request arguments:
{
"projectId": 123,
"columnId": 456,
"page": 1,
"limit": 25
}Extended request arguments:
{
"projectId": 123,
"columnId": 456,
"detail": "extended",
"page": 2,
"limit": 10
}Both modes return the same envelope:
{
"detail": "compact",
"cards": [],
"pagination": {
"page": 1,
"limit": 25,
"returned": 0,
"has_more": false
}
}When has_more is true, pagination.next_page contains the next one-based page.
It is omitted otherwise. The response does not report a total because the
bounded traversal does not fetch the complete collection.
Enriched card text and attachment downloads
sdk_card_tables_get_enriched with format: "text" preserves the card description as Basecamp HTML. Comment bodies are converted to plain text. This deliberate distinction keeps rich description markup and embedded attachment references available to the LLM.
With downloadImages: true, sdk_card_tables_get_enriched returns one text metadata block plus a separate MCP image block for each successfully downloaded image. Base64 is removed from the text block, and query strings/fragments are removed from Basecamp URLs in projected text so signed capabilities are not duplicated into LLM context. Automatic downloads are limited to the first four images with at most two active downloads; metadata remains available for additional images. Individual image failures remain best-effort and are logged to MCP stderr. The imageQuality option applies only to these enriched-card image downloads.
sdk_attachments_download always writes the downloaded bytes to .basecamp/images/ under the process's current working directory. If filename is omitted, it uses attachment; characters other than letters, numbers, ., _, and - become _, while . and .. are rejected. The download directory and destination must not be symlinks. Files are opened without symlink following, verified as regular files, and written with mode 0600 where supported. A download overwrites an existing regular file with the same sanitized name. The response Content-Type is authoritative; caller mimeType is only a fallback for non-image data. MCP image output requires PNG, JPEG, GIF, or WebP magic bytes plus safe response metadata, preventing HTML or unknown bytes mislabeled by a caller from becoming image blocks.
Binary downloads accept only HTTPS URLs without embedded credentials on the exact supported Basecamp API, preview, and storage hosts. Redirects are followed manually with a bounded limit, and every target is validated. OAuth authorization is sent only to 3.basecampapi.com and is never forwarded to preview or storage hosts. Each binary is limited to 10 MiB: oversized Content-Length values are rejected before reading, streamed chunk overflow is canceled, and non-streaming runtimes receive the same post-read cap.
CLI Usage
# Authenticate with Basecamp (opens browser)
basecamp-to-llm auth --open
# List available projects
basecamp-to-llm projects
# Start the MCP server
basecamp-to-llm mcp
# Or start the installed MCP binary directly
basecamp-mcp
# Call any Basecamp API endpoint
basecamp-to-llm api GET /projects.json
basecamp-to-llm api POST /todosets/123/todolists.json -d '{"name":"My List"}'
# List available SDK actions
basecamp-to-llm sdk list
# Run an SDK action
basecamp-to-llm sdk run projects.list
basecamp-to-llm sdk run card_tables.get -a '{"projectId":123,"tableId":456}'
basecamp-to-llm sdk run card_tables.list_cards -a '{"projectId":123,"columnId":456,"page":1,"limit":25}'Programmatic Usage
import { BasecampClient, SDK } from "@aexol-studio/basecamp-to-llm";
const client = new BasecampClient();
// List projects
const projects = new SDK.ProjectsResource(client);
const list = await projects.list();
// List a bounded compact page
const cards = new SDK.CardTablesResource(client);
const page = await cards.listCardsPage(projectId, columnId, {
detail: "compact",
page: 1,
limit: 25,
});
// Create a task with steps
const task = await cards.createCardWithSteps(projectId, columnId, {
title: "Implement auth",
content: "<p>OAuth2 support</p>",
steps: [
{ title: "Design auth flow" },
{ title: "Implement OAuth2" },
{ title: "Write tests" },
],
});For createCardWithSteps, each nested step requires title; optional due_on uses YYYY-MM-DD, and optional assignees is a comma-separated string of Basecamp person IDs. The card-level field remains assignee_ids: number[].
The enriched-card aggregation and text formatter are currently MCP-facing helpers rather than stable top-level programmatic exports. The existing public API (BasecampFetcher, BasecampAuth, BasecampClient, and the SDK resource namespace) remains available; use SDK.CardTablesResource for the stable typed card API.
Development
Prerequisites
- Node.js 20 or newer (CI tests the Node.js 20 and 22 LTS lines)
- npm
Setup
git clone [email protected]:aexol/basecamp-to-llm.git
cd basecamp-to-llm
npm installScripts
npm run build # Compile TypeScript
npm run dev # Watch mode
npm test # Run tests
npm run test:watch # Watch tests
npm run lint # ESLint check
npm run lint:fix # ESLint autofix
npm run format # Format TypeScript with Prettier
npm run format:check # Check TypeScript formatting
npm run clean # Remove dist/Project Structure
src/
├── cli.ts # CLI entry point
├── index.ts # Public exports
├── auth.ts # OAuth2 authentication
├── basecamp-fetcher.ts # Legacy high-level fetcher
├── basecamp-types.ts # TypeScript type definitions
├── mcp-server.ts # MCP server implementation
└── sdk/
├── index.ts # SDK exports
├── client.ts # BasecampClient
├── registry.ts # Tool registry for MCP
├── types.ts # SDK type definitions
└── resources/
├── cardTables.ts # Kanban boards and cards
├── comments.ts # Comments
├── enrichedCards.ts # Enriched card context
├── messages.ts # Messages
├── people.ts # People
├── projects.ts # Projects
├── steps.ts # Card checklist steps
└── todos.ts # TodosLicense
MIT — see LICENSE for details.
Contributions welcome — open an issue or submit a merge request.
