Skip to main content
Glama
authorize.ts4.47 kB
import { randomBytes, randomUUID } from 'crypto'; import express from 'express'; import { fromError } from 'zod-validation-error'; import { getConfig } from '../../config.js'; import { setLongTimeout } from '../../utils/setLongTimeout.js'; import { generateCodeChallenge } from './generateCodeChallenge.js'; import { isValidRedirectUri } from './isValidRedirectUri.js'; import { TABLEAU_CLOUD_SERVER_URL } from './provider.js'; import { mcpAuthorizeSchema } from './schemas.js'; import { PendingAuthorization } from './types.js'; /** * OAuth 2.1 Authorization Endpoint * * Handles authorization requests with PKCE parameters. * Validates request, stores pending authorization, and * redirects to Tableau OAuth. */ export function authorize( app: express.Application, pendingAuthorizations: Map<string, PendingAuthorization>, ): void { const config = getConfig(); app.get('/oauth/authorize', (req, res) => { const result = mcpAuthorizeSchema.safeParse(req.query); if (!result.success) { res.status(400).json({ error: 'invalid_request', error_description: fromError(result.error).toString(), }); return; } const { clientId, redirectUri, responseType, codeChallenge, codeChallengeMethod, state } = result.data; if (responseType !== 'code') { res.status(400).json({ error: 'unsupported_response_type', error_description: 'Only authorization code flow is supported', }); return; } if (codeChallengeMethod !== 'S256') { res.status(400).json({ error: 'invalid_request', error_description: 'Only S256 code challenge method is supported', }); return; } if (!isValidRedirectUri(redirectUri)) { res.status(400).json({ error: 'invalid_request', error_description: `Invalid redirect URI: ${redirectUri}`, }); return; } // Generate Tableau state and store pending authorization const tableauState = randomBytes(32).toString('hex'); const authKey = randomBytes(32).toString('hex'); const tableauClientId = randomUUID(); // 22-64 bytes (44-128 chars) is the recommended length for code verifiers const numCodeVerifierBytes = Math.floor(Math.random() * (64 - 22 + 1)) + 22; const tableauCodeVerifier = randomBytes(numCodeVerifierBytes).toString('hex'); const tableauCodeChallenge = generateCodeChallenge(tableauCodeVerifier); pendingAuthorizations.set(authKey, { clientId, redirectUri, codeChallenge, state: state ?? '', tableauState, tableauClientId, tableauCodeVerifier, }); // Clean up expired authorizations setLongTimeout(() => pendingAuthorizations.delete(authKey), config.oauth.authzCodeTimeoutMs); // Redirect to Tableau OAuth const server = config.server || TABLEAU_CLOUD_SERVER_URL; const oauthUrl = new URL(`${server}/oauth2/v1/auth`); oauthUrl.searchParams.set('client_id', tableauClientId); oauthUrl.searchParams.set('code_challenge', tableauCodeChallenge); oauthUrl.searchParams.set('code_challenge_method', 'S256'); oauthUrl.searchParams.set('response_type', 'code'); oauthUrl.searchParams.set('redirect_uri', config.oauth.redirectUri); oauthUrl.searchParams.set('state', `${authKey}:${tableauState}`); oauthUrl.searchParams.set('device_id', randomUUID()); oauthUrl.searchParams.set('target_site', config.siteName); oauthUrl.searchParams.set('device_name', getDeviceName(redirectUri, state ?? '')); oauthUrl.searchParams.set('client_type', 'tableau-mcp'); res.redirect(oauthUrl.toString()); }); } function getDeviceName(redirectUri: string, state: string): string { const defaultDeviceName = 'tableau-mcp (Unknown agent)'; try { const url = new URL(redirectUri); if (url.protocol === 'https:' || url.protocol === 'http:') { if (redirectUri === 'https://vscode.dev/redirect' && new URL(state).protocol === 'vscode:') { // VS Code normally authenticates in a way that doesn't give any clues about who it is. // It has a backup authentication method they call "URL Handler" that does though. return 'tableau-mcp (VS Code)'; } return defaultDeviceName; } else if (url.protocol === 'cursor:') { return 'tableau-mcp (Cursor)'; } else { return `tableau-mcp (${url.protocol.slice(0, -1)})`; } } catch { return defaultDeviceName; } }

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/datalabs89/tableau-mcp'

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