MCP
The MCP (Model Context Protocol) bridge exposes proto methods annotated with gortexa.ai.v1 as tools. An AI agent sends JSON-RPC to /mcp, and the bridge calls the original service over an in-process gRPC loopback — so AI calls pass through the same interceptor chain as everything else, auth and validation included.
Annotating methods
Section titled “Annotating methods”Add the gortexa.ai.v1.ai_tool annotation to an rpc:
rpc GetResource(GetResourceRequest) returns (Resource) { option (google.api.http) = {get: "/v1/resources/{id}"}; option (gortexa.ai.v1.ai_tool) = { expose: true name: "get_resource" description: "Fetch a single resource by id and return its current state." read_only: true };}read_only and destructive feed the behaviour hints in the tool schema. Methods without expose: true do not appear in the tools list.
Endpoint
Section titled “Endpoint”- Path:
/mcp(Streamable HTTP) - Protocol versions:
2025-03-26(default) and2024-11-05 - Request bodies capped at 1 MiB, read timeout 30 seconds
- JSON-RPC batches capped at 100 elements; a larger batch gets 413 with a
-32000 "batch too large"JSON-RPC error (rejected before the interceptor chain runs) - The bridge is stateless: it issues no
Mcp-Session-Idand keeps no session state; every request is authorized on its own credentials - A rejected
Originand an over-cap batch are each logged at WARN throughslog.Default()(with the origin / element count and the client IP) — previously both were answered with a status code and no operator-visible record - For browser-based clients, enable
server.enable_corsand list the allowed origins inserver.cors_origins— that allowlist is the DNS-rebinding guard; a request whoseOriginis not listed gets403 forbidden origin, and a"*"entry is ignored here (list each browser origin explicitly)
tools/list
Section titled “tools/list”curl -XPOST localhost:8080/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Returns the tool schema for every annotated method. The list is memoized (about 142 ns per call), not recomputed on every request.
tools/call
Section titled “tools/call”Arguments to tools/call are serialized into a protobuf message and dispatched to the original service over the loopback; the result is converted back to JSON-RPC. A call that fails auth gets an unauthenticated error envelope, consistent with gRPC and HTTP behaviour. Authorization and X-Request-Id are forwarded to the loopback only when their bytes are valid gRPC metadata (printable ASCII); an invalid value is dropped rather than propagated, so the caller still gets the chain’s real answer (e.g. unauthenticated) instead of an opaque internal, and a fresh request id is minted downstream. An Authorization value longer than 8 KiB is dropped too: a real credential is a few KB at most, and the bridge re-encodes the header onto one loopback call per batch element (up to 100), so an oversized one is an amplification vector rather than a credential.
Schema export
Section titled “Schema export”The same gortexa.ai.v1 annotations can be exported offline in provider-ready formats:
gortexa export --format=mcpgortexa export --format=openai # OpenAI strict function callinggortexa export --format=geminiAll three outputs are locked by golden tests: when the proto changes, the schemas change, and unexpected drift fails the tests.
How do I expose a gRPC service as MCP tools for an LLM?
Section titled “How do I expose a gRPC service as MCP tools for an LLM?”Annotate the proto methods with gortexa.ai.v1, then add the service to mcp.ServiceDescriptors(...) and mount mcp.NewBridge at /mcp. The tool schema is generated from the proto — there is no separate tool definition to hand-write.
Do MCP tool calls go through my auth interceptors?
Section titled “Do MCP tool calls go through my auth interceptors?”Yes. tools/call is dispatched over an in-process loopback into the same gRPC server, so it traverses recover, request-id, logging, load shedding, rate limiting, the circuit breaker, auth and validation exactly like any other request.
Does the MCP endpoint need sessions?
Section titled “Does the MCP endpoint need sessions?”No. The bridge is stateless: it issues no Mcp-Session-Id and keeps none, and every request is authorized on its own credentials.
How do I configure a browser-based MCP client?
Section titled “How do I configure a browser-based MCP client?”Enable server.enable_cors and list the allowed origins in server.cors_origins. That allowlist doubles as the DNS-rebinding guard — a request from an unlisted origin is rejected with 403 forbidden origin and logged at WARN. A "*" entry, which CORS treats as allow-all, is deliberately ignored by this guard, so list each browser origin explicitly.