01 — Getting started
Requirements
- PHP 8.3+ (the bundle uses typed class constants throughout)
- Symfony 7.x or 8.x
ext-jsonsymfony/expression-language(required by routeconditions — installed automatically with the bundle)
Optional packages (composer suggest):
symfony/security-bundle— needed only if any method declaresrolessymfony/cache— needed only for tag-aware cache invalidationsymfony/rate-limiter— needed only if any method carries#[Rpc\RateLimit]symfony/maker-bundle— enablesbin/console make:rpc-methodsymfony/web-profiler-bundle— enables the RPC panel in the toolbar
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:
symfony/flex(recommended) — applies the bundled recipe oncomposer requiresymfony/routing— route import (see below)
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-servership inside the Composer package (.symfony/recipe/). They are not insymfony/recipes-contribyet, so some setups only apply them fromvendor/oncomposer 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:
src/Rpc/UserGetByEmail.php— handlersrc/Rpc/Dto/UserGetByEmailRequest.php— DTO withAssert\NotBlanksampletests/Rpc/UserGetByEmailTest.php— functional test skeleton
Next steps
- Add a DTO and validation: Parameters & DTOs
- Restrict who can call it: Security & roles
- Cache the result: Caching
- Expose it to MCP clients: MCP