# JSON-RPC 2.0 and MCP Server Testing

> Shift-Left Studio tests JSON-RPC 2.0 services and Model Context Protocol (MCP) servers alongside your REST, SOAP and GraphQL APIs. An MCP server is a JSON-RPC 2.0 server with a fixed set of methods, so the same capability covers both.

Source: https://totalshiftleft.ai/help-center/product-documentation/json-rpc-and-mcp-testing

> **Applies to:** Trial and Enterprise editions · Web app and Desktop app

## Overview

Shift-Left Studio tests JSON-RPC 2.0 services and Model Context Protocol (MCP) servers alongside your REST, SOAP and GraphQL APIs. An MCP server is a JSON-RPC 2.0 server with a fixed set of methods, so the same capability covers both.

JSON-RPC behaves differently from REST in one important way: **a failed call usually comes back with HTTP 200.** The transport succeeded; the operation did not, and the failure is described inside the response body. A test that only checks the status code will pass on a call that failed. Studio therefore gives JSON-RPC its own assertions that look inside the response, and generated tests use them automatically.

You can bring JSON-RPC services into a project from an OpenRPC document, or connect to a live MCP server and choose which of its tools to test. Each method or tool becomes its own endpoint, with generated positive and negative tests.

## Key concepts

| Term | Meaning |
|---|---|
| **Method** | The operation name inside a JSON-RPC request, such as `getBalance`. Every method on a service shares one URL, so the method name — not the path — identifies an endpoint. |
| **Params** | The input to a method. A test's body is just the params; Studio adds the `jsonrpc`, `id` and `method` wrapper when it sends the request. |
| **Result / error** | A JSON-RPC reply contains either a `result` (success) or an `error` with a code and message (failure). |
| **OpenRPC** | A specification format for JSON-RPC services, similar to OpenAPI for REST. |
| **MCP tool** | An action an MCP server offers. Each tool becomes an endpoint that calls `tools/call`. |
| **Protocol error vs tool error** | An MCP server can refuse a call (a JSON-RPC error), or accept it and report that the tool itself failed. The second arrives as a successful reply and needs its own check. |
| **Session** | MCP servers over Streamable HTTP keep a session. Studio opens it, uses it for the call, and closes it for every run. |

## Before you begin

- Your licence must be **Trial** or **Enterprise**. On other editions the JSON-RPC options do not appear in the import dialog.
- You need permission to import APIs into the project.
- For an MCP server: its Streamable HTTP address (for example `https://example.com/mcp`) and, if it requires sign-in, a bearer token.
- For a JSON-RPC service: an OpenRPC document (`.json`), or a URL to one.
- A project environment whose base URL points at the service's origin. JSON-RPC runs over HTTP, so, like REST, it needs one.

## Step 1 — Import an OpenRPC document

1. In the project tree, open the import dialog (**Import API to Project**, or **Import API to Feature** when a feature is selected).
2. Set **API Type:** to **JSON-RPC / MCP**.
3. Under **Import Method**, choose **Upload File** and select your OpenRPC `.json` file, or use URL import with the document's address.
4. Under **Check before importing**, click **Preview methods** to see the methods the document declares before anything is created.
5. Complete the import.

Each method in the document becomes one endpoint. Its parameter schema drives generation the same way a REST request body does, and any documented error codes are stored for negative tests.

> **Tip:** Most internal JSON-RPC services have no published OpenRPC document. A plain method list also imports: a JSON file containing the HTTP path and the method names, for example `{ "httpPath": "/rpc", "methods": ["getBalance", "transfer"] }`.

## Step 2 — Discover and import an MCP server

1. Open the import dialog as above.
2. Under **Import Method**, click **MCP Server**.
3. Enter the **MCP server URL**.
4. If the server requires sign-in, enter its bearer token in **Access token (only if the server needs one)**. Leave it empty otherwise.
5. Click **Connect and list tools**. Studio performs the real MCP handshake and lists the server's tools. Nothing is written yet.
6. Review the **Connected** summary (**Server** and **Tools**). If the server lists more tools than could be read, a note says the list may be incomplete.
7. Under **Choose what to test**, review the tools. Each is marked:
   - **Safe to run** — the server declares the tool read-only. These start ticked.
   - **May change data** — the tool does not declare itself read-only. These start unticked, because a generated happy-path test really runs the tool against your server.
8. Tick or untick tools, then click **Import N tools**.

Each selected tool becomes one endpoint. The tool's own input schema becomes the schema for its arguments, so generated tests exercise the tool's real parameters. Re-importing the same server later compares against what already exists instead of duplicating it. The project's base URL is filled in from the server address when it is not already set.

> **Important:** The access token entered here is used to discover the server and is not stored on the endpoints. For runs against a server that requires sign-in, set the endpoint's **Auth token reference** (see Step 3).

## Step 3 — Review and adjust an endpoint

Open an imported JSON-RPC endpoint to see or edit its settings:

| Field | Purpose |
|---|---|
| **RPC method** | The method name sent in the request (`tools/call` for MCP tools). |
| **HTTP path** | The URL path every method is sent to, such as `/rpc`. |
| **Params shape** | **Named (object)** or **Positional (array)**. |
| **ID strategy** | How the request `id` is set: **Incrementing number**, **UUID** or **Fixed (1)**. |
| **Default params** | The params sent when a test does not set its own. Just the params — not the full request. |
| **Auth token reference** | A reference to a bearer token, resolved when a test runs. Use `env:NAME` for an environment variable, `secretref://provider/path#field` for a secret manager, or `key:NAME` for a saved access key. Never the token itself. |
| **Documented error codes** | Error codes the method's documentation promises (comma-separated). Used to generate negative tests. |

> **Note:** If you paste a complete JSON-RPC request (with `jsonrpc`, `id` and `method`) into a test body — for example, one copied from your logs — Studio accepts it and uses only the params.

## Step 4 — Generate tests

Generate tests for JSON-RPC endpoints the same way as for any other endpoint. Generated tests include:

| Test | Checks |
|---|---|
| **Happy path** | **JSON-RPC: No Error** and **JSON-RPC: Result Exists** (plus **MCP: Tool Call Succeeded** for MCP tools). |
| **Unknown method rejected** | **JSON-RPC: Has Error** with the standard code `-32601`, at HTTP 200. |
| **Invalid params** and other negatives | **JSON-RPC: Has Error** with the documented or standard error code, at HTTP 200. |

For MCP tools marked **May change data**, only import them if you are prepared for generated happy-path tests to act on the server.

## Step 5 — Run the tests

1. Make sure the environment you run against has a base URL pointing at the service's origin (for example `https://api.example.com`). Studio appends the endpoint's HTTP path.
2. Run a test, a test run pack or a workflow as usual. JSON-RPC tests can be steps in workflows.
3. For MCP servers, Studio automatically opens a session, makes the call within it, and closes the session afterwards. If the server replies as an event stream instead of plain JSON, Studio unwraps it so the assertions see the same reply either way.

The run result shows a plain-language verdict instead of a bare HTTP status, because a green "200" would be misleading:

| Verdict | Meaning |
|---|---|
| **Succeeded** | The reply contained a `result` and no `error`. |
| **Error reported** | The call was delivered and the service answered with a JSON-RPC `error`. For a negative test, this is the expected outcome. |
| **Rejected (HTTP N)** | The request was refused before it reached the JSON-RPC layer — for example 401, 403, 404, 415 or 429. These keep their real HTTP status. |
| **No reply** | Nothing usable came back. |

Assertion results also read in plain words, such as *Rejected for the expected reason* or *Rejected, but for a different reason than expected*.

> **Note:** You can run JSON-RPC tests on the **Engine** (server) or the **Agent** (local runner). On the Agent, only `env:NAME` token references resolve; a `key:` or `secretref:` reference fails with a message naming it. Use the **Engine** for those.

## Assertion reference

All JSON-RPC assertions are in the **JSON-RPC** category of the assertion picker.

| Assertion | What it checks | Typical use |
|---|---|---|
| **JSON-RPC: No Error** | The reply has no `error` member. Set it to false to require one. | Every positive test. |
| **JSON-RPC: Has Error** | The reply has an `error` member. | The core negative check. |
| **JSON-RPC: Error Code Equals** | The error code, for example `-32601` (method not found) or `-32602` (invalid params). | Checking a negative test failed for the right reason. |
| **JSON-RPC: Error Message Contains** | The error message contains the given text. | Checking a specific validation message. |
| **JSON-RPC: Result Exists** | The reply has a `result` member. | Positive tests. |
| **MCP: Tool Call Succeeded** | An MCP tool call returned a result and did not flag the tool itself as failed. | Every MCP positive test — a tool failure arrives as a successful reply, so nothing else catches it. |
| **MCP: Tool Output Contains** | The text the tool returned contains the given text. | Checking what a tool actually said. |
| **JSON-RPC: Reply Matches Call** | The reply carries the same `id` the request sent. | Hard-failing a reply that belongs to another request. Without it, a mismatch is shown as a warning in the run details. |

Field checks inside the result use the usual field assertions — for example, a field-equals check on `result.balance`. Dynamic tokens such as `{{row.EXPECTED_CODE}}` work in these assertions as in any other.

Standard JSON-RPC 2.0 error codes: `-32700` parse error, `-32600` invalid request, `-32601` method not found, `-32602` invalid params, `-32603` internal error.

## Current limitations

- **Batch requests** (an array of calls in one request) are not supported. A batch body is rejected with a message asking you to split it into individual tests. A plain array of values is still accepted as positional params.
- **MCP over stdio** is not supported; only Streamable HTTP.
- **MCP resources and prompts** are not modelled; tools only.
- **MCP OAuth sign-in** is not supported; bearer tokens only.
- Studio has been verified against the official MCP reference server and a standard JSON-RPC 2.0 library. It has not been verified against every vendor's server; if yours behaves differently, contact support.

## Troubleshooting

| Symptom | Why it happens | What to do |
|---|---|---|
| **JSON-RPC / MCP** and **MCP Server** are not offered in the import dialog | Your edition does not include JSON-RPC testing, or it is turned off on your installation. | Check your licence (Trial or Enterprise), or ask your administrator. |
| A negative test expecting HTTP 400 never passes | A compliant JSON-RPC server reports errors with HTTP 200. | Replace the status check with **JSON-RPC: Has Error** and **JSON-RPC: Error Code Equals**. |
| A positive test passes although the call failed | The test checks only the status code. | Add **JSON-RPC: No Error** (and **MCP: Tool Call Succeeded** for MCP). |
| **Could not reach the MCP server** | Wrong URL, the server is down, or it requires a token. | Check the address and token, then click **Connect and list tools** again. |
| Runs are **Rejected (HTTP 401)** after an MCP import | The discovery token is not kept on the endpoints. | Set **Auth token reference** on the endpoint. |
| A run fails before sending, asking for a base URL | The environment has no base URL. | Set the environment's base URL to the service origin. |
| A token reference fails on the Agent | The Agent resolves only `env:` references. | Run on the **Engine**, or use an `env:` reference. |

## Best practices

- Never rely on a status check alone for JSON-RPC. Every test should include at least one JSON-RPC assertion.
- For MCP, pair **MCP: Tool Call Succeeded** with **MCP: Tool Output Contains** to check what the tool actually returned.
- Keep tools marked **May change data** unticked unless you are testing against a disposable environment.
- Record documented error codes on each endpoint so negative tests assert the right code.
- If you created JSON-RPC tests in an older release, regenerate them to pick up the current negative-test assertions.

## Related articles

- [Test run](/help-center/product-documentation/test-run)
- [Environments](/help-center/product-documentation/project-settings)
- [Project settings: environments and auth](/help-center/product-documentation/project-settings)
- [WebSocket-RPC Testing](/help-center/product-documentation/websocket-rpc-testing)
- [Testing Server-Sent Events (Streaming) Responses](/help-center/product-documentation/sse-streaming-testing)

## Automating this

Named access keys for JSON-RPC endpoints (used as `key:NAME` in **Auth token reference**) can be saved with `POST /api/jsonrpc/keys` and listed with `GET /api/jsonrpc/keys`. The list returns key names only, never the credential. Saving under an existing name replaces the credential without breaking endpoints that reference it.

## For administrators (self-hosted installations)

| Setting | Default | What switching it does |
|---|---|---|
| `JSONRPC_PROTOCOL_ENABLED` | `true` | Set to `false` to remove JSON-RPC and MCP testing entirely. Nothing can create a JSON-RPC endpoint and the import options disappear. |
| `MCP_DISCOVERY_ALLOW_INTERNAL` | Allowed on single-tenant installs; blocked when multi-tenant | Set `true` or `false` to override whether MCP discovery may connect to localhost and internal network addresses. |

