Skip to main content
Glama

Genkit MCP

Official
by firebase
utils.ts7.24 kB
/** * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as fs from 'fs/promises'; import * as path from 'path'; import type { Runtime } from '../manager/types'; import { isConnectionRefusedError } from './errors'; import { logger } from './logger'; export interface DevToolsInfo { /** URL of the dev tools server. */ url: string; /** Timestamp of when the dev tools server was started. */ timestamp: string; } /** * Finds the project root by looking for a `package.json` file. */ export async function findProjectRoot(): Promise<string> { let currentDir = process.cwd(); while (currentDir !== path.parse(currentDir).root) { const packageJsonPath = path.join(currentDir, 'package.json'); const goModPath = path.join(currentDir, 'go.mod'); const pyprojectPath = path.join(currentDir, 'pyproject.toml'); const pyproject2Path = path.join(currentDir, 'requirements.txt'); try { const [ packageJsonExists, goModExists, pyprojectExists, pyproject2Exists, ] = await Promise.all([ fs .access(packageJsonPath) .then(() => true) .catch(() => false), fs .access(goModPath) .then(() => true) .catch(() => false), fs .access(pyprojectPath) .then(() => true) .catch(() => false), fs .access(pyproject2Path) .then(() => true) .catch(() => false), ]); if ( packageJsonExists || goModExists || pyprojectExists || pyproject2Exists ) { return currentDir; } } catch { // Continue searching if any errors occur } currentDir = path.dirname(currentDir); } return process.cwd(); } /** * Finds the Genkit hidden directory containing runtime state files. */ export async function findRuntimesDir(projectRoot: string): Promise<string> { return path.join(projectRoot, '.genkit', 'runtimes'); } /** * Finds the Genkit hidden directory containing server (UI server, telemetry server, etc) state files. */ export async function findServersDir(projectRoot: string): Promise<string> { return path.join(projectRoot, '.genkit', 'servers'); } /** * Extract project name (i.e. basename of the project root folder) from the * path to a runtime file. * * e.g.) /path/to/<basename>/.genkit/runtimes/123.json returns <basename>. * * @param filePath path to a runtime file * @returns project name */ export function projectNameFromGenkitFilePath(filePath: string): string { const parts = filePath.split('/'); const basePath = parts .slice( 0, Math.max( parts.findIndex((value) => value === '.genkit'), 0 ) ) .join('/'); return basePath === '' ? 'unknown' : path.basename(basePath); } /** * Detects what runtime is used in the current directory. * @returns Runtime of the project directory. */ export async function detectRuntime(directory: string): Promise<Runtime> { const files = await fs.readdir(directory); for (const file of files) { const filePath = path.join(directory, file); const stat = await fs.stat(filePath); if (stat.isFile() && (path.extname(file) === '.go' || file === 'go.mod')) { return 'go'; } } try { await fs.access(path.join(directory, 'package.json')); return 'nodejs'; } catch { return undefined; } } /** * Checks the health of a server with a /api/__health endpoint. */ export async function checkServerHealth(url: string): Promise<boolean> { try { const response = await fetch(`${url}/api/__health`); return response.status === 200; } catch (error) { if (isConnectionRefusedError(error)) { return false; } } return true; } /** * Waits until the server is healthy or the timeout is reached. */ export async function waitUntilHealthy( url: string, maxTimeout = 10000 ): Promise<boolean> { const startTime = Date.now(); while (Date.now() - startTime < maxTimeout) { try { const response = await fetch(`${url}/api/__health`); if (response.status === 200) { return true; } } catch (error) { // Ignore errors and continue retrying } await new Promise((resolve) => setTimeout(resolve, 500)); } return false; } /** * Waits until the server becomes unresponsive or the timeout is reached. */ export async function waitUntilUnresponsive( url: string, maxTimeout = 10000 ): Promise<boolean> { const startTime = Date.now(); while (Date.now() - startTime < maxTimeout) { try { const health = await fetch(`${url}/api/__health`); } catch (error) { if (isConnectionRefusedError(error)) { return true; } } await new Promise((resolve) => setTimeout(resolve, 500)); } return false; } export async function retriable<T>( fn: () => Promise<T>, opts: { maxRetries?: number; delayMs?: number } ): Promise<T> { const maxRetries = opts.maxRetries ?? 3; const delayMs = opts.delayMs ?? 0; let attempt = 0; while (true) { try { return await fn(); } catch (e) { if (attempt >= maxRetries - 1) { throw e; } if (delayMs > 0) { await new Promise((r) => setTimeout(r, delayMs)); } } attempt++; } } /** * Checks if the provided data is a valid dev tools server state file. */ export function isValidDevToolsInfo(data: any): data is DevToolsInfo { return ( typeof data === 'object' && typeof data.url === 'string' && typeof data.timestamp === 'string' ); } /** * Writes the toolsInfo file to the project root */ export async function writeToolsInfoFile(url: string, projectRoot: string) { const serversDir = await findServersDir(projectRoot); const toolsJsonPath = path.join(serversDir, `tools-${process.pid}.json`); try { const serverInfo = { url, timestamp: new Date().toISOString(), } as DevToolsInfo; await fs.mkdir(serversDir, { recursive: true }); await fs.writeFile(toolsJsonPath, JSON.stringify(serverInfo, null, 2)); logger.debug(`Tools Info file written: ${toolsJsonPath}`); } catch (error) { logger.error('Error writing tools config', error); } } /** * Removes the toolsInfo file. */ export async function removeToolsInfoFile( fileName: string, projectRoot: string ) { try { const serversDir = await findServersDir(projectRoot); const filePath = path.join(serversDir, fileName); await fs.unlink(filePath); logger.debug(`Removed unhealthy toolsInfo file ${fileName} from manager.`); } catch (error) { logger.debug(`Failed to delete toolsInfo file: ${error}`); } }

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/firebase/genkit'

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