Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/MCPAPP.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ McpAppServer
Produces an IMcpAppInfoProvider
│
▼
McpAppNativeTool (implements ITool)
McpAppNativeTool (implements IToolWithContext)
Registered in NativePluginRegistry
Agent can call MCP App tools the same as built-in tools
```
Expand Down Expand Up @@ -269,15 +269,18 @@ Discovered ──→ Validated ──→ Loaded ──→ Running

## Tool Bridging

Every tool discovered from an MCP App is registered into the `NativePluginRegistry` via `McpAppNativeTool` (which implements `ITool`):
Every tool discovered from an MCP App is registered into the `NativePluginRegistry` via `McpAppNativeTool` (which implements `IToolWithContext`, extending `ITool`):

- **Registration ID**: `mcpapp:{appId}` (e.g. `mcpapp:grocery-inventory`)
- **Tool name prefix**: automatically applied from the manifest's `toolNamePrefix`
- **Session metadata**: Agent tool calls forward `_meta.sessionId` and `_meta.userId`, preferring the explicit `ToolExecutionContext` session over the ambient runtime session. `userId` uses `AuthenticatedUserId` when available, otherwise `SenderId`. These values are protocol metadata, not tool arguments; calls without a session do not inject either value.
- **Visibility handling**: tools marked with MCP App visibility metadata such as `"ui": { "visibility": ["app"] }` stay available to the browser host but are not registered as model-visible Agent tools
- **Error handling**: invalid JSON arguments and remote execution errors return result text prefixed with `Error:`
- **Schema strictness**: tools with missing/defaulted `inputSchema` are skipped at enumeration time instead of silently normalized
- **Structured output**: supports both text content and structured content from MCP tools, except that UI tools suppress `structuredContent` when OpenClaw feeds results back into the model

Session metadata forwarding uses a lightweight Core `AsyncLocal` scope bridged by the Agent runtime. MCP App retains its Core-only project dependency, with no reflection-based or JIT-only additions.

```csharp
// Internal flow — automatically executed during Gateway startup
await appRegistry.RegisterMcpAppToolsAsync(
Expand Down
10 changes: 7 additions & 3 deletions src/OpenClaw.Agent/AgentExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@ public static IDisposable Push(AgentExecutionContext context)
{
var prior = CurrentValue.Value;
CurrentValue.Value = context;
return new RestoreScope(prior);
return new RestoreScope(prior, ToolSessionContextScope.Push(context.Session));
}

private sealed class RestoreScope(AgentExecutionContext? prior) : IDisposable
private sealed class RestoreScope(AgentExecutionContext? prior, IDisposable sessionScope) : IDisposable
{
public void Dispose() => CurrentValue.Value = prior;
public void Dispose()
{
CurrentValue.Value = prior;
sessionScope.Dispose();
}
}
}
22 changes: 22 additions & 0 deletions src/OpenClaw.Core/Abstractions/ToolSessionContextScope.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using OpenClaw.Core.Models;

namespace OpenClaw.Core.Abstractions;

public static class ToolSessionContextScope
{
private static readonly AsyncLocal<Session?> CurrentValue = new();

public static Session? Current => CurrentValue.Value;

public static IDisposable Push(Session session)
{
var prior = CurrentValue.Value;
CurrentValue.Value = session;
return new RestoreScope(prior);
}

private sealed class RestoreScope(Session? prior) : IDisposable
{
public void Dispose() => CurrentValue.Value = prior;
}
}
Loading
Loading