diff --git a/docs/docs.json b/docs/docs.json
index 90aea2efd..8517207fd 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -172,6 +172,7 @@
"integrations/azure",
"integrations/descope",
"integrations/github",
+ "integrations/scalekit",
"integrations/google",
"integrations/workos"
]
diff --git a/docs/integrations/scalekit.mdx b/docs/integrations/scalekit.mdx
new file mode 100644
index 000000000..89de64ef1
--- /dev/null
+++ b/docs/integrations/scalekit.mdx
@@ -0,0 +1,187 @@
+---
+title: Scalekit 🤝 FastMCP
+sidebarTitle: Scalekit
+description: Secure your FastMCP server with Scalekit
+icon: shield-check
+tag: NEW
+---
+
+import { VersionBadge } from "/snippets/version-badge.mdx"
+
+
+
+
+Install auth stack to your FastMCP server with [Scalekit](https://scalekit.com) using the [Remote OAuth](/servers/auth/remote-oauth) pattern: Scalekit handles user authentication, and the MCP server validates issued tokens.
+
+## Configuration
+
+### Prerequisites
+
+Before you begin
+
+1. Get a [Scalekit account](https://app.scalekit.com/) and grab API credentials such as **Client ID**, **Client Secret** and **Environment URL** from _Dashboard > Developers > Settings_.
+2. Have your FastMCP server's endpoint ready (can be localhost for development, e.g., `http://localhost:8000/mcp`)
+
+### Step 1: Configure MCP server in Scalekit environment
+
+
+
+
+In your Scalekit dashboard:
+ 1. Open the **MCP Servers** section, then select **Create new server**
+ 2. Enter server details: a name, a resource identifier, and the desired MCP client authentication settings
+ 3. Save, then copy the **Resource ID** (for example, res_92015146095)
+
+In your FastMCP project's `.env`:
+
+```sh
+SCALEKIT_ENVIRONMENT_URL=
+SCALEKIT_CLIENT_ID= # skc_7008EXAMPLE46
+SCALEKIT_RESOURCE_ID= # res_926EXAMPLE5878
+MCP_URL=http://localhost:8000/mcp
+```
+
+
+
+
+### Step 2: Add auth to FastMCP server
+
+Create your FastMCP server file and use the ScalekitProvider to handle all the OAuth integration automatically:
+
+```python server.py
+from fastmcp import FastMCP
+from fastmcp.server.auth.providers.scalekit import ScalekitProvider
+
+# Discovers Scalekit endpoints and set up JWT token validation
+auth_provider = ScalekitProvider(
+ environment_url=SCALEKIT_ENVIRONMENT_URL, # Scalekit environment URL
+ client_id=SCALEKIT_CLIENT_ID, # OAuth client ID
+ resource_id=SCALEKIT_RESOURCE_ID, # Resource server ID
+ mcp_url=SERVER_URL, # Is also aud claim
+)
+
+# Create FastMCP server with auth
+mcp = FastMCP(name="My Scalekit Protected Server", auth=auth_provider)
+
+@mcp.tool
+def auth_status() -> dict:
+ """Show Scalekit authentication status."""
+ # Extract user claims from the JWT
+ return {
+ "message": "This tool requires authentication via Scalekit",
+ "authenticated": True,
+ "provider": "Scalekit"
+ }
+
+```
+
+## Testing
+
+### Start the MCP server
+
+```sh
+uv run python server.py
+```
+
+Use any MCP client (for example, mcp-inspector, Claude, VS Code, or Windsurf) to connect to the running serve. Verify that authentication succeeds and requests are authorized as expected.
+
+### Provider selection
+
+Setting this environment variable allows the Scalekit provider to be used automatically without explicitly instantiating it in code.
+
+
+
+Set to `fastmcp.server.auth.providers.scalekit.ScalekitProvider` to use Scalekit authentication.
+
+
+
+### Scalekit-specific configuration
+
+These environment variables provide default values for the Scalekit provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
+
+
+
+Your Scalekit environment URL from the Admin Portal (e.g., `https://your-env.scalekit.com`)
+
+
+
+Your Scalekit OAuth application client ID from the Applications section
+
+
+
+Your Scalekit resource server ID from the Resources section
+
+
+
+Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000/mcp` for development)
+
+
+
+Example `.env`:
+
+```bash
+# Use the Scalekit provider
+FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.scalekit.ScalekitProvider
+
+# Scalekit configuration
+FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_ENVIRONMENT_URL=https://your-env.scalekit.com
+FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_CLIENT_ID=skc_123
+FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_RESOURCE_ID=res_456
+FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL=https://your-server.com/mcp
+```
+
+With environment variables set, your server code simplifies to:
+
+```python server.py
+from fastmcp import FastMCP
+
+# Authentication is automatically configured from environment
+mcp = FastMCP(name="My Scalekit Protected Server")
+
+@mcp.tool
+def protected_action() -> str:
+ """A tool that requires authentication."""
+ return "Access granted via Scalekit!"
+```
+
+## Capabilities
+
+Scalekit supports OAuth 2.1 with Dynamic Client Registration for MCP clients and enterprise SSO, and provides built‑in JWT validation and security controls.
+
+**OAuth 2.1/DCR**: clients self‑register, use PKCE, and work with the Remote OAuth pattern without pre‑provisioned credentials.
+
+**Validation and SSO**: tokens are verified (keys, RS256, issuer, audience, expiry), and SAML, OIDC, OAuth 2.0, ADFS, Azure AD, and Google Workspace are supported; use HTTPS in production and review auth logs as needed.
+
+## Debugging
+
+Enable detailed logging to troubleshoot authentication issues:
+
+```python
+import logging
+logging.basicConfig(level=logging.DEBUG)
+```
+
+### Token inspection
+
+You can inspect JWT tokens in your tools to understand the user context:
+
+```python
+from fastmcp.server.context import request_ctx
+import jwt
+
+@mcp.tool
+def inspect_token() -> dict:
+ """Inspect the current JWT token claims."""
+ context = request_ctx.get()
+
+ # Extract token from Authorization header
+ if hasattr(context, 'request') and hasattr(context.request, 'headers'):
+ auth_header = context.request.headers.get('authorization', '')
+ if auth_header.startswith('Bearer '):
+ token = auth_header[7:]
+ # Decode without verification (already verified by provider)
+ claims = jwt.decode(token, options={"verify_signature": False})
+ return claims
+
+ return {"error": "No token found"}
+```
diff --git a/examples/auth/scalekit_oauth/README.md b/examples/auth/scalekit_oauth/README.md
new file mode 100644
index 000000000..63c555244
--- /dev/null
+++ b/examples/auth/scalekit_oauth/README.md
@@ -0,0 +1,54 @@
+# Scalekit OAuth Example
+
+Demonstrates FastMCP server protection with Scalekit OAuth.
+
+## Setup
+
+### 1. Configure MCP server in Scalekit environment
+
+**Create a Scalekit Account**:
+
+- Go to [Scalekit Dashboard](https://app.scalekit.com/)
+- Navigate to **Developers** → **Settings**
+- Copy your Environment URL, Client ID, and Client Secret
+
+**Register Your MCP Server**:
+
+- Go to **MCP Servers** → **Create New Server**
+- Fill in your MCP server details
+- Note the **Resource ID** (e.g., `res_123`)
+
+Create a `.env` file:
+
+```bash
+# Required Scalekit credentials
+SCALEKIT_ENVIRONMENT_URL=
+SCALEKIT_CLIENT_ID= # skc_7008EXAMPLE46
+SCALEKIT_RESOURCE_ID= # res_926EXAMPLE5878
+MCP_URL=http://localhost:8000/mcp
+```
+
+### 2. Run the Example
+
+Start the server:
+
+```bash
+# From this directory
+uv run python server.py
+```
+
+The server will start on `http://localhost:8000/mcp` with Scalekit OAuth authentication enabled.
+
+Test with client:
+
+```bash
+uv run python client.py
+```
+
+The `client.py` will:
+
+1. Attempt to connect to the server
+2. Detect that OAuth authentication is required
+3. Open a browser for Scalekit authentication
+4. Complete the OAuth flow and connect to the server
+5. Demonstrate calling authenticated tools
diff --git a/examples/auth/scalekit_oauth/client.py b/examples/auth/scalekit_oauth/client.py
new file mode 100644
index 000000000..4146b2c49
--- /dev/null
+++ b/examples/auth/scalekit_oauth/client.py
@@ -0,0 +1,41 @@
+"""OAuth client example for connecting to Scalekit-protected FastMCP servers.
+
+This example demonstrates how to connect to a Scalekit OAuth-protected FastMCP server.
+
+To run:
+ python client.py
+"""
+
+import asyncio
+
+from fastmcp.client import Client
+
+SERVER_URL = "http://127.0.0.1:8000/mcp"
+
+
+async def main():
+ try:
+ async with Client(SERVER_URL, auth="oauth") as client:
+ assert await client.ping()
+ print("âś… Successfully authenticated with Scalekit!")
+
+ tools = await client.list_tools()
+ print(f"đź”§ Available tools ({len(tools)}):")
+ for tool in tools:
+ print(f" - {tool.name}: {tool.description}")
+
+ # Test calling a tool
+ result = await client.call_tool("echo", {"message": "Hello from Scalekit!"})
+ print(f"🎯 Echo result: {result}")
+
+ # Test calling auth status tool
+ auth_status = await client.call_tool("auth_status", {})
+ print(f"👤 Auth status: {auth_status}")
+
+ except Exception as e:
+ print(f"❌ Authentication failed: {e}")
+ raise
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/examples/auth/scalekit_oauth/server.py b/examples/auth/scalekit_oauth/server.py
new file mode 100644
index 000000000..40da711e2
--- /dev/null
+++ b/examples/auth/scalekit_oauth/server.py
@@ -0,0 +1,48 @@
+"""Scalekit OAuth server example for FastMCP.
+
+This example demonstrates how to protect a FastMCP server with Scalekit OAuth.
+
+Required environment variables:
+- SCALEKIT_ENVIRONMENT_URL: Your Scalekit environment URL (e.g., "https://your-env.scalekit.com")
+- SCALEKIT_CLIENT_ID: Your Scalekit OAuth application client ID
+- SCALEKIT_RESOURCE_ID: Your Scalekit resource ID
+
+To run:
+ python server.py
+"""
+
+import os
+
+from fastmcp import FastMCP
+from fastmcp.server.auth.providers.scalekit import ScalekitProvider
+
+auth = ScalekitProvider(
+ environment_url=os.getenv("SCALEKIT_ENVIRONMENT_URL")
+ or "https://your-env.scalekit.com",
+ client_id=os.getenv("SCALEKIT_CLIENT_ID") or "",
+ resource_id=os.getenv("SCALEKIT_RESOURCE_ID") or "",
+ mcp_url=os.getenv("MCP_URL", "http://localhost:8000/mcp"),
+)
+
+mcp = FastMCP("Scalekit OAuth Example Server", auth=auth)
+
+
+@mcp.tool
+def echo(message: str) -> str:
+ """Echo the provided message."""
+ return message
+
+
+@mcp.tool
+def auth_status() -> dict:
+ """Show Scalekit authentication status."""
+ # In a real implementation, you would extract user info from the JWT token
+ return {
+ "message": "This tool requires authentication via Scalekit",
+ "authenticated": True,
+ "provider": "Scalekit",
+ }
+
+
+if __name__ == "__main__":
+ mcp.run(transport="http", port=8000)
diff --git a/src/fastmcp/server/auth/providers/scalekit.py b/src/fastmcp/server/auth/providers/scalekit.py
new file mode 100644
index 000000000..8c6a0a79d
--- /dev/null
+++ b/src/fastmcp/server/auth/providers/scalekit.py
@@ -0,0 +1,181 @@
+"""Scalekit authentication provider for FastMCP.
+
+This module provides ScalekitProvider - a complete authentication solution that integrates
+with Scalekit's OAuth 2.1 and OpenID Connect services, supporting Resource Server
+authentication for seamless MCP client authentication.
+"""
+
+from __future__ import annotations
+
+from typing import Any
+
+import httpx
+from pydantic import AnyHttpUrl
+from pydantic_settings import BaseSettings, SettingsConfigDict
+from starlette.responses import JSONResponse
+from starlette.routing import Route
+
+from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
+from fastmcp.server.auth.providers.jwt import JWTVerifier
+from fastmcp.utilities.logging import get_logger
+from fastmcp.utilities.types import NotSet, NotSetT
+
+logger = get_logger(__name__)
+
+
+class ScalekitProviderSettings(BaseSettings):
+ model_config = SettingsConfigDict(
+ env_prefix="FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_",
+ env_file=".env",
+ extra="ignore",
+ )
+
+ environment_url: AnyHttpUrl
+ client_id: str
+ resource_id: str
+ mcp_url: AnyHttpUrl
+
+
+class ScalekitProvider(RemoteAuthProvider):
+ """Scalekit resource server provider for OAuth 2.1 authentication.
+
+ This provider implements Scalekit integration using resource server pattern.
+ FastMCP acts as a protected resource server that validates access tokens issued
+ by Scalekit's authorization server.
+
+ IMPORTANT SETUP REQUIREMENTS:
+
+ 1. Create an MCP Server in Scalekit Dashboard:
+ - Go to your [Scalekit Dashboard](https://app.scalekit.com/)
+ - Navigate to MCP Servers section
+ - Register a new MCP Server with appropriate scopes
+ - Ensure the Resource Identifier matches exactly what you configure as MCP URL
+ - Note the Resource ID
+
+ 2. Environment Configuration:
+ - Set SCALEKIT_ENVIRONMENT_URL (e.g., https://your-env.scalekit.com)
+ - Set SCALEKIT_CLIENT_ID from your OAuth application
+ - Set SCALEKIT_RESOURCE_ID from your created resource
+ - Set MCP_URL to your FastMCP server's public URL
+
+ For detailed setup instructions, see:
+ https://docs.scalekit.com/mcp/overview/
+
+ Example:
+ ```python
+ from fastmcp.server.auth.providers.scalekit import ScalekitProvider
+
+ # Create Scalekit resource server provider
+ scalekit_auth = ScalekitProvider(
+ environment_url="https://your-env.scalekit.com",
+ client_id="sk_client_...",
+ resource_id="sk_resource_...",
+ mcp_url="https://your-fastmcp-server.com",
+ )
+
+ # Use with FastMCP
+ mcp = FastMCP("My App", auth=scalekit_auth)
+ ```
+ """
+
+ def __init__(
+ self,
+ *,
+ environment_url: AnyHttpUrl | str | NotSetT = NotSet,
+ client_id: str | NotSetT = NotSet,
+ resource_id: str | NotSetT = NotSet,
+ mcp_url: AnyHttpUrl | str | NotSetT = NotSet,
+ token_verifier: TokenVerifier | None = None,
+ ):
+ """Initialize Scalekit resource server provider.
+
+ Args:
+ environment_url: Your Scalekit environment URL (e.g., "https://your-env.scalekit.com")
+ client_id: Your Scalekit OAuth client ID
+ resource_id: Your Scalekit resource ID
+ mcp_url: Public URL of this FastMCP server (used as audience)
+ token_verifier: Optional token verifier. If None, creates JWT verifier for Scalekit
+ """
+ settings = ScalekitProviderSettings.model_validate(
+ {
+ k: v
+ for k, v in {
+ "environment_url": environment_url,
+ "client_id": client_id,
+ "resource_id": resource_id,
+ "mcp_url": mcp_url,
+ }.items()
+ if v is not NotSet
+ }
+ )
+
+ self.environment_url = str(settings.environment_url).rstrip("/")
+ self.client_id = settings.client_id
+ self.resource_id = settings.resource_id
+ self.mcp_url = str(settings.mcp_url)
+
+ # Create default JWT verifier if none provided
+ if token_verifier is None:
+ token_verifier = JWTVerifier(
+ jwks_uri=f"{self.environment_url}/keys",
+ issuer=self.environment_url,
+ algorithm="RS256",
+ audience=self.mcp_url,
+ )
+
+ # Initialize RemoteAuthProvider with Scalekit as the authorization server
+ super().__init__(
+ token_verifier=token_verifier,
+ authorization_servers=[
+ AnyHttpUrl(f"{self.environment_url}/resources/{self.resource_id}")
+ ],
+ base_url=self.mcp_url,
+ )
+
+ def get_routes(
+ self,
+ mcp_path: str | None = None,
+ mcp_endpoint: Any | None = None,
+ ) -> list[Route]:
+ """Get OAuth routes including Scalekit authorization server metadata forwarding.
+
+ This returns the standard protected resource routes plus an authorization server
+ metadata endpoint that forwards Scalekit's OAuth metadata to clients.
+
+ Args:
+ mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
+ mcp_endpoint: The MCP endpoint handler to protect with auth
+ """
+ # Get the standard protected resource routes from RemoteAuthProvider
+ routes = super().get_routes(mcp_path, mcp_endpoint)
+
+ async def oauth_authorization_server_metadata(request):
+ """Forward Scalekit OAuth authorization server metadata with FastMCP customizations."""
+ try:
+ async with httpx.AsyncClient() as client:
+ response = await client.get(
+ f"{self.environment_url}/.well-known/oauth-authorization-server/resources/{self.resource_id}"
+ )
+ response.raise_for_status()
+ metadata = response.json()
+ return JSONResponse(metadata)
+ except Exception as e:
+ logger.error(f"Failed to fetch Scalekit metadata: {e}")
+ return JSONResponse(
+ {
+ "error": "server_error",
+ "error_description": f"Failed to fetch Scalekit metadata: {e}",
+ },
+ status_code=500,
+ )
+
+ # Add Scalekit authorization server metadata forwarding
+ routes.append(
+ Route(
+ "/.well-known/oauth-authorization-server",
+ endpoint=oauth_authorization_server_metadata,
+ methods=["GET"],
+ )
+ )
+
+ return routes
diff --git a/tests/server/auth/providers/test_scalekit.py b/tests/server/auth/providers/test_scalekit.py
new file mode 100644
index 000000000..d7f7a6546
--- /dev/null
+++ b/tests/server/auth/providers/test_scalekit.py
@@ -0,0 +1,162 @@
+"""Tests for Scalekit OAuth provider."""
+
+import os
+from collections.abc import Generator
+from unittest.mock import patch
+
+import httpx
+import pytest
+
+from fastmcp import Client, FastMCP
+from fastmcp.client.transports import StreamableHttpTransport
+from fastmcp.server.auth.providers.scalekit import ScalekitProvider
+from fastmcp.utilities.tests import HeadlessOAuth, run_server_in_process
+
+
+class TestScalekitProvider:
+ """Test Scalekit OAuth provider functionality."""
+
+ def test_init_with_explicit_params(self):
+ """Test ScalekitProvider initialization with explicit parameters."""
+ provider = ScalekitProvider(
+ environment_url="https://my-env.scalekit.com",
+ client_id="sk_client_123",
+ resource_id="sk_resource_456",
+ mcp_url="https://myserver.com/",
+ )
+
+ assert provider.environment_url == "https://my-env.scalekit.com"
+ assert provider.client_id == "sk_client_123"
+ assert provider.resource_id == "sk_resource_456"
+ assert str(provider.mcp_url) == "https://myserver.com/"
+
+ def test_init_with_env_vars(self):
+ """Test ScalekitProvider initialization from environment variables."""
+ with patch.dict(
+ os.environ,
+ {
+ "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_ENVIRONMENT_URL": "https://env-scalekit.com",
+ "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_CLIENT_ID": "skc_123",
+ "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_RESOURCE_ID": "res_456",
+ "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL": "https://envserver.com/mcp",
+ },
+ ):
+ provider = ScalekitProvider()
+
+ assert provider.environment_url == "https://env-scalekit.com"
+ assert provider.client_id == "skc_123"
+ assert provider.resource_id == "res_456"
+ assert str(provider.mcp_url) == "https://envserver.com/mcp"
+
+ def test_environment_variable_loading(self):
+ """Test that environment variables are loaded correctly."""
+ provider = ScalekitProvider(
+ environment_url="https://test-env.scalekit.com",
+ client_id="sk_client_test_123",
+ resource_id="sk_resource_test_456",
+ mcp_url="http://test-server.com",
+ )
+
+ assert provider.environment_url == "https://test-env.scalekit.com"
+ assert provider.client_id == "sk_client_test_123"
+ assert provider.resource_id == "sk_resource_test_456"
+ assert str(provider.mcp_url) == "http://test-server.com/"
+
+ def test_url_trailing_slash_handling(self):
+ """Test that URLs handle trailing slashes correctly."""
+ provider = ScalekitProvider(
+ environment_url="https://my-env.scalekit.com/",
+ client_id="sk_client_123",
+ resource_id="sk_resource_456",
+ mcp_url="https://myserver.com/",
+ )
+
+ assert provider.environment_url == "https://my-env.scalekit.com"
+ assert str(provider.mcp_url) == "https://myserver.com/"
+
+ def test_jwt_verifier_configured_correctly(self):
+ """Test that JWT verifier is configured correctly."""
+ provider = ScalekitProvider(
+ environment_url="https://my-env.scalekit.com",
+ client_id="sk_client_123",
+ resource_id="sk_resource_456",
+ mcp_url="https://myserver.com/",
+ )
+
+ # Check that JWT verifier uses the correct endpoints
+ assert (
+ provider.token_verifier.jwks_uri # type: ignore[attr-defined]
+ == "https://my-env.scalekit.com/keys"
+ )
+ assert (
+ provider.token_verifier.issuer == "https://my-env.scalekit.com" # type: ignore[attr-defined]
+ )
+ assert provider.token_verifier.audience == "https://myserver.com/" # type: ignore[attr-defined]
+
+ def test_authorization_servers_configuration(self):
+ """Test that authorization servers are configured correctly."""
+ provider = ScalekitProvider(
+ environment_url="https://my-env.scalekit.com",
+ client_id="sk_client_123",
+ resource_id="sk_resource_456",
+ mcp_url="https://myserver.com/",
+ )
+
+ assert len(provider.authorization_servers) == 1
+ assert (
+ str(provider.authorization_servers[0])
+ == "https://my-env.scalekit.com/resources/sk_resource_456"
+ )
+
+
+def run_mcp_server(host: str, port: int) -> None:
+ mcp = FastMCP(
+ auth=ScalekitProvider(
+ environment_url="https://test-env.scalekit.com",
+ client_id="sk_client_test_123",
+ resource_id="sk_resource_test_456",
+ mcp_url="http://localhost:4321",
+ )
+ )
+
+ @mcp.tool
+ def add(a: int, b: int) -> int:
+ return a + b
+
+ mcp.run(host=host, port=port, transport="http")
+
+
+@pytest.fixture
+def mcp_server_url() -> Generator[str]:
+ with run_server_in_process(run_mcp_server) as url:
+ yield f"{url}/mcp"
+
+
+@pytest.fixture()
+def client_with_headless_oauth(
+ mcp_server_url: str,
+) -> Generator[Client, None, None]:
+ """Client with headless OAuth that bypasses browser interaction."""
+ client = Client(
+ transport=StreamableHttpTransport(mcp_server_url),
+ auth=HeadlessOAuth(mcp_url=mcp_server_url),
+ )
+ yield client
+
+
+class TestScalekitProviderIntegration:
+ async def test_unauthorized_access(self, mcp_server_url: str):
+ with pytest.raises(httpx.HTTPStatusError) as exc_info:
+ async with Client(mcp_server_url) as client:
+ tools = await client.list_tools() # noqa: F841
+
+ assert isinstance(exc_info.value, httpx.HTTPStatusError)
+ assert exc_info.value.response.status_code == 401
+ assert "tools" not in locals()
+
+ # async def test_authorized_access(self, client_with_headless_oauth: Client):
+ # async with client_with_headless_oauth:
+ # tools = await client_with_headless_oauth.list_tools()
+ # assert tools is not None
+ # assert len(tools) > 0
+ # assert "add" in tools