When the Enterprise scans a planet, it doesn't change the planet — it observes it. The sensor array is a read-only system that gathers information for the bridge crew to act upon.
MCP Resources work exactly like this:
Resources are the model's eyes and ears. They provide context — the raw data that informs decisions — without allowing the model to modify anything.
Just as each sensor on the Enterprise has a designation (long-range sensors, short-range sensors, lateral sensors), each MCP resource has a URI that uniquely identifies it:
from mcp.server.fastmcp import FastMCP
enterprise = FastMCP('USS Enterprise NCC-1701-D')
@enterprise.resource('starfleet://ship-name')
def get_ship_name() -> str:
"""The ship's official registry name."""
return 'USS Enterprise NCC-1701-D'
The URI starfleet://ship-name is a custom protocol. You define the scheme — it can be anything meaningful to your domain: database://, config://, metrics://, etc.
Static resources serve pre-existing data — files, configuration, reference material. Like consulting the ship's crew manifest or technical specifications:
from pathlib import Path
from mcp.server.fastmcp import FastMCP
enterprise = FastMCP('USS Enterprise NCC-1701-D')
DATA_DIR = Path(__file__).parent / 'ship_data'
@enterprise.resource('starfleet://crew-manifest')
def get_crew_manifest() -> str:
"""Complete crew manifest of the Enterprise.
Contains all active personnel: name, rank, department, species.
"""
return (DATA_DIR / 'crew.json').read_text()
@enterprise.resource('starfleet://technical-specs')
def get_technical_specifications() -> str:
"""Ship technical specifications — Galaxy-class starship."""
return (DATA_DIR / 'specs.json').read_text()
@enterprise.resource('starfleet://mission-log')
def get_captains_log() -> str:
"""Captain's log — current mission parameters and status."""
return (DATA_DIR / 'captains_log.md').read_text()
These resources simply read from disk and return the content. The data exists before anyone asks for it — like a PADD loaded with reference material.
Dynamic resources compute data on the fly — like the ship's sensors actively scanning the environment. Every time you query them, they return the latest state:
import json
import random
from datetime import datetime
from mcp.server.fastmcp import FastMCP
enterprise = FastMCP('USS Enterprise NCC-1701-D')
@enterprise.resource('sensors://status')
def get_sensor_status() -> str:
"""Real-time sensor array status — all primary systems."""
shield_integrity = random.uniform(95.0, 100.0)
warp_core_temp = random.uniform(1400, 1600) # Kelvin
hull_integrity = random.uniform(97.0, 100.0)
sensor_readings = {
'timestamp': datetime.now().isoformat(),
'shields': round(shield_integrity, 1),
'hull_integrity': round(hull_integrity, 1),
'warp_core_temp_kelvin': round(warp_core_temp, 0),
'life_support': 'nominal',
'weapons_status': 'standby',
'crew_complement': 1014,
}
return json.dumps(sensor_readings, indent=2)
@enterprise.resource('sensors://long-range/{sector}')
def scan_sector(sector: str) -> str:
"""Long-range sensor scan of a specific sector.
Args:
sector: Sector designation (e.g., "001", "Neutral-Zone")
"""
# In reality, this would query a database or external API
scan_result = {
'sector': sector,
'scan_time': datetime.now().isoformat(),
'objects_detected': random.randint(0, 12),
'anomalies': random.randint(0, 3),
'threat_level': random.choice(['none', 'low', 'moderate']),
'classification': 'routine patrol scan',
}
return json.dumps(scan_result, indent=2)
@enterprise.resource('engineering://warp-core-diagnostics')
def warp_core_diagnostics() -> str:
"""Level 3 warp core diagnostic — real-time telemetry."""
diagnostics = {
'matter_flow': f'{random.uniform(98, 100):.2f}%',
'antimatter_containment': 'stable',
'dilithium_crystal_alignment': f'{random.uniform(99.5, 100):.3f}%',
'plasma_temperature': f'{random.randint(1400, 1550)}K',
'output_efficiency': f'{random.uniform(95, 99.9):.1f}%',
'last_maintenance_stardate': '2026.153',
'status': 'nominal',
}
return json.dumps(diagnostics, indent=2)
Notice the URI template in sensors://long-range/{sector} — the {sector} part is a parameter. The client can request sensors://long-range/001 or sensors://long-range/Neutral-Zone, and the function receives the sector name as an argument.
MCP resources use URIs as identifiers. You define the protocol scheme to organize your resources logically:
| URI Pattern | Use Case | Star Trek Analogy |
|---|---|---|
file:///path/to/data.json | Local files | Ship's library computer |
database://users/active | Database queries | Personnel records |
config://app/settings | Configuration values | System parameters |
metrics://system/health | Live metrics | Engineering readouts |
sensors://long-range/{sector} | Parameterized data | Targeted sensor scans |
The scheme is arbitrary — choose something meaningful to your domain. The key rule: URIs must be unique within a server.
This is the critical distinction every Starfleet engineer must understand:
| Resources (Sensors) | Tools (Phasers) | |
|---|---|---|
| Nature | Read-only data | Executable actions |
| Control | Application-controlled | Model-controlled |
| Side effects | None — pure observation | Yes — changes state |
| When fetched | Host decides (e.g., on context load) | Model decides (during reasoning) |
| Analogy | Reading a sensor display | Firing phasers / engaging warp |
| Safety | Always safe to fetch | May require confirmation |
Rule of thumb:
Example: "What's the crew count?" → Read the crew manifest resource. "Transfer crewman to engineering" → Call a personnel management tool.
import json
import logging
from pathlib import Path
from datetime import datetime
from mcp.server.fastmcp import FastMCP
logging.basicConfig(filename='/tmp/enterprise.log', level=logging.DEBUG)
captains_log = logging.getLogger('bridge')
enterprise = FastMCP('USS Enterprise NCC-1701-D', host='0.0.0.0', port=1701)
DATA_DIR = Path(__file__).parent / 'ship_data'
# === RESOURCES (Sensors — read-only) ===
@enterprise.resource('starfleet://crew-manifest')
def get_crew_manifest() -> str:
"""Complete crew manifest of the Enterprise."""
return (DATA_DIR / 'crew.json').read_text()
@enterprise.resource('sensors://status')
def get_sensor_status() -> str:
"""Real-time sensor array status."""
return json.dumps({'shields': 100, 'hull_integrity': 98.5, 'warp_core_temp': 1500})
# === TOOLS (Phasers — executable actions) ===
@enterprise.tool()
def engage_warp(factor: int) -> str:
"""Engage warp drive at specified factor."""
captains_log.info(f'Warp {factor} engaged')
return f'Aye sir, warp {factor} engaged. ETA to destination: {10/factor:.1f} hours.'
@enterprise.tool()
def red_alert() -> str:
"""Activate Red Alert — all hands to battle stations."""
captains_log.warning('RED ALERT activated')
return 'Red Alert! All hands to battle stations. Shields up, weapons armed.'
# === MAIN ===
if __name__ == '__main__':
import sys
transport = 'streamable-http' if '--http' in sys.argv else 'stdio'
enterprise.run(transport=transport)
The sensor array is online. The Enterprise can now observe (Resources) and act (Tools). In future installments, we'll explore advanced topics: composing multiple MCP servers, authentication, and building a full agent that autonomously coordinates between sensors, weapons, and navigation.
The Federation's finest, reporting for duty.