Skip to content

gRPC Bridge Architecture

This document describes the gRPC bridge component that mediates communication between the Wanaku router and remote capability services.

Architecture Overview

The gRPC bridge implements a transport-agnostic architecture using composition:

text
┌─────────────────────────────────────────────────────────────────┐
│                      Bridge Interfaces                          │
│   ┌─────────────┐  ┌──────────────┐  ┌───────────────────┐     │
│   │ ToolsBridge │  │ResourceBridge│  │CodeExecutorBridge │     │
│   └──────┬──────┘  └──────┬───────┘  └─────────┬─────────┘     │
│   ┌──────┴──────┐  ┌──────┴──────┐  ┌──────────┴──────────┐    │
│   │ McpBridge  │  │ProvisionBridge│ │                      │    │
│   └──────┬──────┘  └──────┬───────┘  │                      │    │
├──────────┼────────────────┼──────────┼──────────────────────┤    │
│          │                │          │                      │    │
│   ┌──────▼──────┐  ┌──────▼───────────┐  ┌─────▼──────────┐    │
│   │InvokerBridge│  │ResourceAcquirer- │  │CodeExecution-  │    │
│   │             │  │Bridge            │  │Bridge          │    │
│   └──────┬──────┘  └──────┬───────────┘  └─────┬──────────┘    │
│          │        ┌───────┤                     │               │
│          │  ┌─────▼───────▼──┐                  │               │
│          │  │ProvisionerBridge│                  │               │
│          │  └────────┬────────┘                  │               │
├──────────┼───────────┼──────────────────────────┼───────────────┤
│                    Transport Abstraction                        │
│                  WanakuBridgeTransport                          │
├─────────────────────────────────────────────────────────────────┤
│                    gRPC Implementation                          │
│   ┌─────────────────┐      ┌────────────────────┐              │
│   │  GrpcTransport  │──────│ GrpcChannelManager │              │
│   └────────┬────────┘      └────────────────────┘              │
│            │                                                    │
│   ┌────────┴────────────────────────────────┐                   │
│   │ GrpcToolResponseTransformer             │                   │
│   │ GrpcResourceResponseTransformer         │                   │
│   └─────────────────────────────────────────┘                   │
│                                                                 │
│                    MCP Implementation                           │
│   ┌──────────────────┐    ┌────────────────┐                    │
│   │ DefaultMcpBridge │────│ ForwardRegistry│                    │
│   └──────────────────┘    └────────────────┘                    │
└─────────────────────────────────────────────────────────────────┘

Key characteristics:

  • Protocol independence via transport abstraction
  • Async-first operations using Mutiny Uni types
  • Response transformation at the transport layer (bridges never see transport-specific types)
  • Service discovery integration for dynamic endpoint resolution
  • Event emission for observability
  • Streaming support for code execution
  • MCP-to-MCP forwarding via McpBridge
  • Consolidated provisioning via ProvisionerBridge
  • Composition over inheritance

Core Abstractions

Bridge Interface

Package: ai.wanaku.backend.bridge

Base marker interface for all bridge implementations.

java
public interface Bridge {
    default String name() {
        return this.getClass().getSimpleName();
    }
}

WanakuBridgeTransport Interface

Package: ai.wanaku.backend.bridge

Core transport abstraction defining the contract for all transport implementations.

java
public interface WanakuBridgeTransport {

    ProvisioningReference provision(
        String name,
        String configData,
        String secretsData,
        ServiceTarget service
    );

    Uni<ToolResponse> invokeTool(
        ToolInvokeRequest request,
        ServiceTarget service
    );

    Uni<List<ResourceContents>> acquireResource(
        ResourceRequest request,
        ServiceTarget service,
        ResourceManager.ResourceArguments arguments,
        ResourceReference mcpResource
    );

    Iterator<CodeExecutionReply> executeCode(
        CodeExecutionRequest request,
        ServiceTarget service
    );

    HealthProbeReply probeHealth(
        HealthProbeRequest request,
        ServiceTarget service
    );
}

The async methods (invokeTool, acquireResource) return already-transformed domain types (e.g., ToolResponse, List<ResourceContents>) rather than transport-specific protobuf types. This keeps bridges protocol-agnostic.

This abstraction enables:

  • Protocol independence (supports gRPC, HTTP, WebSocket)
  • Testability (easy mocking)
  • Separation of concerns
  • Async-first design with Mutiny Uni types

Bridge Interfaces

ToolsBridge

Contract for tool proxy implementations.

java
public interface ToolsBridge extends Bridge {
    Uni<ToolResponse> execute(ToolManager.ToolArguments toolArguments, CallableReference toolReference);
}

ResourceBridge

Contract for resource proxy implementations.

java
public interface ResourceBridge extends Bridge {
    Uni<ResourceResponse> read(
        ResourceManager.ResourceArguments arguments,
        ResourceReference mcpResource
    );
}

ProvisionBridge

Contract for provisioning configuration and secrets.

java
public interface ProvisionBridge {
    ProvisioningReference provision(
        String name, String configData, String secretsData, ServiceTarget service
    );
}

CodeExecutorBridge

Contract for code execution bridge implementations.

java
public interface CodeExecutorBridge extends Bridge {
    Iterator<CodeExecutionReply> executeCode(
        String engineType,
        String language,
        CodeExecutionRequest request
    );
}

gRPC Transport Layer

GrpcChannelManager

Package: ai.wanaku.backend.bridge.transports.grpc

Manages gRPC channel lifecycle.

java
class GrpcChannelManager {
    private static final Map<ServiceTarget, ManagedChannel> CHANNEL_MAP =
        new ConcurrentHashMap<>();

    public ManagedChannel createChannel(ServiceTarget service) {
        return CHANNEL_MAP.computeIfAbsent(service, s ->
            ManagedChannelBuilder
                .forTarget(s.toAddress())
                .usePlaintext()
                .build()
        );
    }

    public void closeChannel() {
        // No-op: channels are cached and reused
    }

    public void shutdown() {
        // Closes all cached channels on application shutdown
        CHANNEL_MAP.values().forEach(ch -> ch.shutdown());
        CHANNEL_MAP.clear();
    }
}

Characteristics:

  • Caches and reuses gRPC channels per service target (reduces connection overhead)
  • Creates plaintext gRPC channels
  • No-op closeChannel() since channels are reused
  • shutdown() closes all cached channels on application termination
  • Design allows for future SSL/TLS support

GrpcTransport

Package: ai.wanaku.backend.bridge.transports.grpc

Central coordinator for all gRPC transport operations.

java
public class GrpcTransport implements WanakuBridgeTransport {
    private final GrpcChannelManager channelManager;
    private final ToolResponseTransformer<ToolInvokeReply> toolTransformer =
        new GrpcToolResponseTransformer();
    private final ResourceResponseTransformer<ResourceReply> resourceTransformer =
        new GrpcResourceResponseTransformer();

    public GrpcTransport() {
        this.channelManager = new GrpcChannelManager();
    }

    @Override
    public ProvisioningReference provision(
            String name, String configData, String secretsData,
            ServiceTarget service) {
        // Build Configuration and Secret protobuf objects
        // Call ProvisionerGrpc stub directly
    }

    @Override
    public Uni<ToolResponse> invokeTool(
            ToolInvokeRequest request, ServiceTarget service) {
        ManagedChannel channel = channelManager.createChannel(service);
        ToolInvokerGrpc.ToolInvokerFutureStub stub =
            ToolInvokerGrpc.newFutureStub(channel);
        return Uni.createFrom().future(stub.invokeTool(request))
            .map(toolTransformer::transformReply);
    }

    @Override
    public Uni<List<ResourceContents>> acquireResource(
            ResourceRequest request, ServiceTarget service,
            ResourceManager.ResourceArguments arguments,
            ResourceReference mcpResource) {
        ManagedChannel channel = channelManager.createChannel(service);
        ResourceAcquirerGrpc.ResourceAcquirerFutureStub stub =
            ResourceAcquirerGrpc.newFutureStub(channel);
        return Uni.createFrom().future(stub.resourceAcquire(request))
            .map(reply -> resourceTransformer.transformReply(reply, arguments, mcpResource));
    }

    @Override
    public Iterator<CodeExecutionReply> executeCode(
            CodeExecutionRequest request, ServiceTarget service) {
        ManagedChannel channel = channelManager.createChannel(service);
        CodeExecutorGrpc.CodeExecutorBlockingStub stub =
            CodeExecutorGrpc.newBlockingStub(channel);
        return stub.executeCode(request);
    }

    @Override
    public HealthProbeReply probeHealth(
            HealthProbeRequest request, ServiceTarget service) {
        ManagedChannel channel = channelManager.createChannel(service);
        HealthProbeGrpc.HealthProbeBlockingStub stub =
            HealthProbeGrpc.newBlockingStub(channel);
        return stub.probe(request);
    }
}

Response Transformers

Package: ai.wanaku.backend.bridge and ai.wanaku.backend.bridge.transports.grpc

Response transformers convert transport-specific types into MCP domain types within the transport layer, ensuring bridges never handle protocol-specific types.

java
// Generic interface for tool response transformation
public interface ToolResponseTransformer<T> {
    ToolResponse transformReply(T reply);
}

// Generic interface for resource response transformation
public interface ResourceResponseTransformer<T> {
    List<ResourceContents> transformReply(
        T reply,
        ResourceManager.ResourceArguments arguments,
        ResourceReference mcpResource
    );
}

// gRPC-specific implementations
class GrpcToolResponseTransformer implements ToolResponseTransformer<ToolInvokeReply> {
    @Override
    public ToolResponse transformReply(ToolInvokeReply reply) {
        // Converts protobuf ProtocolStringList to List<TextContent>
    }
}

class GrpcResourceResponseTransformer implements ResourceResponseTransformer<ResourceReply> {
    @Override
    public List<ResourceContents> transformReply(
            ResourceReply reply,
            ResourceManager.ResourceArguments arguments,
            ResourceReference mcpResource) {
        // Builds TextResourceContents with proper MIME type and URI
    }
}

Bridge Implementations

InvokerBridge

Package: ai.wanaku.backend.bridge

Proxy for tool invocation services. Uses an async-first design with Uni<ToolResponse>.

java
public class InvokerBridge implements ToolsBridge {
    private static final String SERVICE_TYPE_TOOL_INVOKER = "tool-invoker";

    private final ServiceResolver serviceResolver;
    private final WanakuBridgeTransport transport;
    private final EventNotifier eventNotifier;

    public InvokerBridge(
            ServiceResolver serviceResolver,
            WanakuBridgeTransport transport,
            EventNotifier eventNotifier) {
        this.serviceResolver = serviceResolver;
        this.transport = transport;
        this.eventNotifier = eventNotifier;
    }

    @Override
    public Uni<ToolResponse> execute(
            ToolManager.ToolArguments toolArguments, CallableReference toolReference) {
        // 1. Resolve service (try TOOL_INVOKER, fallback to CODE_EXECUTION_ENGINE)
        // 2. Build ToolInvokeRequest via InvokerToolExecutor.buildToolInvokeRequest()
        // 3. Emit started event via EventNotifier
        // 4. Delegate to transport.invokeTool(request, service)
        // 5. Emit completed/failed event on response/failure
    }
}

InvokerToolExecutor

Package: ai.wanaku.backend.bridge

Static utility class providing helper methods for building tool invocation requests. Used by InvokerBridge to construct ToolInvokeRequest objects from tool references and arguments.

java
public final class InvokerToolExecutor {
    private InvokerToolExecutor() {}

    static ToolInvokeRequest buildToolInvokeRequest(
            ToolReference toolReference,
            ToolManager.ToolArguments toolArguments) {

        // Filter out metadata and auth args before converting to string map
        Map<String, Object> filteredArgs = filterOutReservedArgs(toolArguments.args());
        Map<String, String> argumentsMap = CollectionsHelper.toStringStringMap(filteredArgs);

        // Extract metadata headers from args (with prefix stripped)
        Map<String, String> metadataHeaders = extractMetadataHeaders(toolArguments);

        // Extract auth headers from args (with prefix stripped)
        Map<String, String> authHeaders = extractAuthHeaders(toolArguments);

        // Extract tool-defined headers from schema
        Map<String, String> toolDefinedHeaders = extractHeaders(toolReference, toolArguments);

        // Merge headers: metadata first, then tool-defined, then auth (auth wins on conflict)
        Map<String, String> headers = new HashMap<>(metadataHeaders);
        headers.putAll(toolDefinedHeaders);
        headers.putAll(authHeaders);

        String body = extractBody(toolReference, toolArguments);

        return ToolInvokeRequest.newBuilder()
            .setBody(body)
            .setUri(toolReference.getUri())
            .setConfigurationUri(
                Objects.requireNonNullElse(
                    toolReference.getConfigurationURI(), ""))
            .setSecretsUri(
                Objects.requireNonNullElse(
                    toolReference.getSecretsURI(), ""))
            .putAllHeaders(headers)
            .putAllArguments(argumentsMap)
            .build();
    }

    static Map<String, String> extractHeaders(
            ToolReference toolReference,
            ToolManager.ToolArguments toolArguments) {
        // Filter properties where target == "header" and scope == "service"
    }

    static Map<String, String> extractMetadataHeaders(
            ToolManager.ToolArguments toolArguments) {
        // Extract args with "wanaku_meta_" prefix, strip prefix for header name
    }

    static Map<String, String> extractAuthHeaders(
            ToolManager.ToolArguments toolArguments) {
        // Extract args with "wanaku_auth_" prefix, strip prefix for header name
        // These are treated as sensitive and redacted in logs/events
    }

    static Map<String, Object> filterOutReservedArgs(Map<String, Object> args) {
        // Exclude args with "wanaku_meta_" or "wanaku_auth_" prefix from the arguments map
    }
}

EventNotifier

Package: ai.wanaku.backend.bridge

Wraps a MutinyEmitter<ToolCallEvent> to emit tool call lifecycle events for observability.

java
public class EventNotifier {
    public EventNotifier(MutinyEmitter<ToolCallEvent> emitter);

    public ToolCallEvent emitStartedEvent(
        ToolManager.ToolArguments toolArguments,
        ToolReference toolReference,
        ServiceTarget service,
        ToolInvokeRequest request);

    public void emitCompletedEvent(String eventId, String content, long duration);

    public void emitFailedEvent(
        String eventId, ToolCallEvent.ErrorCategory category,
        String errorMessage, long duration);

    public ToolCallEvent.ErrorCategory categorizeException(Exception e);
}

Exception Categorization:

Exception TypeCategory
StatusRuntimeExceptionSERVICE_UNAVAILABLE
ServiceUnavailableExceptionSERVICE_UNAVAILABLE
NullPointerExceptionTOOL_DEFINITION_ERROR
IllegalArgumentExceptionINVALID_ARGUMENTS
OtherUNKNOWN

ResourceAcquirerBridge

Package: ai.wanaku.backend.bridge

Proxy for resource acquisition services. Uses ProvisionerBridge for service resolution and the async transport for resource reading.

java
public class ResourceAcquirerBridge implements ResourceBridge {
    private static final String SERVICE_TYPE_RESOURCE_PROVIDER = "resource-provider";

    private final ProvisionerBridge provisionerBridge;
    private final WanakuBridgeTransport transport;

    public ResourceAcquirerBridge(
            ProvisionerBridge provisionerBridge,
            WanakuBridgeTransport transport) {
        this.provisionerBridge = provisionerBridge;
        this.transport = transport;
    }

    @Override
    public Uni<ResourceResponse> read(
            ResourceManager.ResourceArguments arguments,
            ResourceReference mcpResource) {

        ServiceTarget service = provisionerBridge.resolveService(
            mcpResource.getType(), SERVICE_TYPE_RESOURCE_PROVIDER);

        ResourceRequest request = buildResourceRequest(mcpResource);
        return transport.acquireResource(request, service, arguments, mcpResource)
            .map(ResourceResponse::new);
    }

    private ResourceRequest buildResourceRequest(ResourceReference mcpResource) {
        return ResourceRequest.newBuilder()
            .setLocation(mcpResource.getLocation())
            .setType(mcpResource.getType())
            .setName(mcpResource.getName())
            .setConfigurationURI(
                Objects.requireNonNullElse(
                    mcpResource.getConfigurationURI(), ""))
            .setSecretsURI(
                Objects.requireNonNullElse(
                    mcpResource.getSecretsURI(), ""))
            .build();
    }
}

ProvisionerBridge

Package: ai.wanaku.backend.bridge

Consolidates provisioning logic and service resolution shared between InvokerBridge and ResourceAcquirerBridge.

java
public class ProvisionerBridge implements ProvisionBridge {
    private final ServiceResolver serviceResolver;
    private final WanakuBridgeTransport transport;

    public ProvisionerBridge(ServiceResolver serviceResolver,
                             WanakuBridgeTransport transport) {
        this.serviceResolver = serviceResolver;
        this.transport = transport;
    }

    @Override
    public ProvisioningReference provision(
            String name, String configData, String secretsData,
            ServiceTarget service) {
        return transport.provision(name, configData, secretsData, service);
    }

    public ServiceTarget resolveService(String type, String serviceType) {
        ServiceTarget service = serviceResolver.resolve(type, serviceType);
        if (service == null) {
            throw new ServiceNotFoundException(
                "There is no host registered for service " + type);
        }
        return service;
    }
}

CodeExecutionBridge

Package: ai.wanaku.backend.bridge

Proxy for code execution services with streaming support.

java
public class CodeExecutionBridge implements CodeExecutorBridge {
    private static final String SERVICE_TYPE_CODE_EXECUTION = "code-execution-engine";

    private final ServiceResolver serviceResolver;
    private final WanakuBridgeTransport transport;
    private final MutinyEmitter<ToolCallEvent> toolCallEventEmitter;

    @Override
    public Iterator<CodeExecutionReply> executeCode(
            String engineType,
            String language,
            CodeExecutionRequest request) {

        ServiceTarget service = resolveService(engineType, language);

        ai.wanaku.core.exchange.CodeExecutionRequest grpcRequest =
            buildGrpcRequest(engineType, language, request);

        ToolCallEvent startedEvent = emitStartedEvent(engineType, language, request);
        Instant startTime = Instant.now();

        try {
            Iterator<CodeExecutionReply> iterator =
                transport.executeCode(grpcRequest, service);

            return new CloseableCodeExecutionIterator(
                iterator, startedEvent, startTime, this);

        } catch (Exception e) {
            emitCompletionEvent(startedEvent, startTime, true, e.getMessage());
            throw e;
        }
    }

    private ServiceTarget resolveService(String engineType, String language) {
        ServiceTarget service = serviceResolver.resolveCodeExecution(
            SERVICE_TYPE_CODE_EXECUTION, engineType, language);

        if (service == null) {
            throw new ServiceNotFoundException(
                SERVICE_TYPE_CODE_EXECUTION, engineType + "/" + language);
        }
        return service;
    }

    private ai.wanaku.core.exchange.CodeExecutionRequest buildGrpcRequest(
            String engineType,
            String language,
            CodeExecutionRequest request) {

        String uri = String.format("code-execution-engine://%s/%s",
            engineType, language);

        String decodedCode = decodeBase64Code(request.getCode());

        // Convert List<String> arguments to Map with indexed keys
        Map<String, String> argMap = new HashMap<>();
        List<String> args = request.getArguments();
        for (int i = 0; i < args.size(); i++) {
            argMap.put("arg" + i, args.get(i));
        }

        return ai.wanaku.core.exchange.CodeExecutionRequest.newBuilder()
            .setUri(uri)
            .setCode(decodedCode)
            .putAllArguments(argMap)
            .putAllEnvironment(request.getEnvironment())
            .setTimeout(request.getTimeout())
            .build();
    }

    private String decodeBase64Code(String base64Code) {
        if (base64Code == null || base64Code.isEmpty()) {
            return "";
        }
        try {
            return new String(Base64.getDecoder().decode(base64Code));
        } catch (IllegalArgumentException e) {
            return base64Code;  // Return raw if not valid base64
        }
    }
}

CloseableCodeExecutionIterator

Wraps streaming iterator to ensure terminal events are emitted.

java
public static class CloseableCodeExecutionIterator
        implements Iterator<CodeExecutionReply>, Closeable {

    private final Iterator<CodeExecutionReply> delegate;
    private final ToolCallEvent startedEvent;
    private final Instant startTime;
    private final CodeExecutionBridge bridge;
    private boolean completed = false;
    private boolean hasError = false;
    private String lastErrorMessage = null;

    @Override
    public boolean hasNext() {
        try {
            boolean hasMore = delegate.hasNext();
            if (!hasMore && !completed) {
                bridge.emitCompletionEvent(
                    startedEvent, startTime, hasError, lastErrorMessage);
                completed = true;
            }
            return hasMore;
        } catch (Exception e) {
            hasError = true;
            lastErrorMessage = e.getMessage();
            if (!completed) {
                bridge.emitCompletionEvent(
                    startedEvent, startTime, true, e.getMessage());
                completed = true;
            }
            throw e;
        }
    }

    @Override
    public CodeExecutionReply next() {
        try {
            CodeExecutionReply reply = delegate.next();
            if (reply.getIsError()) {
                hasError = true;
                lastErrorMessage = reply.getContent(0);
            }
            return reply;
        } catch (Exception e) {
            hasError = true;
            lastErrorMessage = e.getMessage();
            if (!completed) {
                bridge.emitCompletionEvent(
                    startedEvent, startTime, true, e.getMessage());
                completed = true;
            }
            throw e;
        }
    }

    @Override
    public void close() {
        if (!completed) {
            bridge.emitCompletionEvent(
                startedEvent, startTime, hasError, lastErrorMessage);
            completed = true;
        }
    }
}

gRPC Protocol Definitions

Provisioner Service

File: provision.proto

protobuf
service Provisioner {
  rpc Provision (ProvisionRequest) returns (ProvisionReply) {}
}

message Configuration {
  PayloadType type = 1;   // REFERENCE or BUILTIN
  string name = 2;
  string payload = 3;
}

message Secret {
  PayloadType type = 1;
  string name = 2;
  string payload = 3;
}

message ProvisionRequest {
  string uri = 1;
  Configuration configuration = 2;
  Secret secret = 3;
}

message ProvisionReply {
  string configurationUri = 1;
  string secretUri = 2;
  map<string, PropertySchema> properties = 3;
}

ToolInvoker Service

File: toolrequest.proto

protobuf
service ToolInvoker {
  rpc InvokeTool (ToolInvokeRequest) returns (ToolInvokeReply) {}
}

message ToolInvokeRequest {
  string uri = 1;
  string body = 2;
  map<string, string> arguments = 3;
  string configurationURI = 4;
  string secretsURI = 5;
  map<string, string> headers = 6;
}

message ToolInvokeReply {
  bool isError = 1;
  repeated string content = 2;
}

ResourceAcquirer Service

File: resourcerequest.proto

protobuf
service ResourceAcquirer {
  rpc ResourceAcquire (ResourceRequest) returns (ResourceReply) {}
}

message ResourceRequest {
  string location = 1;
  string type = 2;
  string name = 3;
  map<string, string> params = 4;
  string configurationURI = 5;
  string secretsURI = 6;
}

message ResourceReply {
  bool isError = 1;
  repeated string content = 2;
}

CodeExecutor Service

File: codeexecution.proto

protobuf
service CodeExecutor {
  rpc ExecuteCode (CodeExecutionRequest) returns (stream CodeExecutionReply) {}
}

message CodeExecutionRequest {
  string uri = 1;
  string body = 2;
  string code = 3;
  map<string, string> arguments = 4;
  string configurationURI = 5;
  string secretsURI = 6;
  int64 timeout = 7;
  map<string, string> environment = 8;
}

message CodeExecutionReply {
  bool isError = 1;
  repeated string content = 2;
  OutputType outputType = 3;
  ExecutionStatus status = 4;
  int32 exitCode = 5;
  int64 timestamp = 6;
}

enum OutputType {
  STDOUT = 0;
  STDERR = 1;
  STATUS = 2;
  COMPLETION = 3;
}

enum ExecutionStatus {
  PENDING = 0;
  RUNNING = 1;
  COMPLETED = 2;
  FAILED = 3;
  CANCELLED = 4;
  TIMEOUT = 5;
}

Request/Response Flows

Tool Invocation Flow (Async)

text
┌─────────────────────────────────────────────────────────────────┐
│ 1. Client Request                                               │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 2. InvokerBridge.execute(ToolArguments, CallableReference)      │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 3. ServiceResolver.resolve(type, "tool-invoker")                │
│    └─ Fallback: resolve(type, "code-execution-engine")          │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 4. buildToolInvokeRequest()                                     │
│    ├─ extractHeaders(): target="header", scope="service"        │
│    ├─ extractBody(): BODY argument if defined                   │
│    └─ Convert arguments to Map<String, String>                  │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 5. GrpcTransport.invokeTool(request, service)                   │
│    ├─ GrpcChannelManager.createChannel(ServiceTarget) [cached]  │
│    ├─ ToolInvokerGrpc.newFutureStub(channel)                    │
│    ├─ stub.invokeTool(request)                                  │
│    └─ GrpcToolResponseTransformer.transformReply()              │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 6. Return Uni<ToolResponse>                                     │
└─────────────────────────────────────────────────────────────────┘

Resource Acquisition Flow (Async)

text
┌─────────────────────────────────────────────────────────────────┐
│ 1. ResourceAcquirerBridge.read(ResourceArguments, Ref)          │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 2. ProvisionerBridge.resolveService(type, "resource-provider")  │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 3. buildResourceRequest()                                       │
│    ├─ location: from ResourceReference                          │
│    ├─ type: from ResourceReference                              │
│    ├─ configurationURI: from ResourceReference (default "")     │
│    └─ secretsURI: from ResourceReference (default "")           │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 4. GrpcTransport.acquireResource(request, service, ...)         │
│    ├─ GrpcChannelManager.createChannel(ServiceTarget) [cached]  │
│    ├─ ResourceAcquirerGrpc.newFutureStub(channel)               │
│    ├─ stub.resourceAcquire(request)                             │
│    └─ GrpcResourceResponseTransformer.transformReply()          │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 5. Return Uni<ResourceResponse>                                 │
└─────────────────────────────────────────────────────────────────┘

Code Execution Flow (Streaming)

text
┌─────────────────────────────────────────────────────────────────┐
│ 1. CodeExecutionBridge.executeCode(engineType, language, req)   │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 2. ServiceResolver.resolveCodeExecution(                        │
│       "code-execution-engine", engineType, language)            │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 3. buildGrpcRequest()                                           │
│    ├─ uri: "code-execution-engine://{engineType}/{language}"    │
│    ├─ code: Base64 decode from request                          │
│    ├─ arguments: List → Map (arg0, arg1, ...)                   │
│    └─ environment: from request                                 │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 4. Emit STARTED event                                           │
│    ├─ Redact code: "[CODE]"                                     │
│    └─ Redact arguments: emit count only                         │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 5. GrpcTransport.executeCode(request, service)                  │
│    ├─ GrpcChannelManager.createChannel(ServiceTarget)           │
│    ├─ CodeExecutorGrpc.newBlockingStub(channel)                 │
│    └─ stub.executeCode(request) → Iterator                      │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 6. Wrap in CloseableCodeExecutionIterator                       │
│    ├─ Tracks completion state                                   │
│    └─ Ensures terminal event on close/completion                │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 7. Stream responses to client                                   │
│    └─ Emit COMPLETED/FAILED when iterator exhausted or closed   │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 8. Return Iterator<CodeExecutionReply>                          │
└─────────────────────────────────────────────────────────────────┘

Provisioning Flow

text
┌─────────────────────────────────────────────────────────────────┐
│ 1. ProvisionerBridge.provision(name, configData, secretsData,   │
│                                service)                         │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 2. GrpcTransport.provision(name, configData, secretsData, svc)  │
│    ├─ Build Configuration protobuf                              │
│    ├─ Build Secret protobuf                                     │
│    ├─ Build ProvisionRequest                                    │
│    ├─ ProvisionerGrpc.ProvisionerBlockingStub                   │
│    └─ stub.provision(request) → ProvisionReply                  │
└────────────────────────────┬────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 3. Return ProvisioningReference(configUri, secretUri, props)    │
└─────────────────────────────────────────────────────────────────┘

Design Patterns

Transport Abstraction Pattern

java
// Interface defines protocol-independent contract with async-first design
interface WanakuBridgeTransport {
    Uni<ToolResponse> invokeTool(ToolInvokeRequest request, ServiceTarget service);
}

// gRPC implementation with response transformation
class GrpcTransport implements WanakuBridgeTransport {
    private final ToolResponseTransformer<ToolInvokeReply> transformer = ...;

    public Uni<ToolResponse> invokeTool(ToolInvokeRequest request, ServiceTarget service) {
        // gRPC FutureStub → Uni with transformer applied
    }
}

// HTTP implementation (future)
class HttpTransport implements WanakuBridgeTransport {
    public Uni<ToolResponse> invokeTool(ToolInvokeRequest request, ServiceTarget service) {
        // HTTP-specific logic with its own transformer
    }
}

Composition Over Inheritance

java
// InvokerBridge delegates to transport for async execution
class InvokerBridge implements ToolsBridge {
    private final ServiceResolver serviceResolver;      // Service discovery
    private final WanakuBridgeTransport transport;     // Protocol handling
}

// ResourceAcquirerBridge delegates to ProvisionerBridge and transport
class ResourceAcquirerBridge implements ResourceBridge {
    private final ProvisionerBridge provisionerBridge;  // Shared provisioning + resolution
    private final WanakuBridgeTransport transport;     // Protocol handling
}

// Each component has single responsibility

Service Locator Pattern

java
// ServiceResolver discovers services dynamically
ServiceTarget service = serviceResolver.resolve("my-tool", "tool-invoker");

// Bridges don't need hardcoded addresses

Iterator Wrapper Pattern

java
// Ensure cleanup even if iteration abandoned
try (CloseableCodeExecutionIterator iter = bridge.executeCode(...)) {
    while (iter.hasNext()) {
        CodeExecutionReply reply = iter.next();
        // Process reply
    }
}  // Terminal event emitted on close

Security Features

FeatureImplementation
Argument RedactionEvents omit actual argument values
Code RedactionCode replaced with "[CODE]" in events
Error SanitizationSafe error messages in responses
Plaintext (dev)Default configuration for development
TLS ReadyChannel manager designed for future SSL/TLS

File Locations

ComponentPath
Bridge.javaapps/wanaku-router-backend/.../bridge/
ToolsBridge.javaapps/wanaku-router-backend/.../bridge/
ResourceBridge.javaapps/wanaku-router-backend/.../bridge/
CodeExecutorBridge.javaapps/wanaku-router-backend/.../bridge/
ProvisionBridge.javaapps/wanaku-router-backend/.../bridge/
McpBridge.javaapps/wanaku-router-backend/.../bridge/
WanakuBridgeTransport.javaapps/wanaku-router-backend/.../bridge/
InvokerBridge.javaapps/wanaku-router-backend/.../bridge/
InvokerToolExecutor.javaapps/wanaku-router-backend/.../bridge/
EventNotifier.javaapps/wanaku-router-backend/.../bridge/
ResourceAcquirerBridge.javaapps/wanaku-router-backend/.../bridge/
CodeExecutionBridge.javaapps/wanaku-router-backend/.../bridge/
ProvisionerBridge.javaapps/wanaku-router-backend/.../bridge/
DefaultMcpBridge.javaapps/wanaku-router-backend/.../bridge/
ForwardClient.javaapps/wanaku-router-backend/.../bridge/
ForwardRegistry.javaapps/wanaku-router-backend/.../bridge/
ToolResponseTransformer.javaapps/wanaku-router-backend/.../bridge/
ResourceResponseTransformer.javaapps/wanaku-router-backend/.../bridge/
GrpcTransport.javaapps/wanaku-router-backend/.../bridge/transports/grpc/
GrpcChannelManager.javaapps/wanaku-router-backend/.../bridge/transports/grpc/
GrpcToolResponseTransformer.javaapps/wanaku-router-backend/.../bridge/transports/grpc/
GrpcResourceResponseTransformer.javaapps/wanaku-router-backend/.../bridge/transports/grpc/
provision.protocore/core-exchange/src/main/proto/
toolrequest.protocore/core-exchange/src/main/proto/
resourcerequest.protocore/core-exchange/src/main/proto/
codeexecution.protocore/core-exchange/src/main/proto/

Resource Provider Protocol

This section describes the gRPC protocol used by resource provider capabilities.

Overview

Resource providers implement the ResourceAcquirerBridge interface. The router calls the provider to retrieve resource contents given a URI.

Request Schema

The router sends a ResourceRequest message:

proto
message ResourceRequest {
  string uri = 1;           // The resource URI to retrieve
  map<string, string> params = 2; // Optional query parameters
}
  • uri — the resource URI as registered with Wanaku (e.g., file:///data/report.txt)
  • params — optional key-value parameters passed by the MCP client

Response Schema

The provider returns a ResourceResponse message:

proto
    message ResourceResponse {
      repeated ResourceContent contents = 1;
      ResourceError error = 2; // Present only on failure
    }

    message ResourceContent {
      string uri = 1;
      string mime_type = 2;
      oneof content {
        string text = 3;
        bytes blob = 4;
      }
    }

    message ResourceError {
      int32 code = 1;
      string message = 2;
    }
    ```

- `contents` — one or more content items returned by the provider
- `error` — present only when the provider encountered an error

### Example Java Resource Provider

```java
        @ApplicationScoped
        public class FileResourceProvider implements ResourceAcquirerBridge {

            @Override
            public ResourceResponse acquire(ResourceRequest request) {
                try {
                    Path path = Path.of(URI.create(request.getUri()));
                    String content = Files.readString(path);
                    return ResourceResponse.newBuilder()
                        .addContents(ResourceContent.newBuilder()
                            .setUri(request.getUri())
                            .setMimeType("text/plain")
                            .setText(content)
                            .build())
                        .build();
                } catch (IOException e) {
                    return ResourceResponse.newBuilder()
                        .setError(ResourceError.newBuilder()
                            .setCode(404)
                            .setMessage("Resource not found: " + e.getMessage())
                            .build())
                        .build();
                }
            }
        }
        ```

        ## Error Handling Patterns

        ### Tool Invoker Error Handling

        Tool invokers should handle errors at two levels:

        **Transport errors** (gRPC connection failures):

        ```java
        try {
            ToolResponse response = stub.invoke(request);
            // handle response
        } catch (StatusRuntimeException e) {
            if (e.getStatus().getCode() == Status.Code.UNAVAILABLE) {
                // Capability service is down — return a friendly error
                log.error("Capability unavailable: {}", e.getMessage());
                return ToolResponse.error("Capability service is temporarily unavailable");
            }
            throw e;
        }
        ```

        **Application errors** (errors returned in the response):

        ```java
        ToolResponse response = stub.invoke(request);
        if (response.hasError()) {
            log.warn("Tool invocation error: {} (code={})",
                response.getError().getMessage(),
                response.getError().getCode());
            // Map error codes to MCP error types
            return mapErrorToMcpResponse(response.getError());
        }
        ```

        ### Resource Provider Error Handling

        Resource providers should return structured errors rather than throwing exceptions:

        ```java
        // GOOD: Return a structured error response
        return ResourceResponse.newBuilder()
            .setError(ResourceError.newBuilder()
                .setCode(404)
                .setMessage("Resource not found: " + uri)
                .build())
            .build();

        // BAD: Throwing an exception causes an unstructured gRPC error
        throw new RuntimeException("Resource not found");
        ```

        **Common error codes:**

        | Code | Meaning | When to use |
        |------|---------|-------------|
        | `400` | Bad request | URI is malformed or parameters are invalid |
        | `404` | Not found | Resource URI does not exist |
        | `403` | Forbidden | Access to the resource is not permitted |
        | `500` | Internal error | Unexpected provider failure |
        | `503` | Service unavailable | Transient failure; client should retry |

        ### Retry and Timeout Configuration

        Configure retry and timeout behavior for gRPC calls:

        ```yaml
        wanaku:
          bridge:
            grpc:
              timeout-ms: 5000       # Per-call timeout in milliseconds
              max-retries: 3         # Retry attempts on UNAVAILABLE errors
              retry-delay-ms: 100    # Initial retry delay
        ```

        ## Testing gRPC Locally

        ### Using grpcurl

        Test tool and resource providers with `grpcurl`:

        ```bash
        # List available services
        grpcurl -plaintext localhost:50051 list

        # Call a tool provider
        grpcurl -plaintext -d '{"name":"my-tool","arguments":{"input":"test"}}' \
          localhost:50051 wanaku.ToolInvoker/Invoke

        # Call a resource provider
        grpcurl -plaintext -d '{"uri":"file:///data/test.txt"}' \
          localhost:50051 wanaku.ResourceAcquirer/Acquire
        ```

        ### Using a Mock Server

        For unit testing, implement a mock gRPC server:

        ```java
        @QuarkusTest
        class MyToolProviderTest {

            @GrpcClient("test-server")
            ToolInvokerGrpc.ToolInvokerBlockingStub stub;

            @Test
            void testToolInvocation() {
                ToolResponse response = stub.invoke(
                    ToolRequest.newBuilder()
                        .setName("my-tool")
                        .putArguments("input", "hello")
                        .build()
                );
                assertThat(response.hasError()).isFalse();
                assertThat(response.getContents(0).getText()).contains("expected");
            }
        }
        ```