Zylos LogoZylos
2026-05-13

ConnectRPC and Proto-First API Design for Agent-Native Backends

connectrpcprotobufapi-designai-agentsgolanggrpc

Executive Summary

The rise of AI agents as first-class API consumers is forcing a rethink of how we design backend services. Traditional REST+OpenAPI designs were built for human-authored clients, and GraphQL for flexible UI data fetching. Neither was designed with the machine-call patterns, semantic precision, and schema strictness that autonomous agents demand.

Proto-first API design — defining services in Protocol Buffers before writing any implementation code — has emerged as the strongest foundation for agent-native backends. When combined with ConnectRPC's dual-protocol architecture (gRPC and HTTP/JSON from a single handler), the approach produces APIs that are simultaneously debuggable by humans, callable by any standard HTTP client, efficient for service-to-service communication, and directly mappable to agent tool descriptions.

Key findings from this research:

  • ConnectRPC serves gRPC, gRPC-Web, and the Connect HTTP/JSON protocol from one handler with zero configuration, removing the traditional browser/backend protocol split
  • Proto comments are a direct input to AI agent tool descriptions — well-annotated .proto files measurably improve LLM tool-call accuracy
  • Google Cloud has proposed gRPC as a native transport for the Model Context Protocol (MCP), signaling industry convergence on proto/gRPC as the agent communication substrate
  • The Buf Schema Registry (BSR) solves multi-repo proto sharing at the dependency management level, the same way npm or Go modules solve package sharing
  • Semantic consistency — aligning product terms, API names, CLI commands, and agent tool identifiers — is a design principle with measurable impact on LLM reliability

ConnectRPC Architecture Overview

The Problem with gRPC's Complexity

ConnectRPC was built to address a fundamental tension in the gRPC ecosystem: the protocol's excellent ideas were buried under extraordinary implementation complexity. Google's gRPC-go library contains over 130,000 lines of code, nearly 100 configuration options, and is incompatible with Go's standard net/http ecosystem. This complexity raised the cost of adoption and created reliability surprises in production.

At the same time, gRPC-Web — the solution for browser access to gRPC services — required a dedicated proxy (Envoy or grpc-gateway) between the browser and the backend, adding operational overhead and a new failure point.

ConnectRPC, built by Buf (the company behind the Buf CLI and Buf Schema Registry), solves both problems by rethinking the protocol layer while keeping the best parts: Protocol Buffers as the schema language, and strong support for all RPC patterns (unary, server streaming, client streaming, bidirectional streaming).

Three Protocols, One Handler

The defining architectural feature of ConnectRPC is that a single Go handler automatically supports three protocols simultaneously, selected at request time via the HTTP Content-Type header:

gRPC Protocol

  • Content-Types: application/grpc, application/grpc+proto, application/grpc+json
  • Full compatibility with the standard gRPC ecosystem (grpcurl, Envoy, gRPC Gateway, all language implementations)
  • Required for service-to-service communication where HTTP/2 is available

gRPC-Web Protocol

  • Content-Types: application/grpc-web, application/grpc-web+proto, application/grpc-web+json
  • Handled natively in the ConnectRPC server — no proxy required
  • Enables browser access without infrastructure changes

Connect Protocol

  • Content-Types: application/proto, application/json (unary); application/connect+proto, application/connect+json (streaming)
  • A purpose-built POST-only protocol over HTTP/1.1 or HTTP/2
  • Unary calls work with standard curl, web browsers, or any HTTP client — no special tooling needed
  • Supports both binary Protobuf and JSON payloads interchangeably

The Go implementation is deliberately minimal — a single package, readable in an afternoon — built on net/http directly. Handlers are standard http.Handler instances; clients wrap http.Client. This makes ConnectRPC work with the entire Go HTTP middleware ecosystem out of the box.

The Developer Experience Improvement

A concrete example of how this simplifies development. Registering a service in Go:

// One handler, three protocols, no extra config
path, handler := greetv1connect.NewGreetServiceHandler(greeter)
mux.Handle(path, handler)

Once running, a developer can test the service with plain curl:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"name": "World"}' \
  https://localhost:8080/greet.v1.GreetService/Greet

The same endpoint accepts binary Protobuf from a gRPC client in production. This curl-friendliness is not a convenience feature — it is architecturally significant for agent-native backends, where AI agents often use HTTP JSON to call APIs.

Production Adoption and CNCF Membership

ConnectRPC is in production at CrowdStrike, PlanetScale, Redpanda, Chick-fil-A, Bluesky, and Dropbox. In early 2025, ConnectRPC joined the Cloud Native Computing Foundation (CNCF), providing vendor-neutral governance and a commitment to the open-source model.

A notable development in March 2026: Anthropic engineer Iain McGinniss open-sourced buffa (a pure-Rust Protocol Buffers implementation with zero-copy message views) and connect-rust (a Tower-based ConnectRPC client and server). Anthropic's own infrastructure now runs on ConnectRPC — a strong signal for the AI-agent ecosystem.


Proto-First API Design for Agent Platforms

Schema-Driven Development: The Core Principle

Proto-first design means writing the .proto file before writing any implementation. The Protocol Buffer definition becomes the contract that all parties — backend services, frontend clients, AI agents, CLI tools — are generated from. This is not merely an organizational preference; it has measurable engineering productivity and reliability benefits.

According to research cited by Buf, teams that adopt schema-driven development "boost engineering productivity significantly" while also reducing outages. A unified schema eliminates the divergence that occurs when API documentation, server implementation, and client code evolve independently.

The .proto file provides:

  • Message definitions: the shape of every request and response
  • Service definitions: every RPC method, its inputs, and outputs
  • Field semantics: names, types, and cardinality (required/optional/repeated)
  • Validation rules: via protovalidate, expressed as CEL annotations directly in the schema
  • Documentation: structured comments that propagate to generated code, OpenAPI specs, and agent tool descriptions

From One Proto File to Many Artifacts

A single .proto file can generate a complete API surface across multiple dimensions simultaneously:

greet.proto
├── buf generate → Go server stubs + client stubs
├── buf generate → TypeScript browser client
├── protoc-gen-connect-openapi → OpenAPI 3.1 spec
├── protoc-gen-go-mcp → MCP tool definitions (for AI agents)
└── buf lint + buf breaking → CI schema governance

This pipeline eliminates an entire category of synchronization bugs. OpenAPI documentation cannot drift from the implementation because they are generated from the same source. MCP tool descriptions cannot be out of date because they are re-generated on every build.

Proto Comments as Agent Tool Descriptions

The most direct connection between proto-first design and AI agent effectiveness is through comments. When generating MCP tool descriptions from .proto files, the tool's description, parameter documentation, and behavioral guidance come directly from the proto comments.

Research from EnterpriseDB's three-part series on building MCP servers from Protobuf found a direct, measurable relationship: when proto field comments explicitly marked required versus optional fields and included example payloads, AI agents:

  • Proactively gathered only the necessary information before calling a tool
  • Identified specific missing fields rather than asking vague clarifying questions
  • Significantly reduced failed tool calls caused by incomplete parameters

A poorly commented proto field like string author = 2; gives an AI agent nothing. A well-annotated field tells the agent exactly what it needs:

// Title of the book. Required. Example: "The Go Programming Language"
string title = 1;

// Author's full name. Required. Format: "First Last"
string author = 2;

// ISBN-13 of the book. Optional. Format: 978-XXXXXXXXXX
string isbn = 3;

The proto comment is not duplicated in a separate wiki, tool description file, or prompt. It lives in the schema, versioned alongside the code, and propagates automatically.

Best practices for AI-friendly proto comments:

  • Use imperative verbs for RPC descriptions: "Create a new booking" not "Booking creation endpoint"
  • Explicitly distinguish required vs. optional fields in the comment
  • Include compact JSON examples for nested object fields
  • Document constraint information (value ranges, string formats, allowed enum values)
  • Describe expected outputs and common failure modes

Protovalidate: Validation in the Schema

Protovalidate (reached v1.0 in 2025, with production trust from Microsoft, GitLab, Nike, CoreWeave, and others) moves field validation from scattered application code into the .proto schema itself, using Google's Common Expression Language (CEL):

message CreateUserRequest {
  string email = 1 [(buf.validate.field).string.email = true];
  uint32 age = 2 [(buf.validate.field).uint32 = {gte: 18, lte: 150}];
  string user_id = 3 [(buf.validate.field).string.uuid = true];
}

For agent-native backends, this is significant: validation rules documented in the schema are the same rules enforced at runtime, in every language. An AI agent reading the schema (or tool description generated from it) can understand not just the field name and type, but the complete validity contract.


Semantic Consistency: From Product to Protocol

The Alignment Problem

In most development teams, product managers name features one way, API designers name endpoints another way, CLI developers use yet another convention, and AI agent tool names are whatever the developer wrote in the day's prompt. Users experience a coherent product name, but underneath there is a vocabulary fragmentation that increases cognitive load for both human developers and AI systems.

For LLMs making tool calls, this fragmentation is not just inconvenient — it is a reliability problem. When a product feature is called "Workspace", the API endpoint is /workspaces, the CLI command is zylos ws, and the agent tool is manage_collab_spaces, the model must perform a multi-step translation that introduces error. Each translation is a potential failure point.

The Semantic Consistency Principle

Semantic consistency is the principle that a concept should carry the same name across all layers of a system:

  • Product documentation and UI labels
  • API service and method names in the proto schema
  • CLI command names
  • Agent tool identifiers and descriptions
  • Internal variable and function names in code

This is not about enforcing a naming convention — it is about using the proto schema as the shared vocabulary that all representations derive from. If the .proto defines service WorkspaceService with rpc CreateWorkspace, then the REST path is /workspace/v1/workspaces, the CLI command is workspace create, and the MCP tool is workspace_create.

The proto becomes the dictionary. Everything else is a translation of the proto vocabulary.

Measurable Impact on LLM Tool-Call Accuracy

Research in the MCP and gRPC integration space has confirmed that naming alignment has a direct impact on AI agent reliability. When tool names, parameter names, and descriptions mirror the product's natural language, models make fewer tool selection errors and construct parameters more accurately on the first attempt.

The practical mechanism: LLMs are trained on vast corpora of code, documentation, and API specifications where consistent naming correlates with correct behavior. When an API's vocabulary matches the product concept, the model can leverage its training signal more effectively. When names are arbitrary or abbreviated, the model must pattern-match without that support.

For a Human×Agent collaboration platform — where agents represent workflows that users understand by product name — this consistency is especially important. An agent that can refer to a "Workspace" by the same name the user uses it is easier to instruct, easier to debug, and easier to trust.

Implementation in Go with ConnectRPC

In practice, semantic consistency with ConnectRPC and Go looks like this:

// workspace/v1/workspace.proto
package workspace.v1;

service WorkspaceService {
  // CreateWorkspace creates a new collaborative workspace.
  // The workspace becomes immediately available to all assigned members.
  rpc CreateWorkspace(CreateWorkspaceRequest) returns (CreateWorkspaceResponse);

  // ListWorkspaces returns all workspaces the caller has access to.
  // Results are ordered by last activity, most recent first.
  rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse);
}

The generated Go package name is workspacev1, the handler is WorkspaceServiceHandler, and the client is WorkspaceServiceClient. A CLI built on the generated client uses workspace as its top-level command naturally. An MCP tool generated from this service is named workspace_create_workspace or workspace.CreateWorkspace depending on the generator — both immediately legible.


Multi-Repo Proto Sharing Patterns

The Dependency Problem

In a multi-repo architecture, proto schemas need to be shared across services that live in different repositories. Without a structured approach, teams resort to copy-pasting .proto files, using Git submodules, or simply declaring that all protos must live in the same repo. Each approach has real costs.

Protocol Buffers historically had no native dependency management. The ecosystem's answer is the Buf Schema Registry (BSR).

The Buf Schema Registry (BSR)

The BSR is a hosted registry for Protobuf schemas that works like npm for JavaScript or Go modules for Go. Teams push versioned proto modules to the registry, and dependent services declare those dependencies in buf.yaml:

# buf.yaml
version: v2
deps:
  - buf.build/myorg/common-types:v1.2.0
  - buf.build/myorg/workspace-api:main

Running buf dep update resolves and pins dependency versions, and buf generate fetches them automatically during code generation. No Git submodules. No copy-paste. No "which version of the proto are you using?" debugging sessions.

The BSR also automatically generates and publishes SDKs to npm, Go modules, Maven, and Swift Package Manager — so client teams can use their native package managers rather than running buf generate themselves.

Pattern 1: Dedicated Proto Repository

A standalone company-apis repository contains all .proto definitions. Service repositories depend on the central repo via BSR or Git. This pattern provides:

  • A single place to browse all APIs
  • Centralized lint and breaking-change enforcement in CI
  • One PR process for API changes (with cross-team review)

The cost is that API changes require touching two repositories (proto repo + implementation repo), adding coordination overhead.

Pattern 2: Proto Monorepo (Recommended for Most Teams)

Proto files live alongside implementation code in a multi-module monorepo. Buf workspaces manage the proto modules:

company-monorepo/
├── buf.yaml          # Workspace root, centralizes policy
├── services/
│   ├── workspace/
│   │   ├── buf.yaml  # Module definition
│   │   └── v1/
│   │       └── workspace.proto
│   └── billing/
│       ├── buf.yaml
│       └── v1/
│           └── billing.proto
└── shared/
    └── types/
        ├── buf.yaml
        └── common.proto

This approach scales linearly: adding a new service adds one module. Cross-service dependencies are declared in buf.yaml and resolved at build time. Breaking change detection runs in CI against the entire workspace.

As documented in the Buf ecosystem, the monorepo approach imposes a small fixed cost in terms of locality on service owners, but scales linearly with organizational size — whereas multiple repos impose coordination costs that scale polynomially.

Pattern 3: BSR as the Integration Layer

For larger organizations with truly independent teams, BSR can bridge multiple repositories without forcing a monorepo. Each team publishes their proto modules to BSR independently; dependent teams consume via BSR references. Breaking change enforcement runs at publish time (not just in CI), preventing incompatible updates from reaching dependents.

This pattern mirrors how large open-source ecosystems manage API evolution — the schema registry is the coordination point, not the repository structure.

Governance in All Patterns

Across all three patterns, buf lint and buf breaking are the CI enforcement layer:

  • buf lint: Enforces naming conventions, field numbering, and documentation requirements (can be configured to require comments on all public RPCs)
  • buf breaking: Compares the current schema against a baseline (the previous BSR version, the main branch, or a specific tag) and fails CI if any breaking changes are detected

For agent-native backends, the breaking change detection is especially valuable: if a field is removed or renamed, the generated MCP tool descriptions will also change — potentially breaking agent workflows. Catching this at CI prevents runtime surprises.


Comparison with Alternative Approaches

REST + OpenAPI

REST with OpenAPI 3.1 is the most mature and widely understood approach. It has excellent tooling (Swagger UI, Redoc, Stoplight), broad language support, and is the lingua franca of public API design.

For agent-native backends, REST+OpenAPI has significant limitations:

  • Schema defined after implementation: In practice, most REST APIs are documented after the fact, not designed schema-first. OpenAPI specs drift from implementation
  • No streaming standard: Server-sent events and WebSockets exist but lack the typed, contract-driven streaming support of gRPC
  • Type system is weaker: JSON Schema (OpenAPI's type system) lacks Protobuf's strictness — enums are strings, integer sizes are loosely defined
  • Tool description overhead: Converting OpenAPI to LLM tool descriptions requires a separate processing step; proto-to-MCP is a compile-time generation

REST+OpenAPI is still correct for:

  • Public APIs consumed by unknown third parties
  • APIs where human browseability and documentation quality are the primary concern
  • Simple CRUD services with no streaming requirements

GraphQL

GraphQL excels at flexible data fetching for UI-heavy applications — it lets clients specify exactly which fields they need, eliminating over-fetching. For agent-native backends, this flexibility becomes a liability:

  • Caching is structurally broken: GraphQL's variable query structure makes HTTP-level caching essentially impossible, a serious problem for high-frequency agent calls
  • Schema is not the single source of truth: GraphQL resolvers can return arbitrary data; the schema is a description, not a guarantee
  • No streaming standard: GraphQL subscriptions are a common extension but are not part of the core protocol
  • Tool descriptions require manual schema traversal: There is no standard way to convert a GraphQL schema into agent tool definitions

GraphQL is the right choice when the primary consumer is a UI that needs flexible data fetching — not an autonomous agent making thousands of typed RPC calls.

tRPC

tRPC provides end-to-end TypeScript type safety for full-stack TypeScript applications. It is excellent within its domain: TypeScript frontend + TypeScript backend, no cross-language support, no binary encoding, no streaming (beyond basic subscription support).

For Go backends serving polyglot clients and AI agents, tRPC is not applicable.

gRPC vs ConnectRPC

Native gRPC (without ConnectRPC) is a legitimate choice for internal service meshes where all clients speak gRPC natively. ConnectRPC's advantage over raw gRPC is developer experience:

  • Browser clients work without a proxy
  • Plain curl works for debugging
  • Standard HTTP middleware works without adaptation
  • The implementation is dramatically simpler (single package vs. 130,000 lines)

For agent-native backends specifically, ConnectRPC's HTTP/JSON support is critical: AI agents (Claude, GPT, Gemini) typically call APIs via HTTP JSON. A ConnectRPC backend serves these calls natively without any translation layer.

Summary Comparison Table

ConcernREST+OpenAPIGraphQLtRPCgRPCConnectRPC
Schema as single source of truthPartialPartialYes (TS)YesYes
Browser-nativeYesYesYesNoYes
Streaming supportWeakPartialWeakStrongStrong
Binary efficiencyNoNoNoYesYes (with proto)
Agent tool generationManualManualN/AAutoAuto
Multi-language codegenManualPartialNoYesYes
Breaking change detectionManual/toolingManualType systemManualbuf breaking
curl/HTTP debugYesPartialYesNoYes

Practical Implementation Guide

Setting Up a ConnectRPC + Go Backend

1. Define the schema

// workspace/v1/workspace.proto
syntax = "proto3";
package workspace.v1;

import "buf/validate/validate.proto";

// WorkspaceService manages collaborative workspaces in the COCO platform.
service WorkspaceService {
  // CreateWorkspace creates a new workspace for human-agent collaboration.
  // The workspace owner is automatically assigned the Admin role.
  rpc CreateWorkspace(CreateWorkspaceRequest) returns (CreateWorkspaceResponse);

  // GetWorkspace retrieves a workspace by its unique ID.
  // Returns NOT_FOUND if the workspace does not exist or the caller lacks access.
  rpc GetWorkspace(GetWorkspaceRequest) returns (GetWorkspaceResponse);
}

message CreateWorkspaceRequest {
  // Display name shown in the UI and referenced by agents. Required.
  // Example: "Q4 Planning" or "Engineering Onboarding"
  string name = 1 [(buf.validate.field).string = {min_len: 1, max_len: 100}];

  // Type of workspace. Required. One of: "project", "team", "personal"
  string workspace_type = 2 [(buf.validate.field).string = {in: ["project", "team", "personal"]}];
}

2. Configure code generation

# buf.gen.yaml
version: v2
plugins:
  - remote: buf.build/protocolbuffers/go
    out: gen/go
    opt: paths=source_relative
  - remote: buf.build/connectrpc/go
    out: gen/go
    opt: paths=source_relative

3. Implement and register the handler

func main() {
    mux := http.NewServeMux()
    
    // Single handler serves gRPC, gRPC-Web, and Connect protocol
    path, handler := workspacev1connect.NewWorkspaceServiceHandler(
        &WorkspaceServer{},
        connect.WithInterceptors(authInterceptor, loggingInterceptor),
    )
    mux.Handle(path, handler)
    
    // Standard net/http server — works with any middleware
    http.ListenAndServeTLS(":8080", certFile, keyFile, h2c.NewHandler(mux, &http2.Server{}))
}

Generating MCP Tools from Proto

Using protoc-gen-go-mcp (open-sourced by Redpanda):

buf generate --template buf.gen.mcp.yaml

The generated MCP server maps each RPC to an MCP tool. The tool's description comes from the RPC comment; parameter descriptions come from field comments. The entire MCP server is kept in sync with the proto schema automatically.

CI Schema Governance

# .github/workflows/proto.yml
- name: Lint proto schemas
  run: buf lint

- name: Check for breaking changes
  run: buf breaking --against ".git#branch=main"

- name: Generate code
  run: buf generate

- name: Verify generated code is up to date
  run: git diff --exit-code gen/

Key Takeaways

1. ConnectRPC removes the browser/backend protocol split. A single handler serves gRPC (for internal services), HTTP/JSON (for browsers and AI agents), and gRPC-Web (for legacy browser setups) without configuration. This eliminates the proxy tax of traditional gRPC deployments.

2. Proto-first design makes the schema the single source of truth. Server stubs, client libraries, OpenAPI specs, validation rules, and MCP tool definitions are all generated from one .proto file. Drift between documentation and implementation becomes a compile-time error, not a runtime surprise.

3. Proto comments are agent instructions. The quality of proto comments directly affects AI agent tool-call accuracy. Imperative descriptions, explicit field requirements, and compact examples in comments propagate to tool descriptions and reduce failed agent interactions.

4. Semantic consistency is a force multiplier. Aligning product terms, API names, CLI commands, and agent tool identifiers reduces the translation burden on both human developers and LLMs. The .proto file is the natural home for this shared vocabulary.

5. The Buf Schema Registry solves multi-repo proto sharing. BSR brings package-manager semantics to proto schemas: declared dependencies, versioned modules, automated SDK publication, and breaking-change enforcement at publish time.

6. gRPC/proto is becoming the agent communication standard. Google Cloud's proposal to use gRPC as a native transport for MCP, combined with Anthropic's connect-rust and buffa projects, signals industry convergence on proto/gRPC as the substrate for agent-to-service communication. Organizations building agent-native backends today on ConnectRPC are aligning with this trajectory.

7. For Human×Agent collaboration platforms, the proto-first approach enables a particularly important guarantee: agents and humans interact with the same service definition. The product concept of a "Workspace" is the API concept, is the CLI concept, is the agent tool concept — one name, zero translation.


Research conducted May 2026. Sources: ConnectRPC official documentation, Buf blog, EnterpriseDB MCP+Protobuf series, Google Cloud blog, Redpanda blog, Buf Schema Registry documentation, gRPC.io blog, protobuf.ai, InfoQ, and comparative API design resources from 2025-2026.