chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# Study Plan Generator with Chainlit & Microsoft Learn Docs MCP
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.8 or higher
|
||||
- pip (Python package manager)
|
||||
- Internet access to connect to the Microsoft Learn Docs MCP server
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone this repository or download the project files.
|
||||
2. Install the required dependencies:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Scenario 1: Simple Query to Docs MCP
|
||||
A command-line client that connects to the Docs MCP server, sends a query, and prints the result.
|
||||
|
||||
1. Run the script:
|
||||
```bash
|
||||
python scenario1.py
|
||||
```
|
||||
2. Enter your documentation question at the prompt.
|
||||
|
||||
### Scenario 2: Study Plan Generator (Chainlit Web App)
|
||||
A web-based interface (using Chainlit) that allows users to generate a personalized, week-by-week study plan for any technical topic.
|
||||
|
||||
1. Start the Chainlit app:
|
||||
```bash
|
||||
chainlit run scenario2.py
|
||||
```
|
||||
2. Open the local URL provided in your terminal (e.g., http://localhost:8000) in your browser.
|
||||
3. In the chat window, enter your study topic and the number of weeks you want to study (e.g., "AI-900 certification, 8 weeks").
|
||||
4. The app will respond with a week-by-week study plan, including links to relevant Microsoft Learn documentation.
|
||||
|
||||
**Environment Variables Required:**
|
||||
|
||||
To use Scenario 2 (the Chainlit web app with Azure OpenAI), you must set the following environment variables in a `.env` file in the `python` directory:
|
||||
|
||||
```
|
||||
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=
|
||||
AZURE_OPENAI_API_KEY=
|
||||
AZURE_OPENAI_ENDPOINT=
|
||||
AZURE_OPENAI_API_VERSION=
|
||||
```
|
||||
|
||||
Fill in these values with your Azure OpenAI resource details before running the app.
|
||||
|
||||
> [!TIP]
|
||||
> You can easily deploy your own models using [Microsoft Foundry](https://ai.azure.com/).
|
||||
|
||||
### Scenario 3: In-Editor Docs with MCP Server in VS Code
|
||||
|
||||
Instead of switching browser tabs to search documentation, you can bring Microsoft Learn Docs directly into your VS Code using the MCP server. This enables you to:
|
||||
- Search and read docs inside VS Code without leaving your coding environment.
|
||||
- Reference documentation and insert links directly into your README or course files.
|
||||
- Use GitHub Copilot and MCP together for a seamless, AI-powered documentation workflow.
|
||||
|
||||
**Example Use Cases:**
|
||||
- Quickly add reference links to a README while writing a course or project documentation.
|
||||
- Use Copilot to generate code and MCP to instantly find and cite relevant docs.
|
||||
- Stay focused in your editor and boost productivity.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Ensure you have a valid [`mcp.json`](../scenario3/mcp.json) configuration in your workspace (location is `.vscode/mcp.json`).
|
||||
|
||||
## Why Chainlit for Scenario 2?
|
||||
|
||||
Chainlit is a modern open-source framework for building conversational web applications. It makes it easy to create chat-based user interfaces that connect to backend services like the Microsoft Learn Docs MCP server. This project uses Chainlit to provide a simple, interactive way to generate personalized study plans in real time. By leveraging Chainlit, you can quickly build and deploy chat-based tools that enhance productivity and learning.
|
||||
|
||||
## What This Does
|
||||
|
||||
This app allows users to create a personalized study plan by simply entering a topic and a duration. The app parses your input, queries the Microsoft Learn Docs MCP server for relevant content, and organizes the results into a structured, week-by-week plan. Each week’s recommendations are displayed in the chat, making it easy to follow and track your progress. The integration ensures you always get the latest, most relevant learning resources.
|
||||
|
||||
## Sample Queries
|
||||
|
||||
Try these queries in the chat window to see how the app responds:
|
||||
|
||||
- `AI-900 certification, 8 weeks`
|
||||
- `Learn Azure Functions, 4 weeks`
|
||||
- `Azure DevOps, 6 weeks`
|
||||
- `Data engineering on Azure, 10 weeks`
|
||||
- `Microsoft security fundamentals, 5 weeks`
|
||||
- `Power Platform, 7 weeks`
|
||||
- `Azure AI services, 12 weeks`
|
||||
- `Cloud architecture, 9 weeks`
|
||||
|
||||
These examples demonstrate the flexibility of the app for different learning goals and timeframes.
|
||||
|
||||
## References
|
||||
|
||||
- [Chainlit Documentation](https://docs.chainlit.io/)
|
||||
- [MCP Documentation](https://github.com/MicrosoftDocs/mcp)
|
||||
@@ -0,0 +1,8 @@
|
||||
chainlit
|
||||
mcp
|
||||
semantic-kernel
|
||||
|
||||
# Security pin: force the patched Werkzeug (a transitive dependency) to resolve
|
||||
# the safe_join Windows device-name DoS advisories
|
||||
# CVE-2025-66221 / CVE-2026-21860 / CVE-2026-27199 (fixed in 3.1.6)
|
||||
werkzeug>=3.1.6
|
||||
@@ -0,0 +1,88 @@
|
||||
# Scenario 1: Simple query to Docs MCP
|
||||
# This script demonstrates how to connect to the Microsoft Learn Docs MCP server,
|
||||
# send a query, and print the result.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
from mcp import ClientSession
|
||||
|
||||
import json
|
||||
|
||||
# Microsoft Docs MCP Server Endpoint
|
||||
MCP_SERVER_URL = "https://learn.microsoft.com/api/mcp"
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger('mcp_client')
|
||||
|
||||
def prompt_user():
|
||||
"""Prompt user for a search query."""
|
||||
print("Type your Microsoft Docs search query (or 'exit' to quit):")
|
||||
try:
|
||||
return input("> ").strip()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print("\nDetected exit signal.")
|
||||
return "exit"
|
||||
|
||||
async def main():
|
||||
logger.info("Connecting to Microsoft Docs MCP Server at: %s", MCP_SERVER_URL)
|
||||
try:
|
||||
async with streamablehttp_client(MCP_SERVER_URL) as (read_stream, write_stream, _):
|
||||
logger.info("Connection established. Initializing MCP session.")
|
||||
async with ClientSession(read_stream, write_stream) as session:
|
||||
await session.initialize()
|
||||
logger.info("Session initialized successfully.")
|
||||
print("\n=== Microsoft Docs MCP Client ===")
|
||||
print("Tool: microsoft_docs_search")
|
||||
print("Enter documentation queries to search Microsoft Learn docs.")
|
||||
print("Type 'exit' or 'quit' to end the session.\n")
|
||||
|
||||
while True:
|
||||
user_query = prompt_user()
|
||||
if not user_query:
|
||||
print("Query cannot be empty. Please try again.")
|
||||
continue
|
||||
if user_query.lower() in ("exit", "quit"):
|
||||
print("Exiting client. Goodbye!")
|
||||
break
|
||||
|
||||
try:
|
||||
logger.info("Executing query: %s", user_query)
|
||||
result = await session.call_tool("microsoft_docs_search", {"question": user_query})
|
||||
# print("RESULT:", result)
|
||||
print("\n--- Search Result ---")
|
||||
if hasattr(result, 'content'):
|
||||
# print(result.content)
|
||||
for item in result.content:
|
||||
my_list = json.loads(item.text)
|
||||
for doc in my_list:
|
||||
print(f"[Title]: {doc.get('title', 'No title')}")
|
||||
print(f"[Content]: {doc.get('content', 'No content')}")
|
||||
print("---")
|
||||
|
||||
else:
|
||||
print("No content returned from the search.")
|
||||
print("---------------------\n")
|
||||
except Exception as e:
|
||||
logger.error("Query failed: %s", e)
|
||||
print(f"Error: {e}. Please try a different query or check your connection.\n")
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Connection error: %s", e)
|
||||
print(f"Failed to connect to Microsoft Docs MCP Server: {e}")
|
||||
print("Please check your internet connection and try again.")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Starting Microsoft Docs MCP Client...")
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
print("\nApplication terminated by user.")
|
||||
sys.exit(0)
|
||||
@@ -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