basecamp-mcp 1.0.4 → 1.1.0

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.
package/README.md CHANGED
@@ -6,9 +6,13 @@ Model Context Protocol (MCP) server for Basecamp integration. Enables LLMs to in
6
6
 
7
7
  The Basecamp MCP server requires Node.js 18+ and works with various MCP clients including Claude Code CLI, Claude Desktop, Cursor, VS Code, and others.
8
8
 
9
- ### Standard Configuration
9
+ ### Prerequisites
10
10
 
11
- The baseline setup applies across most tools:
11
+ You need a Basecamp OAuth app. Register one at [37signals Launchpad](https://launchpad.37signals.com/integrations) with the redirect URI set to `http://localhost:7652/callback`.
12
+
13
+ ### Installation
14
+
15
+ Add the MCP server to your client with your OAuth credentials:
12
16
 
13
17
  ```json
14
18
  {
@@ -18,114 +22,83 @@ The baseline setup applies across most tools:
18
22
  "args": ["-y", "basecamp-mcp@latest"],
19
23
  "env": {
20
24
  "BASECAMP_CLIENT_ID": "your_client_id",
21
- "BASECAMP_CLIENT_SECRET": "your_client_secret",
22
- "BASECAMP_REFRESH_TOKEN": "your_refresh_token",
23
- "BASECAMP_USER_AGENT": "YourApp (your@email.com)",
24
- "BASECAMP_ACCOUNT_ID": "account_id"
25
+ "BASECAMP_CLIENT_SECRET": "your_client_secret"
25
26
  }
26
27
  }
27
28
  }
28
29
  }
29
30
  ```
30
31
 
31
- ### Installation by Client
32
-
33
32
  **Claude Code CLI:**
34
33
  ```bash
35
34
  claude mcp add basecamp npx basecamp-mcp@latest \
36
35
  -e BASECAMP_CLIENT_ID=your_client_id \
37
- -e BASECAMP_CLIENT_SECRET=your_client_secret \
38
- -e BASECAMP_REFRESH_TOKEN=your_refresh_token \
39
- -e BASECAMP_USER_AGENT="YourApp (your@email.com)" \
40
- -e BASECAMP_ACCOUNT_ID=account_id
36
+ -e BASECAMP_CLIENT_SECRET=your_client_secret
41
37
  ```
42
38
 
43
- **Claude Desktop:** Follow the MCP install guide using the standard config above.
39
+ **Claude Desktop:** Follow the MCP install guide using the JSON config above.
44
40
 
45
- **Cursor:** One-click installation available, or manually add configuration through Settings → Tools & Integrations → New MCP Server.
41
+ **Cursor:** Add configuration through Settings → Tools & Integrations → New MCP Server.
46
42
 
47
- **VS Code:** One-click installation provided, or use the CLI:
43
+ **VS Code:**
48
44
  ```bash
49
45
  code --add-mcp '{"name":"basecamp","command":"npx","args":["-y", "basecamp-mcp@latest"]}'
50
46
  ```
51
47
 
52
- **Gemini CLI & Windsurf:** Refer to their respective documentation; use the standard config template.
53
-
54
- ## Configuration
48
+ ### Authentication
55
49
 
56
- The server requires the following environment variables:
50
+ Once the MCP server is running, authenticate using the built-in login tool:
57
51
 
58
- * **`BASECAMP_CLIENT_ID`**Your Basecamp OAuth client ID
59
- * **`BASECAMP_CLIENT_SECRET`** Your Basecamp OAuth client secret
60
- * **`BASECAMP_REFRESH_TOKEN`** Your Basecamp refresh token for authentication
61
- * **`BASECAMP_USER_AGENT`** Your application identifier (format: YourApp (our@email.com))
62
- * **`BASECAMP_ACCOUNT_ID`** — Your Basecamp account ID
52
+ 1. Call `basecamp_login` a browser window will open for Basecamp authorization
53
+ 2. Authorize the app in your browser
54
+ 3. If you have multiple Basecamp accounts, call `basecamp_login` again with the desired `account_id`
55
+ 4. Done! Credentials are saved to `~/.config/basecamp-mcp/credentials.json`
63
56
 
64
- ### Complete Configuration Example
57
+ Use `basecamp_whoami` to check who you're logged in as, and `basecamp_logout` to remove stored credentials.
65
58
 
66
- ```json
67
- {
68
- "mcpServers": {
69
- "basecamp": {
70
- "command": "npx",
71
- "args": ["-y", "basecamp-mcp@latest"],
72
- "env": {
73
- "BASECAMP_CLIENT_ID": "your_client_id",
74
- "BASECAMP_CLIENT_SECRET": "your_client_secret",
75
- "BASECAMP_REFRESH_TOKEN": "your_refresh_token",
76
- "BASECAMP_USER_AGENT": "YourApp (your@email.com)",
77
- "BASECAMP_ACCOUNT_ID": "account_id"
78
- }
79
- }
80
- }
81
- }
82
- ```
83
-
84
- ## Usage
85
-
86
- ### Running the Server
59
+ ## Configuration
87
60
 
88
- ```bash
89
- npm start
90
- ```
61
+ The server requires these environment variables:
91
62
 
92
- Or for development with auto-reload:
63
+ * **`BASECAMP_CLIENT_ID`** Your Basecamp OAuth client ID
64
+ * **`BASECAMP_CLIENT_SECRET`** — Your Basecamp OAuth client secret
93
65
 
94
- ```bash
95
- npm run dev
96
- ```
66
+ ## Available Tools
97
67
 
98
- ### Available Tools
68
+ ### Authentication
69
+ - `basecamp_login` - Authenticate with Basecamp via OAuth browser flow
70
+ - `basecamp_logout` - Remove stored credentials
71
+ - `basecamp_whoami` - Show the currently authenticated user
99
72
 
100
- #### Projects
73
+ ### Projects
101
74
  - `basecamp_list_projects` - List all accessible projects with optional filtering
102
75
  - `basecamp_get_project` - Get detailed project information including dock configuration
103
76
 
104
- #### Messages
77
+ ### Messages
105
78
  - `basecamp_list_messages` - List messages in a message board with optional filtering
106
79
  - `basecamp_list_message_types` - List available message types/categories for a project
107
80
  - `basecamp_get_message` - Get single message details
108
81
  - `basecamp_create_message` - Create new message with optional category and draft status
109
82
  - `basecamp_update_message` - Update message with advanced content editing (supports full replacement, append, prepend, search/replace)
110
83
 
111
- #### TODOs
84
+ ### TODOs
112
85
  - `basecamp_get_todoset` - Get todo set container with all todo lists
113
86
  - `basecamp_list_todos` - List todos in a list with status filtering (active/archived)
114
87
  - `basecamp_create_todo` - Create new todo with optional description
115
88
  - `basecamp_complete_todo` - Mark todo as complete
116
89
  - `basecamp_uncomplete_todo` - Mark todo as incomplete
117
90
 
118
- #### Comments
91
+ ### Comments
119
92
  - `basecamp_list_comments` - List comments on any resource (works universally on all recording types)
120
93
  - `basecamp_create_comment` - Add comment to any resource
121
94
  - `basecamp_update_comment` - Update comment with advanced content editing (supports full replacement, append, prepend, search/replace)
122
95
 
123
- #### People
96
+ ### People
124
97
  - `basecamp_get_me` - Get personal information for the authenticated user
125
98
  - `basecamp_list_people` - List all people with optional filtering by name, email, or title
126
99
  - `basecamp_get_person` - Get person details
127
100
 
128
- #### Kanban
101
+ ### Kanban
129
102
  - `basecamp_list_kanban_columns` - List all columns in a kanban board
130
103
  - `basecamp_list_kanban_cards` - List cards in a column with steps and assignees
131
104
  - `basecamp_get_kanban_card` - Get complete details of a specific card
@@ -133,7 +106,7 @@ npm run dev
133
106
  - `basecamp_update_kanban_card` - Update card with advanced content editing (supports full replacement, append, prepend, search/replace, plus title, due date, assignees, notifications, and complete step array management)
134
107
  - `basecamp_move_kanban_card` - Move a card to a different column and/or position
135
108
 
136
- #### Activity
109
+ ### Activity
137
110
  - `basecamp_list_recordings` - Browse recent activity globally or across specific projects, with filtering by type, date range, person, and text search. All filters support multiple values for OR-matching (e.g., multiple project IDs, person IDs, types, or search terms)
138
111
  - `basecamp_list_campfire_messages` - Browse chat messages from Campfires with filtering by campfire, person, text content, and date range. All filters support multiple values for OR-matching
139
112
 
package/dist/index.d.ts CHANGED
@@ -1,21 +1,3 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Basecamp MCP Server
4
- *
5
- * MCP server for interacting with Basecamp API, providing tools for:
6
- * - Projects (discovering bucket IDs)
7
- * - Messages (with patch support for updates)
8
- * - TODOs (sets, lists, todos)
9
- * - Comments (universal - work on any resource)
10
- * - People
11
- * - Kanban (cards, columns, steps)
12
- * - Activity (recordings browsing)
13
- *
14
- * Environment variables required:
15
- * - BASECAMP_CLIENT_ID
16
- * - BASECAMP_CLIENT_SECRET
17
- * - BASECAMP_REFRESH_TOKEN
18
- * - BASECAMP_USER_AGENT
19
- */
20
2
  export {};
21
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js CHANGED
@@ -1,58 +1,23 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Basecamp MCP Server
4
- *
5
- * MCP server for interacting with Basecamp API, providing tools for:
6
- * - Projects (discovering bucket IDs)
7
- * - Messages (with patch support for updates)
8
- * - TODOs (sets, lists, todos)
9
- * - Comments (universal - work on any resource)
10
- * - People
11
- * - Kanban (cards, columns, steps)
12
- * - Activity (recordings browsing)
13
- *
14
- * Environment variables required:
15
- * - BASECAMP_CLIENT_ID
16
- * - BASECAMP_CLIENT_SECRET
17
- * - BASECAMP_REFRESH_TOKEN
18
- * - BASECAMP_USER_AGENT
19
- */
20
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
21
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
4
  import { registerActivityTools } from "./tools/activity.js";
5
+ import { registerAuthTools } from "./tools/auth.js";
23
6
  import { registerCommentTools } from "./tools/comments.js";
24
7
  import { registerDocumentTools } from "./tools/documents.js";
25
8
  import { registerKanbanTools } from "./tools/kanban.js";
26
9
  import { registerMessageTools } from "./tools/messages.js";
27
10
  import { registerPeopleTools } from "./tools/people.js";
28
- // Import tool registration functions
29
11
  import { registerProjectTools } from "./tools/projects.js";
30
12
  import { registerTodoTools } from "./tools/todos.js";
31
13
  import { registerUploadTools } from "./tools/uploads.js";
32
- /**
33
- * Main server initialization and startup
34
- */
35
14
  async function main() {
36
- // Validate required environment variables
37
- const requiredEnvVars = [
38
- "BASECAMP_CLIENT_ID",
39
- "BASECAMP_CLIENT_SECRET",
40
- "BASECAMP_REFRESH_TOKEN",
41
- "BASECAMP_USER_AGENT",
42
- ];
43
- const missing = requiredEnvVars.filter((varName) => !process.env[varName]);
44
- if (missing.length > 0) {
45
- console.error(`ERROR: Missing required environment variables: ${missing.join(", ")}`);
46
- console.error("Please set these in your environment or .env file before starting the server.");
47
- process.exit(1);
48
- }
49
- // Create MCP server instance
50
15
  const server = new McpServer({
51
16
  name: "basecamp-mcp-server",
52
17
  version: "1.0.0",
53
18
  });
54
- // Register all tool categories
55
19
  console.error("Registering tools...");
20
+ registerAuthTools(server);
56
21
  registerProjectTools(server);
57
22
  registerMessageTools(server);
58
23
  registerTodoTools(server);
@@ -63,14 +28,10 @@ async function main() {
63
28
  registerDocumentTools(server);
64
29
  registerUploadTools(server);
65
30
  console.error("Tools registered successfully");
66
- // Create stdio transport
67
31
  const transport = new StdioServerTransport();
68
- // Connect server to transport
69
32
  await server.connect(transport);
70
33
  console.error("Basecamp MCP server running on stdio");
71
- console.error("Waiting for client connection...");
72
34
  }
73
- // Run the server
74
35
  main().catch((error) => {
75
36
  console.error("Fatal error starting Basecamp MCP server:", error);
76
37
  process.exit(1);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,qCAAqC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;GAEG;AACH,KAAK,UAAU,IAAI;IACjB,0CAA0C;IAC1C,MAAM,eAAe,GAAG;QACtB,oBAAoB;QACpB,wBAAwB;QACxB,wBAAwB;QACxB,qBAAqB;KACtB,CAAC;IAEF,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CACX,kDAAkD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACvE,CAAC;QACF,OAAO,CAAC,KAAK,CACX,+EAA+E,CAChF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,6BAA6B;IAC7B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,+BAA+B;IAC/B,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACtC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAE/C,yBAAyB;IACzB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAE7C,8BAA8B;IAC9B,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACtD,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;AACpD,CAAC;AAED,iBAAiB;AACjB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,CAAC;IAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,KAAK,UAAU,IAAI;IAClB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC5B,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,OAAO;KAChB,CAAC,CAAC;IAEH,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACtC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAE/C,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AACvD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACtB,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,CAAC;IAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAuthTools(server: McpServer): void;
3
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/tools/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYzE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2LzD"}
@@ -0,0 +1,163 @@
1
+ import { z } from "zod";
2
+ import { initializeBasecampClient, clearTokenCache } from "../utils/auth.js";
3
+ import { deleteCredentials, getCredentialsPath, readCredentials, writeCredentials, } from "../utils/credentials.js";
4
+ import { handleBasecampError } from "../utils/errorHandlers.js";
5
+ import { fetchBasecampAccounts, performBasecampOAuthLogin } from "../utils/oauth.js";
6
+ export function registerAuthTools(server) {
7
+ server.registerTool("basecamp_login", {
8
+ title: "Login to Basecamp",
9
+ description: "Authenticate with Basecamp via OAuth. Opens a browser window for authorization. " +
10
+ "If you have multiple Basecamp accounts, call first without account_id to see the list, " +
11
+ "then call again with the desired account_id.",
12
+ inputSchema: {
13
+ account_id: z
14
+ .number()
15
+ .optional()
16
+ .describe("Basecamp account ID. If omitted and you have multiple accounts, returns the list to choose from."),
17
+ },
18
+ annotations: {
19
+ readOnlyHint: false,
20
+ destructiveHint: false,
21
+ idempotentHint: true,
22
+ openWorldHint: true,
23
+ },
24
+ }, async (params) => {
25
+ try {
26
+ const { accessToken, refreshToken } = await performBasecampOAuthLogin();
27
+ const accounts = await fetchBasecampAccounts(accessToken);
28
+ if (accounts.length === 0) {
29
+ return {
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: "No Basecamp 3 accounts found for this user.",
34
+ },
35
+ ],
36
+ };
37
+ }
38
+ let accountId;
39
+ if (params.account_id) {
40
+ const match = accounts.find((a) => a.id === params.account_id);
41
+ if (!match) {
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text",
46
+ text: `Account ID ${params.account_id} not found. Available accounts:\n${formatAccountList(accounts)}`,
47
+ },
48
+ ],
49
+ };
50
+ }
51
+ accountId = String(match.id);
52
+ }
53
+ else if (accounts.length === 1) {
54
+ accountId = String(accounts[0].id);
55
+ }
56
+ else {
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: `Multiple Basecamp accounts found. Please call basecamp_login again with account_id set to one of:\n${formatAccountList(accounts)}`,
62
+ },
63
+ ],
64
+ };
65
+ }
66
+ clearTokenCache();
67
+ await writeCredentials({ refreshToken, accountId });
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: `Login successful! Credentials saved to ${getCredentialsPath()}. Account ID: ${accountId}.`,
73
+ },
74
+ ],
75
+ };
76
+ }
77
+ catch (error) {
78
+ return {
79
+ content: [
80
+ {
81
+ type: "text",
82
+ text: `Login failed: ${error instanceof Error ? error.message : String(error)}`,
83
+ },
84
+ ],
85
+ };
86
+ }
87
+ });
88
+ server.registerTool("basecamp_logout", {
89
+ title: "Logout from Basecamp",
90
+ description: "Remove stored Basecamp credentials.",
91
+ annotations: {
92
+ readOnlyHint: false,
93
+ destructiveHint: true,
94
+ idempotentHint: true,
95
+ openWorldHint: false,
96
+ },
97
+ }, async () => {
98
+ clearTokenCache();
99
+ await deleteCredentials();
100
+ return {
101
+ content: [
102
+ {
103
+ type: "text",
104
+ text: `Logged out. Credentials removed from ${getCredentialsPath()}.`,
105
+ },
106
+ ],
107
+ };
108
+ });
109
+ server.registerTool("basecamp_whoami", {
110
+ title: "Who Am I (Basecamp)",
111
+ description: "Show the currently authenticated Basecamp user, or indicate if not logged in.",
112
+ annotations: {
113
+ readOnlyHint: true,
114
+ destructiveHint: false,
115
+ idempotentHint: true,
116
+ openWorldHint: false,
117
+ },
118
+ }, async () => {
119
+ const creds = await readCredentials();
120
+ if (!creds) {
121
+ return {
122
+ content: [
123
+ {
124
+ type: "text",
125
+ text: "Not logged in. Use basecamp_login to authenticate.",
126
+ },
127
+ ],
128
+ };
129
+ }
130
+ try {
131
+ const client = await initializeBasecampClient();
132
+ const response = await client.people.me({});
133
+ if (response.status !== 200 || !response.body) {
134
+ throw new Error("Failed to get profile");
135
+ }
136
+ const me = response.body;
137
+ return {
138
+ content: [
139
+ {
140
+ type: "text",
141
+ text: JSON.stringify({
142
+ name: me.name,
143
+ email: me.email_address,
144
+ title: me.title,
145
+ account_id: creds.accountId,
146
+ }, null, 2),
147
+ },
148
+ ],
149
+ };
150
+ }
151
+ catch (error) {
152
+ return {
153
+ content: [
154
+ { type: "text", text: handleBasecampError(error) },
155
+ ],
156
+ };
157
+ }
158
+ });
159
+ }
160
+ function formatAccountList(accounts) {
161
+ return accounts.map((a) => `- ${a.name} (id: ${a.id})`).join("\n");
162
+ }
163
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/tools/auth.ts"],"names":[],"mappings":"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"}
@@ -1,24 +1,4 @@
1
- /**
2
- * Authentication utilities for Basecamp API
3
- */
4
1
  import { type Client } from "basecamp-client";
5
- /**
6
- * Initialize and return an authenticated Basecamp client.
7
- *
8
- * Uses environment variables:
9
- * - BASECAMP_CLIENT_ID
10
- * - BASECAMP_CLIENT_SECRET
11
- * - BASECAMP_REFRESH_TOKEN
12
- * - BASECAMP_USER_AGENT (optional)
13
- * - BASECAMP_ACCOUNT_ID
14
- *
15
- * @param accountId - Basecamp account ID to use for the client
16
- * @returns Authenticated Basecamp client instance
17
- * @throws Error if required environment variables are missing or authentication fails
18
- */
19
2
  export declare function initializeBasecampClient(): Promise<Client>;
20
- /**
21
- * Clear the cached bearer token (useful for forcing token refresh)
22
- */
23
3
  export declare function clearTokenCache(): void;
24
4
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAe,KAAK,MAAM,EAAkB,MAAM,iBAAiB,CAAC;AAK3E;;;;;;;;;;;;;GAaG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,MAAM,CAAC,CAwChE;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,MAAM,EAAkB,MAAM,iBAAiB,CAAC;AAM3E,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,MAAM,CAAC,CAqBhE;AAED,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
@@ -1,61 +1,25 @@
1
- /**
2
- * Authentication utilities for Basecamp API
3
- */
4
1
  import { buildClient, getBearerToken } from "basecamp-client";
5
- /** Cached bearer token to avoid repeated OAuth requests */
2
+ import { readCredentials } from "./credentials.js";
3
+ import { getClientCredentials } from "./oauth.js";
6
4
  let cachedBearerToken = null;
7
- /**
8
- * Initialize and return an authenticated Basecamp client.
9
- *
10
- * Uses environment variables:
11
- * - BASECAMP_CLIENT_ID
12
- * - BASECAMP_CLIENT_SECRET
13
- * - BASECAMP_REFRESH_TOKEN
14
- * - BASECAMP_USER_AGENT (optional)
15
- * - BASECAMP_ACCOUNT_ID
16
- *
17
- * @param accountId - Basecamp account ID to use for the client
18
- * @returns Authenticated Basecamp client instance
19
- * @throws Error if required environment variables are missing or authentication fails
20
- */
21
5
  export async function initializeBasecampClient() {
22
- // Validate required environment variables
23
- const requiredEnvVars = [
24
- "BASECAMP_CLIENT_ID",
25
- "BASECAMP_CLIENT_SECRET",
26
- "BASECAMP_REFRESH_TOKEN",
27
- "BASECAMP_ACCOUNT_ID",
28
- ];
29
- const missing = requiredEnvVars.filter((varName) => !process.env[varName]);
30
- if (missing.length > 0) {
31
- throw new Error(`Missing required environment variables: ${missing.join(", ")}. ` +
32
- `Please set these in your environment or .env file.`);
6
+ const creds = await readCredentials();
7
+ if (!creds) {
8
+ throw new Error("Not logged in. Use basecamp_login to authenticate.");
33
9
  }
34
- // Get bearer token (cache if possible to avoid repeated OAuth requests)
10
+ const { clientId, clientSecret } = getClientCredentials();
35
11
  if (!cachedBearerToken) {
36
- try {
37
- cachedBearerToken = await getBearerToken({
38
- clientId: process.env.BASECAMP_CLIENT_ID,
39
- clientSecret: process.env.BASECAMP_CLIENT_SECRET,
40
- refreshToken: process.env.BASECAMP_REFRESH_TOKEN,
41
- userAgent: process.env.BASECAMP_USER_AGENT,
42
- });
43
- }
44
- catch (error) {
45
- throw new Error(`Failed to obtain Basecamp access token: ${error instanceof Error ? error.message : String(error)}. ` +
46
- `Check your BASECAMP_CLIENT_ID, BASECAMP_CLIENT_SECRET, and BASECAMP_REFRESH_TOKEN are correct.`);
47
- }
12
+ cachedBearerToken = await getBearerToken({
13
+ clientId,
14
+ clientSecret,
15
+ refreshToken: creds.refreshToken,
16
+ });
48
17
  }
49
- // Build and return client
50
18
  return buildClient({
51
19
  bearerToken: cachedBearerToken,
52
- accountId: process.env.BASECAMP_ACCOUNT_ID,
53
- userAgent: process.env.BASECAMP_USER_AGENT,
20
+ accountId: creds.accountId,
54
21
  });
55
22
  }
56
- /**
57
- * Clear the cached bearer token (useful for forcing token refresh)
58
- */
59
23
  export function clearTokenCache() {
60
24
  cachedBearerToken = null;
61
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAe,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE3E,2DAA2D;AAC3D,IAAI,iBAAiB,GAAkB,IAAI,CAAC;AAE5C;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,0CAA0C;IAC1C,MAAM,eAAe,GAAG;QACtB,oBAAoB;QACpB,wBAAwB;QACxB,wBAAwB;QACxB,qBAAqB;KACtB,CAAC;IAEF,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,2CAA2C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YAC/D,oDAAoD,CACvD,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,iBAAiB,GAAG,MAAM,cAAc,CAAC;gBACvC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAmB;gBACzC,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAuB;gBACjD,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAuB;gBACjD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB;aAC3C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,2CAA2C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI;gBACnG,gGAAgG,CACnG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,OAAO,WAAW,CAAC;QACjB,WAAW,EAAE,iBAAiB;QAC9B,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAoB;QAC3C,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB;KAC3C,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,iBAAiB,GAAG,IAAI,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAElD,IAAI,iBAAiB,GAAkB,IAAI,CAAC;AAE5C,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC7C,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;IAEtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,oBAAoB,EAAE,CAAC;IAE1D,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACxB,iBAAiB,GAAG,MAAM,cAAc,CAAC;YACxC,QAAQ;YACR,YAAY;YACZ,YAAY,EAAE,KAAK,CAAC,YAAY;SAChC,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,WAAW,CAAC;QAClB,WAAW,EAAE,iBAAiB;QAC9B,SAAS,EAAE,KAAK,CAAC,SAAS;KAC1B,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe;IAC9B,iBAAiB,GAAG,IAAI,CAAC;AAC1B,CAAC"}
@@ -16,11 +16,11 @@ export declare const ContentOperationFields: {
16
16
  find: z.ZodString;
17
17
  replace: z.ZodString;
18
18
  }, "strip", z.ZodTypeAny, {
19
- find: string;
20
19
  replace: string;
21
- }, {
22
20
  find: string;
21
+ }, {
23
22
  replace: string;
23
+ find: string;
24
24
  }>, "many">>;
25
25
  };
26
26
  /**
@@ -0,0 +1,9 @@
1
+ export type Credentials = {
2
+ refreshToken: string;
3
+ accountId: string;
4
+ };
5
+ export declare function getCredentialsPath(): string;
6
+ export declare function readCredentials(): Promise<Credentials | null>;
7
+ export declare function writeCredentials(creds: Credentials): Promise<void>;
8
+ export declare function deleteCredentials(): Promise<void>;
9
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/utils/credentials.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GAAG;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AASF,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAanE;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMvD"}
@@ -0,0 +1,35 @@
1
+ import { chmod, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ const CREDENTIALS_PATH = join(homedir(), ".config", "basecamp-mcp", "credentials.json");
5
+ export function getCredentialsPath() {
6
+ return CREDENTIALS_PATH;
7
+ }
8
+ export async function readCredentials() {
9
+ try {
10
+ const content = await readFile(CREDENTIALS_PATH, "utf-8");
11
+ const data = JSON.parse(content);
12
+ if (!data.refreshToken || !data.accountId) {
13
+ return null;
14
+ }
15
+ return { refreshToken: data.refreshToken, accountId: data.accountId };
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ export async function writeCredentials(creds) {
22
+ const dir = dirname(CREDENTIALS_PATH);
23
+ await mkdir(dir, { recursive: true });
24
+ await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
25
+ await chmod(CREDENTIALS_PATH, 0o600);
26
+ }
27
+ export async function deleteCredentials() {
28
+ try {
29
+ await unlink(CREDENTIALS_PATH);
30
+ }
31
+ catch {
32
+ // File doesn't exist, nothing to do
33
+ }
34
+ }
35
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/utils/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAO1C,MAAM,gBAAgB,GAAG,IAAI,CAC5B,OAAO,EAAE,EACT,SAAS,EACT,cAAc,EACd,kBAAkB,CAClB,CAAC;AAEF,MAAM,UAAU,kBAAkB;IACjC,OAAO,gBAAgB,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACpC,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAEjC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YAC3C,OAAO,IAAI,CAAC;QACb,CAAC;QAED,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAkB;IACxD,MAAM,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACtC,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3E,MAAM,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACtC,IAAI,CAAC;QACJ,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACR,oCAAoC;IACrC,CAAC;AACF,CAAC"}
@@ -0,0 +1,16 @@
1
+ export declare function getClientCredentials(): {
2
+ clientId: string;
3
+ clientSecret: string;
4
+ };
5
+ export declare function performBasecampOAuthLogin(): Promise<{
6
+ accessToken: string;
7
+ refreshToken: string;
8
+ }>;
9
+ export type BasecampAccount = {
10
+ id: number;
11
+ name: string;
12
+ href: string;
13
+ product: string;
14
+ };
15
+ export declare function fetchBasecampAccounts(accessToken: string): Promise<BasecampAccount[]>;
16
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/utils/oauth.ts"],"names":[],"mappings":"AAeA,wBAAgB,oBAAoB,IAAI;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACrB,CAWA;AAwID,wBAAsB,yBAAyB,IAAI,OAAO,CAAC;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACrB,CAAC,CAqBD;AAED,MAAM,MAAM,eAAe,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,qBAAqB,CAC1C,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,EAAE,CAAC,CAiB5B"}
@@ -0,0 +1,131 @@
1
+ import { exec } from "node:child_process";
2
+ import { createServer, } from "node:http";
3
+ import { platform } from "node:os";
4
+ import { URL } from "node:url";
5
+ const REDIRECT_PORT = 7652;
6
+ const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/callback`;
7
+ const AUTHORIZE_BASE = "https://launchpad.37signals.com/authorization/new";
8
+ const TOKEN_URL = "https://launchpad.37signals.com/authorization/token";
9
+ const AUTH_INFO_URL = "https://launchpad.37signals.com/authorization.json";
10
+ export function getClientCredentials() {
11
+ const clientId = process.env.BASECAMP_CLIENT_ID;
12
+ const clientSecret = process.env.BASECAMP_CLIENT_SECRET;
13
+ if (!clientId || !clientSecret) {
14
+ throw new Error("Missing BASECAMP_CLIENT_ID and BASECAMP_CLIENT_SECRET environment variables.");
15
+ }
16
+ return { clientId, clientSecret };
17
+ }
18
+ function buildAuthorizeUrl(clientId) {
19
+ const params = new URLSearchParams({
20
+ type: "web_server",
21
+ client_id: clientId,
22
+ redirect_uri: REDIRECT_URI,
23
+ });
24
+ return `${AUTHORIZE_BASE}?${params.toString()}`;
25
+ }
26
+ function openBrowser(url) {
27
+ const os = platform();
28
+ const cmd = os === "darwin" ? "open" : os === "win32" ? "start" : "xdg-open";
29
+ exec(`${cmd} "${url}"`);
30
+ }
31
+ async function exchangeCodeForTokens(code, clientId, clientSecret) {
32
+ const response = await fetch(TOKEN_URL, {
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/json" },
35
+ body: JSON.stringify({
36
+ type: "web_server",
37
+ client_id: clientId,
38
+ client_secret: clientSecret,
39
+ redirect_uri: REDIRECT_URI,
40
+ code,
41
+ }),
42
+ });
43
+ if (!response.ok) {
44
+ const body = await response.text();
45
+ throw new Error(`Token exchange failed (${response.status}): ${body}`);
46
+ }
47
+ const data = (await response.json());
48
+ return { accessToken: data.access_token, refreshToken: data.refresh_token };
49
+ }
50
+ const SUCCESS_HTML = `<!DOCTYPE html>
51
+ <html>
52
+ <head><title>Basecamp MCP</title></head>
53
+ <body style="font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
54
+ <div style="text-align: center;">
55
+ <h1>Login successful!</h1>
56
+ <p>You can close this tab and return to your MCP client.</p>
57
+ </div>
58
+ </body>
59
+ </html>`;
60
+ function startCallbackServer(clientId, clientSecret) {
61
+ return new Promise((resolveStart) => {
62
+ let resolveTokens;
63
+ const tokensPromise = new Promise((resolve) => {
64
+ resolveTokens = resolve;
65
+ });
66
+ const server = createServer(async (req, res) => {
67
+ const url = new URL(req.url || "/", `http://localhost:${REDIRECT_PORT}`);
68
+ if (url.pathname !== "/callback") {
69
+ res.writeHead(404);
70
+ res.end("Not found");
71
+ return;
72
+ }
73
+ const code = url.searchParams.get("code");
74
+ if (!code) {
75
+ const error = url.searchParams.get("error_description") ||
76
+ "No authorization code received";
77
+ res.writeHead(400);
78
+ res.end(error);
79
+ server.close();
80
+ resolveTokens(null);
81
+ return;
82
+ }
83
+ try {
84
+ const tokens = await exchangeCodeForTokens(code, clientId, clientSecret);
85
+ res.writeHead(200, { "Content-Type": "text/html" });
86
+ res.end(SUCCESS_HTML);
87
+ server.close();
88
+ resolveTokens(tokens);
89
+ }
90
+ catch (err) {
91
+ res.writeHead(500);
92
+ res.end(`Token exchange failed: ${err instanceof Error ? err.message : String(err)}`);
93
+ server.close();
94
+ resolveTokens(null);
95
+ }
96
+ });
97
+ server.once("error", () => {
98
+ resolveStart(null);
99
+ });
100
+ server.once("listening", () => {
101
+ resolveStart({ waitForTokens: () => tokensPromise });
102
+ });
103
+ server.listen(REDIRECT_PORT, "127.0.0.1");
104
+ });
105
+ }
106
+ export async function performBasecampOAuthLogin() {
107
+ const { clientId, clientSecret } = getClientCredentials();
108
+ const serverHandle = await startCallbackServer(clientId, clientSecret);
109
+ if (!serverHandle) {
110
+ throw new Error(`Could not start local server on port ${REDIRECT_PORT}. Is another process using it?`);
111
+ }
112
+ const authorizeUrl = buildAuthorizeUrl(clientId);
113
+ openBrowser(authorizeUrl);
114
+ const tokens = await serverHandle.waitForTokens();
115
+ if (!tokens) {
116
+ throw new Error("Authentication failed — no tokens received");
117
+ }
118
+ return tokens;
119
+ }
120
+ export async function fetchBasecampAccounts(accessToken) {
121
+ const response = await fetch(AUTH_INFO_URL, {
122
+ headers: { Authorization: `Bearer ${accessToken}` },
123
+ });
124
+ if (!response.ok) {
125
+ const body = await response.text();
126
+ throw new Error(`Failed to fetch accounts (${response.status}): ${body}`);
127
+ }
128
+ const data = (await response.json());
129
+ return data.accounts.filter((a) => a.product === "bc3");
130
+ }
131
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/utils/oauth.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basecamp-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "Model Context Protocol (MCP) server for Basecamp integration. Enables LLMs to interact with Basecamp projects, messages, todos, comments, people, and kanban boards.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",