hana-cli
Version:
HANA Developer Command Line Interface
475 lines (366 loc) • 12.5 kB
Markdown
# Quick Implementation Guide: Project-Specific Connection Context for MCP
## Problem Statement
The MCP server always uses connection files from the install path (`~/default-env.json`, `~/.hana-cli/default.json`) instead of from the code project being analyzed by an AI Agent. This means it can't automatically use the correct database for the project context.
## Solution Overview
Pass project connection context through MCP tool parameters → MCP Server sets this as environment/working directory → CLI looks for connections in project directory first.
## Code Changes Required
### 1. Create Connection Context Interface
**File**: `mcp-server/src/connection-context.ts` (NEW FILE)
```typescript
/**
* Connection context for project-specific database connections
* Passed from AI Agent through MCP tools to the CLI
*/
export interface ConnectionContext {
/** Absolute path to project directory */
projectPath?: string;
/** Connection file name relative to projectPath (e.g., '.env', 'default-env.json') */
connectionFile?: string;
/** Direct connection - host/port/credentials for explicit connection setup */
host?: string;
port?: number;
user?: string;
password?: string;
database?: string;
}
```
### 2. Update Executor to Handle Connection Context
**File**: `mcp-server/src/executor.ts`
**Change 1**: Update function signature (line ~240)
BEFORE:
```typescript
export async function executeCommand(
commandName: string,
args: Record<string, any> = {}
): Promise<ExecutionResult & { commandName: string }>
```
AFTER:
```typescript
import { ConnectionContext } from './connection-context.js';
export async function executeCommand(
commandName: string,
args: Record<string, any> = {},
context?: ConnectionContext
): Promise<ExecutionResult & { commandName: string }>
```
**Change 2**: Update spawn call to use context (line ~260)
BEFORE:
```typescript
let stdout = '';
let stderr = '';
// Spawn the CLI process
const child = spawn('node', [cliPath, ...commandArgs], {
env: {
...process.env,
// Ensure stdio output is captured
FORCE_COLOR: '0',
},
cwd: join(__dirname, '..', '..'),
});
```
AFTER:
```typescript
let stdout = '';
let stderr = '';
// Build environment with context
const env = {
...process.env,
FORCE_COLOR: '0',
};
// Apply project context to environment
if (context?.projectPath) {
env.HANA_CLI_PROJECT_PATH = context.projectPath;
}
if (context?.connectionFile) {
env.HANA_CLI_CONN_FILE = context.connectionFile;
}
// Set direct credentials if provided
if (context?.host) {
env.HANA_CLI_HOST = context.host;
env.HANA_CLI_PORT = String(context.port || 30013);
env.HANA_CLI_USER = context.user || '';
env.HANA_CLI_PASSWORD = context.password || '';
if (context.database) {
env.HANA_CLI_DATABASE = context.database;
}
}
// Spawn the CLI process
const child = spawn('node', [cliPath, ...commandArgs], {
env,
cwd: context?.projectPath ? context.projectPath : join(__dirname, '..', '..'),
});
```
### 3. Update MCP Server Tool Handler
**File**: `mcp-server/src/index.ts`
**Change 1**: Import new interface (line 1)
```typescript
import { ConnectionContext } from './connection-context.js';
```
**Change 2**: Update tool request handler where commands are executed (line ~1325)
BEFORE:
```typescript
// Execute the command
try {
const result = await executeCommand(actualCommandName, args || {});
const formattedOutput = formatResult(result);
return {
content: [
{
type: 'text',
text: formattedOutput,
},
],
};
}
```
AFTER:
```typescript
// Execute the command
try {
// Extract connection context if provided by agent
const context = (args as any)?.__projectContext as ConnectionContext | undefined;
// Remove context from args before passing to CLI (it's not a CLI parameter)
const cleanArgs = { ...args };
delete cleanArgs.__projectContext;
const result = await executeCommand(actualCommandName, cleanArgs, context);
const formattedOutput = formatResult(result);
return {
content: [
{
type: 'text',
text: formattedOutput,
},
],
};
}
```
**Change 3**: Add `__projectContext` to command tool schemas (line ~145)
Find the tool schema building code and update each command's inputSchema:
BEFORE:
```typescript
tools.push({
name: `hana_${sanitizeToolName(name)}`,
description: fullDescription,
inputSchema: info.schema,
});
```
AFTER:
```typescript
// Extend schema with project context
const extendedSchema = {
...info.schema,
properties: {
...(info.schema.properties || {}),
__projectContext: {
type: 'object',
description: 'Project-specific connection context (optional). Used to find project-specific connection files instead of install path.',
properties: {
projectPath: {
type: 'string',
description: 'Absolute path to the project directory. Example: "C:/Users/me/projects/my-app"'
},
connectionFile: {
type: 'string',
description: 'Connection file location relative to projectPath. Example: ".env" or "default-env.json"'
},
host: {
type: 'string',
description: 'Database host (for direct connection)'
},
port: {
type: 'number',
description: 'Database port (for direct connection)'
},
user: {
type: 'string',
description: 'Database user (for direct connection)'
},
password: {
type: 'string',
description: 'Database password (for direct connection - use file-based credentials when possible)'
},
database: {
type: 'string',
description: 'Database name (for direct connection)'
}
}
}
}
};
tools.push({
name: `hana_${sanitizeToolName(name)}`,
description: fullDescription,
inputSchema: extendedSchema,
});
```
### 4. Update CLI Connection Resolution
**File**: `utils/connections.js`
**Change**: Add at the beginning of `getConnOptions()` function (line ~92)
```typescript
export async function getConnOptions(prompts) {
base.debug(base.bundle.getText("debug.call", ["getConnOptions"]))
// NEW: Check for project-specific context from MCP server
const projectPath = process.env.HANA_CLI_PROJECT_PATH;
const connFile = process.env.HANA_CLI_CONN_FILE;
// If project path provided, change to that directory so connection resolution starts there
if (projectPath && fs.existsSync(projectPath)) {
process.chdir(projectPath);
base.debug(`Using project directory for connection resolution: ${projectPath}`);
}
// NEW: Check for direct database credentials from MCP (for explicit connection setup)
if (process.env.HANA_CLI_HOST) {
const directConnection = {
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',
}
};
base.debug('Using direct database connection from MCP context');
return directConnection;
}
// Rest of existing code continues...
delete process.env.VCAP_SERVICES
// Try .cdsrc-private.json with CDS binding first
const cdsrcPrivate = prompts?.admin ? undefined : getCdsrcPrivate()
// ... rest of function
}
```
## Usage: AI Agent Perspective
### Example 1: Using Project's .env File
```javascript
// Agent knows the project path and wants to use its .env file
await mcp.callTool('hana_tables', {
schema: 'MY_SCHEMA',
__projectContext: {
projectPath: '/home/user/projects/my-cap-app',
connectionFile: '.env'
}
});
// Result: CLI changes to /home/user/projects/my-cap-app
// Then looks for .env file there (not in install path)
```
### Example 2: Explicit Database Connection
```javascript
// Agent has database credentials and wants to use them directly
await mcp.callTool('hana_import', {
file: 'data.csv',
table: 'MY_TABLE',
__projectContext: {
host: 'database.mydomain.com',
port: 30013,
user: 'DBAdmin',
password: 'MyPassword123',
database: 'SYSTEMDB'
}
});
```
### Example 3: Backward Compatible (No Context)
```javascript
// If no context provided, works as before (uses install path)
await mcp.callTool('hana_status');
// Uses default-env.json from install directory or ~/.hana-cli/
```
## Testing the Implementation
### Test 1: Basic Functionality
```bash
# Create test project with .env
mkdir -p /tmp/test-project
echo "host=myhost" > /tmp/test-project/.env
# Run MCP server and call a command with context
node mcp-server/build/index.js
# Send: hana_tables with __projectContext.projectPath="/tmp/test-project"
# Expected: CLI changes to /tmp/test-project and finds .env there
```
### Test 2: Verify Environment Variables
Add to executor.ts for debugging:
```typescript
if (context) {
console.error('[DEBUG] Applied context:', {
cwd: context.projectPath,
envVars: {
HANA_CLI_PROJECT_PATH: env.HANA_CLI_PROJECT_PATH,
HANA_CLI_CONN_FILE: env.HANA_CLI_CONN_FILE
}
});
}
```
### Test 3: Multiple Projects in One Conversation
```javascript
// First command uses Project A
await mcp.callTool('hana_tables', {
__projectContext: { projectPath: '/path/to/project-a' }
});
// Second command uses Project B (different database)
await mcp.callTool('hana_import', {
__projectContext: { projectPath: '/path/to/project-b' }
});
// Expected: Different databases for each command
```
## Validation Checklist
- [ ] `connection-context.ts` file created
- [ ] `executor.ts` updated with context parameter
- [ ] `index.ts` extracts and passes context
- [ ] Tool schemas include `__projectContext` property
- [ ] `connections.js` checks for `HANA_CLI_PROJECT_PATH` env var
- [ ] Commands work without context (backward compatible)
- [ ] Commands work with context (uses project path)
- [ ] Environment variable setting is correct
- [ ] CWD changing works properly
- [ ] No passwords logged in debug output
## Expected Behavior After Implementation
| Scenario | Before | After |
| ---------- | -------- | ------- |
| Agent calls `hana_tables` without context | Uses `~/.hana-cli/default.json` | Uses `~/.hana-cli/default.json` (same) |
| Agent calls `hana_tables` with projectPath | Uses `~/.hana-cli/default.json` (WRONG!) | Uses `/project/path/.env` (CORRECT!) |
| Agent switches projects mid-conversation | Same DB for all commands (WRONG!) | Each project uses its own DB (CORRECT!) |
| Multiple agents work different projects | Conflicts, uses wrong DB | Isolated contexts, correct DBs |
## Migration Path
1. **Deploy Phase 1**: Add interfaces and update executor, index (2-3 hours)
2. **Deploy Phase 2**: Update CLI connection logic (1 hour)
3. **Test**: Manual testing with different project structures (2 hours)
4. **Release**: No breaking changes, backward compatible
5. **Documentation**: Update README with examples (1 hour)
## Security Considerations
1. **Password in Parameters**: Only use `password` in `__projectContext` for automation/secure systems. Prefer `.env` files.
2. **Path Validation**: Consider validating `projectPath` to prevent directory traversal attacks:
```typescript
if (context?.projectPath) {
const normalized = path.resolve(context.projectPath);
// Optional: Check against whitelist of allowed paths
}
```
3. **Logging**: Never log passwords:
```typescript
// WRONG:
base.debug(context); // Would log password!
// RIGHT:
const safeContext = { ...context };
delete safeContext.password;
base.debug(safeContext);
```
4. **Environment Cleanup**: Consider clearing sensitive env vars after command execution
## Rollback Plan
If issues arise:
1. Remove `__projectContext` handling from `index.ts` tool handler
2. Remove context parameter from `executeCommand()` call
3. Remove env variable checks from `connections.js`
4. Server reverts to install-path-only behavior
No persistent state to clean up - fully reversible.
## See Also
- [Connection Context Readme](./connection-context-readme.md)
- [Architecture](./architecture.md)
- [Server Usage](./server-usage.md)