json-rpc-server

01 — Getting started

Requirements

Optional packages (composer suggest):

The bundle fails the container build with a clear message if you reference a feature whose backing package is missing — you won’t ship “silently broken”.

Installation

composer require knetesin/json-rpc-server

Requirements in the host app:

Two config files (not one)

The bundle needs both files. They do different jobs:

File Purpose
config/packages/json_rpc_server.yaml Bundle settings (paths, MCP, cache, …)
config/routes/json_rpc_server.yaml Registers HTTP routes in Symfony

Changing json_rpc_server.routes in the packages file only changes paths on routes that are already imported — it does not create routes by itself.

With Symfony Flex, the recipe in the package (.symfony/recipe/) should copy both files on first composer require. You should see:

config/packages/json_rpc_server.yaml
config/routes/json_rpc_server.yaml

If the recipe did not run (no config/routes/json_rpc_server.yaml, or no symfony.lock entry for knetesin/json-rpc-server), add them manually:

// config/bundles.php
return [
    // ...
    Knetesin\JsonRpcServerBundle\KnetesinJsonRpcServerBundle::class => ['all' => true],
];
# config/routes/json_rpc_server.yaml  ← required
json_rpc_server:
    resource: '@KnetesinJsonRpcServerBundle/Resources/config/routes.php'
    type: php
# config/packages/json_rpc_server.yaml
json_rpc_server: ~

Then retry:

composer recipes:install knetesin/json-rpc-server --force -v
# or (older Flex): composer symfony:recipes:install knetesin/json-rpc-server --force -v
bin/console cache:clear
bin/console debug:router | grep rpc

Note: Recipes for knetesin/json-rpc-server ship inside the Composer package (.symfony/recipe/). They are not in symfony/recipes-contrib yet, so some setups only apply them from vendor/ on composer require. Manual files above always work.

Routes and paths

Default paths (after the routes file is imported):

Route Path Method Default
rpc /rpc POST enabled
rpc_stream /rpc/stream POST disabled — flip routes.stream.enabled: true if any handler uses #[Rpc\Stream]
rpc_mcp_tools /mcp/tools GET disabled — flip mcp.enabled: true
rpc_mcp_call /mcp/call POST disabled — flip mcp.enabled: true
rpc_openrpc /rpc.openrpc.json GET disabled — flip routes.openrpc.enabled: true to publish

Override paths or disable a route in packages config:

# config/packages/json_rpc_server.yaml
json_rpc_server:
    routes:
        mcp_tools: { path: /mcp2/tools, enabled: true }

See Configuration reference for every routes.* knob.

Your first handler

Create src/Rpc/Add.php:

<?php

declare(strict_types=1);

namespace App\Rpc;

use Knetesin\JsonRpcServerBundle\Attribute as Rpc;

#[Rpc\Method('math.add', description: 'Add two integers.')]
final class Add
{
    /** @return array<string, int> */
    public function __invoke(int $a, int $b): array
    {
        return ['sum' => $a + $b];
    }
}

That’s it. The bundle’s compiler pass picks up every class carrying #[Rpc\Method], builds the metadata at container compile time, and routes incoming calls automatically.

Calling it

curl -X POST http://localhost/rpc \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"math.add","params":{"a":2,"b":3},"id":1}'
{"jsonrpc":"2.0","result":{"sum":5},"id":1}

params can also be positional ([2, 3]) — see Parameters & DTOs.

Inspecting what’s registered

bin/console debug:rpc
RPC methods (1)
+-----------+-------+--------+-------+-----------+-----+------------+
| Name      | Class | Roles  | Cache | RateLimit | MCP | Deprecated |
+-----------+-------+--------+-------+-----------+-----+------------+
| math.add  | Add   | public | —     | —         | no  | —          |
+-----------+-------+--------+-------+-----------+-----+------------+

For details on one method:

bin/console debug:rpc math.add

Scaffolding with maker

If symfony/maker-bundle is installed:

bin/console make:rpc-method UserGetByEmail \
    --method=user.getByEmail --with-dto --with-test

Generates:

Next steps