Files
wehub-resource-sync b957a53def
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:21:23 +08:00

177 lines
6.1 KiB
Python

# /// script # noqa: CPY001
# dependencies = [
# "semantic-kernel[mcp]",
# ]
# ///
# Copyright (c) Microsoft. All rights reserved.
import argparse
import logging
from typing import Annotated, Any, Literal
import anyio
from azure.identity import AzureCliCredential
from semantic_kernel.agents import ChatCompletionAgent
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.functions import kernel_function
logger = logging.getLogger(__name__)
"""
This sample demonstrates how to expose an Agent as a MCP server.
To run this sample, set up your MCP host (like Claude Desktop or VSCode Github Copilot Agents)
with the following configuration:
```json
{
"mcpServers": {
"sk": {
"command": "uv",
"args": [
"--directory=<path to sk project>/semantic-kernel/python/samples/demos/mcp_server",
"run",
"agent_mcp_server.py"
],
"env": {
"AZURE_AI_AGENT_PROJECT_CONNECTION_STRING": "<your azure connection string>",
"AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME": "<your azure model deployment name>",
}
}
}
}
```
Alternatively, you can run this as a SSE server, by setting the same environment variables as above,
and running the following command:
```bash
uv --directory=<path to sk project>/semantic-kernel/python/samples/demos/mcp_server \
run agent_mcp_server.py --transport sse --port 8000
```
This will start a server that listens for incoming requests on port 8000.
In both cases, uv will make sure to install semantic-kernel with the mcp extra for you in a temporary venv.
"""
def parse_arguments():
parser = argparse.ArgumentParser(description="Run the Semantic Kernel MCP server.")
parser.add_argument(
"--transport",
type=str,
choices=["sse", "stdio"],
default="stdio",
help="Transport method to use (default: stdio).",
)
parser.add_argument(
"--port",
type=int,
default=None,
help="Port to use for SSE transport (required if transport is 'sse').",
)
return parser.parse_args()
# Define a simple plugin for the sample
class RestaurantPlugin:
"""A sample Menu Plugin used for the sample."""
@kernel_function(description="List the available restaurants.")
def list_restaurants(self) -> Annotated[str, "Returns a list of available restaurants."]:
return """
1. The Farm: a classic steakhouse with a rustic atmosphere.
2. The Harbor: a seafood restaurant with a view of the ocean.
3. The Joint: a casual eatery with a diverse menu.
"""
@kernel_function(description="Provides a list of specials from the menu.")
def get_specials(
self, restaurant: Literal["The Farm, The Harbor, The Joint"]
) -> Annotated[str, "Returns the specials from the menu."]:
match restaurant:
case "The Farm":
return """
Special Entree: T-bone steak
Special Salad: Caesar Salad
Special Drink: Old Fashioned
"""
case "The Harbor":
return """
Special Soup: Lobster Bisque
Special Salad: Cobb Salad
Special Drink: Mai Tai
"""
case "The Joint":
return """
Special Burger: Avocado and Jalapeno Burger
Special Salad: Greek Salad
Special Drink: Milkshake Strawberry
"""
case _:
return "No specials available for this restaurant."
@kernel_function(description="Provides the price of the requested menu item.")
def get_item_price(
self,
restaurant: Literal["The Farm, The Harbor, The Joint"],
menu_item: Annotated[str, "The name of the menu item."],
) -> Annotated[str, "Returns the price of the menu item."]:
match restaurant:
case "The Farm":
return "$9.99"
case "The Harbor":
return "$12.99"
case "The Joint":
return "$8.99"
case _:
return "No price available for this restaurant."
async def run(transport: Literal["sse", "stdio"] = "stdio", port: int | None = None) -> None:
agent = ChatCompletionAgent(
service=AzureChatCompletion(credential=AzureCliCredential()),
name="Host",
instructions="Answer questions about the menu for different restaurants, use the list_restaurants function "
"to get the list of restaurants.",
plugins=[RestaurantPlugin()], # add the sample plugin to the agent
)
server = agent.as_mcp_server()
if transport == "sse" and port is not None:
import nest_asyncio
import uvicorn
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Mount, Route
sse = SseServerTransport("/messages/")
async def handle_sse(request):
async with sse.connect_sse(request.scope, request.receive, request._send) as (
read_stream,
write_stream,
):
await server.run(read_stream, write_stream, server.create_initialization_options())
starlette_app = Starlette(
debug=True,
routes=[
Route("/sse", endpoint=handle_sse),
Mount("/messages/", app=sse.handle_post_message),
],
)
nest_asyncio.apply()
uvicorn.run(starlette_app, host="0.0.0.0", port=port) # nosec
elif transport == "stdio":
from mcp.server.stdio import stdio_server
async def handle_stdin(stdin: Any | None = None, stdout: Any | None = None) -> None:
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream, server.create_initialization_options())
await handle_stdin()
if __name__ == "__main__":
args = parse_arguments()
anyio.run(run, args.transport, args.port)