Skip to content

Add optional MCP tool formatter (closes #131)#132

Draft
danielnachumdev wants to merge 1 commit into
infragate:developfrom
danielnachumdev:feature/mcp-tool-formatter
Draft

Add optional MCP tool formatter (closes #131)#132
danielnachumdev wants to merge 1 commit into
infragate:developfrom
danielnachumdev:feature/mcp-tool-formatter

Conversation

@danielnachumdev

Copy link
Copy Markdown

Summary

  • Add optional formatter block to MCP tool definitions (def.formatter.cmd, optional timeout) so serialized tool output can be piped through operator-defined shell commands at the CAPA gateway before returning to clients
  • Apply formatting in all MCP tool-call paths; on failure or timeout, fall back to the original output unchanged
  • Add capa sh --json to skip formatting and return raw JSON (uses internal _capa_json meta-arg, stripped server-side)
  • Fix flaky installer — url installs test that hung on slow DNS by using a local server returning 503

Example

tools:
  - id: query
    type: mcp
    def:
      server: "@db"
      tool: run_query
      formatter:
        cmd: "jq -r '.rows[] | [.id, .name] | @tsv'"

Verified

  • bun test src/server/__tests__/tool-formatter.test.ts — 12 passed
  • bun test src/cli/commands/__tests__/sh.test.ts — 41 passed
  • bun test — 1125 passed
  • bun run build — succeeded (dist/capa)

Made with Cursor

Lets MCP tools pipe serialized JSON through an operator-defined shell
command at the gateway, with capa sh --json to bypass formatting. Also
fixes a flaky registry installer test that hung on slow DNS resolution.

Closes infragate#131

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional, operator-configurable post-processing (“formatter”) step for MCP tool outputs at the CAPA gateway, with a CLI escape hatch to request raw JSON output.

Changes:

  • Extend MCP tool definitions with optional def.formatter (cmd, optional timeout) and document the schema.
  • Apply formatter handling across MCP tool-call paths and add capa sh --json to bypass formatting via an internal meta-arg.
  • Stabilize a previously flaky URL-installer test by using a local HTTP server that returns 503.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/types/capabilities.ts Adds ToolFormatterDefinition and wires formatter into ToolMCPDefinition.
src/shared/registries/tests/installer.test.ts Replaces DNS-dependent failure test with local 503 server for determinism.
src/server/tool-formatter.ts Introduces formatter plumbing: meta-arg stripping, result serialization, and shell formatter execution.
src/server/mcp-handler.ts Routes tool-call outputs through the new formatter logic and strips the CLI meta-arg from forwarded args.
src/server/tests/tool-formatter.test.ts Adds unit tests for meta stripping, serialization, formatter application, and timeout/failure behavior.
src/cli/commands/sh.ts Adds --json mode and forwards the skip-formatter meta-arg for tool calls.
src/cli/commands/tests/sh.test.ts Adds tests for parseShellGlobalFlags() and --json parsing behavior.
skills/capabilities-manager/references/capabilities-schema.md Documents the new MCP formatter block and the capa sh --json bypass behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +97 to +113
const timeout = formatter.timeout ?? DEFAULT_FORMATTER_TIMEOUT_MS;
const isWindows = process.platform === 'win32';
const shell = isWindows ? 'cmd.exe' : '/bin/sh';
const shellFlag = isWindows ? '/C' : '-c';

return new Promise((resolve) => {
let settled = false;
const finish = (value: string) => {
if (settled) return;
settled = true;
resolve(value);
};

const proc = spawn(shell, [shellFlag, formatter.cmd], {
stdio: ['pipe', 'pipe', 'pipe'],
windowsHide: true,
});
Comment on lines +82 to +87
it('returns transformed stdout on success', async () => {
const out = await applyToolFormatter('{"name":"alice"}', {
cmd: "jq -r '.name'",
});
expect(out).toBe('alice');
});
Comment thread src/cli/commands/sh.ts
import type { Capabilities } from '../../types/capabilities';
import { getQualifiedToolName } from '../../types/capabilities';
import { slugify } from '../../shared/slug';
import { CAPA_JSON_ARG } from '../../server/tool-formatter';
@Minitour Minitour self-assigned this Jul 9, 2026
@Minitour

Minitour commented Jul 9, 2026

Copy link
Copy Markdown
Member

Thanks for the PR @danielnachumdev!

Can you change the target branch to develop? We use the develop branch to stage several features/changes before merging to main.

Besides that, please address the Copilot comments.
I will do a thorough review locally and come back with additional comments if I see anything.

@Minitour Minitour changed the base branch from main to develop July 12, 2026 20:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants