docs: restructure into topical sections and add the four most-asked-for pages (#3044)
This commit is contained in:
parent
080f2a869d
commit
220d362112
20 changed files with 2403 additions and 343 deletions
0
docs_src/deploy/__init__.py
Normal file
0
docs_src/deploy/__init__.py
Normal file
17
docs_src/deploy/tutorial001.py
Normal file
17
docs_src/deploy/tutorial001.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from mcp.server import MCPServer
|
||||
from mcp.server.transport_security import TransportSecuritySettings
|
||||
|
||||
mcp = MCPServer("Notes")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def add_note(text: str) -> str:
|
||||
"""Save a note."""
|
||||
return f"Saved: {text}"
|
||||
|
||||
|
||||
security = TransportSecuritySettings(
|
||||
allowed_hosts=["mcp.example.com", "mcp.example.com:*"],
|
||||
allowed_origins=["https://app.example.com"],
|
||||
)
|
||||
app = mcp.streamable_http_app(transport_security=security)
|
||||
27
docs_src/deploy/tutorial002.py
Normal file
27
docs_src/deploy/tutorial002.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from mcp_types import ElicitRequest, ElicitRequestFormParams, ElicitResult, InputRequiredResult
|
||||
|
||||
from mcp.server.mcpserver import Context, MCPServer
|
||||
|
||||
CONFIRM = ElicitRequest(
|
||||
params=ElicitRequestFormParams(
|
||||
message="Issue this refund?",
|
||||
requested_schema={"type": "object", "properties": {"ok": {"type": "boolean"}}, "required": ["ok"]},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def make_server() -> MCPServer:
|
||||
"""Every worker process builds one of these, once, at import."""
|
||||
mcp = MCPServer("billing")
|
||||
|
||||
@mcp.tool()
|
||||
async def refund(amount: int, ctx: Context) -> str | InputRequiredResult:
|
||||
"""Refund an amount, once a human has confirmed it."""
|
||||
if ctx.input_responses is None:
|
||||
return InputRequiredResult(input_requests={"ok": CONFIRM}, request_state=f"refund:{amount}")
|
||||
answer = (ctx.input_responses or {}).get("ok")
|
||||
if not isinstance(answer, ElicitResult) or answer.action != "accept" or not (answer.content or {}).get("ok"):
|
||||
return "refund cancelled"
|
||||
return f"refunded ${amount}"
|
||||
|
||||
return mcp
|
||||
27
docs_src/deploy/tutorial003.py
Normal file
27
docs_src/deploy/tutorial003.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from mcp_types import ElicitRequest, ElicitRequestFormParams, ElicitResult, InputRequiredResult
|
||||
|
||||
from mcp.server.mcpserver import Context, MCPServer, RequestStateSecurity
|
||||
|
||||
CONFIRM = ElicitRequest(
|
||||
params=ElicitRequestFormParams(
|
||||
message="Issue this refund?",
|
||||
requested_schema={"type": "object", "properties": {"ok": {"type": "boolean"}}, "required": ["ok"]},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def make_server(key: str) -> MCPServer:
|
||||
"""Every worker process: the same key, and the same name."""
|
||||
mcp = MCPServer("billing", request_state_security=RequestStateSecurity(keys=[key]))
|
||||
|
||||
@mcp.tool()
|
||||
async def refund(amount: int, ctx: Context) -> str | InputRequiredResult:
|
||||
"""Refund an amount, once a human has confirmed it."""
|
||||
if ctx.input_responses is None:
|
||||
return InputRequiredResult(input_requests={"ok": CONFIRM}, request_state=f"refund:{amount}")
|
||||
answer = (ctx.input_responses or {}).get("ok")
|
||||
if not isinstance(answer, ElicitResult) or answer.action != "accept" or not (answer.content or {}).get("ok"):
|
||||
return "refund cancelled"
|
||||
return f"refunded ${amount}"
|
||||
|
||||
return mcp
|
||||
23
docs_src/deploy/tutorial004.py
Normal file
23
docs_src/deploy/tutorial004.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from mcp.server.mcpserver import Context, MCPServer
|
||||
from mcp.server.subscriptions import SubscriptionBus
|
||||
|
||||
NOTES = {"todo": "buy milk"}
|
||||
|
||||
|
||||
def make_server(bus: SubscriptionBus) -> MCPServer:
|
||||
"""Every replica gets its own server object; all of them hold the same bus."""
|
||||
mcp = MCPServer("Notebook", subscriptions=bus)
|
||||
|
||||
@mcp.resource("note://{name}")
|
||||
def note(name: str) -> str:
|
||||
"""One note, by name."""
|
||||
return NOTES[name]
|
||||
|
||||
@mcp.tool()
|
||||
async def edit_note(name: str, text: str, ctx: Context) -> str:
|
||||
"""Replace a note's text."""
|
||||
NOTES[name] = text
|
||||
await ctx.notify_resource_updated(f"note://{name}")
|
||||
return "saved"
|
||||
|
||||
return mcp
|
||||
0
docs_src/legacy_clients/__init__.py
Normal file
0
docs_src/legacy_clients/__init__.py
Normal file
42
docs_src/legacy_clients/tutorial001.py
Normal file
42
docs_src/legacy_clients/tutorial001.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from typing import Annotated
|
||||
|
||||
from mcp_types import ElicitRequestParams, ElicitResult
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mcp import Client
|
||||
from mcp.client import ClientRequestContext
|
||||
from mcp.server import MCPServer
|
||||
from mcp.server.mcpserver import AcceptedElicitation, Elicit, ElicitationResult, Resolve
|
||||
|
||||
mcp = MCPServer("Bookshop")
|
||||
|
||||
|
||||
class Quantity(BaseModel):
|
||||
copies: int
|
||||
|
||||
|
||||
async def ask_quantity() -> Elicit[Quantity]:
|
||||
"""Resolver: ask the user how many copies to put aside."""
|
||||
return Elicit("How many copies?", Quantity)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def reserve(title: str, quantity: Annotated[ElicitationResult[Quantity], Resolve(ask_quantity)]) -> str:
|
||||
"""Reserve copies of a book, asking the user how many."""
|
||||
if isinstance(quantity, AcceptedElicitation):
|
||||
return f"Reserved {quantity.data.copies} of {title!r}."
|
||||
return "Nothing reserved."
|
||||
|
||||
|
||||
async def answer(context: ClientRequestContext, params: ElicitRequestParams) -> ElicitResult:
|
||||
return ElicitResult(action="accept", content={"copies": 2})
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with (
|
||||
Client(mcp, mode="legacy", elicitation_callback=answer) as legacy,
|
||||
Client(mcp, elicitation_callback=answer) as modern,
|
||||
):
|
||||
for client in (legacy, modern):
|
||||
result = await client.call_tool("reserve", {"title": "Dune"})
|
||||
print(client.protocol_version, result.structured_content)
|
||||
28
docs_src/legacy_clients/tutorial002.py
Normal file
28
docs_src/legacy_clients/tutorial002.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
from typing import Annotated
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mcp.server import MCPServer
|
||||
from mcp.server.mcpserver import AcceptedElicitation, Elicit, ElicitationResult, Resolve
|
||||
|
||||
mcp = MCPServer("Bookshop")
|
||||
|
||||
|
||||
class Quantity(BaseModel):
|
||||
copies: int
|
||||
|
||||
|
||||
async def ask_quantity() -> Elicit[Quantity]:
|
||||
"""Resolver: ask the user how many copies to put aside."""
|
||||
return Elicit("How many copies?", Quantity)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def reserve(title: str, quantity: Annotated[ElicitationResult[Quantity], Resolve(ask_quantity)]) -> str:
|
||||
"""Reserve copies of a book, asking the user how many."""
|
||||
if isinstance(quantity, AcceptedElicitation):
|
||||
return f"Reserved {quantity.data.copies} of {title!r}."
|
||||
return "Nothing reserved."
|
||||
|
||||
|
||||
app = mcp.streamable_http_app(stateless_http=True)
|
||||
21
docs_src/legacy_clients/tutorial003.py
Normal file
21
docs_src/legacy_clients/tutorial003.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
from mcp.server import MCPServer
|
||||
from mcp.server.mcpserver import Context
|
||||
|
||||
mcp = MCPServer("Bookshop")
|
||||
|
||||
STOCK = {"Dune": 3}
|
||||
|
||||
|
||||
@mcp.resource("stock://{title}")
|
||||
def stock(title: str) -> str:
|
||||
"""How many copies of one book are on the shelf."""
|
||||
return f"{STOCK[title]} in stock"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def restock(title: str, copies: int, ctx: Context) -> str:
|
||||
"""Put copies of a book back on the shelf."""
|
||||
STOCK[title] = STOCK.get(title, 0) + copies
|
||||
await ctx.notify_resource_updated(f"stock://{title}")
|
||||
await ctx.session.send_resource_updated(f"stock://{title}")
|
||||
return f"{STOCK[title]} in stock"
|
||||
0
docs_src/real_host/__init__.py
Normal file
0
docs_src/real_host/__init__.py
Normal file
34
docs_src/real_host/tutorial001.py
Normal file
34
docs_src/real_host/tutorial001.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from mcp.server import MCPServer
|
||||
|
||||
mcp = MCPServer("Bookshop")
|
||||
|
||||
CATALOG = {
|
||||
"Dune": "Frank Herbert",
|
||||
"Neuromancer": "William Gibson",
|
||||
"The Left Hand of Darkness": "Ursula K. Le Guin",
|
||||
}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def search_books(query: str) -> list[str]:
|
||||
"""Search the catalog by title or author."""
|
||||
needle = query.lower()
|
||||
return [title for title, author in CATALOG.items() if needle in title.lower() or needle in author.lower()]
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_author(title: str) -> str:
|
||||
"""Look up the author of a book in the catalog."""
|
||||
if title not in CATALOG:
|
||||
raise ValueError(f"No book titled {title!r} in the catalog.")
|
||||
return CATALOG[title]
|
||||
|
||||
|
||||
@mcp.resource("catalog://titles")
|
||||
def titles() -> str:
|
||||
"""Every title in the catalog, one per line."""
|
||||
return "\n".join(sorted(CATALOG))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
0
docs_src/troubleshooting/__init__.py
Normal file
0
docs_src/troubleshooting/__init__.py
Normal file
22
docs_src/troubleshooting/tutorial001.py
Normal file
22
docs_src/troubleshooting/tutorial001.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from mcp.server import MCPServer
|
||||
from mcp.server.mcpserver.exceptions import ResourceNotFoundError
|
||||
|
||||
mcp = MCPServer("Weather")
|
||||
|
||||
FORECASTS = {"London": "Rain.", "Cairo": "Sun."}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def forecast(city: str) -> str:
|
||||
"""Today's forecast for one city."""
|
||||
if city not in FORECASTS:
|
||||
raise ValueError(f"No forecast for {city!r}.")
|
||||
return FORECASTS[city]
|
||||
|
||||
|
||||
@mcp.resource("weather://{city}")
|
||||
def report(city: str) -> str:
|
||||
"""The full report for one city."""
|
||||
if city not in FORECASTS:
|
||||
raise ResourceNotFoundError(f"No forecast for {city!r}.")
|
||||
return f"{city}: {FORECASTS[city]}"
|
||||
15
docs_src/troubleshooting/tutorial002.py
Normal file
15
docs_src/troubleshooting/tutorial002.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from mcp.server import MCPServer
|
||||
|
||||
mcp = MCPServer("Weather")
|
||||
|
||||
|
||||
@mcp.tool(name="forecast")
|
||||
def forecast_today(city: str) -> str:
|
||||
"""Today's forecast for one city."""
|
||||
return f"{city}: Rain."
|
||||
|
||||
|
||||
@mcp.tool(name="forecast") # Same name. This registration is dropped.
|
||||
def forecast_hourly(city: str, hours: int) -> str:
|
||||
"""The next few hours for one city."""
|
||||
return f"{city}: Rain for {hours}h."
|
||||
12
docs_src/troubleshooting/tutorial003.py
Normal file
12
docs_src/troubleshooting/tutorial003.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from mcp.server import MCPServer
|
||||
|
||||
mcp = MCPServer("Weather")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def forecast(city: str) -> str:
|
||||
"""Today's forecast for one city."""
|
||||
return f"{city}: Rain."
|
||||
|
||||
|
||||
app = mcp.streamable_http_app()
|
||||
18
docs_src/troubleshooting/tutorial004.py
Normal file
18
docs_src/troubleshooting/tutorial004.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from mcp.server import MCPServer
|
||||
from mcp.server.transport_security import TransportSecuritySettings
|
||||
|
||||
mcp = MCPServer("Weather")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def forecast(city: str) -> str:
|
||||
"""Today's forecast for one city."""
|
||||
return f"{city}: Rain."
|
||||
|
||||
|
||||
app = mcp.streamable_http_app(
|
||||
transport_security=TransportSecuritySettings(
|
||||
allowed_hosts=["mcp.example.com", "mcp.example.com:*"],
|
||||
allowed_origins=["https://app.example.com"],
|
||||
)
|
||||
)
|
||||
16
docs_src/troubleshooting/tutorial005.py
Normal file
16
docs_src/troubleshooting/tutorial005.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from starlette.applications import Starlette
|
||||
from starlette.routing import Mount
|
||||
|
||||
from mcp.server import MCPServer
|
||||
|
||||
mcp = MCPServer("Weather")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def forecast(city: str) -> str:
|
||||
"""Today's forecast for one city."""
|
||||
return f"{city}: Rain."
|
||||
|
||||
|
||||
# The mount works. The MCP app's own lifespan never runs.
|
||||
app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app())])
|
||||
19
docs_src/troubleshooting/tutorial006.py
Normal file
19
docs_src/troubleshooting/tutorial006.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
from mcp.server import MCPServer
|
||||
from mcp.server.mcpserver import Context
|
||||
|
||||
mcp = MCPServer("Bistro")
|
||||
|
||||
|
||||
class Confirmation(BaseModel):
|
||||
confirm: bool
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def book_table(date: str, ctx: Context) -> str:
|
||||
"""Book a table at the bistro."""
|
||||
result = await ctx.elicit(f"Book a table for {date}?", schema=Confirmation)
|
||||
if result.action == "accept" and result.data.confirm:
|
||||
return f"Booked for {date}."
|
||||
return "No booking made."
|
||||
25
docs_src/troubleshooting/tutorial007.py
Normal file
25
docs_src/troubleshooting/tutorial007.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from typing import Annotated
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mcp.server import MCPServer
|
||||
from mcp.server.mcpserver import Elicit, Resolve
|
||||
|
||||
mcp = MCPServer("Bistro")
|
||||
|
||||
|
||||
class Confirmation(BaseModel):
|
||||
confirm: bool
|
||||
|
||||
|
||||
async def ask_to_confirm(date: str) -> Elicit[Confirmation]:
|
||||
"""Resolver: ask the user to confirm the booking."""
|
||||
return Elicit(f"Book a table for {date}?", Confirmation)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def book_table(date: str, answer: Annotated[Confirmation, Resolve(ask_to_confirm)]) -> str:
|
||||
"""Book a table at the bistro."""
|
||||
if answer.confirm:
|
||||
return f"Booked for {date}."
|
||||
return "No booking made."
|
||||
23
docs_src/troubleshooting/tutorial008.py
Normal file
23
docs_src/troubleshooting/tutorial008.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
from mcp.server import MCPServer
|
||||
from mcp.server.mcpserver import Context
|
||||
|
||||
mcp = MCPServer("Bistro")
|
||||
|
||||
|
||||
class Confirmation(BaseModel):
|
||||
confirm: bool
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def book_table(date: str, ctx: Context) -> str:
|
||||
"""Book a table at the bistro."""
|
||||
result = await ctx.elicit(f"Book a table for {date}?", schema=Confirmation)
|
||||
if result.action == "accept" and result.data.confirm:
|
||||
return f"Booked for {date}."
|
||||
return "No booking made."
|
||||
|
||||
|
||||
# Stateless HTTP: every request is its own world. No channel back to the client.
|
||||
app = mcp.streamable_http_app(stateless_http=True)
|
||||
Loading…
Reference in a new issue