hana-cli
Version:
HANA Developer Command Line Interface
346 lines (259 loc) • 10.6 kB
Markdown
# 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