TanStack Query has two verbs: read (useQuery) and write (useMutation). JSON-RPC does not. So pyRPC adds an optional kind on each procedure — declared on the server, shipped through codegen, enforced on the typed hooks.
Server
from pyrpc_core import rpc
@rpc.query
def get_user(user_id: int) -> dict:
...
@rpc.mutation
def update_user(user_id: int, name: str) -> dict:
...
# Bare @rpc still works → kind "query" (backward compatible)What you import
You import Types only. Codegen brands each procedure with _pyrpcKind and emits a runtime map adapters load automatically. No second “kinds” import in app code.
Why not only client-side hooks?
Allowing every procedure to expose both hooks forever works, but lies to the type system. Declaring kind on the server makes the contract honest: a mutation is not a cacheable query key, and autocomplete shows the right hook.
Wire format
Kind does not change JSON-RPC. It is metadata for codegen and adapters. The POST body is still { id, method, params }.

pyRPC