/** * AI-agent MCP config writers — the TypeScript port of the shared C# reference * `com.IvanMurzak.McpPlugin.AgentConfig.JsonAiAgentConfig` / `TomlAiAgentConfig` * (`MCP-Plugin-dotnet/McpPlugin/src/AgentConfig/*.cs`), the single source of truth every * configurator (editor UI, the three engine CLIs, `configure`) writes through. An agent's MCP * client config file is JSON (Claude/Cursor/VS Code/…) or TOML (Codex); this module reproduces the * C# writers' behaviour byte-for-byte so a CLI-written config and an editor-written config are * indistinguishable. * * **Byte-for-byte parity is gated by golden vectors** (`test/golden-vectors/AgentConfig.*.json`), * exactly as the identity module is gated by `ProjectIdentity.GoldenVectors.json`. The vectors pin * the deterministic new-file serialization ({@link JsonAiAgentConfig.expectedFileContent} / * {@link TomlAiAgentConfig.expectedFileContent}) for a canonical property matrix. * * Two behaviours make the writers safe to run against a user's existing config: * - **deterministic property ordering** — server-entry keys are emitted in `Ordinal` sort order * (matches C# `OrderBy(k => k, StringComparer.Ordinal)`), so a re-run never reorders keys; * - **duplicate/deprecated cleanup** — a sibling entry written under a different name but the same * identity value (`command` / `url` by default) is removed, and legacy server names * (`Unity-MCP`) are cleaned up, so re-configuring never leaves a stale duplicate server. * * The `serverName` and `deprecatedServerNames` are constructor parameters (the C# reference hard-codes * `ai-game-developer` / `Unity-MCP`) so an engine adapter selects them — Unreal writes `unreal-mcp`, * Unity/Godot write `ai-game-developer` — keeping engine specifics in the adapter, never here. * * NOTE on JSON escaping: System.Text.Json escapes `<`, `>`, `&`, `+`, `'` and all non-ASCII as * `\uXXXX`; `JSON.stringify` does not. For the ASCII, HTML-safe data these writers emit (forward-slash * paths, `scheme://host/path` URLs, `key=value` args, base64url tokens) the two serializers are * byte-identical — the domain that matters. The golden vectors stay inside that domain. */ /** How a configured value is compared against the value on disk when deciding "already configured". */ export declare enum ValueComparisonMode { /** Byte-for-byte string equality. */ Exact = "Exact", /** Filesystem-path equality (separator-insensitive, trailing-slash-insensitive). */ Path = "Path", /** URL equality (scheme/host case-insensitive, trailing-slash-insensitive). */ Url = "Url" } /** The canonical server-entry name written under the body path (C# `DefaultMcpServerName`). */ export declare const DEFAULT_MCP_SERVER_NAME = "ai-game-developer"; /** Server-entry names written by older plugin versions, cleaned up on configure/unconfigure. */ export declare const DEFAULT_DEPRECATED_MCP_SERVER_NAMES: readonly string[]; /** Property keys used to recognise the same server entry written under a different name. */ export declare const DEFAULT_IDENTITY_KEYS: readonly string[]; /** The nested-body-path delimiter (C# `Consts.MCP.Server.BodyPathDelimiter`). */ export declare const BODY_PATH_DELIMITER = "->"; /** The default body path (C# `Consts.MCP.Server.DefaultBodyPath`). */ export declare const DEFAULT_BODY_PATH = "mcpServers"; /** Split a body path into its object-nesting segments (C# `BodyPathSegments`). */ export declare function bodyPathSegments(bodyPath: string): string[]; /** Options shared by both config writers. */ export interface AgentConfigOptions { /** The canonical server-entry name; defaults to {@link DEFAULT_MCP_SERVER_NAME}. */ serverName?: string; /** Body path (dot-free; `->`-delimited for nesting); defaults to {@link DEFAULT_BODY_PATH}. */ bodyPath?: string; /** Deprecated server names to clean up; defaults to {@link DEFAULT_DEPRECATED_MCP_SERVER_NAMES}. */ deprecatedServerNames?: readonly string[]; } /** A JSON-representable value stored on the server entry (object / array / primitive / null). */ export type JsonNode = string | number | boolean | null | JsonNode[] | { [key: string]: JsonNode; }; /** * The JSON MCP-config writer (Claude Code/Desktop, Cursor, VS Code, Gemini, …). Port of C# * `JsonAiAgentConfig`. Build up the desired server entry with {@link setProperty} / * {@link setPropertyToRemove}, then {@link configure} a config file on disk. `fs` is injected so the * writer is testable with no real filesystem. */ export declare class JsonAiAgentConfig { readonly serverName: string; readonly bodyPath: string; readonly deprecatedServerNames: readonly string[]; private readonly _properties; private readonly _propertiesToRemove; private readonly _identityKeys; constructor(options?: AgentConfigOptions); get identityKeys(): readonly string[]; setProperty(key: string, value: JsonNode, requiredForConfiguration?: boolean, comparison?: ValueComparisonMode): this; setPropertyToRemove(key: string): this; addIdentityKey(key: string): this; /** Apply the HTTP `Authorization: Bearer ` header, or remove `headers` when not required. */ applyHttpAuthorization(isRequired: boolean, token: string | undefined): this; /** Add or remove the stdio `token=` arg (never touches HTTP `headers`, which it strips). */ applyStdioAuthorization(isRequired: boolean, token: string | undefined): this; /** The deterministic new-file content: the server entry nested under the body path, 2-space indent. */ expectedFileContent(): string; /** * Configure `configPath`: create it from {@link expectedFileContent} when absent (or unparsable), * else merge the server entry in — removing deprecated + duplicate sibling entries, dropping the * `propertiesToRemove` keys, and writing properties in Ordinal order. Returns true on success. */ configure(configPath: string, io?: AgentConfigFs): boolean; /** Remove our (and deprecated/duplicate) server entries. Returns true when something was removed. */ unconfigure(configPath: string, io?: AgentConfigFs): boolean; /** True when our server entry, a deprecated entry, or a duplicate sibling is present. */ isDetected(configPath: string, io?: AgentConfigFs): boolean; /** True when every required property matches on disk and no property-to-remove is present. */ isConfigured(configPath: string, io?: AgentConfigFs): boolean; private buildServerEntry; private sortedPropertyKeys; private requiredPropertiesMatch; private hasPropertiesToRemove; private findDuplicateServerEntryKeys; } /** A raw TOML value written back verbatim (floats/dates the minimal parser does not model). */ export declare class RawTomlValue { readonly value: string; constructor(value: string); } /** A value storable in a TOML server section (C# `TomlAiAgentConfig` value union). */ export type TomlValue = string | number | boolean | string[] | number[] | boolean[] | Record | RawTomlValue; /** * The TOML MCP-config writer (Codex). Port of C# `TomlAiAgentConfig`. TOML sections are * `[.]`; the writer merges into an existing section, preserving unrelated * sections/keys and typed values it does not manage (floats/dates via {@link RawTomlValue}). All * output uses `\n` line endings (the C# reference uses `Environment.NewLine`; the golden vectors are * the LF form, which is what the CLI and Linux CI emit). */ export declare class TomlAiAgentConfig { readonly serverName: string; readonly bodyPath: string; readonly deprecatedServerNames: readonly string[]; private readonly _properties; private readonly _propertiesToRemove; private readonly _identityKeys; constructor(options?: AgentConfigOptions); get identityKeys(): readonly string[]; get sectionName(): string; setProperty(key: string, value: TomlValue, requiredForConfiguration?: boolean, comparison?: ValueComparisonMode): this; setPropertyToRemove(key: string): this; addIdentityKey(key: string): this; /** TOML HTTP config does not model an auth header (matches C# — deliberate no-op). */ applyHttpAuthorization(_isRequired: boolean, _token: string | undefined): this; applyStdioAuthorization(isRequired: boolean, token: string | undefined): this; /** The deterministic new-file content: the section header + Ordinal-ordered props, trailing `\n`. */ expectedFileContent(): string; /** Configure `configPath`: create from {@link expectedFileContent} when absent, else merge. */ configure(configPath: string, io?: AgentConfigFs): boolean; unconfigure(configPath: string, io?: AgentConfigFs): boolean; isDetected(configPath: string, io?: AgentConfigFs): boolean; isConfigured(configPath: string, io?: AgentConfigFs): boolean; private generateSection; private sortedKeys; private requiredPropertiesMatch; private hasPropertiesToRemove; private findDuplicateSectionIndices; private removeDuplicateSections; } /** * Minimal filesystem seam used by the config writers, so they are unit-testable without touching * disk. The default is a thin `node:fs` wrapper ({@link nodeFs}). */ export interface AgentConfigFs { existsSync(path: string): boolean; readFileSync(path: string): string; writeFileSync(path: string, data: string): void; mkdirSync(dir: string): void; } export declare const nodeFs: AgentConfigFs; //# sourceMappingURL=agent-config.d.ts.map