From 48ee1597e224ce9e218806c1b023a177dd253909 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Wed, 16 Apr 2025 10:25:31 -0400
Subject: [PATCH] Improve integration documentation
---
docs/patterns/fastapi.mdx | 176 ++++++++++++------------
docs/patterns/openapi.mdx | 257 ++++++++++++++++-------------------
pyproject.toml | 15 +-
src/fastmcp/server/server.py | 4 +-
uv.lock | 8 +-
5 files changed, 224 insertions(+), 236 deletions(-)
diff --git a/docs/patterns/fastapi.mdx b/docs/patterns/fastapi.mdx
index 45e9d2e78..1bbfd6231 100644
--- a/docs/patterns/fastapi.mdx
+++ b/docs/patterns/fastapi.mdx
@@ -1,114 +1,120 @@
---
title: FastAPI Integration
sidebarTitle: FastAPI
-description: Automatically create FastMCP servers directly from FastAPI applications.
+description: Generate MCP servers from FastAPI apps
icon: square-bolt
---
-If you build your APIs using the popular [FastAPI](https://fastapi.tiangolo.com/) framework, FastMCP offers a seamless way to expose your FastAPI application as an MCP server. This leverages the OpenAPI integration internally but simplifies the setup significantly.
-## The Goal: FastAPI App -> MCP Server
+FastMCP can automatically convert FastAPI applications into MCP servers.
-FastAPI automatically generates an OpenAPI specification for your application. FastMCP uses this built-in capability to create an MCP server that mirrors your API routes.
+
+FastMCP does *not* include FastAPI as a dependency; you must install it separately to run these examples.
+
-- FastAPI path operations (`@app.get`, `@app.post`, etc.) become MCP tools, resources, or templates.
-- Pydantic models used in FastAPI for request/response validation are used to generate MCP schemas.
-- Communication happens directly in memory, making it very efficient.
-## Creating from FastAPI App
+```python {2, 22, 25}
+from fastapi import FastAPI
+from fastmcp import FastMCP
-Use the `FastMCP.from_fastapi()` class method. You only need your FastAPI `app` instance.
+
+# A FastAPI app
+app = FastAPI()
+
+@app.get("/items")
+def list_items():
+ return [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]
+
+@app.get("/items/{item_id}")
+def get_item(item_id: int):
+ return {"id": item_id, "name": f"Item {item_id}"}
+
+@app.post("/items")
+def create_item(name: str):
+ return {"id": 3, "name": name}
+
+
+# Create an MCP server from your FastAPI app
+mcp = FastMCP.from_fastapi(app=app)
+
+if __name__ == "__main__":
+ mcp.run() # Start the MCP server
+```
+
+## Route Mapping
+
+By default, FastMCP will map FastAPI routes to MCP components according to the following rules:
+
+| FastAPI Route Type | FastAPI Example | MCP Component | Notes |
+|--------------------|--------------|---------|-------|
+| GET without path params | `@app.get("/stats")` | Resource | Simple resources for fetching data |
+| GET with path params | `@app.get("/users/{id}")` | Resource Template | Path parameters become template parameters |
+| POST, PUT, DELETE, etc. | `@app.post("/users")` | Tool | Operations that modify data |
+
+For more details on route mapping or custom mapping rules, see the [OpenAPI integration documentation](/patterns/openapi#route-mapping); FastMCP uses the same mapping rules for both FastAPI and OpenAPI integrations.
+
+## Complete Example
+
+Here's a more detailed example with a data model:
```python
import asyncio
-from fastapi import FastAPI
+from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
-from fastmcp import FastMCP, Client # Import FastMCP and Client
-
-# 1. Define your FastAPI application
-api_app = FastAPI(title="MyFastAPIApp")
+from fastmcp import FastMCP, Client
+# Define your Pydantic model
class Item(BaseModel):
name: str
price: float
- is_offer: bool | None = None
-@api_app.get("/")
-def read_root():
- return {"Hello": "World"}
+# Create your FastAPI app
+app = FastAPI()
+items = {} # In-memory database
-@api_app.get("/items/{item_id}") # -> Resource Template
-def read_item(item_id: int, q: str | None = None):
- # This will become resource://openapi/read_item_items__item_id__get/{item_id}
- return {"item_id": item_id, "q": q, "description": f"Details for item {item_id}"}
+@app.get("/items")
+def list_items():
+ """List all items"""
+ return list(items.values())
-@api_app.post("/items/") # -> Tool
+@app.get("/items/{item_id}")
+def get_item(item_id: int):
+ """Get item by ID"""
+ if item_id not in items:
+ raise HTTPException(404, "Item not found")
+ return items[item_id]
+
+@app.post("/items")
def create_item(item: Item):
- # This will become the 'create_item_items__post' tool
- print(f"Creating item: {item.name}")
- return {"item_name": item.name, "status": "created"}
+ """Create a new item"""
+ item_id = len(items) + 1
+ items[item_id] = {"id": item_id, **item.model_dump()}
+ return items[item_id]
-# 2. Create the FastMCP server directly from the FastAPI app
-# This is an async class method
-async def create_mcp_server_from_fastapi():
- mcp_server = await FastMCP.from_fastapi(
- app=api_app,
- name="FastAPI_MCP_Bridge" # Optional name for the MCP server
- )
- return mcp_server
-
-# 3. (Example) Run the MCP server and test with an in-memory client
-async def run_and_test():
- server = await create_mcp_server_from_fastapi()
- print(f"Created MCP server '{server.name}' from FastAPI app '{api_app.title}'")
-
- # List discovered components
- tools = await server.list_tools()
- templates = await server.list_resource_templates()
- print("Discovered Tools:", [t.name for t in tools])
- print("Discovered Templates:", [t.uriTemplate for t in templates])
-
- # Test using an in-memory client
- client = Client(server) # Uses FastMCPTransport
- async with client:
- # Call the tool derived from POST /items/
- create_result = await client.call_tool(
- "create_item_items__post",
- {"name": "MCP Special", "price": 99.99} # Pydantic model fields become args
- )
- print("Create Item Tool Result:", create_result[0].text) # JSON string
-
- # Read the resource derived from GET /items/{item_id}
- read_result = await client.read_resource(
- "resource://openapi/read_item_items__item_id__get/42" # Match template URI
- )
- print("Read Item Resource Result:", read_result[0].text) # JSON string
-
- # In a real scenario, you might run the MCP server via stdio or sse
- # print("Running MCP server via stdio...")
- # server.run()
+# Test your MCP server with a client
+async def test():
+ # Create MCP server from FastAPI app
+ mcp = await FastMCP.from_fastapi(app=app)
+
+ # List the components that were created
+ tools = await mcp.list_tools()
+ resources = await mcp.list_resources()
+ templates = await mcp.list_resource_templates()
+
+ print(f"Generated {len(tools)} tools")
+ print(f"Generated {len(resources)} resources")
+ print(f"Generated {len(templates)} templates")
+
+ # In a real scenario, you would run the server:
+ # mcp.run()
if __name__ == "__main__":
- # Requires fastapi, uvicorn, httpx:
- # uv pip install "fastapi[all]" httpx
- try:
- asyncio.run(run_and_test())
- except ImportError as e:
- print(f"Error: {e}. Please install required packages: uv pip install \"fastapi[all]\" httpx")
-
-# Example Output might include:
-# Created MCP server 'FastAPI_MCP_Bridge' from FastAPI app 'MyFastAPIApp'
-# Discovered Tools: ['read_root___get', 'create_item_items__post']
-# Discovered Templates: ['resource://openapi/read_item_items__item_id__get/{item_id}']
-# Create Item Tool Result: {"item_name": "MCP Special", "status": "created"}
-# Read Item Resource Result: {"item_id": 42, "q": null, "description": "Details for item 42"}
+ asyncio.run(test())
```
-### How it Works Internally
+## Benefits
-1. **OpenAPI Generation**: `from_fastapi` asks the FastAPI `app` for its OpenAPI schema dictionary (`app.openapi()`).
-2. **In-Memory Client**: It creates an `httpx.AsyncClient` configured with an `ASGITransport`. This special transport allows `httpx` to call the FastAPI application directly in memory without needing a running web server process.
-3. **OpenAPI Integration**: It calls `FastMCP.from_openapi()`, passing the generated schema and the in-memory `httpx` client.
-4. **MCP Server Creation**: The standard OpenAPI integration logic then proceeds to parse the schema and create the `Tool`, `Resource`, and `ResourceTemplate` components that wrap calls to the in-memory FastAPI app.
-
-This provides a highly efficient way to expose your FastAPI logic through the MCP protocol, leveraging FastAPI's routing, dependency injection, and validation features.
\ No newline at end of file
+- **Leverage existing FastAPI apps** - No need to rewrite your API logic
+- **Schema reuse** - FastAPI's Pydantic models and validation are inherited
+- **Full feature support** - Works with FastAPI's authentication, dependencies, etc.
+- **ASGI transport** - Direct communication without additional HTTP overhead
diff --git a/docs/patterns/openapi.mdx b/docs/patterns/openapi.mdx
index 1767700c2..c537ac92a 100644
--- a/docs/patterns/openapi.mdx
+++ b/docs/patterns/openapi.mdx
@@ -1,174 +1,153 @@
---
title: OpenAPI Integration
sidebarTitle: OpenAPI
-description: Automatically create FastMCP servers from existing OpenAPI specifications.
+description: Generate MCP servers from OpenAPI specs
icon: code-branch
---
-If you have existing REST APIs documented with the OpenAPI Specification (OAS), FastMCP can automatically generate MCP tools, resources, and resource templates directly from that specification. This provides a quick way to make your existing HTTP APIs accessible to MCP clients and LLMs.
+FastMCP can automatically generate an MCP server from an OpenAPI specification. Users only need to provide an OpenAPI specification (3.0 or 3.1) and an API client.
-FastMCP supports both OpenAPI 3.0 and 3.1 specifications for maximum compatibility with existing API definitions.
+```python
+import httpx
+from fastmcp import FastMCP
-## The Goal: API -> MCP Server
+# Create a client for your API
+api_client = httpx.AsyncClient(base_url="https://api.example.com")
-The core idea is to map OpenAPI paths and operations (like `GET /users/{id}` or `POST /orders`) to their corresponding MCP components:
+# Load your OpenAPI spec
+spec = {...}
-- `GET` requests often map to MCP **Resources** (for fetching single items) or **Resource Templates** (if the path has parameters).
-- `POST`, `PUT`, `PATCH`, `DELETE` requests typically map to MCP **Tools** (for actions that create or modify data).
+# Create an MCP server from your OpenAPI spec
+mcp = FastMCP.from_openapi(openapi_spec=spec, client=api_client)
-FastMCP automates this mapping process.
+if __name__ == "__main__":
+ mcp.run()
+```
-## Creating from OpenAPI Spec
+## Route Mapping
-Use the `FastMCP.from_openapi()` class method. You need:
+By default, OpenAPI routes are mapped to MCP components based on these rules:
-1. The OpenAPI specification as a Python dictionary.
-2. An `httpx.AsyncClient` configured to make requests to the actual API backend.
+| OpenAPI Route | Example |MCP Component | Notes |
+|- | - | - | - |
+| `GET` without path params | `GET /stats` | Resource | Simple resources for fetching data |
+| `GET` with path params | `GET /users/{id}` | Resource Template | Path parameters become template parameters |
+| `POST`, `PUT`, `PATCH`, `DELETE`, etc. | `POST /users` | Tool | Operations that modify data |
-
-```python server.py
+Internally, FastMCP uses a priority-ordered set of `RouteMap` objects to determine the component type. Route maps indicate that a specific HTTP method (or methods) and path pattern should be treated as a specific component type. This is the default set of route maps:
+
+```python
+# Simplified version of the actual mapping rules
+DEFAULT_ROUTE_MAPPINGS = [
+ # GET with path parameters -> ResourceTemplate
+ RouteMap(methods=["GET"], pattern=r".*\{.*\}.*",
+ route_type=RouteType.RESOURCE_TEMPLATE),
+
+ # GET without path parameters -> Resource
+ RouteMap(methods=["GET"], pattern=r".*",
+ route_type=RouteType.RESOURCE),
+
+ # All other methods -> Tool
+ RouteMap(methods=["POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"],
+ pattern=r".*", route_type=RouteType.TOOL),
+]
+```
+
+### Custom Route Maps
+
+Users can add custom route maps to override the default mapping behavior. User-supplied route maps are always applied first, before the default route maps.
+
+```python
+from fastmcp.server.openapi import RouteMap, RouteType
+
+# Custom mapping rules
+custom_maps = [
+ # Force all analytics endpoints to be Tools
+ RouteMap(methods=["GET"],
+ pattern=r"^/analytics/.*",
+ route_type=RouteType.TOOL)
+]
+
+# Apply custom mappings
+mcp = await FastMCP.from_openapi(
+ openapi_spec=spec,
+ client=api_client,
+ route_maps=custom_maps
+)
+```
+
+## How It Works
+
+1. FastMCP parses your OpenAPI spec to extract routes and schemas
+2. It applies mapping rules to categorize each route
+3. When an MCP client calls a tool or accesses a resource:
+ - FastMCP constructs an HTTP request based on the OpenAPI definition
+ - It sends the request through the provided httpx client
+ - It translates the HTTP response to the appropriate MCP format
+
+## Complete Example
+
+```python
import asyncio
import httpx
from fastmcp import FastMCP
-# load the OpenAPI specification from the openapi_spec.py file
-petstore_spec = PETSTORE_SPEC
-
-# Client to communicate with the actual Pet Store API backend
-# The base_url should match the server URL in the OpenAPI spec
-http_client = httpx.AsyncClient(base_url="http://petstore.example.com/api")
-
-# Create the FastMCP server from the spec
-# This is an async class method
-async def create_openapi_server():
- mcp_server = await FastMCP.from_openapi(
- openapi_spec=petstore_spec,
- client=http_client,
- name="PetStoreMCP" # Optional name for the MCP server
- )
- return mcp_server
-
-async def run_server():
- server = await create_openapi_server()
- print(f"Starting OpenAPI-based server '{server.name}'...")
-
- # List discovered components
- tools = await server.list_tools()
- resources = await server.list_resources()
- templates = await server.list_resource_templates()
- print("Discovered Tools:", [t.name for t in tools])
- print("Discovered Resources:", [r.uri for r in resources]) # Should be empty if no parameterless GETs
- print("Discovered Templates:", [t.uriTemplate for t in templates])
-
- # Run the server (e.g., via stdio)
- # server.run()
-
-if __name__ == "__main__":
- # Example: Create the server and print discovered components
- # Requires httpx: uv pip install httpx
- asyncio.run(run_server())
-
-# Expected Output might include:
-# Discovered Tools: ['listPets', 'createPet']
-# Discovered Resources: []
-# Discovered Templates: ['resource://openapi/showPetById/{petId}']
-```
-
-```python openapi_spec.py
-# Example OpenAPI Specification (simplified Pet Store)
-PETSTORE_SPEC = {
- "openapi": "3.1.0",
- "info": {"title": "Simple Pet Store", "version": "1.0.0"},
- "servers": [{"url": "http://petstore.example.com/api"}], # Base URL for API calls
+# Sample OpenAPI spec for a Pet Store API
+petstore_spec = {
+ "openapi": "3.0.0",
"paths": {
"/pets": {
"get": {
- "summary": "List all pets",
"operationId": "listPets",
- "tags": ["pets"],
- "parameters": [{ # Query parameter -> Tool argument
- "name": "limit", "in": "query", "schema": {"type": "integer"}
- }],
- "responses": {"200": {"description": "A list of pets."}},
+ "summary": "List all pets"
},
- "post": { # POST -> Tool
- "summary": "Create a pet",
+ "post": {
"operationId": "createPet",
- "tags": ["pets"],
- "requestBody": { # Request body -> Tool arguments
- "required": True,
- "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PetInput"}}}
- },
- "responses": {"201": {"description": "Pet created."}},
- },
+ "summary": "Create a new pet"
+ }
},
- "/pets/{petId}": { # Path parameter -> Resource Template
- "get": { # GET with path param -> Resource Template / FunctionResource
- "summary": "Info for a specific pet",
- "operationId": "showPetById",
- "tags": ["pets"],
- "parameters": [{ # Path parameter -> Template function argument
- "name": "petId", "in": "path", "required": True, "schema": {"type": "string"}
- }],
- "responses": {"200": {"description": "Information about the pet."}},
- },
- },
- },
- "components": {
- "schemas": {
- "PetInput": {"type": "object", "properties": {"name": {"type": "string"}, "tag": {"type": "string"}}},
+ "/pets/{petId}": {
+ "get": {
+ "operationId": "getPet",
+ "summary": "Get a pet by ID",
+ "parameters": [
+ {
+ "name": "petId",
+ "in": "path",
+ "required": True,
+ "schema": {"type": "string"}
+ }
+ ]
+ }
}
}
}
-```
-
-
-### How it Works Internally
-
-1. **Parsing**: `from_openapi` parses the spec using utilities that leverage `openapi-pydantic`. It extracts paths, operations, parameters, request bodies, and responses.
-2. **Mapping**: It applies mapping rules (see below) to decide whether each OpenAPI operation (`GET /pets`, `POST /pets`, `GET /pets/{petId}`) becomes an MCP `Tool`, `Resource`, or `ResourceTemplate`.
-3. **Component Creation**: It creates specialized internal components (`OpenAPITool`, `OpenAPIResource`, `OpenAPIResourceTemplate`).
-4. **HTTP Execution**: When an MCP client calls a tool or reads a resource from this server:
- * The corresponding OpenAPI component constructs an HTTP request based on the OpenAPI definition and the arguments provided by the MCP client.
- * It uses the provided `httpx.AsyncClient` to send the request to the backend API.
- * It processes the HTTP response and returns it to the MCP client in the appropriate MCP format.
-5. **Schema Generation**: The schemas for MCP tools are derived by combining OpenAPI parameters (path, query, header) and request body schemas. Resource template function arguments are derived from path parameters.
-6. **Descriptions**: Tool/Resource descriptions are enhanced with information from OpenAPI responses to give the LLM more context about potential outcomes.
-
-### Default Mapping Rules
-
-FastMCP uses the following default rules to map OpenAPI operations:
-
-- `GET` operation with path parameters (e.g., `/users/{id}`) -> **`ResourceTemplate`**
-- `GET` operation without path parameters (e.g., `/users`) -> **`Resource`**
-- `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD` -> **`Tool`**
-
-### Customize Route Mapping
-
-You can customize the mapping rules by providing a list of `RouteMap` objects directly to `FastMCP.from_openapi()` using the `route_maps` parameter:
-
-```python
-from fastmcp.server.openapi import RouteMap, RouteType
-from fastmcp import FastMCP
-
-# Custom mapping: Treat GET /admin/stats as a Tool, not a Resource
-custom_maps = [
- RouteMap(methods=["GET"], pattern=r"^/admin/stats$", route_type=RouteType.TOOL)
-]
-
-async def create_server_with_custom_mapping():
- mcp_server = await FastMCP.from_openapi(
+async def main():
+ # Client for the Pet Store API
+ client = httpx.AsyncClient(base_url="https://petstore.example.com/api")
+
+ # Create the MCP server
+ mcp = await FastMCP.from_openapi(
openapi_spec=petstore_spec,
- client=http_client,
- name="PetStoreMCP",
- route_maps=custom_maps # Pass custom mapping rules
+ client=client,
+ name="PetStore"
)
- return mcp_server
+
+ # List what components were created
+ tools = await mcp.list_tools()
+ resources = await mcp.list_resources()
+ templates = await mcp.list_resource_templates()
+
+ print(f"Tools: {len(tools)}") # Should include createPet
+ print(f"Resources: {len(resources)}") # Should include listPets
+ print(f"Templates: {len(templates)}") # Should include getPet
+
+ # Start the MCP server
+ mcp.run()
+
+if __name__ == "__main__":
+ asyncio.run(main())
```
-Each `RouteMap` maps one or more HTTP methods and a regular expression pattern for the route path to an MCP `RouteType`. Route maps are processed in order, and the first match wins.
-
-All parameters passed to `FastMCP.from_openapi()` will be forwarded to the underlying `FastMCPOpenAPI` constructor, so you can customize any aspect of the OpenAPI integration directly through this method call.
-
diff --git a/pyproject.toml b/pyproject.toml
index a2a2297ed..0943452cc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,13 +5,13 @@ description = "The fast, Pythonic way to build MCP servers."
authors = [{ name = "Jeremiah Lowin" }]
dependencies = [
"dotenv>=0.9.9",
+ "exceptiongroup>=1.2.2",
+ "httpx>=0.28.1",
"mcp>=1.6.0,<2.0.0",
+ "openapi-pydantic>=0.5.1",
"rich>=13.9.4",
"typer>=0.15.2",
"websockets>=15.0.1",
- "fastapi>=0.115.12",
- "openapi-pydantic>=0.5.1",
- "exceptiongroup>=1.2.2",
]
requires-python = ">=3.10"
readme = "README.md"
@@ -38,6 +38,11 @@ classifiers = [
[dependency-groups]
dev = [
+ "copychat>=0.5.2",
+ "dirty-equals>=0.9.0",
+ "fastapi>=0.115.12",
+ "ipython>=8.12.3",
+ "pdbpp>=0.10.3",
"pre-commit",
"pyright>=1.1.389",
"pytest>=8.3.3",
@@ -45,10 +50,6 @@ dev = [
"pytest-flakefinder",
"pytest-xdist>=3.6.1",
"ruff",
- "copychat>=0.5.2",
- "ipython>=8.12.3",
- "pdbpp>=0.10.3",
- "dirty-equals>=0.9.0",
]
[project.scripts]
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index facd5ff18..8fd15d755 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -13,7 +13,6 @@ import anyio
import httpx
import pydantic_core
import uvicorn
-from fastapi import FastAPI
from mcp.server.lowlevel.helper_types import ReadResourceContents
from mcp.server.lowlevel.server import LifespanResultT
from mcp.server.lowlevel.server import Server as MCPServer
@@ -846,11 +845,12 @@ class FastMCP(Generic[LifespanResultT]):
@classmethod
def from_fastapi(
- cls, app: FastAPI, name: str | None = None, **settings: Any
+ cls, app: "Any", name: str | None = None, **settings: Any
) -> "FastMCPOpenAPI":
"""
Create a FastMCP server from a FastAPI application.
"""
+
from .openapi import FastMCPOpenAPI
client = httpx.AsyncClient(
diff --git a/uv.lock b/uv.lock
index 6fb683270..a12924537 100644
--- a/uv.lock
+++ b/uv.lock
@@ -254,12 +254,12 @@ wheels = [
[[package]]
name = "fastmcp"
-version = "2.1.3.dev31+8d922d9"
+version = "2.1.3.dev42+be2ccc6"
source = { editable = "." }
dependencies = [
{ name = "dotenv" },
{ name = "exceptiongroup" },
- { name = "fastapi" },
+ { name = "httpx" },
{ name = "mcp" },
{ name = "openapi-pydantic" },
{ name = "rich" },
@@ -271,6 +271,7 @@ dependencies = [
dev = [
{ name = "copychat" },
{ name = "dirty-equals" },
+ { name = "fastapi" },
{ name = "ipython" },
{ name = "pdbpp" },
{ name = "pre-commit" },
@@ -286,7 +287,7 @@ dev = [
requires-dist = [
{ name = "dotenv", specifier = ">=0.9.9" },
{ name = "exceptiongroup", specifier = ">=1.2.2" },
- { name = "fastapi", specifier = ">=0.115.12" },
+ { name = "httpx", specifier = ">=0.28.1" },
{ name = "mcp", specifier = ">=1.6.0,<2.0.0" },
{ name = "openapi-pydantic", specifier = ">=0.5.1" },
{ name = "rich", specifier = ">=13.9.4" },
@@ -298,6 +299,7 @@ requires-dist = [
dev = [
{ name = "copychat", specifier = ">=0.5.2" },
{ name = "dirty-equals", specifier = ">=0.9.0" },
+ { name = "fastapi", specifier = ">=0.115.12" },
{ name = "ipython", specifier = ">=8.12.3" },
{ name = "pdbpp", specifier = ">=0.10.3" },
{ name = "pre-commit" },