groupme-mcp-server 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dariel Dato-on
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: groupme-mcp-server
3
+ Version: 0.1.0
4
+ Summary: A FastMCP server that exposes the GroupMe API v3 to MCP clients.
5
+ Keywords: fastmcp,groupme,llm,mcp,model-context-protocol
6
+ Author: Dariel Dato-on
7
+ Author-email: Dariel Dato-on <80038+oddrationale@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Communications :: Chat
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Typing :: Typed
20
+ Requires-Dist: fastmcp>=3.4.7,<4
21
+ Requires-Dist: pydantic-settings>=2.15,<3
22
+ Maintainer: Dariel Dato-on
23
+ Maintainer-email: Dariel Dato-on <80038+oddrationale@users.noreply.github.com>
24
+ Requires-Python: >=3.13
25
+ Project-URL: Homepage, https://github.com/oddrationale/groupme-mcp-server
26
+ Project-URL: Repository, https://github.com/oddrationale/groupme-mcp-server
27
+ Project-URL: Issues, https://github.com/oddrationale/groupme-mcp-server/issues
28
+ Project-URL: Changelog, https://github.com/oddrationale/groupme-mcp-server/blob/main/CHANGELOG.md
29
+ Description-Content-Type: text/markdown
30
+
31
+ # groupme-mcp-server
32
+
33
+ [![CI](https://github.com/oddrationale/groupme-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/oddrationale/groupme-mcp-server/actions/workflows/ci.yml)
34
+ [![codecov](https://codecov.io/gh/oddrationale/groupme-mcp-server/branch/main/graph/badge.svg)](https://codecov.io/gh/oddrationale/groupme-mcp-server)
35
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/oddrationale/groupme-mcp-server/badge)](https://scorecard.dev/viewer/?uri=github.com/oddrationale/groupme-mcp-server)
36
+ [![PyPI](https://img.shields.io/pypi/v/groupme-mcp-server.svg)](https://pypi.org/project/groupme-mcp-server/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/groupme-mcp-server.svg)](https://pypi.org/project/groupme-mcp-server/)
38
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
39
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
40
+
41
+ An [MCP](https://modelcontextprotocol.io) server that exposes the
42
+ [GroupMe API v3](https://dev.groupme.com/docs/v3) to MCP clients, built with
43
+ [FastMCP](https://gofastmcp.com) and hosted on
44
+ [Prefect Horizon](https://gofastmcp.com/deployment/prefect-horizon).
45
+
46
+ > **Status: scaffolding.** The project structure, tooling, and CI/CD are in
47
+ > place. No GroupMe tools are implemented yet.
48
+
49
+ ## Quick start
50
+
51
+ ```bash
52
+ uvx groupme-mcp-server
53
+ ```
54
+
55
+ Or point an MCP client at it directly:
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "groupme": {
61
+ "command": "uvx",
62
+ "args": ["groupme-mcp-server"],
63
+ "env": {
64
+ "GROUPME_MCP_LOG_LEVEL": "INFO"
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## Configuration
72
+
73
+ All settings are read from the environment with the `GROUPME_MCP_` prefix, or
74
+ from a local `.env` file. See [`.env.example`](.env.example).
75
+
76
+ | Variable | Default | Description |
77
+ | ------------------------ | ------- | --------------------------------------------------------------- |
78
+ | `GROUPME_MCP_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`. |
79
+
80
+ ## Development
81
+
82
+ Requires [uv](https://docs.astral.sh/uv/) and Python 3.13+.
83
+
84
+ ```bash
85
+ git clone https://github.com/oddrationale/groupme-mcp-server.git
86
+ cd groupme-mcp-server
87
+ uv sync --all-groups
88
+ uv run lefthook install # install the git hooks
89
+ ```
90
+
91
+ Common tasks:
92
+
93
+ | Command | What it does |
94
+ | -------------------------------- | --------------------------------------------------- |
95
+ | `uv run ruff format .` | Format. |
96
+ | `uv run ruff check --fix .` | Lint and autofix. |
97
+ | `uv run ty check` | Type check. |
98
+ | `uv run pytest` | Run tests. **Fails below 100% coverage.** |
99
+ | `uv run pytest --no-cov -k name` | Run a subset without the coverage gate. |
100
+ | `uv run fastmcp inspect src/groupme_mcp_server/server.py:mcp` | See what Horizon sees. |
101
+
102
+ Coverage is enforced at **100%** (branch coverage included). If a line is
103
+ genuinely untestable, exclude it deliberately with `# pragma: no cover` and say
104
+ why in the PR — do not lower the threshold.
105
+
106
+ ## Deployment
107
+
108
+ The server is live at **https://groupme.fastmcp.app/mcp**, deployed on
109
+ [Prefect Horizon](https://horizon.prefect.io), which builds directly from this
110
+ repository via its GitHub App.
111
+
112
+ - **Entrypoint:** `src/groupme_mcp_server/server.py:mcp`
113
+ - **Dependencies:** installed with `uv sync --frozen --no-dev`, so `uv.lock`
114
+ must be committed and current or the build fails
115
+ - **Environment variables:** registered in the Horizon UI
116
+ - **Auth:** Horizon's built-in OAuth — clients must present a bearer token
117
+
118
+ The `production` target tracks `main` and deploys only after CI passes; every
119
+ pull request gets its own preview deployment. There is no deploy step in GitHub
120
+ Actions — CI gates quality and security, Horizon does the shipping.
121
+
122
+ ## Contributing
123
+
124
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Security issues go through
125
+ [private vulnerability reporting](https://github.com/oddrationale/groupme-mcp-server/security/advisories/new),
126
+ not public issues — see [SECURITY.md](SECURITY.md).
127
+
128
+ ## License
129
+
130
+ [MIT](LICENSE) © Dariel Dato-on
@@ -0,0 +1,100 @@
1
+ # groupme-mcp-server
2
+
3
+ [![CI](https://github.com/oddrationale/groupme-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/oddrationale/groupme-mcp-server/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/oddrationale/groupme-mcp-server/branch/main/graph/badge.svg)](https://codecov.io/gh/oddrationale/groupme-mcp-server)
5
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/oddrationale/groupme-mcp-server/badge)](https://scorecard.dev/viewer/?uri=github.com/oddrationale/groupme-mcp-server)
6
+ [![PyPI](https://img.shields.io/pypi/v/groupme-mcp-server.svg)](https://pypi.org/project/groupme-mcp-server/)
7
+ [![Python](https://img.shields.io/pypi/pyversions/groupme-mcp-server.svg)](https://pypi.org/project/groupme-mcp-server/)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
10
+
11
+ An [MCP](https://modelcontextprotocol.io) server that exposes the
12
+ [GroupMe API v3](https://dev.groupme.com/docs/v3) to MCP clients, built with
13
+ [FastMCP](https://gofastmcp.com) and hosted on
14
+ [Prefect Horizon](https://gofastmcp.com/deployment/prefect-horizon).
15
+
16
+ > **Status: scaffolding.** The project structure, tooling, and CI/CD are in
17
+ > place. No GroupMe tools are implemented yet.
18
+
19
+ ## Quick start
20
+
21
+ ```bash
22
+ uvx groupme-mcp-server
23
+ ```
24
+
25
+ Or point an MCP client at it directly:
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "groupme": {
31
+ "command": "uvx",
32
+ "args": ["groupme-mcp-server"],
33
+ "env": {
34
+ "GROUPME_MCP_LOG_LEVEL": "INFO"
35
+ }
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Configuration
42
+
43
+ All settings are read from the environment with the `GROUPME_MCP_` prefix, or
44
+ from a local `.env` file. See [`.env.example`](.env.example).
45
+
46
+ | Variable | Default | Description |
47
+ | ------------------------ | ------- | --------------------------------------------------------------- |
48
+ | `GROUPME_MCP_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`. |
49
+
50
+ ## Development
51
+
52
+ Requires [uv](https://docs.astral.sh/uv/) and Python 3.13+.
53
+
54
+ ```bash
55
+ git clone https://github.com/oddrationale/groupme-mcp-server.git
56
+ cd groupme-mcp-server
57
+ uv sync --all-groups
58
+ uv run lefthook install # install the git hooks
59
+ ```
60
+
61
+ Common tasks:
62
+
63
+ | Command | What it does |
64
+ | -------------------------------- | --------------------------------------------------- |
65
+ | `uv run ruff format .` | Format. |
66
+ | `uv run ruff check --fix .` | Lint and autofix. |
67
+ | `uv run ty check` | Type check. |
68
+ | `uv run pytest` | Run tests. **Fails below 100% coverage.** |
69
+ | `uv run pytest --no-cov -k name` | Run a subset without the coverage gate. |
70
+ | `uv run fastmcp inspect src/groupme_mcp_server/server.py:mcp` | See what Horizon sees. |
71
+
72
+ Coverage is enforced at **100%** (branch coverage included). If a line is
73
+ genuinely untestable, exclude it deliberately with `# pragma: no cover` and say
74
+ why in the PR — do not lower the threshold.
75
+
76
+ ## Deployment
77
+
78
+ The server is live at **https://groupme.fastmcp.app/mcp**, deployed on
79
+ [Prefect Horizon](https://horizon.prefect.io), which builds directly from this
80
+ repository via its GitHub App.
81
+
82
+ - **Entrypoint:** `src/groupme_mcp_server/server.py:mcp`
83
+ - **Dependencies:** installed with `uv sync --frozen --no-dev`, so `uv.lock`
84
+ must be committed and current or the build fails
85
+ - **Environment variables:** registered in the Horizon UI
86
+ - **Auth:** Horizon's built-in OAuth — clients must present a bearer token
87
+
88
+ The `production` target tracks `main` and deploys only after CI passes; every
89
+ pull request gets its own preview deployment. There is no deploy step in GitHub
90
+ Actions — CI gates quality and security, Horizon does the shipping.
91
+
92
+ ## Contributing
93
+
94
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Security issues go through
95
+ [private vulnerability reporting](https://github.com/oddrationale/groupme-mcp-server/security/advisories/new),
96
+ not public issues — see [SECURITY.md](SECURITY.md).
97
+
98
+ ## License
99
+
100
+ [MIT](LICENSE) © Dariel Dato-on
@@ -0,0 +1,213 @@
1
+ [project]
2
+ name = "groupme-mcp-server"
3
+ version = "0.1.0"
4
+ description = "A FastMCP server that exposes the GroupMe API v3 to MCP clients."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.13"
9
+ keywords = [
10
+ "fastmcp",
11
+ "groupme",
12
+ "llm",
13
+ "mcp",
14
+ "model-context-protocol",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Topic :: Communications :: Chat",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = [
29
+ "fastmcp>=3.4.7,<4",
30
+ "pydantic-settings>=2.15,<3",
31
+ ]
32
+
33
+ [[project.authors]]
34
+ name = "Dariel Dato-on"
35
+ email = "80038+oddrationale@users.noreply.github.com"
36
+
37
+ [[project.maintainers]]
38
+ name = "Dariel Dato-on"
39
+ email = "80038+oddrationale@users.noreply.github.com"
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/oddrationale/groupme-mcp-server"
43
+ Repository = "https://github.com/oddrationale/groupme-mcp-server"
44
+ Issues = "https://github.com/oddrationale/groupme-mcp-server/issues"
45
+ Changelog = "https://github.com/oddrationale/groupme-mcp-server/blob/main/CHANGELOG.md"
46
+
47
+ [project.scripts]
48
+ groupme-mcp-server = "groupme_mcp_server.__main__:main"
49
+
50
+ [build-system]
51
+ requires = ["uv_build>=0.12.7,<0.13.0"]
52
+ build-backend = "uv_build"
53
+
54
+ [dependency-groups]
55
+ dev = [
56
+ "lefthook>=2.1.12",
57
+ "pytest>=9.1.1",
58
+ "pytest-asyncio>=1.4",
59
+ "pytest-cov>=7.1",
60
+ "ruff>=0.16.5",
61
+ "ty>=0.0.75",
62
+ ]
63
+
64
+ [tool.ruff]
65
+ line-length = 100
66
+ target-version = "py313"
67
+ src = [
68
+ "src",
69
+ "tests",
70
+ ]
71
+ extend-exclude = [".venv"]
72
+
73
+ [tool.ruff.format]
74
+ docstring-code-format = true
75
+
76
+ [tool.ruff.lint]
77
+ select = [
78
+ "A",
79
+ "ANN",
80
+ "ARG",
81
+ "ASYNC",
82
+ "B",
83
+ "BLE",
84
+ "C4",
85
+ "C90",
86
+ "D",
87
+ "DTZ",
88
+ "E",
89
+ "EM",
90
+ "ERA",
91
+ "F",
92
+ "FA",
93
+ "FBT",
94
+ "FURB",
95
+ "G",
96
+ "I",
97
+ "ICN",
98
+ "INP",
99
+ "ISC",
100
+ "LOG",
101
+ "N",
102
+ "PERF",
103
+ "PGH",
104
+ "PIE",
105
+ "PL",
106
+ "PT",
107
+ "PTH",
108
+ "PYI",
109
+ "RET",
110
+ "RSE",
111
+ "RUF",
112
+ "S",
113
+ "SIM",
114
+ "SLF",
115
+ "SLOT",
116
+ "T10",
117
+ "T20",
118
+ "TC",
119
+ "TID",
120
+ "TRY",
121
+ "UP",
122
+ "W",
123
+ "YTT",
124
+ ]
125
+ ignore = [
126
+ "D203",
127
+ "D213",
128
+ "ISC001",
129
+ ]
130
+
131
+ [tool.ruff.lint.per-file-ignores]
132
+ "scripts/*.py" = ["INP001"]
133
+ "tests/**/*.py" = [
134
+ "D100",
135
+ "D103",
136
+ "D104",
137
+ "INP001",
138
+ "PLR2004",
139
+ "S101",
140
+ "SLF001",
141
+ "TC",
142
+ ]
143
+
144
+ [tool.ruff.lint.flake8-tidy-imports]
145
+ ban-relative-imports = "all"
146
+
147
+ [tool.ruff.lint.flake8-type-checking]
148
+ strict = true
149
+
150
+ [tool.ruff.lint.isort]
151
+ known-first-party = ["groupme_mcp_server"]
152
+ required-imports = ["from __future__ import annotations"]
153
+
154
+ [tool.ruff.lint.mccabe]
155
+ max-complexity = 10
156
+
157
+ [tool.ruff.lint.pydocstyle]
158
+ convention = "google"
159
+
160
+ [tool.ruff.lint.pylint]
161
+ max-args = 6
162
+
163
+ [tool.ty.environment]
164
+ python-version = "3.13"
165
+
166
+ [tool.ty.src]
167
+ include = [
168
+ "scripts",
169
+ "src",
170
+ "tests",
171
+ ]
172
+
173
+ [tool.ty.terminal]
174
+ error-on-warning = true
175
+
176
+ [tool.pytest.ini_options]
177
+ minversion = "9.0"
178
+ testpaths = ["tests"]
179
+ pythonpath = ["src"]
180
+ addopts = [
181
+ "--cov",
182
+ "--cov-report=term-missing:skip-covered",
183
+ "--cov-report=xml",
184
+ "--import-mode=importlib",
185
+ "--strict-config",
186
+ "--strict-markers",
187
+ "-ra",
188
+ ]
189
+ asyncio_mode = "auto"
190
+ xfail_strict = true
191
+ filterwarnings = ["error"]
192
+ log_cli_level = "INFO"
193
+
194
+ [tool.coverage.run]
195
+ source_pkgs = ["groupme_mcp_server"]
196
+ branch = true
197
+ relative_files = true
198
+
199
+ [tool.coverage.paths]
200
+ source = [
201
+ "src",
202
+ "*/site-packages",
203
+ ]
204
+
205
+ [tool.coverage.report]
206
+ fail_under = 100
207
+ show_missing = true
208
+ skip_covered = true
209
+ exclude_also = [
210
+ "if TYPE_CHECKING:",
211
+ "raise NotImplementedError",
212
+ '@(abc\.)?abstractmethod',
213
+ ]
@@ -0,0 +1,213 @@
1
+ [project]
2
+ name = "groupme-mcp-server"
3
+ version = "0.1.0"
4
+ description = "A FastMCP server that exposes the GroupMe API v3 to MCP clients."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "Dariel Dato-on", email = "80038+oddrationale@users.noreply.github.com" },
10
+ ]
11
+ maintainers = [
12
+ { name = "Dariel Dato-on", email = "80038+oddrationale@users.noreply.github.com" },
13
+ ]
14
+ requires-python = ">=3.13"
15
+ keywords = [
16
+ "fastmcp",
17
+ "groupme",
18
+ "llm",
19
+ "mcp",
20
+ "model-context-protocol",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 3 - Alpha",
24
+ "Intended Audience :: Developers",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3 :: Only",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
30
+ "Topic :: Communications :: Chat",
31
+ "Topic :: Software Development :: Libraries :: Python Modules",
32
+ "Typing :: Typed",
33
+ ]
34
+ dependencies = [
35
+ "fastmcp>=3.4.7,<4",
36
+ "pydantic-settings>=2.15,<3",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/oddrationale/groupme-mcp-server"
41
+ Repository = "https://github.com/oddrationale/groupme-mcp-server"
42
+ Issues = "https://github.com/oddrationale/groupme-mcp-server/issues"
43
+ Changelog = "https://github.com/oddrationale/groupme-mcp-server/blob/main/CHANGELOG.md"
44
+
45
+ [project.scripts]
46
+ groupme-mcp-server = "groupme_mcp_server.__main__:main"
47
+
48
+ [build-system]
49
+ requires = ["uv_build>=0.12.7,<0.13.0"]
50
+ build-backend = "uv_build"
51
+
52
+ [dependency-groups]
53
+ dev = [
54
+ "lefthook>=2.1.12",
55
+ "pytest>=9.1.1",
56
+ "pytest-asyncio>=1.4",
57
+ "pytest-cov>=7.1",
58
+ "ruff>=0.16.5",
59
+ "ty>=0.0.75",
60
+ ]
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # Ruff - linting and formatting
64
+ # ---------------------------------------------------------------------------
65
+ [tool.ruff]
66
+ line-length = 100
67
+ target-version = "py313"
68
+ src = ["src", "tests"]
69
+ extend-exclude = [".venv"]
70
+
71
+ [tool.ruff.format]
72
+ docstring-code-format = true
73
+
74
+ [tool.ruff.lint]
75
+ select = [
76
+ "A", # flake8-builtins
77
+ "ANN", # flake8-annotations
78
+ "ARG", # flake8-unused-arguments
79
+ "ASYNC", # flake8-async
80
+ "B", # flake8-bugbear
81
+ "BLE", # flake8-blind-except
82
+ "C4", # flake8-comprehensions
83
+ "C90", # mccabe
84
+ "D", # pydocstyle
85
+ "DTZ", # flake8-datetimez
86
+ "E", # pycodestyle errors
87
+ "EM", # flake8-errmsg
88
+ "ERA", # eradicate
89
+ "F", # pyflakes
90
+ "FA", # flake8-future-annotations
91
+ "FBT", # flake8-boolean-trap
92
+ "FURB", # refurb
93
+ "G", # flake8-logging-format
94
+ "I", # isort
95
+ "ICN", # flake8-import-conventions
96
+ "INP", # flake8-no-pep420
97
+ "ISC", # flake8-implicit-str-concat
98
+ "LOG", # flake8-logging
99
+ "N", # pep8-naming
100
+ "PERF", # perflint
101
+ "PGH", # pygrep-hooks
102
+ "PIE", # flake8-pie
103
+ "PL", # pylint
104
+ "PT", # flake8-pytest-style
105
+ "PTH", # flake8-use-pathlib
106
+ "PYI", # flake8-pyi
107
+ "RET", # flake8-return
108
+ "RSE", # flake8-raise
109
+ "RUF", # ruff-specific rules
110
+ "S", # flake8-bandit
111
+ "SIM", # flake8-simplify
112
+ "SLF", # flake8-self
113
+ "SLOT", # flake8-slots
114
+ "T10", # flake8-debugger
115
+ "T20", # flake8-print
116
+ "TC", # flake8-type-checking
117
+ "TID", # flake8-tidy-imports
118
+ "TRY", # tryceratops
119
+ "UP", # pyupgrade
120
+ "W", # pycodestyle warnings
121
+ "YTT", # flake8-2020
122
+ ]
123
+ ignore = [
124
+ "D203", # incompatible with D211 (no blank line before class docstring)
125
+ "D213", # incompatible with D212 (summary on first line)
126
+ "ISC001", # conflicts with the formatter
127
+ ]
128
+
129
+ [tool.ruff.lint.per-file-ignores]
130
+ "scripts/*.py" = ["INP001"] # standalone hook scripts, not a package
131
+ "tests/**/*.py" = [
132
+ "D100", # tests do not need module docstrings
133
+ "D103", # tests do not need function docstrings
134
+ "D104", # tests do not need package docstrings
135
+ "INP001", # tests are intentionally not a package
136
+ "PLR2004", # magic values are fine in assertions
137
+ "S101", # assert is the point of a test
138
+ "SLF001", # tests may poke at private members
139
+ "TC", # fixture/param types read better as plain imports in tests
140
+ ]
141
+
142
+ [tool.ruff.lint.flake8-tidy-imports]
143
+ ban-relative-imports = "all"
144
+
145
+ [tool.ruff.lint.flake8-type-checking]
146
+ strict = true
147
+
148
+ [tool.ruff.lint.isort]
149
+ known-first-party = ["groupme_mcp_server"]
150
+ required-imports = ["from __future__ import annotations"]
151
+
152
+ [tool.ruff.lint.mccabe]
153
+ max-complexity = 10
154
+
155
+ [tool.ruff.lint.pydocstyle]
156
+ convention = "google"
157
+
158
+ [tool.ruff.lint.pylint]
159
+ max-args = 6
160
+
161
+ # ---------------------------------------------------------------------------
162
+ # ty - type checking
163
+ # ---------------------------------------------------------------------------
164
+ [tool.ty.environment]
165
+ python-version = "3.13"
166
+
167
+ [tool.ty.src]
168
+ include = ["scripts", "src", "tests"]
169
+
170
+ [tool.ty.terminal]
171
+ error-on-warning = true
172
+
173
+ # ---------------------------------------------------------------------------
174
+ # pytest
175
+ # ---------------------------------------------------------------------------
176
+ [tool.pytest.ini_options]
177
+ minversion = "9.0"
178
+ testpaths = ["tests"]
179
+ pythonpath = ["src"]
180
+ addopts = [
181
+ "--cov",
182
+ "--cov-report=term-missing:skip-covered",
183
+ "--cov-report=xml",
184
+ "--import-mode=importlib",
185
+ "--strict-config",
186
+ "--strict-markers",
187
+ "-ra",
188
+ ]
189
+ asyncio_mode = "auto"
190
+ xfail_strict = true
191
+ filterwarnings = ["error"]
192
+ log_cli_level = "INFO"
193
+
194
+ # ---------------------------------------------------------------------------
195
+ # coverage - 100% or bust
196
+ # ---------------------------------------------------------------------------
197
+ [tool.coverage.run]
198
+ source_pkgs = ["groupme_mcp_server"]
199
+ branch = true
200
+ relative_files = true
201
+
202
+ [tool.coverage.paths]
203
+ source = ["src", "*/site-packages"]
204
+
205
+ [tool.coverage.report]
206
+ fail_under = 100
207
+ show_missing = true
208
+ skip_covered = true
209
+ exclude_also = [
210
+ "if TYPE_CHECKING:",
211
+ "raise NotImplementedError",
212
+ '@(abc\.)?abstractmethod',
213
+ ]
@@ -0,0 +1,12 @@
1
+ """A FastMCP server that exposes the GroupMe API v3 to MCP clients."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import version
6
+
7
+ from groupme_mcp_server.server import mcp
8
+ from groupme_mcp_server.settings import Settings, get_settings
9
+
10
+ __version__ = version("groupme-mcp-server")
11
+
12
+ __all__ = ["Settings", "__version__", "get_settings", "mcp"]
@@ -0,0 +1,14 @@
1
+ """Console-script entrypoint for running the server locally over stdio."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from groupme_mcp_server.server import mcp
6
+
7
+
8
+ def main() -> None:
9
+ """Run the GroupMe MCP server over stdio."""
10
+ mcp.run()
11
+
12
+
13
+ if __name__ == "__main__": # pragma: no cover
14
+ main()
@@ -0,0 +1,15 @@
1
+ """The FastMCP server instance.
2
+
3
+ Prefect Horizon is configured to load this module and use the ``mcp`` object as
4
+ its entrypoint (``src/groupme_mcp_server/server.py:mcp``).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from fastmcp import FastMCP
10
+
11
+ INSTRUCTIONS = """\
12
+ Tools for reading and sending GroupMe messages via the GroupMe API v3.
13
+ """
14
+
15
+ mcp: FastMCP = FastMCP(name="groupme-mcp-server", instructions=INSTRUCTIONS)
@@ -0,0 +1,39 @@
1
+ """Runtime configuration, loaded from the environment.
2
+
3
+ Values are read from ``GROUPME_MCP_*`` environment variables (Prefect Horizon
4
+ injects these at runtime) or from a local ``.env`` file during development.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from functools import lru_cache
10
+ from typing import Literal
11
+
12
+ from pydantic_settings import BaseSettings, SettingsConfigDict
13
+
14
+ LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
15
+
16
+
17
+ class Settings(BaseSettings):
18
+ """Configuration for the GroupMe MCP server."""
19
+
20
+ model_config = SettingsConfigDict(
21
+ env_prefix="GROUPME_MCP_",
22
+ env_file=".env",
23
+ env_file_encoding="utf-8",
24
+ extra="ignore",
25
+ frozen=True,
26
+ )
27
+
28
+ log_level: LogLevel = "INFO"
29
+ """Verbosity of the server's own logging."""
30
+
31
+
32
+ @lru_cache(maxsize=1)
33
+ def get_settings() -> Settings:
34
+ """Return the process-wide settings, constructing them on first use.
35
+
36
+ Returns:
37
+ The cached [`Settings`][groupme_mcp_server.settings.Settings] instance.
38
+ """
39
+ return Settings()