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-cligrpcurl-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
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:latestVerify:
grpcurl -versionChoose a descriptor source
| Source | When |
|---|---|
| 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.protoRunning
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.ServiceInvoke 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| Flag | Use |
|---|---|
-plaintext | No TLS (dev / internal) |
-insecure | TLS, but skip verification |
-cacert | CA certificate file |
-cert, -key | Client certificate + private key (mTLS) |
-H | Add header (repeat for multiple) |
-d | Inline JSON request payload |
-d @<file> | Read JSON from file |
-d @ | Read JSON from stdin |
-import-path | Proto import path |
-proto | Proto file path |
-protoset | Compiled 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/MethodStreaming 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}
EOMEach 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-streaming-test-author references/status-codes.mdParsing 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 existPer the status-code catalog in grpc-streaming-test-author (references/status-codes.md), NotFound (code 5) maps to HTTP 404 and indicates the resource isn't present. Exit code is non-zero.
Exit codes
| Exit code | Meaning |
|---|---|
| 0 | RPC succeeded |
| non-zero | RPC 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"
fiFor 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/GetUserExit 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
doneThis finds methods registered in reflection but returning Unimplemented (per the status-code catalog in grpc-streaming-test-author (references/status-codes.md)).
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
-insecure against production endpoints | MITM exposure; certificate validation bypass | Always provide -cacert |
| Reflection enabled in production | Schema disclosure; attack surface | Disable reflection in prod; use protoset distribution |
| Long-lived bearer tokens in CI scripts | Token leak via logs / artifacts | Short-lived tokens; mask in CI logs |
| Comparing stdout text across versions | grpcurl reformats; test brittleness | Compare JSON via -format=json + jq |
| Smoke-testing a write RPC | Side effects in prod / staging | Health-check + read-only smoke only |
Stream RPC with -d inline (no @) | Only one message sent | Use -d @ + stdin |
No -H "x-request-id" | Hard to trace from server logs | Always inject a unique ID for traceability |
--proto without --import-path for multi-file schemas | Import resolution fails | Set --import-path to project root |
Limitations
References
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 and carries the catalog of what is breaking and why (field-number reservation, wire-safe vs wire-incompatible changes, oneof/map constraints, the four buf categories) in references/versioning-strategy.md; 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-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. Also carries the interceptor-layer test patterns (Go / Java / grpc-js auth, retry, logging, error-mapping, chained ordering via a spy handler) in references/interceptors.md. Use when writing client-side tests that need a controllable gRPC server response (success cases, error cases, timeouts, single-response error injection) without spinning up a real backend, or when testing a gRPC interceptor. 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-streaming-test-author
The single gRPC-streaming test home: builds 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); carries the 17-code gRPC status catalog (retry semantics per AIP-194, grpc-gateway HTTP mapping) in references/status-codes.md and the wire-level / live-server streaming patterns (deadline propagation, server-side cancellation, metadata, ghz load) in references/wire-level-testing.md. Use when adding tests for a new or existing streaming RPC, auditing a suite for uncovered categories, or asserting status-code behavior. Different test surface from grpc-mock (the in-process harness itself).