Skip to main content
Glama

gates

List, fetch, or execute reusable workflow gates for regression, confidence checks, and stage-specific actions across projects.

Instructions

List, fetch, or execute reusable workflow gates (regression, confidence, etc).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYes
idNoFor action=get or action=run
stageNoFor action=for-stage
projectRootNoFor action=run
autoSpawnNoOn action=run: auto-start the sidecar UI (kit ui) if not running and stream progress to it.

Implementation Reference

  • Input schema for the 'gates' MCP tool, defining actions: list, get, for-stage, run, with corresponding parameters.
    {
      name: 'gates',
      description: 'List, fetch, or execute reusable workflow gates (regression, confidence, etc).',
      inputSchema: {
        type: 'object',
        properties: {
          action:      { type: 'string', enum: ['list', 'get', 'for-stage', 'run'] },
          id:          { type: 'string', description: 'For action=get or action=run' },
          stage:       { type: 'string', enum: ['pre-plan', 'pre-execute', 'pre-verify', 'post-verify', 'any'], description: 'For action=for-stage' },
          projectRoot: { type: 'string', description: 'For action=run' },
          autoSpawn:   { type: 'boolean', description: 'On action=run: auto-start the sidecar UI (kit ui) if not running and stream progress to it.' },
        },
        required: ['action'],
      },
    },
  • Registration of the 'gates' tool handler in the HANDLERS map, which is dispatched by the MCP CallToolRequestSchema handler.
    const HANDLERS = {
      kit:           handleKit,
      sync:          handleSync,
      'reverse-sync':handleReverseSync,
      gates:         handleGates,
      forensics:     handleForensics,
      install:       handleInstall,
    };
  • handleGates — the MCP tool handler that dispatches actions: list (listGates), get (getGate), for-stage (gatesForStage), run (runGate).
    async function handleGates(args) {
      switch (args.action) {
        case 'list':      return listGates();
        case 'get':       return getGate(args.id);
        case 'for-stage': return gatesForStage(args.stage);
        case 'run':
          return withAutoSpawn(args, 'gates.run', () =>
            runGate(args.id, {
              projectRoot: args.projectRoot,
              yes: true,            // MCP context: never prompt
              interactive: false,   // MCP never prompts
            }));
        default: return { error: `Unknown action: ${args.action}` };
      }
    }
  • Core gate functions: listGates (reads gates/*.md files with frontmatter), getGate (fetches a single gate by ID), gatesForStage (filters gates by stage).
    export async function listGates(gatesRoot = DEFAULT_GATES_ROOT) {
      let entries;
      try { entries = await fs.readdir(gatesRoot, { withFileTypes: true }); }
      catch { return []; }
      const out = [];
      for (const e of entries) {
        if (!e.isFile() || !e.name.endsWith('.md')) continue;
        const abs = path.join(gatesRoot, e.name);
        const raw = await fs.readFile(abs, 'utf8');
        const meta = parseFrontmatter(raw);
        out.push({
          id: meta.id ?? e.name.replace(/\.md$/, ''),
          stage: meta.stage ?? 'any',
          blocking: meta.blocking !== false && meta.blocking !== 'false',
          description: meta.description ?? '',
          absPath: abs,
        });
      }
      return out.sort((a, b) => a.id.localeCompare(b.id));
    }
    
    export async function getGate(id, gatesRoot = DEFAULT_GATES_ROOT) {
      const all = await listGates(gatesRoot);
      const g = all.find(x => x.id === id);
      if (!g) throw new Error(`Unknown gate: ${id}. Available: ${all.map(x => x.id).join(', ')}`);
      const raw = await fs.readFile(g.absPath, 'utf8');
      return { ...g, content: raw };
    }
    
    export async function gatesForStage(stage, gatesRoot = DEFAULT_GATES_ROOT) {
      const all = await listGates(gatesRoot);
      return all.filter(g => g.stage === stage || g.stage === 'any');
    }
    
    function parseFrontmatter(raw) {
      const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
      if (!m) return {};
      const out = {};
      for (const line of m[1].split(/\r?\n/)) {
        const mm = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
        if (mm) out[mm[1]] = mm[2].trim();
      }
      return out;
    }
  • runGate — executes a gate either as a shell script (extracts ## Check section code blocks) or manual mode, returning a structured verdict.
    export async function runGate(id, opts = {}) {
      const projectRoot = path.resolve(opts.projectRoot ?? process.cwd());
      const yes         = !!opts.yes;
      const onLog       = opts.onLog ?? ((s) => stderr.write(s + '\n'));
      const interactive = opts.interactive !== false && !yes;
    
      const gate = await getGate(id, opts.gatesRoot);
      const parsed = parseGateBody(gate.content);
    
      onLog('');
      onLog(`Gate: ${gate.id}  [stage=${gate.stage}, blocking=${gate.blocking}]`);
      if (gate.description) onLog(`Description: ${gate.description}`);
      onLog('');
    
      if (parsed.shellBlocks.length > 0) {
        return runShellGate(gate, parsed, { projectRoot, yes, interactive, onLog });
      }
      return runManualGate(gate, parsed, { projectRoot, interactive, onLog });
    }
    
    // --- shell-mode gates ---
    
    async function runShellGate(gate, parsed, { projectRoot, yes, interactive, onLog }) {
      const script = parsed.shellBlocks.join('\n\n');
    
      onLog(`Will execute (cwd=${projectRoot}):`);
      onLog('─────');
      onLog(script);
      onLog('─────');
    
      let proceed = yes;
      if (interactive && !yes) {
        proceed = await ask('execute? [y/N] ');
      }
      if (!proceed) {
        return { id: gate.id, verdict: 'skipped', blocking: gate.blocking, reason: 'user declined or non-interactive without --yes' };
      }
    
      const { exitCode, stdout, stderr: errOut } = await execScript(script, projectRoot);
      const verdict = mapVerdict(exitCode, gate);
      onLog(`exit=${exitCode}  →  verdict=${verdict}`);
    
      return {
        id: gate.id,
        verdict,
        blocking: gate.blocking,
        exitCode,
        stdout: trim(stdout),
        stderr: trim(errOut),
      };
    }
    
    // --- manual-mode gates ---
    
    async function runManualGate(gate, parsed, { projectRoot, interactive, onLog }) {
      onLog('This gate has no executable check. Body:');
      onLog('─────');
      onLog(parsed.body.trim());
      onLog('─────');
    
      if (!interactive) {
        return { id: gate.id, verdict: 'manual', blocking: gate.blocking, reason: 'manual gate; no auto-decision in non-interactive mode' };
      }
    
      const choice = await askChoice('verdict? [p]assed / [w]arn / [b]lock / [s]kip: ', {
        p: 'passed', w: 'warn', b: 'block', s: 'skipped',
      });
    
      return { id: gate.id, verdict: choice, blocking: gate.blocking };
    }
    
    // --- parsing ---
    
    function parseGateBody(content) {
      const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
      const checkSection = extractSection(body, 'Check');
      const shellBlocks = extractCodeBlocks(checkSection || '');
      return { body, checkSection, shellBlocks };
    }
    
    function extractSection(body, heading) {
      // Line-by-line: find `## Heading`, capture everything until the next `## ` or EOF.
      // Plain regex with `\Z` doesn't exist in JS, and `(?=^##|$)` is awkward — easier this way.
      const lines = body.split(/\r?\n/);
      const startRe = new RegExp(`^##\\s+${heading}\\s*$`, 'i');
      let start = -1, end = lines.length;
      for (let i = 0; i < lines.length; i++) {
        if (startRe.test(lines[i])) { start = i + 1; break; }
      }
      if (start === -1) return null;
      for (let i = start; i < lines.length; i++) {
        if (/^##\s+/.test(lines[i])) { end = i; break; }
      }
      return lines.slice(start, end).join('\n').trim();
    }
    
    function extractCodeBlocks(text) {
      const out = [];
      const re = /```(?:bash|sh|shell)?\s*\n([\s\S]*?)```/g;
      let m;
      while ((m = re.exec(text)) !== null) {
        const code = m[1].trim();
        if (code) out.push(code);
      }
      return out;
    }
    
    // --- exec ---
    
    async function execScript(script, cwd) {
      // Write to a temp file and run with bash. We don't try to inline -c because
      // the scripts can be multiline and contain quoting we'd have to escape.
      const tmp = path.join(os.tmpdir(), `kit-gate-${Date.now()}-${Math.random().toString(36).slice(2)}.sh`);
      await fs.writeFile(tmp, script, { encoding: 'utf8', mode: 0o700 });
      try {
        const child = spawn('bash', [tmp], { cwd, env: process.env });
        const stdout = [], stderrOut = [];
        child.stdout.on('data', (b) => stdout.push(b));
        child.stderr.on('data', (b) => stderrOut.push(b));
        const exitCode = await new Promise((resolve, reject) => {
          child.on('error', (e) => reject(new Error(`failed to spawn bash: ${e.message}. Install Git Bash or WSL on Windows.`)));
          child.on('close', resolve);
        });
        return {
          exitCode: exitCode ?? -1,
          stdout: Buffer.concat(stdout).toString('utf8'),
          stderr: Buffer.concat(stderrOut).toString('utf8'),
        };
      } finally {
        await fs.unlink(tmp).catch(() => {});
      }
    }
    
    // --- verdict mapping ---
    
    function mapVerdict(exitCode, gate) {
      if (exitCode === 0) return 'passed';
      return gate.blocking ? 'block' : 'warn';
    }
    
    // --- prompts ---
    
    async function ask(question) {
      const rl = createInterface({ input, output });
      try {
        const a = (await rl.question(question)).trim().toLowerCase();
        return a === 'y' || a === 'yes';
      } finally {
        rl.close();
      }
    }
    
    async function askChoice(question, mapping) {
      const rl = createInterface({ input, output });
      try {
        while (true) {
          const a = (await rl.question(question)).trim().toLowerCase();
          if (mapping[a]) return mapping[a];
          output.write(`unknown choice "${a}". try one of: ${Object.keys(mapping).join(', ')}\n`);
        }
      } finally {
        rl.close();
      }
    }
    
    function trim(s) {
      if (!s) return s;
      return s.length > 4000 ? s.slice(0, 4000) + `\n…(truncated, ${s.length} bytes total)` : s;
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must convey behavioral traits. It states 'list, fetch, or execute' but does not disclose side effects of execution, permissions required, or what happens when a gate is run. The autoSpawn parameter behavior is not described.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that conveys the tool's purpose efficiently, with no wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 5 parameters, no output schema, and no annotations, the description is too brief to fully explain the tool's behavior. It omits details about what 'for-stage' means, what happens during execution, and the role of the sidecar UI.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 80%, with most parameters already described. The description adds no new semantic meaning beyond listing the actions, so it meets the baseline for a high-coverage schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool handles gates with verbs list, fetch, and execute, and specifies the resource as reusable workflow gates. It distinguishes from siblings like forensics or kit, which are unrelated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage via the listed actions (list, fetch, execute) but provides no explicit guidance on when to use each action or when to avoid this tool. No alternatives or exclusions are mentioned.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/luanpdd/kit-mcp'

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