Write Database Connection Env
setup_write_envWrites license, server settings, and database credentials into a config file, preserving existing parameters and comments. Use for initial credential setup or to update all core connection fields at once.
Instructions
Write license, server settings, and database credentials into config/db-connection.env using a partial-merge strategy that preserves all other parameters and comments in the file.
USE WHEN:
The user provides a complete set of license + database credentials in one go
The user is doing initial credential setup (first time filling in the empty template)
You need to fill in or update ALL core connection fields together (license, server, database) — even if some values are already set, this tool will overwrite them consistently
The user says things like "isi credentials", "setup koneksi database", "fill in the connection info", "atur license dan database"
DO NOT USE FOR:
Generating the initial config skeleton -> use 'setup_init_config'
Changing only one or two fields (e.g. just the password, or toggle a flag) -> use 'setup_update_env'
Validating license/connection -> use 'setup_validate_config'
Behavior: read existing file, update LICENSE/SERVER_/DB_ entries in place, append any missing keys at the bottom, and write back. Comments, blank lines, and unrelated parameters are preserved verbatim. Output file: /config/db-connection.env.
PRESENTATION GUIDANCE:
Match the user's language. If the user writes in Indonesian, respond in Indonesian.
Never mention internal tool names in the reply to the user. Describe actions by what they do (e.g. "set up the initial config", "update a single value", "validate the connection").
Speak in plain language. Summarise what was written; do not paste the full field list unless the user explicitly asks.
Do not echo sensitive values (license keys, passwords) into chat even when they appear masked in the response. Confirm only that they were set.
When a precondition is not met (e.g. config file is missing), frame it as a question or next-step suggestion rather than an error.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| cwd | Yes | Absolute path of the project folder | |
| license | Yes | ||
| serverAddress | No | 127.0.0.1 | |
| serverPort | No | ||
| dbType | Yes | ||
| dbHost | Yes | ||
| dbPort | Yes | ||
| dbUser | Yes | ||
| dbPassword | Yes | ||
| dbName | Yes |
Implementation Reference
- src/tools/setup/write-env.ts:12-183 (handler)The full handler function for 'setup_write_env' tool. Registers the tool with McpServer, defines the input schema, and implements the logic: reads the existing db-connection.env file, merges license/server/database credentials using a partial-merge strategy, writes back the result, and returns a success/failure response.
export function registerSetupWriteEnv(server: McpServer): void { server.registerTool( 'setup_write_env', { title: 'Write Database Connection Env', description: `Write license, server settings, and database credentials into config/db-connection.env using a partial-merge strategy that preserves all other parameters and comments in the file. USE WHEN: - The user provides a complete set of license + database credentials in one go - The user is doing initial credential setup (first time filling in the empty template) - You need to fill in or update ALL core connection fields together (license, server, database) — even if some values are already set, this tool will overwrite them consistently - The user says things like "isi credentials", "setup koneksi database", "fill in the connection info", "atur license dan database" DO NOT USE FOR: - Generating the initial config skeleton -> use 'setup_init_config' - Changing only one or two fields (e.g. just the password, or toggle a flag) -> use 'setup_update_env' - Validating license/connection -> use 'setup_validate_config' Behavior: read existing file, update LICENSE/SERVER_*/DB_* entries in place, append any missing keys at the bottom, and write back. Comments, blank lines, and unrelated parameters are preserved verbatim. Output file: <cwd>/config/db-connection.env. PRESENTATION GUIDANCE: - Match the user's language. If the user writes in Indonesian, respond in Indonesian. - Never mention internal tool names in the reply to the user. Describe actions by what they do (e.g. "set up the initial config", "update a single value", "validate the connection"). - Speak in plain language. Summarise what was written; do not paste the full field list unless the user explicitly asks. - Do not echo sensitive values (license keys, passwords) into chat even when they appear masked in the response. Confirm only that they were set. - When a precondition is not met (e.g. config file is missing), frame it as a question or next-step suggestion rather than an error.`, inputSchema: { cwd: z.string().min(1).describe('Absolute path of the project folder'), license: z .string() .regex(/^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/, 'Format must be XXXX-XXXX-XXXX-XXXX'), serverAddress: z.string().default('127.0.0.1'), serverPort: z.number().int().min(1).max(65535).default(3000), dbType: z.enum(['postgresql', 'mysql', 'oracle', 'sqlite']), dbHost: z.string().min(1), dbPort: z.number().int().min(1).max(65535), dbUser: z.string().min(1), dbPassword: z.string(), dbName: z.string().min(1), }, annotations: { title: 'Write Env Config', readOnlyHint: false, idempotentHint: true, }, }, async (args) => { const projectCwd = resolve(args.cwd); const configDir = join(projectCwd, 'config'); const envPath = join(configDir, 'db-connection.env'); // Precondition check: the config file must exist before it can be written. // Treated as a non-error precondition per the authoring guide §3.4. try { await access(envPath); } catch { return { content: [ { type: 'text', text: `Precondition not met: the configuration file does not exist yet. Project path: ${projectCwd} Expected file: ${envPath} For the assistant: - The user is trying to write credentials into a configuration that has not been created yet. - Suggest generating the initial RESTForge configuration first, then retry filling in the credentials. - When explaining to the user, say something like "the configuration file isn't there yet — should I set up the initial config first?". Do not mention internal tool names.`, }, ], isError: false, }; } // Real I/O failure on read: surface as error per §3.4. let existingContent: string; try { existingContent = await readFile(envPath, 'utf-8'); } catch (error: unknown) { const msg = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Failed to read the configuration file before applying the credentials. Project path: ${projectCwd} File: ${envPath} Reason: ${msg} For the assistant: - Tell the user that the configuration file exists but could not be read. - Summarise the likely cause (permissions, file lock, encoding) in plain language; do not paste the raw error unless the user explicitly asks. - Offer to retry once the underlying issue is resolved. Do not mention internal tool names.`, }, ], isError: true, }; } const fields: Record<string, EnvFieldValue> = { LICENSE: args.license, SERVER_ADDRESS: args.serverAddress, SERVER_PORT: args.serverPort, DB_TYPE: args.dbType, DB_HOST: args.dbHost, DB_PORT: args.dbPort, DB_USER: args.dbUser, DB_PASSWORD: args.dbPassword, DB_NAME: args.dbName, }; const existingEntries = parseEnvFile(existingContent); const merged = mergeEnvEntries(existingEntries, fields); const newContent = serializeEnvFile(merged.entries); // Real I/O failure on write: surface as error per §3.4. try { await writeFile(envPath, newContent, 'utf-8'); } catch (error: unknown) { const msg = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Failed to write the credentials to the configuration file. Project path: ${projectCwd} File: ${envPath} Reason: ${msg} For the assistant: - Tell the user that the credentials could not be saved. - Summarise the likely cause (permissions, disk full, file lock) in plain language; do not paste the raw error unless the user explicitly asks. - The original file is unchanged. Offer to retry once the underlying issue is resolved. Do not mention internal tool names.`, }, ], isError: true, }; } // Success: labeled facts + masked summary per §3.5. const updatedKeys = merged.updated.map((u) => u.key); const addedKeys = merged.added.map((a) => a.key); const unchangedCount = merged.unchanged.length; const text = `Credentials and connection settings written successfully. Project path: ${projectCwd} File: ${envPath} License: ${args.license.slice(0, 4)}-****-****-**** Server: ${args.serverAddress}:${args.serverPort} Database: ${args.dbType}://${args.dbUser}:***@${args.dbHost}:${args.dbPort}/${args.dbName} Updated keys (${updatedKeys.length}): ${updatedKeys.join(', ') || '(none)'} Added keys (${addedKeys.length}): ${addedKeys.join(', ') || '(none)'} Unchanged keys (${unchangedCount}): preserved (Live Sync, Redis, Kafka, Logging, etc.) For the assistant: - Confirm to the user that the license and database credentials were saved. - Summarise in plain language: which database is configured (type, host, name) and that the license is set; do not echo the literal license key or password. - Do not paste the masked summary block verbatim unless the user explicitly asks for the details. - Suggest validating the configuration as the next step (license check + database connectivity). Do not mention internal tool names.`; return { content: [{ type: 'text', text }] }; } ); } - src/tools/setup/write-env.ts:42-54 (schema)Input schema for setup_write_env: validates cwd (string), license (XXXX-XXXX-XXXX-XXXX format), serverAddress (default 127.0.0.1), serverPort (1-65535, default 3000), dbType (postgresql/mysql/oracle/sqlite), dbHost, dbPort, dbUser, dbPassword, dbName.
cwd: z.string().min(1).describe('Absolute path of the project folder'), license: z .string() .regex(/^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/, 'Format must be XXXX-XXXX-XXXX-XXXX'), serverAddress: z.string().default('127.0.0.1'), serverPort: z.number().int().min(1).max(65535).default(3000), dbType: z.enum(['postgresql', 'mysql', 'oracle', 'sqlite']), dbHost: z.string().min(1), dbPort: z.number().int().min(1).max(65535), dbUser: z.string().min(1), dbPassword: z.string(), dbName: z.string().min(1), }, - src/tools/setup/index.ts:12-22 (registration)Registration entry point. registerSetupTools calls registerSetupWriteEnv(server) to register all setup tools including setup_write_env.
export function registerSetupTools(server: McpServer): void { registerSetupCreateFolder(server); registerSetupInstallPackage(server); registerSetupInitConfig(server); registerSetupWriteEnv(server); registerSetupReadEnv(server); registerSetupUpdateEnv(server); registerSetupValidateConfig(server); registerSetupGetConfigSchema(server); registerSetupGetInitTemplate(server); } - src/lib/env-parser.ts:129-161 (helper)mergeEnvEntries: helper that performs the partial-merge strategy used by setup_write_env. Takes existing env entries and updates, returns merged entries with tracking of updated/added/unchanged keys.
export function mergeEnvEntries( existing: EnvEntry[], updates: Record<string, EnvFieldValue> ): MergeResult { const updateKeys = new Set(Object.keys(updates)); const updated: MergeResult['updated'] = []; const added: MergeResult['added'] = []; const unchanged: string[] = []; const next: EnvEntry[] = existing.map((entry) => { if (entry.kind !== 'kv') return entry; if (!updateKeys.has(entry.key)) { unchanged.push(entry.key); return entry; } const newValue = normalizeFieldValue(updates[entry.key]); updateKeys.delete(entry.key); if (entry.value === newValue) { unchanged.push(entry.key); return entry; } updated.push({ key: entry.key, before: entry.value, after: newValue }); return { ...entry, value: newValue }; }); for (const remainingKey of updateKeys) { const value = normalizeFieldValue(updates[remainingKey]); next.push({ kind: 'kv', key: remainingKey, value }); added.push({ key: remainingKey, value }); } return { entries: next, updated, added, unchanged }; } - src/lib/env-parser.ts:50-82 (helper)parseEnvFile: helper that parses an env file into structured entries (kv pairs, comments, blank lines), used by setup_write_env to read the existing config file before merging.
export function parseEnvFile(content: string): EnvEntry[] { const lines = content.split(/\r?\n/); const entries: EnvEntry[] = []; const lastIsTrailingNewline = lines.length > 0 && lines[lines.length - 1] === ''; const effectiveLines = lastIsTrailingNewline ? lines.slice(0, -1) : lines; for (const rawLine of effectiveLines) { const trimmed = rawLine.trim(); if (trimmed === '') { entries.push({ kind: 'blank' }); continue; } if (trimmed.startsWith('#')) { entries.push({ kind: 'comment', text: rawLine }); continue; } const match = KV_REGEX.exec(rawLine); if (!match) { entries.push({ kind: 'comment', text: rawLine }); continue; } const key = match[1]; const { value: rawValue, comment } = splitInlineComment(match[2]); const value = stripQuotes(rawValue); const entry: EnvEntry = { kind: 'kv', key, value }; if (comment !== undefined) entry.commentInline = comment; entries.push(entry); } return entries; }