fast-gateway 0.0.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 (29) hide show
  1. fast_gateway-0.0.1/.gitignore +30 -0
  2. fast_gateway-0.0.1/LICENSE +21 -0
  3. fast_gateway-0.0.1/PKG-INFO +381 -0
  4. fast_gateway-0.0.1/README.md +350 -0
  5. fast_gateway-0.0.1/pyproject.toml +159 -0
  6. fast_gateway-0.0.1/src/fast_mcp_gateway/__init__.py +55 -0
  7. fast_gateway-0.0.1/src/fast_mcp_gateway/access.py +147 -0
  8. fast_gateway-0.0.1/src/fast_mcp_gateway/api/__init__.py +6 -0
  9. fast_gateway-0.0.1/src/fast_mcp_gateway/api/groups.py +74 -0
  10. fast_gateway-0.0.1/src/fast_mcp_gateway/api/servers.py +92 -0
  11. fast_gateway-0.0.1/src/fast_mcp_gateway/app.py +178 -0
  12. fast_gateway-0.0.1/src/fast_mcp_gateway/builder.py +82 -0
  13. fast_gateway-0.0.1/src/fast_mcp_gateway/catalog.py +92 -0
  14. fast_gateway-0.0.1/src/fast_mcp_gateway/connect.py +66 -0
  15. fast_gateway-0.0.1/src/fast_mcp_gateway/hooks.py +177 -0
  16. fast_gateway-0.0.1/src/fast_mcp_gateway/models.py +134 -0
  17. fast_gateway-0.0.1/src/fast_mcp_gateway/plugins/__init__.py +66 -0
  18. fast_gateway-0.0.1/src/fast_mcp_gateway/plugins/agentos/README.md +84 -0
  19. fast_gateway-0.0.1/src/fast_mcp_gateway/plugins/agentos/__init__.py +12 -0
  20. fast_gateway-0.0.1/src/fast_mcp_gateway/plugins/agentos/detectors.py +142 -0
  21. fast_gateway-0.0.1/src/fast_mcp_gateway/plugins/agentos/plugin.py +114 -0
  22. fast_gateway-0.0.1/src/fast_mcp_gateway/plugins/agentos/policy.py +40 -0
  23. fast_gateway-0.0.1/src/fast_mcp_gateway/plugins/agentos/settings.py +46 -0
  24. fast_gateway-0.0.1/src/fast_mcp_gateway/py.typed +0 -0
  25. fast_gateway-0.0.1/src/fast_mcp_gateway/routing.py +76 -0
  26. fast_gateway-0.0.1/src/fast_mcp_gateway/search.py +81 -0
  27. fast_gateway-0.0.1/src/fast_mcp_gateway/store/__init__.py +6 -0
  28. fast_gateway-0.0.1/src/fast_mcp_gateway/store/base.py +83 -0
  29. fast_gateway-0.0.1/src/fast_mcp_gateway/store/sqlite.py +421 -0
@@ -0,0 +1,30 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Tooling caches
13
+ .pytest_cache/
14
+ .mypy_cache/
15
+ .ruff_cache/
16
+ .coverage
17
+ .coverage.*
18
+ htmlcov/
19
+
20
+ # Gateway runtime artifacts
21
+ *.db
22
+ gateway.db
23
+
24
+ # Editor / OS
25
+ .DS_Store
26
+ .idea/
27
+ .vscode/
28
+ .claude
29
+ docs/superpowers
30
+ .code-review-graph
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nir Adler
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,381 @@
1
+ Metadata-Version: 2.4
2
+ Name: fast-gateway
3
+ Version: 0.0.1
4
+ Summary: A lean, FastMCP-based gateway that turns a registry of upstream MCP servers into one governed, namespaced MCP endpoint mounted on FastAPI.
5
+ Project-URL: Homepage, https://github.com/niradler/fast-mcp-gateway
6
+ Project-URL: Repository, https://github.com/niradler/fast-mcp-gateway
7
+ Project-URL: Issues, https://github.com/niradler/fast-mcp-gateway/issues
8
+ Author: niradler
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: fastapi,fastmcp,gateway,llm,mcp,model-context-protocol,proxy
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: aiosqlite>=0.22.1
25
+ Requires-Dist: fastapi>=0.136.3
26
+ Requires-Dist: fastmcp>=3.3.1
27
+ Provides-Extra: agt
28
+ Requires-Dist: agent-governance-toolkit-core>=4.0.0; extra == 'agt'
29
+ Requires-Dist: agent-os-kernel>=4.0.0; extra == 'agt'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # fast-mcp-gateway
33
+
34
+ [![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue.svg)](https://www.python.org)
35
+ [![Built on FastMCP](https://img.shields.io/badge/built%20on-FastMCP%20v3-orange.svg)](https://gofastmcp.com)
36
+ [![FastAPI](https://img.shields.io/badge/mounts%20on-FastAPI-009688.svg)](https://fastapi.tiangolo.com)
37
+ [![Checked with mypy](https://img.shields.io/badge/types-mypy%20strict-2a6db2.svg)](https://mypy-lang.org)
38
+ [![Lint: ruff](https://img.shields.io/badge/lint-ruff-d7ff64.svg)](https://docs.astral.sh/ruff/)
39
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
40
+
41
+ A lean Python package that mounts on **FastAPI** and turns a registry of upstream
42
+ **MCP servers** into one governed, namespaced MCP endpoint. The core stays thin;
43
+ everything cross-cutting — auth, policy, human-in-the-loop, redaction, audit, cost
44
+ limits — is a **hook function** you pass at mount time, or a **plugin** that bundles
45
+ several together.
46
+
47
+ ```text
48
+ many upstream MCP servers ──► fast-mcp-gateway ──► one governed /mcp endpoint
49
+ github, slack, jira… (namespaced + filtered + policy-checked)
50
+ ```
51
+
52
+ > [!NOTE]
53
+ > **Status: 0.0.1, under active development.** This is the first public release and
54
+ > APIs may change. Server registry, the proxy builder, the full hook pipeline, groups
55
+ > with allow/deny, group-scoped endpoints, the plugin system, and the `search_tools` /
56
+ > `describe_tool` meta-tools are implemented and tested. The bundled reference hooks are
57
+ > still in progress — see the [roadmap](#roadmap).
58
+
59
+ ## Why
60
+
61
+ Point an LLM at a dozen MCP servers directly and you get a dozen connections, a dozen
62
+ auth schemes, no namespacing, no central policy, and no way to hide a dangerous tool.
63
+ `fast-mcp-gateway` puts one endpoint in front of them all:
64
+
65
+ - **One connection, many servers** — register upstreams in a store; the gateway
66
+ proxies each under its own namespace (`github_*`, `slack_*`, …).
67
+ - **Governed** — filter which tools are exposed, gate calls behind policy or
68
+ human approval, redact results, audit everything — all as hooks.
69
+ - **Reuse, don't rebuild** — [FastMCP](https://gofastmcp.com) already does proxying,
70
+ transport bridging, composition/namespacing, and protocol middleware. This package
71
+ builds only what it lacks: the registry, groups, the builder, the hook runner, and
72
+ the plugin seam.
73
+
74
+ ## Features
75
+
76
+ - **Server registry (Store)** — persistent CRUD over upstream MCP servers; ships with
77
+ a zero-setup `SqliteStore`, swappable for Postgres / Redis / in-memory via one
78
+ protocol.
79
+ - **Namespaced proxying** — each enabled server is mounted under its `name` as a
80
+ prefix; `reload()` rebuilds the mounts from the registry.
81
+ - **Five hook seams** — `pre_mcp_connect`, `pre_list_tools`, `pre_tool_call`,
82
+ `confirmation`, `post_tool_call`. Auth, policy, HIL, redaction, audit, and cost
83
+ limits are all plain async functions.
84
+ - **Access control** — per-server and per-group **allow/deny** glob lists, enforced on
85
+ both `list_tools` (hides) and `call_tool` (blocks).
86
+ - **Groups & group-scoped endpoints** — expose a curated subset of servers/tools at
87
+ `/mcp/g/{group}`, served by the same shared MCP app (no per-group duplication).
88
+ - **Plugins** — bundle hooks, FastMCP middleware, an admin router, ASGI mounts, and
89
+ meta-tools into one named extension with `setup` / `teardown`.
90
+ - **Optional policy engine** — an `agt` extra wires Microsoft's
91
+ [agent-governance-toolkit](https://github.com/microsoft/agent-governance-toolkit)
92
+ (agent-os) in as a policy plugin.
93
+ - **Typed throughout** — `mypy --strict`, `py.typed`, full type hints.
94
+
95
+ ## Architecture
96
+
97
+ ```text
98
+ FastAPI app
99
+ ├── /admin → APIRouter (CRUD: servers, groups, reload) [we build]
100
+ ├── /mcp → FastMCP.http_app() (the gateway MCP server) [FastMCP]
101
+ │ ├── mount(proxy_github, namespace="github")
102
+ │ ├── mount(proxy_slack, namespace="slack") ← namespacing
103
+ │ └── HookMiddleware + search meta-tools
104
+ └── /mcp/g/{grp} → same MCP app, scoped to one group's servers/tools [we build]
105
+ ```
106
+
107
+ The gateway is a **parent FastMCP server** that proxies each registered upstream and
108
+ mounts it under a namespace, exposed as an ASGI app you mount onto your own FastAPI app
109
+ alongside an admin router for CRUD. A `HookMiddleware` and an `AccessPolicy` wrap every
110
+ `list_tools` / `call_tool` request.
111
+
112
+ ## Getting started
113
+
114
+ ### Prerequisites
115
+
116
+ - Python **3.11+**
117
+ - [uv](https://docs.astral.sh/uv/) (recommended) or pip
118
+
119
+ ### Install
120
+
121
+ ```bash
122
+ uv add fast-mcp-gateway # or: pip install fast-mcp-gateway
123
+ ```
124
+
125
+ ### Quickstart
126
+
127
+ ```python
128
+ import os
129
+ from fastapi import FastAPI
130
+ from fast_mcp_gateway import ConnectContext, ConnectSettings, Hooks, SqliteStore, create_gateway
131
+
132
+
133
+ async def inject_auth(ctx: ConnectContext) -> ConnectSettings | None:
134
+ # Auth is just a hook: return headers merged over the server's static headers.
135
+ if ctx.server.name == "github":
136
+ return ConnectSettings(headers={"Authorization": f"Bearer {os.environ['GH_TOKEN']}"})
137
+ return None
138
+
139
+
140
+ gateway = create_gateway(
141
+ store=SqliteStore("gateway.db"),
142
+ hooks=Hooks(pre_mcp_connect=[inject_auth]),
143
+ )
144
+
145
+ # The MCP server manages sessions via lifespan, so wire it on the host app:
146
+ app = FastAPI(lifespan=gateway.lifespan)
147
+ gateway.install(app) # mounts /admin (CRUD) and /mcp (MCP endpoint)
148
+ ```
149
+
150
+ Register an upstream server through the admin API, then reload:
151
+
152
+ ```bash
153
+ curl -X POST http://127.0.0.1:8000/admin/servers \
154
+ -H 'content-type: application/json' \
155
+ -d '{"name": "math", "url": "http://127.0.0.1:9000/mcp/", "transport": "http"}'
156
+
157
+ curl -X POST http://127.0.0.1:8000/admin/reload
158
+ ```
159
+
160
+ Its tools now appear at the gateway endpoint under the `math_` namespace.
161
+
162
+ ### Run the bundled example
163
+
164
+ ```bash
165
+ make run # uv run uvicorn examples.basic_app:app --reload
166
+ # Admin + OpenAPI docs: http://127.0.0.1:8000/docs
167
+ # MCP endpoint: http://127.0.0.1:8000/mcp/
168
+ ```
169
+
170
+ ## Hooks
171
+
172
+ A hook is an async function, grouped in a `Hooks` container and passed at mount time.
173
+ Each binds to the layer where it belongs:
174
+
175
+ | Hook | Binds to | Runs |
176
+ | --- | --- | --- |
177
+ | `pre_mcp_connect` | proxy client factory | before opening an upstream session |
178
+ | `pre_list_tools` | `HookMiddleware.on_list_tools` | on catalog requests |
179
+ | `pre_tool_call` | `HookMiddleware.on_call_tool` (pre) | before forwarding a call |
180
+ | `confirmation` | `on_call_tool` (when `REQUIRE_CONFIRMATION`) | human-in-the-loop approval |
181
+ | `post_tool_call` | `HookMiddleware.on_call_tool` (post) | after the upstream result |
182
+
183
+ Hooks chain in registration order. A `pre_tool_call` hook may **continue**, **mutate
184
+ args**, **deny**, or return **`REQUIRE_CONFIRMATION`** — which triggers the
185
+ `confirmation` hooks.
186
+
187
+ > [!IMPORTANT]
188
+ > Confirmation is **fail-safe**: if any confirmation hook rejects, or none is
189
+ > registered, the call is denied. Policy, guardrails, audit, and cost limits are all
190
+ > just hooks — nothing special in the core.
191
+
192
+ ```python
193
+ from fast_mcp_gateway import Hooks, ToolCallResult, ToolDecision
194
+
195
+
196
+ async def block_deletes(ctx) -> ToolCallResult | None:
197
+ if ctx.message.name.endswith("_delete_all"):
198
+ return ToolCallResult(decision=ToolDecision.REQUIRE_CONFIRMATION, reason="destructive")
199
+ return None
200
+
201
+
202
+ async def approve(ctx) -> bool:
203
+ return await ask_a_human(ctx.tool_name, ctx.arguments) # your HIL channel
204
+
205
+
206
+ hooks = Hooks(pre_tool_call=[block_deletes], confirmation=[approve])
207
+ ```
208
+
209
+ ## Access control
210
+
211
+ Every server record carries `allow` / `deny` glob lists; groups carry their own on top.
212
+ `deny` wins over `allow`; an empty `allow` means "allow all". The policy is enforced in
213
+ two places: hidden from `list_tools` and blocked at `call_tool`.
214
+
215
+ ```jsonc
216
+ // POST /admin/servers
217
+ { "name": "fs", "url": "...", "deny": ["delete_*", "*_admin"] }
218
+ ```
219
+
220
+ ### Groups & group-scoped endpoints
221
+
222
+ Create a group, set its membership, and a curated view is served at
223
+ `/mcp/g/{group}` — showing only that group's member servers with the group's
224
+ allow/deny applied **on top of** each server's own rules. One shared parent server
225
+ backs every group view; there is no per-group proxy duplication.
226
+
227
+ ```text
228
+ /mcp → all enabled servers, every permitted tool
229
+ /mcp/g/analytics → only the 'analytics' group's servers & tools
230
+ ```
231
+
232
+ ## Plugins
233
+
234
+ A **plugin** is a named bundle of extensions applied at `create_gateway` time. Where a
235
+ single hook is one function, a plugin can contribute hooks **and** FastMCP middleware
236
+ (for around-the-call control like circuit breaking or retry), an admin `APIRouter`,
237
+ ASGI sub-app mounts, meta-tool registration, and async `setup` / `teardown` bound to
238
+ the gateway lifespan.
239
+
240
+ ```python
241
+ from fast_mcp_gateway import create_gateway, SqliteStore
242
+
243
+ gateway = create_gateway(
244
+ store=SqliteStore("gateway.db"),
245
+ plugins=[MyAuditPlugin(), MyRateLimitPlugin()],
246
+ )
247
+ ```
248
+
249
+ A plugin implements the `Plugin` protocol: a `name`, a `contributions(context)` method
250
+ returning `PluginContributions`, and `setup` / `teardown` coroutines. The
251
+ `GatewayContext` it receives exposes the `store`, the parent `mcp`, and a `reload`
252
+ callable. These authoring types are top-level exports:
253
+
254
+ ```python
255
+ from fast_mcp_gateway import Plugin, PluginContributions, GatewayContext
256
+ ```
257
+
258
+ ### Optional: agent-os plugin (experimental)
259
+
260
+ > [!WARNING]
261
+ > The `agt` integration is **experimental**. Its upstream dependency is not yet on
262
+ > PyPI, so it installs only inside a uv project (see the install note below).
263
+
264
+ The `agt` extra wires Microsoft's
265
+ [agent-governance-toolkit](https://github.com/microsoft/agent-governance-toolkit)
266
+ (agent-os) in as `AgtAgentOsPlugin`. Its core capability is the **policy engine**: it
267
+ evaluates policy for every tool call — scoped to the active group — and denies calls the
268
+ policy rejects. Additional agent-os capabilities are opt-in toggles on `AgtAgentOsSettings`
269
+ (which reuses agent-os's own config types — `DetectionConfig`, `IntentCategory`, `EgressRule`):
270
+
271
+ | Toggle | Seam | Effect |
272
+ | --- | --- | --- |
273
+ | `enable_prompt_injection` | `pre_tool_call` | deny calls whose arguments look like prompt injection |
274
+ | `enable_semantic_policy` (+ `semantic_deny`) | `pre_tool_call` | deny calls whose classified intent is dangerous |
275
+ | `enable_response_scan` | `post_tool_call` | block responses flagged unsafe (credential/PII/threat) |
276
+ | `enable_credential_redaction` | `post_tool_call` | redact secrets/PII out of responses |
277
+ | `enable_egress_policy` (+ `egress_rules`) | `pre_mcp_connect` | refuse upstreams whose URL is outside the allowlist |
278
+
279
+ ```bash
280
+ uv add "fast-mcp-gateway[agt]" # from within a uv project — honors the git source
281
+ ```
282
+
283
+ ```python
284
+ from fast_mcp_gateway import create_gateway, SqliteStore
285
+ from fast_mcp_gateway.plugins.agentos import AgtAgentOsPlugin, AgtAgentOsSettings
286
+
287
+ gateway = create_gateway(
288
+ store=SqliteStore("gateway.db"),
289
+ plugins=[
290
+ AgtAgentOsPlugin(
291
+ AgtAgentOsSettings(
292
+ policy_dir="./policies",
293
+ fail_closed=True,
294
+ enable_prompt_injection=True,
295
+ enable_response_scan=True,
296
+ enable_credential_redaction=True,
297
+ )
298
+ )
299
+ ],
300
+ )
301
+ ```
302
+
303
+ > [!NOTE]
304
+ > The `agt` extra is sourced from the agent-governance-toolkit GitHub monorepo (via uv
305
+ > `[tool.uv.sources]`) until `agent-os-kernel` 4.x is published to PyPI. Because of that
306
+ > git source, install it from within a uv project (`uv add "fast-mcp-gateway[agt]"`),
307
+ > which honors the source; a plain `pip install "fast-mcp-gateway[agt]"` cannot resolve
308
+ > the dependency and will fail until it lands on PyPI. Upstream, `agent-os-kernel` is
309
+ > being renamed/consolidated to `agent-governance-toolkit-core`. The gateway and the
310
+ > plugin system work fully **without** the extra — only this one integration needs it.
311
+
312
+ ## Admin API
313
+
314
+ | Method | Path | Purpose |
315
+ | --- | --- | --- |
316
+ | `GET` / `POST` | `/admin/servers` | list / register servers |
317
+ | `GET` / `PATCH` / `DELETE` | `/admin/servers/{id}` | read / update / remove |
318
+ | `GET` | `/admin/servers/{id}/tools` | live tool introspection |
319
+ | `POST` | `/admin/servers/{id}/test` | connect + handshake check |
320
+ | `GET` / `POST` | `/admin/groups` | list / create groups |
321
+ | `GET` / `PATCH` / `DELETE` | `/admin/groups/{id}` | read / update / remove |
322
+ | `PUT` | `/admin/groups/{id}/servers` | set membership |
323
+ | `POST` | `/admin/reload` | rebuild mounts from the store |
324
+
325
+ CRUD writes to the `Store`; `POST /admin/reload` (or `await gateway.reload()`) rebuilds
326
+ the proxy mounts. There is no live hot-swap in v1 — simple and lean.
327
+
328
+ > [!WARNING]
329
+ > The `/admin` API is **unauthenticated by default** and mutates the registry —
330
+ > registering upstreams, rewriting allow/deny lists, injecting connection headers, and
331
+ > triggering reload. The host app **must** protect it. Pass FastAPI dependencies via
332
+ > `Gateway.install(app, admin_dependencies=[Depends(require_admin)])` to guard the admin
333
+ > router, and/or place it behind reverse-proxy or network-level auth.
334
+
335
+ ## Store
336
+
337
+ The gateway's only persistence dependency is the `Store` protocol. `SqliteStore`
338
+ (single file, zero setup) ships as the default; Postgres / Redis / in-memory are
339
+ drop-in via `store=` with no core changes.
340
+
341
+ ```python
342
+ class Store(Protocol):
343
+ async def initialize(self) -> None: ...
344
+ async def list_servers(self) -> list[ServerRecord]: ...
345
+ async def create_server(self, data: ServerCreate) -> ServerRecord: ...
346
+ # … plus get/patch/delete for servers and groups
347
+ ```
348
+
349
+ ## Development
350
+
351
+ ```bash
352
+ make install # uv sync (venv + deps incl. dev group)
353
+ make check # lint + format-check + typecheck + tests (CI gate; run before done)
354
+ make test # pytest
355
+ make format # ruff format + safe lint fixes
356
+ make build # sdist + wheel
357
+ ```
358
+
359
+ Tooling: [uv](https://docs.astral.sh/uv/) (env + packaging), **ruff** (lint + format),
360
+ **mypy --strict** (types, the gate), **pytest** + pytest-asyncio. Run `make help` for
361
+ all targets.
362
+
363
+ > [!TIP]
364
+ > On Windows, `make` is not built in — use it from WSL/Git Bash, install GNU Make
365
+ > (`scoop install make`), or run the underlying `uv run ...` commands directly.
366
+
367
+ ## Roadmap
368
+
369
+ | Phase | Deliverable | Status |
370
+ | --- | --- | --- |
371
+ | 0 | Package skeleton, `Store` protocol + `SqliteStore`, `create_gateway()` | done |
372
+ | 1 | Server CRUD + builder (registry → proxy mount) + `reload()` + `pre_mcp_connect` | done |
373
+ | 2 | `HookMiddleware`: `pre_tool_call` / `post_tool_call` / `pre_list_tools` | done |
374
+ | 3 | Groups + per-server/group allow-deny + group-scoped `/mcp/g/{group}` endpoints | done |
375
+ | — | Plugin system + agent-os policy integration | done |
376
+ | 4 | `search_tools` / `describe_tool` meta-tools + catalog cache | done |
377
+ | 5 | Reference hooks (audit, allow/deny, confirmation), docs, packaging | planned |
378
+
379
+ ## License
380
+
381
+ [MIT](LICENSE)