Skip to content

Pass metadata context through tool pipeline to MCP calls (#1323)#1462

Open
alexheifetz wants to merge 7 commits intomainfrom
issue/1323
Open

Pass metadata context through tool pipeline to MCP calls (#1323)#1462
alexheifetz wants to merge 7 commits intomainfrom
issue/1323

Conversation

@alexheifetz
Copy link
Contributor

@alexheifetz alexheifetz commented Mar 3, 2026

Summary

Introduces ToolCallContext — an immutable, framework-agnostic key-value bag that carries out-of-band metadata (auth tokens, tenant IDs, correlation IDs) through the tool execution pipeline to MCP calls, without exposing it to the LLM.

Resolves Issue #1323

Context Flow

ProcessOptions.toolCallContext
  → DefaultToolLoop (stores, forwards on every call)
    → tool.call(input, context)
      → DelegatingTool chains (auto-propagation)
        → SpringToolCallbackWrapper → Spring AI ToolContext → MCP
        → MethodTool → injects into @LlmTool method parameters

Major Changes

New: ToolCallContext

Immutable typed key-value bag with merge semantics, EMPTY singleton, and toMap() for Spring AI bridging.

New: Tool.call(input, context) overload

Two-arg call method on Tool interface. Default discards context for full backward compatibility.

Canonical DelegatingTool call pattern

DelegatingTool.call(String) now routes through call(String, ToolCallContext.EMPTY), making the two-arg method the single canonical entry point for decorator logic. All decorators override one method only — call(String, ToolCallContext). ArchUnit test (DelegatingToolArchitectureTest) runs on every mvn test to enforce this contract.

Updated: MethodTool context injection

@LlmTool methods can declare a ToolCallContext parameter — framework injects it at call time and excludes it from the LLM-facing JSON schema. Works for both Kotlin and Java methods.

Updated: Spring AI bridges

SpringToolCallbackAdapter and SpringToolCallbackWrapper convert between ToolCallContext and Spring AI ToolContext in both directions. SpringToolCallbackAdapter deduplicated to a single call path.

Updated: ProcessOptions

New toolCallContext property with withToolCallContext(Map) Java-friendly overload.

New: ToolCallContextMcpMetaConverter

Allowlist/denylist filter controlling which context entries cross the MCP boundary. Defined but not yet wired — integration pending Spring AI MCP _meta finalization.

Not Yet Included

  • ToolCallContextMcpMetaConverter wiring into SpringToolCallbackWrapper
  • PromptRunner-level context enrichment (per-interaction override)

@alexheifetz alexheifetz marked this pull request as ready for review March 3, 2026 06:18
Copy link
Contributor

@igordayen igordayen left a comment

Choose a reason for hiding this comment

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

commented on sonare violations

toolDecorator: ((Tool) -> Tool)?,
inspectors: List<ToolLoopInspector>,
transformers: List<ToolLoopTransformer>,
toolCallContext: ToolCallContext,
Copy link
Contributor

Choose a reason for hiding this comment

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

This function has 8 parameters, which is greater than the 7 authorized.. mark as //NOSONAR or consider addition to decorator

@alexheifetz
Copy link
Contributor Author

parameter packing maybe addressed in the different PR

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces ToolCallContext — an immutable, framework-agnostic key-value bag that carries out-of-band metadata (auth tokens, tenant IDs, correlation IDs) through the tool execution pipeline to MCP calls, resolving issue #1323. The context flows explicitly through Tool.call(input, context) without being exposed to the LLM's JSON schema.

Changes:

  • Introduces ToolCallContext class with factory methods, merge semantics, and EMPTY singleton; adds a two-arg call(input, context) overload to Tool interface with auto-propagation via DelegatingTool; injects context into @LlmTool methods (both Kotlin and Java) while excluding from LLM-facing schema
  • Updates the tool loop pipeline (DefaultToolLoop, ParallelToolLoop, ToolLoopFactory) and all decorator tools to propagate ToolCallContext; bridges between Embabel and Spring AI contexts in SpringToolCallbackAdapter/SpringToolCallbackWrapper
  • Adds shell commands (set-context, show-context, -c flag) for interactive context management, ToolCallContextMcpMetaConverter for MCP boundary filtering, comprehensive documentation, and extensive test coverage

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
ToolCallContext.kt New immutable key-value context class with factory, merge, and contains operations
Tool.kt Adds two-arg call(input, context) default, ContextAwareFunction interface, and ContextAwareFunctionalTool
DelegatingTool.kt Default context propagation through decorator chains
MethodTool.kt Context injection for @LlmTool methods + schema exclusion for both Kotlin and Java
ArtifactSinkingTool.kt Context-aware call override with inline refactor
ReplanningTools.kt Context-aware call overrides for ReplanningTool and ConditionalReplanningTool
ToolDecorators.kt All 6 decorator tools updated with two-arg call + inline refactor
DefaultToolLoop.kt Uses two-arg tool.call(arguments, toolCallContext) for all tool invocations
ParallelToolLoop.kt Passes toolCallContext through to parent DefaultToolLoop
ToolLoopFactory.kt Adds toolCallContext parameter to factory interface
ToolLoopLlmOperations.kt Resolves effective context from process + interaction levels
LlmInteraction.kt Adds toolCallContext field for interaction-level context
ProcessOptions.kt Adds toolCallContext property with Java-friendly withToolCallContext overloads
SpringToolCallbackAdapter.kt Bridges between Embabel ToolCallContext and Spring AI ToolContext
ObservabilityToolCallback.kt Forwards ToolContext through observation decorator
OutputTransformingToolCallback.kt Forwards ToolContext through output transformation decorator
ToolCallContextMcpMetaConverter.kt New allowlist/denylist filter for MCP metadata boundary (not yet wired)
ShellCommands.kt New set-context/show-context commands and -c flag on execute
commands.adoc Shell commands documentation
reference/tools/page.adoc Comprehensive tool context reference documentation
reference/agent-process/page.adoc ProcessOptions documentation update
Test files (8 files) Comprehensive unit, integration, and Java interop tests

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

Comment on lines +42 to +43
override fun call(input: String, context: ToolCallContext): Tool.Result =
delegate.call(input, context)
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

The new DelegatingTool.call(input, context) default forwards directly to delegate.call(input, context), which bypasses the behavior added in call(input) by several DelegatingTool implementations not updated in this PR:

  • ConditionalAwaitingTool (in AwaitingTools.kt): the decider.evaluate() check is skipped when context is present
  • AssetAddingTool (in AssetAddingTool.kt): asset tracking is skipped when context is present

Since DefaultToolLoop.executeToolCall now always calls the two-arg tool.call(arguments, toolCallContext), these decorators will have their behavior silently bypassed at runtime. They need the same treatment as the decorators updated in this PR (override call(input, context) to apply their logic while forwarding context to the delegate).

Copilot uses AI. Check for mistakes.
@alexheifetz alexheifetz marked this pull request as draft March 4, 2026 23:48
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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


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

alexheifetz added a commit that referenced this pull request Mar 5, 2026
Single-arg call(String) now delegates to call(String, ToolContext?).
One code path for result mapping, logging, and error handling.

Also fixes incorrect PR reference in ShellCommandsContextTest KDoc
(#1456#1462).
@alexheifetz alexheifetz marked this pull request as ready for review March 5, 2026 01:43
Introduce ToolCallContext as an immutable, framework-agnostic key-value
bag for passing out-of-band metadata (auth tokens, tenant IDs, correlation
IDs) through the tool pipeline without polluting JSON input schemas.

Core API:
- ToolCallContext with merge, contains, and Map conversion
- Tool interface two-arg call(input, context) with backward-compatible default
- DelegatingTool default propagation through decorator chains
- ContextAwareFunction for lambda-based context-aware tools

Implementation:
- MethodTool (Kotlin + Java): inject ToolCallContext, exclude from schema
- ReplanningTool, ArtifactSinkingTool: context-aware overrides
- DefaultToolLoop: accept and forward context on every invocation
- SpringToolCallbackAdapter/Wrapper: bridge to/from Spring AI ToolContext
- ProcessOptions: toolCallContext property with Java-friendly Map overload
- ToolCallContextMcpMetaConverter: allowlist/denylist filtering (not yet wired)

Tests:
- ToolCallContext unit, flow, and Java interop tests
- MethodTool context injection and schema exclusion (Kotlin + Java)
Single-arg call(String) now delegates to call(String, ToolContext?).
One code path for result mapping, logging, and error handling.

Also fixes incorrect PR reference in ShellCommandsContextTest KDoc
(#1456#1462).
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 6, 2026

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