Sensor Array Active: Exposing Resources


MCP Series — Part 4

📡 What Are Resources? The Ship's Sensor Array

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.

🎯 The @resource Decorator: Registering Sensors

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: Reading the Ship's Manifest

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: Real-Time Sensor Readings

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.

🔗 Resource URIs: Custom Protocols

MCP resources use URIs as identifiers. You define the protocol scheme to organize your resources logically:

URI PatternUse CaseStar Trek Analogy
file:///path/to/data.jsonLocal filesShip's library computer
database://users/activeDatabase queriesPersonnel records
config://app/settingsConfiguration valuesSystem parameters
metrics://system/healthLive metricsEngineering readouts
sensors://long-range/{sector}Parameterized dataTargeted sensor scans

The scheme is arbitrary — choose something meaningful to your domain. The key rule: URIs must be unique within a server.

⚖️ Resources vs Tools: When to Use Which

This is the critical distinction every Starfleet engineer must understand:

Resources (Sensors)Tools (Phasers)
NatureRead-only dataExecutable actions
ControlApplication-controlledModel-controlled
Side effectsNone — pure observationYes — changes state
When fetchedHost decides (e.g., on context load)Model decides (during reasoning)
AnalogyReading a sensor displayFiring phasers / engaging warp
SafetyAlways safe to fetchMay 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.

🛠️ Putting It All Together

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)

🖖 What's Next

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.