tenant-tool-bridge.tsā¢13.4 kB
#!/usr/bin/env node
/**
* Tenant Tool Bridge
*
* Bridges multi-tenant sessions with server-universal tools while maintaining isolation.
* Each tenant can access server-universal tools within their isolated workspace.
*/
import { spawn, exec } from 'child_process';
import { promisify } from 'util';
import * as path from 'path';
import * as fs from 'fs/promises';
const execAsync = promisify(exec);
interface ToolExecutionContext {
sessionId: string;
workspace: string;
gitConfig: {
name?: string;
email?: string;
pat?: string;
};
sessionInfo: any;
}
interface ToolCallResult {
success: boolean;
stdout?: string;
stderr?: string;
exit_code?: number;
message?: string;
data?: any;
}
export class TenantToolBridge {
private serverUniversalPath: string;
private baseUrl: string;
constructor(private orchestrator: any) {
this.serverUniversalPath = path.resolve(process.cwd(), '../apps/local-server/src/server-universal.js');
this.baseUrl = 'http://localhost:3000/mcp';
console.log('š Tenant Tool Bridge initialized');
console.log(`š” Server Universal: ${this.serverUniversalPath}`);
console.log(`š Endpoint: ${this.baseUrl}`);
}
/**
* Execute a server-universal tool within a tenant's session context
*/
async executeTool(
sessionId: string,
toolName: string,
args: any = {}
): Promise<ToolCallResult> {
try {
// 1. Validate session exists and is active
const context = await this.getSessionContext(sessionId);
if (!context) {
return {
success: false,
message: `Session ${sessionId} not found or inactive`,
exit_code: -1
};
}
// 2. Route to appropriate tool handler
switch (toolName) {
case 'bash':
return await this.executeBashCommand(context, args.command || '');
case 'admin_local_server':
return await this.executeAdminCommand(context, args.command || '', args.adminPassword);
case 'search_local':
return await this.executeSearchCommand(context, args);
case 'get_local_book':
return await this.executeBookCommand(context, args);
case 'get_local_content':
return await this.executeContentCommand(context, args);
case 'list_local_books':
return await this.executeListBooksCommand(context, args);
case 'get_database_stats':
return await this.executeStatsCommand(context);
case 'find_egw_quotes':
return await this.executeQuotesCommand(context, args);
default:
return {
success: false,
message: `Unknown tool: ${toolName}`,
exit_code: -1
};
}
} catch (error) {
console.error(`ā Tool bridge error for ${toolName}:`, error.message);
return {
success: false,
message: `Tool execution failed: ${error.message}`,
exit_code: -1
};
}
}
/**
* Get session context for tool execution
*/
private async getSessionContext(sessionId: string): Promise<ToolExecutionContext | null> {
try {
const session = await this.orchestrator.getSession(sessionId);
if (!session || !session.isActive) {
return null;
}
return {
sessionId: session.id,
workspace: session.workspace,
gitConfig: session.gitConfig || {},
sessionInfo: session
};
} catch (error) {
console.error(`ā Failed to get session context:`, error.message);
return null;
}
}
/**
* Execute bash command within tenant workspace
*/
private async executeBashCommand(context: ToolExecutionContext, command: string): Promise<ToolCallResult> {
try {
// Set up environment for this tenant
const env = {
...process.env,
PWD: context.workspace,
GIT_WORK_TREE: context.workspace,
GIT_DIR: path.join(context.workspace, '.git'),
GIT_AUTHOR_NAME: context.gitConfig.name || 'Anonymous',
GIT_AUTHOR_EMAIL: context.gitConfig.email || 'anonymous@example.com',
GIT_COMMITTER_NAME: context.gitConfig.name || 'Anonymous',
GIT_COMMITTER_EMAIL: context.gitConfig.email || 'anonymous@example.com'
};
// Add tenant's PAT if available
if (context.gitConfig.pat) {
env.GITHUB_PAT = context.gitConfig.pat;
}
// Enhanced git command handling with tenant's PAT
let enhancedCommand = command;
if (context.gitConfig.pat && command.startsWith('git')) {
enhancedCommand = this.enhanceGitCommand(command, context.gitConfig.pat);
}
// Execute command within tenant workspace
const result = await execAsync(enhancedCommand, {
cwd: context.workspace,
env: env,
timeout: 30000,
maxBuffer: 1024 * 1024 * 10 // 10MB buffer
});
return {
success: true,
stdout: result.stdout,
stderr: result.stderr,
exit_code: 0,
message: 'Command executed successfully in tenant workspace'
};
} catch (error: any) {
return {
success: false,
stdout: error.stdout || '',
stderr: error.stderr || error.message,
exit_code: error.code || 1,
message: 'Command failed in tenant workspace'
};
}
}
/**
* Execute admin command with security validation
*/
private async executeAdminCommand(context: ToolExecutionContext, command: string, adminPassword?: string): Promise<ToolCallResult> {
try {
// Validate admin password if required
if (process.env.REQUIRE_ADMIN_PASSWORD !== 'false') {
const requiredPassword = process.env.ADMIN_PASSWORD || 'admin18401844';
if (adminPassword !== requiredPassword) {
return {
success: false,
message: 'Admin password required for system-level access',
exit_code: -1
};
}
}
// Security: Ensure command stays within tenant workspace
const safeCommand = this.validateAdminCommand(command, context.workspace);
if (!safeCommand) {
return {
success: false,
message: 'Command contains unsafe operations or attempts to access outside workspace',
exit_code: -1
};
}
const result = await execAsync(safeCommand, {
cwd: context.workspace,
timeout: 30000,
maxBuffer: 1024 * 1024 * 10
});
return {
success: true,
stdout: result.stdout,
stderr: result.stderr,
exit_code: 0,
message: 'Admin command executed successfully in tenant workspace'
};
} catch (error: any) {
return {
success: false,
stdout: error.stdout || '',
stderr: error.stderr || error.message,
exit_code: error.code || 1,
message: 'Admin command failed in tenant workspace'
};
}
}
/**
* Execute database search command
*/
private async executeSearchCommand(context: ToolExecutionContext, args: any): Promise<ToolCallResult> {
try {
// Call server-universal via HTTP API
const response = await this.callServerUniversal('search_local', args);
return {
success: true,
data: response,
message: 'Search completed successfully'
};
} catch (error: any) {
return {
success: false,
message: `Search failed: ${error.message}`,
exit_code: -1
};
}
}
/**
* Execute book command
*/
private async executeBookCommand(context: ToolExecutionContext, args: any): Promise<ToolCallResult> {
try {
const response = await this.callServerUniversal('get_local_book', args);
return {
success: true,
data: response,
message: 'Book retrieved successfully'
};
} catch (error: any) {
return {
success: false,
message: `Book retrieval failed: ${error.message}`,
exit_code: -1
};
}
}
/**
* Execute content command
*/
private async executeContentCommand(context: ToolExecutionContext, args: any): Promise<ToolCallResult> {
try {
const response = await this.callServerUniversal('get_local_content', args);
return {
success: true,
data: response,
message: 'Content retrieved successfully'
};
} catch (error: any) {
return {
success: false,
message: `Content retrieval failed: ${error.message}`,
exit_code: -1
};
}
}
/**
* Execute list books command
*/
private async executeListBooksCommand(context: ToolExecutionContext, args: any): Promise<ToolCallResult> {
try {
const response = await this.callServerUniversal('list_local_books', args);
return {
success: true,
data: response,
message: 'Books listed successfully'
};
} catch (error: any) {
return {
success: false,
message: `Book listing failed: ${error.message}`,
exit_code: -1
};
}
}
/**
* Execute stats command
*/
private async executeStatsCommand(context: ToolExecutionContext): Promise<ToolCallResult> {
try {
const response = await this.callServerUniversal('get_database_stats', {});
return {
success: true,
data: response,
message: 'Stats retrieved successfully'
};
} catch (error: any) {
return {
success: false,
message: `Stats retrieval failed: ${error.message}`,
exit_code: -1
};
}
}
/**
* Execute quotes command
*/
private async executeQuotesCommand(context: ToolExecutionContext, args: any): Promise<ToolCallResult> {
try {
const response = await this.callServerUniversal('find_egw_quotes', args);
return {
success: true,
data: response,
message: 'Quotes found successfully'
};
} catch (error: any) {
return {
success: false,
message: `Quote search failed: ${error.message}`,
exit_code: -1
};
}
}
/**
* Call server-universal via HTTP API
*/
private async callServerUniversal(tool: string, args: any): Promise<any> {
const requestBody = {
jsonrpc: '2.0',
id: Date.now(),
method: 'tools/call',
params: {
name: tool,
arguments: args
}
};
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
throw new Error(`Server-universal HTTP error: ${response.status}`);
}
const result = await response.json();
if (result.error) {
throw new Error(result.error.message);
}
return result.result;
}
/**
* Enhance git command with tenant's PAT
*/
private enhanceGitCommand(command: string, pat: string): string {
if (command.includes('git clone https://github.com/')) {
return command.replace(
'https://github.com/',
`https://${pat}@github.com/`
);
} else if (command.includes('git push')) {
return `
git remote set-url origin https://${pat}@github.com/$(git config --get remote.origin.url | sed 's/.*github\\.com\\///')
git push origin
`;
} else if (command.includes('git pull') || command.includes('git fetch')) {
return command.replace(
'origin',
`https://${pat}@github.com/$(git config --get remote.origin.url | sed 's/.*github\\.com\\///')`
);
}
return command;
}
/**
* Validate admin command for security
*/
private validateAdminCommand(command: string, workspace: string): string | null {
// Dangerous patterns to block
const dangerousPatterns = [
/rm\s+-rf\s+\//,
/rm\s+-rf\s+\.\./,
/cd\s+\.\./,
/\.\.\/\.\./,
/sudo/,
/su\s/,
/chmod\s+777/,
/chown\s/,
/passwd/,
/useradd/,
/userdel/,
/groupadd/,
/groupdel/
];
for (const pattern of dangerousPatterns) {
if (pattern.test(command)) {
return null;
}
}
// Ensure command doesn't try to escape workspace
if (command.includes('..') && !command.includes('./')) {
return null;
}
return command;
}
/**
* Get available tools for a tenant
*/
getAvailableTools(): string[] {
return [
'bash',
'admin_local_server',
'search_local',
'get_local_book',
'get_local_content',
'list_local_books',
'get_database_stats',
'find_egw_quotes'
];
}
/**
* Get bridge status and health
*/
async getBridgeStatus(): Promise<any> {
try {
// Test server-universal connectivity
const testResponse = await this.callServerUniversal('get_database_stats', {});
return {
status: 'healthy',
serverUniversalConnected: true,
availableTools: this.getAvailableTools(),
timestamp: new Date().toISOString()
};
} catch (error) {
return {
status: 'error',
serverUniversalConnected: false,
error: error.message,
availableTools: this.getAvailableTools(),
timestamp: new Date().toISOString()
};
}
}
}