Testland
Browse all skills & agents

grpcurl-cli

Wraps grpcurl, the curl-equivalent CLI for gRPC. Covers descriptor sources (server reflection default, --import-path + --proto for proto files, --protoset for compiled descriptor sets), service discovery (`list`, `describe`), invoking unary RPCs (`-d '{...}'`, `-d @file.json`, `-d @` for stdin), streaming RPCs (newline-delimited JSON via stdin), TLS configuration (--cacert, --cert, --key, --insecure, --plaintext), header injection (-H 'Authorization: Bearer ...'), and exit codes. Use for ad-hoc gRPC debugging, smoke testing, scriptable PR-time gates, and CLI-based interaction with reflective gRPC services.

Install with skills.sh (any agent)

npx skills add testland/qa --skill grpcurl-cli
View source

grpcurl-cli

Overview

Per github.com/fullstorydev/grpcurl (opens in new window), grpcurl can invoke any gRPC method using one of three descriptor sources (server reflection, .proto source files, or compiled .protoset files) and emits JSON responses. This skill wraps grpcurl for: ad-hoc debugging, scriptable smoke tests, and CI-based contract verification.

When to use

  • Manually exercising a gRPC server during development.
  • Smoke test in CI: "can the deployed server respond to this RPC?"
  • Reproducing a production bug locally before writing a test.
  • Inspecting a third-party gRPC service that supports reflection.
  • Verifying gRPC-to-HTTP gateway behaviour by comparing grpcurl
    • HTTP curl side by side.

Authoring

Install

Per grpcurl README (opens in new window):

# Homebrew
brew install grpcurl

# Go install
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

# Docker
docker pull fullstorydev/grpcurl:latest

Verify:

grpcurl -version

Choose a descriptor source

SourceWhen
Server reflection (default)Server implements the grpc.reflection.v1 reflection service
Proto source files (-import-path + -proto)Reflection disabled; you have the .proto files
Protoset file (-protoset)Pre-compiled descriptor distributed alongside the binary

Server reflection is the easiest but security-sensitive - many production deployments disable it (reflection.Register(s) not called). Plan accordingly.

Generate a protoset if neither reflection nor proto files are available at call time:

protoc --proto_path=. \
       --descriptor_set_out=myservice.protoset \
       --include_imports \
       my/custom/server/service.proto

Running

Discover

Per grpcurl README (opens in new window):

# List services
grpcurl -plaintext localhost:8787 list

# List methods in a service
grpcurl -plaintext localhost:8787 list my.custom.server.Service

# Describe a method (full schema)
grpcurl -plaintext localhost:8787 describe my.custom.server.Service.MethodOne

# Export proto schemas
grpcurl -plaintext -proto-out-dir "out_protos" localhost:8787 \
    describe my.custom.server.Service

# Export a protoset
grpcurl -plaintext -protoset-out "out.protoset" localhost:8787 \
    describe my.custom.server.Service

Invoke unary RPCs

# Plaintext (no TLS)
grpcurl -plaintext \
    -d '{"id": 1234, "tags": ["foo","bar"]}' \
    localhost:8080 my.custom.server.Service/Method

# TLS with server-only cert
grpcurl -cacert ca.crt \
    -d '{"id": 1234}' \
    grpc.example.com:443 my.custom.server.Service/Method

# Mutual TLS
grpcurl -cacert ca.crt -cert client.crt -key client.key \
    -d '{"id": 1234}' \
    grpc.example.com:443 my.custom.server.Service/Method
FlagUse
-plaintextNo TLS (dev / internal)
-insecureTLS, but skip verification
-cacertCA certificate file
-cert, -keyClient certificate + private key (mTLS)
-HAdd header (repeat for multiple)
-dInline JSON request payload
-d @<file>Read JSON from file
-d @Read JSON from stdin
-import-pathProto import path
-protoProto file path
-protosetCompiled descriptor set

Auth headers

grpcurl -H "Authorization: Bearer ${TOKEN}" \
    -H "x-request-id: $(uuidgen)" \
    -d '{"id": 1234}' \
    grpc.example.com:443 my.custom.server.Service/Method

Streaming RPCs

Per grpcurl README (opens in new window): for bidi or client-streaming, use newline-delimited JSON via -d @:

grpcurl -plaintext \
    -d @ \
    localhost:8080 my.custom.server.Service/StreamMethod << EOM
{"request": 1}
{"request": 2}
{"request": 3}
EOM

Each line is one message to the server. Closing stdin signals end-of-stream.

Reproducing a production bug

# Capture failing request from logs / tracing
PAYLOAD=$(cat trace-payload.json)

# Reproduce against a staging instance
grpcurl -cacert ca.crt \
    -H "Authorization: Bearer ${TOKEN}" \
    -d "${PAYLOAD}" \
    staging.example.com:443 my.custom.server.Service/Method

# Compare status code per grpc-status-code-mapping-reference

Parsing results

Successful response

{
  "id": "1234",
  "name": "alice",
  "createdAt": "2026-05-01T10:30:00Z"
}

Exit code 0 → RPC succeeded (gRPC OK).

Error response

ERROR:
  Code: NotFound
  Message: User 1234 does not exist

Per grpc-status-code-mapping-reference, NotFound (code 5) maps to HTTP 404 and indicates the resource isn't present. Exit code is non-zero.

Exit codes

Exit codeMeaning
0RPC succeeded
non-zeroRPC failed (Code printed to stderr)

For scripting:

if grpcurl -plaintext -d '{"id":"x"}' \
    localhost:8080 my.Service/GetUser > /tmp/out 2>&1; then
    echo "RPC ok"
else
    code=$(grep "Code:" /tmp/out | awk '{print $2}')
    echo "RPC failed: $code"
fi

For richer parsing in CI: -format=json and use jq:

grpcurl -plaintext -format=json -d '{"id":"x"}' \
    localhost:8080 my.Service/GetUser \
    | jq '.id'

CI integration

Smoke test on deploy

# .github/workflows/grpc-smoke.yml
name: grpc-smoke
on:
  deployment_status:

jobs:
  smoke:
    if: github.event.deployment_status.state == 'success'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - name: Install grpcurl
        run: |
          # release assets are version-stamped; bump the tag on upgrade
          curl -L https://github.com/fullstorydev/grpcurl/releases/download/v1.9.3/grpcurl_1.9.3_linux_x86_64.tar.gz | tar xz
          sudo mv grpcurl /usr/local/bin/
      - name: Verify health endpoint
        run: |
          grpcurl -cacert ./certs/ca.crt \
              ${{ github.event.deployment.payload.host }}:443 \
              grpc.health.v1.Health/Check
      - name: Sample request
        env:
          TOKEN: ${{ secrets.SMOKE_TEST_TOKEN }}
        run: |
          grpcurl -cacert ./certs/ca.crt \
              -H "Authorization: Bearer ${TOKEN}" \
              -d '{"id":"smoke-test-1"}' \
              ${{ github.event.deployment.payload.host }}:443 \
              user.v1.UserService/GetUser

Exit code 0 → deploy is verified responsive. Non-zero → alert.

Reflection-based smoke matrix

Where reflection is enabled, smoke-test every RPC:

SERVICES=$(grpcurl -plaintext localhost:8080 list | grep -v '^grpc')
for service in $SERVICES; do
  METHODS=$(grpcurl -plaintext localhost:8080 list "$service")
  for method in $METHODS; do
    # Skip unary methods that require payload
    grpcurl -plaintext "localhost:8080" "$method" 2>&1 \
      | grep -q "Code: \(Unimplemented\|InvalidArgument\)" || \
        echo "WARN: $method may be unreachable"
  done
done

This finds methods registered in reflection but returning Unimplemented (per grpc-status-code-mapping-reference).

Anti-patterns

Anti-patternWhy it failsFix
-insecure against production endpointsMITM exposure; certificate validation bypassAlways provide -cacert
Reflection enabled in productionSchema disclosure; attack surfaceDisable reflection in prod; use protoset distribution
Long-lived bearer tokens in CI scriptsToken leak via logs / artifactsShort-lived tokens; mask in CI logs
Comparing stdout text across versionsgrpcurl reformats; test brittlenessCompare JSON via -format=json + jq
Smoke-testing a write RPCSide effects in prod / stagingHealth-check + read-only smoke only
Stream RPC with -d inline (no @)Only one message sentUse -d @ + stdin
No -H "x-request-id"Hard to trace from server logsAlways inject a unique ID for traceability
--proto without --import-path for multi-file schemasImport resolution failsSet --import-path to project root

Limitations

  • Reflection-only is fragile. Production-disabling reflection is standard; smoke matrices need protoset distribution.
  • JSON ↔ proto conversion has edge cases. int64 numbers large enough to lose precision in JSON become strings (per proto-JSON spec); be deliberate.
  • No replay mode. Can't replay a captured trace; one invocation per call.
  • No throughput testing. For load see ghz-load.
  • No mock server mode. For mocking see grpc-mock.
  • Streaming feedback is line-buffered. Server-streaming responses print as they arrive but stdin flushing can delay client-side messages.

References

  • grpcurl README: github.com/fullstorydev/grpcurl (opens in new window).
  • Companion catalogs: protobuf-versioning-strategy-reference, grpc-status-code-mapping-reference.
  • Sibling tools: buf-cli-lint-breaking-build, ghz-load, grpc-mock, grpc-streaming-test-author.
  • Sibling protocol-test plugin: grpc-streaming-tests (in the qa-realtime-protocols plugin; wire-level streaming semantics).

Related skills

buf-cli-lint-breaking-build

Wraps the buf CLI for protobuf PR gating: `buf build` (compile .proto), `buf lint` (STANDARD rules: snake_case fields, Service suffix), `buf breaking --against {ref}` (detect wire/codegen breakage vs a git/BSR baseline), and `buf format`. Use as the CI proto-lint + breaking-change gate, or to debug a breaking failure by rule ID (e.g. FIELD_NO_DELETE_UNLESS_NUMBER_RESERVED) and pick the FILE/PACKAGE/WIRE_JSON/WIRE ruleset per consumer. This is the detection TOOL that enforces the rules; for the catalog of what is breaking and why use protobuf-versioning-strategy-reference, and for cross-service schema contract testing use protobuf-compat-checking - not this.

ghz-load

Wraps ghz, the gRPC load testing tool, for throughput and latency benchmarking. Covers test invocation (--proto + --call + host:port; or --protoset for compiled descriptors), load parameters (-n total requests, -c concurrency, -r RPS rate limit, -z duration), output formats (json/csv/html/influx-summary for CI consumption), the metrics reported (RPS achieved, latency p50/p95/p99, status-code distribution, errors), and CI integration patterns for regression gating. Use when benchmarking a gRPC service's throughput or detecting latency regressions in CI.

grpc-interceptor-test-author

Authors unit tests for gRPC interceptor logic: Go grpc.UnaryServerInterceptor/UnaryClientInterceptor, Java ServerInterceptor/ClientInterceptor, and grpc-js client interceptors. Covers auth (Unauthenticated on bad token), retry (backoff on Unavailable), logging/tracing (metadata extraction + propagation), error-mapping (status translation), and chained interceptor ordering - by calling the interceptor directly with a spy handler, no live backend. Use when a gRPC interceptor is written or modified. Different test surface from grpc-streaming-test-author (multi-message stream sequences) and grpc-mock (service handler logic) - use those, not this, for streams or handlers.

grpc-mock

Wraps gRPC server-mocking patterns for client-side tests: Go bufconn (in-memory net.Listener via google.golang.org/grpc/test/bufconn) + mockgen-generated interface mocks, Python pytest-grpc fixtures + unittest.mock patching of stubs, JVM grpc-mock library / in-process gRPC server (InProcessServerBuilder), Node @grpc/grpc-js fake server with NewServer-on-port-0. Use when writing client-side tests that need a controllable gRPC server response (success cases, error cases per grpc-status-code-mapping-reference, timeouts, and single-response error injection) without spinning up a real backend. For multi-message streaming-sequence tests (server-streaming, bidi), use grpc-streaming-test-author instead. Distinct from grpcurl-cli (ad-hoc CLI invocation against a real server) and ghz-load (perf against a real server).

grpc-status-code-mapping-reference

Pure-reference catalog of gRPC standard status codes - the 17 canonical codes (OK..UNAUTHENTICATED), their numeric values, semantics, retry behaviour per AIP-194 (only UNAVAILABLE is auto-retry-safe), and the gRPC-to-HTTP status mapping used by grpc-gateway (NOT_FOUND→404, INVALID_ARGUMENT→400, PERMISSION_DENIED→403, UNAUTHENTICATED→401, RESOURCE_EXHAUSTED→429, FAILED_PRECONDITION→400 not 412, ABORTED→409, UNAVAILABLE→503, DEADLINE_EXCEEDED→504, etc.). Use when designing a gRPC service's error vocabulary, writing assertions in gRPC client tests, configuring retry policies, or mapping gRPC errors to HTTP via a gateway. Consumed by buf-cli-lint-breaking-build, ghz-load, grpcurl-cli, grpc-mock, grpc-streaming-test-author.

grpc-streaming-test-author

Workflow-driven skill that builds gRPC streaming-RPC test suites from a proto definition. Classifies each RPC by pattern (unary, server-streaming, client-streaming, bidi), then emits the required categories per pattern - ordering preservation, completion semantics (server close after stream end, client half-close), cancellation, deadline handling, partial-stream failure. Produces skeletons for Go (bufconn + Send/Recv), Python (iterators), JVM (StreamObserver), Node (call.write/end). Use when adding tests for a new streaming RPC or auditing a suite for uncovered categories. Different test surface from grpc-interceptor-test-author (interceptor layer) and grpc-mock (harness); for wire-level streaming semantics use grpc-streaming-tests, not this.

protobuf-versioning-strategy-reference

Pure-reference catalog of protobuf3 versioning and breaking-change rules: field-number reservation (reserve on delete; 1..536870911; 19000-19999 reserved), wire-safe vs wire-incompatible changes (add/remove safe with reservation; changing a field number always breaks), compatible type conversions (int32/uint32/int64/uint64/bool; sint32/sint64; string/bytes for UTF-8; enum/int), oneof + map constraints, and buf's four breaking categories (FILE/PACKAGE/WIRE_JSON/WIRE) with rule IDs. Use when designing a schema change or picking a buf breaking ruleset. This is the catalog of what is breaking and why, not a scanner; to detect changes in CI use buf-cli-lint-breaking-build, for the gRPC status-code vocabulary use grpc-status-code-mapping-reference, and for cross-service contract testing use protobuf-compat-checking.