npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kaiseraraujo/basecamp-mcp-server

v1.2.0

Published

MCP server for the Basecamp API — projects, to-dos, messages, documents, Campfire chat and people

Readme

basecamp-mcp-server

An MCP server for the Basecamp API. It gives an MCP client — Claude Code, Claude Desktop, or anything else that speaks the protocol — read and write access to Basecamp projects, to-dos, message boards, documents, Campfire chat and people.

Published on npm as @kaiseraraujo/basecamp-mcp-server.

Why this exists

Basecamp's own Agents preview ships a CLI and SDKs, but no MCP server. That's a real gap if your agent isn't running on your laptop: a CLI has to be installed on the machine doing the work, so a remote or cloud session can't reach it. This server is a plain stdio MCP process talking to the public Basecamp 4 API over HTTPS, so it works anywhere the client can start a Node process — locally, in a container, or in a hosted session.

Design notes worth knowing before you wire it up:

  • OAuth refresh is handled for you. You supply a refresh token once; the server exchanges it for access tokens and caches them in memory until a minute before they expire.
  • Responses are size-capped. List results render to at most ~25,000 characters, halving the page until they fit, so a project with a thousand to-dos can't flood the model's context. Every truncated or paged response says so and tells you the next page to ask for.
  • Rich text is flattened. Basecamp stores message and document bodies as HTML; this server converts them to plain text before handing them to the model.
  • Every tool returns both a readable text rendering and structuredContent for programmatic use. Pass response_format: "json" on any list or get tool to see the raw records instead.

Tools

Projects

| Tool | What it does | | --- | --- | | basecamp_list_projects | List active projects with their IDs and enabled tools | | basecamp_get_project | One project in full, including the dock with per-tool IDs | | basecamp_create_project | Create a project. Note that Basecamp enforces a per-account project limit and answers 507 once you are at it — on the Free plan that limit is 1 | | basecamp_update_project | Rename a project or change its description. Reads first and resends untouched fields, so a partial update stays partial |

To-dos

| Tool | What it does | | --- | --- | | basecamp_list_todolists | The to-do lists in a project | | basecamp_create_todolist | Create a to-do list in a project's To-dos tool, resolving the todoset automatically. A project's To-dos tool starts empty, so a list has to exist before basecamp_create_todo can add anything | | basecamp_list_todos | To-dos in a list; completed: true returns the completed ones instead | | basecamp_create_todo | Add a to-do, optionally assigned and dated | | basecamp_update_todo | Change a to-do's title, description, assignees or dates. Basecamp's API clears any field you omit, so this tool reads the to-do first and resends its current values for anything you didn't supply — a partial update stays partial. Pass null for due_on/starts_on to clear a date, or [] for assignee_ids to clear assignees | | basecamp_move_todo | Move a to-do into another list in the same project, or reorder it within its current one. The to-do keeps its ID, comments, assignees and history — the tool for restructuring a project, since deleting and recreating loses all of that | | basecamp_complete_todo | Complete a to-do, or reopen it with completed: false | | basecamp_delete_todo | Move a to-do to the project's trash. Basecamp has no permanent delete through the API — the to-do leaves the list but stays recoverable from the project's trash for a limited period (about 30 days) before Basecamp purges it |

Messages

| Tool | What it does | | --- | --- | | basecamp_list_messages | Message board posts, newest first | | basecamp_get_message | One post in full | | basecamp_create_message | Post to the message board, or save a draft |

Documents

| Tool | What it does | | --- | --- | | basecamp_list_documents | Documents in the project's Docs & Files vault | | basecamp_get_document | One document in full | | basecamp_create_document | Create a document, published or drafted |

Campfire

| Tool | What it does | | --- | --- | | basecamp_list_chat_lines | Recent lines from the project's chat room | | basecamp_post_chat_line | Post a line to the chat room |

Comments

| Tool | What it does | | --- | --- | | basecamp_list_comments | Comments on any to-do, message or document | | basecamp_create_comment | Comment on any to-do, message or document |

People

| Tool | What it does | | --- | --- | | basecamp_list_people | Everyone in the account, or just one project's people | | basecamp_whoami | The identity the server is acting as |

Setup

1. Register an OAuth application

Create an app at https://launchpad.37signals.com/integrations. Any redirect URI works, including one nothing is listening on — you only need to read the authorization code out of the address bar. http://localhost:8989/callback is a fine choice.

Note the Client ID and Client Secret it gives you.

2. Get a refresh token

Open this URL in a browser, substituting your own values:

https://launchpad.37signals.com/authorization/new?type=web_server&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI

Approve the request. The browser will then fail to load the redirect URI — that is expected, since nothing is listening there. Copy the code=... parameter out of the address bar and exchange it:

curl -X POST "https://launchpad.37signals.com/authorization/token?type=web_server\
&client_id=YOUR_CLIENT_ID\
&redirect_uri=YOUR_REDIRECT_URI\
&client_secret=YOUR_CLIENT_SECRET\
&code=THE_CODE_FROM_THE_ADDRESS_BAR"

Keep the refresh_token from the response — that is the long-lived credential this server needs. The access_token alongside it lasts two weeks; you don't need to save it, because the server refreshes its own access tokens from the refresh token as needed.

3. Find your account ID

curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  https://launchpad.37signals.com/authorization.json

The response lists the Basecamp accounts you belong to. Take the numeric id of the account whose projects you want to work with (it's the same number that appears in your Basecamp URLs).

4. Install

The package is on npm as @kaiseraraujo/basecamp-mcp-server. You don't have to install it explicitly — the .mcp.json example in step 6 runs it with npx, which fetches it on first use. To put the basecamp-mcp-server command on your PATH instead:

npm install -g @kaiseraraujo/basecamp-mcp-server

To run it from a source checkout — for development, or to pin a local build:

git clone https://github.com/kaiseraraujo/basecamp-mcp-server.git
cd basecamp-mcp-server
npm install
npm run build

5. Configure the environment

| Variable | Required | Description | | --- | --- | --- | | BASECAMP_CLIENT_ID | yes | OAuth client ID from step 1 | | BASECAMP_CLIENT_SECRET | yes | OAuth client secret from step 1 | | BASECAMP_REFRESH_TOKEN | yes | Refresh token from step 2 | | BASECAMP_ACCOUNT_ID | yes | Numeric account ID from step 3 | | BASECAMP_USER_AGENT | no | Identifies your integration to Basecamp; they ask for a contact address, e.g. My Integration ([email protected]) | | BASECAMP_MAX_RETRIES | no | How many times to retry a failed request. Default 2; 0 disables retries |

These are credentials. They belong in your environment or your MCP client's config — never committed to this repository. The included .gitignore covers .env files, but the safest place is your client's config outside the repo.

6. Register it with your MCP client

An .mcp.json entry that runs the published package with npx — no path to keep in sync and no build step:

{
  "mcpServers": {
    "basecamp": {
      "command": "npx",
      "args": ["-y", "@kaiseraraujo/basecamp-mcp-server"],
      "env": {
        "BASECAMP_CLIENT_ID": "your-client-id",
        "BASECAMP_CLIENT_SECRET": "your-client-secret",
        "BASECAMP_REFRESH_TOKEN": "your-refresh-token",
        "BASECAMP_ACCOUNT_ID": "your-account-id",
        "BASECAMP_USER_AGENT": "My Integration ([email protected])"
      }
    }
  }
}

If you are working from a source checkout instead, point at the built entry point with an absolute path:

{
  "mcpServers": {
    "basecamp": {
      "command": "node",
      "args": ["/absolute/path/to/basecamp-mcp-server/dist/index.js"],
      "env": {
        "BASECAMP_CLIENT_ID": "your-client-id",
        "BASECAMP_CLIENT_SECRET": "your-client-secret",
        "BASECAMP_REFRESH_TOKEN": "your-refresh-token",
        "BASECAMP_ACCOUNT_ID": "your-account-id",
        "BASECAMP_USER_AGENT": "My Integration ([email protected])"
      }
    }
  }
}

Restart the client and ask it to run basecamp_whoami to confirm the credentials work.

Claude Code cloud environments

In a Claude Code cloud environment the four required BASECAMP_* variables are set as environment variables on the environment itself rather than in a config file, so setup is two lines:

npm install -g @kaiseraraujo/basecamp-mcp-server
claude mcp add basecamp --scope user -- basecamp-mcp-server

The server picks the credentials up from the environment, so there is no env block to fill in.

Behaviour worth knowing

Pagination

Basecamp paginates every collection at 15 records and honours no page-size parameter — per_page, page_size and limit are all ignored on the wire. Every list tool therefore takes a limit (default 50, max 500) that is served by following the Link header inside the server, so a 36-item to-do list comes back in one tool call rather than three. Whole pages are kept, so a response can run up to 14 items past the number you asked for; that keeps the reported next page exact and non-overlapping. Responses are still capped at 25,000 characters and report has_more when they are cut short.

Retries

Basecamp's edge intermittently returns 502s under concurrent writes, and rate-limits at 50 requests per 10-second window. Failed requests are retried automatically — twice by default, exponential backoff with full jitter, honouring Retry-After. Set BASECAMP_MAX_RETRIES=0 to turn this off.

Retries are asymmetric on purpose. A GET is retried on 429, 500, 502, 503 and 504, plus timeouts and dropped connections. A write is retried only on 429/502/503/504, where the request did not reach the application, and never on a 500 or a lost connection — there the record may well have been created, and a blind retry would duplicate it.

Other 5xx codes are left alone, since they describe something that will not clear by itself. Basecamp answers an over-quota project creation with 507, for instance; retrying it would only burn requests.

Errors carry Basecamp's own x-request-id as [Basecamp request ID: …], which is what 37signals support asks for when a failure needs chasing down.

content is plain text, description is HTML

On basecamp_create_todo and basecamp_update_todo the two fields treat markup in opposite ways, and it is easy to get caught by:

  • content (the title) is stored and displayed verbatim. Passing A & B puts the literal string A & B on screen, not A & B. Write the characters you want.
  • description is HTML, and renders the same entity as A & B.

The values are passed through unchanged rather than silently unescaped, since a title can legitimately contain an ampersand-escape.

Development

npm run build    # compile TypeScript to dist/
npm start        # run the built server on stdio
npm test         # build, then run the transport tests
npm run clean    # remove dist/

test/client.test.mjs swaps Basecamp for a scripted axios adapter, so the pagination and retry behaviour above is exercised exactly rather than waited on. It needs no credentials.

The server speaks JSON-RPC over stdio, so you can smoke-test it without Basecamp credentials — listing tools never touches the API:

printf '%s\n%s\n%s\n' \
 '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"1.0.0"}}}' \
 '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
 '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
 | node dist/index.js

Source layout:

  • src/basecamp.ts — OAuth token refresh, the authenticated HTTP wrapper, pagination, error mapping, and dock-tool resolution
  • src/format.ts — HTML stripping, markdown/JSON rendering, and the response size cap
  • src/types.ts — Basecamp record shapes
  • src/index.ts — tool definitions
  • test/client.test.mjs — transport tests for pagination, retries and error mapping

License

MIT