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.
- groupme_mcp_server-0.1.0/LICENSE +21 -0
- groupme_mcp_server-0.1.0/PKG-INFO +130 -0
- groupme_mcp_server-0.1.0/README.md +100 -0
- groupme_mcp_server-0.1.0/pyproject.toml +213 -0
- groupme_mcp_server-0.1.0/pyproject.toml.orig +213 -0
- groupme_mcp_server-0.1.0/src/groupme_mcp_server/__init__.py +12 -0
- groupme_mcp_server-0.1.0/src/groupme_mcp_server/__main__.py +14 -0
- groupme_mcp_server-0.1.0/src/groupme_mcp_server/py.typed +0 -0
- groupme_mcp_server-0.1.0/src/groupme_mcp_server/server.py +15 -0
- groupme_mcp_server-0.1.0/src/groupme_mcp_server/settings.py +39 -0
|
@@ -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
|
+
[](https://github.com/oddrationale/groupme-mcp-server/actions/workflows/ci.yml)
|
|
34
|
+
[](https://codecov.io/gh/oddrationale/groupme-mcp-server)
|
|
35
|
+
[](https://scorecard.dev/viewer/?uri=github.com/oddrationale/groupme-mcp-server)
|
|
36
|
+
[](https://pypi.org/project/groupme-mcp-server/)
|
|
37
|
+
[](https://pypi.org/project/groupme-mcp-server/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
[](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
|
+
[](https://github.com/oddrationale/groupme-mcp-server/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/oddrationale/groupme-mcp-server)
|
|
5
|
+
[](https://scorecard.dev/viewer/?uri=github.com/oddrationale/groupme-mcp-server)
|
|
6
|
+
[](https://pypi.org/project/groupme-mcp-server/)
|
|
7
|
+
[](https://pypi.org/project/groupme-mcp-server/)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](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()
|
|
File without changes
|
|
@@ -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()
|