aps_login
Initiate three-legged OAuth login for Autodesk Platform Services. Opens browser for user sign-in and consent, then caches and auto-refreshes the token for subsequent API calls with user's permissions.
Instructions
Start a 3‑legged OAuth login for APS (user context). Opens the user's browser to the Autodesk sign‑in page. After the user logs in and grants consent, the token is cached to disk and auto‑refreshed. All subsequent API calls use the 3LO token (with the user's own permissions) until aps_logout is called. The OAuth scope is determined by the APS_SCOPE setting configured by the user.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/index.ts:142-156 (registration)Tool registration definition for aps_login in the TOOLS array – defines name, description, and inputSchema (no input parameters).
// 0a ── aps_login (3‑legged OAuth) { name: "aps_login", description: "Start a 3‑legged OAuth login for APS (user context). " + "Opens the user's browser to the Autodesk sign‑in page. " + "After the user logs in and grants consent, the token is cached to disk " + "and auto‑refreshed. All subsequent API calls use the 3LO token " + "(with the user's own permissions) until aps_logout is called. " + "The OAuth scope is determined by the APS_SCOPE setting configured by the user.", inputSchema: { type: "object" as const, properties: {}, }, }, - src/index.ts:1002-1013 (handler)Main handler for aps_login – calls requireApsEnv() and performAps3loLogin() to start 3-legged OAuth flow.
// ── aps_login (3LO) ───────────────────────────────────────── if (name === "aps_login") { requireApsEnv(); const scope = APS_SCOPE || "data:read"; const result = await performAps3loLogin( APS_CLIENT_ID, APS_CLIENT_SECRET, scope, APS_CALLBACK_PORT, ); return ok(result.message); } - src/aps-auth.ts:264-391 (helper)performAps3loLogin() – the core implementation that starts a temporary HTTP server, opens the user's browser to the APS authorize endpoint, exchanges the auth code for tokens, and caches them to disk.
export async function performAps3loLogin( clientId: string, clientSecret: string, scope: string, callbackPort = 8910, ): Promise<{ access_token: string; message: string }> { const redirectUri = `http://localhost:${callbackPort}/callback`; return new Promise((resolve, reject) => { const server = createServer( async (req: IncomingMessage, res: ServerResponse) => { const reqUrl = new URL(req.url ?? "/", `http://localhost:${callbackPort}`); if (reqUrl.pathname !== "/callback") { res.writeHead(404); res.end("Not found"); return; } const error = reqUrl.searchParams.get("error"); if (error) { const desc = reqUrl.searchParams.get("error_description") ?? error; const safeDesc = escapeHtml(desc); res.writeHead(400, { "Content-Type": "text/html" }); res.end( `<html><body><h2>Authorization failed</h2><p>${safeDesc}</p></body></html>`, ); server.close(); reject(new Error(`APS authorization failed: ${desc}`)); return; } const code = reqUrl.searchParams.get("code"); if (!code) { res.writeHead(400, { "Content-Type": "text/html" }); res.end( "<html><body><h2>Missing authorization code</h2></body></html>", ); server.close(); reject(new Error("No authorization code received in callback.")); return; } // Exchange the authorization code for tokens try { const tokenBody = new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: redirectUri, client_id: clientId, client_secret: clientSecret, }); const tokenRes = await fetch(APS_TOKEN_URL, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: tokenBody.toString(), }); if (!tokenRes.ok) { const text = await tokenRes.text(); const safeText = escapeHtml(text); res.writeHead(500, { "Content-Type": "text/html" }); res.end( `<html><body><h2>Token exchange failed</h2><pre>${safeText}</pre></body></html>`, ); server.close(); reject( new Error(`Token exchange failed (${tokenRes.status}): ${safeText}`), ); return; } const data = (await tokenRes.json()) as { access_token: string; refresh_token: string; expires_in: number; }; const cacheData: Aps3loTokenData = { access_token: data.access_token, refresh_token: data.refresh_token, expires_at: Date.now() + (data.expires_in - 60) * 1000, scope, }; write3loCache(cacheData); cached3lo = cacheData; res.writeHead(200, { "Content-Type": "text/html" }); res.end( "<html><body><h2>Logged in to APS</h2>" + "<p>You can close this tab and return to Claude Desktop.</p></body></html>", ); server.close(); resolve({ access_token: data.access_token, message: `3-legged login successful. Tokens cached to ${TOKEN_FILE}. ` + "The token will auto-refresh when it expires.", }); } catch (err) { res.writeHead(500, { "Content-Type": "text/html" }); res.end( `<html><body><h2>Error</h2><pre>${String(err)}</pre></body></html>`, ); server.close(); reject(err); } }, ); server.listen(callbackPort, () => { const authUrl = new URL(APS_AUTHORIZE_URL); authUrl.searchParams.set("client_id", clientId); authUrl.searchParams.set("response_type", "code"); authUrl.searchParams.set("redirect_uri", redirectUri); authUrl.searchParams.set("scope", scope); openBrowser(authUrl.toString()); }); // Give the user 2 minutes to complete login setTimeout(() => { server.close(); reject(new Error("3LO login timed out after 2 minutes. Try again.")); }, 120_000); }); } - src/aps-auth.ts:229-248 (helper)openBrowser() helper – cross-platform utility to open a URL in the user's default browser (Windows PowerShell, macOS open, Linux xdg-open).
function openBrowser(url: string): void { let program: string; let args: string[]; if (process.platform === "win32") { // Use PowerShell to avoid cmd.exe treating '&' in the URL as a command separator. program = "powershell"; args = ["-NoProfile", "-NonInteractive", "-Command", `Start-Process '${url}'`]; } else if (process.platform === "darwin") { program = "open"; args = [url]; } else { program = "xdg-open"; args = [url]; } const child = spawn(program, args, { stdio: "ignore" }); child.on("error", () => { /* ignore – best‑effort */ }); child.unref(); } - src/aps-auth.ts:203-226 (helper)3LO token cache helpers (read3loCache, write3loCache, deleteCacheFile) – persists tokens to ~/.aps-mcp/3lo-tokens.json.
const TOKEN_DIR = join(homedir(), ".aps-mcp"); const TOKEN_FILE = join(TOKEN_DIR, "3lo-tokens.json"); function read3loCache(): Aps3loTokenData | null { try { if (!existsSync(TOKEN_FILE)) return null; return JSON.parse(readFileSync(TOKEN_FILE, "utf8")) as Aps3loTokenData; } catch { return null; } } function write3loCache(data: Aps3loTokenData): void { if (!existsSync(TOKEN_DIR)) mkdirSync(TOKEN_DIR, { recursive: true }); writeFileSync(TOKEN_FILE, JSON.stringify(data, null, 2)); } function deleteCacheFile(): void { try { if (existsSync(TOKEN_FILE)) unlinkSync(TOKEN_FILE); } catch { /* ignore */ } }