import type { ClientId } from '@zhixuan92/multi-model-agent-core'; import { type AtomicFsDeps } from './atomic-write.js'; import { type ClientCapability, type McpConfigFormat } from './capability-registry.js'; /** Why a registration write/remove was refused. Surfaced for inventory reporting. */ type RegistrationConflictReason = 'unrecognised_entry' | 'invalid_config' | 'stale_bytes' | 'static_credential' | 'unsupported_format'; /** Thrown by {@link writeOwnedRegistration}/{@link removeOwnedRegistration} whenever * user content, a concurrent write, or an invalid input blocks the mutation. The * file (if any) is guaranteed unchanged when this is thrown. */ export declare class RegistrationConflictError extends Error { readonly code: "registration_conflict"; readonly reason: RegistrationConflictReason; constructor(reason: RegistrationConflictReason, message: string); } /** * Is this `mcpServers.mma` (or equivalent) value an entry MMA itself wrote? * * Dispatches on the client's `mcpConfigFormat`: * - `stdio-json` (the Claude Desktop bridge shape): exactly `{command, args}`; * `args` is `[entrypoint, "mcp"]` or that plus `--client=`, and both * the Node command and resolved CLI entrypoint are absolute paths. * - every other supported format (`json`, `plugin-json`): key set ⊆ * `{url, serverUrl, headers, env}` and the URL is exactly MMA's own loopback * `/mcp` endpoint. */ export declare function isOwnedMcpEntry(value: unknown, format: McpConfigFormat, expectedStdioEntrypoint?: string): boolean; export declare function assertNoStaticCredential(entry: unknown): void; /** Refuse the write when the config changed between the initial read and now. See * the module doc's point 3 — this is what makes atomic replacement safe against a * concurrent save rather than merely torn-file-safe. */ export declare function assertBytesUnchanged(fs: AtomicFsDeps, path: string, expected: Buffer | undefined): void; interface WriteOwnedRegistrationInput { path: string; clientId: ClientId; /** The MMA entry to merge in at `mcpServers.mma`. Never include a static bearer * token here — see the module doc's point 5. */ entry: Record; /** Test/advanced seam: overrides the real filesystem. */ fs?: AtomicFsDeps; } interface OwnedRegistrationResult { path: string; changed: boolean; } /** * Merge `entry` into `path`'s `mcpServers.mma`, refusing rather than clobbering any * content this module cannot prove MMA owns. See the module doc for the full * discipline (ownership recognition, refuse-not-clobber, stale detection, atomic * rename, no static credential). */ export declare function writeOwnedRegistration(input: WriteOwnedRegistrationInput): Promise; interface RemoveOwnedRegistrationInput { path: string; clientId: ClientId; /** Required for `stdio-json` clients: the CLI entrypoint this installation * launches. Removal must prove ownership just as strictly as writing does, and * a stdio entry's only proof is that it launches THIS entrypoint. Omitting it * makes ownership unprovable, so the entry is preserved rather than removed. */ expectedStdioEntrypoint?: string; fs?: AtomicFsDeps; } /** * Remove `mcpServers.mma` from `path` and nothing else. Applies the SAME ownership * recogniser as {@link writeOwnedRegistration}, so it can never delete a hand-written * entry that a write would have refused to overwrite. */ export declare function removeOwnedRegistration(input: RemoveOwnedRegistrationInput): Promise; /** * Per-client registration writers. * * Everything above this point is the shared, format-generic ownership/stale/ * atomic-rename/no-static-credential machinery. Everything below dispatches to * the client-specific writer that renders each client's own recognised entry * shape and installs it through that machinery — selected by the capability * registry, never a hand-maintained target switch. */ /** The result every per-client writer (install or remove) returns — one shape * shared across `packages/server/src/provisioning/writers/*`. */ type ClientRegistrationStatus = 'registered' | 'absent' | 'failed'; export interface ClientRegistrationResult { status: ClientRegistrationStatus; path: string; clientId: ClientId; /** Whether this call actually changed the file on disk (`false` for an * already-correct idempotent write, or a remove that found nothing to remove). */ changed: boolean; conflictReason?: RegistrationConflictReason; message?: string; /** * Structural self-check that the just-written (or already-registered) entry is * complete enough for a FRESH adapter to connect without retry. This is * deliberately NOT a live network/process handshake — the daemon may not even be * running yet at provisioning time — only a proof that the rendered entry itself * is internally consistent (see {@link assertInitializable}). */ initializeOnce(): Promise<{ ok: true; }>; } export interface WriteClientRegistrationInput { capability: ClientCapability; /** Absolute home directory every writer resolves its user-level config path from. */ homeDir: string; /** Daemon port for HTTP-style writers. Ignored by the stdio bridge (Claude Desktop). */ daemonPort: number; /** Absolute path of the CLI entrypoint the stdio bridge launches (Claude Desktop only). */ cliEntrypoint: string; /** Absolute path of the Node binary that launches the stdio bridge. Defaults to * `process.execPath`. */ execPath?: string; /** `process.platform`, overridable for Claude Desktop's macOS/Windows path split. */ platform?: string; /** Windows APPDATA root for Claude Desktop's user-level configuration. */ appData?: string; /** Test/advanced seam: overrides the real filesystem for every writer this input reaches. */ fs?: AtomicFsDeps; } /** Uniform failure mapping: an error from ANY writer (a refused conflict or an * unexpected I/O failure alike) becomes a `failed` result rather than a thrown * exception — matching the Contract's "returns failed without clobbering user * content" rather than raising past the caller. */ export declare function failedClientRegistrationResult(clientId: ClientId, path: string, err: unknown): ClientRegistrationResult; /** * Structural completeness check every writer's `initializeOnce()` delegates to. * Proves the rendered entry has whatever a fresh client adapter needs to connect — * an absolute stdio command + args for the bridge shape, or a non-empty loopback * URL for every HTTP-style shape (including codex's TOML, whose entry is a plain * `{url, ...}` record by the time it reaches this check). */ export declare function assertInitializable(entry: Record, format: McpConfigFormat): void; /** * Shared install path for every writer whose format merges as JSON (`json`, * `plugin-json`, `stdio-json`) through {@link writeOwnedRegistration}. Codex's * `toml` format cannot go through this — see `writers/codex.ts`, which reuses the * same atomic-rename/stale-check/no-static-credential primitives directly. */ export declare function installJsonClientRegistration(params: { capability: ClientCapability; path: string; entry: Record; fs?: AtomicFsDeps; }): Promise; /** Shared remove path, symmetric with {@link installJsonClientRegistration}. */ export declare function removeJsonClientRegistration(params: { capability: ClientCapability; path: string; /** Required for `stdio-json` clients — see {@link RemoveOwnedRegistrationInput}. */ expectedStdioEntrypoint?: string; fs?: AtomicFsDeps; }): Promise; export {}; //# sourceMappingURL=registration-writer.d.ts.map