morgenmcp 0.2.1__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.
Files changed (50) hide show
  1. morgenmcp-0.2.1/.claude/skills/fastmcp/SKILL.md +88 -0
  2. morgenmcp-0.2.1/.claude/skills/fastmcp/reference/CHANGELOG.md +163 -0
  3. morgenmcp-0.2.1/.claude/skills/fastmcp/reference/middleware.md +85 -0
  4. morgenmcp-0.2.1/.claude/skills/fastmcp/reference/registration.md +81 -0
  5. morgenmcp-0.2.1/.claude/skills/fastmcp/reference/testing.md +42 -0
  6. morgenmcp-0.2.1/.github/dependabot.yml +32 -0
  7. morgenmcp-0.2.1/.github/workflows/ci.yml +43 -0
  8. morgenmcp-0.2.1/.github/workflows/release.yml +48 -0
  9. morgenmcp-0.2.1/.gitignore +65 -0
  10. morgenmcp-0.2.1/.gitmodules +8 -0
  11. morgenmcp-0.2.1/.pre-commit-config.yaml +11 -0
  12. morgenmcp-0.2.1/.python-version +1 -0
  13. morgenmcp-0.2.1/AGENTS.md +68 -0
  14. morgenmcp-0.2.1/AI_POLICY.md +102 -0
  15. morgenmcp-0.2.1/CLAUDE.md +196 -0
  16. morgenmcp-0.2.1/LICENSE +202 -0
  17. morgenmcp-0.2.1/NOTICE +14 -0
  18. morgenmcp-0.2.1/PKG-INFO +230 -0
  19. morgenmcp-0.2.1/README.md +192 -0
  20. morgenmcp-0.2.1/mise.toml +7 -0
  21. morgenmcp-0.2.1/morgenmcp/__init__.py +12 -0
  22. morgenmcp-0.2.1/morgenmcp/_version.py +24 -0
  23. morgenmcp-0.2.1/morgenmcp/client.py +676 -0
  24. morgenmcp-0.2.1/morgenmcp/models.py +512 -0
  25. morgenmcp-0.2.1/morgenmcp/resources.py +308 -0
  26. morgenmcp-0.2.1/morgenmcp/server.py +744 -0
  27. morgenmcp-0.2.1/morgenmcp/tools/__init__.py +26 -0
  28. morgenmcp-0.2.1/morgenmcp/tools/accounts.py +42 -0
  29. morgenmcp-0.2.1/morgenmcp/tools/calendars.py +136 -0
  30. morgenmcp-0.2.1/morgenmcp/tools/events.py +775 -0
  31. morgenmcp-0.2.1/morgenmcp/tools/id_registry.py +242 -0
  32. morgenmcp-0.2.1/morgenmcp/tools/id_utils.py +64 -0
  33. morgenmcp-0.2.1/morgenmcp/tools/outputs.py +339 -0
  34. morgenmcp-0.2.1/morgenmcp/tools/tags.py +190 -0
  35. morgenmcp-0.2.1/morgenmcp/tools/tasks.py +597 -0
  36. morgenmcp-0.2.1/morgenmcp/tools/utils.py +209 -0
  37. morgenmcp-0.2.1/morgenmcp/validators.py +350 -0
  38. morgenmcp-0.2.1/pyproject.toml +110 -0
  39. morgenmcp-0.2.1/tests/__init__.py +1 -0
  40. morgenmcp-0.2.1/tests/conftest.py +20 -0
  41. morgenmcp-0.2.1/tests/test_client.py +918 -0
  42. morgenmcp-0.2.1/tests/test_id_persistence.py +232 -0
  43. morgenmcp-0.2.1/tests/test_integration.py +112 -0
  44. morgenmcp-0.2.1/tests/test_mcp_server.py +877 -0
  45. morgenmcp-0.2.1/tests/test_models.py +435 -0
  46. morgenmcp-0.2.1/tests/test_resources.py +382 -0
  47. morgenmcp-0.2.1/tests/test_tasks_tags.py +574 -0
  48. morgenmcp-0.2.1/tests/test_tools.py +1607 -0
  49. morgenmcp-0.2.1/tests/test_validators.py +405 -0
  50. morgenmcp-0.2.1/uv.lock +1495 -0
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: fastmcp
3
+ description: Up-to-date reference for FastMCP (Python MCP server framework) as used in MorgenMCP — middleware authoring, tool/resource/prompt registration, ToolError conventions, in-memory Client testing, transports. Use when modifying morgenmcp/server.py, tools/, resources.py, or writing/debugging tests against fastmcp.Client. Prefer this over memory: verify against docs/fastmcp/docs/ (pinned submodule) before relying on any API detail — FastMCP ships breaking changes across 3.x minors.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # FastMCP reference (MorgenMCP)
8
+
9
+ **Pinned version: 3.4.3** (`pyproject.toml`: `fastmcp>=3.4,<3.5`). Local docs submodule
10
+ `docs/fastmcp/` is checked out at tag `v3.4.3` (`1eedd1f6`) — matches the installed package
11
+ exactly. **Always check `docs/fastmcp/docs/` before trusting a remembered API detail** —
12
+ online docs and this skill's own prose can describe a different version.
13
+
14
+ > Facts here were verified against the pinned submodule and cross-checked with a deep-research
15
+ > pass (2026-07-07). Re-verify before relying on a signature if the submodule tag has moved —
16
+ > see reference/CHANGELOG.md.
17
+
18
+ ## Where MorgenMCP deviates from FastMCP defaults (know these first)
19
+
20
+ - **Call-expression registration, not decorators.** `server.py` uses `mcp.tool(name=...)(func)` /
21
+ `mcp.resource(uri, ...)(func)` instead of `@mcp.tool`. This is a documented-valid alternative
22
+ (see reference/registration.md) chosen so tool/resource functions stay plain, independently
23
+ importable async functions for unit testing.
24
+ - **`ResponseCachingMiddleware` is a FastMCP-provided class**, imported directly from
25
+ `fastmcp.server.middleware.caching` (not subclassed). MorgenMCP's contribution is only the
26
+ *configuration* — `CallToolSettings`/`ReadResourceSettings` (60s TTL, which tools/resources are
27
+ cached) — so a FastMCP upgrade touching this module directly affects this repo's cache behavior.
28
+ - **stdio transport only**, no HTTP/auth surface. FastMCP auth (OAuth Proxy, MultiAuth) is
29
+ HTTP-only by design — under stdio it is silently skipped, not rejected (`run_stdio_async` never
30
+ reads `self.auth`; the server runs with `skip_auth=True`). So auth is simply inert here, and
31
+ auth-hardening changelog entries can be skipped when assessing upgrade risk for this repo.
32
+
33
+ ## Quick recipes
34
+
35
+ **Middleware** (subclass `fastmcp.server.middleware.Middleware`, register via `mcp.add_middleware`):
36
+ ```python
37
+ from fastmcp.server.middleware import Middleware, MiddlewareContext
38
+
39
+ class LoggingMiddleware(Middleware):
40
+ async def on_message(self, context: MiddlewareContext, call_next):
41
+ return await call_next(context)
42
+
43
+ mcp.add_middleware(LoggingMiddleware()) # order = execution order, first added = outermost
44
+ ```
45
+ Deny by **raising**, not returning falsy: `ToolError` / `ResourceError` / `PromptError` / `McpError`.
46
+ `on_initialize` must reject *before* calling `call_next()` — raising after only logs.
47
+
48
+ **Registration** (call-expression form, as used in this repo):
49
+ ```python
50
+ mcp.tool(name="morgen_list_events", description=...)(list_events)
51
+ mcp.resource("morgen://calendar/{calendar_id}/events")(get_calendar_events)
52
+ ```
53
+
54
+ **Testing** (in-memory, no transport — pass the server instance directly):
55
+ ```python
56
+ from fastmcp import Client
57
+
58
+ async def test_x():
59
+ async with Client(mcp) as client:
60
+ result = await client.call_tool("morgen_list_events", {...})
61
+ assert result.data == {...}
62
+ ```
63
+ Don't open `Client` inside a pytest fixture (event-loop issues) — only build/return the server there.
64
+
65
+ **Errors**: `ToolError` messages are *always* sent to the client, regardless of `mask_error_details`.
66
+ Generic exceptions get masked to a generic string when `mask_error_details=True`. This is why
67
+ `utils.py`'s `@handle_tool_errors` converts everything to `ToolError`.
68
+
69
+ **Transport** (this repo only uses the first):
70
+ ```python
71
+ mcp.run() # stdio (default) — what MorgenMCP uses
72
+ mcp.run(transport="http", host="127.0.0.1", port=8000) # Streamable HTTP → http://host:port/mcp
73
+ ```
74
+
75
+ ## Topic reference (load as needed)
76
+
77
+ - Middleware hook signatures, `MiddlewareContext` fields, deny-by-raising details → [reference/middleware.md](reference/middleware.md)
78
+ - Tool/resource/prompt registration forms, primitive-result auto-wrapping, RFC 6570 URI templates → [reference/registration.md](reference/registration.md)
79
+ - Testing patterns (in-memory Client, `run_server_async` vs `run_server_in_process`) → [reference/testing.md](reference/testing.md)
80
+ - Per-version changelog (3.0.0 → 3.4.3), what's a breaking change, security-hardening timeline → [reference/CHANGELOG.md](reference/CHANGELOG.md)
81
+
82
+ ## Why FastMCP here (context)
83
+
84
+ MorgenMCP wraps the Morgen calendar API as an MCP server. It runs stdio-only with no auth
85
+ provider, registers 22 tools, and layers FastMCP's `ResponseCachingMiddleware` (60s TTL,
86
+ configured not subclassed) plus a virtual-ID system on top of FastMCP's tool/resource primitives.
87
+ See the project's own `CLAUDE.md` for the full architecture — this skill only covers the FastMCP
88
+ framework layer.
@@ -0,0 +1,163 @@
1
+ # FastMCP version history (3.0.0 → 3.4.3)
2
+
3
+ Compiled 2026-07-07 from `docs/fastmcp/docs/updates.mdx` (official changelog) plus direct git-log
4
+ inspection of the `docs/fastmcp` submodule, then re-audited 2026-07-07 against the submodule docs,
5
+ the installed `.venv` package source, and authoritative external sources (NVD/GHSA/PyPI/the
6
+ PrefectHQ/fastmcp repo). Every version entry, code-name, date, CVE/GHSA ID, and the `257fe325`
7
+ caching commit is confirmed; the re-audit corrected six items (see git history of this file).
8
+ Version numbers are as precise as sources allow — flag anything you verify further so this file
9
+ stays current (see "How to refresh" below).
10
+
11
+ ## 3.0.0
12
+
13
+ - **`ResourceResult`/`ResourceContent`** established as the canonical resource return type (list
14
+ of per-item content, individual MIME types/metadata).
15
+ - **`version` kwarg** added to `@mcp.resource`/`@mcp.tool`/`@mcp.prompt`; the selected version is
16
+ surfaced through `meta.fastmcp.version`/`meta.fastmcp.versions` (and clients can request one via
17
+ `_meta.fastmcp.version`) — it rides the `_meta` channel, not a field separate from it.
18
+ - **RFC 6570 URI template query params** (`{?param1,param2}`) — note: `updates.mdx` attributes
19
+ these to **2.13.0** (Oct 2025), not 3.0.0. The `{param}` / `{param*}` (wildcard) / `{?...}`
20
+ (query) forms are all valid in 3.x; see reference/registration.md for the working syntax.
21
+ - **OAuth Proxy gains CIMD** (client ID metadata document) support.
22
+ - Component visibility controls added.
23
+
24
+ ## 3.0.2
25
+
26
+ - Fix: MCP transport auth headers no longer leak through to downstream OpenAPI APIs (issue #3260,
27
+ fixed by PR #3262).
28
+ - Fix: background task workers correctly receive the originating request ID.
29
+
30
+ ## 3.1.0 — "Code to Joy" (2026-03-03)
31
+
32
+ - **Code Mode Transform**: instead of exposing the raw tool catalog, the LLM gets meta-tools —
33
+ search for relevant tools via BM25, inspect schemas, then write Python chaining `call_tool()`
34
+ calls inside a sandbox. Reduces context spent on tool schemas for large tool catalogs.
35
+ - **`MultiAuth`**: composes multiple token-verification sources into one auth layer. Tries a
36
+ designated `server` (an OAuth Proxy, which owns all OAuth routes/metadata) first, then
37
+ `verifiers` in list order (contributing only token-verification logic); 401 if all fail.
38
+ - PropelAuth provider support.
39
+ - Google GenAI sampling handler.
40
+
41
+ ## 3.1.1
42
+
43
+ - Pin `pydantic-monty<0.0.8` — hotfix for a breaking change in Monty affecting Code Mode.
44
+
45
+ ## 3.2.0 — "Show Don't Tool"
46
+
47
+ - **The Apps release**: tools can return interactive UIs (charts, dashboards, forms, maps) via
48
+ `FastMCPApp` — `@app.ui()` for LLM-visible tools, `@app.tool()` for backend tools the UI calls.
49
+ - **Security hardening pass**, tied to GHSA-vv7q-7jx5-f767 (OpenAPI provider SSRF/path
50
+ traversal): SSRF/path-traversal prevention (URL-encoding path params, restricting `$ref`
51
+ resolution to local refs), JWT algorithm restriction (blocks `HS*` when JWKS is configured),
52
+ OAuth scope enforcement (prefers IdP-granted scopes), CSRF double-submit cookie validation,
53
+ refresh-token misuse prevention (rejects refresh tokens used as Bearer tokens).
54
+ - **Breaking change** ⚠️: `FastMCPApp` tool calls now route through `___`-prefixed compound tool
55
+ names internally (PR #3667), replacing a prior `_meta`-injection approach that broke in real MCP
56
+ hosts (Goose, MCP Jam) that don't forward `_meta` on `callServerTool`. **Superseded two patches
57
+ later**: 3.2.4 (#3824) replaced the `___` scheme with hash-based backend tool routing — so the
58
+ literal `AppName___tool_name` format is a 3.2.0–3.2.3-only artifact, not the current mechanism.
59
+ - Dev server: `fastmcp dev apps` previews app tools in-browser with an MCP message inspector.
60
+
61
+ ## 3.2.1 – 3.2.4 (patch line)
62
+
63
+ - 3.2.1: auth-provider audience validation fixes (Cognito `client_id`, Azure `identifier_uri`),
64
+ consent-cookie LRU cap, OpenAPI 3.0 `nullable` field leak fix.
65
+ - 3.2.2: fixes the Azure audience regression from 3.2.1.
66
+ - 3.2.3: pins `fakeredis<2.35.0` (2.35.0 rename broke pydocket's `memory://` backend).
67
+ - 3.2.4: background tasks scoped to authorization context instead of MCP session (**breaking**
68
+ for session-scoped semantics), docstring-derived parameter descriptions, `FileUpload`
69
+ decoded-base64-size validation, proxy stops forwarding inbound headers to unrelated servers,
70
+ Keycloak OAuth provider added, and the 3.2.0 `___`-prefixed app-tool routing replaced with
71
+ hash-based backend routing (#3824) — also breaking for anyone who hard-coded the `___` names.
72
+
73
+ ## 3.3.0 — "Slim Reaper"
74
+
75
+ - **`fastmcp-slim`**: dependency-light distribution shipping client + transport layer without
76
+ Starlette/Uvicorn/server stack — same import namespace, lighter footprint for CI/agents/library
77
+ dependents.
78
+ - OAuth proxy hardening: silent-consent AS-in-the-middle guard, dot-segment redirect rejection,
79
+ per-token response cache partitioning.
80
+ - `AzureB2CProvider` user flows, public `update_scopes()` on `OAuthProxy`.
81
+ - `@mcp.tool(run_in_thread=False)` for thread-affine tools.
82
+
83
+ ## 3.3.1 — "Loop There It Is"
84
+
85
+ - Hotfix for the 3.3 packaging split: standalone component imports (`from fastmcp.tools import
86
+ tool`) no longer pull in the server stack or trip a circular import.
87
+ - **This is the version MorgenMCP shipped on until 2026-07-07.**
88
+
89
+ ## 3.4.0 — "Remote Control"
90
+
91
+ - **`fastmcp-remote`**: standalone stdio↔HTTP bridge package — `uvx fastmcp-remote
92
+ https://example.com/mcp` connects stdio-only hosts to HTTP-hosted servers, OAuth auto-enabled
93
+ for HTTPS.
94
+ - Proxies now forward `initialize` upstream — a missing/misconfigured backend fails the handshake
95
+ loudly instead of returning an empty-but-connected proxy.
96
+ - `fastmcp_access_token_expiry_seconds` decouples client-facing token lifetime from short
97
+ upstream `expires_in`.
98
+ - `ToolResult(..., is_error=True)` returns rich errors the model can act on instead of only
99
+ raising.
100
+
101
+ ## 3.4.1 — "Floor It"
102
+
103
+ - Security patch: floors `starlette>=1.0.1`, closing CVE-2026-48710 (previously only constrained
104
+ transitively via `mcp`).
105
+ - OAuthProxy logs refresh-token cache misses instead of failing silently.
106
+
107
+ ## 3.4.2 — "Heads Up"
108
+
109
+ - Compatibility patch: `JWTVerifier` now accepts JWTs with private, non-critical JWS header
110
+ parameters (e.g. Clerk's `cat`) instead of rejecting before signature/claim validation.
111
+
112
+ ## 3.4.3 — "The Fast and the Secure-ious" (2026-07-05)
113
+
114
+ - SSRF allow-list hardening: every IPv6 transition form (NAT64, 6to4, Teredo, ISATAP) now
115
+ unwraps to its embedded IPv4 target and is checked against the same allow-list policy.
116
+ - DNS rebinding protection: Streamable HTTP validates `Host` and browser `Origin` before session
117
+ handling.
118
+ - Stricter OAuth redirects: unsafe schemes rejected before registration, DCR clients bound to
119
+ their registered redirect URIs.
120
+ - Reliability: proxy session-teardown races, discriminator-tag handling in JSON schema
121
+ conversion, several middleware/resource-template fixes.
122
+ - **This is the version MorgenMCP upgraded to on 2026-07-07** (`fastmcp>=3.4,<3.5`).
123
+
124
+ ## Relevant to a specific bug hunt (caching middleware)
125
+
126
+ Between 3.3.1 and 3.4.3, commit `257fe325` ("fix: caching middleware TypeError on cache miss due
127
+ to mismatched call_next parameter", #4301) patched a positional/keyword mismatch in the
128
+ *vendored `fastmcp_slim` docs-submodule snapshot's* middleware chain composition
129
+ (`fastmcp_slim/fastmcp/server/server.py`). **This did not reproduce against the actually
130
+ installed 3.3.1 package** — the installed chain composition (`partial(mw, call_next=chain)`) is
131
+ shaped differently and was verified empirically (via `Client(mcp).list_tools()`) to work fine on
132
+ both 3.3.1 and 3.4.3. Lesson: the `docs/fastmcp` submodule's *git history* can contain commits
133
+ that don't map cleanly onto the installed PyPI package's actual code layout — always verify
134
+ against the installed package (`.venv/lib/.../site-packages/fastmcp/`), not just submodule diffs,
135
+ before concluding a changelog entry affects this repo.
136
+
137
+ **Second lesson from the same investigation**: an earlier version of this file, and of SKILL.md /
138
+ reference/middleware.md, claimed `ResponseCachingMiddleware` was hand-rolled by MorgenMCP rather
139
+ than FastMCP-provided — sourced from a deep-research pass whose adversarial verifiers refuted the
140
+ true claim 0-3, apparently because they only checked public docs pages and never inspected the
141
+ actually-installed package. That was wrong (see reference/middleware.md for the corrected facts).
142
+ Always cross-check "framework doesn't have X" claims directly against
143
+ `.venv/lib/.../site-packages/fastmcp/` before writing them into a skill as fact — a web-search-based
144
+ research pass can produce confident false negatives about implementation details that aren't
145
+ documented in public docs but are plainly present in the source.
146
+
147
+ ## Not FastMCP framework features (commonly conflated)
148
+
149
+ - **No `fastmcp.ValidationError`** documented for tool/resource argument validation as of 3.4.3 —
150
+ validation failures surface via Pydantic coercion, not a dedicated FastMCP exception type.
151
+ - **Pydantic `by_alias`/`exclude_none` serialization** is a MorgenMCP project convention, not a
152
+ documented FastMCP requirement. The `model_dump(by_alias=True, exclude_none=True)` calls live in
153
+ `client.py` (request bodies); `models.py` supplies the field aliases and `validate_by_name`/
154
+ `validate_by_alias` config that make that round-trip work.
155
+
156
+ ## How to refresh this file
157
+
158
+ 1. `cd docs/fastmcp && git fetch --tags origin && git tag -l "v3.*"` — check for new tags.
159
+ 2. `git log --oneline <old-tag>..<new-tag>` — skim for security/breaking-change commits.
160
+ 3. `git show <new-tag>:docs/updates.mdx` — read the official per-release summary.
161
+ 4. Update `SKILL.md`'s "Pinned version" line and this file's changelog table.
162
+ 5. Empirically re-verify any claim about MorgenMCP's own code (like the caching-middleware note
163
+ above) against the *installed* package, not just the docs submodule — they can diverge.
@@ -0,0 +1,85 @@
1
+ # Middleware
2
+
3
+ Source: `docs/fastmcp/docs/servers/middleware.mdx` (pinned v3.4.3).
4
+
5
+ ## Authoring
6
+
7
+ Subclass `fastmcp.server.middleware.Middleware`, override the hooks you need:
8
+
9
+ ```python
10
+ from fastmcp.server.middleware import Middleware, MiddlewareContext
11
+
12
+ class LoggingMiddleware(Middleware):
13
+ async def on_message(self, context: MiddlewareContext, call_next):
14
+ result = await call_next(context)
15
+ return result
16
+ ```
17
+
18
+ Register on a server instance:
19
+
20
+ ```python
21
+ mcp.add_middleware(LoggingMiddleware())
22
+ ```
23
+
24
+ `add_middleware` (`fastmcp_slim/fastmcp/server/server.py:527`):
25
+ ```python
26
+ def add_middleware(self, middleware: Middleware) -> None:
27
+ self.middleware.append(middleware)
28
+ ```
29
+ Registration order = execution order for the request leg; **reversed** for the response leg
30
+ (first-added middleware is outermost — sees the request first, the response last). This repo's
31
+ `ResponseCachingMiddleware` registration order in `server.py` therefore determines whether other
32
+ middleware sees pre- or post-cache traffic.
33
+
34
+ ## Hook signatures
35
+
36
+ All hooks share the same shape:
37
+ ```python
38
+ async def hook(self, context: MiddlewareContext, call_next) -> result_type
39
+ ```
40
+
41
+ Most general → most specific:
42
+ - `on_message` — every message, requests + notifications
43
+ - `on_request` / `on_notification`
44
+ - `on_call_tool`, `on_read_resource`, `on_get_prompt`
45
+ - `on_list_tools`, `on_list_resources`, `on_list_resource_templates`, `on_list_prompts`
46
+ - `on_initialize`
47
+
48
+ Override `__call__` directly instead of the named hooks for raw/uniform handling of every message
49
+ type without dispatch overhead.
50
+
51
+ `MiddlewareContext` fields: `method`, `source`, `type`, `message`, `timestamp`, `fastmcp_context`.
52
+ - Tool/prompt calls: `context.message.name`, `context.message.arguments`
53
+ - Resource reads: `context.message.uri`
54
+
55
+ `on_list_tools` returns `list[Tool]` — filter/mutate the list before returning to hide tools from
56
+ specific clients.
57
+
58
+ ## Denying requests
59
+
60
+ Raise, don't return falsy:
61
+ - `ToolError` — tool calls
62
+ - `ResourceError` — resource reads
63
+ - `PromptError` — prompt gets
64
+ - `McpError` — general/protocol-level
65
+
66
+ **`on_initialize` must reject *before* calling `call_next()`.** Raising after `call_next()`
67
+ returns only logs the error server-side — it does not reach the client, because the initialize
68
+ response has already been dispatched.
69
+
70
+ ## Caching middleware — is a framework feature
71
+
72
+ FastMCP ships a built-in response-caching middleware:
73
+ `fastmcp.server.middleware.caching.ResponseCachingMiddleware`, confirmed directly against the
74
+ installed package (`.venv/lib/python3.14/site-packages/fastmcp/server/middleware/caching.py`,
75
+ 593 lines). It comes with per-method settings classes — `CallToolSettings`,
76
+ `ReadResourceSettings`, `ListToolsSettings`, `ListResourcesSettings`, `ListPromptsSettings`,
77
+ `GetPromptSettings` (all `TypedDict`s controlling TTL/enable per operation) — plus
78
+ `ResponseCachingStatistics` and cache-key partitioning by auth token.
79
+
80
+ `morgenmcp/server.py` imports and configures this class directly (not a subclass): 60s TTL,
81
+ an explicit allowlist of cacheable read-only tools plus all resources. Upgrading FastMCP *can*
82
+ change this repo's caching behavior if a release touches `fastmcp.server.middleware.caching` —
83
+ check that module's diff specifically, not just the general `Middleware` base class, when
84
+ assessing upgrade risk (see reference/CHANGELOG.md for the version-by-version history and a
85
+ past false-negative on this exact claim).
@@ -0,0 +1,81 @@
1
+ # Tool / resource / prompt registration
2
+
3
+ Sources: `docs/fastmcp/docs/servers/tools.mdx`, `docs/fastmcp/docs/servers/resources.mdx`.
4
+
5
+ ## Registration forms
6
+
7
+ Bare decorator:
8
+ ```python
9
+ @mcp.tool
10
+ def my_tool(x: int) -> int: ...
11
+
12
+ @mcp.resource("users://email/{email}")
13
+ def lookup_user(email: str) -> dict: ...
14
+ ```
15
+
16
+ With options:
17
+ ```python
18
+ @mcp.tool(name=..., description=..., tags={...}, meta={...}, timeout=float, output_schema={...})
19
+ ```
20
+
21
+ **Call-expression form** (what MorgenMCP uses in `server.py`, so functions stay plain and
22
+ independently testable):
23
+ ```python
24
+ mcp.resource("users://email/{email}")(lookup_user)
25
+ mcp.tool(name="morgen_list_events")(list_events)
26
+ ```
27
+ This also lets one function register under multiple URIs/names by calling it more than once.
28
+
29
+ For bound methods, use `fastmcp.tools.tool()` standalone then `mcp.add_tool(bound_method)` —
30
+ the decorator form doesn't work directly on instance/class methods.
31
+
32
+ ## Primitive-result auto-wrapping
33
+
34
+ MCP structured-output schemas require an object root. FastMCP handles this automatically based
35
+ on the function's return annotation:
36
+ - Annotated primitive (e.g. `-> int`) → wrapped into `structuredContent: {"result": 8}`, with
37
+ `"x-fastmcp-wrap-result": true` on the schema. Clients auto-unwrap this into `.data`.
38
+ - No return annotation → no `structuredContent` at all, only `content`.
39
+ - `dict` / dataclass / Pydantic model → always becomes structured content directly, no wrapping,
40
+ even without an explicit `output_schema`.
41
+ - Full manual control: return `ToolResult(content=..., structured_content=..., meta=...)` —
42
+ importable as `from fastmcp.tools import ToolResult` (or `fastmcp.tools.tool`, a runtime
43
+ `sys.modules` alias; the actual class lives in `fastmcp/tools/base.py`, not a `tool.py` file).
44
+
45
+ MorgenMCP's tools return `{"success": True, ...}` dicts (per `CLAUDE.md`), so they land in the
46
+ "dict → structured content directly" case — no wrapping envelope to worry about.
47
+
48
+ ## Error handling
49
+
50
+ - `ToolError` (`fastmcp.exceptions`) messages are **always** sent to the client verbatim,
51
+ regardless of the `mask_error_details` setting on `FastMCP(...)`.
52
+ - Generic exceptions (`TypeError`, `ValueError`, etc.) get masked to a generic string when
53
+ `mask_error_details=True`.
54
+ - No `fastmcp.ValidationError` is documented for tool/resource handlers in these files —
55
+ argument validation failures come from Pydantic coercion. Coercion strictness is controlled by
56
+ `strict_input_validation` (default: flexible coercion; `FastMCP(strict_input_validation=True)`
57
+ enforces strict JSON-Schema validation instead).
58
+ - This is why `morgenmcp/tools/utils.py`'s `@handle_tool_errors` converts `ValidationError`,
59
+ `MorgenAPIError`, and unexpected exceptions to `ToolError` — it's the only exception type
60
+ guaranteed to reach the client with a useful message.
61
+
62
+ ## RFC 6570 URI templates (resources)
63
+
64
+ `docs/fastmcp/docs/servers/resources.mdx` §"RFC 6570 URI Templates":
65
+ - `{param}` — exactly one path segment, does not cross `/`.
66
+ - `{param*}` — wildcard, captures multiple segments up to the next literal/param:
67
+ `@mcp.resource("path://{filepath*}")`.
68
+ - `{?param1,param2}` — form-style query params (v2.13.0+). Must map to *optional* (defaulted)
69
+ function params; path params must map to *required* params:
70
+ ```python
71
+ @mcp.resource("data://{id}{?format}")
72
+ def get_data(id: str, format: str = "json"): ...
73
+ ```
74
+ Values are auto-coerced to `int`/`float`/`bool`/`str` per type hint.
75
+
76
+ ## Return type: `ResourceResult`
77
+
78
+ Introduced v3.0.0. `ResourceResult(contents=str|bytes|list[ResourceContent], meta=dict|None)`;
79
+ `ResourceContent(content=Any, mime_type=str|None, meta=dict|None)`. Plain `str` becomes
80
+ `TextResourceContents`, `bytes` becomes `BlobResourceContents` (base64-encoded). Simple resources
81
+ can just return `str`/`bytes` directly without constructing `ResourceResult`.
@@ -0,0 +1,42 @@
1
+ # Testing patterns
2
+
3
+ Source: `docs/fastmcp/docs/development/tests.mdx`.
4
+
5
+ ## In-memory Client testing (the pattern to use)
6
+
7
+ Pass the `FastMCP` server instance directly to `Client` — this runs the real MCP protocol in the
8
+ same process, no network transport, no subprocess:
9
+
10
+ ```python
11
+ from fastmcp import FastMCP, Client
12
+
13
+ server = FastMCP("WeatherServer")
14
+
15
+ @server.tool
16
+ def get_temperature(city: str) -> dict:
17
+ return {"city": city, "temp": temps.get(city, 70)}
18
+
19
+ async def test_weather_operations():
20
+ async with Client(server) as client:
21
+ result = await client.call_tool("get_temperature", {"city": "NYC"})
22
+ assert result.data == {"city": "NYC", "temp": 72}
23
+ ```
24
+
25
+ **Don't open `Client` inside a pytest fixture** — event-loop lifecycle issues. Only build/return
26
+ the server instance from fixtures; open the `Client` context manager inside the test function
27
+ itself.
28
+
29
+ This is exactly the pattern MorgenMCP's `test_mcp_server.py` uses: `fastmcp.Client(mcp)` against
30
+ the real server object, with `MORGENMCP_DATA_DIR=tmp_path` to isolate the persistent ID store.
31
+
32
+ ## Real network transport (rarely needed)
33
+
34
+ Two options, in order of preference:
35
+ - `fastmcp.utilities.tests.run_server_async(server) as url` — in-process, async context manager.
36
+ Prefer this when you actually need a URL (e.g. testing HTTP-specific behavior).
37
+ - `run_server_in_process` — spawns a real subprocess. Only use when true process isolation is
38
+ required (e.g. testing stdio transport specifically, or crash isolation).
39
+
40
+ MorgenMCP doesn't currently need either — all tests go through the in-memory `Client` pattern or
41
+ mock the HTTP client directly (`test_client.py` via `@respx.mock`, `test_tools.py` via
42
+ `patch("morgenmcp.tools.*.get_client")`).
@@ -0,0 +1,32 @@
1
+ # Low-maintenance Dependabot config: monthly, grouped updates so a quiet month
2
+ # yields one PR per ecosystem. Submodules and pre-commit hooks are left out on
3
+ # purpose (submodules are version-pinned; stale hooks are harmless).
4
+ version: 2
5
+
6
+ updates:
7
+ - package-ecosystem: uv
8
+ directory: /
9
+ schedule:
10
+ interval: monthly
11
+ open-pull-requests-limit: 3
12
+ commit-message:
13
+ prefix: "chore(deps)"
14
+ groups:
15
+ python-minor-patch:
16
+ patterns: ["*"]
17
+ update-types: ["minor", "patch"]
18
+ ignore:
19
+ # fastmcp is submodule-pinned; only patch bumps are wanted here.
20
+ - dependency-name: fastmcp
21
+ update-types: ["version-update:semver-major", "version-update:semver-minor"]
22
+
23
+ - package-ecosystem: github-actions
24
+ directory: /
25
+ schedule:
26
+ interval: monthly
27
+ open-pull-requests-limit: 3
28
+ commit-message:
29
+ prefix: "chore(ci)"
30
+ groups:
31
+ actions:
32
+ patterns: ["*"]
@@ -0,0 +1,43 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ matrix:
14
+ os: [ubuntu-latest, macos-latest, windows-latest]
15
+ python-version: ["3.14"]
16
+ include:
17
+ # Also test requires-python's floor, so newer syntax fails here.
18
+ - os: ubuntu-latest
19
+ python-version: "3.12"
20
+
21
+ steps:
22
+ - uses: actions/checkout@v7
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v7
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+ enable-cache: true
29
+
30
+ - name: Install dependencies
31
+ run: uv sync --all-extras
32
+
33
+ - name: Lint with ruff
34
+ run: uv run ruff check .
35
+
36
+ - name: Check formatting with ruff
37
+ run: uv run ruff format --check .
38
+
39
+ - name: Type check with pyright
40
+ run: uv run pyright
41
+
42
+ - name: Run tests
43
+ run: uv run pytest
@@ -0,0 +1,48 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v7
13
+
14
+ - name: Install uv
15
+ uses: astral-sh/setup-uv@v7
16
+
17
+ - name: Install dependencies
18
+ run: uv sync --all-extras
19
+
20
+ - name: Run tests
21
+ run: uv run pytest
22
+
23
+ release:
24
+ needs: test
25
+ runs-on: ubuntu-latest
26
+ environment: pypi
27
+ permissions:
28
+ contents: write
29
+ id-token: write
30
+
31
+ steps:
32
+ - uses: actions/checkout@v7
33
+ with:
34
+ fetch-depth: 0
35
+
36
+ - name: Install uv
37
+ uses: astral-sh/setup-uv@v7
38
+
39
+ - name: Build sdist and wheel
40
+ run: uv build
41
+
42
+ - name: Create GitHub Release
43
+ env:
44
+ GH_TOKEN: ${{ github.token }}
45
+ run: gh release create ${{ github.ref_name }} dist/* --generate-notes --title "${{ github.ref_name }}"
46
+
47
+ - name: Publish to PyPI
48
+ run: uv publish
@@ -0,0 +1,65 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ .venv/
25
+ venv/
26
+ ENV/
27
+
28
+ # PyCharm / JetBrains IDEs
29
+ .idea/
30
+
31
+ # Junie (JetBrains AI assistant)
32
+ .junie/
33
+
34
+ # VS Code
35
+ .vscode/
36
+
37
+ # Editor swap files
38
+ *.swp
39
+ *.swo
40
+ *~
41
+
42
+ # Testing
43
+ .pytest_cache/
44
+ .coverage
45
+ htmlcov/
46
+ .tox/
47
+ .nox/
48
+
49
+ # Local settings and secrets — track shared config, ignore the rest
50
+ .claude/*
51
+ !.claude/settings.json
52
+ !.claude/hooks
53
+ !.claude/skills
54
+ .env
55
+ .env.local
56
+ *.env
57
+ .envrc
58
+ mise.local.toml
59
+
60
+ # OS files
61
+ .DS_Store
62
+ Thumbs.db
63
+
64
+ # Generated at build time by hatch-vcs (see [tool.hatch.build.hooks.vcs])
65
+ morgenmcp/_version.py
@@ -0,0 +1,8 @@
1
+ [submodule "docs/morgen-dev-docs"]
2
+ path = docs/morgen-dev-docs
3
+ url = https://github.com/morgen-so/morgen-dev-docs.git
4
+
5
+ [submodule "docs/fastmcp"]
6
+ path = docs/fastmcp
7
+ url = https://github.com/jlowin/fastmcp.git
8
+ shallow = true
@@ -0,0 +1,11 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.16.6
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+ - repo: https://github.com/RobertCraigie/pyright-python
9
+ rev: v1.1.408
10
+ hooks:
11
+ - id: pyright
@@ -0,0 +1 @@
1
+ 3.12