Skip to content

STDIO and MCP configuration

STDIO connects a client to a local Orionis process: the client writes requests to stdin and reads responses from stdout. It uses the same compiled server, schemas, hooks, and container as HTTP, without opening an HTTP port.

In your application’s configured AI file, usually routes/ai.py, register an explicit handle:

from app.mcp.servers.demo_server import DemoServer
from orionis.support.facades import Mcp
Mcp.local("demo", DemoServer)

If this registration already exists from Servers and routes, keep it only once. Load the file with ai="routes/ai.py" in the existing app.withRouting(...) call in bootstrap/app.py, before app.create(); retain its other arguments.

Mcp.local() receives a handle and a class inheriting Server. It does not accept an instance or a module path supplied by a client. Handles must be unique and match [A-Za-z0-9][A-Za-z0-9_.-]{0,127}: between 1 and 128 characters, starting with a letter or digit. demo and support.internal are valid; a name containing spaces is not.

The local handle is independent of Server.name and the HTTP address. You can register the same class using Mcp.web("/mcp/demo", DemoServer) and Mcp.local("demo", DemoServer). Each registration publishes its own transport; creating one does not automatically create the other.

From the project root, either alternative lists registered HTTP and STDIO servers:

Terminal window
orionis mcp:list
python -B reactor mcp:list

The table shows transport, endpoint or handle, and server class. It does not open ports or start the transport. To execute the local server, use one alternative:

Terminal window
orionis mcp:start demo
Terminal window
python -B reactor mcp:start demo
CommandOwn argumentsResult
mcp:listNone.Displays registered servers.
mcp:start nameRequired name: local handle.Runs STDIO until input closes or the transport terminates.
mcp:start --help--help.Describes the local argument without starting a server.

An unknown handle writes a diagnostic to stderr and exits with code 1. mcp:start does not discover classes from arbitrary names. Component generators create files; they do not replace Mcp.local() registration.

Each message is one UTF-8 JSON-RPC object on a single line, terminated by a newline. Do not send a pretty-printed multiline JSON document or an array of calls. This message discovers the server:

{"jsonrpc":"2.0","id":"discover-1","method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}

The response uses the same id and returns the same server/discover contract as HTTP. Do not send HTTP headers through stdin: the revision and capabilities live in params._meta. There is no initialize step; only MCP 2026-07-28 is supported.

The reader keeps accepting input while requests execute within the configured budget. Each request has its own scope; correlate concurrent responses by id without depending on their order. A notifications/cancelled notification with params.requestId can cancel an active STDIO request. EOF or transport I/O failure cancels active requests and waits for cleanup; delivery of pending responses is not guaranteed. An ordinary JSON-RPC error returns its error without terminating other requests.

stdout is reserved for protocol messages. Orionis separates its console output from the MCP channel, but your components must also avoid writing diagnostics or non-JSON-RPC data there. Use logging or stderr for diagnostics. This matters when converting an example containing print() into an MCP tool.

sequenceDiagram
    participant C as Local client
    participant S as mcp:start process
    participant D as MCP dispatcher
    C->>S: JSON-RPC + newline through stdin
    S->>S: Bounded read and admission
    S->>D: Request within its scope
    D-->>S: Result or progress messages
    S-->>C: JSON-RPC lines on stdout
    C->>S: Another request or cancellation
    C->>S: EOF on input close
    S->>S: Finish tasks and release transport

Use McpRequest for the call’s method, arguments, and metadata. Services inject into constructors and methods as in the tool examples. An HTTP Request exists only in HTTP transport; do not request it for a tool that must also work over STDIO.

HTTP middleware and Authorization headers do not execute over stdin. Primitive shouldRegister and authorize hooks still apply. Define a trust policy for the local process rather than treating client metadata as an authenticated identity.

The McpConfig entity validates options. Configure them from bootstrap/app.py using the existing app instance, before app.create():

app.withConfigMcp(
max_request_size=1024 * 1024,
max_concurrent_requests=32,
default_page_size=50,
max_page_size=100,
allowed_origins=["https://agent.example.com"],
)

Specify only options you intend to change. Remaining fields use entity defaults and environment variables. If your project already has MCP configuration, integrate values there rather than maintaining two sources. When using compiled bootstrap, follow the cache and restart procedure after changes.

FieldEnvironment variableDefault
max_request_sizeMCP_MAX_REQUEST_SIZEHTTP_MAX_BODY_SIZE or 1 MiB; maximum input bytes.
max_concurrent_requestsMCP_MAX_CONCURRENT_REQUESTSHTTP_MAX_CONCURRENT_REQUESTS or 32 active requests.
max_response_sizeMCP_MAX_RESPONSE_SIZE4 MiB; serialized response budget.
max_metadata_sizeMCP_MAX_METADATA_SIZE64 KiB of serialized metadata.
default_page_sizeMCP_DEFAULT_PAGE_SIZE50 entries per page.
max_page_sizeMCP_MAX_PAGE_SIZE100; validation ceiling for default_page_size.

MCP pagination accepts an opaque cursor, rather than a client-provided page size. max_page_size does not enable an RPC limit argument. Concurrency limits apply per transport instance; each HTTP endpoint has its own. The HTTP kernel can impose an additional request limit.

FieldEnvironment variableDefault
subscription_buffer_sizeMCP_SUBSCRIPTION_BUFFER_SIZE64 events in each subscription buffer.
max_subscriptionsMCP_MAX_SUBSCRIPTIONS1024 listeners in the event bus.
max_resource_subscriptionsMCP_MAX_RESOURCE_SUBSCRIPTIONS64 resource URIs per listener.
subscription_keepaliveMCP_SUBSCRIPTION_KEEPALIVE15 seconds between keepalives.
tool_search_max_resultsMCP_TOOL_SEARCH_MAX_RESULTS20 results per catalog search.
tool_search_max_callsMCP_TOOL_SEARCH_MAX_CALLS5 calls per catalog execution.
tool_search_max_output_bytesMCP_TOOL_SEARCH_MAX_OUTPUT_BYTES256 KiB of catalog output.

Open streams retain an admission slot until they finish. Limits and the in-memory bus belong to the process; they are not distributed quotas shared between workers. See Advanced workflows for subscriptions, grouped tools, and their requirements.

FieldEnvironment variableDefault
allowed_originsCORS_ALLOW_ORIGINSEmpty list.

A request without Origin is accepted. When present, the header must exactly match a configured HTTP(S) origin, such as https://agent.example.com. Wildcards, paths, and an implicit same-origin exception are not accepted. An empty list rejects every present Origin.

This option validates MCP origins; browser integrations also need CORS configured in the HTTP flow. An agent running as a remote process is not necessarily a browser and need not send Origin.

Integer budgets must be positive; subscription_keepalive must be finite and greater than zero, and default_page_size cannot exceed max_page_size. Invalid configuration is rejected while creating the entity, before serving requests.