feat: implement MCP server wrapping groupme-sdk #2

Merged
forgejo_admin merged 2 commits from 1-feat-implement-mcp-server-wrapping-group into main 2026-03-24 08:49:44 +00:00

Summary

MCP server wrapping groupme-sdk so AI agents can manage GroupMe groups, members, and messages from conversations. Follows pal-e-docs-mcp patterns exactly: FastMCP decorators, hatchling build, uv for dependency management, Forgejo PyPI for groupme-sdk.

Changes

  • pyproject.toml -- project config with groupme-sdk from Forgejo PyPI, hatchling build, ruff + pytest config
  • src/groupme_mcp/server.py -- FastMCP app, GroupMeClient lifecycle, _ok/_error_response helpers
  • src/groupme_mcp/__main__.py -- entry point for python -m groupme_mcp
  • src/groupme_mcp/tools/__init__.py -- tool registration via module imports
  • src/groupme_mcp/tools/groups.py -- create_group, list_groups, get_group
  • src/groupme_mcp/tools/members.py -- add_member, remove_member, list_members
  • src/groupme_mcp/tools/messages.py -- send_message
  • tests/conftest.py -- mock_client fixture patching get_client in all tool modules
  • tests/test_groups.py -- 8 tests covering group tools + error handling
  • tests/test_members.py -- 8 tests covering member tools + error handling
  • tests/test_messages.py -- 3 tests covering send_message + error handling
  • .gitignore -- standard Python ignores

Test Plan

  • pytest tests/ -- 20 tests, all passing
  • ruff format + ruff check -- clean
  • membership_audit is DEFERRED per issue spec (depends on basketball-api DB tables)

Review Checklist

  • Tests pass (pytest tests/ -- 20 passed)
  • Linter clean (ruff format + ruff check)
  • No unrelated changes
  • Follows pal-e-docs-mcp patterns (FastMCP, hatchling, uv, Forgejo PyPI)
  • Plan: plan-wkq
  • Forgejo issue: Closes #1
  • Parent issue: forgejo_admin/basketball-api#157
## Summary MCP server wrapping groupme-sdk so AI agents can manage GroupMe groups, members, and messages from conversations. Follows pal-e-docs-mcp patterns exactly: FastMCP decorators, hatchling build, uv for dependency management, Forgejo PyPI for groupme-sdk. ## Changes - `pyproject.toml` -- project config with groupme-sdk from Forgejo PyPI, hatchling build, ruff + pytest config - `src/groupme_mcp/server.py` -- FastMCP app, GroupMeClient lifecycle, _ok/_error_response helpers - `src/groupme_mcp/__main__.py` -- entry point for `python -m groupme_mcp` - `src/groupme_mcp/tools/__init__.py` -- tool registration via module imports - `src/groupme_mcp/tools/groups.py` -- create_group, list_groups, get_group - `src/groupme_mcp/tools/members.py` -- add_member, remove_member, list_members - `src/groupme_mcp/tools/messages.py` -- send_message - `tests/conftest.py` -- mock_client fixture patching get_client in all tool modules - `tests/test_groups.py` -- 8 tests covering group tools + error handling - `tests/test_members.py` -- 8 tests covering member tools + error handling - `tests/test_messages.py` -- 3 tests covering send_message + error handling - `.gitignore` -- standard Python ignores ## Test Plan - `pytest tests/` -- 20 tests, all passing - `ruff format` + `ruff check` -- clean - membership_audit is DEFERRED per issue spec (depends on basketball-api DB tables) ## Review Checklist - [x] Tests pass (`pytest tests/` -- 20 passed) - [x] Linter clean (`ruff format` + `ruff check`) - [x] No unrelated changes - [x] Follows pal-e-docs-mcp patterns (FastMCP, hatchling, uv, Forgejo PyPI) ## Related - Plan: `plan-wkq` - Forgejo issue: Closes #1 - Parent issue: `forgejo_admin/basketball-api#157`
Thin MCP layer over groupme-sdk following pal-e-docs-mcp patterns.
7 tools: create_group, list_groups, get_group, add_member, remove_member,
list_members, send_message. 20 unit tests with mocked SDK.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Review

Verdict: Approved -- clean implementation, no blockers.

What looks good

  • Follows pal-e-docs-mcp patterns exactly: FastMCP, hatchling build, uv sources, Forgejo PyPI index, tool registration via module imports
  • get_client() lazy singleton matches get_sdk() pattern from reference
  • _error_response correctly handles GroupMeHTTPError (with status_code + response_body), GroupMeError, and generic exceptions
  • All 7 tools are thin wrappers -- no business logic in the MCP layer
  • conftest.py patches get_client in each tool module's namespace (same monkeypatch pattern as pal-e-docs-mcp)
  • 20 tests cover happy path, error handling (HTTP errors, generic errors, ValueError), and argument forwarding
  • pyproject.toml matches reference structure (hatchling, uv.sources, forgejo index, ruff config)
  • .gitignore covers pycache, venv, egg-info, build artifacts

No issues found

  • No missing test coverage for the 7 tools
  • No SDK method signature mismatches (verified against groupme-sdk source)
  • No lint violations (ruff format + check clean)
  • membership_audit correctly deferred per spec
## Review **Verdict: Approved** -- clean implementation, no blockers. ### What looks good - Follows pal-e-docs-mcp patterns exactly: FastMCP, hatchling build, uv sources, Forgejo PyPI index, tool registration via module imports - `get_client()` lazy singleton matches `get_sdk()` pattern from reference - `_error_response` correctly handles `GroupMeHTTPError` (with status_code + response_body), `GroupMeError`, and generic exceptions - All 7 tools are thin wrappers -- no business logic in the MCP layer - `conftest.py` patches `get_client` in each tool module's namespace (same monkeypatch pattern as pal-e-docs-mcp) - 20 tests cover happy path, error handling (HTTP errors, generic errors, ValueError), and argument forwarding - `pyproject.toml` matches reference structure (hatchling, uv.sources, forgejo index, ruff config) - `.gitignore` covers pycache, venv, egg-info, build artifacts ### No issues found - No missing test coverage for the 7 tools - No SDK method signature mismatches (verified against groupme-sdk source) - No lint violations (ruff format + check clean) - membership_audit correctly deferred per spec
Author
Owner

PR #2 Review

DOMAIN REVIEW

Tech stack: Python 3.12+ / FastMCP / groupme-sdk / hatchling / uv / pytest / ruff

This is an MCP server wrapping groupme-sdk, following the established pal-e-docs-mcp pattern. I verified against the reference implementation at /home/ldraney/pal-e-docs-mcp/.

Pattern fidelity (pal-e-docs-mcp): Near-perfect match.

  • pyproject.toml structure: identical (hatchling build, uv sources, Forgejo PyPI index, ruff config, pytest config)
  • server.py lifecycle: same singleton pattern (_client / get_client()), same _ok() / _error_response() helpers, same register_all_tools() import-at-module-level pattern
  • tools/__init__.py registration: identical pattern with noqa: F401 on lazy imports
  • Tool modules: same from ..server import _error_response, _ok, get_client, mcp import line, same Annotated[..., Field(...)] parameter typing, same try/except-return-string pattern

Python/PEP compliance:

  • PEP 484 type hints: present on all functions
  • PEP 257 docstrings: present on all public functions and modules
  • from __future__ import annotations: used consistently
  • Line length 120, ruff rules E/F/W/I: configured correctly

Error handling architecture:

  • _error_response() in server.py has a three-tier dispatch: GroupMeHTTPError (includes status_code + optional response_body), GroupMeError (detail only), generic Exception (detail only). The GroupMeError and generic Exception branches produce identical output -- this is intentional (matches pal-e-docs-mcp pattern where SDK errors and generic errors both fall through to the same format).
  • Every tool function wraps its call in try/except Exception and returns structured JSON errors. No uncaught exceptions possible.

SDK integration:

  • GroupMeClient() constructor reads GROUPME_ACCESS_TOKEN from env (verified in groupme-sdk source at /home/ldraney/groupme-sdk/src/groupme_sdk/client.py:33). The MCP server's get_client() delegates this correctly.
  • GroupMeNotFoundError is a subclass of GroupMeHTTPError with hardcoded status_code=404 (verified in /home/ldraney/groupme-sdk/src/groupme_sdk/exceptions.py:19-23). The _error_response handler catches it via the isinstance(exc, GroupMeHTTPError) branch. Correct.

Test quality:

  • 20 tests across 3 files (8 groups, 8 members, 3 messages, plus 1 conftest)
  • Coverage: happy path, error handling (HTTP errors, generic errors, SDK validation errors), default parameter behavior, empty list edge cases
  • Mock strategy: monkeypatch.setattr on each tool module's get_client reference. This is the correct approach since each module imports get_client into its own namespace.
  • Test for GroupMeNotFoundError correctly verifies status_code == 404 in the response.
  • Tests directly call tool functions (not through MCP transport), which is appropriate for unit testing the tool logic.

BLOCKERS

None.

All BLOCKER criteria pass:

  • Test coverage: 20 tests covering all 7 tool functions with happy path + error handling + edge cases
  • Input validation: All user-facing parameters use Pydantic Annotated[..., Field(...)] with descriptions. The add_member tool correctly delegates the "at least one of phone/email/user_id" validation to the SDK (which raises ValueError), and this is tested
  • No secrets in code: GROUPME_ACCESS_TOKEN is read from environment, never hardcoded
  • No DRY violations in auth/security: Single get_client() function, no duplicated auth logic

NITS

  1. Missing README.md update -- pyproject.toml declares readme = "README.md" and the existing README on main is a 2-line stub. For a publishable package, the README should document installation, configuration (GROUPME_ACCESS_TOKEN), available tools, and usage with Claude/MCP. Not a blocker for an internal tool, but worth a follow-up.

  2. _client global reset -- The singleton _client in server.py has no reset_client() function. If the token changes or a test needs to swap clients, there is no clean way to reset. The reference implementation (pal-e-docs-mcp) has the same limitation, so this is a known pattern gap, not a regression.

  3. add_member missing server-side validation -- The tool relies entirely on the SDK to validate that at least one of phone_number, email, or user_id is provided. This is fine (SDK raises ValueError, which is caught and returned as an error), but a @mcp.tool() docstring note that the SDK enforces this constraint would aid agent comprehension.

  4. tests/__init__.py missing -- No __init__.py in the tests directory. Not required by modern pytest, but present in some project conventions.

SOP COMPLIANCE

  • Branch named after issue: 1-feat-implement-mcp-server-wrapping-group (matches issue #1)
  • PR body has: Summary, Changes, Test Plan, Related -- all present
  • Related references plan slug: plan-wkq referenced in Related section
  • Related references parent issue: forgejo_admin/basketball-api#157
  • No secrets committed: confirmed, token read from env
  • No unnecessary file changes (scope creep): all 13 files are directly related to the MCP server implementation
  • Commit messages: PR title is descriptive (feat: implement MCP server wrapping groupme-sdk)
  • membership_audit deferred per spec: explicitly noted in Test Plan

PROCESS OBSERVATIONS

  • Deployment frequency: New repo, first PR. Clean bootstrap following an established pattern reduces change failure risk.
  • Pattern reuse: The pal-e-docs-mcp template is proving its value -- this implementation required zero architectural decisions, just domain adaptation. This is exactly what DORA elite looks like for onboarding new services.
  • Deferred scope: membership_audit tool correctly deferred (depends on basketball-api DB tables). This is good discipline -- ship what works, defer what has external dependencies.

VERDICT: APPROVED

## PR #2 Review ### DOMAIN REVIEW **Tech stack:** Python 3.12+ / FastMCP / groupme-sdk / hatchling / uv / pytest / ruff This is an MCP server wrapping groupme-sdk, following the established pal-e-docs-mcp pattern. I verified against the reference implementation at `/home/ldraney/pal-e-docs-mcp/`. **Pattern fidelity (pal-e-docs-mcp):** Near-perfect match. - `pyproject.toml` structure: identical (hatchling build, uv sources, Forgejo PyPI index, ruff config, pytest config) - `server.py` lifecycle: same singleton pattern (`_client` / `get_client()`), same `_ok()` / `_error_response()` helpers, same `register_all_tools()` import-at-module-level pattern - `tools/__init__.py` registration: identical pattern with `noqa: F401` on lazy imports - Tool modules: same `from ..server import _error_response, _ok, get_client, mcp` import line, same `Annotated[..., Field(...)]` parameter typing, same try/except-return-string pattern **Python/PEP compliance:** - PEP 484 type hints: present on all functions - PEP 257 docstrings: present on all public functions and modules - `from __future__ import annotations`: used consistently - Line length 120, ruff rules E/F/W/I: configured correctly **Error handling architecture:** - `_error_response()` in `server.py` has a three-tier dispatch: `GroupMeHTTPError` (includes status_code + optional response_body), `GroupMeError` (detail only), generic `Exception` (detail only). The `GroupMeError` and generic `Exception` branches produce identical output -- this is intentional (matches pal-e-docs-mcp pattern where SDK errors and generic errors both fall through to the same format). - Every tool function wraps its call in try/except Exception and returns structured JSON errors. No uncaught exceptions possible. **SDK integration:** - `GroupMeClient()` constructor reads `GROUPME_ACCESS_TOKEN` from env (verified in groupme-sdk source at `/home/ldraney/groupme-sdk/src/groupme_sdk/client.py:33`). The MCP server's `get_client()` delegates this correctly. - `GroupMeNotFoundError` is a subclass of `GroupMeHTTPError` with hardcoded `status_code=404` (verified in `/home/ldraney/groupme-sdk/src/groupme_sdk/exceptions.py:19-23`). The `_error_response` handler catches it via the `isinstance(exc, GroupMeHTTPError)` branch. Correct. **Test quality:** - 20 tests across 3 files (8 groups, 8 members, 3 messages, plus 1 conftest) - Coverage: happy path, error handling (HTTP errors, generic errors, SDK validation errors), default parameter behavior, empty list edge cases - Mock strategy: `monkeypatch.setattr` on each tool module's `get_client` reference. This is the correct approach since each module imports `get_client` into its own namespace. - Test for `GroupMeNotFoundError` correctly verifies `status_code == 404` in the response. - Tests directly call tool functions (not through MCP transport), which is appropriate for unit testing the tool logic. ### BLOCKERS None. All BLOCKER criteria pass: - **Test coverage:** 20 tests covering all 7 tool functions with happy path + error handling + edge cases - **Input validation:** All user-facing parameters use Pydantic `Annotated[..., Field(...)]` with descriptions. The `add_member` tool correctly delegates the "at least one of phone/email/user_id" validation to the SDK (which raises `ValueError`), and this is tested - **No secrets in code:** `GROUPME_ACCESS_TOKEN` is read from environment, never hardcoded - **No DRY violations in auth/security:** Single `get_client()` function, no duplicated auth logic ### NITS 1. **Missing `README.md` update** -- `pyproject.toml` declares `readme = "README.md"` and the existing README on main is a 2-line stub. For a publishable package, the README should document installation, configuration (`GROUPME_ACCESS_TOKEN`), available tools, and usage with Claude/MCP. Not a blocker for an internal tool, but worth a follow-up. 2. **`_client` global reset** -- The singleton `_client` in `server.py` has no `reset_client()` function. If the token changes or a test needs to swap clients, there is no clean way to reset. The reference implementation (pal-e-docs-mcp) has the same limitation, so this is a known pattern gap, not a regression. 3. **`add_member` missing server-side validation** -- The tool relies entirely on the SDK to validate that at least one of `phone_number`, `email`, or `user_id` is provided. This is fine (SDK raises `ValueError`, which is caught and returned as an error), but a `@mcp.tool()` docstring note that the SDK enforces this constraint would aid agent comprehension. 4. **`tests/__init__.py` missing** -- No `__init__.py` in the tests directory. Not required by modern pytest, but present in some project conventions. ### SOP COMPLIANCE - [x] Branch named after issue: `1-feat-implement-mcp-server-wrapping-group` (matches issue #1) - [x] PR body has: Summary, Changes, Test Plan, Related -- all present - [x] Related references plan slug: `plan-wkq` referenced in Related section - [x] Related references parent issue: `forgejo_admin/basketball-api#157` - [x] No secrets committed: confirmed, token read from env - [x] No unnecessary file changes (scope creep): all 13 files are directly related to the MCP server implementation - [x] Commit messages: PR title is descriptive (`feat: implement MCP server wrapping groupme-sdk`) - [x] membership_audit deferred per spec: explicitly noted in Test Plan ### PROCESS OBSERVATIONS - **Deployment frequency:** New repo, first PR. Clean bootstrap following an established pattern reduces change failure risk. - **Pattern reuse:** The pal-e-docs-mcp template is proving its value -- this implementation required zero architectural decisions, just domain adaptation. This is exactly what DORA elite looks like for onboarding new services. - **Deferred scope:** `membership_audit` tool correctly deferred (depends on basketball-api DB tables). This is good discipline -- ship what works, defer what has external dependencies. ### VERDICT: APPROVED
forgejo_admin deleted branch 1-feat-implement-mcp-server-wrapping-group 2026-03-24 08:49:44 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/groupme-mcp!2
No description provided.