Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions 03-GettingStarted/10-elicitation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Elicitation

Elicitation is a feature in MCP were you ask the user for more context. This is a powerful features as sometimes you don't have all the information from the start or sometimes a choice the user makes completely changes what should happen next. There's all kinds of scenarios where this is useful from booking scenarios to general workflows

## Scenarios

## Messages

## Server implementation

## Client implementation

## Assignment

## Quiz

## Summary
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions 03-GettingStarted/10-elicitation/code/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Please select runtime to run the code:

- [TypeScript](./typescript/README.md)
- [Python](./python/README.md)
93 changes: 93 additions & 0 deletions 03-GettingStarted/10-elicitation/code/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Run the code

This sample demonstrates the use of Elicitation

## Set up environment

```sh
python -m venv venv
source ./venv/bin/activate
```

## Run server

```sh
uvicorn server:app
```

You should see:

```text
INFO: Started server process [5016]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```

This should start a server on port 8000.

## Test out the server in VS Code

Add an entry like so to *mcp.json*:

```
"server": {
"type": "sse",
"url": "http://localhost:8000/sse"

}
```

and start the server. Now try typing the following prompt in the chat:

```text
Book trip on 2025-02-01
```

This prompt should trigger an Elicitation scenario where you're asked for more input. Typing "2025-01-01" should lead to a successful booking:

![Elicitation example VS Code](../../assets/elicitation.png)

## Run client

Run the following command:

```sh
python client.py
```

It should start the client and you should see the following output:

```text
Available tools: ['book_trip']
[CLIENT] Received elicitation data: No trips available on 2025-01-02. Would you like to try another date?
[CLIENT]: Selecting alternative date: 2025-01-01
Result: [SUCCESS] Booked for 2025-01-01
```

Let's highlight a piece of code, the client handler for elicitation as we're hardcoding the responses back to the server:

```python
async def elicitation_callback_handler(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
print(f"[CLIENT] Received elicitation data: {params.message}")

# 1. refuses no select other date
# return ElicitResult(action="accept", content={
# "checkAlternative": False
# }) # should say no booking made, WORKS

# 2. cancels booking
# return ElicitResult(action="decline"), WORKS

print("[CLIENT]: Selecting alternative date: 2025-01-01")

# 3. opts to select another date, 2025-01-01 which leads to a booking
return ElicitResult(action="accept", content={
"checkAlternative": True,
"alternativeDate": "2025-01-01"
}) # should book 1 jan instead of initial 2nd Jan
```

Here we are hardcoding back an "accept" response with a prefilled alternate date that the server will accept. We have also provided other response types like 1) User refuses to select a date which should lead to a response saying no booking has been made. 2) This response is more like the user dismisses the whole dialogue and also this leads to no booking taking place.

You're encouraged to improve this code by making this user-driven instead of hardcoded and also to try out the different responses to see the difference.
57 changes: 57 additions & 0 deletions 03-GettingStarted/10-elicitation/code/python/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# TODO add python client

import asyncio

from mcp import ClientSession
from mcp.client.sse import sse_client
from mcp.types import ElicitRequestParams, ElicitResult, TextContent
from mcp.shared.context import RequestContext

async def elicitation_callback_handler(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
print(f"[CLIENT] Received elicitation data: {params.message}")

# 1. refuses no select other date
# return ElicitResult(action="accept", content={
# "checkAlternative": False
# }) # should say no booking made, WORKS

# 2. cancels booking
# return ElicitResult(action="decline"), WORKS

print("[CLIENT]: Selecting alternative date: 2025-01-01")

# 3. opts to select another date, 2025-01-01 which leads to a booking
return ElicitResult(action="accept", content={
"checkAlternative": True,
"alternativeDate": "2025-01-01"
}) # should book 1 jan instead of initial 2nd Jan




async def main():
# Connect to a Server-Sent Events (SSE) server
async with sse_client(url="http://localhost:8000/sse") as (
read_stream,
write_stream
):
# Create a session using the client streams
async with ClientSession(
read_stream,
write_stream,
elicitation_callback=elicitation_callback_handler) as session:
# Initialize the connection
await session.initialize()
# List available tools
tools = await session.list_tools()
print(f"Available tools: {[tool.name for tool in tools.tools]}")

# call tool
result = await session.call_tool("book_trip", {
"date": "2025-01-02"
})
print("Result: ", result.content[0].text)


if __name__ == "__main__":
asyncio.run(main())
55 changes: 55 additions & 0 deletions 03-GettingStarted/10-elicitation/code/python/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from pydantic import BaseModel, Field

from mcp.server.fastmcp import Context, FastMCP
from mcp.server.session import ServerSession

from starlette.applications import Starlette
from starlette.routing import Mount, Host

mcp = FastMCP(name="Elicitation Example")

# todo: elicitation example, turn it into sse

class BookingPreferences(BaseModel):
"""Schema for collecting user preferences."""

checkAlternative: bool = Field(description="Would you like to check another date?")
alternativeDate: str = Field(
default="2024-12-26",
description="Alternative date (YYYY-MM-DD)",
)

def not_available_date(date: str) -> bool:
# Simulate date availability check
return date != "2024-12-25"


@mcp.tool()
async def book_trip(date: str, ctx: Context[ServerSession, None]) -> str:
"""Book a trip with date availability check."""
# Check if date is available
if not_available_date(date):
# Date unavailable - ask user for alternative
result = await ctx.elicit(
message=(f"No trips available on {date}. Would you like to try another date?"),
schema=BookingPreferences,
)

if result.action == "accept" and result.data:
if result.data.checkAlternative:
return f"[SUCCESS] Booked for {result.data.alternativeDate}"
return "[CANCELLED] No booking made"
return "[CANCELLED] Booking cancelled"

# Date available
return f"[SUCCESS] Booked for {date}"

app = Starlette(
routes=[
Mount('/', app=mcp.sse_app()),
]
)

if __name__ == "__main__":
print("Starting Elicitation Example MCP Server...")
mcp.run()
86 changes: 86 additions & 0 deletions 03-GettingStarted/10-elicitation/code/typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Run sample

Here's how to run this Elicitation sample.

## Install dependencies

```sh
npm install
```

## Run

Compile and run the server

```sh
npm run build
npm start
```

That should start a server on `http://localhost:3000`.

### Run via Visual Studio Code

Create an entry in `mcp.json` like so:

```json
"server-elicitation": {
"type": "sse",
"url": "http://localhost:3000/sse"

}
```

Run a prompt like so:

```text
Make a booking on the "2025-01-02"
```

You should see this trigger an elicitation as "2025-01-01" is the only available date. When you're asked for input select "true" and fill in "2025-01-01" this should state that a booking has been made on "2025-01-01".

![Elicitation in Visual Studio Code](../../assets/elicitation.png)

### Run the client

In a separate terminal, run the client

```sh
npm run client
```

```text
No trip available at on 2025-01-02. Would you like to check alternative dates?

Provide the following information:
[INPUT]: 'checkAlternatives' of type boolean
[INPUT]: 'newDate' of type string
Tool result: {
content: [
{ type: 'text', text: 'Trip booked on alternate date: 2025-01-01' }
]
}
```

NOTE: the client is hard coding a response to the server's eliciation and provide an "accept" message with a filled in date on "2025-01-01", you're recommended to change this code to ask the user for input. Look for this code to change it in *client.ts*:

```typescript
client.setRequestHandler(ElicitRequestSchema, (params) => {
console.log(`${params.params.message}`);
console.log("\nProvide the following information:");
let schema = params.params.requestedSchema.properties;
for (let key in schema) {
console.log(`[INPUT]: '${key}' of type ${schema[key].type}`);
}

// TODO, ask for this input instead of faking the response like below

return {
action: "accept",
content: {
checkAlternatives: true,
newDate: "2025-01-01"
},
};
});
```
Loading