GroupOffice MCP Server
An MCP (Model Context Protocol) server for GroupOffice groupware, built on FastMCP. It talks to GroupOffice's JMAP-style batch/RPC API and exposes Contacts, Calendars, Tasks, Notes, Projects, Comments, History, Users, Groups, and file attachments as MCP tools.
Defaults to read-only. Mutating tools (create/update/delete/upload) are
rejected before any API call unless you explicitly set GROUPOFFICE_READONLY=false.
Features
- Contacts & Address Books - search, read, create, update, delete
- Calendars & Events - search, read, create, update, delete
- Tasks & Task Lists - search, read, create, update, delete
- Notes - search, read, create, update, delete
- Projects (Projects v3 module, optional) - search, read, create, update, delete
- Comments - search, read, create, update, delete
- History (audit log, read-only) - search
- Users & Groups (read-only - administration is out of scope)
- File attachments - upload/download as GroupOffice blobs
- MCP resources for browsable lists (address books, calendars, task lists, supported-entity registry)
- MCP prompts encoding a "read before write" workflow (daily briefing, contact lookup before creating a duplicate, conflict check before scheduling an event)
- Every tool carries MCP annotations (
readOnlyHint/destructiveHint/idempotentHint/openWorldHint) so clients can reason about risk
Installation
pip install -e .
# or, once published:
pip install groupoffice-mcp-server
This project targets the fastmcp build packaged at
github.com/Vitexus/python3-fastmcp
(Debian python3-fastmcp). A generic PyPI fastmcp install may differ.
Configuration
Copy .env.example to .env and fill in your instance's details, or set these
environment variables directly:
| Variable | Required | Default | Description |
|---|---|---|---|
GROUPOFFICE_URL |
yes | - | Base URL of your GroupOffice instance, e.g. https://groupoffice.example.com |
GROUPOFFICE_API_TOKEN |
yes | - | Bearer token from System Settings -> API Keys (requires the "API key generator" community module) |
GROUPOFFICE_VERIFY_SSL |
no | true |
Verify TLS certificates |
GROUPOFFICE_TIMEOUT |
no | 30 |
HTTP request timeout, seconds |
GROUPOFFICE_MAX_RETRIES |
no | 3 |
Connection-level retries on transient network errors |
GROUPOFFICE_DEBUG |
no | false |
Enable debug logging |
GROUPOFFICE_READONLY |
no | true |
When true, all mutating tools are rejected. Set false to allow writes. |
There is deliberately no default for GROUPOFFICE_URL/GROUPOFFICE_API_TOKEN -
a bundled demo/default host would be a security footgun, so the server refuses
to start without them.
Getting an API token
- In GroupOffice, go to System Settings -> Modules, install the community "API key generator" module if it isn't installed yet.
- Go to System Settings -> API Keys -> Add key.
- Give it a name and pick the user it should act as, then save.
- Open the key's menu (⋮) -> View access token (or Copy token to clipboard) to get the bearer token.
Usage
Run directly:
groupoffice-mcp-server
Or add it to an MCP client (e.g. Claude Desktop) config:
{
"mcpServers": {
"groupoffice": {
"command": "groupoffice-mcp-server",
"env": {
"GROUPOFFICE_URL": "https://groupoffice.example.com",
"GROUPOFFICE_API_TOKEN": "your-api-token"
}
}
}
}
Tools
Every entity follows the same query_*/get_*/create_*/update_*/delete_*
shape. filter accepts a raw GroupOffice JMAP filter dict for anything beyond
the named convenience parameters.
| Entity | Tools | Notes |
|---|---|---|
| AddressBook | list_addressbooks |
read-only |
| Contact | query_contacts, get_contact, create_contact, update_contact, delete_contact |
addressbook_id filters by addressBookId |
| Calendar | list_calendars |
read-only |
| CalendarEvent | query_calendar_events, get_calendar_event, create_calendar_event, update_calendar_event, delete_calendar_event |
no end property - events use start + duration (ISO 8601, e.g. PT1H) |
| TaskList | list_tasklists |
read-only |
| Task | query_tasks, get_task, create_task, update_task, delete_task |
tasklist_id filters by tasklistId; completion is percentComplete (0-100), not a boolean |
| Note | query_notes, get_note, create_note, update_note, delete_note |
|
| Project3 | query_projects, get_project, create_project, update_project, delete_project |
optional module - errors on instances where it isn't installed |
| Comment | query_comments, get_comment, create_comment, update_comment, delete_comment |
filter by entity (friendly name, e.g. "Contact") + entity_id |
| LogEntry (History) | query_history |
read-only audit log; same entity/entity_id filter as Comment |
| User | query_users, get_user |
read-only - user administration is out of scope |
| Group | query_groups, get_group |
read-only - group administration is out of scope |
| Blob | upload_file, download_file |
upload returns a blob_id to attach via another entity's data |
Instance (multi-tenant administration) is deliberately not exposed - it is
high-privilege and out of scope for this server.
Resources
groupoffice://addressbooks,groupoffice://calendars,groupoffice://tasklists- browsable equivalents of the
list_*tools
- browsable equivalents of the
groupoffice://entities- static registry of entities/operations this server exposes (not introspected from/api/doc.php, which is per-instance HTML, not a stable machine-readable contract)
Prompts
daily_briefing(date=None)- today's calendar events + open taskscontact_lookup(query)- search before ever suggestingcreate_contact, to avoid duplicatesschedule_event_safely(title, start, end, calendar_id=None)- checks for conflicts before suggestingcreate_calendar_event
Security considerations
- Read-only by default.
GROUPOFFICE_READONLY=falseis required to allow any create/update/delete/upload call; this is enforced centrally by a FastMCP middleware hook (ReadOnlyGuardMiddleware) that runs before every tool call, keyed off each tool'sreadOnlyHintannotation - a new tool can't accidentally skip the gate. - The bearer token is passed via environment variable only; it is never logged or written to disk by this server.
User/Groupadministration and theInstance(multi-tenant admin) entity are not exposed by this server at all, regardless of read-only mode, since they carry a much larger blast radius than typical groupware data.
Known limitations
- Covers a curated subset of GroupOffice's 60+ entities, not the full object
model. Adding another entity is mechanical (new
@mcp.toolfunctions inserver.pycalling the existing genericclient.get/query/set) - no transport changes needed. - GroupOffice publishes no OpenAPI/Swagger spec; this server was built and
verified against a live instance's actual (undocumented in places) query
filter behavior. Filter/property names can vary by GroupOffice version -
check your instance's
/api/doc.phpif a named convenience filter (addressbook_id,calendar_id,tasklist_id,entity/entity_id) doesn't behave as expected; the rawfilterdict parameter always works as an escape hatch. download_filereturns base64-encoded content, which inflates size by ~33% - fine for small attachments, not recommended for large files.- Auth is bearer-token-only, matching GroupOffice's documented API; there is no OAuth2 flow (GroupOffice's own "OAuth2 Client" feature is for GroupOffice acting as a client to other services, not for authenticating third parties against GroupOffice itself).
- Debian packaging (matching
multiflexi-mcp-server'sdebian/layout) is a possible follow-up, not included in this initial version.
Development
pip install -e ".[dev]"
pytest tests/ -v
Tests run entirely offline against a mocked GroupOfficeClient (via
httpx.MockTransport for client-layer tests, and dependency injection for
tool-layer tests) - no live GroupOffice instance is required.
Manual live smoke test
Run the server directly to confirm it starts and connects:
export GROUPOFFICE_URL=https://your-instance.example.com
export GROUPOFFICE_API_TOKEN=your-token
python -m groupoffice_mcp_server.server
# or, once installed: groupoffice-mcp-server
To call individual tools against a live instance without a full MCP client,
use FastMCP's in-memory Client (the same pattern the test suite uses):
import asyncio
from fastmcp import Client
from groupoffice_mcp_server.config import GroupOfficeConfig
from groupoffice_mcp_server.server import create_server
async def main():
mcp = create_server(GroupOfficeConfig.from_env())
async with Client(mcp) as c:
result = await c.call_tool("query_contacts", {"limit": 5})
print(result.data)
# should be refused - read-only mode is on by default
try:
await c.call_tool("create_contact", {"data": {"firstName": "Test"}})
except Exception as e:
print("blocked as expected:", e)
asyncio.run(main())
Only run a mutating call (GROUPOFFICE_READONLY=false) against a
disposable/test instance, never production data.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file groupoffice_mcp_server-0.2.0.tar.gz.
File metadata
- Download URL: groupoffice_mcp_server-0.2.0.tar.gz
- Upload date:
- Size: 37.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.1.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d0dfc2b6a52eec0e5b9490cbd73abcc7e7878eaa5155ee31fa321263d68a06f
|
|
| MD5 |
987dff084777c797c1e7411c7d656a49
|
|
| BLAKE2b-256 |
24ebc0f190e24834153fe7fa94b3938eb94b1ceb3166236124fd79a2a15cb1f9
|
File details
Details for the file groupoffice_mcp_server-0.2.0-py3-none-any.whl.
File metadata
- Download URL: groupoffice_mcp_server-0.2.0-py3-none-any.whl
- Upload date:
- Size: 17.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.1.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f0f5fc5b2fdb71a4cf078e4263cc51598df77de30728e39befcc8ad43ea2d4d
|
|
| MD5 |
e65b52723911aecab8ec61645f6b7249
|
|
| BLAKE2b-256 |
043a6d8339391cb9116f200da1a962f00806b8e659da14d2d9a11914a11d378f
|