ruby-sdk/AGENTS.md
Koichi ITO 719e5e7fee Introduce ServerSession for per-connection state
## Motivation and Context

The Ruby SDK uses a 1-Server-to-many-sessions model where session-specific state
(`@client`, `@logging_message_notification`) is stored on the shared `Server` instance.
This causes the last client to connect to overwrite state for all sessions,
and requires `session_id` to be threaded through method signatures for session-scoped notifications.

The Python SDK avoids this by creating a `ServerSession` per connection that wraps a shared `Server`.
Each session holds its own state and naturally scopes notifications through its own stream.

### Changes

- Added `MCP::ServerSession` class holding per-session state: client info,
  logging configuration, and a `transport` reference for session-scoped
  notification delivery.
- `StreamableHTTPTransport#handle_initialization` creates a `ServerSession`
  per `initialize` request and stores it in the session hash.
- `handle_regular_request` routes requests through the session's
  `ServerSession` instead of the shared `Server` directly.
- `Server#handle` and `Server#handle_json` accept an optional `session:`
  keyword (`ServerSession` instance).
- `Server#init` stores client info on the session when available.
- `Server#configure_logging_level` stores logging config on the session.
- `ServerContext` and `Progress` accept a `notification_target:` which
  can be either a `ServerSession` (session-scoped) or a `Server` (broadcast).
  No `session_id:` parameter needed.
- `ServerContext#notify_progress` and `#notify_log_message` delegate to
  the `notification_target` without `session_id` threading.
- `StdioTransport` creates a single `ServerSession` on `open`,
  making the session model transparent across both transports.

### Design Decision

This follows the Python SDK's "shared `Server` + per-connection `Session`" pattern.
The `Server` holds configuration (tools, prompts, resources, handlers).
Each `ServerSession` holds per-connection state (client info, logging level, stream writer).
Notifications from `ServerSession` go only to that session's stream.

## How Has This Been Tested?

All existing tests pass. All conformance tests pass. Added tests verifying:

- Session-scoped log notification is sent only to the originating session.
- Session-scoped progress notification is sent only to the originating session.
- Each session stores its own client info independently.
- Each session stores its own logging level independently.
- `StdioTransport#open` creates a `ServerSession` and stores client info on it.

## Breaking Change

None for end users.

The public API (`Server.new`, `define_tool`,`server_context.report_progress`,
`server_context.notify_log_message`, etc.) is unchanged. The following internal API
changes affect only SDK internals:

- `ServerContext.new` now requires `notification_target:` instead of just`progress:`.
- `Progress.new` now takes `notification_target:` instead of `server:`.
- `Server#handle` and `Server#handle_json` accept an optional `session:` keyword.
2026-03-29 08:04:56 +09:00

4.8 KiB

AGENTS.md

Project overview

This is the official Ruby SDK for the Model Context Protocol (MCP), implementing both server and client functionality for JSON-RPC 2.0 based communication between LLM applications and context providers.

Dev environment setup

  • Ruby 3.2.0+ required to run the full test suite, including all Sorbet-related features
  • Run bundle install to install dependencies
  • Dependencies: json-schema >= 4.1 - Schema validation

Build and test commands

  • bundle install - Install dependencies
  • rake test - Run all tests
  • rake rubocop - Run linter
  • rake - Run tests and linting (default task)
  • ruby -I lib -I test test/path/to/specific_test.rb - Run single test file
  • gem build mcp.gemspec - Build the gem

Testing instructions

  • Test files are in test/ directory with _test.rb suffix
  • Run full test suite with rake test
  • Run individual tests with ruby -I lib -I test test/path/to/file_test.rb
  • Tests should pass before submitting PRs

Code style guidelines

  • Follow RuboCop rules (run rake rubocop)
  • Use frozen string literals
  • Follow Ruby community conventions
  • Keep dependencies minimal

Commit message conventions

  • Use conventional commit format when possible
  • Include clear, descriptive commit messages
  • Releases are triggered by updating version in lib/mcp/version.rb and merging to main

Release process

  • Follow Keep a Changelog format in CHANGELOG.md
  • Update CHANGELOG.md before cutting releases
  • Use git history and PR merge commits to construct changelog entries
  • Format entries as: "Terse description of the change (#nnn)"
  • Keep entries in flat list format (no nesting)
  • Git tags mark commits that cut new releases
  • Exclude maintenance PRs that don't concern end users
  • Check upstream remote for PRs if available

Architecture overview

Core Components

MCP::Server (lib/mcp/server.rb):

  • Main server class handling JSON-RPC requests and holding shared configuration (tools, prompts, resources, handlers, capabilities)
  • Implements MCP protocol methods: initialize, ping, tools/list, tools/call, prompts/list, prompts/get, resources/list, resources/read
  • Supports custom method registration via define_custom_method
  • Handles instrumentation, exception reporting, and notifications
  • Uses JsonRpcHandler for request processing

MCP::ServerSession (lib/mcp/server_session.rb):

  • Per-connection state: client info, logging level
  • Created by the transport layer for each client connection
  • Delegates request handling to the shared Server

MCP::Client (lib/mcp/client.rb):

  • Client interface for communicating with MCP servers
  • Transport-agnostic design with pluggable transport layers
  • Supports tool listing and invocation

Transport Layer:

  • MCP::Server::Transports::StdioTransport - Command-line stdio transport
  • MCP::Server::Transports::StreamableHttpTransport - HTTP with streaming support
  • MCP::Client::HTTP - HTTP client transport (requires faraday gem)

Protocol Components:

  • MCP::Tool - Tool definition with input/output schemas and annotations
  • MCP::Prompt - Prompt templates with argument validation
  • MCP::Resource - Resource registration and retrieval
  • MCP::Configuration - Global configuration with exception reporting and instrumentation

Key Patterns

Three Ways to Define Components:

  1. Class inheritance (e.g., class MyTool < MCP::Tool)
  2. Define methods (e.g., MCP::Tool.define(name: "my_tool") { ... })
  3. Server registration (e.g., server.define_tool(name: "my_tool") { ... })

Schema Validation:

  • Tools support input_schema and output_schema for JSON Schema validation
  • Protocol version 2025-03-26+ supports tool annotations (destructive_hint, idempotent_hint, etc.)
  • Validation is configurable via configuration.validate_tool_call_arguments

Session Architecture:

  • Server holds shared configuration (tools, prompts, resources, handlers)
  • ServerSession holds per-connection state (client info, logging level)
  • Both StdioTransport and StreamableHTTPTransport create a ServerSession per connection, making the session model transparent across transports
  • Session-scoped notifications (notify_progress, notify_log_message) are sent only to the originating client via ServerSession
  • Server-wide notifications (notify_tools_list_changed, etc.) broadcast to all sessions via Server

Context Passing:

  • server_context hash passed through tool/prompt calls for request-specific data
  • Methods can accept server_context: keyword argument for accessing context

Integration patterns

  • Rails controllers: Use server.handle_json(request.body.read) for HTTP endpoints
  • Command-line tools: Use StdioTransport.new(server).open for CLI applications
  • HTTP services: Use StreamableHttpTransport for web-based servers