-
Notifications
You must be signed in to change notification settings - Fork 11
Content: Added Multi-Tenant MCP Example and Cleaned Up Vercel Next.js Example #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # Python | ||
| __pycache__/ | ||
| *.py[cod] | ||
| *$py.class | ||
| *.so | ||
| .Python | ||
| build/ | ||
| develop-eggs/ | ||
| dist/ | ||
| downloads/ | ||
| eggs/ | ||
| .eggs/ | ||
| lib/ | ||
| lib64/ | ||
| parts/ | ||
| sdist/ | ||
| var/ | ||
| wheels/ | ||
| *.egg-info/ | ||
| .installed.cfg | ||
| *.egg | ||
|
|
||
| # Virtual environments | ||
| .env | ||
| .venv | ||
| env/ | ||
| venv/ | ||
| ENV/ | ||
| env.bak/ | ||
| venv.bak/ | ||
|
|
||
| # IDE | ||
| .vscode/ | ||
| .idea/ | ||
| *.swp | ||
| *.swo | ||
| *~ | ||
|
|
||
| # OS | ||
| .DS_Store | ||
| .DS_Store? | ||
| ._* | ||
| .Spotlight-V100 | ||
| .Trashes | ||
| ehthumbs.db | ||
| Thumbs.db | ||
|
|
||
| # Git | ||
| .git/ | ||
| .gitignore | ||
|
|
||
| # Docker | ||
| Dockerfile | ||
| .dockerignore | ||
|
|
||
| # Logs | ||
| *.log | ||
|
|
||
| # Documentation | ||
| README.md |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Descope MCP / OAuth — use DESCOPE_CONFIG_URL from your MCP server well-known URL, OR legacy project_id + DESCOPE_BASE_URL below. | ||
| DESCOPE_PROJECT_ID=P2ygkdk1hUDj1H7xh6flFxo5lMWs | ||
|
|
||
| # MCP server issuer (recommended) | ||
| DESCOPE_CONFIG_URL=https://api.descope.com/v1/apps/agentic/P2ygkdk1hUDj1H7xh6flFxo5lMWs/MS39o0PTWlxeDdTqeCB0ZgRuuMCiQ/.well-known/openid-configuration | ||
|
|
||
| # Legacy (used only when DESCOPE_CONFIG_URL is unset) | ||
| DESCOPE_BASE_URL=https://api.descope.com | ||
|
|
||
| # Public URL of this MCP server (OAuth metadata) | ||
| SERVER_URL=http://localhost:3000 | ||
|
|
||
| # Required for switch_tenant: persist custom attribute currentTenant via Management API | ||
| DESCOPE_MANAGEMENT_KEY=K3CXoCreHLJdnnx6POIrZUSNOFPB8iQTp2zzR9Y4IOUYBA0kmrlEqnMPjhIAtxTSn5EGzFX |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Descope MCP / OAuth — use DESCOPE_CONFIG_URL from your MCP server well-known URL, OR legacy project_id + DESCOPE_BASE_URL below. | ||
| DESCOPE_PROJECT_ID= | ||
|
|
||
| # MCP server issuer / OpenID discovery (recommended) | ||
| DESCOPE_CONFIG_URL= | ||
|
|
||
| # Legacy (used only when DESCOPE_CONFIG_URL is unset) | ||
| DESCOPE_BASE_URL=https://api.descope.com | ||
|
|
||
| # Public URL of this MCP server (OAuth metadata) | ||
| SERVER_URL=http://localhost:3000 | ||
|
|
||
| # Required for switch_tenant: persist custom attribute currentTenant via Management API | ||
| DESCOPE_MANAGEMENT_KEY= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| .env | ||
| dist/ | ||
| node_modules/ | ||
| .vercel | ||
| .next | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| FROM python:3.12-slim | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing User Instruction More DetailsThis rule checks whether a `USER` instruction is specified in the Dockerfile. The rule fails when the `USER` instruction is missing, causing the container to run with root privileges (UID 0). If an attacker compromises an application running as root, they gain the privileges needed to potentially escape the container and attack the host node. It also increases the blast radius of a breach, allowing full control to modify files or install malware within the container. Enforcing a non-root user is a fundamental security measure that minimizes the attack surface and contains the impact of a potential compromise. Expected Found
To ignore this finding as an exception, reply to this conversation with If you'd like to ignore this finding in all future scans, add an exception in the .wiz file (learn more) or create an Ignore Rule (learn more). To get more details on how to remediate this issue using AI, reply to this conversation with |
||
|
|
||
| # Set working directory | ||
| WORKDIR /app | ||
|
|
||
| # Install system dependencies | ||
| RUN apt-get update && apt-get install -y \ | ||
| gcc \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # Copy requirements first for better caching | ||
| COPY pyproject.toml requirements.txt ./ | ||
|
|
||
| # Install Python dependencies | ||
| RUN pip install --no-cache-dir -r requirements.txt | ||
|
|
||
| # Copy application code | ||
| COPY . . | ||
|
|
||
| # Expose the port the app runs on | ||
| EXPOSE 3000 | ||
|
|
||
| # Set environment variables | ||
| ENV PYTHONPATH=/app | ||
| ENV PYTHONUNBUFFERED=1 | ||
|
|
||
| # Health check | ||
| HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ | ||
| CMD curl -f http://localhost:3000/health || exit 1 | ||
|
|
||
| # Run the application | ||
| CMD ["python", "server.py"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # Multi-tenant MCP Server (FastMCP + Descope) | ||
|
|
||
|  | ||
|
|
||
| ## Introduction | ||
|
|
||
| This example demonstrates **tenant-scoped MCP tools** with Descope: | ||
|
|
||
| - The **`switch_tenant`** tool validates the tenant with **`mgmt.tenant.load`** (and session settings for SSO), then persists the active tenant id in the user’s **`currentTenant`** custom attribute using **`mgmt.user.patch`**. | ||
| - **Tenant-specific tools** are tagged for `tenant-a` vs `tenant-b`; on **every tool execution** the server calls Descope **`GET …/oauth2/v1/userinfo`** with the caller’s bearer token and verifies that `currentTenant` matches the tool’s tenant before running business logic. | ||
|
|
||
| ## Prerequisites (Descope Console) | ||
|
|
||
| 1. Create an **[MCP Server](https://docs.descope.com/agentic-identity-hub/mcp-servers/settings)** in Descope and note **Well-Known URL** (`DESCOPE_CONFIG_URL`) — or use legacy `DESCOPE_PROJECT_ID` + `DESCOPE_BASE_URL` for local dev. | ||
| 2. Define a **custom attribute** named **`currentTenant`** on users (same key used in Management API patches). | ||
| 3. Generate a **[Management Key](https://docs.descope.com/settings/project-management)** with permission to update users (`DESCOPE_MANAGEMENT_KEY`). Required only for **`switch_tenant`**. | ||
| 4. Ensure MCP clients request scopes that include **`descope.custom_claims`** (and related OIDC scopes) so **UserInfo** returns custom attributes. | ||
|
|
||
| ## Requirements | ||
|
|
||
| - Python 3.12+ | ||
| - Dependencies in `pyproject.toml` (`uv sync`) | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### 1. Clone and enter this example | ||
|
|
||
| ```bash | ||
| git clone https://github.com/descope/ai.git | ||
| cd ai/examples/multi-tenant-mcp | ||
| ``` | ||
|
|
||
| ### 2. Environment variables | ||
|
|
||
| Copy `.env.example` to `.env` and fill in: | ||
|
|
||
| | Variable | Purpose | | ||
| |----------|---------| | ||
| | `DESCOPE_PROJECT_ID` | Descope project id | | ||
| | `DESCOPE_CONFIG_URL` | MCP server Well-Known URL (recommended) | | ||
| | `DESCOPE_BASE_URL` | Legacy; used only if `DESCOPE_CONFIG_URL` is unset | | ||
| | `SERVER_URL` | Public base URL of this MCP server | | ||
| | `DESCOPE_MANAGEMENT_KEY` | Enables **`switch_tenant`** (updates `currentTenant`) | | ||
|
|
||
| ### 3. Install and run | ||
|
|
||
| ```bash | ||
| uv sync | ||
| uv run python server.py | ||
| ``` | ||
|
|
||
| Visit [http://localhost:3000](http://localhost:3000) for the landing page. | ||
|
|
||
| ## Tools | ||
|
|
||
| | Tool | Tenant | Description | | ||
| |------|--------|-------------| | ||
| | `switch_tenant` | *(any)* | Looks up tenant via Management API, rejects unknown tenants / SSO tenants, then sets `currentTenant` | | ||
| | `tenant_a_inventory_snapshot` | `tenant-a` | Dummy “inventory” tool | | ||
| | `tenant_b_metrics_ping` | `tenant-b` | Dummy metrics ping | | ||
| | `tenant_b_notes_echo` | `tenant-b` | Echoes a note string | | ||
|
|
||
| If UserInfo **`currentTenant`** does not match the tool’s tenant, the handler returns an error explaining to call **`switch_tenant`** first. | ||
|
|
||
| **`switch_tenant`** requires the tenant id to exist in Descope (`mgmt.tenant.load`). If **`enforceSSO`** is set, **`authType`** is SAML/OIDC, **SSO Setup Suite** is enabled, or **federated SSO app IDs** are configured, the tool returns an error instructing the user to sign in via SSO instead of switching from MCP. | ||
|
|
||
| ## Implementation notes | ||
|
|
||
| - **UserInfo URL:** `{DESCOPE_BASE_URL}/oauth2/v1/userinfo` with `Authorization: Bearer <access_token>`. | ||
| - **Login id for Management API:** Resolved from JWT claims (`email`, `loginIds`, etc.). | ||
| - Demo tenant ids are **`tenant-a`** and **`tenant-b`** (`ALLOWED_TENANTS` in `server.py`). Align these with real tenant ids if you attach users to Descope tenants. | ||
|
|
||
| ### FastMCP component visibility | ||
|
|
||
| Tools for **tenant-a** and **tenant-b** are tagged and **disabled globally** via `mcp.disable(...)`. Each MCP session **re-enables only the tools for the user’s current tenant** using `ctx.enable_components` / `ctx.disable_components` after reading UserInfo (`sync_session_tenant_visibility`), so clients typically **do not see** the other tenant’s tools. Call **`switch_tenant`** once after connecting so an existing Descope `currentTenant` is applied to the session’s tool list. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 HIGH: The
multi-tenant-mcpdirectory only contains.envand.gitignore— no actual example code. The PR title says "Added Multi-Tenant MCP Example" but there's no implementation here (nopackage.json, no source files, no README). Is this intentionally just scaffolding, or was code missed from the commit?