Skip to content

Conversation

@adam91holt
Copy link

Summary

Implements config-driven result mapping to project large JSON payloads down to specific fields while preserving the full MCP envelope in .raw.

Motivation

Some MCP tools return very large JSON objects when you only care about a few fields. This PR adds per-tool projection configuration in config/mcporter.json so that CallResult.json() only returns the fields you care about.

Features

  • Config-based projection: Define resultMapping per server/tool with pick[] arrays of JSON paths
  • Nested field support: Use dot-notation ("customer.name", "metadata.stats.views") with full structure preservation
  • Array handling: Automatically applies projection to each element when root result is an array
  • Runtime integration: Runtime.callTool() applies mapping automatically before returning results
  • CLI support: mcporter call --output json respects mappings; --output raw provides escape hatch

Example Configuration

{
  "mcpServers": {
    "linear": {
      "baseUrl": "https://mcp.linear.app/mcp",
      "resultMapping": {
        "list_documents": {
          "pick": ["id", "title", "url"]
        },
        "search_documentation": {
          "pick": ["id", "title", "url", "metadata.author", "metadata.stats.views"]
        }
      }
    }
  }
}

Example Usage

Before (full response):

[
  {
    "id": "doc-1",
    "title": "Getting Started",
    "url": "https://...",
    "content": "Very long content...",
    "metadata": {
      "author": "Alice",
      "tags": ["tutorial", "beginner"],
      "stats": { "views": 1234, "likes": 56 }
    }
  }
]

After (with mapping):

[
  {
    "id": "doc-1",
    "title": "Getting Started",
    "url": "https://..."
  }
]

Implementation Details

Config Schema (src/config-schema.ts)

  • Added ToolResultMappingSchema with pick field
  • Extended ServerDefinition interface with optional resultMapping

CallResult Enhancement (src/result-utils.ts)

  • Added pick<J>(paths: string | readonly string[]): J | null - Project JSON to subset of fields
  • Added withJsonOverride<J>(nextJson: J): CallResult<T> - Return new CallResult with overridden JSON
  • Extended createCallResult() to accept optional jsonOverride parameter
  • Nested paths preserve structure: ["id", "metadata.author"] produces { id: "...", metadata: { author: "..." } }

Runtime Integration (src/runtime.ts)

  • Updated callTool() to lookup result mapping configuration
  • Automatically applies projection when mapping is configured
  • Added private lookupResultMapping() helper method

Documentation

  • Added "Config-based result mapping" section to README
  • Updated config reference example with resultMapping
  • Updated CLI reference to explain --output json vs --output raw behavior

Tests

Added comprehensive test coverage in tests/result-utils.test.ts:

  • ✅ 13 new tests covering all functionality
  • ✅ Top-level and nested field projection
  • ✅ Array and single object handling
  • ✅ Structure preservation for nested paths
  • ✅ Edge cases (null, missing fields, empty arrays)
  • ✅ Override behavior

Test Results:

✓ Test Files  88 passed (89 total)
✓ Tests      321 passed (323 total)

Breaking Changes

None - this is a purely additive feature. All existing functionality remains unchanged.

Checklist

  • Implementation complete
  • Tests added and passing
  • Documentation updated (README + CLI reference)
  • TypeScript compilation successful
  • All existing tests passing
  • No breaking changes

🤖 Generated with Claude Code

@adam91holt
Copy link
Author

@steipete i haven't tested this yet. I'm just on my phone and Claude got a bit excited 😝 will test this soon and give you an update

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

src/runtime.ts Outdated
Comment on lines 205 to 208
// Create CallResult, apply projection, and return the result with overridden json()
const base = createCallResult(rawResult);
const projected = base.pick(mapping.pick);
return base.withJsonOverride(projected).raw;

Choose a reason for hiding this comment

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

P1 Badge Honor mapped results when returning callTool output

The new result-mapping branch in callTool builds a projected payload, but it returns base.withJsonOverride(projected).raw, which is just the original MCP envelope without the override. As a result, any configured resultMapping.pick is silently ignored—CLI calls with --output json or daemon clients still receive the full, unprojected tool response. Returning the projected data (or a CallResult that surfaces the override) is required for mappings to take effect.

Useful? React with 👍 / 👎.

Add per-tool result mapping configuration to project JSON payloads to
specific fields while preserving the full MCP envelope in `.raw`.

- **Config-based projection**: Define `resultMapping` per server/tool
  in `config/mcporter.json` with `pick[]` arrays of JSON paths
- **Nested field support**: Use dot-notation (`"customer.name"`,
  `"metadata.stats.views"`) with full structure preservation
- **Array handling**: Automatically applies projection to each element
  when root result is an array
- **Runtime integration**: `Runtime.callTool()` applies mapping
  automatically before returning results
- **CLI support**: `mcporter call --output json` respects mappings;
  `--output raw` provides escape hatch

- Added `ToolResultMapping` type to config schema
- Extended `CallResult` interface with:
  - `pick<J>(paths: string | readonly string[]): J | null`
  - `withJsonOverride<J>(nextJson: J): CallResult<T>`
- Updated `createCallResult()` to support optional `jsonOverride`
- Modified `Runtime.callTool()` to lookup and apply mappings
- Added comprehensive test coverage (13 new tests)

```jsonc
{
  "mcpServers": {
    "linear": {
      "baseUrl": "https://mcp.linear.app/mcp",
      "resultMapping": {
        "list_documents": {
          "pick": ["id", "title", "url"]
        }
      }
    }
  }
}
```

Closes: Feature request for result projection
Breaking: None - purely additive feature
Tests: All 321 tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@adam91holt adam91holt force-pushed the feat/config-driven-result-mapping branch from 7f3180d to d42a78c Compare November 18, 2025 05:15
The previous implementation created a projected CallResult but returned
`.raw`, which just gave back the original MCP envelope without the
projection. This meant configured resultMapping.pick was silently ignored.

Now we properly modify the MCP envelope's content[].json field to contain
the projected data, ensuring mappings take effect for CLI calls, daemon
clients, and proxy usage.

## Changes

- Modified Runtime.callTool() to update the raw MCP envelope with
  projected data instead of just creating an override that's never used
- Added 4 integration tests verifying:
  - Result mapping is applied when configured
  - Unprojected results when no mapping
  - Array result handling
  - Nested structure preservation

## Test Results

✓ All 339 tests passing (335 existing + 4 new)
✓ TypeScript builds successfully

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@steipete
Copy link
Owner

Hi adam! Intersting addition, thank you! A bit worried about complexity.
And if we build this we'll need to enable it for call and cli mode for consistency.

What's your concrete pain?

@steipete steipete marked this pull request as draft November 18, 2025 05:20
@adam91holt
Copy link
Author

If we take the the slack mcp for example it just returns loads of pointless metadata when doing something like listing messages, a lot of the time you just care about the message and who sent it. But instead it's polluting the context window.

@adam91holt
Copy link
Author

P.s you can probably do this more efficiently, I'm just showing essentially what I want to be able to do @steipete

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.

2 participants