microsoft-todo-mcp-server
Version:
Microsoft Todo MCP service for Claude and Cursor. Fork of @jhirono/todomcp
178 lines (174 loc) • 6.79 kB
JavaScript
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