UNPKG

hana-cli

Version:
346 lines (259 loc) 10.6 kB
# How the MCP Server Gets Connection Context - Current State & Solution ## Quick Summary **Current Problem**: The MCP server always uses connection files from its install path or the user's home directory, not from the project being worked on by an AI Agent. **Solution**: Pass project-specific connection context through the MCP tool parameters to the CLI execution layer. --- ## Current Flow (Simplified) ```mermaid flowchart TB A["MCP Server"] --> B["executeCommand('tables', { schema: 'MY_SCHEMA' })"] B --> C["Spawns: node bin/cli.js tables --schema MY_SCHEMA"] C --> D["CLI looks for connections:<br/>1. .cdsrc-private.json (install dir or pwd)<br/>2. default-env.json (install dir or pwd)<br/>3. ~/.hana-cli/default.json (user home)<br/>4. VCAP_SERVICES (environment)"] ``` **The problem**: Connection files are always from install path, not the project being analyzed. --- ## Key Code Locations ### 1. MCP Tool Execution Entry Point **File**: `mcp-server/src/index.ts` (line 1300+) ```typescript // Tool handler extracts command name and calls executor const result = await executeCommand(actualCommandName, args || {}); ``` Current limitation: No project context passed here. ### 2. Command Execution **File**: `mcp-server/src/executor.ts` (line 240+) ```typescript const child = spawn('node', [cliPath, ...commandArgs], { env: { ...process.env, FORCE_COLOR: '0', }, cwd: join(__dirname, '..', '..'), // Always uses install directory! }); ``` **Problem**: `cwd` (current working directory) is hardcoded to install path, environment variables are never customized for projects. ### 3. Connection Resolution **File**: `utils/database/index.js` (line 35+) ```typescript static async getNewClient(prompts) { if (!prompts.profile) { prompts.profile = 'hybrid' // Default const { default: classAccess } = await import("./hanaDirect.js") } else { process.env.CDS_ENV = prompts.profile let optionsCDS = cds.env.requires.db // Looks in cds.env // ... } } ``` **Problem**: `cds.env.requires.db` is resolved from CDS configuration in current working directory. If CWD is install path, it uses install path config. ### 4. Credential Resolution **File**: `utils/connections.js` (line 90+) ```typescript export async function getConnOptions(prompts) { // Resolution order (all relative to process.cwd()): const cdsrcPrivate = getCdsrcPrivate() // Looks in cwd const envFile = getDefaultEnv() // Looks in cwd const envFile = getFileCheckParents(...) // Searches up directory tree xsenv.loadEnv(envFile) // Loads from found file } ``` **Problem**: All path searches start from current working directory and parent directories. If CWD is MCP install path, it will never find project-specific connection files. --- ## Recommended Implementation: Context Passing ### Step 1: Define ConnectionContext Interface Create in `mcp-server/src/connection-context.ts`: ```typescript export interface ConnectionContext { projectPath?: string; // Absolute path to project connectionFile?: string; // Relative path like ".env" or absolute path host?: string; // Direct connection - host port?: number; // Direct connection - port user?: string; // Direct connection - user password?: string; // Direct connection - password (use cautiously) database?: string; // Direct connection - database name } ``` ### Step 2: Update executeCommand() in executor.ts **Current** (line 240): ```typescript export async function executeCommand( commandName: string, args: Record<string, any> = {} ): Promise<ExecutionResult & { commandName: string }> ``` **Update to**: ```typescript export async function executeCommand( commandName: string, args: Record<string, any> = {}, context?: ConnectionContext ): Promise<ExecutionResult & { commandName: string }> ``` **Add environment setup** (before spawn): ```typescript const env = { ...process.env, FORCE_COLOR: '0' }; // Apply project context to environment if (context?.projectPath) { // Set CWD to project path so connection resolution starts there cwd = context.projectPath; } if (context?.connectionFile) { // Pass connection file hint to CLI env.HANA_CLI_CONN_FILE = context.connectionFile; } // Set direct credentials if provided (use cautiously for security) if (context?.host) env.HANA_CLI_HOST = context.host; if (context?.port) env.HANA_CLI_PORT = String(context.port); if (context?.user) env.HANA_CLI_USER = context.user; if (context?.password) env.HANA_CLI_PASSWORD = context.password; if (context?.database) env.HANA_CLI_DATABASE = context.database; const child = spawn('node', [cliPath, ...commandArgs], { env, cwd, }); ``` ### Step 3: Update CLI to Recognize Context Environment Variables Modify `utils/connections.js` (line 90+): **Add this at the beginning of getConnOptions()**: ```typescript export async function getConnOptions(prompts) { base.debug(base.bundle.getText("debug.call", ["getConnOptions"])) // NEW: Check for project-specific context from MCP const projectPath = process.env.HANA_CLI_PROJECT_PATH || prompts.projectPath; const connFile = process.env.HANA_CLI_CONN_FILE; // If project path provided, use it as search origin if (projectPath) { process.chdir(projectPath); // Change to project directory } delete process.env.VCAP_SERVICES // Try direct credentials first (if passed via MCP) if (process.env.HANA_CLI_HOST) { return { hana: { host: process.env.HANA_CLI_HOST, port: parseInt(process.env.HANA_CLI_PORT || '30013'), user: process.env.HANA_CLI_USER, password: process.env.HANA_CLI_PASSWORD, database: process.env.HANA_CLI_DATABASE || 'SYSTEMDB', } }; } // Rest of existing resolution logic... ``` ### Step 4: Update MCP Tool Schema in index.ts Add to EVERY command tool (around line 14+): ```typescript tools.push({ name: `hana_${sanitizeToolName(name)}`, description: fullDescription, inputSchema: { type: 'object', properties: { ...info.schema.properties, // Existing parameters // NEW: Project context __projectContext: { type: 'object', description: 'Project-specific connection context (optional)', properties: { projectPath: { type: 'string', description: 'Absolute path to the project directory' }, connectionFile: { type: 'string', description: 'Name of connection file (e.g., ".env", "default-env.json"). Will be searched relative to projectPath.' }, host: { type: 'string', description: 'Database host' }, port: { type: 'number', description: 'Database port' }, user: { type: 'string', description: 'Database user' }, password: { type: 'string', description: 'Database password (use cautiously)' }, database: { type: 'string', description: 'Database name' } } } }, required: info.schema.required }, }); ``` ### Step 5: Extract and Pass Context in Tool Handler Update line ~1300 in index.ts: **Current**: ```typescript try { const result = await executeCommand(actualCommandName, args || {}); const formattedOutput = formatResult(result); ``` **Update to**: ```typescript try { // Extract context if provided const context = (args as any)?.__projectContext; // Remove context from args before passing to CLI const cleanArgs = { ...args }; delete cleanArgs.__projectContext; const result = await executeCommand(actualCommandName, cleanArgs, context); const formattedOutput = formatResult(result); ``` --- ## Usage Example ### AI Agent with Project Context ```typescript // Agent knows it's working on a CAP project const projectPath = "C:/Users/developer/projects/my-cap-app"; // Call any command with project context const tables = await mcp.callTool('hana_tables', { schema: 'MY_SCHEMA', __projectContext: { projectPath: projectPath, connectionFile: '.env' // Uses project's .env file } }); // Result uses the database connection defined in // C:/Users/developer/projects/my-cap-app/.env // NOT the MCP install directory's default-env.json ``` ### Without Context (Falls Back to Current Behavior) ```typescript // If no __projectContext provided, uses install path as before // Backward compatible! const tables = await mcp.callTool('hana_tables', { schema: 'MY_SCHEMA' }); ``` --- ## Files to Modify (Summary) | File | Change | Priority | | --- | --- | --- | | `mcp-server/src/executor.ts` | Add `context` parameter, build environment, set cwd | HIGH | | `mcp-server/src/index.ts` | Add `__projectContext` to tool schemas, extract in handler | HIGH | | `utils/connections.js` | Check `HANA_CLI_*` environment variables | MEDIUM | | `mcp-server/src/connection-context.ts` | New file - interface definition | HIGH | --- ## Testing Checklist - [ ] Command executes with `__projectContext` provided - [ ] Command executes without `__projectContext` (backward compat) - [ ] Project-specific .env file is used when context provided - [ ] CLI execution happens in correct project directory - [ ] Environment variables properly set for direct connection - [ ] Multiple commands can use different project contexts in same conversation - [ ] Connection files are found relative to project path, not install path --- ## Security Notes 1. **Credentials in Parameters**: Avoid passing passwords in `__projectContext`. Prefer `.env` files. 2. **Path Validation**: Always validate `projectPath` doesn't contain `..` or escape sandbox 3. **Logging**: Never log passwords or sensitive credentials 4. **Encryption**: For future credential storage, use encrypted vaults --- ## Future Enhancements 1. **Connection Registry Tool**: `hana_set_project_context` to "remember" context for session 2. **Auto-Detection**: Detect project root from file structure, find its connection files automatically 3. **Multi-Project Workflows**: Support working with connected databases across multiple projects 4. **Secure Credential Storage**: Store credentials encrypted in MCP server state --- ## Backward Compatibility This solution is **100% backward compatible**: - `__projectContext` is optional - If not provided, uses current behavior (install path connections) - Existing MCP integrations work unchanged - No breaking changes to CLI