Skip to main content
Glama
serviceDiscovery.ts5.81 kB
import * as fs from "node:fs"; import * as path from "node:path"; /** * Represents a discovered Tusk service from a .tusk/config.yaml file */ export interface DiscoveredService { /** The service ID from the config */ id: string; /** The service name from the config */ name: string; /** Path to the .tusk/config.yaml file */ configPath: string; /** Root directory containing the .tusk folder */ rootPath: string; } /** * Parse a .tusk/config.yaml file to extract service info. * Uses simple regex parsing to avoid YAML dependency. */ function parseConfigYaml(configPath: string): { id?: string; name?: string } { try { const content = fs.readFileSync(configPath, "utf-8"); // Simple regex parsing for id and name under service: // Looks for patterns like: // service: // id: "xxx" // name: "yyy" const idMatch = content.match( /service:\s*\n(?:[^\n]*\n)*?\s*id:\s*["']?([^"'\n]+)["']?/ ); const nameMatch = content.match( /service:\s*\n(?:[^\n]*\n)*?\s*name:\s*["']?([^"'\n]+)["']?/ ); return { id: idMatch?.[1]?.trim(), name: nameMatch?.[1]?.trim(), }; } catch { return {}; } } /** * Recursively search for .tusk/config.yaml files starting from the given roots. * Searches up to maxDepth levels deep. */ function findTuskConfigs( roots: string[], maxDepth: number = 3 ): DiscoveredService[] { const services: DiscoveredService[] = []; const visited = new Set<string>(); function searchDirectory(dir: string, depth: number): void { if (depth > maxDepth || visited.has(dir)) { return; } visited.add(dir); const configPath = path.join(dir, ".tusk", "config.yaml"); if (fs.existsSync(configPath)) { const parsed = parseConfigYaml(configPath); if (parsed.id) { services.push({ id: parsed.id, name: parsed.name || path.basename(dir), configPath, rootPath: dir, }); } // Don't search subdirectories if we found a config here return; } // Search subdirectories try { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if ( entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules" ) { searchDirectory(path.join(dir, entry.name), depth + 1); } } } catch { // Ignore permission errors } } for (const root of roots) { // Normalize the path and resolve symlinks try { const resolvedRoot = fs.realpathSync(root); searchDirectory(resolvedRoot, 0); } catch { // If we can't resolve the path, try it directly searchDirectory(root, 0); } } return services; } /** * Service discovery context that manages discovered services * and provides the appropriate service ID for API calls. */ export class ServiceDiscoveryContext { private discoveredServices: DiscoveredService[] = []; private defaultServiceId?: string; constructor(envServiceId?: string) { this.defaultServiceId = envServiceId; } /** * Discover services from the given workspace roots. */ discoverFromRoots(roots: string[]): void { this.discoveredServices = findTuskConfigs(roots); console.error( `Discovered ${this.discoveredServices.length} Tusk service(s):` ); for (const service of this.discoveredServices) { console.error(` - ${service.name} (${service.id})`); } } /** * Get all discovered services. */ getServices(): DiscoveredService[] { return this.discoveredServices; } /** * Get the service ID to use for an API call. * Priority: * 1. Explicitly provided serviceId * 2. Environment variable default * 3. Single discovered service (if only one) * 4. Error if multiple discovered and none specified */ resolveServiceId(providedServiceId?: string): string { // 1. Explicit service ID if (providedServiceId) { return providedServiceId; } // 2. Environment variable default if (this.defaultServiceId) { return this.defaultServiceId; } // 3. Single discovered service if (this.discoveredServices.length === 1) { return this.discoveredServices[0].id; } // 4. Multiple or no services if (this.discoveredServices.length === 0) { throw new Error( "No Tusk service configured. Either set TUSK_DRIFT_SERVICE_ID " + "environment variable or ensure a .tusk/config.yaml exists in your workspace." ); } // Multiple services found const serviceList = this.discoveredServices .map((s) => ` - "${s.name}" (id: ${s.id})`) .join("\n"); throw new Error( `Multiple Tusk services found. Please specify observableServiceId:\n${serviceList}` ); } /** * Check if we have any services available (discovered or default). */ hasServices(): boolean { return !!this.defaultServiceId || this.discoveredServices.length > 0; } /** * Get instructions text about available services. */ getServicesDescription(): string { if (this.discoveredServices.length === 0) { if (this.defaultServiceId) { return `Using configured service: ${this.defaultServiceId}`; } return "No services discovered."; } if (this.discoveredServices.length === 1) { const s = this.discoveredServices[0]; return `Using service: "${s.name}" (id: ${s.id}, path: ${s.rootPath})`; } const serviceList = this.discoveredServices .map((s) => `- "${s.name}" (id: ${s.id}, path: ${s.rootPath})`) .join("\n"); return `Multiple services available. Specify observableServiceId when querying:\n${serviceList}`; } }

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/Use-Tusk/drift-mcp'

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