MCP Terminal Server

/** * 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 { Runtime } from '../manager/types'; /** * 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); } throw new Error('Could not find project root'); } /** * Finds the Genkit hidden directory containing runtime state files. */ export async function findRuntimesDir(projectRoot?: string): Promise<string> { const root = projectRoot ?? (await findProjectRoot()); return path.join(root, '.genkit', 'runtimes'); } /** * Finds the Genkit hidden directory containing server (UI server, telemetry server, etc) state files. */ export async function findServersDir(projectRoot?: string): Promise<string> { const root = projectRoot ?? (await findProjectRoot()); return path.join(root, '.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 ( error instanceof Error && (error.cause as any).code === 'ECONNREFUSED' ) { 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 ( error instanceof Error && (error.cause as any).code === 'ECONNREFUSED' ) { 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++; } }