Skip to main content
Glama
keywaysh

Keyway MCP Server

by keywaysh

keyway_inject_run

Execute commands with securely injected environment variables from Keyway secrets manager, isolating sensitive data to specific processes.

Instructions

Run a command with Keyway secrets injected as environment variables. Secrets are only available to this command.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
commandYesThe command to run (e.g., "npm", "python")
argsNoArguments to pass to the command
environmentNoEnvironment to pull secrets from (default: "development")
timeoutNoTimeout in milliseconds (default: 300000 = 5 minutes)

Implementation Reference

  • The main handler function `injectRun` implements the logic for the `keyway_inject_run` tool by pulling secrets, merging them into the environment, executing the requested command, and sanitizing the output.
    export async function injectRun(args: {
      command: string;
      args?: string[];
      environment?: string;
      timeout?: number;
    }): Promise<CallToolResult> {
      // Validate command is not empty
      if (!args.command || !args.command.trim()) {
        return {
          content: [{ type: 'text', text: 'Error: command is required' }],
          isError: true,
        };
      }
    
      const token = await getToken();
      const repository = getRepository();
      const environment = args.environment || 'development';
      const timeout = Math.min(args.timeout || DEFAULT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS); // Cap at 5 min
    
      // Pull secrets
      const content = await pullSecrets(repository, environment, token);
      const secrets = parseEnvContent(content);
    
      // Merge secrets with current environment
      const env = { ...process.env, ...secrets };
    
      // Run command with secrets injected
      const result = await new Promise<{
        exitCode: number;
        stdout: string;
        stderr: string;
        stdoutTruncated: boolean;
        stderrTruncated: boolean;
      }>((resolve, reject) => {
        const child = spawn(args.command, args.args || [], {
          cwd: process.cwd(),
          env,
          shell: false, // Prevent shell injection
          stdio: ['ignore', 'pipe', 'pipe'],
        });
    
        let stdout = '';
        let stderr = '';
        let stdoutBytes = 0;
        let stderrBytes = 0;
    
        const timer = setTimeout(() => {
          child.kill('SIGTERM');
          // Give process time to cleanup, then force kill
          setTimeout(() => child.kill('SIGKILL'), 5000);
          reject(new Error(`Command timed out after ${timeout / 1000}s`));
        }, timeout);
    
        child.stdout.on('data', (data: Buffer) => {
          // Limit memory usage by stopping accumulation after max size
          if (stdoutBytes < MAX_OUTPUT_BYTES) {
            stdout += data.toString();
            stdoutBytes += data.length;
          }
        });
    
        child.stderr.on('data', (data: Buffer) => {
          if (stderrBytes < MAX_OUTPUT_BYTES) {
            stderr += data.toString();
            stderrBytes += data.length;
          }
        });
    
        child.on('close', (code) => {
          clearTimeout(timer);
    
          // Truncate and mask output
          const stdoutResult = truncateOutput(stdout, MAX_OUTPUT_BYTES);
          const stderrResult = truncateOutput(stderr, MAX_OUTPUT_BYTES);
    
          resolve({
            exitCode: code ?? 1,
            stdout: maskSecrets(stdoutResult.text, secrets),
            stderr: maskSecrets(stderrResult.text, secrets),
            stdoutTruncated: stdoutResult.truncated || stdoutBytes >= MAX_OUTPUT_BYTES,
            stderrTruncated: stderrResult.truncated || stderrBytes >= MAX_OUTPUT_BYTES,
          });
        });
    
        child.on('error', (err) => {
          clearTimeout(timer);
          reject(new Error(`Failed to execute command: ${err.message}`));
        });
      });
    
      const response: Record<string, unknown> = {
        exitCode: result.exitCode,
        stdout: result.stdout,
        stderr: result.stderr,
        secretsInjected: Object.keys(secrets).length,
      };
    
      // Include truncation warnings if applicable
      if (result.stdoutTruncated || result.stderrTruncated) {
        response.warnings = [];
        if (result.stdoutTruncated) {
          (response.warnings as string[]).push('stdout was truncated (exceeded 1MB)');
        }
        if (result.stderrTruncated) {
          (response.warnings as string[]).push('stderr was truncated (exceeded 1MB)');
        }
      }
    
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(response, null, 2),
          },
        ],
        isError: result.exitCode !== 0,
      };
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keywaysh/keyway-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server