exponential-mcp 0.1.1

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 ADDED
@@ -0,0 +1,132 @@
1
+ # Exponential MCP
2
+
3
+ Connect Claude to your [Exponential](https://exponential.im) workspace. Manage projects, actions, and OKRs directly from Claude.
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Create an API Key
8
+
9
+ Go to [exponential.im/settings/api-keys](https://www.exponential.im/settings/api-keys) and create a new key:
10
+ - Click **Create API Key**
11
+ - Select **JWT Token** as the token type
12
+ - Copy the generated key
13
+
14
+ ### 2. Set Up the MCP Server
15
+
16
+ ```bash
17
+ npx exponential-mcp init
18
+ ```
19
+
20
+ Paste your API key when prompted.
21
+
22
+ ### 3. Configure for Your Claude Client
23
+
24
+ **For Claude Desktop:**
25
+ - `exponential-mcp init` will automatically configure Claude Desktop
26
+ - Restart Claude Desktop
27
+
28
+ **For Claude Code (VSCode Extension):**
29
+ - The server uses `.mcp.json` in your project directory
30
+ - Reload your VSCode window (`Cmd+Shift+P` → "Developer: Reload Window")
31
+ - The server will be available in your current project
32
+
33
+ ### 4. Ask Claude to manage your tasks!
34
+
35
+ ## What Claude Can Do
36
+
37
+ Once connected, Claude can:
38
+
39
+ - **List projects** – "What projects am I working on?"
40
+ - **View actions** – "Show my active tasks"
41
+ - **Create actions** – "Add a task to call John tomorrow"
42
+ - **Complete actions** – "Mark the report task as done"
43
+ - **View OKRs** – "What are my Q1 goals?"
44
+ - **Search** – "Find anything related to Kenya"
45
+
46
+ ## Manual Setup
47
+
48
+ ### For Claude Desktop
49
+
50
+ **macOS (recommended path):** `~/Library/Application Support/Claude/claude_desktop_config.json`
51
+ **Legacy path:** `~/.claude/claude_desktop_config.json`
52
+
53
+ Add this to the appropriate file:
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "exponential": {
59
+ "command": "/absolute/path/to/node",
60
+ "args": ["/absolute/path/to/exponential-mcp/dist/index.js"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### For Claude Code (VSCode Extension)
67
+
68
+ Create a `.mcp.json` file in your project directory:
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "exponential": {
74
+ "command": "npx",
75
+ "args": ["-y", "exponential-mcp", "serve"]
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ Then reload your VSCode window.
82
+
83
+ **Note:** The API key is stored in the Exponential SDK config store (created by `npx exponential-mcp init`), so you don't need to specify it in the MCP configuration. Run `exponential-mcp config` to see the current storage path and values. Run `exponential-mcp doctor` to print a recommended MCP config snippet for your machine.
84
+
85
+ ## Commands
86
+
87
+ ```bash
88
+ # Initialize with your API key
89
+ exponential-mcp init
90
+
91
+ # Show current config
92
+ exponential-mcp config
93
+
94
+ # Diagnose local setup
95
+ exponential-mcp doctor
96
+
97
+ # Start server manually (usually not needed)
98
+ exponential-mcp serve
99
+ ```
100
+
101
+ ## Available Tools
102
+
103
+ | Tool | Description |
104
+ |------|-------------|
105
+ | `get_workspaces` | List all workspaces |
106
+ | `get_projects` | List projects (optionally by workspace) |
107
+ | `get_actions` | List actions/tasks (filter by project or status) |
108
+ | `create_action` | Create a new task (supports natural language) |
109
+ | `complete_action` | Mark an action as done |
110
+ | `get_goals` | List OKRs with progress |
111
+ | `search` | Search across everything |
112
+
113
+ ## Development
114
+
115
+ ```bash
116
+ # Clone the repo
117
+ git clone https://github.com/your-org/exponential-mcp
118
+ cd exponential-mcp
119
+
120
+ # Install dependencies
121
+ npm install
122
+
123
+ # Build
124
+ npm run build
125
+
126
+ # Run locally
127
+ npm start
128
+ ```
129
+
130
+ ## License
131
+
132
+ MIT
package/dist/api.d.ts ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Exponential API Client
3
+ * Handles authenticated requests to the Exponential API
4
+ */
5
+ export interface ExponentialConfig {
6
+ apiKey: string;
7
+ baseUrl?: string;
8
+ }
9
+ export interface Project {
10
+ id: string;
11
+ name: string;
12
+ description?: string;
13
+ status: string;
14
+ priority: string;
15
+ progress: number;
16
+ }
17
+ export interface Action {
18
+ id: string;
19
+ name: string;
20
+ description?: string;
21
+ status: string;
22
+ priority: string;
23
+ dueDate?: string;
24
+ projectId?: string;
25
+ project?: {
26
+ name: string;
27
+ };
28
+ }
29
+ export interface Goal {
30
+ id: number;
31
+ title: string;
32
+ description?: string;
33
+ period?: string;
34
+ keyResults: KeyResult[];
35
+ }
36
+ export interface KeyResult {
37
+ id: string;
38
+ title: string;
39
+ targetValue?: number;
40
+ currentValue?: number;
41
+ status: string;
42
+ }
43
+ export interface Workspace {
44
+ id: string;
45
+ name: string;
46
+ slug: string;
47
+ }
48
+ export declare class ExponentialAPI {
49
+ private apiKey;
50
+ private baseUrl;
51
+ constructor(config: ExponentialConfig);
52
+ private request;
53
+ private trpcQuery;
54
+ private trpcMutation;
55
+ getWorkspaces(): Promise<Workspace[]>;
56
+ getProjects(workspaceId?: string): Promise<Project[]>;
57
+ getProject(id: string): Promise<Project>;
58
+ getActions(params?: {
59
+ projectId?: string;
60
+ status?: string;
61
+ }): Promise<Action[]>;
62
+ createAction(data: {
63
+ name: string;
64
+ description?: string;
65
+ projectId?: string;
66
+ priority?: string;
67
+ dueDate?: string;
68
+ }): Promise<Action>;
69
+ updateAction(id: string, data: {
70
+ name?: string;
71
+ status?: string;
72
+ priority?: string;
73
+ dueDate?: string;
74
+ }): Promise<Action>;
75
+ completeAction(id: string): Promise<Action>;
76
+ getGoals(workspaceId?: string): Promise<Goal[]>;
77
+ getGoal(id: number): Promise<Goal>;
78
+ quickCreateAction(text: string): Promise<Action>;
79
+ search(query: string): Promise<{
80
+ projects: Project[];
81
+ actions: Action[];
82
+ goals: Goal[];
83
+ }>;
84
+ }
package/dist/api.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Exponential API Client
3
+ * Handles authenticated requests to the Exponential API
4
+ */
5
+ const DEFAULT_BASE_URL = 'https://www.exponential.im';
6
+ export class ExponentialAPI {
7
+ apiKey;
8
+ baseUrl;
9
+ constructor(config) {
10
+ this.apiKey = config.apiKey;
11
+ this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
12
+ }
13
+ async request(endpoint, options = {}) {
14
+ const url = `${this.baseUrl}${endpoint}`;
15
+ const response = await fetch(url, {
16
+ ...options,
17
+ headers: {
18
+ 'Authorization': `Bearer ${this.apiKey}`,
19
+ 'Content-Type': 'application/json',
20
+ ...options.headers,
21
+ },
22
+ });
23
+ if (!response.ok) {
24
+ const error = await response.text();
25
+ throw new Error(`Exponential API error (${response.status}): ${error}`);
26
+ }
27
+ return response.json();
28
+ }
29
+ // tRPC-style calls
30
+ async trpcQuery(procedure, input) {
31
+ const inputStr = input ? encodeURIComponent(JSON.stringify(input)) : '';
32
+ const url = `/api/trpc/${procedure}${inputStr ? `?input=${inputStr}` : ''}`;
33
+ return this.request(url).then(r => r.result.data);
34
+ }
35
+ async trpcMutation(procedure, input) {
36
+ const url = `/api/trpc/${procedure}`;
37
+ return this.request(url, {
38
+ method: 'POST',
39
+ body: JSON.stringify(input),
40
+ }).then(r => r.result.data);
41
+ }
42
+ // === Workspace ===
43
+ async getWorkspaces() {
44
+ return this.trpcQuery('workspace.list');
45
+ }
46
+ // === Projects ===
47
+ async getProjects(workspaceId) {
48
+ return this.trpcQuery('project.list', { workspaceId });
49
+ }
50
+ async getProject(id) {
51
+ return this.trpcQuery('project.get', { id });
52
+ }
53
+ // === Actions ===
54
+ async getActions(params) {
55
+ return this.trpcQuery('action.list', params);
56
+ }
57
+ async createAction(data) {
58
+ return this.trpcMutation('action.create', data);
59
+ }
60
+ async updateAction(id, data) {
61
+ return this.trpcMutation('action.update', { id, ...data });
62
+ }
63
+ async completeAction(id) {
64
+ return this.updateAction(id, { status: 'COMPLETED' });
65
+ }
66
+ // === Goals (OKRs) ===
67
+ async getGoals(workspaceId) {
68
+ return this.trpcQuery('goal.list', { workspaceId });
69
+ }
70
+ async getGoal(id) {
71
+ return this.trpcQuery('goal.get', { id });
72
+ }
73
+ // === Quick Actions ===
74
+ async quickCreateAction(text) {
75
+ // Uses natural language parsing (dates, project names, etc.)
76
+ return this.trpcMutation('action.quickCreate', { text });
77
+ }
78
+ // === Search ===
79
+ async search(query) {
80
+ return this.trpcQuery('search.global', { query });
81
+ }
82
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Exponential MCP CLI
4
+ * Setup and configuration for the MCP server
5
+ */
6
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Exponential MCP CLI
4
+ * Setup and configuration for the MCP server
5
+ */
6
+ import { program } from 'commander';
7
+ import { createConfigStore } from 'exponential-sdk';
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
9
+ import { homedir } from 'os';
10
+ import { dirname, join } from 'path';
11
+ import * as readline from 'readline';
12
+ import { fileURLToPath } from 'url';
13
+ const CONFIG_DIR = join(homedir(), '.config', 'exponential-mcp');
14
+ const LEGACY_CONFIG_PATH = join(CONFIG_DIR, 'config.json');
15
+ const configStore = createConfigStore({ projectName: 'exponential-mcp' });
16
+ const LEGACY_CLAUDE_CONFIG_PATH = join(homedir(), '.claude', 'claude_desktop_config.json');
17
+ const MACOS_CLAUDE_CONFIG_PATH = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
18
+ function prompt(question) {
19
+ const rl = readline.createInterface({
20
+ input: process.stdin,
21
+ output: process.stdout,
22
+ });
23
+ return new Promise((resolve) => {
24
+ rl.question(question, (answer) => {
25
+ rl.close();
26
+ resolve(answer.trim());
27
+ });
28
+ });
29
+ }
30
+ function migrateLegacyConfig() {
31
+ if (configStore.isAuthenticated() || !existsSync(LEGACY_CONFIG_PATH)) {
32
+ return;
33
+ }
34
+ try {
35
+ const legacy = JSON.parse(readFileSync(LEGACY_CONFIG_PATH, 'utf-8'));
36
+ if (legacy?.apiKey) {
37
+ configStore.saveConfig({
38
+ token: legacy.apiKey,
39
+ apiUrl: legacy.baseUrl || 'https://www.exponential.im',
40
+ });
41
+ }
42
+ }
43
+ catch {
44
+ // Ignore legacy config parsing errors.
45
+ }
46
+ }
47
+ function getClaudeConfigPath() {
48
+ if (process.platform === 'darwin') {
49
+ if (existsSync(MACOS_CLAUDE_CONFIG_PATH)) {
50
+ return MACOS_CLAUDE_CONFIG_PATH;
51
+ }
52
+ if (existsSync(LEGACY_CLAUDE_CONFIG_PATH)) {
53
+ return LEGACY_CLAUDE_CONFIG_PATH;
54
+ }
55
+ return MACOS_CLAUDE_CONFIG_PATH;
56
+ }
57
+ return LEGACY_CLAUDE_CONFIG_PATH;
58
+ }
59
+ function getServerEntry() {
60
+ const serverPath = fileURLToPath(new URL('./index.js', import.meta.url));
61
+ return {
62
+ command: process.execPath,
63
+ args: [serverPath],
64
+ };
65
+ }
66
+ function ensureParentDir(filePath) {
67
+ const dir = dirname(filePath);
68
+ if (!existsSync(dir)) {
69
+ mkdirSync(dir, { recursive: true });
70
+ }
71
+ }
72
+ program
73
+ .name('exponential-mcp')
74
+ .description('MCP server for Exponential - connect Claude to your productivity data')
75
+ .version('0.1.0');
76
+ program
77
+ .command('init')
78
+ .description('Set up Exponential MCP with your API key')
79
+ .option('-k, --key <key>', 'API key (or will prompt)')
80
+ .option('--base-url <url>', 'Custom Exponential URL (default: https://www.exponential.im)')
81
+ .action(async (options) => {
82
+ console.log('🚀 Setting up Exponential MCP...\n');
83
+ // Get API key
84
+ let apiKey = options.key;
85
+ if (!apiKey) {
86
+ console.log('Create an API key at: https://www.exponential.im/settings/api-keys');
87
+ console.log(' → Select "JWT Token" as the token type\n');
88
+ apiKey = await prompt('Paste your API key: ');
89
+ }
90
+ if (!apiKey) {
91
+ console.error('❌ API key is required');
92
+ process.exit(1);
93
+ }
94
+ // Create config directory for legacy migrations.
95
+ if (!existsSync(CONFIG_DIR)) {
96
+ mkdirSync(CONFIG_DIR, { recursive: true });
97
+ }
98
+ configStore.saveConfig({
99
+ token: apiKey,
100
+ apiUrl: options.baseUrl || 'https://www.exponential.im',
101
+ });
102
+ console.log(`✅ Config saved to ${configStore.getConfigPath()}\n`);
103
+ // Update Claude Desktop config
104
+ await updateClaudeConfig();
105
+ console.log('\n🎉 Setup complete!\n');
106
+ console.log('Next steps:');
107
+ console.log(' 1. Restart Claude Desktop');
108
+ console.log(' 2. Ask Claude to "list my projects" or "create a task"\n');
109
+ });
110
+ program
111
+ .command('config')
112
+ .description('Show current configuration')
113
+ .action(() => {
114
+ migrateLegacyConfig();
115
+ if (!configStore.isAuthenticated()) {
116
+ console.log('No config found. Run "exponential-mcp init" first.');
117
+ return;
118
+ }
119
+ const config = configStore.loadConfig();
120
+ console.log('Current config:');
121
+ console.log(` Base URL: ${config.apiUrl}`);
122
+ console.log(` API Key: ${config.token.substring(0, 10)}...`);
123
+ });
124
+ program
125
+ .command('serve')
126
+ .description('Start the MCP server (usually called by Claude Desktop)')
127
+ .action(async () => {
128
+ // Import and run the server
129
+ await import('./index.js');
130
+ });
131
+ program
132
+ .command('doctor')
133
+ .description('Diagnose local MCP setup for Claude Desktop')
134
+ .action(() => {
135
+ migrateLegacyConfig();
136
+ const issues = [];
137
+ const configPath = getClaudeConfigPath();
138
+ const recommended = getServerEntry();
139
+ console.log('🩺 Exponential MCP Doctor\n');
140
+ if (!configStore.isAuthenticated()) {
141
+ issues.push('No API key found. Run "exponential-mcp init" first.');
142
+ }
143
+ if (!existsSync(configPath)) {
144
+ issues.push(`Claude Desktop config not found at ${configPath}`);
145
+ }
146
+ let config = null;
147
+ if (existsSync(configPath)) {
148
+ try {
149
+ config = JSON.parse(readFileSync(configPath, 'utf-8'));
150
+ }
151
+ catch {
152
+ issues.push(`Claude Desktop config is not valid JSON: ${configPath}`);
153
+ }
154
+ }
155
+ const serverConfig = config?.mcpServers?.exponential;
156
+ if (!serverConfig) {
157
+ issues.push('No "exponential" entry found in Claude Desktop config.');
158
+ }
159
+ else {
160
+ if (!serverConfig.command) {
161
+ issues.push('MCP config is missing "command" for Exponential.');
162
+ }
163
+ else if (!existsSync(serverConfig.command)) {
164
+ issues.push(`MCP command not found: ${serverConfig.command}`);
165
+ }
166
+ const serverArg = Array.isArray(serverConfig.args) ? serverConfig.args[0] : null;
167
+ if (!serverArg) {
168
+ issues.push('MCP config args missing server entry point.');
169
+ }
170
+ else if (!existsSync(serverArg)) {
171
+ issues.push(`MCP server entry not found: ${serverArg}`);
172
+ }
173
+ }
174
+ if (issues.length === 0) {
175
+ console.log('✅ No issues found. Claude Desktop should load the Exponential MCP server.');
176
+ }
177
+ else {
178
+ console.log('⚠️ Issues found:\n');
179
+ for (const issue of issues) {
180
+ console.log(`- ${issue}`);
181
+ }
182
+ }
183
+ console.log('\nRecommended MCP config:\n');
184
+ console.log(JSON.stringify({
185
+ mcpServers: {
186
+ exponential: recommended,
187
+ },
188
+ }, null, 2));
189
+ });
190
+ async function updateClaudeConfig() {
191
+ const configPath = getClaudeConfigPath();
192
+ ensureParentDir(configPath);
193
+ let claudeConfig = { mcpServers: {} };
194
+ // Read existing config if present
195
+ if (existsSync(configPath)) {
196
+ try {
197
+ claudeConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
198
+ if (!claudeConfig.mcpServers) {
199
+ claudeConfig.mcpServers = {};
200
+ }
201
+ }
202
+ catch (e) {
203
+ console.log('⚠️ Could not parse existing Claude config, creating new one');
204
+ }
205
+ }
206
+ // Add exponential server
207
+ claudeConfig.mcpServers.exponential = getServerEntry();
208
+ writeFileSync(configPath, JSON.stringify(claudeConfig, null, 2));
209
+ console.log(`✅ Claude Desktop config updated: ${configPath}`);
210
+ }
211
+ program.parse();
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Exponential MCP Server
4
+ * Connects Claude to your Exponential workspace
5
+ */
6
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,299 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Exponential MCP Server
4
+ * Connects Claude to your Exponential workspace
5
+ */
6
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
9
+ import { ExponentialClient, createConfigStore } from 'exponential-sdk';
10
+ import { readFileSync, existsSync } from 'fs';
11
+ import { homedir } from 'os';
12
+ import { join } from 'path';
13
+ const LEGACY_CONFIG_PATH = join(homedir(), '.config', 'exponential-mcp', 'config.json');
14
+ const configStore = createConfigStore({ projectName: 'exponential-mcp' });
15
+ function migrateLegacyConfig() {
16
+ if (configStore.isAuthenticated() || !existsSync(LEGACY_CONFIG_PATH)) {
17
+ return;
18
+ }
19
+ try {
20
+ const legacy = JSON.parse(readFileSync(LEGACY_CONFIG_PATH, 'utf-8'));
21
+ if (legacy?.apiKey) {
22
+ configStore.saveConfig({
23
+ token: legacy.apiKey,
24
+ apiUrl: legacy.baseUrl || 'https://www.exponential.im',
25
+ });
26
+ }
27
+ }
28
+ catch {
29
+ // Ignore legacy config parsing errors.
30
+ }
31
+ }
32
+ function loadClientConfig() {
33
+ migrateLegacyConfig();
34
+ if (configStore.isAuthenticated()) {
35
+ const config = configStore.loadConfig();
36
+ return { token: config.token, apiUrl: config.apiUrl };
37
+ }
38
+ const token = process.env.EXPONENTIAL_API_KEY || process.env.EXPONENTIAL_API_TOKEN;
39
+ const apiUrl = process.env.EXPONENTIAL_API_URL ||
40
+ process.env.EXPONENTIAL_BASE_URL ||
41
+ 'https://www.exponential.im';
42
+ if (!token) {
43
+ console.error('Error: No API key found.');
44
+ console.error('Run "npx exponential-mcp init" to set up, or set EXPONENTIAL_API_KEY env var.');
45
+ process.exit(1);
46
+ }
47
+ return { token, apiUrl };
48
+ }
49
+ // Tool definitions
50
+ const TOOLS = [
51
+ {
52
+ name: 'get_projects',
53
+ description: 'List all projects in Exponential. Returns project names, status, and progress.',
54
+ inputSchema: {
55
+ type: 'object',
56
+ properties: {
57
+ workspaceId: {
58
+ type: 'string',
59
+ description: 'Optional workspace ID to filter projects'
60
+ }
61
+ }
62
+ }
63
+ },
64
+ {
65
+ name: 'get_actions',
66
+ description: 'List actions/tasks. Can filter by project or status.',
67
+ inputSchema: {
68
+ type: 'object',
69
+ properties: {
70
+ projectId: {
71
+ type: 'string',
72
+ description: 'Filter by project ID'
73
+ },
74
+ status: {
75
+ type: 'string',
76
+ enum: ['ACTIVE', 'COMPLETED', 'CANCELLED'],
77
+ description: 'Filter by status (default: ACTIVE)'
78
+ }
79
+ }
80
+ }
81
+ },
82
+ {
83
+ name: 'create_action',
84
+ description: 'Create a new action/task. Supports natural language with dates and project names.',
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {
88
+ text: {
89
+ type: 'string',
90
+ description: 'Action description (can include "tomorrow", "next week", project names, etc.)'
91
+ }
92
+ },
93
+ required: ['text']
94
+ }
95
+ },
96
+ {
97
+ name: 'complete_action',
98
+ description: 'Mark an action as completed',
99
+ inputSchema: {
100
+ type: 'object',
101
+ properties: {
102
+ id: {
103
+ type: 'string',
104
+ description: 'Action ID to complete'
105
+ }
106
+ },
107
+ required: ['id']
108
+ }
109
+ },
110
+ {
111
+ name: 'get_goals',
112
+ description: 'List OKRs (Objectives and Key Results) with their progress',
113
+ inputSchema: {
114
+ type: 'object',
115
+ properties: {
116
+ workspaceId: {
117
+ type: 'string',
118
+ description: 'Optional workspace ID'
119
+ }
120
+ }
121
+ }
122
+ },
123
+ {
124
+ name: 'search',
125
+ description: 'Search across projects, actions, and goals',
126
+ inputSchema: {
127
+ type: 'object',
128
+ properties: {
129
+ query: {
130
+ type: 'string',
131
+ description: 'Search query'
132
+ }
133
+ },
134
+ required: ['query']
135
+ }
136
+ },
137
+ {
138
+ name: 'get_workspaces',
139
+ description: 'List all workspaces the user has access to',
140
+ inputSchema: {
141
+ type: 'object',
142
+ properties: {}
143
+ }
144
+ }
145
+ ];
146
+ async function main() {
147
+ const config = loadClientConfig();
148
+ const client = new ExponentialClient({
149
+ token: config.token,
150
+ apiUrl: config.apiUrl,
151
+ });
152
+ const trpcClient = client.client;
153
+ const server = new Server({
154
+ name: 'exponential-mcp',
155
+ version: '0.1.0',
156
+ }, {
157
+ capabilities: {
158
+ tools: {},
159
+ },
160
+ });
161
+ // List available tools
162
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
163
+ tools: TOOLS,
164
+ }));
165
+ // Handle tool calls
166
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
167
+ const { name, arguments: args } = request.params;
168
+ try {
169
+ switch (name) {
170
+ case 'get_projects': {
171
+ const projects = await client.projects.list({
172
+ workspaceId: args?.workspaceId,
173
+ });
174
+ return {
175
+ content: [
176
+ {
177
+ type: 'text',
178
+ text: JSON.stringify(projects, null, 2),
179
+ },
180
+ ],
181
+ };
182
+ }
183
+ case 'get_actions': {
184
+ const status = args?.status;
185
+ const projectId = args?.projectId;
186
+ let actions;
187
+ if (status === 'COMPLETED') {
188
+ actions = await client.actions.getKanban({
189
+ projectId,
190
+ status: 'DONE',
191
+ });
192
+ }
193
+ else if (status === 'CANCELLED') {
194
+ actions = await client.actions.getKanban({
195
+ projectId,
196
+ status: 'CANCELLED',
197
+ });
198
+ }
199
+ else {
200
+ actions = await client.actions.list({ projectId });
201
+ }
202
+ return {
203
+ content: [
204
+ {
205
+ type: 'text',
206
+ text: JSON.stringify(actions, null, 2),
207
+ },
208
+ ],
209
+ };
210
+ }
211
+ case 'create_action': {
212
+ // SDK does not yet expose quickCreate, so we use the underlying tRPC client.
213
+ const action = await trpcClient.action.quickCreate.mutate({
214
+ text: args?.text,
215
+ });
216
+ return {
217
+ content: [
218
+ {
219
+ type: 'text',
220
+ text: `Created action: ${action.name} (ID: ${action.id})`,
221
+ },
222
+ ],
223
+ };
224
+ }
225
+ case 'complete_action': {
226
+ // SDK does not yet expose action updates, so we use the underlying tRPC client.
227
+ const action = await trpcClient.action.update.mutate({
228
+ id: args?.id,
229
+ status: 'COMPLETED',
230
+ });
231
+ return {
232
+ content: [
233
+ {
234
+ type: 'text',
235
+ text: `Completed: ${action.name}`,
236
+ },
237
+ ],
238
+ };
239
+ }
240
+ case 'get_goals': {
241
+ const goals = await trpcClient.goal.list.query({
242
+ workspaceId: args?.workspaceId,
243
+ });
244
+ return {
245
+ content: [
246
+ {
247
+ type: 'text',
248
+ text: JSON.stringify(goals, null, 2),
249
+ },
250
+ ],
251
+ };
252
+ }
253
+ case 'search': {
254
+ const results = await trpcClient.search.global.query({
255
+ query: args?.query,
256
+ });
257
+ return {
258
+ content: [
259
+ {
260
+ type: 'text',
261
+ text: JSON.stringify(results, null, 2),
262
+ },
263
+ ],
264
+ };
265
+ }
266
+ case 'get_workspaces': {
267
+ const workspaces = await client.workspaces.list();
268
+ return {
269
+ content: [
270
+ {
271
+ type: 'text',
272
+ text: JSON.stringify(workspaces, null, 2),
273
+ },
274
+ ],
275
+ };
276
+ }
277
+ default:
278
+ throw new Error(`Unknown tool: ${name}`);
279
+ }
280
+ }
281
+ catch (error) {
282
+ const message = error instanceof Error ? error.message : 'Unknown error';
283
+ return {
284
+ content: [
285
+ {
286
+ type: 'text',
287
+ text: `Error: ${message}`,
288
+ },
289
+ ],
290
+ isError: true,
291
+ };
292
+ }
293
+ });
294
+ // Start server
295
+ const transport = new StdioServerTransport();
296
+ await server.connect(transport);
297
+ console.error('Exponential MCP server running');
298
+ }
299
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "exponential-mcp",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for Exponential - connect Claude to your projects, actions, and goals",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "exponential-mcp": "./dist/cli.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "start": "node dist/index.js",
14
+ "cli": "node dist/cli.js"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "claude",
19
+ "exponential",
20
+ "productivity",
21
+ "anthropic"
22
+ ],
23
+ "author": "Exponential",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.0.0",
27
+ "commander": "^12.0.0",
28
+ "exponential-sdk": "^1.0.0",
29
+ "zod": "^3.22.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "typescript": "^5.3.0"
34
+ },
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "files": [
39
+ "dist"
40
+ ]
41
+ }