Skip to main content
Glama

STAC MCP Server

by BnJam
fast_server.py15.6 kB
from __future__ import annotations import json import logging from typing import Any from fastmcp.prompts.prompt import PromptMessage, TextContent from fastmcp.server.server import FastMCP from stac_mcp.tools import execution app = FastMCP() _LOGGER = logging.getLogger(__name__) # Prompts to guide agents how to call tools correctly. # # Notes on metadata locations (why both `meta` and `_meta` exist): # - The `@app.prompt(..., meta=...)` decorator accepts a `meta` kwarg which # FastMCP stores on the prompt descriptor returned by `client.list_prompts()` # as `.meta`. This is the decorator-provided metadata (schema/example). # - When the prompt function returns a `PromptMessage`, we attach the # machine-readable payload (the JSON schema + example used to generate the # human-facing prompt) under the MCP aliased metadata field `_meta` on the # PromptMessage instance. Clients retrieving a rendered prompt (`get_prompt`) # should therefore look for `message._meta["machine_payload"]` for the # machine payload, while discovery via `list_prompts()` will find decorator # metadata under `.meta`. # # This dual-mapping keeps decorator metadata discoverable via prompt listing # while making the specific per-message machine payload available on the # returned PromptMessage under `_meta` so agents can programmatically inspect # the exact payload used to render the human text. @app.prompt( name="tool_get_root_prompt", description="Usage for get_root tool", meta={ "schema": {"type": "object", "properties": {}, "required": []}, "example": {}, }, ) def _prompt_get_root() -> PromptMessage: schema = {"type": "object", "properties": {}, "required": []} payload = { "name": "get_root", "description": "Return the STAC root document.", "parameters": schema, "example": {}, } human = ( f"Tool: get_root\nDescription: {payload['description']}\n\n" "Parameters:\n" f"{json.dumps(schema, indent=2)}\n\n" "Example:\n" f"{json.dumps(payload['example'], indent=2)}" ) return PromptMessage( role="user", content=TextContent(type="text", text=human), _meta={"machine_payload": payload}, ) @app.prompt( name="tool_get_conformance_prompt", description="Usage for get_conformance tool", meta={ "schema": {"type": "object", "properties": {}, "required": []}, "example": {}, }, ) def _prompt_get_conformance() -> PromptMessage: schema = {"type": "object", "properties": {}, "required": []} payload = { "name": "get_conformance", "description": "Return server conformance classes.", "parameters": schema, "example": {}, } human = ( f"Tool: get_conformance\nDescription: {payload['description']}\n\n" "Parameters:\n" f"{json.dumps(schema, indent=2)}\n\n" "Example:\n" f"{json.dumps(payload['example'], indent=2)}" ) return PromptMessage( role="user", content=TextContent(type="text", text=human), _meta={"machine_payload": payload}, ) @app.prompt( name="tool_search_collections_prompt", description="Usage for search_collections tool", meta={ "schema": { "type": "object", "properties": { "limit": {"type": "integer", "default": 10}, "catalog_url": {"type": "string"}, }, "required": [], }, "example": {"limit": 5}, }, ) def _prompt_search_collections() -> PromptMessage: schema = { "type": "object", "properties": { "limit": {"type": "integer", "default": 10}, "catalog_url": {"type": "string"}, }, "required": [], } payload = { "name": "search_collections", "description": "Return a page of STAC collections.", "parameters": schema, "example": {"limit": 5}, } human = ( f"Tool: search_collections\nDescription: {payload['description']}\n\n" "Parameters:\n" f"{json.dumps(schema, indent=2)}\n\n" "Example:\n" f"{json.dumps(payload['example'], indent=2)}" ) return PromptMessage( role="user", content=TextContent(type="text", text=human), _meta={"machine_payload": payload}, ) @app.prompt( name="tool_get_collection_prompt", description="Usage for get_collection tool", meta={ "schema": { "type": "object", "properties": { "collection_id": {"type": "string"}, "catalog_url": {"type": "string"}, }, "required": ["collection_id"], }, "example": {"collection_id": "my-collection"}, }, ) def _prompt_get_collection() -> PromptMessage: schema = { "type": "object", "properties": { "collection_id": {"type": "string"}, "catalog_url": {"type": "string"}, }, "required": ["collection_id"], } payload = { "name": "get_collection", "description": "Fetch a single STAC Collection by id.", "parameters": schema, "example": {"collection_id": "my-collection"}, } human = ( f"Tool: get_collection\nDescription: {payload['description']}\n\n" "Parameters:\n" f"{json.dumps(schema, indent=2)}\n\n" "Example:\n" f"{json.dumps(payload['example'], indent=2)}" ) return PromptMessage( role="user", content=TextContent(type="text", text=human), _meta={"machine_payload": payload}, ) @app.prompt( name="tool_get_item_prompt", description="Usage for get_item tool", meta={ "schema": { "type": "object", "properties": { "collection_id": {"type": "string"}, "item_id": {"type": "string"}, "output_format": { "type": "string", "enum": ["text", "json"], "default": "text", }, "catalog_url": {"type": "string"}, }, "required": ["collection_id", "item_id"], }, "example": {"collection_id": "c1", "item_id": "i1", "output_format": "json"}, }, ) def _prompt_get_item() -> PromptMessage: schema = { "type": "object", "properties": { "collection_id": {"type": "string"}, "item_id": {"type": "string"}, "output_format": { "type": "string", "enum": ["text", "json"], "default": "text", }, "catalog_url": {"type": "string"}, }, "required": ["collection_id", "item_id"], } payload = { "name": "get_item", "description": "Retrieve a single STAC Item.", "parameters": schema, "example": {"collection_id": "c1", "item_id": "i1", "output_format": "json"}, } human = ( f"Tool: get_item\nDescription: {payload['description']}\n\n" "Parameters:\n" f"{json.dumps(schema, indent=2)}\n\n" "Example:\n" f"{json.dumps(payload['example'], indent=2)}" ) return PromptMessage( role="user", content=TextContent(type="text", text=human), _meta={"machine_payload": payload}, ) @app.prompt( name="tool_search_items_prompt", description="Usage for search_items tool", meta={ "schema": { "type": "object", "properties": { "collections": {"type": "array", "items": {"type": "string"}}, "bbox": { "type": "array", "items": {"type": "number"}, "minItems": 4, "maxItems": 4, }, "datetime": {"type": "string"}, "limit": {"type": "integer", "default": 10}, }, "required": ["collections"], }, "example": {"collections": ["c1"], "limit": 3}, }, ) def _prompt_search_items() -> PromptMessage: schema = { "type": "object", "properties": { "collections": {"type": "array", "items": {"type": "string"}}, "bbox": { "type": "array", "items": {"type": "number"}, "minItems": 4, "maxItems": 4, }, "datetime": {"type": "string"}, "limit": {"type": "integer", "default": 10}, }, "required": ["collections"], } payload = { "name": "search_items", "description": "Search for STAC Items.", "parameters": schema, "example": {"collections": ["c1"], "limit": 3}, } human = ( f"Tool: search_items\nDescription: {payload['description']}\n\n" "Parameters:\n" f"{json.dumps(schema, indent=2)}\n\n" "Example:\n" f"{json.dumps(payload['example'], indent=2)}" ) return PromptMessage( role="user", content=TextContent(type="text", text=human), _meta={"machine_payload": payload}, ) @app.prompt( name="tool_estimate_data_size_prompt", description="Usage for estimate_data_size tool", meta={ "schema": { "type": "object", "properties": { "collections": {"type": "array", "items": {"type": "string"}}, "bbox": { "type": "array", "items": {"type": "number"}, "minItems": 4, "maxItems": 4, }, "datetime": {"type": "string"}, "query": {"type": "object"}, "aoi_geojson": {"type": "object"}, "limit": {"type": "integer", "default": 100}, "force_metadata_only": {"type": "boolean", "default": False}, "output_format": { "type": "string", "enum": ["text", "json"], "default": "text", }, }, "required": ["collections"], }, "example": {"collections": ["c1"], "limit": 10, "output_format": "json"}, }, ) def _prompt_estimate_data_size() -> PromptMessage: schema = { "type": "object", "properties": { "collections": {"type": "array", "items": {"type": "string"}}, "bbox": { "type": "array", "items": {"type": "number"}, "minItems": 4, "maxItems": 4, }, "datetime": {"type": "string"}, "query": {"type": "object"}, "aoi_geojson": {"type": "object"}, "limit": {"type": "integer", "default": 100}, "force_metadata_only": {"type": "boolean", "default": False}, "output_format": { "type": "string", "enum": ["text", "json"], "default": "text", }, }, "required": ["collections"], } payload = { "name": "estimate_data_size", "description": "Estimate data size for a STAC query.", "parameters": schema, "example": {"collections": ["c1"], "limit": 10, "output_format": "json"}, } # Note for users: this tool returns both the DataArray-reported size # (reported_bytes from .data.nbytes) and a registry-corrected size # (registry_bytes) when the sensor registry suggests a different # instrument-native dtype. The numeric totals are computed from the # reported values by default; check 'registry_bytes' for storage-native # estimates. human = ( f"Tool: estimate_data_size\nDescription: {payload['description']}\n\n" "Parameters:\n" f"{json.dumps(schema, indent=2)}\n\n" "Note: The response includes per-variable fields 'reported_bytes' and\n" "'registry_bytes' when applicable. Use 'registry_bytes' to estimate\n" "instrument-native storage sizes.\n\n" "Example:\n" f"{json.dumps(payload['example'], indent=2)}" ) return PromptMessage( role="user", content=TextContent(type="text", text=human), _meta={"machine_payload": payload}, ) @app.tool async def get_root() -> list[dict[str, Any]]: """Get the root STAC catalog document.""" return await execution.execute_tool("get_root", {}, catalog_url=None, headers=None) @app.tool async def get_conformance() -> list[dict[str, Any]]: """Get the conformance classes for the STAC API.""" return await execution.execute_tool( "get_conformance", {}, catalog_url=None, headers=None ) @app.tool async def search_collections( limit: int | None = 10, catalog_url: str | None = None, ) -> list[dict[str, Any]]: """Search for STAC collections.""" return await execution.execute_tool( "search_collections", {"limit": limit}, catalog_url=catalog_url, headers=None ) @app.tool async def get_collection( collection_id: str, catalog_url: str | None = None, ) -> list[dict[str, Any]]: """Get a specific STAC collection by its ID.""" return await execution.execute_tool( "get_collection", {"collection_id": collection_id}, catalog_url=catalog_url, headers=None, ) @app.tool async def get_item( collection_id: str, item_id: str, output_format: str | None = "text", catalog_url: str | None = None, ) -> list[dict[str, Any]]: """Get a specific STAC Item by collection and item ID.""" return await execution.execute_tool( "get_item", arguments={ "collection_id": collection_id, "item_id": item_id, "output_format": output_format, }, catalog_url=catalog_url, headers=None, ) @app.tool async def search_items( collections: list[str], bbox: list[float] | None = None, datetime: str | None = None, limit: int | None = 10, query: dict[str, Any] | None = None, output_format: str | None = "text", catalog_url: str | None = None, ) -> list[dict[str, Any]]: """Search for STAC items.""" return await execution.execute_tool( "search_items", arguments={ "collections": collections, "bbox": bbox, "datetime": datetime, "limit": limit, "query": query, "output_format": output_format, }, catalog_url=catalog_url, headers=None, ) @app.tool async def estimate_data_size( collections: list[str], bbox: list[float] | None = None, datetime: str | None = None, query: dict[str, Any] | None = None, aoi_geojson: dict[str, Any] | None = None, limit: int | None = 10, force_metadata_only: bool | None = False, output_format: str | None = "text", catalog_url: str | None = None, ) -> list[dict[str, Any]]: """Estimate the data size for a STAC query.""" return await execution.execute_tool( "estimate_data_size", arguments={ "collections": collections, "bbox": bbox, "datetime": datetime, "query": query, "aoi_geojson": aoi_geojson, "limit": limit, "force_metadata_only": force_metadata_only, "output_format": output_format, }, catalog_url=catalog_url, headers=None, )

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BnJam/stac-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server