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.
Files changed (53) hide show
  1. mcp_core_auth-0.2.1/.env.example +45 -0
  2. mcp_core_auth-0.2.1/.gitignore +18 -0
  3. mcp_core_auth-0.2.1/CONTRIBUTING.md +42 -0
  4. mcp_core_auth-0.2.1/LICENSE +21 -0
  5. mcp_core_auth-0.2.1/PKG-INFO +146 -0
  6. mcp_core_auth-0.2.1/README.md +113 -0
  7. mcp_core_auth-0.2.1/SECURITY.md +34 -0
  8. mcp_core_auth-0.2.1/deploy/logto/.env.example +14 -0
  9. mcp_core_auth-0.2.1/deploy/logto/README.md +218 -0
  10. mcp_core_auth-0.2.1/deploy/logto/bootstrap-apps.example.json +21 -0
  11. mcp_core_auth-0.2.1/deploy/logto/bootstrap-apps.py +330 -0
  12. mcp_core_auth-0.2.1/deploy/logto/deploy.sh +88 -0
  13. mcp_core_auth-0.2.1/deploy/logto/docker-compose.local.yml +30 -0
  14. mcp_core_auth-0.2.1/deploy/logto/docker-compose.prod.yml +33 -0
  15. mcp_core_auth-0.2.1/deploy/logto/docker-compose.yml +34 -0
  16. mcp_core_auth-0.2.1/deploy/logto/nginx-auth.conf.template +77 -0
  17. mcp_core_auth-0.2.1/deploy/logto/provision-ses.sh +75 -0
  18. mcp_core_auth-0.2.1/deploy/logto/verify.py +270 -0
  19. mcp_core_auth-0.2.1/docs/architecture.png +0 -0
  20. mcp_core_auth-0.2.1/docs/fastmcp-migration.md +185 -0
  21. mcp_core_auth-0.2.1/docs/integration-guide.md +859 -0
  22. mcp_core_auth-0.2.1/docs/oauth-mcp-integration.md +316 -0
  23. mcp_core_auth-0.2.1/examples/minimal_server/.env.example +23 -0
  24. mcp_core_auth-0.2.1/examples/minimal_server/main.py +55 -0
  25. mcp_core_auth-0.2.1/examples/minimal_server/mcp_tools.py +53 -0
  26. mcp_core_auth-0.2.1/examples/minimal_server/requirements.txt +3 -0
  27. mcp_core_auth-0.2.1/pyproject.toml +54 -0
  28. mcp_core_auth-0.2.1/src/mcp_core/__init__.py +324 -0
  29. mcp_core_auth-0.2.1/src/mcp_core/auth.py +235 -0
  30. mcp_core_auth-0.2.1/src/mcp_core/billing.py +269 -0
  31. mcp_core_auth-0.2.1/src/mcp_core/dcr.py +185 -0
  32. mcp_core_auth-0.2.1/src/mcp_core/health.py +71 -0
  33. mcp_core_auth-0.2.1/src/mcp_core/mcp_mount.py +240 -0
  34. mcp_core_auth-0.2.1/src/mcp_core/py.typed +0 -0
  35. mcp_core_auth-0.2.1/src/mcp_core/routes.py +251 -0
  36. mcp_core_auth-0.2.1/src/mcp_core/tool_logging.py +68 -0
  37. mcp_core_auth-0.2.1/tests/.env.live.example +20 -0
  38. mcp_core_auth-0.2.1/tests/conftest.py +239 -0
  39. mcp_core_auth-0.2.1/tests/live/__init__.py +0 -0
  40. mcp_core_auth-0.2.1/tests/live/conftest.py +120 -0
  41. mcp_core_auth-0.2.1/tests/live/test_live_auth.py +106 -0
  42. mcp_core_auth-0.2.1/tests/live/test_live_billing.py +100 -0
  43. mcp_core_auth-0.2.1/tests/live/test_live_health.py +42 -0
  44. mcp_core_auth-0.2.1/tests/live/test_live_integration.py +128 -0
  45. mcp_core_auth-0.2.1/tests/live/test_live_logging.py +54 -0
  46. mcp_core_auth-0.2.1/tests/test_auth.py +289 -0
  47. mcp_core_auth-0.2.1/tests/test_billing.py +282 -0
  48. mcp_core_auth-0.2.1/tests/test_branded_sign_in.py +168 -0
  49. mcp_core_auth-0.2.1/tests/test_dcr.py +190 -0
  50. mcp_core_auth-0.2.1/tests/test_health.py +81 -0
  51. mcp_core_auth-0.2.1/tests/test_integration.py +145 -0
  52. mcp_core_auth-0.2.1/tests/test_logging.py +94 -0
  53. 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,18 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .eggs/
7
+ *.egg
8
+ .venv/
9
+ venv/
10
+ .env
11
+ .env.live
12
+ tests/.env.live
13
+ deploy/logto/bootstrap-apps.json
14
+ .pytest_cache/
15
+ htmlcov/
16
+ .coverage
17
+ *.so
18
+ .mypy_cache/
@@ -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
+ [![Release](https://img.shields.io/github/v/release/swapp1990/mcp-core)](https://github.com/swapp1990/mcp-core/releases)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
38
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](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
+ [![Release](https://img.shields.io/github/v/release/swapp1990/mcp-core)](https://github.com/swapp1990/mcp-core/releases)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](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
+ }