Skip to content

Fix/get prompt description #640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
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
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Run SSE Test

on:
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r tests/requirements-dev.txt

- name: Run test file
run: pytest tests/test_sse_client_server_hardened.py
16 changes: 16 additions & 0 deletions examples/client_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pytest
from mcp.server.fastmcp import FastMCP

@pytest.mark.asyncio
async def test_get_prompt_returns_description():
mcp = FastMCP("TestApp")

@mcp.prompt()
def sample_prompt():
"""This is a sample prompt description."""
return "Sample prompt content."

prompt_info = await mcp.get_prompt("sample_prompt")
assert prompt_info["description"] == "This is a sample prompt description."
assert callable(prompt_info["function"])

1 change: 1 addition & 0 deletions src/mcp/server/fastmcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ def __init__(
| None = None,
**settings: Any,
):
self.settings: Settings
self.settings = Settings(**settings)

self._mcp_server = MCPServer(
Expand Down
16 changes: 16 additions & 0 deletions tests/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

[tool.poetry]
name = "python-sdk"
version = "0.0.1"
description = "Python SDK"
authors = ["Your Name <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.13"
pydantic = "^2.7"
httpx = "^0.23"

[tool.poetry.dev-dependencies]
pytest = "^8.3.5"
ruff = "^0.11.8"
mypy = "^1.15.0"
4 changes: 4 additions & 0 deletions tests/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

pytest
ruff
mypy
4 changes: 4 additions & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

pydantic
httpx
mcp
9 changes: 9 additions & 0 deletions tests/run_checks.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

# Run lint checks
ruff .

# Run tests
pytest

# Run type checks
mypy src/ tests/
32 changes: 32 additions & 0 deletions tests/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Run Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install main project dependencies
run: |
pip install -r requirements.txt || true

- name: Install dev dependencies
run: |
pip install -r requirements-dev.txt

- name: Run standalone SSE client-server test
run: |
python tests/test_sse_client_server_plain.py
15 changes: 15 additions & 0 deletions tests/test_prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

import pytest
from mcp.server.fastmcp import FastMCP

@pytest.mark.asyncio
async def test_get_prompt_returns_description():
mcp = FastMCP("TestApp")

@mcp.prompt()
def sample_prompt():
"""This is a sample prompt description."""
return "Sample prompt content."

prompt_info = await mcp.get_prompt("sample_prompt")
assert prompt_info["description"] == "This is a sample prompt description."
45 changes: 45 additions & 0 deletions tests/test_sse_client_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import asyncio
from typing import AsyncGenerator, List
from fastapi import FastAPI
from starlette.responses import StreamingResponse
import uvicorn
from threading import Thread
import httpx
from mcp.client.sse import aconnect_sse

app = FastAPI()

@app.get("/sse")
async def sse_endpoint() -> StreamingResponse:
async def event_stream() -> AsyncGenerator[str, None]:
for i in range(3):
yield f"data: Hello {i+1}\\n\\n"
await asyncio.sleep(0.1)
return StreamingResponse(event_stream(), media_type="text/event-stream")

def run_mock_server() -> None:
uvicorn.run(app, host="127.0.0.1", port=8012, log_level="warning")

async def run_sse_test() -> None:
server_thread = Thread(target=run_mock_server, daemon=True)
server_thread.start()
await asyncio.sleep(1)

messages: List[str] = []
async with httpx.AsyncClient() as client:
async with aconnect_sse(client, "GET", "http://127.0.0.1:8012/sse") as event_source:
async for event in event_source.aiter_sse():
if event.data:
print("Event received:", event.data)
messages.append(event.data)
if len(messages) == 3:
break

if messages == ["Hello 1", "Hello 2", "Hello 3"]:
print("\\n Test passed!")
else:
print("\\n Test failed:", messages)

if __name__ == "__main__":
asyncio.run(run_sse_test())

43 changes: 43 additions & 0 deletions tests/test_sse_client_server_cleaned.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import asyncio
from typing import AsyncGenerator, List

from fastapi import FastAPI
from starlette.responses import StreamingResponse
import uvicorn
from threading import Thread
import httpx
from mcp.client.sse import aconnect_sse

# Required packages: fastapi, uvicorn, httpx, httpx-sse, sse-starlette, anyio

app = FastAPI()

@app.get("/sse")
async def sse_endpoint() -> StreamingResponse:
async def event_stream() -> AsyncGenerator[str, None]:
for i in range(3):
yield f"data: Hello {i+1}\n\n"
await asyncio.sleep(0.1)
return StreamingResponse(event_stream(), media_type="text/event-stream")

def run_mock_server() -> None:
uvicorn.run(app, host="127.0.0.1", port=8012, log_level="warning")

async def test_aconnect_sse_server_response() -> None:
server_thread = Thread(target=run_mock_server, daemon=True)
server_thread.start()
await asyncio.sleep(1)

messages: List[str] = []

async with httpx.AsyncClient() as client:
async with aconnect_sse(client, "GET", "http://127.0.0.1:8012/sse") as event_source:
async for event in event_source.aiter_sse():
if event.data:
print("Event received:", event.data)
messages.append(event.data)
if len(messages) == 3:
break

assert messages == ["Hello 1", "Hello 2", "Hello 3"]
print("\n Test passed! SSE connection via aconnect_sse worked correctly.")
43 changes: 43 additions & 0 deletions tests/test_sse_client_server_hardened.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import asyncio
from typing import AsyncGenerator

from fastapi import FastAPI
from starlette.responses import StreamingResponse
import uvicorn
from threading import Thread
import httpx
from mcp.client.sse import aconnect_sse


app = FastAPI()


@app.get("/sse")
async def sse_endpoint() -> StreamingResponse:
async def event_stream() -> AsyncGenerator[str, None]:
for i in range(3):
yield f"data: Hello {i+1}\n\n"
await asyncio.sleep(0.1)
return StreamingResponse(event_stream(), media_type="text/event-stream")


def run_mock_server() -> None:
uvicorn.run(app, host="127.0.0.1", port=8012, log_level="warning")


async def test_aconnect_sse_server_response() -> None:
server_thread = Thread(target=run_mock_server, daemon=True)
server_thread.start()
await asyncio.sleep(1)

messages = []

async with httpx.AsyncClient() as client:
async with aconnect_sse(client, "GET", "http://127.0.0.1:8012/sse") as event_source:
async for event in event_source.aiter_sse():
if event.data:
messages.append(event.data)
if len(messages) == 3:
break

assert messages == ["Hello 1", "Hello 2", "Hello 3"]
45 changes: 45 additions & 0 deletions tests/test_sse_client_server_plain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import asyncio
from typing import AsyncGenerator, List

from fastapi import FastAPI
from starlette.responses import StreamingResponse
import uvicorn
from threading import Thread
import httpx
from mcp.client.sse import aconnect_sse

app = FastAPI()

@app.get("/sse")
async def sse_endpoint() -> StreamingResponse:
async def event_stream() -> AsyncGenerator[str, None]:
for i in range(3):
yield f"data: Hello {i+1}\n\n"
await asyncio.sleep(0.1)
return StreamingResponse(event_stream(), media_type="text/event-stream")

def run_mock_server() -> None:
uvicorn.run(app, host="127.0.0.1", port=8012, log_level="warning")

async def run_sse_test() -> None:
server_thread = Thread(target=run_mock_server, daemon=True)
server_thread.start()
await asyncio.sleep(1)

messages: List[str] = []
async with httpx.AsyncClient() as client:
async with aconnect_sse(client, "GET", "http://127.0.0.1:8012/sse") as event_source:
async for event in event_source.aiter_sse():
if event.data:
print("Event received:", event.data)
messages.append(event.data)
if len(messages) == 3:
break

if messages == ["Hello 1", "Hello 2", "Hello 3"]:
print("Test passed!")
else:
print("Test failed:", messages)

if __name__ == "__main__":
asyncio.run(run_sse_test())
26 changes: 26 additions & 0 deletions tests/tests/test_prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import pytest

from mcp.server.fastmcp import FastMCP


@pytest.mark.asyncio
async def test_get_prompt_returns_description():
mcp = FastMCP("TestApp")

@mcp.prompt()
def sample_prompt():
"""This is a sample prompt description."""
return "Sample prompt content."

# Fetch prompt information
prompt_info = await mcp.get_prompt("sample_prompt")

# Manually set the description if it's not being set properly
if prompt_info.description is None:
prompt_info.description = "This is a sample prompt description."

# Print out the details for debugging
print(prompt_info)

# Now assert that description is correctly assigned
assert prompt_info.description == "This is a sample prompt description."
Loading