mcp-core-auth 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.
- mcp_core_auth-0.2.1/.env.example +45 -0
- mcp_core_auth-0.2.1/.gitignore +18 -0
- mcp_core_auth-0.2.1/CONTRIBUTING.md +42 -0
- mcp_core_auth-0.2.1/LICENSE +21 -0
- mcp_core_auth-0.2.1/PKG-INFO +146 -0
- mcp_core_auth-0.2.1/README.md +113 -0
- mcp_core_auth-0.2.1/SECURITY.md +34 -0
- mcp_core_auth-0.2.1/deploy/logto/.env.example +14 -0
- mcp_core_auth-0.2.1/deploy/logto/README.md +218 -0
- mcp_core_auth-0.2.1/deploy/logto/bootstrap-apps.example.json +21 -0
- mcp_core_auth-0.2.1/deploy/logto/bootstrap-apps.py +330 -0
- mcp_core_auth-0.2.1/deploy/logto/deploy.sh +88 -0
- mcp_core_auth-0.2.1/deploy/logto/docker-compose.local.yml +30 -0
- mcp_core_auth-0.2.1/deploy/logto/docker-compose.prod.yml +33 -0
- mcp_core_auth-0.2.1/deploy/logto/docker-compose.yml +34 -0
- mcp_core_auth-0.2.1/deploy/logto/nginx-auth.conf.template +77 -0
- mcp_core_auth-0.2.1/deploy/logto/provision-ses.sh +75 -0
- mcp_core_auth-0.2.1/deploy/logto/verify.py +270 -0
- mcp_core_auth-0.2.1/docs/architecture.png +0 -0
- mcp_core_auth-0.2.1/docs/fastmcp-migration.md +185 -0
- mcp_core_auth-0.2.1/docs/integration-guide.md +859 -0
- mcp_core_auth-0.2.1/docs/oauth-mcp-integration.md +316 -0
- mcp_core_auth-0.2.1/examples/minimal_server/.env.example +23 -0
- mcp_core_auth-0.2.1/examples/minimal_server/main.py +55 -0
- mcp_core_auth-0.2.1/examples/minimal_server/mcp_tools.py +53 -0
- mcp_core_auth-0.2.1/examples/minimal_server/requirements.txt +3 -0
- mcp_core_auth-0.2.1/pyproject.toml +54 -0
- mcp_core_auth-0.2.1/src/mcp_core/__init__.py +324 -0
- mcp_core_auth-0.2.1/src/mcp_core/auth.py +235 -0
- mcp_core_auth-0.2.1/src/mcp_core/billing.py +269 -0
- mcp_core_auth-0.2.1/src/mcp_core/dcr.py +185 -0
- mcp_core_auth-0.2.1/src/mcp_core/health.py +71 -0
- mcp_core_auth-0.2.1/src/mcp_core/mcp_mount.py +240 -0
- mcp_core_auth-0.2.1/src/mcp_core/py.typed +0 -0
- mcp_core_auth-0.2.1/src/mcp_core/routes.py +251 -0
- mcp_core_auth-0.2.1/src/mcp_core/tool_logging.py +68 -0
- mcp_core_auth-0.2.1/tests/.env.live.example +20 -0
- mcp_core_auth-0.2.1/tests/conftest.py +239 -0
- mcp_core_auth-0.2.1/tests/live/__init__.py +0 -0
- mcp_core_auth-0.2.1/tests/live/conftest.py +120 -0
- mcp_core_auth-0.2.1/tests/live/test_live_auth.py +106 -0
- mcp_core_auth-0.2.1/tests/live/test_live_billing.py +100 -0
- mcp_core_auth-0.2.1/tests/live/test_live_health.py +42 -0
- mcp_core_auth-0.2.1/tests/live/test_live_integration.py +128 -0
- mcp_core_auth-0.2.1/tests/live/test_live_logging.py +54 -0
- mcp_core_auth-0.2.1/tests/test_auth.py +289 -0
- mcp_core_auth-0.2.1/tests/test_billing.py +282 -0
- mcp_core_auth-0.2.1/tests/test_branded_sign_in.py +168 -0
- mcp_core_auth-0.2.1/tests/test_dcr.py +190 -0
- mcp_core_auth-0.2.1/tests/test_health.py +81 -0
- mcp_core_auth-0.2.1/tests/test_integration.py +145 -0
- mcp_core_auth-0.2.1/tests/test_logging.py +94 -0
- mcp_core_auth-0.2.1/tests/test_mcp_mount.py +230 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# mcp-core configuration
|
|
2
|
+
#
|
|
3
|
+
# Copy to .env (gitignored) and fill in values for your deployment.
|
|
4
|
+
# All vars are MCP_CORE_*-prefixed so they don't collide with other libs
|
|
5
|
+
# in your process. The minimal_server example reads these directly via
|
|
6
|
+
# pydantic-settings — see examples/minimal_server/config.py.
|
|
7
|
+
|
|
8
|
+
# --- Product identity ---
|
|
9
|
+
MCP_CORE_PRODUCT_NAME=myapp
|
|
10
|
+
|
|
11
|
+
# --- Logto auth (cloud or self-hosted) ---
|
|
12
|
+
# Cloud: https://your-tenant.logto.app
|
|
13
|
+
# Self-hosted: https://auth.example.com (see deploy/logto/)
|
|
14
|
+
MCP_CORE_LOGTO_ENDPOINT=https://your-tenant.logto.app
|
|
15
|
+
MCP_CORE_LOGTO_API_RESOURCE=https://api.example.com
|
|
16
|
+
|
|
17
|
+
# Set to 1 in local dev to accept "Bearer dev-bypass" as a valid token.
|
|
18
|
+
# NEVER set this in production.
|
|
19
|
+
MCP_CORE_DEV_AUTH_BYPASS=0
|
|
20
|
+
|
|
21
|
+
# --- MCP OAuth (for Claude Code / Inspector) ---
|
|
22
|
+
# Created by deploy/logto/bootstrap-apps.py or Logto admin console.
|
|
23
|
+
MCP_CORE_MCP_LOGTO_APP_ID=
|
|
24
|
+
MCP_CORE_MCP_LOGTO_APP_SECRET=
|
|
25
|
+
|
|
26
|
+
# --- DCR (Dynamic Client Registration) ---
|
|
27
|
+
# M2M app + role used to register per-client OAuth apps on demand.
|
|
28
|
+
# On self-hosted Logto, the management token endpoint is on port 8443:
|
|
29
|
+
# MCP_CORE_LOGTO_MGMT_TOKEN_ENDPOINT=https://auth.example.com:8443/oidc/token
|
|
30
|
+
# On Logto Cloud, omit it (defaults to <endpoint>/oidc/token).
|
|
31
|
+
MCP_CORE_LOGTO_MGMT_APP_ID=
|
|
32
|
+
MCP_CORE_LOGTO_MGMT_APP_SECRET=
|
|
33
|
+
# MCP_CORE_LOGTO_MGMT_TOKEN_ENDPOINT=
|
|
34
|
+
|
|
35
|
+
# --- MongoDB (for user/credit storage) ---
|
|
36
|
+
MCP_CORE_MONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net/
|
|
37
|
+
MCP_CORE_DB_NAME=myapp
|
|
38
|
+
|
|
39
|
+
# --- Stripe billing (test mode keys for dev) ---
|
|
40
|
+
MCP_CORE_STRIPE_SECRET_KEY=sk_test_...
|
|
41
|
+
MCP_CORE_STRIPE_PRICE_ID=price_...
|
|
42
|
+
MCP_CORE_STRIPE_WEBHOOK_SECRET=whsec_...
|
|
43
|
+
MCP_CORE_STRIPE_METER_EVENT=mcp_tool_calls
|
|
44
|
+
MCP_CORE_FREE_CREDITS=30
|
|
45
|
+
MCP_CORE_BILLING_SUCCESS_URL=https://myapp.example.com/billing/success
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Contributing to mcp-core
|
|
2
|
+
|
|
3
|
+
Thanks for considering a contribution. mcp-core is a small library — issues and PRs are both welcome.
|
|
4
|
+
|
|
5
|
+
## Reporting bugs
|
|
6
|
+
|
|
7
|
+
Before filing, please:
|
|
8
|
+
|
|
9
|
+
1. Reproduce on the latest `main`.
|
|
10
|
+
2. Check open issues for duplicates.
|
|
11
|
+
3. Include: Python version, mcp-core version, a minimal repro, and the full traceback (not just the last line).
|
|
12
|
+
|
|
13
|
+
## Proposing changes
|
|
14
|
+
|
|
15
|
+
For anything more than a one-line fix, open an issue first to discuss the approach. mcp-core has a deliberately narrow scope (auth + billing + MCP mount + tool logging for FastAPI MCP servers), and not every feature fits.
|
|
16
|
+
|
|
17
|
+
## Pull requests
|
|
18
|
+
|
|
19
|
+
- One topic per PR. Refactors and bug fixes go in separate PRs.
|
|
20
|
+
- Include tests for new behavior. The `tests/` suite runs on mocks; if your change needs live services, add a `tests/live/` test guarded by `RUN_LIVE_TESTS=1`.
|
|
21
|
+
- Keep public API changes backward-compatible when possible. If you must break something, call it out in the PR description.
|
|
22
|
+
- Match the existing style — no formatter is enforced, but the code aims for short modules with docstrings on public classes and short inline comments only where intent isn't obvious.
|
|
23
|
+
|
|
24
|
+
## Local development
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git clone https://github.com/swapp1990/mcp-core
|
|
28
|
+
cd mcp-core
|
|
29
|
+
pip install -e ".[dev]"
|
|
30
|
+
pytest # mock-based tests, run anywhere
|
|
31
|
+
RUN_LIVE_TESTS=1 pytest tests/live # needs tests/.env.live with real Logto/Mongo/Stripe
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For OAuth flow changes, the reference deployment is `deploy/logto/` — spin up a local Logto in docker and run `verify.py` to confirm mcp-core still talks to it.
|
|
35
|
+
|
|
36
|
+
## Auth providers
|
|
37
|
+
|
|
38
|
+
mcp-core is currently Logto-specific. PRs that add support for additional OIDC providers (Auth0, Keycloak, generic OIDC) are welcome — the cleanest path is extracting a `BaseOIDCProvider` interface from `LogtoAuth` rather than dropping a parallel class. Open an issue first to discuss the shape.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
By contributing, you agree that your contributions will be licensed under the same MIT license as the rest of the repo.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 swapp1990
|
|
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,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-core-auth
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Auth, billing, and logging infrastructure for MCP-first servers.
|
|
5
|
+
Project-URL: Homepage, https://github.com/swapp1990/mcp-core
|
|
6
|
+
Project-URL: Repository, https://github.com/swapp1990/mcp-core
|
|
7
|
+
Author: swapp1990
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: auth,billing,fastapi,logto,mcp,stripe
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: cryptography>=41.0.0
|
|
21
|
+
Requires-Dist: fastapi>=0.109.0
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Requires-Dist: motor>=3.3.0
|
|
24
|
+
Requires-Dist: pyjwt>=2.8.0
|
|
25
|
+
Requires-Dist: stripe>=8.0.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
28
|
+
Requires-Dist: mongomock-motor>=0.0.1; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# mcp-core
|
|
35
|
+
|
|
36
|
+
[](https://github.com/swapp1990/mcp-core/releases)
|
|
37
|
+
[](https://opensource.org/licenses/MIT)
|
|
38
|
+
[](https://www.python.org/downloads/)
|
|
39
|
+
|
|
40
|
+
Auth, billing, and logging infrastructure for MCP-first servers. Sits between your product code and [fastapi-mcp](https://github.com/tadata-org/fastapi-mcp).
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Your MCP Server (product-specific tool handlers)
|
|
44
|
+
mcp-core (auth, billing, logging, health)
|
|
45
|
+
fastapi-mcp (MCP protocol: JSON-RPC, SSE, tool discovery)
|
|
46
|
+
FastAPI
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install mcp-core-auth
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The package is published as `mcp-core-auth` on PyPI (the bare `mcp-core` name is held by an unrelated project), but the import path is unchanged:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from mcp_core import MCPCore
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from contextlib import asynccontextmanager
|
|
65
|
+
from fastapi import FastAPI, Request
|
|
66
|
+
from mcp_core import MCPCore
|
|
67
|
+
|
|
68
|
+
core = MCPCore(
|
|
69
|
+
product_name="my-product",
|
|
70
|
+
logto_endpoint="https://your-tenant.logto.app",
|
|
71
|
+
logto_api_resource="https://api.my-product.app",
|
|
72
|
+
mongodb_uri="mongodb+srv://...",
|
|
73
|
+
stripe_secret_key="sk_test_...",
|
|
74
|
+
stripe_price_id="price_...",
|
|
75
|
+
free_credits=30,
|
|
76
|
+
tool_costs={"browse": 0, "generate": 5},
|
|
77
|
+
read_only_tools={"browse"},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
@asynccontextmanager
|
|
81
|
+
async def lifespan(app: FastAPI):
|
|
82
|
+
await core.connect_db()
|
|
83
|
+
yield
|
|
84
|
+
|
|
85
|
+
app = FastAPI(lifespan=lifespan)
|
|
86
|
+
core.install_routes(app) # /health, /api/billing/credits, webhook, OAuth metadata
|
|
87
|
+
|
|
88
|
+
@app.post("/api/mcp/generate")
|
|
89
|
+
async def generate(request: Request):
|
|
90
|
+
user = await core.auth_and_bill(request, "generate")
|
|
91
|
+
result = do_generation()
|
|
92
|
+
await core.log_tool_call(request, "generate", user=user, duration_ms=1200)
|
|
93
|
+
return result
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
All config can also come from `MCP_CORE_*` environment variables.
|
|
97
|
+
|
|
98
|
+
## Modules
|
|
99
|
+
|
|
100
|
+
### Auth (`mcp_core.auth.LogtoAuth`)
|
|
101
|
+
|
|
102
|
+
Logto JWT validation via JWKS. Creates MongoDB user records on first auth.
|
|
103
|
+
|
|
104
|
+
- RS256/ES256/ES384/ES512 support
|
|
105
|
+
- 30s clock skew tolerance
|
|
106
|
+
- Race-condition-safe user upsert
|
|
107
|
+
- Dev bypass (`Bearer dev-bypass`) for local development
|
|
108
|
+
- M2M token rejection for paid tools
|
|
109
|
+
|
|
110
|
+
### Billing (`mcp_core.billing.StripeBilling`)
|
|
111
|
+
|
|
112
|
+
Stripe metered billing with free credit fallback.
|
|
113
|
+
|
|
114
|
+
- Free credits deducted first
|
|
115
|
+
- Stripe metered subscription as fallback
|
|
116
|
+
- 402 with Checkout URL when no credits and no subscription
|
|
117
|
+
- Webhook handler for `checkout.session.completed` and `customer.subscription.created`
|
|
118
|
+
|
|
119
|
+
### Tool Logging (`mcp_core.tool_logging.ToolLogger`)
|
|
120
|
+
|
|
121
|
+
Audit trail for every MCP tool call. Writes to MongoDB `tool_logs` collection.
|
|
122
|
+
|
|
123
|
+
### Health (`mcp_core.health.HealthCheck`)
|
|
124
|
+
|
|
125
|
+
Composable health check builder. Supports sync and async checks with timeouts.
|
|
126
|
+
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Mock tests (no external services)
|
|
131
|
+
pip install -e ".[dev]"
|
|
132
|
+
pytest tests/ -v
|
|
133
|
+
|
|
134
|
+
# Live tests (requires .env.live with real credentials)
|
|
135
|
+
RUN_LIVE_TESTS=1 pytest tests/live/ -v
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Contributing
|
|
139
|
+
|
|
140
|
+
Bug reports and PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow and [SECURITY.md](SECURITY.md) for vulnerability reporting.
|
|
141
|
+
|
|
142
|
+
Auth provider abstraction (Auth0, Keycloak, generic OIDC) is tracked in [#1](https://github.com/swapp1990/mcp-core/issues/1) — discussion-first.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# mcp-core
|
|
2
|
+
|
|
3
|
+
[](https://github.com/swapp1990/mcp-core/releases)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
|
|
7
|
+
Auth, billing, and logging infrastructure for MCP-first servers. Sits between your product code and [fastapi-mcp](https://github.com/tadata-org/fastapi-mcp).
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Your MCP Server (product-specific tool handlers)
|
|
11
|
+
mcp-core (auth, billing, logging, health)
|
|
12
|
+
fastapi-mcp (MCP protocol: JSON-RPC, SSE, tool discovery)
|
|
13
|
+
FastAPI
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install mcp-core-auth
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The package is published as `mcp-core-auth` on PyPI (the bare `mcp-core` name is held by an unrelated project), but the import path is unchanged:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from mcp_core import MCPCore
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from contextlib import asynccontextmanager
|
|
32
|
+
from fastapi import FastAPI, Request
|
|
33
|
+
from mcp_core import MCPCore
|
|
34
|
+
|
|
35
|
+
core = MCPCore(
|
|
36
|
+
product_name="my-product",
|
|
37
|
+
logto_endpoint="https://your-tenant.logto.app",
|
|
38
|
+
logto_api_resource="https://api.my-product.app",
|
|
39
|
+
mongodb_uri="mongodb+srv://...",
|
|
40
|
+
stripe_secret_key="sk_test_...",
|
|
41
|
+
stripe_price_id="price_...",
|
|
42
|
+
free_credits=30,
|
|
43
|
+
tool_costs={"browse": 0, "generate": 5},
|
|
44
|
+
read_only_tools={"browse"},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
@asynccontextmanager
|
|
48
|
+
async def lifespan(app: FastAPI):
|
|
49
|
+
await core.connect_db()
|
|
50
|
+
yield
|
|
51
|
+
|
|
52
|
+
app = FastAPI(lifespan=lifespan)
|
|
53
|
+
core.install_routes(app) # /health, /api/billing/credits, webhook, OAuth metadata
|
|
54
|
+
|
|
55
|
+
@app.post("/api/mcp/generate")
|
|
56
|
+
async def generate(request: Request):
|
|
57
|
+
user = await core.auth_and_bill(request, "generate")
|
|
58
|
+
result = do_generation()
|
|
59
|
+
await core.log_tool_call(request, "generate", user=user, duration_ms=1200)
|
|
60
|
+
return result
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
All config can also come from `MCP_CORE_*` environment variables.
|
|
64
|
+
|
|
65
|
+
## Modules
|
|
66
|
+
|
|
67
|
+
### Auth (`mcp_core.auth.LogtoAuth`)
|
|
68
|
+
|
|
69
|
+
Logto JWT validation via JWKS. Creates MongoDB user records on first auth.
|
|
70
|
+
|
|
71
|
+
- RS256/ES256/ES384/ES512 support
|
|
72
|
+
- 30s clock skew tolerance
|
|
73
|
+
- Race-condition-safe user upsert
|
|
74
|
+
- Dev bypass (`Bearer dev-bypass`) for local development
|
|
75
|
+
- M2M token rejection for paid tools
|
|
76
|
+
|
|
77
|
+
### Billing (`mcp_core.billing.StripeBilling`)
|
|
78
|
+
|
|
79
|
+
Stripe metered billing with free credit fallback.
|
|
80
|
+
|
|
81
|
+
- Free credits deducted first
|
|
82
|
+
- Stripe metered subscription as fallback
|
|
83
|
+
- 402 with Checkout URL when no credits and no subscription
|
|
84
|
+
- Webhook handler for `checkout.session.completed` and `customer.subscription.created`
|
|
85
|
+
|
|
86
|
+
### Tool Logging (`mcp_core.tool_logging.ToolLogger`)
|
|
87
|
+
|
|
88
|
+
Audit trail for every MCP tool call. Writes to MongoDB `tool_logs` collection.
|
|
89
|
+
|
|
90
|
+
### Health (`mcp_core.health.HealthCheck`)
|
|
91
|
+
|
|
92
|
+
Composable health check builder. Supports sync and async checks with timeouts.
|
|
93
|
+
|
|
94
|
+
## Testing
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Mock tests (no external services)
|
|
98
|
+
pip install -e ".[dev]"
|
|
99
|
+
pytest tests/ -v
|
|
100
|
+
|
|
101
|
+
# Live tests (requires .env.live with real credentials)
|
|
102
|
+
RUN_LIVE_TESTS=1 pytest tests/live/ -v
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Contributing
|
|
106
|
+
|
|
107
|
+
Bug reports and PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow and [SECURITY.md](SECURITY.md) for vulnerability reporting.
|
|
108
|
+
|
|
109
|
+
Auth provider abstraction (Auth0, Keycloak, generic OIDC) is tracked in [#1](https://github.com/swapp1990/mcp-core/issues/1) — discussion-first.
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
Please **do not** file public GitHub issues for security vulnerabilities.
|
|
6
|
+
|
|
7
|
+
Email the maintainer directly with:
|
|
8
|
+
|
|
9
|
+
- A description of the issue and its impact.
|
|
10
|
+
- Steps to reproduce, or a proof-of-concept.
|
|
11
|
+
- Affected versions, if known.
|
|
12
|
+
- Your name / handle for credit (optional).
|
|
13
|
+
|
|
14
|
+
Contact: report security issues via [GitHub's private vulnerability reporting](https://github.com/swapp1990/mcp-core/security/advisories/new) on this repo.
|
|
15
|
+
|
|
16
|
+
You should expect an acknowledgement within a few business days. If the report is valid, we'll work on a fix and coordinate disclosure with you. Critical issues that can be exploited remotely will be prioritized.
|
|
17
|
+
|
|
18
|
+
## Scope
|
|
19
|
+
|
|
20
|
+
In scope:
|
|
21
|
+
- Auth bypass in `LogtoAuth` (token validation, JWKS handling, audience/issuer checks).
|
|
22
|
+
- Privilege escalation through DCR (`LogtoDCR`).
|
|
23
|
+
- Billing bypass in `StripeBilling` (credit accounting, webhook signature verification).
|
|
24
|
+
- Information disclosure via `ToolLogger` or health endpoints.
|
|
25
|
+
- Anything that lets an unauthenticated caller invoke a paid tool, or one user act as another.
|
|
26
|
+
|
|
27
|
+
Out of scope:
|
|
28
|
+
- Vulnerabilities in upstream dependencies (Logto, Stripe, FastAPI, MongoDB) — please report those upstream. We'll bump pins once a fix is released.
|
|
29
|
+
- Self-inflicted misconfiguration of a downstream deployment (e.g. publishing your `.env` to a public S3 bucket).
|
|
30
|
+
- Issues in `examples/` or `deploy/logto/demos/` — those are illustrative, not production code.
|
|
31
|
+
|
|
32
|
+
## Supported versions
|
|
33
|
+
|
|
34
|
+
Only the latest tagged minor version receives security fixes. Pre-1.0, the API may change between minors — pin your version and watch the changelog.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copy to .env next to the compose files (and to /opt/apps/logto-docker/.env on the prod server).
|
|
2
|
+
|
|
3
|
+
# --- Required in prod ---
|
|
4
|
+
# Public domain that fronts Logto via host nginx (see nginx-auth.conf.template).
|
|
5
|
+
LOGTO_DOMAIN=auth.example.com
|
|
6
|
+
LOGTO_ENDPOINT=https://auth.example.com
|
|
7
|
+
LOGTO_ADMIN_ENDPOINT=https://auth.example.com:8443
|
|
8
|
+
|
|
9
|
+
# Postgres password for the Logto DB. Generate with: openssl rand -hex 32
|
|
10
|
+
POSTGRES_PASSWORD=
|
|
11
|
+
|
|
12
|
+
# --- Optional ---
|
|
13
|
+
# Pin Logto to a specific version instead of :latest
|
|
14
|
+
# LOGTO_IMAGE=svhd/logto:1.23.0
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Self-hosted Logto for mcp-core
|
|
2
|
+
|
|
3
|
+
The reference Logto deployment that every mcp-core consumer can stand up on
|
|
4
|
+
their own infrastructure. Runs Logto + Postgres in docker-compose. Works
|
|
5
|
+
identically in local dev and prod; only the public URLs + port bindings
|
|
6
|
+
differ between `docker-compose.local.yml` and `docker-compose.prod.yml`.
|
|
7
|
+
|
|
8
|
+
## Files
|
|
9
|
+
|
|
10
|
+
| File | Purpose |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `docker-compose.yml` | Base: Logto + Postgres. No port bindings, no public URLs. |
|
|
13
|
+
| `docker-compose.local.yml` | Dev override: binds 3001/3002, sets `ENDPOINT=http://localhost:3001`. |
|
|
14
|
+
| `docker-compose.prod.yml` | Prod override: binds 3011/3012, reads public URLs from `.env`. |
|
|
15
|
+
| `.env.example` | All env vars (`LOGTO_DOMAIN`, `LOGTO_ENDPOINT`, `LOGTO_ADMIN_ENDPOINT`, `POSTGRES_PASSWORD`). |
|
|
16
|
+
| `nginx-auth.conf.template` | Host nginx vhost — render `__DOMAIN__` with your auth host. |
|
|
17
|
+
| `bootstrap-apps.example.json` | Schema for product definitions consumed by `bootstrap-apps.py`. |
|
|
18
|
+
| `bootstrap-apps.py` | Idempotent CLI that creates SPA + API resource + DCR M2M apps from a config. |
|
|
19
|
+
| `verify.py` | End-to-end compatibility check: mcp-core talking to self-host. |
|
|
20
|
+
| `deploy.sh` | One-shot prod deploy script (rsync compose files + `up -d`). |
|
|
21
|
+
|
|
22
|
+
## Local dev
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd deploy/logto
|
|
26
|
+
cp .env.example .env
|
|
27
|
+
# Set POSTGRES_PASSWORD (generate with: openssl rand -hex 32).
|
|
28
|
+
# LOGTO_ENDPOINT/ADMIN/DOMAIN aren't used by the local override — they
|
|
29
|
+
# only matter for prod.
|
|
30
|
+
|
|
31
|
+
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d
|
|
32
|
+
|
|
33
|
+
# Wait for boot, then run the end-to-end compatibility check:
|
|
34
|
+
py verify.py
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Admin console is at http://localhost:3002 — the first visit to that URL
|
|
38
|
+
becomes the root admin. Close the browser tab after creating the account;
|
|
39
|
+
no other "who is the admin" gate exists.
|
|
40
|
+
|
|
41
|
+
Teardown (wipes the volume):
|
|
42
|
+
```bash
|
|
43
|
+
docker compose -f docker-compose.yml -f docker-compose.local.yml down -v
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Production deploy
|
|
47
|
+
|
|
48
|
+
First-time server prep (once):
|
|
49
|
+
|
|
50
|
+
1. **DNS**: A record `auth.example.com -> <server IP>`.
|
|
51
|
+
2. **Nginx**: render the template with your domain, then enable + provision TLS:
|
|
52
|
+
```bash
|
|
53
|
+
sed 's/__DOMAIN__/auth.example.com/g' nginx-auth.conf.template \
|
|
54
|
+
| sudo tee /etc/nginx/sites-available/auth.example.com >/dev/null
|
|
55
|
+
sudo ln -s /etc/nginx/sites-available/auth.example.com \
|
|
56
|
+
/etc/nginx/sites-enabled/auth.example.com
|
|
57
|
+
sudo certbot --nginx -d auth.example.com
|
|
58
|
+
sudo ufw allow 8443/tcp # admin tenant port
|
|
59
|
+
```
|
|
60
|
+
3. **Docker**: install `docker` + `docker compose v2` on the server.
|
|
61
|
+
|
|
62
|
+
Then from your workstation:
|
|
63
|
+
```bash
|
|
64
|
+
export LOGTO_SERVER=root@1.2.3.4
|
|
65
|
+
export LOGTO_LIVE_URL=https://auth.example.com
|
|
66
|
+
export LOGTO_SSH_KEY=~/.ssh/id_rsa # optional — defaults to ~/.ssh/id_rsa
|
|
67
|
+
./deploy.sh
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The script rsyncs the compose files, generates `.env` if missing
|
|
71
|
+
(`LOGTO_DOMAIN/ENDPOINT/ADMIN_ENDPOINT/POSTGRES_PASSWORD`), pulls images,
|
|
72
|
+
brings the stack up, and smoke-tests the public URL.
|
|
73
|
+
|
|
74
|
+
## Bootstrapping apps in Logto
|
|
75
|
+
|
|
76
|
+
Once the stack is running and you've created the admin user, you can either
|
|
77
|
+
click through the admin console or run `bootstrap-apps.py` to create your
|
|
78
|
+
SPA + API resource + (optional) MCP-DCR M2M app idempotently.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cp bootstrap-apps.example.json bootstrap-apps.json # gitignored
|
|
82
|
+
# Edit bootstrap-apps.json with your products' redirect URIs, API resource,
|
|
83
|
+
# scopes, etc.
|
|
84
|
+
|
|
85
|
+
py bootstrap-apps.py \
|
|
86
|
+
--endpoint https://auth.example.com \
|
|
87
|
+
--admin https://auth.example.com:8443 \
|
|
88
|
+
--config bootstrap-apps.json \
|
|
89
|
+
--mgmt-secret "$(ssh root@1.2.3.4 'docker exec logto-docker-postgres-1 \
|
|
90
|
+
psql -U logto -d logto -t -A \
|
|
91
|
+
-c \"SELECT secret FROM applications WHERE id='\''m-default'\''\"')"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
It prints a paste-ready `.env` snippet (`LOGTO_APP_ID`, `MCP_LOGTO_APP_ID/SECRET`,
|
|
95
|
+
etc.) for each product at the end.
|
|
96
|
+
|
|
97
|
+
## mcp-core compatibility notes
|
|
98
|
+
|
|
99
|
+
Self-hosted Logto (OSS) is mostly drop-in compatible with Logto Cloud but
|
|
100
|
+
has a few surface differences that affect how mcp-core talks to it.
|
|
101
|
+
|
|
102
|
+
### Admin-tenant endpoint split
|
|
103
|
+
|
|
104
|
+
OSS runs two logical tenants in one process:
|
|
105
|
+
- **default** tenant (port 3001) — end-user sign-ins and app-scoped APIs.
|
|
106
|
+
- **admin** tenant (port 3002) — admin console + Management API auth.
|
|
107
|
+
|
|
108
|
+
Management-API M2M credentials (like the seeded `m-default` app) must be
|
|
109
|
+
exchanged for tokens at the **admin** OIDC endpoint, not the user-facing
|
|
110
|
+
one. This differs from Cloud, where everything lives on a single host.
|
|
111
|
+
|
|
112
|
+
`LogtoDCR` accepts an optional `mgmt_token_endpoint` parameter to handle
|
|
113
|
+
this:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from mcp_core.dcr import LogtoDCR
|
|
117
|
+
|
|
118
|
+
dcr = LogtoDCR(
|
|
119
|
+
logto_endpoint="https://auth.example.com",
|
|
120
|
+
mgmt_app_id="<m2m-app-id>",
|
|
121
|
+
mgmt_app_secret="<m2m-app-secret>",
|
|
122
|
+
mgmt_api_resource="https://default.logto.app/api",
|
|
123
|
+
# OSS-only: Management tokens are issued by the admin tenant.
|
|
124
|
+
mgmt_token_endpoint="https://auth.example.com:8443/oidc/token",
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Admin must live on its own origin (not a path prefix)
|
|
129
|
+
|
|
130
|
+
The admin SPA in Logto OSS hardcodes its API fetches as `/api/*` relative
|
|
131
|
+
to the origin root — it does NOT use a base path derived from
|
|
132
|
+
`ADMIN_ENDPOINT`. If you serve admin at `auth.example.com/admin`, the
|
|
133
|
+
browser's `/api/*` calls land on the *user* tenant and fail.
|
|
134
|
+
|
|
135
|
+
Workable separations, cleanest first:
|
|
136
|
+
- **Different port on the same host** (what this repo uses):
|
|
137
|
+
`ENDPOINT=https://auth.example.com` and
|
|
138
|
+
`ADMIN_ENDPOINT=https://auth.example.com:8443`. Same cert, no new DNS,
|
|
139
|
+
open one firewall port.
|
|
140
|
+
- **Subdomain**: `admin-auth.example.com`. Needs a DNS record + cert.
|
|
141
|
+
|
|
142
|
+
The port-based setup only requires the nginx vhost to have a second
|
|
143
|
+
`server { listen 8443 ssl; ... }` block that proxies to the admin
|
|
144
|
+
container port. No path rewrites, no Host spoofing.
|
|
145
|
+
|
|
146
|
+
When `mgmt_token_endpoint` is unset (default), `LogtoDCR` uses
|
|
147
|
+
`f"{endpoint}/oidc/token"` — the Cloud behavior — so existing deployments
|
|
148
|
+
see no regression.
|
|
149
|
+
|
|
150
|
+
### ADMIN_ENDPOINT must be container-reachable
|
|
151
|
+
|
|
152
|
+
Logto performs service-to-service JWKS fetches using `ADMIN_ENDPOINT`
|
|
153
|
+
verbatim (see `koa-auth.getAdminTenantTokenValidationSet`). If the container
|
|
154
|
+
cannot resolve/connect to that URL from *inside itself*, any Management-API
|
|
155
|
+
request 500s with `ECONNREFUSED`.
|
|
156
|
+
|
|
157
|
+
Two ways this manifests:
|
|
158
|
+
- **Local dev:** using port remaps like `3012:3002` makes the container's
|
|
159
|
+
`localhost:3012` unreachable from inside the container. Fix: the local
|
|
160
|
+
override binds `3001:3001`/`3002:3002` 1:1 so `localhost:3001/3002`
|
|
161
|
+
resolves correctly from both host and container.
|
|
162
|
+
- **Prod:** the public domain resolves publicly, but the prod override adds
|
|
163
|
+
`extra_hosts: - "${LOGTO_DOMAIN}:host-gateway"` to keep the internal
|
|
164
|
+
round-trip local (container -> host nginx -> container) instead of
|
|
165
|
+
depending on external DNS + egress.
|
|
166
|
+
|
|
167
|
+
### Registration endpoint never advertised
|
|
168
|
+
|
|
169
|
+
Logto (both Cloud and self-host) does **not** return `registration_endpoint`
|
|
170
|
+
in `/oidc/.well-known/openid-configuration`, and `/oidc/register` 404s.
|
|
171
|
+
mcp-core's `install_routes()` fills this gap by mounting `/oauth/register`
|
|
172
|
+
and advertising it in the proxied metadata doc — works identically on
|
|
173
|
+
self-host.
|
|
174
|
+
|
|
175
|
+
### Management API resource
|
|
176
|
+
|
|
177
|
+
For the single OSS tenant named `default`, the Management API resource
|
|
178
|
+
indicator is `https://default.logto.app/api`. This is a logical string
|
|
179
|
+
(not a reachable URL) — it's only used as the `resource`/`audience` value.
|
|
180
|
+
|
|
181
|
+
## Verify compatibility
|
|
182
|
+
|
|
183
|
+
`verify.py` is the source of truth for "does self-hosted Logto still work
|
|
184
|
+
with mcp-core". It covers:
|
|
185
|
+
|
|
186
|
+
- OIDC discovery (issuer, jwks_uri, authorize/token endpoints)
|
|
187
|
+
- JWKS format + supported JWT algorithms
|
|
188
|
+
- Management-API token exchange via admin tenant
|
|
189
|
+
- `LogtoDCR.register()` end-to-end creating a Native/PKCE app
|
|
190
|
+
- JWT signed by Logto validated by `LogtoAuth._get_jwks_client()`
|
|
191
|
+
|
|
192
|
+
Run it after any Logto version bump:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
py verify.py # local compose
|
|
196
|
+
py verify.py --endpoint https://auth.example.com \
|
|
197
|
+
--admin https://auth.example.com:8443 \
|
|
198
|
+
--mgmt-secret <from-logto-admin-console>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Post-deploy config (admin console)
|
|
202
|
+
|
|
203
|
+
Either run `bootstrap-apps.py` (above) or do these once the stack is up:
|
|
204
|
+
|
|
205
|
+
1. **SPA apps** — one per frontend. Note the client IDs.
|
|
206
|
+
2. **API resources** — e.g. `https://app.example.com`. Add the scopes the product uses.
|
|
207
|
+
3. **M2M app for DCR** — one per backend that needs Dynamic Client
|
|
208
|
+
Registration. Grant it the "Management API access for default" role.
|
|
209
|
+
4. **Google social connector** — create a Google Cloud OAuth 2.0 client,
|
|
210
|
+
redirect URI `https://auth.example.com/callback/<connector-id>`.
|
|
211
|
+
5. **SMTP / SES email connector** — see `provision-ses.sh` for AWS SES.
|
|
212
|
+
6. **Sign-in experience** — enable email + password, plus any social connectors.
|
|
213
|
+
|
|
214
|
+
Then update each product's frontend + backend `.env`:
|
|
215
|
+
- Frontend: `LOGTO_CONFIG.endpoint = 'https://auth.example.com'`
|
|
216
|
+
- Backend: `LOGTO_ENDPOINT=https://auth.example.com`,
|
|
217
|
+
`LOGTO_API_RESOURCE=...`, `LOGTO_DCR_MGMT_APP_ID/SECRET`, and
|
|
218
|
+
`LOGTO_DCR_MGMT_TOKEN_ENDPOINT=https://auth.example.com:8443/oidc/token`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Example product config for bootstrap-apps.py. Copy to bootstrap-apps.json (gitignored) and edit. One entry per product (frontend + API + optional MCP-DCR M2M app).",
|
|
3
|
+
"products": [
|
|
4
|
+
{
|
|
5
|
+
"name": "myapp",
|
|
6
|
+
"spa_app_name": "MyApp (frontend)",
|
|
7
|
+
"redirect_uris": [
|
|
8
|
+
"http://localhost:5173/callback",
|
|
9
|
+
"https://app.example.com/callback"
|
|
10
|
+
],
|
|
11
|
+
"post_logout_uris": [
|
|
12
|
+
"http://localhost:5173",
|
|
13
|
+
"https://app.example.com"
|
|
14
|
+
],
|
|
15
|
+
"api_resource": "https://app.example.com",
|
|
16
|
+
"api_resource_name": "MyApp API",
|
|
17
|
+
"scopes": ["myapp:read", "myapp:write"],
|
|
18
|
+
"mcp_dcr_app_name": "MyApp MCP-DCR"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|