Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed __init__.py
Empty file.
7 changes: 4 additions & 3 deletions noxfile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import shutil
from pathlib import Path

import nox
import shutil

nox.options.stop_on_first_error = True
nox.options.reuse_existing_virtualenvs = False
Expand Down Expand Up @@ -33,7 +34,7 @@ def lint(session):
session.run(
"uv", "tool", "run", "black", "--verbose", "--check", "--diff", "--color", "."
)
session.run("uv", "tool", "run", "ruff", "--verbose", "check", ".")
session.run("uv", "tool", "run", "ruff@0.16.5", "--verbose", "check", ".")


@nox.session
Expand Down Expand Up @@ -68,7 +69,7 @@ def build(session):

session.install("build", "twine", "check-wheel-contents")

session.run(*"python -m build --sdist --wheel".split())
session.run(*["python", "-m", "build", "--sdist", "--wheel"])
session.run("check-wheel-contents", "dist")


Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "planet-mcp"
version = "0.3.0"
version = "0.4.0"
readme = "README.md"
dependencies = [
"aiocache>=0.12.3",
Expand Down Expand Up @@ -45,7 +45,7 @@ addopts = ["-v", "--tb=short"]
dev = [
"black>=25.1.0",
"pyright>=1.1.403",
"ruff>=0.12.4",
"ruff==0.16.5",
]
test = [
"pytest>=8.0.0",
Expand Down
3 changes: 2 additions & 1 deletion src/planet_mcp/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
"""

import argparse

from planet_mcp.server import init


def parse_args() -> argparse.Namespace:
def csv(value):
return set(t.strip() for t in (value or "").split(","))
return {t.strip() for t in (value or "").split(",")}

parser = argparse.ArgumentParser(
description="Planet MCP Server",
Expand Down
12 changes: 9 additions & 3 deletions src/planet_mcp/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
from typing import Any
from typing import NotRequired

from typing_extensions import TypedDict

Position = list[float]
Coordinates = (
Position | list[Position] | list[list[Position]] | list[list[list[Position]]]
)


class Geometry(TypedDict):
type: str
coordinates: Any
content: str | None
coordinates: NotRequired[Coordinates]
content: NotRequired[str]
12 changes: 8 additions & 4 deletions src/planet_mcp/server.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import AsyncIterator

from fastmcp import FastMCP

from planet_mcp import servers

_instructions = """
Expand Down Expand Up @@ -41,13 +43,15 @@ def init(
for server in servers.all:
try:
# server protocol is either a variable or callable named mcp
entry = getattr(server, "mcp")
entry = server.mcp
except AttributeError:
raise Exception(f"programmer error, mcp attribute not in {server}")
raise AttributeError(
f"programmer error, mcp attribute not in {server}"
) from None
if callable(entry):
entry = entry()
if not isinstance(entry, FastMCP):
raise Exception(
raise TypeError(
f"programmer error, expected FastMCP type, got {type(entry)}"
)
if enabled_servers is None or entry.name in enabled_servers:
Expand Down
4 changes: 1 addition & 3 deletions src/planet_mcp/servers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from . import sdk
from . import tiles
from . import search
from . import sdk, search, tiles

all = [
sdk,
Expand Down
12 changes: 7 additions & 5 deletions src/planet_mcp/servers/sdk.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import functools
import inspect
from types import NoneType
import typing
from types import NoneType
from typing import Union

import planet
from fastmcp import FastMCP

from planet_mcp import models
from planet_mcp.clients import session

from . import descriptions
from fastmcp import FastMCP
import planet
from typing import Union

# tools we don't want enabled at all.
# they simply don't work well in an AI context.
Expand Down Expand Up @@ -165,7 +167,7 @@ async def wrapper(*args, **kwargs):
hint = hint | None
wrapper.__annotations__[param_name] = hint

except Exception as e:
except Exception as e: # noqa: BLE001
print(f"Error modifying signature: {e}")
wrapper.__annotations__ = {}

Expand Down
8 changes: 2 additions & 6 deletions src/planet_mcp/servers/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,9 @@ async def data_search(
if start_date is not None or end_date is not None:
datefilter_config = {}
if start_date is not None:
datefilter_config["gte"] = datetime.fromisoformat(
start_date.replace("Z", "+00:00")
).isoformat()
datefilter_config["gte"] = datetime.fromisoformat(start_date).isoformat()
if end_date is not None:
datefilter_config["lte"] = datetime.fromisoformat(
end_date.replace("Z", "+00:00")
).isoformat()
datefilter_config["lte"] = datetime.fromisoformat(end_date).isoformat()
datefilter = {
"type": "DateRangeFilter",
"field_name": "acquired",
Expand Down
5 changes: 3 additions & 2 deletions src/planet_mcp/servers/tiles.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import Annotated
from fastmcp import FastMCP
from fastmcp.utilities.types import Image

import httpx
import mercantile
from fastmcp import FastMCP
from fastmcp.utilities.types import Image
from pydantic import Field

from planet_mcp.clients import session
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from http import HTTPStatus

import httpx
import pytest
import respx
Expand Down
35 changes: 33 additions & 2 deletions tests/test_server.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import json
from http import HTTPStatus

import httpx
import pytest
from fastmcp import Client
import respx
from fastmcp import Client

from planet_mcp.server import init

client = Client(init())
Expand All @@ -23,9 +26,37 @@ async def test_search_tool():
"item_types": ["SkySatScene"],
"start_date": "2023-01-01",
"end_date": "2023-01-02",
"geometry": {"type": "Point", "coordinates": [0, 0], "content": None},
"geometry": {"type": "Point", "coordinates": [0, 0]},
},
)
assert len(result.content) == 1
assert result.content[0].type == "text"
assert result.content[0].text == '[{"type":"Feature"}]'

request = json.loads(respx.calls.last.request.content)
assert request["geometry"] == {"type": "Point", "coordinates": [0, 0]}


@pytest.mark.asyncio
@respx.mock
async def test_search_tool_feature_reference():
respx.request(
"POST", "https://api.planet.com/data/v1/quick-search"
).return_value = httpx.Response(
HTTPStatus.OK, json={"features": [{"type": "Feature"}]}
)
ref = "pl:features/my/test-collection-123/my-feature-id"
async with client:
result = await client.call_tool(
"sdk_data_search",
{
"item_types": ["SkySatScene"],
"start_date": None,
"end_date": None,
"geometry": {"type": "ref", "content": ref},
},
)
assert result.content[0].text == '[{"type":"Feature"}]'

request = json.loads(respx.calls.last.request.content)
assert request["geometry"] == {"type": "ref", "content": ref}
49 changes: 24 additions & 25 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading