UNPKG

microsoft-todo-mcp-server

Version:

Microsoft Todo MCP service for Claude and Cursor. Fork of @jhirono/todomcp

178 lines (174 loc) 6.79 kB
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/token-manager.ts import { readFileSync, writeFileSync, existsSync } from "fs"; import { join } from "path"; import { homedir } from "os"; var TokenManager = class { tokenFilePath; currentTokens = null; constructor() { const configDir = process.platform === "win32" ? join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "microsoft-todo-mcp") : join(homedir(), ".config", "microsoft-todo-mcp"); if (!existsSync(configDir)) { __require("fs").mkdirSync(configDir, { recursive: true }); } this.tokenFilePath = join(configDir, "tokens.json"); console.error(`Token file path: ${this.tokenFilePath}`); } // Try to get tokens from multiple sources async getTokens() { if (process.env.MS_TODO_ACCESS_TOKEN && process.env.MS_TODO_REFRESH_TOKEN) { const envTokens = { accessToken: process.env.MS_TODO_ACCESS_TOKEN, refreshToken: process.env.MS_TODO_REFRESH_TOKEN, expiresAt: Date.now() + 3600 * 1e3 // Assume 1 hour if not specified }; if (Date.now() > envTokens.expiresAt) { const refreshed = await this.refreshToken(envTokens.refreshToken); if (refreshed) { return refreshed; } } return envTokens; } if (existsSync(this.tokenFilePath)) { try { const data = readFileSync(this.tokenFilePath, "utf8"); this.currentTokens = JSON.parse(data); if (this.currentTokens) { if (Date.now() > this.currentTokens.expiresAt) { const refreshed = await this.refreshToken(this.currentTokens.refreshToken); if (refreshed) { return refreshed; } } return this.currentTokens; } } catch (error) { console.error("Error reading token file:", error); } } const legacyPath = join(process.cwd(), "tokens.json"); if (existsSync(legacyPath)) { try { const data = readFileSync(legacyPath, "utf8"); const tokens = JSON.parse(data); this.saveTokens(tokens); return tokens; } catch (error) { console.error("Error reading legacy token file:", error); } } return null; } async refreshToken(refreshToken) { var _a, _b, _c; try { const clientId = ((_a = this.currentTokens) == null ? void 0 : _a.clientId) || process.env.CLIENT_ID; const clientSecret = ((_b = this.currentTokens) == null ? void 0 : _b.clientSecret) || process.env.CLIENT_SECRET; const tenantId = ((_c = this.currentTokens) == null ? void 0 : _c.tenantId) || process.env.TENANT_ID || "organizations"; if (!clientId || !clientSecret) { console.error("Missing client credentials for token refresh"); return null; } const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; const formData = new URLSearchParams({ client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, grant_type: "refresh_token", scope: "offline_access Tasks.Read Tasks.ReadWrite Tasks.Read.Shared Tasks.ReadWrite.Shared User.Read" }); const response = await fetch(tokenEndpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: formData }); if (!response.ok) { const errorText = await response.text(); console.error(`Token refresh failed: ${errorText}`); this.promptForReauth(); return null; } const data = await response.json(); const newTokens = { accessToken: data.access_token, refreshToken: data.refresh_token || refreshToken, expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3, // 5 min buffer clientId, clientSecret, tenantId }; this.saveTokens(newTokens); await this.updateClaudeConfig(newTokens); return newTokens; } catch (error) { console.error("Error refreshing token:", error); this.promptForReauth(); return null; } } saveTokens(tokens) { this.currentTokens = tokens; writeFileSync(this.tokenFilePath, JSON.stringify(tokens, null, 2), "utf8"); } // Update Claude config automatically async updateClaudeConfig(tokens) { try { const claudeConfigPath = process.platform === "win32" ? join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json") : process.platform === "darwin" ? join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json") : join(homedir(), ".config", "Claude", "claude_desktop_config.json"); if (!existsSync(claudeConfigPath)) { return; } const config = JSON.parse(readFileSync(claudeConfigPath, "utf8")); if (config.mcpServers && config.mcpServers["microsoft-todo"]) { config.mcpServers["microsoft-todo"].env = { ...config.mcpServers["microsoft-todo"].env, MS_TODO_ACCESS_TOKEN: tokens.accessToken, MS_TODO_REFRESH_TOKEN: tokens.refreshToken }; writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), "utf8"); console.error("Updated Claude config with new tokens"); } } catch (error) { console.error("Could not update Claude config:", error); } } promptForReauth() { console.error(` ================================================================= TOKEN REFRESH FAILED - REAUTHENTICATION REQUIRED Your Microsoft To Do tokens have expired and could not be refreshed. To fix this: 1. Open a new terminal 2. Navigate to the microsoft-todo-mcp-server directory 3. Run: pnpm run auth 4. Complete the authentication in your browser 5. Restart Claude Desktop to use the new tokens Your tokens are stored in: ${this.tokenFilePath} ================================================================= `); } // Store client credentials with tokens for future refreshes async storeCredentials(clientId, clientSecret, tenantId) { if (this.currentTokens) { this.currentTokens.clientId = clientId; this.currentTokens.clientSecret = clientSecret; this.currentTokens.tenantId = tenantId; this.saveTokens(this.currentTokens); } } }; var tokenManager = new TokenManager(); export { TokenManager, tokenManager }; //# sourceMappingURL=token-manager.js.map