/** * Remote MCP servers — streamable-HTTP MCP servers the owner connects once * (Zoho MCP first, any other server the same way) and Crewly hands to every * agent whose role may use them. * * Stored at `/remote-mcp-servers.json` with mode 0600. The URL * (Zoho's carries the server's API key) and any headers are secrets: they * are never logged, never returned in full by {@link toView}, and never * written into a project directory (a project `.mcp.json` would be * committed). Agents get them through per-session files under CREWLY_HOME * (see remote-mcp-launch.service). * * Each server is a gated connector `mcp:` in * {@link ConnectorAccessService}: open to every agent until the owner picks * roles. * * @module services/connector/remote-mcp.service */ import { ConnectorAccessService } from './connector-access.service.js'; /** Catalog preset a server was added from. */ export type RemoteMcpProvider = 'zoho' | 'custom'; /** One stored server (secrets included — never send this to a client). */ export interface RemoteMcpServer { /** Stable id; also the MCP server name agents see and the `mcp:` access key. */ id: string; /** Owner-facing name. */ label: string; /** Streamable-HTTP endpoint (secret). */ url: string; /** Extra request headers (secret values). */ headers?: Record; /** Catalog preset. */ provider?: RemoteMcpProvider; /** ISO time it was added. */ createdAt: string; } /** What the API returns: no secrets. */ export interface RemoteMcpServerView { id: string; label: string; provider: RemoteMcpProvider; /** Scheme + host only, e.g. `https://crm-123.zohomcp.com/…`. */ urlMasked: string; /** Header names (values withheld). */ headerNames: string[]; createdAt: string; /** Key of its role allowlist in connector access. */ connectorId: string; } /** Input for {@link RemoteMcpService.add}. */ export interface AddRemoteMcpInput { label: string; url: string; headers?: Record; provider?: string; } /** A validation failure the API turns into a 400. */ export declare class RemoteMcpValidationError extends Error { constructor(message: string); } /** * The connector-access id of a server. * * @param id - Server id * @returns `mcp:` */ export declare function remoteMcpConnectorId(id: string): string; /** * Show a URL without its secret parts: scheme and host only. * * @param url - Stored URL * @returns e.g. `https://crm-123.zohomcp.com/…`, or `(hidden)` when it does not parse */ export declare function maskRemoteMcpUrl(url: string): string; /** * Validate a server URL: https (http only for localhost), no whitespace, * quotes or backslashes (it is embedded in launch files), bounded length. * * @param raw - Pasted URL * @returns The trimmed URL * @throws RemoteMcpValidationError when it is not usable (message never echoes the URL) */ export declare function validateRemoteMcpUrl(raw: unknown): string; /** * Validate optional headers: token-shaped names, single-line values. * * @param raw - Headers object or undefined * @returns Clean headers, or undefined when none * @throws RemoteMcpValidationError on a bad name or value */ export declare function validateRemoteMcpHeaders(raw: unknown): Record | undefined; /** * Clean a label. * * @param raw - Label input * @returns Trimmed label * @throws RemoteMcpValidationError when empty */ export declare function normalizeRemoteMcpLabel(raw: unknown): string; /** * Derive an id (MCP server name) from a label: lowercase slug, unique. * * @param label - Label * @param taken - Ids already in use * @returns e.g. `zoho`, `zoho-2` */ export declare function deriveRemoteMcpId(label: string, taken: Set): string; /** * Turn a stored server into its API shape. * * @param server - Stored server * @returns The view (no URL, no header values) */ export declare function toView(server: RemoteMcpServer): RemoteMcpServerView; /** * Reads and writes the remote MCP server store. */ export declare class RemoteMcpService { private static instance; private readonly logger; private readonly filePath; private readonly access; private writeChain; /** * @param crewlyHome - Override the home directory (tests) * @param access - Connector access service (tests) */ constructor(crewlyHome?: string, access?: ConnectorAccessService); static getInstance(): RemoteMcpService; /** Reset the singleton (tests). */ static resetInstance(): void; /** Absolute path of the store file. */ getFilePath(): string; /** * Every stored server, with secrets. Unreadable entries are dropped. * * @returns Servers in the order they were added */ list(): Promise; /** * One server, with secrets. * * @param id - Server id * @returns The server or undefined */ get(id: string): Promise; /** * Add a server. * * @param input - Label, URL, optional headers and provider * @returns The stored server * @throws RemoteMcpValidationError on bad input or a full store */ add(input: AddRemoteMcpInput): Promise; /** * Rename a server (its id, and so its MCP name and allowlist, stay). * * @param id - Server id * @param label - New label * @returns The updated server, or undefined when there is none */ rename(id: string, label: string): Promise; /** * Remove a server and its allowlist. * * @param id - Server id * @returns True when it existed */ remove(id: string): Promise; /** * Servers an agent role may use (its `mcp:` allowlist passes). * * @param role - Agent role * @returns Allowed servers, with secrets */ serversForRole(role: string): Promise; /** * Serialised read-modify-write; the file is written 0600. * * @param fn - Change to apply * @returns The change's result */ private mutate; } /** * Write JSON readable only by the owner (0600), atomically. * * @param filePath - Destination * @param data - JSON value */ export declare function writeSecretJson(filePath: string, data: unknown): Promise; //# sourceMappingURL=remote-mcp.service.d.ts.map