Skip to main content
Glama
master-server.ts6.24 kB
// Phase 1: avoid hard dependency on SDK types to ensure compilation import type { ServerCapabilities, LoadedServer } from '../types/server.js' import type { MasterConfig, RoutingConfig, ServerConfig } from '../types/config.js' import type { AuthHeaders, OAuthDelegation } from '../types/auth.js' import { ProtocolHandler } from './protocol-handler.js' import { DefaultModuleLoader } from '../modules/module-loader.js' import { CapabilityAggregator } from '../modules/capability-aggregator.js' import { RequestRouter } from '../modules/request-router.js' import { Logger } from '../utils/logger.js' import { MultiAuthManager } from '../auth/multi-auth-manager.js' import { OAuthFlowController } from '../oauth/flow-controller.js' export class MasterServer { readonly server: unknown readonly handler: ProtocolHandler private readonly loader = new DefaultModuleLoader() private readonly aggregator = new CapabilityAggregator() private readonly servers = new Map<string, LoadedServer>() private router!: RequestRouter private config?: MasterConfig private authManager?: MultiAuthManager private oauthController?: OAuthFlowController private getAuthHeaders: ( serverId: string, clientToken?: string ) => Promise<AuthHeaders | OAuthDelegation | undefined> constructor(capabilities?: Partial<ServerCapabilities>, routing?: RoutingConfig) { const version = (globalThis as any)?.process?.env?.APP_VERSION ?? '0.1.0' this.server = { name: 'master-mcp-server', version } this.getAuthHeaders = async (_serverId: string, clientToken?: string) => clientToken ? { Authorization: `Bearer ${clientToken}` } : undefined this.router = new RequestRouter(this.servers, this.aggregator, this.getAuthHeaders.bind(this), { routing }) this.handler = new ProtocolHandler({ aggregator: this.aggregator, router: this.router }) void capabilities } async startFromConfig(config: MasterConfig, clientToken?: string): Promise<void> { Logger.info('Starting MasterServer from config') this.config = config await this.loadServers(config.servers, clientToken) await this.discoverAllCapabilities(clientToken) } async loadServers(servers: ServerConfig[], clientToken?: string): Promise<void> { Logger.info('Loading servers', { servers }) const loaded = await this.loader.loadServers(servers, clientToken) Logger.info('Loaded servers', { loaded: Array.from(loaded.entries()) }) this.servers.clear() for (const [id, s] of loaded) this.servers.set(id, s) this.router = new RequestRouter(this.servers, this.aggregator, this.getAuthHeaders.bind(this), { routing: this.config?.routing, }) ;(this as any).handler = new ProtocolHandler({ aggregator: this.aggregator, router: this.router }) } async discoverAllCapabilities(clientToken?: string): Promise<void> { Logger.info('Discovering all capabilities', { servers: Array.from(this.servers.entries()) }) const headersOnly = async (serverId: string, token?: string) => { const res = await this.getAuthHeaders(serverId, token) if (res && (res as OAuthDelegation).type === 'oauth_delegation') { return token ? { Authorization: `Bearer ${token}` } : undefined } return res as AuthHeaders | undefined } await this.aggregator.discoverCapabilities(this.servers, clientToken, headersOnly) Logger.info('Discovered all capabilities', { tools: this.aggregator.getAllTools(this.servers), resources: this.aggregator.getAllResources(this.servers) }) } // Allow host app to inject an auth header strategy (e.g., MultiAuthManager) setAuthHeaderProvider( fn: (serverId: string, clientToken?: string) => Promise<AuthHeaders | OAuthDelegation | undefined> ): void { this.getAuthHeaders = fn this.router = new RequestRouter(this.servers, this.aggregator, this.getAuthHeaders.bind(this), { routing: this.config?.routing, }) ;(this as any).handler = new ProtocolHandler({ aggregator: this.aggregator, router: this.router }) } getRouter(): RequestRouter { return this.router } getAggregatedTools(): ServerCapabilities['tools'] { return this.aggregator.getAllTools(this.servers) } getAggregatedResources(): ServerCapabilities['resources'] { return this.aggregator.getAllResources(this.servers) } async performHealthChecks(clientToken?: string): Promise<Record<string, boolean>> { const results: Record<string, boolean> = {} for (const [id, s] of this.servers) { results[id] = await this.loader.performHealthCheck(s, clientToken) } return results } async restartServer(id: string): Promise<void> { await this.loader.restartServer(id) } async unloadAll(): Promise<void> { await Promise.all(Array.from(this.servers.keys()).map((id) => this.loader.unload(id))) this.servers.clear() } attachAuthManager(manager: MultiAuthManager): void { this.authManager = manager this.setAuthHeaderProvider((serverId: string, clientToken?: string) => { if (!clientToken) return Promise.resolve(undefined) return this.authManager!.prepareAuthForBackend(serverId, clientToken) }) } // Provide an OAuthFlowController wired to the current config and auth manager. // Host runtimes (Node/Workers) can use this to mount HTTP endpoints without coupling MasterServer to a specific HTTP framework. getOAuthFlowController(): OAuthFlowController { if (!this.config) throw new Error('MasterServer config not initialized') if (!this.authManager) throw new Error('Auth manager not attached') if (!this.oauthController) { this.oauthController = new OAuthFlowController( { getConfig: () => this.config!, storeDelegatedToken: async (clientToken, serverId, token) => { await this.authManager!.storeDelegatedToken(clientToken, serverId, token) }, }, '/oauth' ) } return this.oauthController } updateRouting(routing?: RoutingConfig): void { this.router = new RequestRouter(this.servers, this.aggregator, this.getAuthHeaders.bind(this), { routing }) ;(this as any).handler = new ProtocolHandler({ aggregator: this.aggregator, router: this.router }) } }

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/Jakedismo/master-mcp-server'

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