|
23 | 23 | import json |
24 | 24 | import random |
25 | 25 | from pathlib import Path |
26 | | -from typing import Optional |
| 26 | +from typing import Optional, Literal |
27 | 27 | from collections import Counter |
| 28 | +from dataclasses import dataclass |
28 | 29 |
|
29 | | -from fastmcp import FastMCP |
| 30 | +from fastmcp import FastMCP, Context |
30 | 31 |
|
31 | 32 | # Initialize FastMCP server |
32 | 33 | mcp = FastMCP( |
33 | | - "Copilot Tips Server", |
34 | | - description="MCP server for GitHub Copilot tips and tricks - O'Reilly teaching example" |
| 34 | + name="Copilot Tips Server", |
| 35 | + instructions="MCP server for GitHub Copilot tips and tricks. Use tools to search, retrieve, and manage tips. Use resources to get categories and statistics." |
35 | 36 | ) |
36 | 37 |
|
37 | 38 | # Load tips data |
@@ -391,67 +392,143 @@ def learning_path(current_skill_level: str) -> str: |
391 | 392 |
|
392 | 393 |
|
393 | 394 | # ============================================================================= |
394 | | -# ELICITATIONS (Basic examples - client support varies) |
| 395 | +# ELICITATIONS - Interactive tools that prompt users for input |
395 | 396 | # ============================================================================= |
396 | 397 |
|
397 | | -# Note: Elicitations are a newer MCP feature with limited client support. |
398 | | -# These are included as teaching examples but may fall back to prompts. |
| 398 | +# Dataclass for structured tip finder input |
| 399 | +@dataclass |
| 400 | +class TipFinderPreferences: |
| 401 | + """User preferences for finding tips.""" |
| 402 | + difficulty: str |
| 403 | + category: str |
399 | 404 |
|
400 | | -@mcp.prompt() |
401 | | -def interactive_tip_finder() -> str: |
| 405 | + |
| 406 | +@mcp.tool |
| 407 | +async def interactive_tip_finder(ctx: Context) -> dict: |
402 | 408 | """ |
403 | | - Interactive prompt that guides users through finding the right tip. |
| 409 | + Interactively gather user preferences and find matching tips. |
404 | 410 |
|
405 | | - This serves as a fallback for elicitations where client support is limited. |
| 411 | + Uses MCP elicitation to prompt the user for their experience level |
| 412 | + and area of interest, then returns relevant tips. |
| 413 | +
|
| 414 | + Note: Requires client support for elicitations. |
406 | 415 | """ |
407 | | - return """Let me help you find the perfect GitHub Copilot tip! |
| 416 | + # Step 1: Get difficulty preference |
| 417 | + difficulty_result = await ctx.elicit( |
| 418 | + message="What's your experience level with GitHub Copilot?", |
| 419 | + response_type=Literal["beginner", "intermediate", "advanced"] |
| 420 | + ) |
| 421 | + |
| 422 | + if difficulty_result.action != "accept": |
| 423 | + return { |
| 424 | + "success": False, |
| 425 | + "message": "Tip finder cancelled - no difficulty selected" |
| 426 | + } |
408 | 427 |
|
409 | | -Please answer these questions: |
| 428 | + difficulty = difficulty_result.data |
| 429 | + |
| 430 | + # Step 2: Get category preference |
| 431 | + category_result = await ctx.elicit( |
| 432 | + message="Which area would you like tips for?", |
| 433 | + response_type=Literal[ |
| 434 | + "Prompting Techniques", |
| 435 | + "IDE Shortcuts", |
| 436 | + "Code Generation", |
| 437 | + "Chat Features", |
| 438 | + "Workspace Context", |
| 439 | + "Security & Privacy" |
| 440 | + ] |
| 441 | + ) |
| 442 | + |
| 443 | + if category_result.action != "accept": |
| 444 | + return { |
| 445 | + "success": False, |
| 446 | + "message": "Tip finder cancelled - no category selected" |
| 447 | + } |
410 | 448 |
|
411 | | -1. **What's your experience level?** |
412 | | - - Beginner (just getting started) |
413 | | - - Intermediate (comfortable with basics) |
414 | | - - Advanced (looking for power-user tips) |
| 449 | + category = category_result.data |
415 | 450 |
|
416 | | -2. **What area are you interested in?** |
417 | | - - Prompting Techniques (writing better prompts) |
418 | | - - IDE Shortcuts (keyboard efficiency) |
419 | | - - Code Generation (getting better code output) |
420 | | - - Chat Features (using Copilot Chat effectively) |
421 | | - - Workspace Context (leveraging project context) |
422 | | - - Security & Privacy (safe Copilot usage) |
| 451 | + # Step 3: Find matching tips |
| 452 | + tips = get_tips_store() |
| 453 | + matching = [ |
| 454 | + t for t in tips |
| 455 | + if t["difficulty"] == difficulty and t["category"] == category |
| 456 | + ] |
423 | 457 |
|
424 | | -3. **What's your specific goal right now?** |
425 | | - (Describe what you're trying to accomplish) |
| 458 | + if not matching: |
| 459 | + # Fall back to just category match |
| 460 | + matching = [t for t in tips if t["category"] == category] |
426 | 461 |
|
427 | | -Once you answer, I'll search for the most relevant tips and provide personalized recommendations!""" |
| 462 | + return { |
| 463 | + "success": True, |
| 464 | + "preferences": { |
| 465 | + "difficulty": difficulty, |
| 466 | + "category": category |
| 467 | + }, |
| 468 | + "tips": matching[:5], |
| 469 | + "total_matches": len(matching) |
| 470 | + } |
428 | 471 |
|
429 | 472 |
|
430 | | -@mcp.prompt() |
431 | | -def quiz_me(category: Optional[str] = None) -> str: |
| 473 | +@mcp.tool |
| 474 | +async def guided_random_tip(ctx: Context) -> dict: |
432 | 475 | """ |
433 | | - Generate a quiz prompt to test knowledge of Copilot tips. |
| 476 | + Get a random tip with optional guided filtering via elicitation. |
434 | 477 |
|
435 | | - Args: |
436 | | - category: Optional category to focus the quiz on |
| 478 | + Prompts the user to optionally filter by category before |
| 479 | + returning a random tip. |
437 | 480 |
|
438 | | - Returns: |
439 | | - A formatted quiz prompt. |
| 481 | + Note: Requires client support for elicitations. |
440 | 482 | """ |
441 | | - category_text = f' in the "{category}" category' if category else "" |
| 483 | + # Ask if user wants to filter |
| 484 | + filter_result = await ctx.elicit( |
| 485 | + message="Would you like to filter tips by category, or get a completely random tip?", |
| 486 | + response_type=Literal["filter by category", "completely random"] |
| 487 | + ) |
442 | 488 |
|
443 | | - return f"""Let's test your GitHub Copilot knowledge{category_text}! |
| 489 | + if filter_result.action != "accept": |
| 490 | + return { |
| 491 | + "success": False, |
| 492 | + "message": "Cancelled" |
| 493 | + } |
444 | 494 |
|
445 | | -I'll ask you questions about best practices and tips. For each question: |
446 | | -1. I'll describe a scenario |
447 | | -2. You tell me which tip or technique would be most helpful |
448 | | -3. I'll provide feedback and explain the best approach |
| 495 | + tips = get_tips_store() |
449 | 496 |
|
450 | | -Ready? Let's start with an easy one! |
| 497 | + if filter_result.data == "filter by category": |
| 498 | + # Get category preference |
| 499 | + category_result = await ctx.elicit( |
| 500 | + message="Select a category:", |
| 501 | + response_type=Literal[ |
| 502 | + "Prompting Techniques", |
| 503 | + "IDE Shortcuts", |
| 504 | + "Code Generation", |
| 505 | + "Chat Features", |
| 506 | + "Workspace Context", |
| 507 | + "Security & Privacy" |
| 508 | + ] |
| 509 | + ) |
| 510 | + |
| 511 | + if category_result.action != "accept": |
| 512 | + return { |
| 513 | + "success": False, |
| 514 | + "message": "Category selection cancelled" |
| 515 | + } |
451 | 516 |
|
452 | | -**Question 1:** You're writing a new function but Copilot's suggestions don't match what you need. What's the FIRST thing you should try? |
| 517 | + tips = [t for t in tips if t["category"] == category_result.data] |
453 | 518 |
|
454 | | -(Answer, and I'll give you feedback and move to the next question!)""" |
| 519 | + if not tips: |
| 520 | + return { |
| 521 | + "success": False, |
| 522 | + "error": f"No tips in category '{category_result.data}'" |
| 523 | + } |
| 524 | + |
| 525 | + selected = random.choice(tips) |
| 526 | + |
| 527 | + return { |
| 528 | + "success": True, |
| 529 | + "tip": selected, |
| 530 | + "pool_size": len(tips) |
| 531 | + } |
455 | 532 |
|
456 | 533 |
|
457 | 534 | # ============================================================================= |
|
0 commit comments