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:
┌─────────────────────────────────────────────────────────────────┐
│ 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
Unitypes - 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.
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.
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
Unitypes
Bridge Interfaces
ToolsBridge
Contract for tool proxy implementations.
public interface ToolsBridge extends Bridge {
Uni<ToolResponse> execute(ToolManager.ToolArguments toolArguments, CallableReference toolReference);
}ResourceBridge
Contract for resource proxy implementations.
public interface ResourceBridge extends Bridge {
Uni<ResourceResponse> read(
ResourceManager.ResourceArguments arguments,
ResourceReference mcpResource
);
}ProvisionBridge
Contract for provisioning configuration and secrets.
public interface ProvisionBridge {
ProvisioningReference provision(
String name, String configData, String secretsData, ServiceTarget service
);
}CodeExecutorBridge
Contract for code execution bridge implementations.
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.
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.
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.
// 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>.
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.
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.
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 Type | Category |
|---|---|
StatusRuntimeException | SERVICE_UNAVAILABLE |
ServiceUnavailableException | SERVICE_UNAVAILABLE |
NullPointerException | TOOL_DEFINITION_ERROR |
IllegalArgumentException | INVALID_ARGUMENTS |
| Other | UNKNOWN |
ResourceAcquirerBridge
Package: ai.wanaku.backend.bridge
Proxy for resource acquisition services. Uses ProvisionerBridge for service resolution and the async transport for resource reading.
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.
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.
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.
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
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
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
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
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)
┌─────────────────────────────────────────────────────────────────┐
│ 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)
┌─────────────────────────────────────────────────────────────────┐
│ 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)
┌─────────────────────────────────────────────────────────────────┐
│ 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
┌─────────────────────────────────────────────────────────────────┐
│ 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
// 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
// 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 responsibilityService Locator Pattern
// ServiceResolver discovers services dynamically
ServiceTarget service = serviceResolver.resolve("my-tool", "tool-invoker");
// Bridges don't need hardcoded addressesIterator Wrapper Pattern
// Ensure cleanup even if iteration abandoned
try (CloseableCodeExecutionIterator iter = bridge.executeCode(...)) {
while (iter.hasNext()) {
CodeExecutionReply reply = iter.next();
// Process reply
}
} // Terminal event emitted on closeSecurity Features
| Feature | Implementation |
|---|---|
| Argument Redaction | Events omit actual argument values |
| Code Redaction | Code replaced with "[CODE]" in events |
| Error Sanitization | Safe error messages in responses |
| Plaintext (dev) | Default configuration for development |
| TLS Ready | Channel manager designed for future SSL/TLS |
File Locations
| Component | Path |
|---|---|
| Bridge.java | apps/wanaku-router-backend/.../bridge/ |
| ToolsBridge.java | apps/wanaku-router-backend/.../bridge/ |
| ResourceBridge.java | apps/wanaku-router-backend/.../bridge/ |
| CodeExecutorBridge.java | apps/wanaku-router-backend/.../bridge/ |
| ProvisionBridge.java | apps/wanaku-router-backend/.../bridge/ |
| McpBridge.java | apps/wanaku-router-backend/.../bridge/ |
| WanakuBridgeTransport.java | apps/wanaku-router-backend/.../bridge/ |
| InvokerBridge.java | apps/wanaku-router-backend/.../bridge/ |
| InvokerToolExecutor.java | apps/wanaku-router-backend/.../bridge/ |
| EventNotifier.java | apps/wanaku-router-backend/.../bridge/ |
| ResourceAcquirerBridge.java | apps/wanaku-router-backend/.../bridge/ |
| CodeExecutionBridge.java | apps/wanaku-router-backend/.../bridge/ |
| ProvisionerBridge.java | apps/wanaku-router-backend/.../bridge/ |
| DefaultMcpBridge.java | apps/wanaku-router-backend/.../bridge/ |
| ForwardClient.java | apps/wanaku-router-backend/.../bridge/ |
| ForwardRegistry.java | apps/wanaku-router-backend/.../bridge/ |
| ToolResponseTransformer.java | apps/wanaku-router-backend/.../bridge/ |
| ResourceResponseTransformer.java | apps/wanaku-router-backend/.../bridge/ |
| GrpcTransport.java | apps/wanaku-router-backend/.../bridge/transports/grpc/ |
| GrpcChannelManager.java | apps/wanaku-router-backend/.../bridge/transports/grpc/ |
| GrpcToolResponseTransformer.java | apps/wanaku-router-backend/.../bridge/transports/grpc/ |
| GrpcResourceResponseTransformer.java | apps/wanaku-router-backend/.../bridge/transports/grpc/ |
| provision.proto | core/core-exchange/src/main/proto/ |
| toolrequest.proto | core/core-exchange/src/main/proto/ |
| resourcerequest.proto | core/core-exchange/src/main/proto/ |
| codeexecution.proto | core/core-exchange/src/main/proto/ |