# /// 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=/semantic-kernel/python/samples/demos/mcp_server", "run", "agent_mcp_server.py" ], "env": { "AZURE_AI_AGENT_PROJECT_CONNECTION_STRING": "", "AZURE_AI_AGENT_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=/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)