| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- import path from "path"
- import z from "zod"
- import { Global } from "../global"
- export namespace McpAuth {
- export const Tokens = z.object({
- accessToken: z.string(),
- refreshToken: z.string().optional(),
- expiresAt: z.number().optional(),
- scope: z.string().optional(),
- })
- export type Tokens = z.infer<typeof Tokens>
- export const ClientInfo = z.object({
- clientId: z.string(),
- clientSecret: z.string().optional(),
- clientIdIssuedAt: z.number().optional(),
- clientSecretExpiresAt: z.number().optional(),
- })
- export type ClientInfo = z.infer<typeof ClientInfo>
- export const Entry = z.object({
- tokens: Tokens.optional(),
- clientInfo: ClientInfo.optional(),
- codeVerifier: z.string().optional(),
- oauthState: z.string().optional(),
- serverUrl: z.string().optional(), // Track the URL these credentials are for
- })
- export type Entry = z.infer<typeof Entry>
- const filepath = path.join(Global.Path.data, "mcp-auth.json")
- export async function get(mcpName: string): Promise<Entry | undefined> {
- const data = await all()
- return data[mcpName]
- }
- /**
- * Get auth entry and validate it's for the correct URL.
- * Returns undefined if URL has changed (credentials are invalid).
- */
- export async function getForUrl(mcpName: string, serverUrl: string): Promise<Entry | undefined> {
- const entry = await get(mcpName)
- if (!entry) return undefined
- // If no serverUrl is stored, this is from an old version - consider it invalid
- if (!entry.serverUrl) return undefined
- // If URL has changed, credentials are invalid
- if (entry.serverUrl !== serverUrl) return undefined
- return entry
- }
- export async function all(): Promise<Record<string, Entry>> {
- const file = Bun.file(filepath)
- return file.json().catch(() => ({}))
- }
- export async function set(mcpName: string, entry: Entry, serverUrl?: string): Promise<void> {
- const file = Bun.file(filepath)
- const data = await all()
- // Always update serverUrl if provided
- if (serverUrl) {
- entry.serverUrl = serverUrl
- }
- await Bun.write(file, JSON.stringify({ ...data, [mcpName]: entry }, null, 2), { mode: 0o600 })
- }
- export async function remove(mcpName: string): Promise<void> {
- const file = Bun.file(filepath)
- const data = await all()
- delete data[mcpName]
- await Bun.write(file, JSON.stringify(data, null, 2), { mode: 0o600 })
- }
- export async function updateTokens(mcpName: string, tokens: Tokens, serverUrl?: string): Promise<void> {
- const entry = (await get(mcpName)) ?? {}
- entry.tokens = tokens
- await set(mcpName, entry, serverUrl)
- }
- export async function updateClientInfo(mcpName: string, clientInfo: ClientInfo, serverUrl?: string): Promise<void> {
- const entry = (await get(mcpName)) ?? {}
- entry.clientInfo = clientInfo
- await set(mcpName, entry, serverUrl)
- }
- export async function updateCodeVerifier(mcpName: string, codeVerifier: string): Promise<void> {
- const entry = (await get(mcpName)) ?? {}
- entry.codeVerifier = codeVerifier
- await set(mcpName, entry)
- }
- export async function clearCodeVerifier(mcpName: string): Promise<void> {
- const entry = await get(mcpName)
- if (entry) {
- delete entry.codeVerifier
- await set(mcpName, entry)
- }
- }
- export async function updateOAuthState(mcpName: string, oauthState: string): Promise<void> {
- const entry = (await get(mcpName)) ?? {}
- entry.oauthState = oauthState
- await set(mcpName, entry)
- }
- export async function getOAuthState(mcpName: string): Promise<string | undefined> {
- const entry = await get(mcpName)
- return entry?.oauthState
- }
- export async function clearOAuthState(mcpName: string): Promise<void> {
- const entry = await get(mcpName)
- if (entry) {
- delete entry.oauthState
- await set(mcpName, entry)
- }
- }
- /**
- * Check if stored tokens are expired.
- * Returns null if no tokens exist, false if no expiry or not expired, true if expired.
- */
- export async function isTokenExpired(mcpName: string): Promise<boolean | null> {
- const entry = await get(mcpName)
- if (!entry?.tokens) return null
- if (!entry.tokens.expiresAt) return false
- return entry.tokens.expiresAt < Date.now() / 1000
- }
- }
|