# Edit Protobuf and gRPC

> Change gRPC, gRPC-Web, Connect and Protobuf messages as JSON in breakpoints, Compose, Map Local and scripts; Cloak Proxy encodes them back to Protobuf and shows each call's gRPC status.

Source: https://docs.subcloak.com/cloak-proxy/inspect/protobuf-and-grpc/

Added in Cloak Proxy 0.4.0.

Protobuf bodies are binary, so you can't edit them like JSON. Cloak Proxy shows them as JSON wherever you can change a body: a paused [breakpoint](https://docs.subcloak.com/cloak-proxy/tools/breakpoint/), [Compose](https://docs.subcloak.com/cloak-proxy/tools/repeat-and-compose/), a [Map Local](https://docs.subcloak.com/cloak-proxy/tools/map-local/) rule and a [script](https://docs.subcloak.com/cloak-proxy/tools/scripting/). When you send it, the JSON is encoded back to Protobuf and framed the way the call expects (gRPC, gRPC-Web or Connect). You can mock an edge case of a gRPC API without writing code.

With your [.proto files imported](https://docs.subcloak.com/cloak-proxy/inspect/body-viewers/#decode-with-your-proto-files), messages use field names. Without them, fields are keyed by number, in a form that encodes back to exactly the same bytes.

*[Screenshot: The Breakpoint window with a paused gRPC-Web response from shop.v1.Orders/GetOrder. A purple bar says gRPC-Web, field numbers (no schema), Edit as JSON; it's encoded again on Execute. Below, the body as JSON: 1 is fc68188e, 2 is 4999, 3 is 2, 4 is a list of Trail running shoes and Socks, and 5:float is 1.5.. A paused gRPC-Web response, edited as JSON. Execute encodes it again.]*

## What Cloak Proxy reads

| Content-Type | Framing | Status |
|---|---|---|
| `application/grpc`, `application/grpc+proto` | gRPC: length-prefixed messages | In the `grpc-status` header or HTTP/2 trailers |
| `application/grpc-web`, `application/grpc-web+proto` | gRPC-Web: the same frames, then a trailer frame | In the trailer frame, inside the body |
| `application/grpc-web-text`, `…+proto` | gRPC-Web as base64 text | In the trailer frame |
| `application/connect+proto` | Connect streaming: frames, then an end-of-stream message | In the end-of-stream JSON |
| `application/proto` with a `Connect-Protocol-Version` header | Connect unary: one bare message | The HTTP status, with a JSON `{code, message}` body for errors |
| `application/x-protobuf`, `application/protobuf`, `application/vnd.google.protobuf` | One bare message | None |

Messages compressed with gzip or deflate are decompressed to read and edit them, and sent on uncompressed, which every gRPC peer accepts.

## The gRPC status

gRPC answers HTTP 200 whatever happened; the call's own result is its gRPC status. Cloak Proxy finds it in the headers, in a gRPC-Web trailer frame or in a Connect end-of-stream message.

| Where | What you see |
|---|---|
| Traffic list | A failed call's Status shows **200 ✕** in red and its dot turns red. Hover for the status name and message. |
| Inspector header | A **gRPC NOT_FOUND** pill (green for **gRPC OK**) next to the HTTP status. Hover for the code and message. |
| Body | The decode note starts with the status, for example **Status NOT_FOUND · gRPC-Web without a schema: fields by number**. A body with only a status shows it as `{status, message}`. |

*[Screenshot: The inspector of a gRPC-Web call: a 200 OK pill next to a red gRPC NOT_FOUND pill, and a body showing status NOT_FOUND and message Order fc68188e not found.. The HTTP status says 200; the gRPC status says the order wasn't found.]*

## Edit messages

1. In a breakpoint, a paused gRPC, gRPC-Web, Connect or Protobuf body opens as JSON under a purple bar. The bar says how it's framed, the message type (or **field numbers (no schema)**) and how many messages it holds.
2. Edit the JSON. The editor's **Tree** view works too.
3. Click **Execute**. Cloak Proxy encodes the messages, frames them as before and keeps the trailers. If the JSON doesn't fit (an unknown field, an enum value that doesn't exist, a decimal in a whole-number field), the bar shows why and nothing is sent.

A body you don't change is sent byte for byte. A stream with several messages is a JSON array, one item per message; add or remove items to change how many are sent.

### Field numbers without a schema

Without a schema, each key is a field number, with the wire type after a colon when the value alone doesn't say it:

| Key and value | Encodes as |
|---|---|
| `"1": 150` | A varint. Negative numbers are written as 64-bit two's complement, as `int32` and `int64` are. |
| `"2": "Ada Park"` | Length-delimited text |
| `"3": {"1": 7}` | A nested message |
| `"4:bytes": "3q2+7w=="` | Length-delimited bytes, in base64 |
| `"5:float": 1.5`, `"5:fixed32": 7` | 32-bit |
| `"6:double": 2.5`, `"6:fixed64": 7` | 64-bit |
| `"7:sint64": -2` | A zigzag varint (`sint32`, `sint64`) |
| `"8": [1, 2, 3]` | A repeated field: one value per occurrence |

Packed repeated fields arrive as one length-delimited value, so they show as `bytes`. [Import the .proto file](https://docs.subcloak.com/cloak-proxy/inspect/body-viewers/#decode-with-your-proto-files) to edit them as lists.

### With a schema

With an imported schema, messages use the Protocol Buffers JSON mapping: field names, enums by name, 64-bit numbers as strings. The type is the one picked for the URL under **Decode As**, or the gRPC method's input or output type. Unknown fields and enum values are refused, so a typo can't silently drop a field.

## Write Protobuf from JSON

### Compose

Set the request's `Content-Type` to a Protobuf type (for example `application/grpc-web+proto`), then write the body as JSON. A purple bar above the body says it will be encoded. When you send, the JSON is encoded with the URL's message type, or by field number, and framed. **Edit and Repeat** on a captured Protobuf request opens its body as JSON.

### Map Local

A Map Local rule can answer a gRPC call with a message written as JSON.

*[Screenshot: A Map Local rule for shop.v1.Orders/GetOrder answering with Content-Type application/grpc-web+proto, the option Write the body as JSON and send it as Protobuf turned on, Message type empty (by field number), gRPC status 0 OK, and the body as JSON with fields 1 to 4.. A gRPC-Web answer written as JSON, with its status.]*

1. In the rule's **Respond with › Inline**, set the `Content-Type` header to the type the app expects.
2. Turn on **Write the body as JSON and send it as Protobuf**.
3. Enter a **Message type** from your imported schemas, or leave it empty to write fields by number (or to use the type the gRPC path gives).
4. For gRPC-Web, Connect and gRPC, pick the **gRPC status** and a message, for example **5 NOT_FOUND** to test the app's error handling. gRPC-Web puts it in a trailer frame, Connect in the end-of-stream message, and gRPC in the `grpc-status` header.
5. Write the body: an object for one message, an array for several (a stream), or `[]` for a reply that is only a status.

The editor checks the body as you type. If a request matches while the body doesn't encode, the app gets a 500 that says why.

### Scripts

A script's `request` and `response` get a `protobuf` property for Protobuf bodies: the message as JSON (an array for several), with field names or field numbers as above. Change it, and the body is encoded again:

```js title="Script"
function onResponse(context, url, request, response) {
  response.protobuf.status = "CANCELLED"; // with a schema
  // response.protobuf["3"] = 4;          // without one
  return response;
}
```

`body` still holds the raw body as text; leave it alone when you change `protobuf`.

## With an AI assistant

The `get_protobuf` MCP tool returns a captured body as JSON with its framing, message type, gRPC method, every message of a stream, and the gRPC status and trailers. To change a reply, an assistant can create a Map Local rule with `upsert_rule`: `headers` with the Content-Type, `body` as the JSON, and `protobuf: {messageType, grpcStatus, grpcMessage}`.

> Make the app's GetOrder call fail with NOT_FOUND, using the response of request #42 as the starting point.

## Limits

- gRPC over HTTP/2 carries its status in trailers. A Map Local answer sends it in the headers instead, which gRPC clients accept for a reply with no messages (trailers-only); with messages, most clients read it too, but some expect a trailer.
- WebSocket messages that carry Protobuf are shown, not edited, as Protobuf.
- Groups (a deprecated wire type) can't be edited without a schema.

## Related

  - [Body viewers](https://docs.subcloak.com/cloak-proxy/inspect/body-viewers/#protobuf-and-grpc): Decode Protobuf and gRPC, and import .proto files.
  - [Breakpoint](https://docs.subcloak.com/cloak-proxy/tools/breakpoint/): Pause requests and responses to edit them.
  - [Map Local](https://docs.subcloak.com/cloak-proxy/tools/map-local/): Answer requests with a local response.
  - [gRPC client](https://docs.subcloak.com/cloak-proxy/api-client/protocols/grpc/): Call gRPC services from the API client.
