Learn how to connect your own AI tool or application to Astravue MCP using OAuth 2.0.
Overview#
Astravue provides a hosted MCP server that lets any MCP-compatible client interact with your workspace.If you are building your own AI tool or integration, this guide walks you through the OAuth 2.0 Authorization Code flow with PKCE used by Astravue MCP.| Transport | URL | Notes |
|---|
| Streamable HTTP | https://api.astravue.com/mcp | Only supported transport |
OAuth 2.0 Authorization Code flow with PKCE
Streamable HTTP transport support
Secure credential storage
Prerequisites#
This guide uses TypeScript / JavaScript examples.The OAuth 2.0 flow, PKCE implementation, and MCP protocol are language-agnostic — the same concepts apply in any language.
Required libraries (TypeScript / JavaScript)
Key references#
Model Context Protocol Specification
RFC 6749 — OAuth 2.0 Authorization Framework
RFC 8414 — Authorization Server Metadata
RFC 9728 — OAuth 2.0 Protected Resource Metadata
Step 1 — OAuth Discovery#
Before connecting, discover Astravue’s OAuth configuration using the standard discovery process.1.
Fetch Protected Resource Metadata (RFC 9728)
2.
Fetch Authorization Server Metadata (RFC 8414)
type OAuthMetadata = {
issuer: string
authorization_endpoint: string
token_endpoint: string
registration_endpoint?: string
}
async function discoverOAuthMetadata(mcpServerUrl: string): Promise<OAuthMetadata> {
const url = new URL(mcpServerUrl)
const protectedResourceUrl = new URL(
"/.well-known/oauth-protected-resource",
url
)
const protectedResourceResponse = await fetch(protectedResourceUrl.toString())
if (!protectedResourceResponse.ok) {
throw new Error("Failed to fetch protected resource metadata")
}
const protectedResource = await protectedResourceResponse.json()
const authServerUrl = protectedResource.authorization_servers[0]
const metadataUrl = new URL(
"/.well-known/oauth-authorization-server",
authServerUrl
)
const metadataResponse = await fetch(metadataUrl.toString())
if (!metadataResponse.ok) {
throw new Error("Failed to fetch authorization server metadata")
}
return metadataResponse.json()
}
Astravue discovery endpoints:https://api.astravue.com/.well-known/oauth-protected-resource
https://api.astravue.com/.well-known/oauth-authorization-server
Step 2 — Generate PKCE Parameters#
PKCE protects the OAuth authorization code flow.import { randomBytes, createHash } from "crypto"
function base64URLEncode(buffer: Buffer): string {
return buffer
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "")
}
function generateCodeVerifier(): string {
return base64URLEncode(randomBytes(32))
}
function generateCodeChallenge(verifier: string): string {
const hash = createHash("sha256")
.update(verifier)
.digest()
return base64URLEncode(hash)
}
const codeVerifier = generateCodeVerifier()
const codeChallenge = generateCodeChallenge(codeVerifier)
Never send the codeVerifier until the token exchange step.Store it securely in memory or encrypted session storage.
Step 3 — Dynamic Client Registration#
Astravue MCP supports RFC 7591 dynamic client registration.async function registerClient(metadata: OAuthMetadata, redirectUri: string) {
const registration = {
client_name: "Your MCP Client",
redirect_uris: [redirectUri]
}
const response = await fetch(metadata.registration_endpoint!, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(registration)
})
if (!response.ok) {
throw new Error("Client registration failed")
}
return response.json()
}
Step 4 — Build Authorization URL#
function buildAuthorizationUrl(
metadata: OAuthMetadata,
clientId: string,
redirectUri: string,
codeChallenge: string,
state: string
) {
const params = new URLSearchParams({
response_type: "code",
client_id: clientId,
redirect_uri: redirectUri,
scope: "openid profile email",
state: state,
code_challenge: codeChallenge,
code_challenge_method: "S256",
resource: mcpServerUrl
})
return `${metadata.authorization_endpoint}?${params}`
}
Redirect your user to this URL to begin authentication.
Step 5 — Handle OAuth Callback#
function parseCallback(url: string) {
const params = new URLSearchParams(new URL(url).search)
return {
code: params.get("code"),
state: params.get("state"),
error: params.get("error")
}
}
Always validate the state parameter before continuing.
Step 6 — Exchange Authorization Code#
async function exchangeCodeForTokens(
code: string,
codeVerifier: string,
metadata: OAuthMetadata,
clientId: string,
redirectUri: string
) {
const params = new URLSearchParams({
grant_type: "authorization_code",
code,
client_id: clientId,
redirect_uri: redirectUri,
code_verifier: codeVerifier,
resource: "https://api.astravue.com/mcp"
})
const response = await fetch(metadata.token_endpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: params.toString()
})
if (!response.ok) {
throw new Error("Token exchange failed")
}
return response.json()
}
The resource parameter ensures the access token is issued for the Astravue MCP server (sets the aud claim).Web apps — store tokens server-side only
Desktop apps — use OS keychain
Mobile apps — use secure keystore APIs
Always encrypt tokens at rest
Step 7 — Connect to Astravue MCP#
Astravue MCP uses Streamable HTTP transport only.import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
async function createMcpClient(accessToken: string): Promise<Client> {
const client = new Client(
{ name: "your-mcp-client", version: "1.0.0" },
{ capabilities: { roots: {}, sampling: {} } }
)
const transport = new StreamableHTTPClientTransport(
new URL("https://api.astravue.com/mcp"),
{
headers: {
Authorization: `Bearer ${accessToken}`,
"User-Agent": "YourApp-MCP-Client/1.0"
}
}
)
await client.connect(transport)
return client
}
Step 8 — Handle Token Refresh#
Access tokens expire after 30 minutes.async function refreshAccessToken(
refreshToken: string,
metadata: OAuthMetadata,
clientId: string
) {
const params = new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: clientId
})
const response = await fetch(metadata.token_endpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: params.toString()
})
if (!response.ok) {
throw new Error("Token refresh failed")
}
return response.json()
}
Astravue uses refresh token rotation.Each refresh request returns a new refresh token and invalidates the previous one.
Token Reference#
| Token | Lifetime | Purpose |
|---|
| Access token | 30 min | Authorizes MCP tool calls |
| Refresh token | 24 hours | Issues new access tokens |
Security Checklist#
Validate OAuth state parameter
Refresh tokens before expiry
Handle invalid_grant gracefully
Log OAuth operations for auditing
Troubleshooting#
Additional resources#