chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# Scenario 2: Integrate Docs MCP into a web development project
|
||||
# This script demonstrates how to use Chainlit to build a conversational web app
|
||||
# that queries the Microsoft Learn Docs MCP server.
|
||||
|
||||
import chainlit as cl
|
||||
import logging
|
||||
import json
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
from mcp import ClientSession
|
||||
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from azure.core.credentials import AzureKeyCredential
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.functions import kernel_function
|
||||
from semantic_kernel.agents import ChatCompletionAgent
|
||||
|
||||
|
||||
MCP_SERVER_URL = "https://learn.microsoft.com/api/mcp"
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger('mcp_client')
|
||||
|
||||
# MCP Docs Plugin as a Semantic Kernel plugin
|
||||
class MCPDocsPlugin:
|
||||
def __init__(self, mcp_server_url):
|
||||
self.mcp_server_url = mcp_server_url
|
||||
|
||||
@kernel_function(name="search_docs", description="Search Microsoft Docs using MCP")
|
||||
async def search_docs(self, question: str) -> str:
|
||||
async with streamablehttp_client(self.mcp_server_url) as (read_stream, write_stream, _):
|
||||
async with ClientSession(read_stream, write_stream) as session:
|
||||
await session.initialize()
|
||||
result = await session.call_tool("microsoft_docs_search", {"question": question})
|
||||
output = []
|
||||
if hasattr(result, 'content'):
|
||||
for item in result.content:
|
||||
try:
|
||||
my_list = json.loads(item.text)
|
||||
for doc in my_list:
|
||||
title = doc.get('title', 'No title')
|
||||
content = doc.get('content', 'No content')
|
||||
output.append(f"**{title}**\n{content}")
|
||||
except Exception:
|
||||
output.append(item.text)
|
||||
return "\n".join(output)
|
||||
else:
|
||||
return "No content returned from the search."
|
||||
|
||||
# Register the MCP Docs search as a function
|
||||
async def mcp_docs_search(question: str):
|
||||
async with streamablehttp_client(MCP_SERVER_URL) as (read_stream, write_stream, _):
|
||||
async with ClientSession(read_stream, write_stream) as session:
|
||||
await session.initialize()
|
||||
result = await session.call_tool("microsoft_docs_search", {"question": question})
|
||||
output = []
|
||||
if hasattr(result, 'content'):
|
||||
for item in result.content:
|
||||
try:
|
||||
my_list = json.loads(item.text)
|
||||
for doc in my_list:
|
||||
title = doc.get('title', 'No title')
|
||||
content = doc.get('content', 'No content')
|
||||
output.append(f"**{title}**\n{content}")
|
||||
except Exception:
|
||||
output.append(item.text)
|
||||
return "\n".join(output)
|
||||
else:
|
||||
return "No content returned from the search."
|
||||
|
||||
@cl.on_chat_start
|
||||
async def start():
|
||||
await cl.Message(content="Welcome! Enter your Microsoft Docs search query below.").send()
|
||||
kernel = Kernel()
|
||||
service_id = "agent"
|
||||
kernel.add_service(AzureChatCompletion(service_id=service_id))
|
||||
from semantic_kernel.connectors.ai import FunctionChoiceBehavior
|
||||
settings = kernel.get_prompt_execution_settings_from_service_id(service_id=service_id)
|
||||
settings.function_choice_behavior = FunctionChoiceBehavior.Auto()
|
||||
# Register the MCPDocsPlugin
|
||||
mcp_plugin = MCPDocsPlugin(MCP_SERVER_URL)
|
||||
kernel.add_plugin(mcp_plugin, plugin_name="MCPDocs")
|
||||
# Create the agent
|
||||
agent = ChatCompletionAgent(
|
||||
service=AzureChatCompletion(),
|
||||
name="DocsAgent",
|
||||
instructions="You are a helpful assistant that uses the MCPDocs plugin to answer Microsoft Docs questions. Format your answers clearly.",
|
||||
plugins=[mcp_plugin]
|
||||
)
|
||||
cl.user_session.set("kernel", kernel)
|
||||
cl.user_session.set("agent", agent)
|
||||
cl.user_session.set("settings", settings)
|
||||
|
||||
@cl.on_message
|
||||
async def handle_message(message: cl.Message):
|
||||
agent = cl.user_session.get("agent")
|
||||
user_query = message.content.strip()
|
||||
if not user_query:
|
||||
await cl.Message(content="Query cannot be empty. Please try again.").send()
|
||||
return
|
||||
answer = cl.Message(content="Processing your request...")
|
||||
await answer.send()
|
||||
try:
|
||||
response_printed = False
|
||||
async for content in agent.invoke(user_query):
|
||||
msg = content.content
|
||||
if hasattr(msg, "content"):
|
||||
msg = msg.content
|
||||
if msg:
|
||||
await answer.stream_token(str(msg))
|
||||
response_printed = True
|
||||
if not response_printed:
|
||||
await answer.stream_token("No response generated by the agent.\n")
|
||||
await answer.update()
|
||||
except Exception as e:
|
||||
await answer.stream_token(f"\n\n❌ Error: {str(e)}\n\n")
|
||||
await answer.update()
|
||||
Reference in New Issue
Block a user