|
| 1 | +"""Example demonstrating hierarchical organization of tools, prompts, and resources using custom URIs. |
| 2 | +
|
| 3 | +This example shows how to: |
| 4 | +1. Register tools, prompts, and resources with hierarchical URIs |
| 5 | +2. Create group discovery resources at well-known URIs |
| 6 | +3. Filter items by URI paths for better organization |
| 7 | +""" |
| 8 | + |
| 9 | +import json |
| 10 | +from typing import cast |
| 11 | + |
| 12 | +from pydantic import AnyUrl |
| 13 | + |
| 14 | +from mcp.server.fastmcp import FastMCP |
| 15 | +from mcp.types import ListFilters, TextContent, TextResourceContents |
| 16 | + |
| 17 | +# Create FastMCP server instance |
| 18 | +mcp = FastMCP("hierarchical-example") |
| 19 | + |
| 20 | + |
| 21 | +# Group discovery resources |
| 22 | +@mcp.resource("mcp://groups/tools") |
| 23 | +def get_tool_groups() -> str: |
| 24 | + """Discover available tool groups.""" |
| 25 | + return json.dumps( |
| 26 | + { |
| 27 | + "groups": [ |
| 28 | + {"name": "math", "description": "Mathematical operations", "uri_paths": ["mcp://tools/math/"]}, |
| 29 | + {"name": "string", "description": "String manipulation", "uri_paths": ["mcp://tools/string/"]}, |
| 30 | + ] |
| 31 | + }, |
| 32 | + indent=2, |
| 33 | + ) |
| 34 | + |
| 35 | + |
| 36 | +@mcp.resource("mcp://groups/prompts") |
| 37 | +def get_prompt_groups() -> str: |
| 38 | + """Discover available prompt groups.""" |
| 39 | + return json.dumps( |
| 40 | + { |
| 41 | + "groups": [ |
| 42 | + {"name": "greetings", "description": "Greeting prompts", "uri_paths": ["mcp://prompts/greetings/"]}, |
| 43 | + { |
| 44 | + "name": "instructions", |
| 45 | + "description": "Instructional prompts", |
| 46 | + "uri_paths": ["mcp://prompts/instructions/"], |
| 47 | + }, |
| 48 | + ] |
| 49 | + }, |
| 50 | + indent=2, |
| 51 | + ) |
| 52 | + |
| 53 | + |
| 54 | +# Math tools organized under mcp://tools/math/ |
| 55 | +@mcp.tool(uri="mcp://tools/math/add") |
| 56 | +def add(a: float, b: float) -> float: |
| 57 | + """Add two numbers.""" |
| 58 | + return a + b |
| 59 | + |
| 60 | + |
| 61 | +@mcp.tool(uri="mcp://tools/math/multiply") |
| 62 | +def multiply(a: float, b: float) -> float: |
| 63 | + """Multiply two numbers.""" |
| 64 | + return a * b |
| 65 | + |
| 66 | + |
| 67 | +# String tools organized under mcp://tools/string/ |
| 68 | +@mcp.tool(uri="mcp://tools/string/reverse") |
| 69 | +def reverse(text: str) -> str: |
| 70 | + """Reverse a string.""" |
| 71 | + return text[::-1] |
| 72 | + |
| 73 | + |
| 74 | +@mcp.tool(uri="mcp://tools/string/upper") |
| 75 | +def upper(text: str) -> str: |
| 76 | + """Convert to uppercase.""" |
| 77 | + return text.upper() |
| 78 | + |
| 79 | + |
| 80 | +# Greeting prompts organized under mcp://prompts/greetings/ |
| 81 | +@mcp.prompt(uri="mcp://prompts/greetings/hello") |
| 82 | +def hello_prompt(name: str) -> str: |
| 83 | + """Generate a hello greeting.""" |
| 84 | + return f"Hello, {name}! How can I help you today?" |
| 85 | + |
| 86 | + |
| 87 | +@mcp.prompt(uri="mcp://prompts/greetings/goodbye") |
| 88 | +def goodbye_prompt(name: str) -> str: |
| 89 | + """Generate a goodbye message.""" |
| 90 | + return f"Goodbye, {name}! Have a great day!" |
| 91 | + |
| 92 | + |
| 93 | +# Instruction prompts organized under mcp://prompts/instructions/ |
| 94 | +@mcp.prompt(uri="mcp://prompts/instructions/setup") |
| 95 | +def setup_prompt(tool: str) -> str: |
| 96 | + """Generate setup instructions for a tool.""" |
| 97 | + return ( |
| 98 | + f"To set up {tool}, follow these steps:\n" |
| 99 | + "1. Install the required dependencies\n" |
| 100 | + "2. Configure the settings\n" |
| 101 | + "3. Run the initialization script\n" |
| 102 | + "4. Verify the installation" |
| 103 | + ) |
| 104 | + |
| 105 | + |
| 106 | +@mcp.prompt(uri="mcp://prompts/instructions/debug") |
| 107 | +def debug_prompt(error: str) -> str: |
| 108 | + """Generate debugging instructions for an error.""" |
| 109 | + return ( |
| 110 | + f"To debug '{error}':\n" |
| 111 | + "1. Check the error logs\n" |
| 112 | + "2. Verify input parameters\n" |
| 113 | + "3. Enable verbose logging\n" |
| 114 | + "4. Isolate the issue with minimal reproduction" |
| 115 | + ) |
| 116 | + |
| 117 | + |
| 118 | +if __name__ == "__main__": |
| 119 | + # Example of testing the hierarchical organization |
| 120 | + import asyncio |
| 121 | + |
| 122 | + from mcp.shared.memory import create_connected_server_and_client_session |
| 123 | + |
| 124 | + async def test_hierarchy(): |
| 125 | + """Test the hierarchical organization.""" |
| 126 | + async with create_connected_server_and_client_session(mcp._mcp_server) as client: |
| 127 | + # 1. List ALL tools to show what's available |
| 128 | + print("=== All Available Tools ===") |
| 129 | + all_tools = await client.list_tools() |
| 130 | + for tool in all_tools.tools: |
| 131 | + print(f"- {tool.name} ({tool.uri}): {tool.description}") |
| 132 | + |
| 133 | + # 2. Discover tool groups and list tools in each group |
| 134 | + print("\n=== Discovering Tool Groups ===") |
| 135 | + result = await client.read_resource(uri=AnyUrl("mcp://groups/tools")) |
| 136 | + tool_groups = json.loads(cast(TextResourceContents, result.contents[0]).text) |
| 137 | + |
| 138 | + for group in tool_groups["groups"]: |
| 139 | + print(f"\n--- {group['name'].upper()} Tools ({group['description']}) ---") |
| 140 | + # Use the URI paths from the group definition |
| 141 | + group_tools = await client.list_tools( |
| 142 | + filters=ListFilters(uri_paths=[AnyUrl(uri) for uri in group["uri_paths"]]) |
| 143 | + ) |
| 144 | + for tool in group_tools.tools: |
| 145 | + print(f" - {tool.name}: {tool.description}") |
| 146 | + |
| 147 | + # 3. Call tools by name (still works!) |
| 148 | + print("\n=== Calling Tools by Name ===") |
| 149 | + result = await client.call_tool("add", {"a": 10, "b": 5}) |
| 150 | + print(f"add(10, 5) = {cast(TextContent, result.content[0]).text}") |
| 151 | + |
| 152 | + result = await client.call_tool("reverse", {"text": "Hello"}) |
| 153 | + print(f"reverse('Hello') = {cast(TextContent, result.content[0]).text}") |
| 154 | + |
| 155 | + # 4. Call tools by URI |
| 156 | + print("\n=== Calling Tools by URI ===") |
| 157 | + result = await client.call_tool("mcp://tools/math/multiply", {"a": 7, "b": 8}) |
| 158 | + print( |
| 159 | + f"Call mcp://tools/math/multiply with {{'a': 7, 'b': 8}} = {cast(TextContent, result.content[0]).text}" |
| 160 | + ) |
| 161 | + |
| 162 | + result = await client.call_tool("mcp://tools/string/upper", {"text": "hello world"}) |
| 163 | + print( |
| 164 | + f"Call mcp://tools/string/upper with {{'text': 'hello world'}} = " |
| 165 | + f"{cast(TextContent, result.content[0]).text}" |
| 166 | + ) |
| 167 | + |
| 168 | + # 5. List all prompts |
| 169 | + print("\n=== All Available Prompts ===") |
| 170 | + all_prompts = await client.list_prompts() |
| 171 | + for prompt in all_prompts.prompts: |
| 172 | + print(f"- {prompt.name} ({prompt.uri}): {prompt.description}") |
| 173 | + |
| 174 | + # 5. Discover prompt groups and list prompts in each group |
| 175 | + print("\n=== Discovering Prompt Groups ===") |
| 176 | + result = await client.read_resource(uri=AnyUrl("mcp://groups/prompts")) |
| 177 | + prompt_groups = json.loads(cast(TextResourceContents, result.contents[0]).text) |
| 178 | + |
| 179 | + for group in prompt_groups["groups"]: |
| 180 | + print(f"\n--- {group['name'].upper()} Prompts ({group['description']}) ---") |
| 181 | + # Use the URI paths from the group definition |
| 182 | + group_prompts = await client.list_prompts( |
| 183 | + filters=ListFilters(uri_paths=[AnyUrl(uri) for uri in group["uri_paths"]]) |
| 184 | + ) |
| 185 | + for prompt in group_prompts.prompts: |
| 186 | + print(f" - {prompt.name}: {prompt.description}") |
| 187 | + |
| 188 | + # 6. Use a prompt |
| 189 | + print("\n=== Using a Prompt ===") |
| 190 | + result = await client.get_prompt("hello_prompt", {"name": "Alice"}) |
| 191 | + print(f"Prompt result: {cast(TextContent, result.messages[0].content).text}") |
| 192 | + |
| 193 | + # Run the test |
| 194 | + asyncio.run(test_hierarchy()) |
0 commit comments