Hello World Tutorial#
Build your first OSPREY agent. One MCP server, one mock control system, zero complexity. By the end of this tutorial, you’ll have an agent that reads control system channels using the Osprey agent.
Prerequisites
Required:
Python 3.11+
Claude Code CLI installed
Osprey framework installed (
uv tool install osprey-framework, oruv sync --extra devif working from a clone)ANTHROPIC_API_KEYset in your environment
If you haven’t installed the framework yet, follow the installation guide.
Step 1: Create the Project#
Create a ready-to-run project in one command:
osprey build my-first-agent --preset hello-world
cd my-first-agent
Note
First run may take 1–2 minutes to create a virtual environment and install dependencies. Subsequent builds are near-instant.
Step 2: Understand What Was Generated#
Your project has three key files that control everything:
config.yml — Project Configuration
control_system:
type: mock # Change to 'epics' for real hardware
writes_enabled: false # Read-only by default
limits_checking:
enabled: true
database_path: "data/channel_limits.json"
allow_unlisted_channels: true
claude_code:
servers:
controls: {enabled: true} # The one MCP server
This tells OSPREY to use the mock connector, which accepts any channel
name and returns synthetic data. In production, you change type: mock to
type: epics and point it at real hardware — your agent code stays the same.
The limits_checking section enables the safety limits system. The limits
database defines allowed ranges and read-only channels. With
allow_unlisted_channels: true, channels not in the database can still be
read freely.
.mcp.json — MCP Server Discovery
This file is auto-generated by osprey build from your enabled servers in
config.yml — you don’t need to write or edit it by hand. The rendered
content looks roughly like:
{
"mcpServers": {
"controls": {
"command": "python",
"args": ["-m", "osprey.mcp_server.control_system"],
"env": { "OSPREY_CONFIG": "config.yml" }
}
}
}
This is how the Osprey agent discovers the control system MCP server. When the
agent starts, it launches this server and gains access to tools like
channel_read and channel_write.
CLAUDE.md — Agent Behavior Instructions
This file contains instructions that the Osprey agent reads on startup. It defines how the agent should behave, what safety rules to follow, and how to format responses. Edit this file to customize your agent’s personality and behavior.
Note
The hello_world template also installs agent hooks for
safety enforcement. These hooks run automatically before each tool call to
check limits and require human approval for writes. You don’t need to
configure them manually.
Step 3: Start the Agent#
From your project directory:
osprey claude chat
Note
osprey claude chat is the recommended way to launch: it reads
config.yml and points the agent at your configured LLM provider.
Launching the bare agent CLI skips this, so it silently uses whatever
provider your shell environment points at — see
Use the CLI Chat Interface.
Note
On first run, the Osprey agent will ask you to trust the MCP servers in this project. Accept to allow the agent to use the control system tools.
The Osprey agent connects to the control_system MCP server and is ready to accept
queries.
Step 4: Try It Out#
Try these queries to see the mock control system in action:
Read a single channel:
You: Read channel SR:BEAM:CURRENT
Expected output (the exact wording depends on your CLAUDE.md and the
agent’s output style, but the channel, a value with units, and a status
should all appear):
Channel: SR:BEAM:CURRENT
Value: 250.3 mA
Status: OK
The mock connector returns synthetic beam current data. It generates realistic values based on the channel name.
Ask what’s available:
You: What channels are available?
The agent explains it can read any channel name — the mock connector accepts all PV names without needing a real control system.
Read multiple channels:
You: Read SR:VAC:PRESSURE:01 and SR:MAG:QF:01:CURRENT:RB
The Osprey agent calls the channel_read tool for each channel and presents the
results together — vacuum pressure and magnet readback values.
Step 5: Safety & Limits#
OSPREY enforces safety at two levels: limits checking (automatic) and human approval (interactive). This step walks through both.
1. Enable writes
Edit config.yml to allow write operations:
control_system:
type: mock
writes_enabled: true # Changed from false
config.yml is a build-time input. Safety-critical fields like
writes_enabled are baked into the agent’s generated configuration (the
.claude/ artifacts), so editing config.yml alone does not change the
agent’s behavior. Regenerate the artifacts and relaunch:
osprey claude regen
osprey claude chat
Note
You can check whether your artifacts are in sync at any time with
osprey claude status. If you forget to regenerate, the agent warns you at
startup that config.yml has drifted from its generated configuration.
2. Write within limits (succeeds with approval)
You: Write 150.0 to SR:MAG:QF:01:CURRENT:SP
The value 150.0 is within the allowed range for this channel (0–300 A in the limits database). The approval hook prompts you before executing:
⚠ Write requested: SR:MAG:QF:01:CURRENT:SP = 150.0
Approve? [y/N]
Type y to approve. The mock connector accepts the write and confirms.
3. Write outside limits (blocked)
You: Write 500.0 to SR:MAG:QF:01:CURRENT:SP
The value 500.0 exceeds the maximum of 300.0 defined in
data/channel_limits.json. The limits hook blocks the write before it
reaches the connector — no approval prompt appears. The agent surfaces
something like:
✗ Write blocked: 500.0 exceeds max=300.0 for SR:MAG:QF:01:CURRENT:SP
4. Write to a read-only channel (blocked)
You: Write 1.0 to SR:BEAM:CURRENT
SR:BEAM:CURRENT is marked as read-only in data/channel_limits.json.
The limits hook blocks the write regardless of the value, and the agent
reports something like:
✗ Write blocked: SR:BEAM:CURRENT is read-only
Step 6: Understand the Flow#
Here’s what happens when you ask “Read channel SR:BEAM:CURRENT”:
You ──→ Osprey agent ──→ channel_read MCP tool
│
Mock Connector
│
Synthetic data ──→ Osprey agent formats response ──→ You
You type a natural language query
The Osprey agent decides to call the
channel_readMCP toolThe MCP server receives the call and routes it to the mock connector
The mock connector generates synthetic data (realistic noise, naming-based values)
The Osprey agent formats the response and presents it to you
This is the same flow in production — just with type: epics instead of
type: mock. The connector handles the difference; your agent and queries
stay the same.
Step 7: Customize#
Change agent behavior by editing CLAUDE.md:
Add a line like “Always report values with 4 decimal places” or “When reading magnet channels, also explain what the magnet does.” Restart the Osprey agent to see the effect.
Add new channels to the limits database by editing
data/channel_limits.json:
{
"SR:MAG:CORR:01:CURRENT:SP": {
"min_value": -5.0,
"max_value": 5.0,
"writable": true
}
}
This lets you define safe operating ranges for additional channels. Any write
to a channel listed here is checked against its min_value/max_value
range before the approval prompt.
Next Steps#
You’ve built a working agent with one MCP server and a mock control system. Here’s where to go from here:
Production deployment: The Production Control Systems Tutorial template adds channel finder, electronic logbook search, archiver access, and a web terminal
Architecture deep dive: The Conceptual Tutorial explains the MCP server architecture, connector system, and safety mechanisms
CLI reference: See CLI Reference for all
ospreycommandsLaunch options & providers: Use the CLI Chat Interface explains what
osprey claude chatsets up and how to point the agent at a non-default LLM provider viaconfig.yml