Skip to main content

Overview

GroupMe MCP is a thin MCP server layered over the GroupMe API v3. It exposes GroupMe operations as MCP tools that any compatible AI client can call. The server is built with:
  • FastMCP from the mcp library — handles tool registration and the MCP protocol
  • httpx — async HTTP client for all GroupMe API requests
  • stdio transport — the server communicates with MCP clients via standard input/output
The source is split across three files under src/groupme_mcp/.

Source structure

client.py — GroupMeClient

GroupMeClient is an async HTTP client that wraps the GroupMe REST API. Key details:
  • Reads GROUPME_TOKEN from the environment at __init__ time — the server will not start without it
  • Base URL is https://api.groupme.com/v3
  • Three private helpers — _get, _post, and _delete — handle all HTTP communication. They inject the token as a query parameter (?token=...) on every request and call raise_for_status() so HTTP errors propagate as exceptions
  • All methods are async and open a fresh httpx.AsyncClient per request
Example method:
The _get helper adds token to params and sends the request:

server.py — MCP tools

server.py defines all MCP tools using FastMCP’s @mcp.tool() decorator. A single GroupMeClient instance is created at module load time and shared across all tools. Key details:
  • Each tool is a thin shim — it validates or transforms arguments if needed, then delegates directly to the corresponding GroupMeClient method
  • Tools that send messages (send_message, send_direct_message) auto-generate a source_guid UUID when the caller omits it. This prevents the GroupMe API from rejecting duplicate sends if the tool is called twice with the same payload
  • Docstrings are required — FastMCP uses the docstring and Args: block to generate the tool’s JSON schema exposed to MCP clients
Example tool:

main.py — Entry point

main.py is the entrypoint invoked by the groupme-mcp script (defined in pyproject.toml). It performs one check before starting: if GROUPME_TOKEN is not set in the environment, it prints a helpful error message to stderr and exits with code 1. This prevents a confusing crash inside GroupMeClient.__init__.
The import of server is intentionally deferred — it instantiates GroupMeClient, which reads GROUPME_TOKEN. Importing before the check would cause a KeyError instead of the friendly error message.

Transport

The server uses stdio transport (transport="stdio"). MCP clients such as Claude Desktop communicate with the server by spawning it as a subprocess and writing/reading JSON-RPC messages over stdin and stdout. This is the standard transport for locally installed MCP servers. It requires no network port, no authentication beyond the process boundary, and works with any MCP-compatible client that supports subprocess execution.

Testing approach

Tests are in tests/ and split into two files:
  • tests/test_client.py — tests at the HTTP level, verifying that GroupMeClient methods build the correct URLs and pass the correct parameters
  • tests/test_server.py — tests at the tool level, verifying that tool functions call the correct client methods with the correct arguments
Both files use pytest-httpx (HTTPXMock) to intercept outgoing httpx requests at the transport level, so no real GroupMe API calls are made. asyncio_mode = "auto" is set in pyproject.toml, so async test functions run automatically without @pytest.mark.asyncio.
Both test files call os.environ.setdefault("GROUPME_TOKEN", "test-token") before importing anything from groupme_mcp. This must come first, because GroupMeClient reads the token at __init__ time and the module is imported at collection time.