UNPKG

@extenshi/mcp

Version:

Extenshi MCP server — bring the extension catalog (search, security, market research) and pre-publish scanning into your AI tools

90 lines • 3.45 kB
/** * Configuration loading for the Extenshi MCP server. * * The MCP server runs locally (stdio) inside the developer's AI client, so the * API key comes from the environment — never from a request header. We * deliberately reuse the SAME key sources as `@extenshi/cli` so a developer who * already ran `extenshi login` is set up with zero extra steps: * * 1. Environment variable EXTENSHI_API_KEY (set in the client's mcp config) * 2. A `.env` in the current working directory (does NOT override real env) * 3. Config file ~/.extenshi/config.json (written by `extenshi login`) * * Base URLs are baked into the package at build time and are NOT configurable: * every install talks to the production backend. There is intentionally no env * var or config-file override for them (was EXTENSHI_BFF_URL / EXTENSHI_API_URL * / EXTENSHI_DOCS_URL) — only the API key is read from the environment. * * - https://bff.extenshi.io (read tools) * - https://scan.extenshi.io (scan) * - https://docs.extenshi.io (search_docs) * * stdout is the MCP protocol channel — this module must NEVER write to it. */ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; // Compiled-in production endpoints. Constant by design — do not read these from // the environment or the config file (see module doc above). const BFF_URL = 'https://bff.extenshi.io'; const SCAN_URL = 'https://scan.extenshi.io'; const DOCS_URL = 'https://docs.extenshi.io'; const CONFIG_PATH = path.join(os.homedir(), '.extenshi', 'config.json'); function readConfigFile() { try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); } catch { return {}; } } /** * Parse a `.env` body into key/value pairs. Supports comments (#), blank * lines, an optional `export ` prefix, and single/double-quoted values. */ function parseDotEnv(raw) { const out = {}; for (const rawLine of raw.split(/\r?\n/)) { const line = rawLine.trim(); if (!line || line.startsWith('#')) continue; const withoutExport = line.startsWith('export ') ? line.slice('export '.length) : line; const eq = withoutExport.indexOf('='); if (eq === -1) continue; const key = withoutExport.slice(0, eq).trim(); if (!key) continue; let value = withoutExport.slice(eq + 1).trim(); if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { value = value.slice(1, -1); } out[key] = value; } return out; } /** Load a local `.env` into process.env without overriding real env. Fail-soft. */ function loadDotEnv(cwd = process.cwd()) { try { const parsed = parseDotEnv(fs.readFileSync(path.join(cwd, '.env'), 'utf8')); for (const [key, value] of Object.entries(parsed)) { if (process.env[key] === undefined) process.env[key] = value; } } catch { // No .env or unreadable — non-fatal. } } export function loadConfig() { loadDotEnv(); const file = readConfigFile(); return { apiKey: process.env.EXTENSHI_API_KEY ?? file.apiKey ?? null, // Endpoints are compiled-in constants — never overridden by env/config. bffUrl: BFF_URL, scanUrl: SCAN_URL, docsUrl: DOCS_URL, }; } //# sourceMappingURL=config.js.map