#
tokens: 6394/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── build.sh
├── install.sh
├── LICENSE
├── pyproject.toml
├── README.md
├── setup.py
├── src
│   └── mcp_server_deep_research
│       ├── __init__.py
│       └── server.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python specific
 2 | __pycache__/
 3 | *.py[cod]
 4 | *$py.class
 5 | *.so
 6 | .Python
 7 | env/
 8 | build/
 9 | develop-eggs/
10 | dist/
11 | downloads/
12 | eggs/
13 | .eggs/
14 | lib/
15 | lib64/
16 | parts/
17 | sdist/
18 | var/
19 | *.egg-info/
20 | .installed.cfg
21 | *.egg
22 | .pytest_cache/
23 | .coverage
24 | htmlcov/
25 | .tox/
26 | .nox/
27 | 
28 | # Virtual Environment
29 | venv/
30 | ENV/
31 | env/
32 | .env/
33 | 
34 | # IDE specific files
35 | .idea/
36 | .vscode/
37 | *.swp
38 | *.swo
39 | .DS_Store
40 | 
41 | # Project specific
42 | .cache/
43 | logs/
44 | *.log 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Server for Deep Research
  2 | 
  3 | MCP Server for Deep Research is a tool designed for conducting comprehensive research on complex topics. It helps you explore questions in depth, find relevant sources, and generate structured research reports.
  4 | 
  5 | Your personal Research Assistant, turning research questions into comprehensive, well-cited reports.
  6 | 
  7 | ## 🚀 Try it Out
  8 | 
  9 | [![Watch the demo](https://img.youtube.com/vi/_a7sfo5yxoI/maxresdefault.jpg)]([VIDEO_URL](https://youtu.be/_a7sfo5yxoI))
 10 | Youtube: https://youtu.be/_a7sfo5yxoI
 11 | 
 12 | 1. **Download Claude Desktop**
 13 |    - Get it [here](https://claude.ai/download)
 14 | 
 15 | 2. **Install and Set Up**
 16 |    - On macOS, run the following command in your terminal:
 17 |    ```bash
 18 |    python setup.py
 19 |    ```
 20 | 
 21 | 3. **Start Researching**
 22 |    - Select the deep-research prompt template from MCP
 23 |    - Begin your research by providing a research question
 24 | 
 25 | ## Features
 26 | 
 27 | The Deep Research MCP Server offers a complete research workflow:
 28 | 
 29 | 1. **Question Elaboration**
 30 |    - Expands and clarifies your research question
 31 |    - Identifies key terms and concepts
 32 |    - Defines scope and parameters
 33 | 
 34 | 2. **Subquestion Generation**
 35 |    - Creates focused subquestions that address different aspects
 36 |    - Ensures comprehensive coverage of the main topic
 37 |    - Provides structure for systematic research
 38 | 
 39 | 3. **Web Search Integration**
 40 |    - Uses Claude's built-in web search capabilities
 41 |    - Performs targeted searches for each subquestion
 42 |    - Identifies relevant and authoritative sources
 43 |    - Collects diverse perspectives on the topic
 44 | 
 45 | 4. **Content Analysis**
 46 |    - Evaluates information quality and relevance
 47 |    - Synthesizes findings from multiple sources
 48 |    - Provides proper citations for all sources
 49 | 
 50 | 5. **Report Generation**
 51 |    - Creates well-structured, comprehensive reports as artifacts
 52 |    - Properly cites all sources used
 53 |    - Presents a balanced view with evidence-based conclusions
 54 |    - Uses appropriate formatting for clarity and readability
 55 | 
 56 | ## 📦 Components
 57 | 
 58 | ### Prompts
 59 | - **deep-research**: Tailored for comprehensive research tasks with a structured approach
 60 | 
 61 | ## ⚙️ Modifying the Server
 62 | 
 63 | ### Claude Desktop Configurations
 64 | - macOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
 65 | - Windows: `%APPDATA%/Claude/claude_desktop_config.json`
 66 | 
 67 | ### Development (Unpublished Servers)
 68 | ```json
 69 | "mcpServers": {
 70 |   "mcp-server-deep-research": {
 71 |     "command": "uv",
 72 |     "args": [
 73 |       "--directory",
 74 |       "/Users/username/repos/mcp-server-application/mcp-server-deep-research",
 75 |       "run",
 76 |       "mcp-server-deep-research"
 77 |     ]
 78 |   }
 79 | }
 80 | ```
 81 | 
 82 | ### Published Servers
 83 | ```json
 84 | "mcpServers": {
 85 |   "mcp-server-deep-research": {
 86 |     "command": "uvx",
 87 |     "args": [
 88 |       "mcp-server-deep-research"
 89 |     ]
 90 |   }
 91 | }
 92 | ```
 93 | 
 94 | ## 🛠️ Development
 95 | 
 96 | ### Building and Publishing
 97 | 1. **Sync Dependencies**
 98 |    ```bash
 99 |    uv sync
100 |    ```
101 | 
102 | 2. **Build Distributions**
103 |    ```bash
104 |    uv build
105 |    ```
106 |    Generates source and wheel distributions in the dist/ directory.
107 | 
108 | 3. **Publish to PyPI**
109 |    ```bash
110 |    uv publish
111 |    ```
112 | 
113 | ## 🤝 Contributing
114 | 
115 | Contributions are welcome! Whether you're fixing bugs, adding features, or improving documentation, your help makes this project better.
116 | 
117 | ## 📜 License
118 | 
119 | This project is licensed under the MIT License.
120 | See the LICENSE file for details.
121 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_deep_research/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | from . import server
 2 | import asyncio
 3 | 
 4 | def main():
 5 |     """Main entry point for the package."""
 6 |     asyncio.run(server.main())
 7 | 
 8 | # Optionally expose other important items at package level
 9 | __all__ = ['main', 'server']
10 | 
```

--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | # Build script for the MCP server deep research package
 3 | 
 4 | echo "Building mcp-server-deep-research package..."
 5 | cd /Users/hezhang/repos/mcp-server-application/mcp-server-deep-research
 6 | uv build
 7 | 
 8 | echo "Done building. The wheel file should be in the dist/ directory."
 9 | ls -la dist/
10 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "mcp-server-deep-research"
 3 | version = "0.1.1"
 4 | description = "A MCP server for deep research and report generation"
 5 | readme = "README.md"
 6 | requires-python = ">=3.10"
 7 | dependencies = [
 8 |     "mcp>=1.0.0",
 9 | ]
10 | 
11 | [[project.authors]]
12 | name = "Xing Xing"
13 | email = "[email protected]"
14 | 
15 | [build-system]
16 | requires = ["hatchling"]
17 | build-backend = "hatchling.build"
18 | 
19 | [project.scripts]
20 | mcp-server-deep-research = "mcp_server_deep_research:main"
21 | 
```

--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | # Install script for the MCP server deep research package
 3 | 
 4 | echo "Finding the latest wheel file..."
 5 | WHEEL_FILE=$(ls -t /Users/hezhang/repos/mcp-server-application/mcp-server-deep-research/dist/*.whl | head -1)
 6 | 
 7 | if [ -z "$WHEEL_FILE" ]; then
 8 |   echo "No wheel file found. Please run build.sh first."
 9 |   exit 1
10 | fi
11 | 
12 | echo "Installing wheel file: $WHEEL_FILE"
13 | uv pip install --force-reinstall $WHEEL_FILE
14 | 
15 | echo "Creating/updating Claude desktop config..."
16 | CONFIG_DIR="$HOME/Library/Application Support/Claude"
17 | CONFIG_FILE="$CONFIG_DIR/claude_desktop_config.json"
18 | 
19 | # Create directory if it doesn't exist
20 | mkdir -p "$CONFIG_DIR"
21 | 
22 | # Create or update config file
23 | if [ -f "$CONFIG_FILE" ]; then
24 |   # Update existing config
25 |   echo "Updating existing Claude config..."
26 |   
27 |   # Check if jq is installed
28 |   if ! command -v jq &> /dev/null; then
29 |     echo "jq is not installed. Creating a new config file..."
30 |     cat > "$CONFIG_FILE" << EOF
31 | {
32 |   "mcpServers": {
33 |     "mcp-server-deep-research": {
34 |       "command": "mcp-server-deep-research"
35 |     }
36 |   }
37 | }
38 | EOF
39 |   else
40 |     # Use jq to update config
41 |     jq '.mcpServers."mcp-server-deep-research" = {"command": "mcp-server-deep-research"}' "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
42 |   fi
43 | else
44 |   # Create new config file
45 |   echo "Creating new Claude config file..."
46 |   cat > "$CONFIG_FILE" << EOF
47 | {
48 |   "mcpServers": {
49 |     "mcp-server-deep-research": {
50 |       "command": "mcp-server-deep-research"
51 |     }
52 |   }
53 | }
54 | EOF
55 | fi
56 | 
57 | echo "Installation complete. Restart Claude to use the updated server."
58 | 
```

--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """Setup script for MCP server deep research environment."""
  3 | 
  4 | import json
  5 | import subprocess
  6 | import sys
  7 | from pathlib import Path
  8 | import re
  9 | import time
 10 | 
 11 | 
 12 | def run_command(cmd, check=True):
 13 |     """Run a shell command and return output."""
 14 |     try:
 15 |         result = subprocess.run(
 16 |             cmd, shell=True, check=check, capture_output=True, text=True
 17 |         )
 18 |         return result.stdout.strip()
 19 |     except subprocess.CalledProcessError as e:
 20 |         print(f"Error running command '{cmd}': {e}")
 21 |         return None
 22 | 
 23 | 
 24 | def ask_permission(question):
 25 |     """Ask user for permission."""
 26 |     while True:
 27 |         response = input(f"{question} (y/n): ").lower()
 28 |         if response in ["y", "yes"]:
 29 |             return True
 30 |         if response in ["n", "no"]:
 31 |             return False
 32 |         print("Please answer 'y' or 'n'")
 33 | 
 34 | 
 35 | def check_uv():
 36 |     """Check if uv is installed and install if needed."""
 37 |     if not run_command("which uv", check=False):
 38 |         if ask_permission("uv is not installed. Would you like to install it?"):
 39 |             print("Installing uv...")
 40 |             run_command("curl -LsSf https://astral.sh/uv/install.sh | sh")
 41 |             print("uv installed successfully")
 42 |         else:
 43 |             sys.exit("uv is required to continue")
 44 | 
 45 | 
 46 | def setup_venv():
 47 |     """Create virtual environment if it doesn't exist."""
 48 |     if not Path(".venv").exists():
 49 |         if ask_permission("Virtual environment not found. Create one?"):
 50 |             print("Creating virtual environment...")
 51 |             run_command("uv venv")
 52 |             print("Virtual environment created successfully")
 53 |         else:
 54 |             sys.exit("Virtual environment is required to continue")
 55 | 
 56 | 
 57 | def sync_dependencies():
 58 |     """Sync project dependencies."""
 59 |     print("Syncing dependencies...")
 60 |     run_command("uv sync")
 61 |     print("Dependencies synced successfully")
 62 | 
 63 | 
 64 | def check_claude_desktop():
 65 |     """Check if Claude desktop app is installed."""
 66 |     app_path = "/Applications/Claude.app"
 67 |     if not Path(app_path).exists():
 68 |         print("Claude desktop app not found.")
 69 |         print("Please download and install from: https://claude.ai/download")
 70 |         if not ask_permission("Continue after installing Claude?"):
 71 |             sys.exit("Claude desktop app is required to continue")
 72 | 
 73 | 
 74 | def setup_claude_config():
 75 |     """Setup Claude desktop config file."""
 76 |     config_path = Path(
 77 |         "~/Library/Application Support/Claude/claude_desktop_config.json"
 78 |     ).expanduser()
 79 |     config_dir = config_path.parent
 80 | 
 81 |     if not config_dir.exists():
 82 |         config_dir.mkdir(parents=True)
 83 | 
 84 |     config = (
 85 |         {"mcpServers": {}}
 86 |         if not config_path.exists()
 87 |         else json.loads(config_path.read_text())
 88 |     )
 89 |     return config_path, config
 90 | 
 91 | 
 92 | def build_package():
 93 |     """Build package and get wheel path."""
 94 |     print("Building package...")
 95 |     try:
 96 |         # Use Popen for real-time and complete output capture
 97 |         process = subprocess.Popen(
 98 |             "uv build",
 99 |             shell=True,
100 |             stdout=subprocess.PIPE,
101 |             stderr=subprocess.PIPE,
102 |             text=True,
103 |         )
104 |         stdout, stderr = process.communicate()  # Capture output
105 |         output = stdout + stderr  # Combine both streams
106 |         print(f"Raw output: {output}")  # Debug: check output
107 |     except Exception as e:
108 |         sys.exit(f"Error running build: {str(e)}")
109 | 
110 |     # Check if the command was successful
111 |     if process.returncode != 0:
112 |         sys.exit(f"Build failed with error code {process.returncode}")
113 | 
114 |     # Extract wheel file path from the combined output
115 |     match = re.findall(r"dist/[^\s]+\.whl", output.strip())
116 |     whl_file = match[-1] if match else None
117 |     if not whl_file:
118 |         sys.exit("Failed to find wheel file in build output")
119 | 
120 |     # Convert to absolute path
121 |     path = Path(whl_file).absolute()
122 |     return str(path)
123 | 
124 | 
125 | def update_config(config_path, config, wheel_path):
126 |     """Update Claude config with MCP server settings."""
127 |     config.setdefault("mcpServers", {})
128 |     config["mcpServers"]["mcp-server-deep-research"] = {
129 |         "command": "uvx",
130 |         "args": ["--from", wheel_path, "mcp-server-deep-research"],
131 |     }
132 | 
133 |     config_path.write_text(json.dumps(config, indent=2))
134 |     print(f"Updated config at {config_path}")
135 | 
136 | 
137 | def restart_claude():
138 |     """Restart Claude desktop app if running."""
139 |     if run_command("pgrep -x Claude", check=False):
140 |         if ask_permission("Claude is running. Restart it?"):
141 |             print("Restarting Claude...")
142 |             run_command("pkill -x Claude")
143 |             time.sleep(2)
144 |             run_command("open -a Claude")
145 |             print("Claude restarted successfully")
146 |     else:
147 |         print("Starting Claude...")
148 |         run_command("open -a Claude")
149 | 
150 | 
151 | def main():
152 |     """Main setup function."""
153 |     print("Starting setup...")
154 |     check_uv()
155 |     setup_venv()
156 |     sync_dependencies()
157 |     check_claude_desktop()
158 |     config_path, config = setup_claude_config()
159 |     wheel_path = build_package()
160 |     update_config(config_path, config, wheel_path)
161 |     restart_claude()
162 |     print("Setup completed successfully!")
163 | 
164 | 
165 | if __name__ == "__main__":
166 |     main()
167 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_deep_research/server.py:
--------------------------------------------------------------------------------

```python
  1 | from enum import Enum
  2 | import logging
  3 | from typing import Any
  4 | import json
  5 | 
  6 | # Import MCP server
  7 | from mcp.server.models import InitializationOptions
  8 | from mcp.types import (
  9 |     TextContent,
 10 |     Tool,
 11 |     Resource,
 12 |     Prompt,
 13 |     PromptArgument,
 14 |     GetPromptResult,
 15 |     PromptMessage,
 16 | )
 17 | from mcp.server import NotificationOptions, Server
 18 | from pydantic import AnyUrl
 19 | import mcp.server.stdio
 20 | 
 21 | logger = logging.getLogger(__name__)
 22 | logger.info("Starting deep research server")
 23 | 
 24 | 
 25 | ### Prompt templates
 26 | class DeepResearchPrompts(str, Enum):
 27 |     DEEP_RESEARCH = "deep-research"
 28 | 
 29 | 
 30 | class PromptArgs(str, Enum):
 31 |     RESEARCH_QUESTION = "research_question"
 32 | 
 33 | 
 34 | PROMPT_TEMPLATE = """
 35 | You are a professional researcher tasked with conducting thorough research on a topic and producing a structured, comprehensive report. Your goal is to provide a detailed analysis that addresses the research question systematically.
 36 | 
 37 | The research question is:
 38 | 
 39 | <research_question>
 40 | {research_question}
 41 | </research_question>
 42 | 
 43 | Follow these steps carefully:
 44 | 
 45 | 1. <question_elaboration>
 46 |    Elaborate on the research question. Define key terms, clarify the scope, and identify the core issues that need to be addressed. Consider different angles and perspectives that are relevant to the question.
 47 | </question_elaboration>
 48 | 
 49 | 2. <subquestions>
 50 |    Based on your elaboration, generate 3-5 specific subquestions that will help structure your research. Each subquestion should:
 51 |    - Address a specific aspect of the main research question
 52 |    - Be focused and answerable through web research
 53 |    - Collectively provide comprehensive coverage of the main question
 54 | </subquestions>
 55 | 
 56 | 3. For each subquestion:
 57 |    a. <web_search_results>
 58 |       Search for relevant information using web search. For each subquestion, perform searches with carefully formulated queries.
 59 |       Extract meaningful content from the search results, focusing on:
 60 |       - Authoritative sources
 61 |       - Recent information when relevant
 62 |       - Diverse perspectives
 63 |       - Factual data and evidence
 64 |       
 65 |       Be sure to properly cite all sources and avoid extensive quotations. Limit quotes to less than 25 words each and use no more than one quote per source.
 66 |    </web_search_results>
 67 | 
 68 |    b. Analyze the collected information, evaluating:
 69 |       - Relevance to the subquestion
 70 |       - Credibility of sources
 71 |       - Consistency across sources
 72 |       - Comprehensiveness of coverage
 73 | 
 74 | 4. Create a beautifully formatted research report as an artifact. Your report should:
 75 |    - Begin with an introduction framing the research question
 76 |    - Include separate sections for each subquestion with findings
 77 |    - Synthesize information across sections
 78 |    - Provide a conclusion answering the main research question
 79 |    - Include proper citations of all sources
 80 |    - Use tables, lists, and other formatting for clarity where appropriate
 81 | 
 82 | The final report should be well-organized, carefully written, and properly cited. It should present a balanced view of the topic, acknowledge limitations and areas of uncertainty, and make clear, evidence-based conclusions.
 83 | 
 84 | Remember these important guidelines:
 85 | - Never provide extensive quotes from copyrighted content
 86 | - Limit quotes to less than 25 words each
 87 | - Use only one quote per source
 88 | - Properly cite all sources
 89 | - Do not reproduce song lyrics, poems, or other copyrighted creative works
 90 | - Put everything in your own words except for properly quoted material
 91 | - Keep summaries of copyrighted content to 2-3 sentences maximum
 92 | 
 93 | Please begin your research process, documenting each step carefully.
 94 | """
 95 | 
 96 | 
 97 | ### Research Processor
 98 | class ResearchProcessor:
 99 |     def __init__(self):
100 |         self.research_data = {
101 |             "question": "",
102 |             "elaboration": "",
103 |             "subquestions": [],
104 |             "search_results": {},
105 |             "extracted_content": {},
106 |             "final_report": "",
107 |         }
108 |         self.notes: list[str] = []
109 | 
110 |     def add_note(self, note: str):
111 |         """Add a note to the research process."""
112 |         self.notes.append(note)
113 |         logger.debug(f"Note added: {note}")
114 | 
115 |     def update_research_data(self, key: str, value: Any):
116 |         """Update a specific key in the research data dictionary."""
117 |         self.research_data[key] = value
118 |         self.add_note(f"Updated research data: {key}")
119 | 
120 |     def get_research_notes(self) -> str:
121 |         """Return all research notes as a newline-separated string."""
122 |         return "\n".join(self.notes)
123 | 
124 |     def get_research_data(self) -> dict:
125 |         """Return the current research data dictionary."""
126 |         return self.research_data
127 | 
128 | 
129 | ### MCP Server Definition
130 | async def main():
131 |     research_processor = ResearchProcessor()
132 |     server = Server("deep-research-server")
133 | 
134 |     @server.list_resources()
135 |     async def handle_list_resources() -> list[Resource]:
136 |         logger.debug("Handling list_resources request")
137 |         return [
138 |             Resource(
139 |                 uri="research://notes",
140 |                 name="Research Process Notes",
141 |                 description="Notes generated during the research process",
142 |                 mimeType="text/plain",
143 |             ),
144 |             Resource(
145 |                 uri="research://data",
146 |                 name="Research Data",
147 |                 description="Structured data collected during the research process",
148 |                 mimeType="application/json",
149 |             ),
150 |         ]
151 | 
152 |     @server.read_resource()
153 |     async def handle_read_resource(uri: AnyUrl) -> str:
154 |         logger.debug(f"Handling read_resource request for URI: {uri}")
155 |         if str(uri) == "research://notes":
156 |             return research_processor.get_research_notes()
157 |         elif str(uri) == "research://data":
158 |             return json.dumps(research_processor.get_research_data(), indent=2)
159 |         else:
160 |             raise ValueError(f"Unknown resource: {uri}")
161 | 
162 |     @server.list_prompts()
163 |     async def handle_list_prompts() -> list[Prompt]:
164 |         logger.debug("Handling list_prompts request")
165 |         return [
166 |             Prompt(
167 |                 name=DeepResearchPrompts.DEEP_RESEARCH,
168 |                 description="A prompt to conduct deep research on a question",
169 |                 arguments=[
170 |                     PromptArgument(
171 |                         name=PromptArgs.RESEARCH_QUESTION,
172 |                         description="The research question to investigate",
173 |                         required=True,
174 |                     ),
175 |                 ],
176 |             )
177 |         ]
178 | 
179 |     @server.get_prompt()
180 |     async def handle_get_prompt(
181 |         name: str, arguments: dict[str, str] | None
182 |     ) -> GetPromptResult:
183 |         logger.debug(f"Handling get_prompt request for {name} with args {arguments}")
184 |         if name != DeepResearchPrompts.DEEP_RESEARCH:
185 |             logger.error(f"Unknown prompt: {name}")
186 |             raise ValueError(f"Unknown prompt: {name}")
187 | 
188 |         if not arguments or PromptArgs.RESEARCH_QUESTION not in arguments:
189 |             logger.error("Missing required argument: research_question")
190 |             raise ValueError("Missing required argument: research_question")
191 | 
192 |         research_question = arguments[PromptArgs.RESEARCH_QUESTION]
193 |         prompt = PROMPT_TEMPLATE.format(research_question=research_question)
194 | 
195 |         # Store the research question
196 |         research_processor.update_research_data("question", research_question)
197 |         research_processor.add_note(
198 |             f"Research initiated on question: {research_question}"
199 |         )
200 | 
201 |         logger.debug(
202 |             f"Generated prompt template for research_question: {research_question}"
203 |         )
204 |         return GetPromptResult(
205 |             description=f"Deep research template for: {research_question}",
206 |             messages=[
207 |                 PromptMessage(
208 |                     role="user",
209 |                     content=TextContent(type="text", text=prompt.strip()),
210 |                 )
211 |             ],
212 |         )
213 | 
214 |     @server.list_tools()
215 |     async def handle_list_tools() -> list[Tool]:
216 |         logger.debug("Handling list_tools request")
217 |         # We're not exposing any tools since we'll be using Claude's built-in web search
218 |         return []
219 | 
220 |     async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
221 |         logger.debug("Server running with stdio transport")
222 |         await server.run(
223 |             read_stream,
224 |             write_stream,
225 |             InitializationOptions(
226 |                 server_name="deep-research-server",
227 |                 server_version="0.1.0",
228 |                 capabilities=server.get_capabilities(
229 |                     notification_options=NotificationOptions(),
230 |                     experimental_capabilities={},
231 |                 ),
232 |             ),
233 |         )
234 | 
```