Skip to main content
Glama
cli.ts10.2 kB
// src/lib.ts import { Command, OptionValues } from "commander"; import * as fs from "node:fs"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; import { getVersion } from "./utils.js"; import { parseNameValuePair, addParsedSourceToTarget, normalizeAndAddDomain, logConfigSummary, } from "./cli_lib.js"; import { logger } from "./logger.js"; const VERSION = getVersion(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export interface CliConfig { docSources: Record<string, string>; allowedDomains: Set<string>; openApiSpecs: Record<string, string>; } // Source loading functions /** @internal */ export function loadDefaultSources( defaultsPath: string ): Record<string, string> { const defaultSources: Record<string, string> = {}; try { const defaultsContent = fs.readFileSync(defaultsPath, "utf-8"); const lines = defaultsContent.split("\n"); for (const line of lines) { const trimmedLine = line.trim(); if (trimmedLine.startsWith("-")) { const content = trimmedLine.substring(1).trim(); const parsed = parseNameValuePair(content); if (parsed) { addParsedSourceToTarget(parsed, defaultSources, "defaults"); } } } } catch (error) { logger.error( `Failed to load default sources: ${error instanceof Error ? error.message : error}` ); } return defaultSources; } // Process different source types /** @internal */ export function processSourceOptions( sources: Record<string, string>, singleOptions: string[] | undefined, singleFlagName: string, multipleOption: string | undefined, multipleFlagName: string ): Record<string, string> { // Process individual options if (singleOptions && singleOptions.length > 0) { for (const option of singleOptions) { const parsed = parseNameValuePair(option); if (parsed) { addParsedSourceToTarget(parsed, sources, singleFlagName); } } } // Process space-separated option if (multipleOption) { const items = multipleOption.split(/\s+/); for (const item of items) { if (!item.trim()) continue; const parsed = parseNameValuePair(item.trim()); if (parsed) { addParsedSourceToTarget(parsed, sources, multipleFlagName); } } } return sources; } /** @internal */ export function processDomainOptions( allowDomainOptions: string[] | undefined, allowDomainsOption: string | undefined, denyDomainOptions: string[] | undefined, denyDomainsOption: string | undefined, docSources: Record<string, string>, openApiSpecs: Record<string, string> ): Set<string> { const allowedDomains = new Set<string>(); const deniedDomains = new Set<string>(); let userSpecifiedDomains = false; // Process individual --allow-domain options if (allowDomainOptions && allowDomainOptions.length > 0) { userSpecifiedDomains = true; allowDomainOptions.forEach((domain) => { normalizeAndAddDomain(domain, allowedDomains, "--allow-domain"); }); } // Process space-separated --allow-domains option if (allowDomainsOption) { userSpecifiedDomains = true; const domainsList = allowDomainsOption.split(/\s+/); domainsList.forEach((domain) => { normalizeAndAddDomain(domain, allowedDomains, "--allow-domains"); }); } // Always add explicitly allowed domains from CLI flags if (userSpecifiedDomains) { logger.info("Using explicitly allowed domains from CLI flags"); } // Add inferred domains from sources (always add these in addition to any explicitly allowed domains) inferDomainsFromSources(openApiSpecs, allowedDomains); inferDomainsFromSources(docSources, allowedDomains); // Process individual --deny-domain options if (denyDomainOptions && denyDomainOptions.length > 0) { userSpecifiedDomains = true; denyDomainOptions.forEach((domain) => { normalizeAndAddDomain(domain, deniedDomains, "--deny-domain"); }); } // Process space-separated --deny-domains option if (denyDomainsOption) { userSpecifiedDomains = true; const domainsList = denyDomainsOption.split(/\s+/); domainsList.forEach((domain) => { normalizeAndAddDomain(domain, deniedDomains, "--deny-domains"); }); } // Remove denied domains from allowed domains deniedDomains.forEach((domain) => { allowedDomains.delete(domain); }); return allowedDomains; } /** @internal */ export function inferDomainsFromSources( sources: Record<string, string>, allowedDomains: Set<string> ): void { Object.values(sources).forEach((url) => { try { if (url.startsWith("http:") || url.startsWith("https:")) { const parsedUrl = new URL(url); const hostname = parsedUrl.hostname.toLowerCase(); allowedDomains.add(hostname); } } catch (e) { logger.error( `Failed to parse source URL '${url}' for domain inference. Error: ${ e instanceof Error ? e.message : String(e) }. Skipping.` ); } }); // Warning messages if (allowedDomains.size === 0 && Object.keys(sources).length > 0) { logger.warn( "Warning: No remote URLs configured or parsed, and no explicit domains allowed. Fetching might be restricted to local files only." ); } else if (allowedDomains.size === 0) { logger.warn( "No domains could be inferred from sources. Only local file access will be allowed." ); } } // Main option processing functions /** @internal */ export function getDocSources( options: OptionValues, includeDefaults: boolean ): Record<string, string> { let docSources: Record<string, string> = {}; let deprecatedDocSources: Record<string, string> = {}; // Load defaults if needed if (includeDefaults) { const defaultsPath = path.resolve(__dirname, "static/defaults.md"); docSources = loadDefaultSources(defaultsPath); } // Process URL options docSources = processSourceOptions( docSources, options.llmsTxtSource, "--llms-txt-source", options.llmsTxtSources, "--llms-txt-sources" ); if (options?.url?.length > 0 || options?.urls?.length > 0) { logger.warn( "Warning: The --url and --urls options are deprecated. Use --llms-txt-source and --llms-txt-sources instead." ); } deprecatedDocSources = processSourceOptions( deprecatedDocSources, options.url, "--url", options.urls, "--urls" ); // Merge deprecated sources into docSources Object.assign(docSources, deprecatedDocSources); if (Object.keys(docSources).length === 0) { logger.warn( "No documentation sources provided. Use --source to add sources." ); } return docSources; } /** @internal */ export function getOpenApiSpecs(options: OptionValues): Record<string, string> { let openApiSpecs: Record<string, string> = {}; openApiSpecs = processSourceOptions( openApiSpecs, options.openapiSpecSource, "--openapi-spec-source", options.openapiSpecSources, "--openapi-spec-sources" ); if (Object.keys(openApiSpecs).length === 0) { logger.warn( "No OpenAPI specs provided. Use --openapi to add OpenAPI specs." ); } return openApiSpecs; } // Main CLI parsing function export function parseCliArgs(): CliConfig { const program = new Command(); program .name("SushiMCP") .description( "Starts SushiMCP, a dev tools model context protocol server that serves context on a roll." ) .version(VERSION) .option( // DEPRECATED: Use --llms-txt-source instead "--url <name:url>", "Specify a single documentation source (repeatable)", (value, previous: string[] = []) => previous.concat(value), [] ) .option( // DEPRECATED: Use --llms-txt-sources instead "--urls <string>", "Specify a list of llms.txt sources as a single space-separated string (e.g., 'name1:url1 name2:url2')" ) .option( "--llms-txt-source <name:url>", "Specify a single documentation source (repeatable)", (value, previous: string[] = []) => previous.concat(value), [] ) .option( "--llms-txt-sources <string>", "Specify a list of llms.txt sources as a single space-separated string (e.g., 'name1:url1 name2:url2')" ) .option( "--openapi-spec-source <name:url>", "Specify a single OpenAPI spec source (repeatable)", (value, previous: string[] = []) => previous.concat(value), [] ) .option( "--openapi-spec-sources <string>", "Specify a list of OpenAPI spec sources as a single space-separated string (e.g., 'name1:url1 name2:url2')" ) .option( "--no-defaults", "Do NOT include default documentation sources from src/defaults.md" ) .option( "--allow-domain <domain>", "Allow fetching from a specific domain (repeatable, use '*' for all)", (value, previous: string[] = []) => previous.concat(value), [] ) .option( "--allow-domains <string>", "Allow fetching from a list of domains as a single space-separated string (e.g., 'domain1 domain2')" ) .option( "--deny-domain <domain>", "Deny fetching from a specific domain (repeatable, use '*' for all)", (value, previous: string[] = []) => previous.concat(value), [] ) .option( "--deny-domains <string>", "Deny fetching from a list of domains as a single space-separated string (e.g., 'domain1 domain2')" ); program.parse(process.argv); const options = program.opts(); // Process all options const docSources = getDocSources(options, options.defaults); const openApiSpecs = getOpenApiSpecs(options); const allowedDomains = processDomainOptions( options.allowDomain, options.allowDomains, options.denyDomain, options.denyDomains, docSources, openApiSpecs ); // Create the final config const config = { allowedDomains, docSources, openApiSpecs }; // Log summary if in appropriate mode logConfigSummary(config); return config; } // Copyright (C) 2025 Christopher White // SPDX-License-Identifier: AGPL-3.0-or-later

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/maverickg59/sushimcp'

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