Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault

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: {},
      },
    },
  • 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);
    }
  • 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);
      });
    }
  • 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();
    }
  • 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 */
      }
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses the browser opening, token caching, auto-refresh, and scope setting, but lacks details on failure behavior, repeated calls, or idempotency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four concise sentences, front-loaded with purpose, no wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers key aspects (login flow, token management, scope, logout), but omits error handling and return value details.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0 parameters, the baseline is 4; the description adds no parameter info but provides useful context about the OAuth flow.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Start a 3‑legged OAuth login') and resource ('APS'), and distinguishes from siblings like aps_logout by explaining the token caching and auto-refresh.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains when to use (before other API calls needing user context) and when to stop (aps_logout), but does not explicitly mention when not to use or contrast with aps_get_token.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/EverseDevelopment/APS.MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server