#
tokens: 35215/50000 1/62 files (page 3/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 of 3. Use http://codebase.md/adamsmaka/flutter-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .dockerignore
├── .github
│   └── workflows
│       ├── build-executables.yml
│       └── publish-pypi.yml
├── .gitignore
├── .pypirc.template
├── .python-version
├── build.spec
├── CHANGELOG.md
├── CLAUDE.md
├── context7-installation-analysis.md
├── docker
│   ├── docker-compose.yml
│   └── Dockerfile
├── docs
│   ├── api-reference.md
│   ├── CLIENT-CONFIGURATIONS.md
│   ├── CONTRIBUTING.md
│   ├── DEVELOPMENT.md
│   ├── planning
│   │   ├── context7-marketing-analysis.md
│   │   ├── flutter-mcp-project-summary.md
│   │   ├── IMPLEMENTATION_SUMMARY.md
│   │   ├── ingestion-strategy.md
│   │   ├── initial-vision.md
│   │   ├── project-summary.md
│   │   ├── project-tasks.csv
│   │   ├── README-highlights.md
│   │   └── task-summary.md
│   ├── PUBLISHING.md
│   ├── README.md
│   ├── token-counting-analysis.md
│   ├── token-management-implementation.md
│   ├── TOOL-CONSOLIDATION-PLAN.md
│   ├── truncation-algorithm.md
│   └── VERSION_SPECIFICATION.md
├── ERROR_HANDLING.md
├── examples
│   ├── token_management_demo.py
│   └── truncation_demo.py
├── LICENSE
├── MANIFEST.in
├── npm-wrapper
│   ├── .gitignore
│   ├── bin
│   │   └── flutter-mcp.js
│   ├── index.js
│   ├── package.json
│   ├── publish.sh
│   ├── README.md
│   └── scripts
│       └── install.js
├── pyproject.toml
├── QUICK_FIX.md
├── README.md
├── RELEASE_GUIDE.md
├── scripts
│   ├── build-executables.sh
│   ├── publish-pypi.sh
│   └── test-server.sh
├── setup.py
├── src
│   └── flutter_mcp
│       ├── __init__.py
│       ├── __main__.py
│       ├── cache.py
│       ├── cli.py
│       ├── error_handling.py
│       ├── logging_utils.py
│       ├── recovery.py
│       ├── server.py
│       ├── token_manager.py
│       └── truncation.py
├── tests
│   ├── __init__.py
│   ├── test_integration.py
│   ├── test_token_management.py
│   ├── test_tools.py
│   └── test_truncation.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/src/flutter_mcp/server.py:
--------------------------------------------------------------------------------

```python
   1 | #!/usr/bin/env python3
   2 | """Flutter MCP Server - Real-time Flutter/Dart documentation for AI assistants"""
   3 | 
   4 | import asyncio
   5 | import json
   6 | import re
   7 | from typing import Optional, Dict, List, Any, Tuple
   8 | from datetime import datetime
   9 | import time
  10 | 
  11 | from mcp.server.fastmcp import FastMCP
  12 | import httpx
  13 | # Redis removed - using SQLite cache instead
  14 | from bs4 import BeautifulSoup
  15 | import structlog
  16 | from structlog.contextvars import bind_contextvars
  17 | from rich.console import Console
  18 | 
  19 | # Import our custom logging utilities
  20 | from .logging_utils import format_cache_stats, print_server_header
  21 | 
  22 | # Initialize structured logging
  23 | # IMPORTANT: For MCP servers, logs must go to stderr, not stdout
  24 | # stdout is reserved for the JSON-RPC protocol
  25 | import sys
  26 | import logging
  27 | 
  28 | # Configure structlog with enhanced formatting
  29 | structlog.configure(
  30 |     processors=[
  31 |         structlog.contextvars.merge_contextvars,
  32 |         structlog.processors.add_log_level,
  33 |         structlog.processors.StackInfoRenderer(),
  34 |         structlog.dev.set_exc_info,
  35 |         structlog.processors.TimeStamper(fmt="%H:%M:%S", utc=False),
  36 |         # Our custom processor comes before the renderer!
  37 |         format_cache_stats,
  38 |         # Use ConsoleRenderer for beautiful colored output
  39 |         structlog.dev.ConsoleRenderer(
  40 |             colors=True,
  41 |             exception_formatter=structlog.dev.plain_traceback,
  42 |         ),
  43 |     ],
  44 |     wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
  45 |     context_class=dict,
  46 |     logger_factory=structlog.PrintLoggerFactory(file=sys.stderr),
  47 |     cache_logger_on_first_use=True,
  48 | )
  49 | logger = structlog.get_logger()
  50 | 
  51 | # Rich console for direct output
  52 | console = Console(stderr=True)
  53 | 
  54 | # Initialize FastMCP server
  55 | mcp = FastMCP("Flutter Docs Server")
  56 | 
  57 | # Import our SQLite-based cache
  58 | from .cache import get_cache
  59 | # Import error handling utilities
  60 | from .error_handling import (
  61 |     NetworkError, DocumentationNotFoundError, RateLimitError,
  62 |     with_retry, safe_http_get, format_error_response,
  63 |     CircuitBreaker
  64 | )
  65 | # Legacy version parser functionality now integrated in resolve_identifier()
  66 | # Import truncation utilities
  67 | from .truncation import truncate_flutter_docs, create_truncator, DocumentTruncator
  68 | # Import token management
  69 | from .token_manager import TokenManager
  70 | 
  71 | # Initialize cache manager
  72 | cache_manager = get_cache()
  73 | logger.info("cache_initialized", cache_type="sqlite", path=cache_manager.db_path)
  74 | 
  75 | # Initialize token manager
  76 | token_manager = TokenManager()
  77 | 
  78 | 
  79 | class RateLimiter:
  80 |     """Rate limiter for respectful web scraping (2 requests/second)"""
  81 |     
  82 |     def __init__(self, calls_per_second: float = 2.0):
  83 |         self.semaphore = asyncio.Semaphore(1)
  84 |         self.min_interval = 1.0 / calls_per_second
  85 |         self.last_call = 0
  86 |     
  87 |     async def acquire(self):
  88 |         async with self.semaphore:
  89 |             current_time = time.time()
  90 |             elapsed = current_time - self.last_call
  91 |             if elapsed < self.min_interval:
  92 |                 await asyncio.sleep(self.min_interval - elapsed)
  93 |             self.last_call = time.time()
  94 | 
  95 | 
  96 | # Global rate limiter instance
  97 | rate_limiter = RateLimiter()
  98 | 
  99 | 
 100 | # ============================================================================
 101 | # Helper Functions for Tool Consolidation
 102 | # ============================================================================
 103 | 
 104 | def resolve_identifier(identifier: str) -> Tuple[str, str, Optional[str]]:
 105 |     """
 106 |     Resolve an identifier to determine its type and clean form.
 107 |     
 108 |     Args:
 109 |         identifier: The identifier to resolve (e.g., "Container", "material.AppBar", 
 110 |                    "dart:async.Future", "provider", "dio:^5.0.0")
 111 |     
 112 |     Returns:
 113 |         Tuple of (type, clean_id, library) where:
 114 |         - type: "flutter_class", "dart_class", "pub_package", or "unknown"
 115 |         - clean_id: Cleaned identifier without prefixes or version constraints
 116 |         - library: Library name for classes, None for packages
 117 |     """
 118 |     # Check for version constraint (indicates package)
 119 |     if ':' in identifier and not identifier.startswith('dart:'):
 120 |         # It's a package with version constraint
 121 |         package_name = identifier.split(':')[0]
 122 |         return ("pub_package", package_name, None)
 123 |     
 124 |     # Check for Dart API pattern (dart:library.Class)
 125 |     if identifier.startswith('dart:'):
 126 |         match = re.match(r'dart:(\w+)\.(\w+)', identifier)
 127 |         if match:
 128 |             library = f"dart:{match.group(1)}"
 129 |             class_name = match.group(2)
 130 |             return ("dart_class", class_name, library)
 131 |         else:
 132 |             # Just dart:library without class
 133 |             return ("dart_class", identifier, None)
 134 |     
 135 |     # Check for Flutter library.class pattern
 136 |     flutter_libs = ['widgets', 'material', 'cupertino', 'painting', 'animation', 
 137 |                     'rendering', 'services', 'gestures', 'foundation']
 138 |     for lib in flutter_libs:
 139 |         if identifier.startswith(f"{lib}."):
 140 |             class_name = identifier.split('.', 1)[1]
 141 |             return ("flutter_class", class_name, lib)
 142 |     
 143 |     # Check if it's a known Flutter widget (common ones)
 144 |     common_widgets = ['Container', 'Row', 'Column', 'Text', 'Scaffold', 'AppBar',
 145 |                       'ListView', 'GridView', 'Stack', 'Card', 'IconButton']
 146 |     if identifier in common_widgets:
 147 |         return ("flutter_class", identifier, "widgets")
 148 |     
 149 |     # Check if it looks like a package name (lowercase, may contain underscores)
 150 |     if identifier.islower() or '_' in identifier:
 151 |         return ("pub_package", identifier, None)
 152 |     
 153 |     # Default to unknown
 154 |     return ("unknown", identifier, None)
 155 | 
 156 | 
 157 | def filter_by_topic(content: str, topic: str, doc_type: str) -> str:
 158 |     """
 159 |     Extract specific sections from documentation based on topic.
 160 |     
 161 |     Args:
 162 |         content: Full documentation content
 163 |         topic: Topic to filter by (e.g., "constructors", "methods", "properties", 
 164 |                 "examples", "dependencies", "usage")
 165 |         doc_type: Type of documentation ("flutter_class", "dart_class", "pub_package")
 166 |     
 167 |     Returns:
 168 |         Filtered content containing only the requested topic
 169 |     """
 170 |     if not content:
 171 |         return "No content available"
 172 |     
 173 |     topic_lower = topic.lower()
 174 |     
 175 |     if doc_type in ["flutter_class", "dart_class"]:
 176 |         # For class documentation, extract specific sections
 177 |         lines = content.split('\n')
 178 |         in_section = False
 179 |         section_content = []
 180 |         section_headers = {
 181 |             "constructors": ["## Constructors", "### Constructors"],
 182 |             "methods": ["## Methods", "### Methods"],
 183 |             "properties": ["## Properties", "### Properties"],
 184 |             "examples": ["## Code Examples", "### Examples", "## Examples"],
 185 |             "description": ["## Description", "### Description"],
 186 |         }
 187 |         
 188 |         if topic_lower in section_headers:
 189 |             headers = section_headers[topic_lower]
 190 |             for i, line in enumerate(lines):
 191 |                 if any(header in line for header in headers):
 192 |                     in_section = True
 193 |                     section_content.append(line)
 194 |                 elif in_section and line.startswith('##'):
 195 |                     # Reached next major section
 196 |                     break
 197 |                 elif in_section:
 198 |                     section_content.append(line)
 199 |             
 200 |             if section_content:
 201 |                 return '\n'.join(section_content)
 202 |             else:
 203 |                 return f"No {topic} section found in documentation"
 204 |         else:
 205 |             return f"Unknown topic '{topic}' for class documentation"
 206 |     
 207 |     elif doc_type == "pub_package":
 208 |         # For package documentation, different sections
 209 |         if topic_lower == "dependencies":
 210 |             # Extract dependencies from the content
 211 |             deps_match = re.search(r'"dependencies":\s*\[(.*?)\]', content, re.DOTALL)
 212 |             if deps_match:
 213 |                 deps = deps_match.group(1)
 214 |                 return f"Dependencies: {deps}"
 215 |             return "No dependencies information found"
 216 |         
 217 |         elif topic_lower == "usage":
 218 |             # Try to extract usage/getting started section from README
 219 |             if "readme" in content.lower():
 220 |                 # Look for usage patterns in README
 221 |                 patterns = [r'## Usage.*?(?=##|\Z)', r'## Getting Started.*?(?=##|\Z)',
 222 |                            r'## Quick Start.*?(?=##|\Z)', r'## Installation.*?(?=##|\Z)']
 223 |                 for pattern in patterns:
 224 |                     match = re.search(pattern, content, re.DOTALL | re.IGNORECASE)
 225 |                     if match:
 226 |                         return match.group(0).strip()
 227 |             return "No usage information found"
 228 |         
 229 |         elif topic_lower == "examples":
 230 |             # Extract code examples from README
 231 |             code_blocks = re.findall(r'```(?:dart|flutter)?\n(.*?)\n```', content, re.DOTALL)
 232 |             if code_blocks:
 233 |                 examples = []
 234 |                 for i, code in enumerate(code_blocks[:5]):  # Limit to 5 examples
 235 |                     examples.append(f"Example {i+1}:\n```dart\n{code}\n```")
 236 |                 return '\n\n'.join(examples)
 237 |             return "No code examples found"
 238 |     
 239 |     # Default: return full content if topic not recognized
 240 |     return content
 241 | 
 242 | 
 243 | def to_unified_id(doc_type: str, identifier: str, library: str = None) -> str:
 244 |     """
 245 |     Convert documentation reference to unified ID format.
 246 |     
 247 |     Args:
 248 |         doc_type: Type of documentation ("flutter_class", "dart_class", "pub_package")
 249 |         identifier: The identifier (class name or package name)
 250 |         library: Optional library name for classes
 251 |     
 252 |     Returns:
 253 |         Unified ID string (e.g., "flutter:material.AppBar", "dart:async.Future", "package:dio")
 254 |     """
 255 |     if doc_type == "flutter_class":
 256 |         if library:
 257 |             return f"flutter:{library}.{identifier}"
 258 |         else:
 259 |             return f"flutter:widgets.{identifier}"  # Default to widgets
 260 |     elif doc_type == "dart_class":
 261 |         if library:
 262 |             return f"{library}.{identifier}"
 263 |         else:
 264 |             return f"dart:core.{identifier}"  # Default to core
 265 |     elif doc_type == "pub_package":
 266 |         return f"package:{identifier}"
 267 |     else:
 268 |         return identifier
 269 | 
 270 | 
 271 | def from_unified_id(unified_id: str) -> Tuple[str, str, Optional[str]]:
 272 |     """
 273 |     Parse unified ID format back to components.
 274 |     
 275 |     Args:
 276 |         unified_id: Unified ID string (e.g., "flutter:material.AppBar")
 277 |     
 278 |     Returns:
 279 |         Tuple of (type, identifier, library)
 280 |     """
 281 |     if unified_id.startswith("flutter:"):
 282 |         parts = unified_id[8:].split('.', 1)  # Remove "flutter:" prefix
 283 |         if len(parts) == 2:
 284 |             return ("flutter_class", parts[1], parts[0])
 285 |         else:
 286 |             return ("flutter_class", parts[0], "widgets")
 287 |     
 288 |     elif unified_id.startswith("dart:"):
 289 |         match = re.match(r'(dart:\w+)\.(\w+)', unified_id)
 290 |         if match:
 291 |             return ("dart_class", match.group(2), match.group(1))
 292 |         else:
 293 |             return ("dart_class", unified_id, None)
 294 |     
 295 |     elif unified_id.startswith("package:"):
 296 |         return ("pub_package", unified_id[8:], None)
 297 |     
 298 |     else:
 299 |         return ("unknown", unified_id, None)
 300 | 
 301 | 
 302 | def estimate_doc_size(content: str) -> str:
 303 |     """
 304 |     Estimate documentation size category based on content length.
 305 |     
 306 |     Args:
 307 |         content: Documentation content
 308 |     
 309 |     Returns:
 310 |         Size category: "small", "medium", or "large"
 311 |     """
 312 |     if not content:
 313 |         return "small"
 314 |     
 315 |     # Rough token estimation (1 token ≈ 4 characters)
 316 |     estimated_tokens = len(content) / 4
 317 |     
 318 |     if estimated_tokens < 1000:
 319 |         return "small"
 320 |     elif estimated_tokens < 4000:
 321 |         return "medium"
 322 |     else:
 323 |         return "large"
 324 | 
 325 | 
 326 | def rank_results(results: List[Dict[str, Any]], query: str) -> List[Dict[str, Any]]:
 327 |     """
 328 |     Rank search results based on relevance to query.
 329 |     
 330 |     Args:
 331 |         results: List of search results
 332 |         query: Original search query
 333 |     
 334 |     Returns:
 335 |         Sorted list of results with updated relevance scores
 336 |     """
 337 |     query_lower = query.lower()
 338 |     query_words = set(query_lower.split())
 339 |     
 340 |     for result in results:
 341 |         # Start with existing relevance score if present
 342 |         score = result.get("relevance", 0.5)
 343 |         
 344 |         # Boost for exact title match
 345 |         title = result.get("title", "").lower()
 346 |         if query_lower == title:
 347 |             score += 0.5
 348 |         elif query_lower in title:
 349 |             score += 0.3
 350 |         
 351 |         # Boost for word matches in title
 352 |         title_words = set(title.split())
 353 |         word_overlap = len(query_words & title_words) / len(query_words) if query_words else 0
 354 |         score += word_overlap * 0.2
 355 |         
 356 |         # Consider description matches
 357 |         description = result.get("description", "").lower()
 358 |         if query_lower in description:
 359 |             score += 0.1
 360 |         
 361 |         # Boost for type preferences
 362 |         if "state" in query_lower and result.get("type") == "concept":
 363 |             score += 0.2
 364 |         elif "package" in query_lower and result.get("type") == "pub_package":
 365 |             score += 0.2
 366 |         elif any(word in query_lower for word in ["widget", "class"]) and result.get("type") == "flutter_class":
 367 |             score += 0.2
 368 |         
 369 |         # Cap score at 1.0
 370 |         result["relevance"] = min(score, 1.0)
 371 |     
 372 |     # Sort by relevance score (descending)
 373 |     return sorted(results, key=lambda x: x.get("relevance", 0), reverse=True)
 374 | 
 375 | 
 376 | # Circuit breakers for external services
 377 | flutter_docs_circuit = CircuitBreaker(
 378 |     failure_threshold=5,
 379 |     recovery_timeout=60.0,
 380 |     expected_exception=(NetworkError, httpx.HTTPStatusError)
 381 | )
 382 | 
 383 | pub_dev_circuit = CircuitBreaker(
 384 |     failure_threshold=5,
 385 |     recovery_timeout=60.0,
 386 |     expected_exception=(NetworkError, httpx.HTTPStatusError)
 387 | )
 388 | 
 389 | # Cache TTL strategy (in seconds)
 390 | CACHE_DURATIONS = {
 391 |     "flutter_api": 86400,      # 24 hours for stable APIs
 392 |     "dart_api": 86400,         # 24 hours for Dart APIs
 393 |     "pub_package": 43200,      # 12 hours for packages (may update more frequently)
 394 |     "cookbook": 604800,        # 7 days for examples
 395 |     "stackoverflow": 3600,     # 1 hour for community content
 396 | }
 397 | 
 398 | 
 399 | def get_cache_key(doc_type: str, identifier: str, version: str = None) -> str:
 400 |     """Generate cache keys for different documentation types"""
 401 |     if version:
 402 |         # Normalize version string for cache key
 403 |         version = version.replace(' ', '_').replace('>=', 'gte').replace('<=', 'lte').replace('^', 'caret')
 404 |         return f"{doc_type}:{identifier}:{version}"
 405 |     return f"{doc_type}:{identifier}"
 406 | 
 407 | 
 408 | def clean_text(element) -> str:
 409 |     """Clean and extract text from BeautifulSoup element"""
 410 |     if not element:
 411 |         return ""
 412 |     text = element.get_text(strip=True)
 413 |     # Remove excessive whitespace
 414 |     text = re.sub(r'\s+', ' ', text)
 415 |     return text.strip()
 416 | 
 417 | 
 418 | def format_constructors(constructors: List) -> str:
 419 |     """Format constructor information for AI consumption"""
 420 |     if not constructors:
 421 |         return "No constructors found"
 422 |     
 423 |     result = []
 424 |     for constructor in constructors:
 425 |         name = constructor.find('h3')
 426 |         signature = constructor.find('pre')
 427 |         desc = constructor.find('p')
 428 |         
 429 |         if name:
 430 |             result.append(f"### {clean_text(name)}")
 431 |         if signature:
 432 |             result.append(f"```dart\n{clean_text(signature)}\n```")
 433 |         if desc:
 434 |             result.append(clean_text(desc))
 435 |         result.append("")
 436 |     
 437 |     return "\n".join(result)
 438 | 
 439 | 
 440 | def format_properties(properties: List) -> str:
 441 |     """Format property information"""
 442 |     if not properties:
 443 |         return "No properties found"
 444 |     
 445 |     result = []
 446 |     for prop_list in properties:
 447 |         items = prop_list.find_all('dt')
 448 |         for item in items:
 449 |             prop_name = clean_text(item)
 450 |             prop_desc = item.find_next_sibling('dd')
 451 |             if prop_name:
 452 |                 result.append(f"- **{prop_name}**: {clean_text(prop_desc) if prop_desc else 'No description'}")
 453 |     
 454 |     return "\n".join(result)
 455 | 
 456 | 
 457 | def format_methods(methods: List) -> str:
 458 |     """Format method information"""
 459 |     if not methods:
 460 |         return "No methods found"
 461 |     
 462 |     result = []
 463 |     for method in methods:
 464 |         name = method.find('h3')
 465 |         signature = method.find('pre')
 466 |         desc = method.find('p')
 467 |         
 468 |         if name:
 469 |             result.append(f"### {clean_text(name)}")
 470 |         if signature:
 471 |             result.append(f"```dart\n{clean_text(signature)}\n```")
 472 |         if desc:
 473 |             result.append(clean_text(desc))
 474 |         result.append("")
 475 |     
 476 |     return "\n".join(result)
 477 | 
 478 | 
 479 | def extract_code_examples(soup: BeautifulSoup) -> str:
 480 |     """Extract code examples from documentation"""
 481 |     examples = soup.find_all('pre', class_='language-dart')
 482 |     if not examples:
 483 |         examples = soup.find_all('pre')  # Fallback to any pre tags
 484 |     
 485 |     if not examples:
 486 |         return "No code examples found"
 487 |     
 488 |     result = []
 489 |     for i, example in enumerate(examples[:5]):  # Limit to 5 examples
 490 |         code = clean_text(example)
 491 |         if code:
 492 |             result.append(f"#### Example {i+1}:\n```dart\n{code}\n```\n")
 493 |     
 494 |     return "\n".join(result)
 495 | 
 496 | 
 497 | async def process_documentation(html: str, class_name: str, tokens: int = None) -> Dict[str, Any]:
 498 |     """Context7-style documentation processing pipeline with smart truncation and token counting.
 499 |     
 500 |     Returns a dict containing:
 501 |         - content: The processed markdown content
 502 |         - token_count: Final token count after any truncation
 503 |         - original_tokens: Original token count before truncation
 504 |         - truncated: Boolean indicating if content was truncated
 505 |         - truncation_note: Optional note about truncation
 506 |     """
 507 |     soup = BeautifulSoup(html, 'html.parser')
 508 |     
 509 |     # Remove navigation, scripts, styles, etc.
 510 |     for element in soup.find_all(['script', 'style', 'nav', 'header', 'footer']):
 511 |         element.decompose()
 512 |     
 513 |     # 1. Parse - Extract key sections
 514 |     description = soup.find('section', class_='desc')
 515 |     constructors = soup.find_all('section', class_='constructor')
 516 |     properties = soup.find_all('dl', class_='properties')
 517 |     methods = soup.find_all('section', class_='method')
 518 |     
 519 |     # 2. Enrich - Format for AI consumption
 520 |     markdown = f"""# {class_name}
 521 | 
 522 | ## Description
 523 | {clean_text(description) if description else 'No description available'}
 524 | 
 525 | ## Constructors
 526 | {format_constructors(constructors)}
 527 | 
 528 | ## Properties
 529 | {format_properties(properties)}
 530 | 
 531 | ## Methods
 532 | {format_methods(methods)}
 533 | 
 534 | ## Code Examples
 535 | {extract_code_examples(soup)}
 536 | """
 537 |     
 538 |     # Count tokens before truncation
 539 |     original_tokens = token_manager.count_tokens(markdown)
 540 |     truncated = False
 541 |     truncation_note = None
 542 |     
 543 |     # 3. Truncate if needed
 544 |     if tokens and original_tokens > tokens:
 545 |         markdown = truncate_flutter_docs(
 546 |             markdown,
 547 |             class_name,
 548 |             max_tokens=tokens,
 549 |             strategy="balanced"
 550 |         )
 551 |         truncated = True
 552 |         truncation_note = f"Documentation truncated from {original_tokens} to approximately {tokens} tokens"
 553 |     
 554 |     # Count final tokens
 555 |     final_tokens = token_manager.count_tokens(markdown)
 556 |     
 557 |     return {
 558 |         "content": markdown,
 559 |         "token_count": final_tokens,
 560 |         "original_tokens": original_tokens if truncated else final_tokens,
 561 |         "truncated": truncated,
 562 |         "truncation_note": truncation_note
 563 |     }
 564 | 
 565 | 
 566 | def resolve_flutter_url(query: str) -> Optional[str]:
 567 |     """Intelligently resolve documentation URLs from queries"""
 568 |     # Common Flutter class patterns
 569 |     patterns = {
 570 |         r"^(\w+)$": "https://api.flutter.dev/flutter/widgets/{0}-class.html",
 571 |         r"^widgets\.(\w+)$": "https://api.flutter.dev/flutter/widgets/{0}-class.html",
 572 |         r"^material\.(\w+)$": "https://api.flutter.dev/flutter/material/{0}-class.html",
 573 |         r"^cupertino\.(\w+)$": "https://api.flutter.dev/flutter/cupertino/{0}-class.html",
 574 |         r"^painting\.(\w+)$": "https://api.flutter.dev/flutter/painting/{0}-class.html",
 575 |         r"^animation\.(\w+)$": "https://api.flutter.dev/flutter/animation/{0}-class.html",
 576 |         r"^rendering\.(\w+)$": "https://api.flutter.dev/flutter/rendering/{0}-class.html",
 577 |         r"^services\.(\w+)$": "https://api.flutter.dev/flutter/services/{0}-class.html",
 578 |         r"^gestures\.(\w+)$": "https://api.flutter.dev/flutter/gestures/{0}-class.html",
 579 |         r"^foundation\.(\w+)$": "https://api.flutter.dev/flutter/foundation/{0}-class.html",
 580 |         # Dart core libraries
 581 |         r"^dart:core\.(\w+)$": "https://api.dart.dev/stable/dart-core/{0}-class.html",
 582 |         r"^dart:async\.(\w+)$": "https://api.dart.dev/stable/dart-async/{0}-class.html",
 583 |         r"^dart:collection\.(\w+)$": "https://api.dart.dev/stable/dart-collection/{0}-class.html",
 584 |         r"^dart:convert\.(\w+)$": "https://api.dart.dev/stable/dart-convert/{0}-class.html",
 585 |         r"^dart:io\.(\w+)$": "https://api.dart.dev/stable/dart-io/{0}-class.html",
 586 |         r"^dart:math\.(\w+)$": "https://api.dart.dev/stable/dart-math/{0}-class.html",
 587 |         r"^dart:typed_data\.(\w+)$": "https://api.dart.dev/stable/dart-typed_data/{0}-class.html",
 588 |         r"^dart:ui\.(\w+)$": "https://api.dart.dev/stable/dart-ui/{0}-class.html",
 589 |     }
 590 |     
 591 |     for pattern, url_template in patterns.items():
 592 |         if match := re.match(pattern, query, re.IGNORECASE):
 593 |             return url_template.format(*match.groups())
 594 |     
 595 |     return None
 596 | 
 597 | 
 598 | 
 599 | 
 600 | @mcp.tool()
 601 | async def get_flutter_docs(
 602 |     class_name: str, 
 603 |     library: str = "widgets",
 604 |     tokens: int = 8000
 605 | ) -> Dict[str, Any]:
 606 |     """
 607 |     Get Flutter class documentation on-demand with optional smart truncation.
 608 |     
 609 |     **DEPRECATED**: This tool is deprecated. Please use flutter_docs() instead.
 610 |     The new tool provides better query resolution and unified interface.
 611 |     
 612 |     Args:
 613 |         class_name: Name of the Flutter class (e.g., "Container", "Scaffold")
 614 |         library: Flutter library (e.g., "widgets", "material", "cupertino")
 615 |         tokens: Maximum token limit for truncation (default: 8000, min: 500)
 616 |     
 617 |     Returns:
 618 |         Dictionary with documentation content or error message
 619 |     """
 620 |     bind_contextvars(tool="get_flutter_docs", class_name=class_name, library=library)
 621 |     logger.warning("deprecated_tool_usage", tool="get_flutter_docs", replacement="flutter_docs")
 622 |     
 623 |     # Validate tokens parameter
 624 |     if tokens < 500:
 625 |         return {"error": "tokens parameter must be at least 500"}
 626 |     
 627 |     # Call the new flutter_docs tool
 628 |     identifier = f"{library}.{class_name}" if library != "widgets" else class_name
 629 |     result = await flutter_docs(identifier, max_tokens=tokens)
 630 |     
 631 |     # Transform back to old format
 632 |     if result.get("error"):
 633 |         return {
 634 |             "error": result["error"],
 635 |             "suggestion": result.get("suggestion", "")
 636 |         }
 637 |     else:
 638 |         return {
 639 |             "source": result.get("source", "live"),
 640 |             "class": result.get("class", class_name),
 641 |             "library": result.get("library", library),
 642 |             "content": result.get("content", ""),
 643 |             "fetched_at": datetime.utcnow().isoformat(),
 644 |             "truncated": result.get("truncated", False)
 645 |         }
 646 | 
 647 | 
 648 | async def _get_flutter_docs_impl(
 649 |     class_name: str, 
 650 |     library: str = "widgets",
 651 |     tokens: int = None
 652 | ) -> Dict[str, Any]:
 653 |     """
 654 |     Internal implementation of get_flutter_docs functionality.
 655 |     """
 656 |     # Check cache first
 657 |     cache_key = get_cache_key("flutter_api", f"{library}:{class_name}")
 658 |     
 659 |     # Check cache
 660 |     cached_data = cache_manager.get(cache_key)
 661 |     if cached_data:
 662 |         logger.info("cache_hit")
 663 |         return cached_data
 664 |     
 665 |     # Rate-limited fetch from Flutter docs
 666 |     await rate_limiter.acquire()
 667 |     
 668 |     # Determine URL based on library type
 669 |     if library.startswith("dart:"):
 670 |         # Convert dart:core to dart-core format for Dart API
 671 |         dart_lib = library.replace("dart:", "dart-")
 672 |         url = f"https://api.dart.dev/stable/{dart_lib}/{class_name}-class.html"
 673 |     else:
 674 |         # Flutter libraries use api.flutter.dev
 675 |         url = f"https://api.flutter.dev/flutter/{library}/{class_name}-class.html"
 676 |     
 677 |     logger.info("fetching_docs", url=url)
 678 |     
 679 |     try:
 680 |         async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
 681 |             response = await client.get(
 682 |                 url,
 683 |                 headers={
 684 |                     "User-Agent": "Flutter-MCP-Docs/1.0 (github.com/flutter-mcp/flutter-mcp)"
 685 |                 }
 686 |             )
 687 |             response.raise_for_status()
 688 |             
 689 |             # Process HTML - Context7 style pipeline with truncation
 690 |             doc_result = await process_documentation(response.text, class_name, tokens)
 691 |             
 692 |             # Cache the result with token metadata
 693 |             result = {
 694 |                 "source": "live",
 695 |                 "class": class_name,
 696 |                 "library": library,
 697 |                 "content": doc_result["content"],
 698 |                 "fetched_at": datetime.utcnow().isoformat(),
 699 |                 "truncated": doc_result["truncated"],
 700 |                 "token_count": doc_result["token_count"],
 701 |                 "original_tokens": doc_result["original_tokens"],
 702 |                 "truncation_note": doc_result["truncation_note"]
 703 |             }
 704 |             cache_manager.set(cache_key, result, CACHE_DURATIONS["flutter_api"], token_count=doc_result["token_count"])
 705 |             
 706 |             logger.info("docs_fetched_success", 
 707 |                        content_length=len(doc_result["content"]),
 708 |                        token_count=doc_result["token_count"],
 709 |                        truncated=doc_result["truncated"])
 710 |             return result
 711 |             
 712 |     except httpx.HTTPStatusError as e:
 713 |         logger.error("http_error", status_code=e.response.status_code)
 714 |         return {
 715 |             "error": f"HTTP {e.response.status_code}: Documentation not found for {library}.{class_name}",
 716 |             "suggestion": "Check the class name and library. Common libraries: widgets, material, cupertino"
 717 |         }
 718 |     except Exception as e:
 719 |         logger.error("fetch_error", error=str(e))
 720 |         return {
 721 |             "error": f"Failed to fetch documentation: {str(e)}",
 722 |             "url": url
 723 |         }
 724 | 
 725 | 
 726 | @mcp.tool()
 727 | async def search_flutter_docs(query: str, tokens: int = 5000) -> Dict[str, Any]:
 728 |     """
 729 |     Search across Flutter/Dart documentation sources with fuzzy matching.
 730 |     
 731 |     **DEPRECATED**: This tool is deprecated. Please use flutter_search() instead.
 732 |     The new tool provides better filtering and more structured results.
 733 |     
 734 |     Searches Flutter API docs, Dart API docs, and pub.dev packages.
 735 |     Returns top 5-10 most relevant results with brief descriptions.
 736 |     
 737 |     Args:
 738 |         query: Search query (e.g., "state management", "Container", "navigation", "http requests")
 739 |         tokens: Maximum token limit for response (default: 5000, min: 500)
 740 |     
 741 |     Returns:
 742 |         Search results with relevance scores and brief descriptions
 743 |     """
 744 |     bind_contextvars(tool="search_flutter_docs", query=query)
 745 |     logger.warning("deprecated_tool_usage", tool="search_flutter_docs", replacement="flutter_search")
 746 |     
 747 |     # Validate tokens parameter
 748 |     if tokens < 500:
 749 |         return {"error": "tokens parameter must be at least 500"}
 750 |     
 751 |     # Call new flutter_search tool
 752 |     result = await flutter_search(query, limit=10)
 753 |     
 754 |     # Transform back to old format
 755 |     return {
 756 |         "query": result["query"],
 757 |         "results": result["results"],
 758 |         "total": result.get("total_results", result.get("returned_results", 0)),
 759 |         "timestamp": result.get("timestamp", datetime.utcnow().isoformat()),
 760 |         "suggestions": result.get("suggestions", [])
 761 |     }
 762 | 
 763 | 
 764 | async def _search_flutter_docs_impl(
 765 |     query: str,
 766 |     limit: int = 10,
 767 |     types: List[str] = None
 768 | ) -> Dict[str, Any]:
 769 |     """
 770 |     Internal implementation of search functionality.
 771 |     """
 772 |     logger.info("searching_docs")
 773 |     
 774 |     results = []
 775 |     query_lower = query.lower()
 776 |     
 777 |     # Check cache for search results
 778 |     cache_key = get_cache_key("search_results", query_lower)
 779 |     cached_data = cache_manager.get(cache_key)
 780 |     if cached_data:
 781 |         logger.info("search_cache_hit")
 782 |         return cached_data
 783 |     
 784 |     # 1. Try direct URL resolution first (exact matches)
 785 |     if url := resolve_flutter_url(query):
 786 |         logger.info("url_resolved", url=url)
 787 |         
 788 |         # Extract class name and library from URL
 789 |         if "flutter/widgets" in url:
 790 |             library = "widgets"
 791 |         elif "flutter/material" in url:
 792 |             library = "material"
 793 |         elif "flutter/cupertino" in url:
 794 |             library = "cupertino"
 795 |         else:
 796 |             library = "unknown"
 797 |         
 798 |         class_match = re.search(r'/([^/]+)-class\.html$', url)
 799 |         if class_match:
 800 |             class_name = class_match.group(1)
 801 |             doc = await _get_flutter_docs_impl(class_name, library)
 802 |             if "error" not in doc:
 803 |                 results.append({
 804 |                     "type": "flutter_class",
 805 |                     "relevance": 1.0,
 806 |                     "title": f"{class_name} ({library})",
 807 |                     "description": f"Flutter {library} widget/class",
 808 |                     "url": url,
 809 |                     "content_preview": doc.get("content", "")[:200] + "..."
 810 |                 })
 811 |     
 812 |     # 2. Check common Flutter widgets and classes
 813 |     common_flutter_items = [
 814 |         # State management related
 815 |         ("StatefulWidget", "widgets", "Base class for widgets that have mutable state"),
 816 |         ("StatelessWidget", "widgets", "Base class for widgets that don't require mutable state"),
 817 |         ("State", "widgets", "Logic and internal state for a StatefulWidget"),
 818 |         ("InheritedWidget", "widgets", "Base class for widgets that propagate information down the tree"),
 819 |         ("Provider", "widgets", "A widget that provides a value to its descendants"),
 820 |         ("ValueListenableBuilder", "widgets", "Rebuilds when ValueListenable changes"),
 821 |         ("NotificationListener", "widgets", "Listens for Notifications bubbling up"),
 822 |         
 823 |         # Layout widgets
 824 |         ("Container", "widgets", "A convenience widget that combines common painting, positioning, and sizing"),
 825 |         ("Row", "widgets", "Displays children in a horizontal array"),
 826 |         ("Column", "widgets", "Displays children in a vertical array"),
 827 |         ("Stack", "widgets", "Positions children relative to the box edges"),
 828 |         ("Scaffold", "material", "Basic material design visual layout structure"),
 829 |         ("Expanded", "widgets", "Expands a child to fill available space in Row/Column"),
 830 |         ("Flexible", "widgets", "Controls how a child flexes in Row/Column"),
 831 |         ("Wrap", "widgets", "Displays children in multiple runs"),
 832 |         ("Flow", "widgets", "Positions children using transformation matrices"),
 833 |         ("Table", "widgets", "Displays children in a table layout"),
 834 |         ("Align", "widgets", "Aligns a child within itself"),
 835 |         ("Center", "widgets", "Centers a child within itself"),
 836 |         ("Positioned", "widgets", "Positions a child in a Stack"),
 837 |         ("FittedBox", "widgets", "Scales and positions child within itself"),
 838 |         ("AspectRatio", "widgets", "Constrains child to specific aspect ratio"),
 839 |         ("ConstrainedBox", "widgets", "Imposes additional constraints on child"),
 840 |         ("SizedBox", "widgets", "Box with a specified size"),
 841 |         ("FractionallySizedBox", "widgets", "Sizes child to fraction of total space"),
 842 |         ("LimitedBox", "widgets", "Limits child size when unconstrained"),
 843 |         ("Offstage", "widgets", "Lays out child as if visible but paints nothing"),
 844 |         ("LayoutBuilder", "widgets", "Builds widget tree based on parent constraints"),
 845 |         
 846 |         # Navigation
 847 |         ("Navigator", "widgets", "Manages a stack of Route objects"),
 848 |         ("Route", "widgets", "An abstraction for an entry managed by a Navigator"),
 849 |         ("MaterialPageRoute", "material", "A modal route that replaces the entire screen"),
 850 |         ("NavigationBar", "material", "Material 3 navigation bar"),
 851 |         ("NavigationRail", "material", "Material navigation rail"),
 852 |         ("BottomNavigationBar", "material", "Bottom navigation bar"),
 853 |         ("Drawer", "material", "Material design drawer"),
 854 |         ("TabBar", "material", "Material design tabs"),
 855 |         ("TabBarView", "material", "Page view for TabBar"),
 856 |         ("WillPopScope", "widgets", "Intercepts back button press"),
 857 |         ("BackButton", "material", "Material design back button"),
 858 |         
 859 |         # Input widgets
 860 |         ("TextField", "material", "A material design text field"),
 861 |         ("TextFormField", "material", "A FormField that contains a TextField"),
 862 |         ("Form", "widgets", "Container for form fields"),
 863 |         ("GestureDetector", "widgets", "Detects gestures on widgets"),
 864 |         ("InkWell", "material", "Rectangular area that responds to touch with ripple"),
 865 |         ("Dismissible", "widgets", "Can be dismissed by dragging"),
 866 |         ("Draggable", "widgets", "Can be dragged to DragTarget"),
 867 |         ("LongPressDraggable", "widgets", "Draggable triggered by long press"),
 868 |         ("DragTarget", "widgets", "Receives data from Draggable"),
 869 |         ("DropdownButton", "material", "Material design dropdown button"),
 870 |         ("Slider", "material", "Material design slider"),
 871 |         ("Switch", "material", "Material design switch"),
 872 |         ("Checkbox", "material", "Material design checkbox"),
 873 |         ("Radio", "material", "Material design radio button"),
 874 |         ("DatePicker", "material", "Material design date picker"),
 875 |         ("TimePicker", "material", "Material design time picker"),
 876 |         
 877 |         # Lists & Grids
 878 |         ("ListView", "widgets", "Scrollable list of widgets"),
 879 |         ("GridView", "widgets", "Scrollable 2D array of widgets"),
 880 |         ("CustomScrollView", "widgets", "ScrollView with slivers"),
 881 |         ("SingleChildScrollView", "widgets", "Box with single scrollable child"),
 882 |         ("PageView", "widgets", "Scrollable list that works page by page"),
 883 |         ("ReorderableListView", "material", "List where items can be reordered"),
 884 |         ("RefreshIndicator", "material", "Material design pull-to-refresh"),
 885 |         
 886 |         # Common material widgets
 887 |         ("AppBar", "material", "A material design app bar"),
 888 |         ("Card", "material", "A material design card"),
 889 |         ("ListTile", "material", "A single fixed-height row for lists"),
 890 |         ("IconButton", "material", "A material design icon button"),
 891 |         ("ElevatedButton", "material", "A material design elevated button"),
 892 |         ("FloatingActionButton", "material", "A material design floating action button"),
 893 |         ("Chip", "material", "Material design chip"),
 894 |         ("ChoiceChip", "material", "Material design choice chip"),
 895 |         ("FilterChip", "material", "Material design filter chip"),
 896 |         ("ActionChip", "material", "Material design action chip"),
 897 |         ("CircularProgressIndicator", "material", "Material circular progress"),
 898 |         ("LinearProgressIndicator", "material", "Material linear progress"),
 899 |         ("SnackBar", "material", "Material design snackbar"),
 900 |         ("BottomSheet", "material", "Material design bottom sheet"),
 901 |         ("ExpansionPanel", "material", "Material expansion panel"),
 902 |         ("Stepper", "material", "Material design stepper"),
 903 |         ("DataTable", "material", "Material design data table"),
 904 |         
 905 |         # Visual Effects
 906 |         ("Opacity", "widgets", "Makes child partially transparent"),
 907 |         ("Transform", "widgets", "Applies transformation before painting"),
 908 |         ("RotatedBox", "widgets", "Rotates child by integral quarters"),
 909 |         ("ClipRect", "widgets", "Clips child to rectangle"),
 910 |         ("ClipRRect", "widgets", "Clips child to rounded rectangle"),
 911 |         ("ClipOval", "widgets", "Clips child to oval"),
 912 |         ("ClipPath", "widgets", "Clips child to path"),
 913 |         ("DecoratedBox", "widgets", "Paints decoration around child"),
 914 |         ("BackdropFilter", "widgets", "Applies filter to existing painted content"),
 915 |         
 916 |         # Animation
 917 |         ("AnimatedBuilder", "widgets", "A widget that rebuilds when animation changes"),
 918 |         ("AnimationController", "animation", "Controls an animation"),
 919 |         ("Hero", "widgets", "Marks a child for hero animations"),
 920 |         ("AnimatedContainer", "widgets", "Animated version of Container"),
 921 |         ("AnimatedOpacity", "widgets", "Animated version of Opacity"),
 922 |         ("AnimatedPositioned", "widgets", "Animated version of Positioned"),
 923 |         ("AnimatedDefaultTextStyle", "widgets", "Animated version of DefaultTextStyle"),
 924 |         ("AnimatedAlign", "widgets", "Animated version of Align"),
 925 |         ("AnimatedPadding", "widgets", "Animated version of Padding"),
 926 |         ("AnimatedSize", "widgets", "Animates its size to match child"),
 927 |         ("AnimatedCrossFade", "widgets", "Cross-fades between two children"),
 928 |         ("AnimatedSwitcher", "widgets", "Animates when switching between children"),
 929 |         
 930 |         # Async widgets
 931 |         ("FutureBuilder", "widgets", "Builds based on interaction with a Future"),
 932 |         ("StreamBuilder", "widgets", "Builds based on interaction with a Stream"),
 933 |         
 934 |         # Utility widgets
 935 |         ("MediaQuery", "widgets", "Establishes media query subtree"),
 936 |         ("Theme", "material", "Applies theme to descendant widgets"),
 937 |         ("DefaultTextStyle", "widgets", "Default text style for descendants"),
 938 |         ("Semantics", "widgets", "Annotates widget tree with semantic descriptions"),
 939 |         ("MergeSemantics", "widgets", "Merges semantics of descendants"),
 940 |         ("ExcludeSemantics", "widgets", "Drops semantics of descendants"),
 941 |     ]
 942 |     
 943 |     # Score Flutter items based on query match
 944 |     for class_name, library, description in common_flutter_items:
 945 |         relevance = calculate_relevance(query_lower, class_name.lower(), description.lower())
 946 |         if relevance > 0.3:  # Threshold for inclusion
 947 |             results.append({
 948 |                 "type": "flutter_class",
 949 |                 "relevance": relevance,
 950 |                 "title": f"{class_name} ({library})",
 951 |                 "description": description,
 952 |                 "class_name": class_name,
 953 |                 "library": library
 954 |             })
 955 |     
 956 |     # 3. Check common Dart core classes
 957 |     common_dart_items = [
 958 |         ("List", "dart:core", "An indexable collection of objects with a length"),
 959 |         ("Map", "dart:core", "A collection of key/value pairs"),
 960 |         ("Set", "dart:core", "A collection of objects with no duplicate elements"),
 961 |         ("String", "dart:core", "A sequence of UTF-16 code units"),
 962 |         ("Future", "dart:async", "Represents a computation that completes with a value or error"),
 963 |         ("Stream", "dart:async", "A source of asynchronous data events"),
 964 |         ("Duration", "dart:core", "A span of time"),
 965 |         ("DateTime", "dart:core", "An instant in time"),
 966 |         ("RegExp", "dart:core", "A regular expression pattern"),
 967 |         ("Iterable", "dart:core", "A collection of values that can be accessed sequentially"),
 968 |     ]
 969 |     
 970 |     for class_name, library, description in common_dart_items:
 971 |         relevance = calculate_relevance(query_lower, class_name.lower(), description.lower())
 972 |         if relevance > 0.3:
 973 |             results.append({
 974 |                 "type": "dart_class",
 975 |                 "relevance": relevance,
 976 |                 "title": f"{class_name} ({library})",
 977 |                 "description": description,
 978 |                 "class_name": class_name,
 979 |                 "library": library
 980 |             })
 981 |     
 982 |     # 4. Search popular pub.dev packages
 983 |     popular_packages = [
 984 |         # State Management
 985 |         ("provider", "State management library that makes it easy to connect business logic to widgets"),
 986 |         ("riverpod", "A reactive caching and data-binding framework"),
 987 |         ("bloc", "State management library implementing the BLoC design pattern"),
 988 |         ("get", "Open source state management, navigation and utilities"),
 989 |         ("mobx", "Reactive state management library"),
 990 |         ("redux", "Predictable state container"),
 991 |         ("stacked", "MVVM architecture solution"),
 992 |         ("get_it", "Service locator for dependency injection"),
 993 |         
 994 |         # Networking
 995 |         ("dio", "Powerful HTTP client for Dart with interceptors and FormData"),
 996 |         ("http", "A composable, multi-platform, Future-based API for HTTP requests"),
 997 |         ("retrofit", "Type-safe HTTP client generator"),
 998 |         ("chopper", "HTTP client with built-in JsonConverter"),
 999 |         ("graphql_flutter", "GraphQL client for Flutter"),
1000 |         ("socket_io_client", "Socket.IO client"),
1001 |         ("web_socket_channel", "WebSocket connections"),
1002 |         
1003 |         # Storage & Database
1004 |         ("shared_preferences", "Flutter plugin for reading and writing simple key-value pairs"),
1005 |         ("sqflite", "SQLite plugin for Flutter with support for iOS, Android and MacOS"),
1006 |         ("hive", "Lightweight and blazing fast key-value database written in pure Dart"),
1007 |         ("isar", "Fast cross-platform database"),
1008 |         ("objectbox", "High-performance NoSQL database"),
1009 |         ("drift", "Reactive persistence library"),
1010 |         ("floor", "SQLite abstraction with Room-like API"),
1011 |         
1012 |         # Firebase
1013 |         ("firebase_core", "Flutter plugin to use Firebase Core API"),
1014 |         ("firebase_auth", "Flutter plugin for Firebase Auth"),
1015 |         ("firebase_database", "Flutter plugin for Firebase Realtime Database"),
1016 |         ("cloud_firestore", "Flutter plugin for Cloud Firestore"),
1017 |         ("firebase_messaging", "Push notifications via FCM"),
1018 |         ("firebase_storage", "Flutter plugin for Firebase Cloud Storage"),
1019 |         ("firebase_analytics", "Flutter plugin for Google Analytics for Firebase"),
1020 |         
1021 |         # UI/UX Libraries
1022 |         ("flutter_bloc", "Flutter widgets that make it easy to implement BLoC design pattern"),
1023 |         ("animations", "Beautiful pre-built animations for Flutter"),
1024 |         ("flutter_svg", "SVG rendering and widget library for Flutter"),
1025 |         ("cached_network_image", "Flutter library to load and cache network images"),
1026 |         ("flutter_slidable", "Slidable list item actions"),
1027 |         ("shimmer", "Shimmer loading effect"),
1028 |         ("liquid_swipe", "Liquid swipe page transitions"),
1029 |         ("flutter_staggered_grid_view", "Staggered grid layouts"),
1030 |         ("carousel_slider", "Carousel widget"),
1031 |         ("photo_view", "Zoomable image widget"),
1032 |         ("flutter_spinkit", "Loading indicators collection"),
1033 |         ("lottie", "Render After Effects animations"),
1034 |         ("rive", "Interactive animations"),
1035 |         
1036 |         # Platform Integration
1037 |         ("url_launcher", "Flutter plugin for launching URLs"),
1038 |         ("path_provider", "Flutter plugin for getting commonly used locations on the filesystem"),
1039 |         ("image_picker", "Flutter plugin for selecting images from image library or camera"),
1040 |         ("connectivity_plus", "Flutter plugin for discovering network connectivity"),
1041 |         ("permission_handler", "Permission plugin for Flutter"),
1042 |         ("geolocator", "Flutter geolocation plugin for Android and iOS"),
1043 |         ("google_fonts", "Flutter package to use fonts from fonts.google.com"),
1044 |         ("flutter_local_notifications", "Local notifications"),
1045 |         ("share_plus", "Share content to other apps"),
1046 |         ("file_picker", "Native file picker"),
1047 |         ("open_file", "Open files with default apps"),
1048 |         
1049 |         # Navigation
1050 |         ("go_router", "A declarative routing package for Flutter"),
1051 |         ("auto_route", "Code generation for type-safe route navigation"),
1052 |         ("beamer", "Handle your application routing"),
1053 |         ("fluro", "Flutter routing library"),
1054 |         
1055 |         # Developer Tools
1056 |         ("logger", "Beautiful logging utility"),
1057 |         ("pretty_dio_logger", "Dio interceptor for logging"),
1058 |         ("flutter_dotenv", "Load environment variables"),
1059 |         ("device_info_plus", "Device information"),
1060 |         ("package_info_plus", "App package information"),
1061 |         ("equatable", "Simplify equality comparisons"),
1062 |         ("freezed", "Code generation for immutable classes"),
1063 |         ("json_serializable", "Automatically generate code for JSON"),
1064 |         ("build_runner", "Build system for Dart code generation"),
1065 |     ]
1066 |     
1067 |     for package_name, description in popular_packages:
1068 |         relevance = calculate_relevance(query_lower, package_name.lower(), description.lower())
1069 |         if relevance > 0.3:
1070 |             results.append({
1071 |                 "type": "pub_package",
1072 |                 "relevance": relevance,
1073 |                 "title": f"{package_name} (pub.dev)",
1074 |                 "description": description,
1075 |                 "package_name": package_name
1076 |             })
1077 |     
1078 |     # 5. Concept-based search (for queries like "state management", "navigation", etc.)
1079 |     concepts = {
1080 |         "state management": [
1081 |             ("setState", "The simplest way to manage state in Flutter"),
1082 |             ("InheritedWidget", "Share data across the widget tree"),
1083 |             ("provider", "Popular state management package"),
1084 |             ("riverpod", "Improved provider with compile-time safety"),
1085 |             ("bloc", "Business Logic Component pattern"),
1086 |             ("get", "Lightweight state management solution"),
1087 |             ("mobx", "Reactive state management"),
1088 |             ("redux", "Predictable state container"),
1089 |             ("ValueNotifier", "Simple observable pattern"),
1090 |             ("ChangeNotifier", "Observable object for multiple listeners"),
1091 |         ],
1092 |         "navigation": [
1093 |             ("Navigator", "Stack-based navigation in Flutter"),
1094 |             ("go_router", "Declarative routing package"),
1095 |             ("auto_route", "Code generation for routes"),
1096 |             ("Named routes", "Navigation using route names"),
1097 |             ("Deep linking", "Handle URLs in your app"),
1098 |             ("WillPopScope", "Intercept back navigation"),
1099 |             ("NavigatorObserver", "Observe navigation events"),
1100 |             ("Hero animations", "Animate widgets between routes"),
1101 |             ("Modal routes", "Full-screen modal pages"),
1102 |             ("BottomSheet navigation", "Navigate with bottom sheets"),
1103 |         ],
1104 |         "http": [
1105 |             ("http", "Official Dart HTTP package"),
1106 |             ("dio", "Advanced HTTP client with interceptors"),
1107 |             ("retrofit", "Type-safe HTTP client generator"),
1108 |             ("chopper", "HTTP client with built-in JsonConverter"),
1109 |             ("GraphQL", "Query language for APIs"),
1110 |             ("REST API", "RESTful web services"),
1111 |             ("WebSocket", "Real-time bidirectional communication"),
1112 |             ("gRPC", "High performance RPC framework"),
1113 |         ],
1114 |         "database": [
1115 |             ("sqflite", "SQLite for Flutter"),
1116 |             ("hive", "NoSQL database for Flutter"),
1117 |             ("drift", "Reactive persistence library"),
1118 |             ("objectbox", "Fast NoSQL database"),
1119 |             ("shared_preferences", "Simple key-value storage"),
1120 |             ("isar", "Fast cross-platform database"),
1121 |             ("floor", "SQLite abstraction"),
1122 |             ("sembast", "NoSQL persistent store"),
1123 |             ("Firebase Realtime Database", "Cloud-hosted NoSQL database"),
1124 |             ("Cloud Firestore", "Scalable NoSQL cloud database"),
1125 |         ],
1126 |         "animation": [
1127 |             ("AnimationController", "Control animations"),
1128 |             ("AnimatedBuilder", "Build animations efficiently"),
1129 |             ("Hero", "Shared element transitions"),
1130 |             ("animations", "Pre-built animation package"),
1131 |             ("rive", "Interactive animations"),
1132 |             ("lottie", "After Effects animations"),
1133 |             ("AnimatedContainer", "Implicit animations"),
1134 |             ("TweenAnimationBuilder", "Simple custom animations"),
1135 |             ("Curves", "Animation easing functions"),
1136 |             ("Physics-based animations", "Spring and friction animations"),
1137 |         ],
1138 |         "architecture": [
1139 |             ("BLoC Pattern", "Business Logic Component pattern for state management"),
1140 |             ("MVVM", "Model-View-ViewModel architecture pattern"),
1141 |             ("Clean Architecture", "Domain-driven design with clear separation"),
1142 |             ("Repository Pattern", "Abstraction layer for data sources"),
1143 |             ("Provider Pattern", "Dependency injection and state management"),
1144 |             ("GetX Pattern", "Reactive state management with GetX"),
1145 |             ("MVC", "Model-View-Controller pattern in Flutter"),
1146 |             ("Redux", "Predictable state container pattern"),
1147 |             ("Riverpod Architecture", "Modern reactive caching framework"),
1148 |             ("Domain Driven Design", "DDD principles in Flutter"),
1149 |             ("Hexagonal Architecture", "Ports and adapters pattern"),
1150 |             ("Feature-based structure", "Organize code by features"),
1151 |         ],
1152 |         "testing": [
1153 |             ("Widget Testing", "Testing Flutter widgets in isolation"),
1154 |             ("Integration Testing", "End-to-end testing of Flutter apps"),
1155 |             ("Unit Testing", "Testing Dart code logic"),
1156 |             ("Golden Testing", "Visual regression testing"),
1157 |             ("Mockito", "Mocking framework for Dart"),
1158 |             ("flutter_test", "Flutter testing framework"),
1159 |             ("test", "Dart testing package"),
1160 |             ("integration_test", "Flutter integration testing"),
1161 |             ("mocktail", "Type-safe mocking library"),
1162 |             ("Test Coverage", "Measuring test completeness"),
1163 |             ("TDD", "Test-driven development"),
1164 |             ("BDD", "Behavior-driven development"),
1165 |         ],
1166 |         "performance": [
1167 |             ("Performance Profiling", "Analyzing app performance"),
1168 |             ("Widget Inspector", "Debugging widget trees"),
1169 |             ("Timeline View", "Performance timeline analysis"),
1170 |             ("Memory Profiling", "Analyzing memory usage"),
1171 |             ("Shader Compilation", "Reducing shader jank"),
1172 |             ("Build Optimization", "Optimizing build methods"),
1173 |             ("Lazy Loading", "Loading content on demand"),
1174 |             ("Image Caching", "Efficient image loading"),
1175 |             ("Code Splitting", "Reducing initial bundle size"),
1176 |             ("Tree Shaking", "Removing unused code"),
1177 |             ("Const Constructors", "Compile-time optimizations"),
1178 |             ("RepaintBoundary", "Isolate expensive paints"),
1179 |         ],
1180 |         "platform": [
1181 |             ("Platform Channels", "Communication with native code"),
1182 |             ("Method Channel", "Invoking platform-specific APIs"),
1183 |             ("Event Channel", "Streaming data from platform"),
1184 |             ("iOS Integration", "Flutter iOS-specific features"),
1185 |             ("Android Integration", "Flutter Android-specific features"),
1186 |             ("Web Support", "Flutter web-specific features"),
1187 |             ("Desktop Support", "Flutter desktop applications"),
1188 |             ("Embedding Flutter", "Adding Flutter to existing apps"),
1189 |             ("Platform Views", "Embedding native views"),
1190 |             ("FFI", "Foreign Function Interface"),
1191 |             ("Plugin Development", "Creating Flutter plugins"),
1192 |             ("Platform-specific UI", "Adaptive UI patterns"),
1193 |         ],
1194 |         "debugging": [
1195 |             ("Flutter Inspector", "Visual debugging tool"),
1196 |             ("Logging", "Debug logging in Flutter"),
1197 |             ("Breakpoints", "Using breakpoints in Flutter"),
1198 |             ("DevTools", "Flutter DevTools suite"),
1199 |             ("Error Handling", "Handling errors in Flutter"),
1200 |             ("Crash Reporting", "Capturing and reporting crashes"),
1201 |             ("Debug Mode", "Flutter debug mode features"),
1202 |             ("Assert Statements", "Debug-only checks"),
1203 |             ("Stack Traces", "Understanding error traces"),
1204 |             ("Network Debugging", "Inspecting network requests"),
1205 |             ("Layout Explorer", "Visualize layout constraints"),
1206 |             ("Performance Overlay", "On-device performance metrics"),
1207 |         ],
1208 |         "forms": [
1209 |             ("Form", "Container for form fields"),
1210 |             ("TextFormField", "Text input with validation"),
1211 |             ("FormField", "Base class for form fields"),
1212 |             ("Form Validation", "Validating user input"),
1213 |             ("Input Decoration", "Styling form fields"),
1214 |             ("Focus Management", "Managing input focus"),
1215 |             ("Keyboard Actions", "Custom keyboard actions"),
1216 |             ("Input Formatters", "Format input as typed"),
1217 |             ("Form State", "Managing form state"),
1218 |             ("Custom Form Fields", "Creating custom inputs"),
1219 |         ],
1220 |         "theming": [
1221 |             ("ThemeData", "Application theme configuration"),
1222 |             ("Material Theme", "Material Design theming"),
1223 |             ("Dark Mode", "Supporting dark theme"),
1224 |             ("Custom Themes", "Creating custom themes"),
1225 |             ("Theme Extensions", "Extending theme data"),
1226 |             ("Color Schemes", "Material 3 color system"),
1227 |             ("Typography", "Text theming"),
1228 |             ("Dynamic Theming", "Runtime theme changes"),
1229 |             ("Platform Theming", "Platform-specific themes"),
1230 |         ],
1231 |     }
1232 |     
1233 |     # Check if query matches any concept
1234 |     for concept, items in concepts.items():
1235 |         if concept in query_lower or any(word in concept for word in query_lower.split()):
1236 |             for item_name, item_desc in items:
1237 |                 results.append({
1238 |                     "type": "concept",
1239 |                     "relevance": 0.8,
1240 |                     "title": item_name,
1241 |                     "description": item_desc,
1242 |                     "concept": concept
1243 |                 })
1244 |     
1245 |     # Apply type filtering if specified
1246 |     if types:
1247 |         filtered_results = []
1248 |         for result in results:
1249 |             result_type = result.get("type", "")
1250 |             # Map result types to filter types
1251 |             if "flutter" in types and result_type == "flutter_class":
1252 |                 filtered_results.append(result)
1253 |             elif "dart" in types and result_type == "dart_class":
1254 |                 filtered_results.append(result)
1255 |             elif "package" in types and result_type == "pub_package":
1256 |                 filtered_results.append(result)
1257 |             elif "concept" in types and result_type == "concept":
1258 |                 filtered_results.append(result)
1259 |         results = filtered_results
1260 |     
1261 |     # Sort results by relevance
1262 |     results.sort(key=lambda x: x["relevance"], reverse=True)
1263 |     
1264 |     # Apply limit
1265 |     results = results[:limit]
1266 |     
1267 |     # Fetch actual documentation for top results if needed
1268 |     enriched_results = []
1269 |     for result in results:
1270 |         if result["type"] == "flutter_class" and "class_name" in result:
1271 |             # Only fetch full docs for top 3 Flutter classes
1272 |             if len(enriched_results) < 3:
1273 |                 try:
1274 |                     doc = await _get_flutter_docs_impl(result["class_name"], result["library"])
1275 |                     if not doc.get("error"):
1276 |                         result["documentation_available"] = True
1277 |                         result["content_preview"] = doc.get("content", "")[:300] + "..."
1278 |                     else:
1279 |                         result["documentation_available"] = False
1280 |                         result["error_info"] = doc.get("error_type", "unknown")
1281 |                 except Exception as e:
1282 |                     logger.warning("search_enrichment_error", error=str(e), class_name=result.get("class_name"))
1283 |                     result["documentation_available"] = False
1284 |                     result["error_info"] = "enrichment_failed"
1285 |         elif result["type"] == "pub_package" and "package_name" in result:
1286 |             # Add pub.dev URL
1287 |             result["url"] = f"https://pub.dev/packages/{result['package_name']}"
1288 |             result["documentation_available"] = True
1289 |         
1290 |         enriched_results.append(result)
1291 |     
1292 |     # Prepare final response
1293 |     response = {
1294 |         "query": query,
1295 |         "results": enriched_results,
1296 |         "total": len(enriched_results),
1297 |         "timestamp": datetime.utcnow().isoformat(),
1298 |         "suggestions": generate_search_suggestions(query_lower, enriched_results)
1299 |     }
1300 |     
1301 |     # Cache the search results for 1 hour
1302 |     cache_manager.set(cache_key, response, 3600)
1303 |     
1304 |     return response
1305 | 
1306 | 
1307 | def calculate_relevance(query: str, title: str, description: str) -> float:
1308 |     """Calculate relevance score based on fuzzy matching."""
1309 |     score = 0.0
1310 |     
1311 |     # Exact match in title
1312 |     if query == title:
1313 |         score += 1.0
1314 |     # Partial match in title
1315 |     elif query in title:
1316 |         score += 0.8
1317 |     # Word match in title
1318 |     elif any(word in title for word in query.split()):
1319 |         score += 0.6
1320 |     
1321 |     # Match in description
1322 |     if query in description:
1323 |         score += 0.4
1324 |     elif any(word in description for word in query.split() if len(word) > 3):
1325 |         score += 0.2
1326 |     
1327 |     # Fuzzy match using character overlap
1328 |     title_overlap = len(set(query) & set(title)) / len(set(query) | set(title)) if title else 0
1329 |     desc_overlap = len(set(query) & set(description)) / len(set(query) | set(description)) if description else 0
1330 |     score += (title_overlap * 0.3 + desc_overlap * 0.1)
1331 |     
1332 |     return min(score, 1.0)
1333 | 
1334 | 
1335 | def generate_search_suggestions(query: str, results: List[Dict]) -> List[str]:
1336 |     """Generate helpful search suggestions based on query and results."""
1337 |     suggestions = []
1338 |     
1339 |     if not results:
1340 |         suggestions.append(f"Try searching for specific widget names like 'Container' or 'Scaffold'")
1341 |         suggestions.append(f"Use package names from pub.dev like 'provider' or 'dio'")
1342 |         suggestions.append(f"Search for concepts like 'state management' or 'navigation'")
1343 |     elif len(results) < 3:
1344 |         suggestions.append(f"For more results, try broader terms or related concepts")
1345 |         if any(r["type"] == "flutter_class" for r in results):
1346 |             suggestions.append(f"You can also search for specific libraries like 'material.AppBar'")
1347 |     
1348 |     return suggestions
1349 | 
1350 | 
1351 | @mcp.tool()
1352 | async def flutter_docs(
1353 |     identifier: str,
1354 |     topic: Optional[str] = None,
1355 |     tokens: int = 10000
1356 | ) -> Dict[str, Any]:
1357 |     """
1358 |     Unified tool to get Flutter/Dart documentation with smart identifier resolution.
1359 |     
1360 |     Automatically detects the type of identifier and fetches appropriate documentation.
1361 |     Supports Flutter classes, Dart classes, and pub.dev packages.
1362 |     
1363 |     Args:
1364 |         identifier: The identifier to look up. Examples:
1365 |                    - "Container" (Flutter widget)
1366 |                    - "material.AppBar" (library-qualified Flutter class)
1367 |                    - "dart:async.Future" (Dart API)
1368 |                    - "provider" (pub.dev package)
1369 |                    - "pub:dio" (explicit pub.dev package)
1370 |                    - "flutter:Container" (explicit Flutter class)
1371 |         topic: Optional topic filter. For classes: "constructors", "methods", 
1372 |                "properties", "examples". For packages: "getting-started", 
1373 |                "examples", "api", "installation"
1374 |         tokens: Maximum tokens for response (default: 10000, min: 1000)
1375 |     
1376 |     Returns:
1377 |         Dictionary with documentation content, type, and metadata
1378 |     """
1379 |     bind_contextvars(tool="flutter_docs", identifier=identifier, topic=topic)
1380 |     logger.info("resolving_identifier", identifier=identifier)
1381 |     
1382 |     # Validate tokens parameter
1383 |     if tokens < 1000:
1384 |         return {"error": "tokens parameter must be at least 1000"}
1385 |     
1386 |     # Parse identifier to determine type
1387 |     identifier_lower = identifier.lower()
1388 |     doc_type = None
1389 |     library = None
1390 |     class_name = None
1391 |     package_name = None
1392 |     
1393 |     # Check for explicit prefixes
1394 |     if identifier.startswith("pub:"):
1395 |         doc_type = "pub_package"
1396 |         package_name = identifier[4:]
1397 |     elif identifier.startswith("flutter:"):
1398 |         doc_type = "flutter_class"
1399 |         class_name = identifier[8:]
1400 |         library = "widgets"  # Default to widgets
1401 |     elif identifier.startswith("dart:"):
1402 |         doc_type = "dart_class"
1403 |         # Parse dart:library.Class format
1404 |         parts = identifier.split(".")
1405 |         if len(parts) == 2:
1406 |             library = parts[0]
1407 |             class_name = parts[1]
1408 |         else:
1409 |             class_name = identifier[5:]
1410 |             library = "dart:core"
1411 |     elif "." in identifier:
1412 |         # Library-qualified name (e.g., material.AppBar)
1413 |         parts = identifier.split(".", 1)
1414 |         library = parts[0]
1415 |         class_name = parts[1]
1416 |         
1417 |         if library.startswith("dart:"):
1418 |             doc_type = "dart_class"
1419 |         else:
1420 |             doc_type = "flutter_class"
1421 |     else:
1422 |         # Auto-detect type by trying different sources
1423 |         # First check if it's a known Flutter class
1424 |         flutter_libs = ["widgets", "material", "cupertino", "painting", "animation", 
1425 |                        "rendering", "services", "gestures", "foundation"]
1426 |         
1427 |         # Try to find in common Flutter widgets
1428 |         for lib in flutter_libs:
1429 |             test_url = f"https://api.flutter.dev/flutter/{lib}/{identifier}-class.html"
1430 |             if identifier.lower() in ["container", "scaffold", "appbar", "column", "row", 
1431 |                                     "text", "button", "listview", "gridview", "stack"]:
1432 |                 doc_type = "flutter_class"
1433 |                 class_name = identifier
1434 |                 library = "widgets" if identifier.lower() in ["container", "column", "row", "text", "stack"] else "material"
1435 |                 break
1436 |         
1437 |         if not doc_type:
1438 |             # Could be a package or unknown Flutter class
1439 |             # We'll try both and see what works
1440 |             doc_type = "auto"
1441 |             class_name = identifier
1442 |             package_name = identifier
1443 |     
1444 |     # Based on detected type, fetch documentation
1445 |     result = {
1446 |         "identifier": identifier,
1447 |         "type": doc_type,
1448 |         "topic": topic,
1449 |         "max_tokens": tokens
1450 |     }
1451 |     
1452 |     if doc_type == "flutter_class" or (doc_type == "auto" and class_name):
1453 |         # Try Flutter documentation first
1454 |         flutter_doc = await get_flutter_docs(class_name, library or "widgets", tokens=tokens)
1455 |         
1456 |         if "error" not in flutter_doc:
1457 |             # Successfully found Flutter documentation
1458 |             content = flutter_doc.get("content", "")
1459 |             
1460 |             # Apply topic filtering if requested
1461 |             if topic:
1462 |                 content = filter_documentation_by_topic(content, topic, "flutter_class")
1463 |                 # Recount tokens after filtering
1464 |                 filtered_tokens = token_manager.count_tokens(content)
1465 |                 # If filtering reduced content below token limit, no need for further truncation
1466 |                 if tokens and filtered_tokens > tokens:
1467 |                     content = truncate_flutter_docs(content, class_name, tokens, strategy="balanced")
1468 |                     final_tokens = token_manager.count_tokens(content)
1469 |                 else:
1470 |                     final_tokens = filtered_tokens
1471 |             else:
1472 |                 # Use the token count from get_flutter_docs if no filtering
1473 |                 final_tokens = flutter_doc.get("token_count", token_manager.count_tokens(content))
1474 |             
1475 |             result.update({
1476 |                 "type": "flutter_class",
1477 |                 "class": class_name,
1478 |                 "library": flutter_doc.get("library"),
1479 |                 "content": content,
1480 |                 "source": flutter_doc.get("source"),
1481 |                 "truncated": flutter_doc.get("truncated", False) or topic is not None,
1482 |                 "token_count": final_tokens,
1483 |                 "original_tokens": flutter_doc.get("original_tokens", final_tokens),
1484 |                 "truncation_note": flutter_doc.get("truncation_note")
1485 |             })
1486 |             return result
1487 |         elif doc_type != "auto":
1488 |             # Explicit Flutter class not found
1489 |             return {
1490 |                 "identifier": identifier,
1491 |                 "type": "flutter_class",
1492 |                 "error": flutter_doc.get("error"),
1493 |                 "suggestion": flutter_doc.get("suggestion")
1494 |             }
1495 |     
1496 |     if doc_type == "dart_class":
1497 |         # Try Dart documentation
1498 |         dart_doc = await get_flutter_docs(class_name, library, tokens=tokens)
1499 |         
1500 |         if "error" not in dart_doc:
1501 |             content = dart_doc.get("content", "")
1502 |             
1503 |             # Apply topic filtering if requested
1504 |             if topic:
1505 |                 content = filter_documentation_by_topic(content, topic, "dart_class")
1506 |                 # Recount tokens after filtering
1507 |                 filtered_tokens = token_manager.count_tokens(content)
1508 |                 # If filtering reduced content below token limit, no need for further truncation
1509 |                 if tokens and filtered_tokens > tokens:
1510 |                     content = truncate_flutter_docs(content, class_name, tokens, strategy="balanced")
1511 |                     final_tokens = token_manager.count_tokens(content)
1512 |                 else:
1513 |                     final_tokens = filtered_tokens
1514 |             else:
1515 |                 # Use the token count from get_flutter_docs if no filtering
1516 |                 final_tokens = dart_doc.get("token_count", token_manager.count_tokens(content))
1517 |             
1518 |             result.update({
1519 |                 "type": "dart_class",
1520 |                 "class": class_name,
1521 |                 "library": library,
1522 |                 "content": content,
1523 |                 "source": dart_doc.get("source"),
1524 |                 "truncated": dart_doc.get("truncated", False) or topic is not None,
1525 |                 "token_count": final_tokens,
1526 |                 "original_tokens": dart_doc.get("original_tokens", final_tokens),
1527 |                 "truncation_note": dart_doc.get("truncation_note")
1528 |             })
1529 |             return result
1530 |         else:
1531 |             return {
1532 |                 "identifier": identifier,
1533 |                 "type": "dart_class",
1534 |                 "error": dart_doc.get("error"),
1535 |                 "suggestion": "Check the class name and library. Example: dart:async.Future"
1536 |             }
1537 |     
1538 |     if doc_type == "pub_package" or doc_type == "auto":
1539 |         # Try pub.dev package
1540 |         package_doc = await _get_pub_package_info_impl(package_name)
1541 |         
1542 |         if "error" not in package_doc:
1543 |             # Successfully found package
1544 |             # Format content based on topic
1545 |             if topic:
1546 |                 content = format_package_content_by_topic(package_doc, topic)
1547 |             else:
1548 |                 content = format_package_content(package_doc)
1549 |             
1550 |             # Count original tokens
1551 |             original_tokens = token_manager.count_tokens(content)
1552 |             truncated = False
1553 |             truncation_note = None
1554 |             
1555 |             # Apply token truncation if needed
1556 |             if tokens and original_tokens > tokens:
1557 |                 truncator = create_truncator(tokens)
1558 |                 content = truncator.truncate(content)
1559 |                 truncated = True
1560 |                 truncation_note = f"Documentation truncated from {original_tokens} to approximately {tokens} tokens"
1561 |             
1562 |             # Count final tokens
1563 |             final_tokens = token_manager.count_tokens(content)
1564 |             
1565 |             result.update({
1566 |                 "type": "pub_package",
1567 |                 "package": package_name,
1568 |                 "version": package_doc.get("version"),
1569 |                 "content": content,
1570 |                 "source": package_doc.get("source"),
1571 |                 "metadata": {
1572 |                     "description": package_doc.get("description"),
1573 |                     "homepage": package_doc.get("homepage"),
1574 |                     "repository": package_doc.get("repository"),
1575 |                     "likes": package_doc.get("likes"),
1576 |                     "pub_points": package_doc.get("pub_points"),
1577 |                     "platforms": package_doc.get("platforms")
1578 |                 },
1579 |                 "truncated": truncated or topic is not None,
1580 |                 "token_count": final_tokens,
1581 |                 "original_tokens": original_tokens if truncated else final_tokens,
1582 |                 "truncation_note": truncation_note
1583 |             })
1584 |             return result
1585 |         elif doc_type == "pub_package":
1586 |             # Explicit package not found
1587 |             return {
1588 |                 "identifier": identifier,
1589 |                 "type": "pub_package",
1590 |                 "error": package_doc.get("error"),
1591 |                 "suggestion": "Check the package name on pub.dev"
1592 |             }
1593 |     
1594 |     # If auto-detection failed to find anything
1595 |     if doc_type == "auto":
1596 |         # Try search as last resort
1597 |         search_results = await search_flutter_docs(identifier)
1598 |         if search_results.get("results"):
1599 |             top_result = search_results["results"][0]
1600 |             return {
1601 |                 "identifier": identifier,
1602 |                 "type": "search_suggestion",
1603 |                 "error": f"Could not find exact match for '{identifier}'",
1604 |                 "suggestion": f"Did you mean '{top_result['title']}'?",
1605 |                 "search_results": search_results["results"][:3]
1606 |             }
1607 |         else:
1608 |             return {
1609 |                 "identifier": identifier,
1610 |                 "type": "not_found",
1611 |                 "error": f"No documentation found for '{identifier}'",
1612 |                 "suggestion": "Try using explicit prefixes like 'pub:', 'flutter:', or 'dart:'"
1613 |             }
1614 |     
1615 |     # Should not reach here
1616 |     return {
1617 |         "identifier": identifier,
1618 |         "type": "error",
1619 |         "error": "Failed to resolve identifier"
1620 |     }
1621 | 
1622 | 
1623 | def filter_documentation_by_topic(content: str, topic: str, doc_type: str) -> str:
1624 |     """Filter documentation content by topic"""
1625 |     topic_lower = topic.lower()
1626 |     
1627 |     if doc_type in ["flutter_class", "dart_class"]:
1628 |         # Class documentation topics
1629 |         lines = content.split('\n')
1630 |         filtered_lines = []
1631 |         current_section = None
1632 |         include_section = False
1633 |         
1634 |         for line in lines:
1635 |             # Detect section headers
1636 |             if line.startswith('## '):
1637 |                 section_name = line[3:].lower()
1638 |                 current_section = section_name
1639 |                 
1640 |                 # Determine if we should include this section
1641 |                 if topic_lower == "constructors" and "constructor" in section_name:
1642 |                     include_section = True
1643 |                 elif topic_lower == "methods" and "method" in section_name:
1644 |                     include_section = True
1645 |                 elif topic_lower == "properties" and "propert" in section_name:
1646 |                     include_section = True
1647 |                 elif topic_lower == "examples" and ("example" in section_name or "code" in section_name):
1648 |                     include_section = True
1649 |                 else:
1650 |                     include_section = False
1651 |             
1652 |             # Always include the class name and description
1653 |             if line.startswith('# ') or (current_section == "description" and not line.startswith('## ')):
1654 |                 filtered_lines.append(line)
1655 |             elif include_section:
1656 |                 filtered_lines.append(line)
1657 |         
1658 |         return '\n'.join(filtered_lines)
1659 |     
1660 |     return content
1661 | 
1662 | 
1663 | def format_package_content(package_doc: Dict[str, Any]) -> str:
1664 |     """Format package documentation into readable content"""
1665 |     content = []
1666 |     
1667 |     # Header
1668 |     content.append(f"# {package_doc['name']} v{package_doc['version']}")
1669 |     content.append("")
1670 |     
1671 |     # Description
1672 |     content.append("## Description")
1673 |     content.append(package_doc.get('description', 'No description available'))
1674 |     content.append("")
1675 |     
1676 |     # Metadata
1677 |     content.append("## Package Information")
1678 |     content.append(f"- **Version**: {package_doc['version']}")
1679 |     content.append(f"- **Published**: {package_doc.get('updated', 'Unknown')}")
1680 |     content.append(f"- **Publisher**: {package_doc.get('publisher', 'Unknown')}")
1681 |     content.append(f"- **Platforms**: {', '.join(package_doc.get('platforms', []))}")
1682 |     content.append(f"- **Likes**: {package_doc.get('likes', 0)}")
1683 |     content.append(f"- **Pub Points**: {package_doc.get('pub_points', 0)}")
1684 |     content.append(f"- **Popularity**: {package_doc.get('popularity', 0)}")
1685 |     content.append("")
1686 |     
1687 |     # Links
1688 |     if package_doc.get('homepage') or package_doc.get('repository'):
1689 |         content.append("## Links")
1690 |         if package_doc.get('homepage'):
1691 |             content.append(f"- **Homepage**: {package_doc['homepage']}")
1692 |         if package_doc.get('repository'):
1693 |             content.append(f"- **Repository**: {package_doc['repository']}")
1694 |         if package_doc.get('documentation'):
1695 |             content.append(f"- **Documentation**: {package_doc['documentation']}")
1696 |         content.append("")
1697 |     
1698 |     # Dependencies
1699 |     if package_doc.get('dependencies'):
1700 |         content.append("## Dependencies")
1701 |         for dep in package_doc['dependencies']:
1702 |             content.append(f"- {dep}")
1703 |         content.append("")
1704 |     
1705 |     # Environment
1706 |     if package_doc.get('environment'):
1707 |         content.append("## Environment")
1708 |         for key, value in package_doc['environment'].items():
1709 |             content.append(f"- **{key}**: {value}")
1710 |         content.append("")
1711 |     
1712 |     # README
1713 |     if package_doc.get('readme'):
1714 |         content.append("## README")
1715 |         content.append(package_doc['readme'])
1716 |     
1717 |     return '\n'.join(content)
1718 | 
1719 | 
1720 | def format_package_content_by_topic(package_doc: Dict[str, Any], topic: str) -> str:
1721 |     """Format package documentation filtered by topic"""
1722 |     topic_lower = topic.lower()
1723 |     content = []
1724 |     
1725 |     # Always include header
1726 |     content.append(f"# {package_doc['name']} v{package_doc['version']}")
1727 |     content.append("")
1728 |     
1729 |     if topic_lower == "installation":
1730 |         content.append("## Installation")
1731 |         content.append("")
1732 |         content.append("Add this to your package's `pubspec.yaml` file:")
1733 |         content.append("")
1734 |         content.append("```yaml")
1735 |         content.append("dependencies:")
1736 |         content.append(f"  {package_doc['name']}: ^{package_doc['version']}")
1737 |         content.append("```")
1738 |         content.append("")
1739 |         content.append("Then run:")
1740 |         content.append("```bash")
1741 |         content.append("flutter pub get")
1742 |         content.append("```")
1743 |         
1744 |         # Include environment requirements
1745 |         if package_doc.get('environment'):
1746 |             content.append("")
1747 |             content.append("### Requirements")
1748 |             for key, value in package_doc['environment'].items():
1749 |                 content.append(f"- **{key}**: {value}")
1750 |                 
1751 |     elif topic_lower == "getting-started":
1752 |         content.append("## Getting Started")
1753 |         content.append("")
1754 |         content.append(package_doc.get('description', 'No description available'))
1755 |         content.append("")
1756 |         
1757 |         # Extract getting started section from README if available
1758 |         if package_doc.get('readme'):
1759 |             readme_lower = package_doc['readme'].lower()
1760 |             # Look for getting started section
1761 |             start_idx = readme_lower.find("getting started")
1762 |             if start_idx == -1:
1763 |                 start_idx = readme_lower.find("quick start")
1764 |             if start_idx == -1:
1765 |                 start_idx = readme_lower.find("usage")
1766 |             
1767 |             if start_idx != -1:
1768 |                 # Extract section
1769 |                 readme_section = package_doc['readme'][start_idx:]
1770 |                 # Find next section header
1771 |                 next_section = readme_section.find("\n## ")
1772 |                 if next_section != -1:
1773 |                     readme_section = readme_section[:next_section]
1774 |                 content.append(readme_section)
1775 |                 
1776 |     elif topic_lower == "examples":
1777 |         content.append("## Examples")
1778 |         content.append("")
1779 |         
1780 |         # Extract examples from README
1781 |         if package_doc.get('readme'):
1782 |             readme = package_doc['readme']
1783 |             # Find code blocks
1784 |             code_blocks = re.findall(r'```[\w]*\n(.*?)\n```', readme, re.DOTALL)
1785 |             if code_blocks:
1786 |                 for i, code in enumerate(code_blocks[:5]):  # Limit to 5 examples
1787 |                     content.append(f"### Example {i+1}")
1788 |                     content.append("```dart")
1789 |                     content.append(code)
1790 |                     content.append("```")
1791 |                     content.append("")
1792 |             else:
1793 |                 content.append("No code examples found in documentation.")
1794 |                 
1795 |     elif topic_lower == "api":
1796 |         content.append("## API Reference")
1797 |         content.append("")
1798 |         content.append(f"Full API documentation: https://pub.dev/documentation/{package_doc['name']}/latest/")
1799 |         content.append("")
1800 |         
1801 |         # Include basic package info
1802 |         content.append("### Package Information")
1803 |         content.append(f"- **Version**: {package_doc['version']}")
1804 |         content.append(f"- **Platforms**: {', '.join(package_doc.get('platforms', []))}")
1805 |         
1806 |         if package_doc.get('dependencies'):
1807 |             content.append("")
1808 |             content.append("### Dependencies")
1809 |             for dep in package_doc['dependencies']:
1810 |                 content.append(f"- {dep}")
1811 |     
1812 |     else:
1813 |         # Default to full content for unknown topics
1814 |         return format_package_content(package_doc)
1815 |     
1816 |     return '\n'.join(content)
1817 | 
1818 | 
1819 | @mcp.tool()
1820 | async def process_flutter_mentions(text: str, tokens: int = 4000) -> Dict[str, Any]:
1821 |     """
1822 |     Parse text for @flutter_mcp mentions and return relevant documentation.
1823 |     
1824 |     NOTE: This tool is maintained for backward compatibility. For new integrations,
1825 |     consider using the unified tools directly:
1826 |     - flutter_docs: For Flutter/Dart classes and pub.dev packages
1827 |     - flutter_search: For searching Flutter/Dart documentation
1828 |     
1829 |     Supports patterns like:
1830 |     - @flutter_mcp provider (pub.dev package - latest version)
1831 |     - @flutter_mcp provider:^6.0.0 (specific version constraint)
1832 |     - @flutter_mcp riverpod:2.5.1 (exact version)
1833 |     - @flutter_mcp dio:>=5.0.0 <6.0.0 (version range)
1834 |     - @flutter_mcp bloc:latest (latest version keyword)
1835 |     - @flutter_mcp material.AppBar (Flutter class)
1836 |     - @flutter_mcp dart:async.Future (Dart API)
1837 |     - @flutter_mcp Container (widget)
1838 |     
1839 |     Args:
1840 |         text: Text containing @flutter_mcp mentions
1841 |         tokens: Maximum token limit for each mention's documentation (default: 4000, min: 500)
1842 |     
1843 |     Returns:
1844 |         Dictionary with parsed mentions and their documentation
1845 |     """
1846 |     bind_contextvars(tool="process_flutter_mentions", text_length=len(text))
1847 |     
1848 |     # Validate tokens parameter
1849 |     if tokens < 500:
1850 |         return {"error": "tokens parameter must be at least 500"}
1851 |     
1852 |     # Updated pattern to match @flutter_mcp mentions with version constraints
1853 |     # Now supports version constraints like :^6.0.0, :>=5.0.0 <6.0.0, etc.
1854 |     pattern = r'@flutter_mcp\s+([a-zA-Z0-9_.:]+(?:\s*[<>=^]+\s*[0-9.+\-\w]+(?:\s*[<>=]+\s*[0-9.+\-\w]+)?)?)'
1855 |     mentions = re.findall(pattern, text)
1856 |     
1857 |     if not mentions:
1858 |         return {
1859 |             "mentions_found": 0,
1860 |             "message": "No @flutter_mcp mentions found in text",
1861 |             "results": []
1862 |         }
1863 |     
1864 |     logger.info("mentions_found", count=len(mentions))
1865 |     results = []
1866 |     
1867 |     # Process each mention using the unified flutter_docs tool
1868 |     for mention in mentions:
1869 |         logger.info("processing_mention", mention=mention)
1870 |         
1871 |         try:
1872 |             # Parse version constraints if present
1873 |             if ':' in mention and not mention.startswith('dart:'):
1874 |                 # Package with version constraint
1875 |                 parts = mention.split(':', 1)
1876 |                 identifier = parts[0]
1877 |                 version_spec = parts[1]
1878 |                 
1879 |                 # For packages with version constraints, use get_pub_package_info
1880 |                 if version_spec and version_spec != 'latest':
1881 |                     # Extract actual version if it's a simple version number
1882 |                     version = None
1883 |                     if re.match(r'^\d+\.\d+\.\d+$', version_spec.strip()):
1884 |                         version = version_spec.strip()
1885 |                     
1886 |                     # Get package with specific version
1887 |                     doc_result = await get_pub_package_info(identifier, version=version)
1888 |                     
1889 |                     if "error" not in doc_result:
1890 |                         results.append({
1891 |                             "mention": mention,
1892 |                             "type": "pub_package",
1893 |                             "documentation": doc_result
1894 |                         })
1895 |                         if version_spec and version_spec != version:
1896 |                             results[-1]["documentation"]["version_constraint"] = version_spec
1897 |                     else:
1898 |                         results.append({
1899 |                             "mention": mention,
1900 |                             "type": "package_version_error",
1901 |                             "error": doc_result["error"]
1902 |                         })
1903 |                 else:
1904 |                     # Latest version requested
1905 |                     doc_result = await flutter_docs(identifier, max_tokens=tokens)
1906 |             else:
1907 |                 # Use unified flutter_docs for all other cases
1908 |                 doc_result = await flutter_docs(mention, max_tokens=tokens)
1909 |             
1910 |             # Process the result from flutter_docs
1911 |             if "error" not in doc_result:
1912 |                 # Determine type based on result
1913 |                 doc_type = doc_result.get("type", "unknown")
1914 |                 
1915 |                 if doc_type == "flutter_class":
1916 |                     results.append({
1917 |                         "mention": mention,
1918 |                         "type": "flutter_class",
1919 |                         "documentation": doc_result
1920 |                     })
1921 |                 elif doc_type == "dart_class":
1922 |                     results.append({
1923 |                         "mention": mention,
1924 |                         "type": "dart_api",
1925 |                         "documentation": doc_result
1926 |                     })
1927 |                 elif doc_type == "pub_package":
1928 |                     results.append({
1929 |                         "mention": mention,
1930 |                         "type": "pub_package",
1931 |                         "documentation": doc_result
1932 |                     })
1933 |                 else:
1934 |                     # Fallback for auto-detected types
1935 |                     results.append({
1936 |                         "mention": mention,
1937 |                         "type": doc_result.get("type", "flutter_widget"),
1938 |                         "documentation": doc_result
1939 |                     })
1940 |             else:
1941 |                 # Try search as fallback
1942 |                 search_result = await flutter_search(mention, limit=1)
1943 |                 if search_result.get("results"):
1944 |                     results.append({
1945 |                         "mention": mention,
1946 |                         "type": search_result["results"][0].get("type", "flutter_widget"),
1947 |                         "documentation": search_result["results"][0]
1948 |                     })
1949 |                 else:
1950 |                     results.append({
1951 |                         "mention": mention,
1952 |                         "type": "not_found",
1953 |                         "error": f"No documentation found for '{mention}'"
1954 |                     })
1955 |                     
1956 |         except Exception as e:
1957 |             logger.error("mention_processing_error", mention=mention, error=str(e))
1958 |             results.append({
1959 |                 "mention": mention,
1960 |                 "type": "error",
1961 |                 "error": f"Error processing mention: {str(e)}"
1962 |             })
1963 |     
1964 |     # Format results - keep the same format for backward compatibility
1965 |     formatted_results = []
1966 |     for result in results:
1967 |         if "error" in result:
1968 |             formatted_results.append({
1969 |                 "mention": result["mention"],
1970 |                 "type": result["type"],
1971 |                 "error": result["error"]
1972 |             })
1973 |         else:
1974 |             doc = result["documentation"]
1975 |             if result["type"] == "pub_package":
1976 |                 # Format package info
1977 |                 formatted_result = {
1978 |                     "mention": result["mention"],
1979 |                     "type": "pub_package",
1980 |                     "name": doc.get("name", ""),
1981 |                     "version": doc.get("version", ""),
1982 |                     "description": doc.get("description", ""),
1983 |                     "documentation_url": doc.get("documentation", ""),
1984 |                     "dependencies": doc.get("dependencies", []),
1985 |                     "likes": doc.get("likes", 0),
1986 |                     "pub_points": doc.get("pub_points", 0)
1987 |                 }
1988 |                 
1989 |                 # Add version constraint info if present
1990 |                 if "version_constraint" in doc:
1991 |                     formatted_result["version_constraint"] = doc["version_constraint"]
1992 |                 if "resolved_version" in doc:
1993 |                     formatted_result["resolved_version"] = doc["resolved_version"]
1994 |                     
1995 |                 formatted_results.append(formatted_result)
1996 |             else:
1997 |                 # Format Flutter/Dart documentation
1998 |                 formatted_results.append({
1999 |                     "mention": result["mention"],
2000 |                     "type": result["type"],
2001 |                     "class": doc.get("class", doc.get("identifier", "")),
2002 |                     "library": doc.get("library", ""),
2003 |                     "content": doc.get("content", ""),
2004 |                     "source": doc.get("source", "live")
2005 |                 })
2006 |     
2007 |     return {
2008 |         "mentions_found": len(mentions),
2009 |         "unique_mentions": len(set(mentions)),
2010 |         "results": formatted_results,
2011 |         "timestamp": datetime.utcnow().isoformat(),
2012 |         "note": "This tool is maintained for backward compatibility. Consider using flutter_docs or flutter_search directly."
2013 |     }
2014 | 
2015 | 
2016 | def clean_readme_markdown(readme_content: str) -> str:
2017 |     """Clean and format README markdown for AI consumption"""
2018 |     if not readme_content:
2019 |         return "No README available"
2020 |     
2021 |     # Remove HTML comments
2022 |     readme_content = re.sub(r'<!--.*?-->', '', readme_content, flags=re.DOTALL)
2023 |     
2024 |     # Remove excessive blank lines
2025 |     readme_content = re.sub(r'\n{3,}', '\n\n', readme_content)
2026 |     
2027 |     # Remove badges/shields (common in READMEs but not useful for AI)
2028 |     readme_content = re.sub(r'!\[.*?\]\(.*?shields\.io.*?\)', '', readme_content)
2029 |     readme_content = re.sub(r'!\[.*?\]\(.*?badge.*?\)', '', readme_content)
2030 |     
2031 |     # Clean up any remaining formatting issues
2032 |     readme_content = readme_content.strip()
2033 |     
2034 |     return readme_content
2035 | 
2036 | 
2037 | @mcp.tool()
2038 | async def get_pub_package_info(package_name: str, version: Optional[str] = None, tokens: int = 6000) -> Dict[str, Any]:
2039 |     """
2040 |     Get package information from pub.dev including README content.
2041 |     
2042 |     **DEPRECATED**: This tool is deprecated. Please use flutter_docs() instead
2043 |     with the "pub:" prefix (e.g., flutter_docs("pub:provider")).
2044 |     
2045 |     Args:
2046 |         package_name: Name of the pub.dev package (e.g., "provider", "bloc", "dio")
2047 |         version: Optional specific version to fetch (e.g., "6.0.5", "2.5.1")
2048 |         tokens: Maximum token limit for response (default: 6000, min: 500)
2049 |     
2050 |     Returns:
2051 |         Package information including version, description, metadata, and README
2052 |     """
2053 |     bind_contextvars(tool="get_pub_package_info", package=package_name, version=version)
2054 |     logger.warning("deprecated_tool_usage", tool="get_pub_package_info", replacement="flutter_docs")
2055 |     
2056 |     # Validate tokens parameter
2057 |     if tokens < 500:
2058 |         return {"error": "tokens parameter must be at least 500"}
2059 |     
2060 |     # Call new flutter_docs tool
2061 |     identifier = f"pub:{package_name}"
2062 |     if version:
2063 |         identifier += f":{version}"
2064 |     
2065 |     result = await flutter_docs(identifier, max_tokens=tokens)
2066 |     
2067 |     # Transform back to old format
2068 |     if result.get("error"):
2069 |         return {
2070 |             "error": result["error"]
2071 |         }
2072 |     else:
2073 |         metadata = result.get("metadata", {})
2074 |         return {
2075 |             "source": result.get("source", "live"),
2076 |             "name": result.get("package", package_name),
2077 |             "version": result.get("version", "latest"),
2078 |             "description": metadata.get("description", ""),
2079 |             "homepage": metadata.get("homepage", ""),
2080 |             "repository": metadata.get("repository", ""),
2081 |             "documentation": f"https://pub.dev/packages/{package_name}",
2082 |             "dependencies": [],  # Not included in new format
2083 |             "readme": result.get("content", ""),
2084 |             "pub_points": metadata.get("pub_points", 0),
2085 |             "likes": metadata.get("likes", 0),
2086 |             "fetched_at": datetime.utcnow().isoformat()
2087 |         }
2088 | 
2089 | 
2090 | async def _get_pub_package_info_impl(package_name: str, version: Optional[str] = None) -> Dict[str, Any]:
2091 |     """
2092 |     Internal implementation of get_pub_package_info functionality.
2093 |     """
2094 |     
2095 |     # Check cache first
2096 |     cache_key = get_cache_key("pub_package", package_name, version)
2097 |     
2098 |     # Check cache
2099 |     cached_data = cache_manager.get(cache_key)
2100 |     if cached_data:
2101 |         logger.info("cache_hit")
2102 |         return cached_data
2103 |     
2104 |     # Rate limit before fetching
2105 |     await rate_limiter.acquire()
2106 |     
2107 |     # Fetch from pub.dev API
2108 |     url = f"https://pub.dev/api/packages/{package_name}"
2109 |     logger.info("fetching_package", url=url)
2110 |     
2111 |     try:
2112 |         async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
2113 |             # Fetch package info
2114 |             response = await client.get(
2115 |                 url,
2116 |                 headers={
2117 |                     "User-Agent": "Flutter-MCP-Docs/1.0 (github.com/flutter-mcp/flutter-mcp)"
2118 |                 }
2119 |             )
2120 |             response.raise_for_status()
2121 |             
2122 |             data = response.json()
2123 |             
2124 |             # If specific version requested, find it in versions list
2125 |             if version:
2126 |                 version_data = None
2127 |                 for v in data.get("versions", []):
2128 |                     if v.get("version") == version:
2129 |                         version_data = v
2130 |                         break
2131 |                 
2132 |                 if not version_data:
2133 |                     return {
2134 |                         "error": f"Version '{version}' not found for package '{package_name}'",
2135 |                         "available_versions": [v.get("version") for v in data.get("versions", [])][:10]  # Show first 10
2136 |                     }
2137 |                 
2138 |                 pubspec = version_data.get("pubspec", {})
2139 |                 actual_version = version_data.get("version", version)
2140 |                 published_date = version_data.get("published", "")
2141 |             else:
2142 |                 # Use latest version
2143 |                 latest = data.get("latest", {})
2144 |                 pubspec = latest.get("pubspec", {})
2145 |                 actual_version = latest.get("version", "unknown")
2146 |                 published_date = latest.get("published", "")
2147 |             
2148 |             result = {
2149 |                 "source": "live",
2150 |                 "name": package_name,
2151 |                 "version": actual_version,
2152 |                 "description": pubspec.get("description", "No description available"),
2153 |                 "homepage": pubspec.get("homepage", ""),
2154 |                 "repository": pubspec.get("repository", ""),
2155 |                 "documentation": pubspec.get("documentation", f"https://pub.dev/packages/{package_name}"),
2156 |                 "dependencies": list(pubspec.get("dependencies", {}).keys()),
2157 |                 "dev_dependencies": list(pubspec.get("dev_dependencies", {}).keys()),
2158 |                 "environment": pubspec.get("environment", {}),
2159 |                 "platforms": data.get("platforms", []),
2160 |                 "updated": published_date,
2161 |                 "publisher": data.get("publisher", ""),
2162 |                 "likes": data.get("likeCount", 0),
2163 |                 "pub_points": data.get("pubPoints", 0),
2164 |                 "popularity": data.get("popularityScore", 0)
2165 |             }
2166 |             
2167 |             # Fetch README content from package page
2168 |             # For specific versions, pub.dev uses /versions/{version} path
2169 |             if version:
2170 |                 readme_url = f"https://pub.dev/packages/{package_name}/versions/{actual_version}"
2171 |             else:
2172 |                 readme_url = f"https://pub.dev/packages/{package_name}"
2173 |             logger.info("fetching_readme", url=readme_url)
2174 |             
2175 |             try:
2176 |                 # Rate limit before second request
2177 |                 await rate_limiter.acquire()
2178 |                 
2179 |                 readme_response = await client.get(
2180 |                     readme_url,
2181 |                     headers={
2182 |                         "User-Agent": "Flutter-MCP-Docs/1.0 (github.com/flutter-mcp/flutter-mcp)"
2183 |                     }
2184 |                 )
2185 |                 readme_response.raise_for_status()
2186 |                 
2187 |                 # Parse page HTML to extract README
2188 |                 soup = BeautifulSoup(readme_response.text, 'html.parser')
2189 |                 
2190 |                 # Find the README content - pub.dev uses a section with specific classes
2191 |                 readme_div = soup.find('section', class_='detail-tab-readme-content')
2192 |                 if not readme_div:
2193 |                     # Try finding any section with markdown-body class
2194 |                     readme_div = soup.find('section', class_='markdown-body')
2195 |                     if not readme_div:
2196 |                         # Try finding div with markdown-body
2197 |                         readme_div = soup.find('div', class_='markdown-body')
2198 |                 
2199 |                 if readme_div:
2200 |                     # Extract text content and preserve basic markdown structure
2201 |                     # Convert common HTML elements back to markdown
2202 |                     for br in readme_div.find_all('br'):
2203 |                         br.replace_with('\n')
2204 |                     
2205 |                     for p in readme_div.find_all('p'):
2206 |                         p.insert_after('\n\n')
2207 |                     
2208 |                     for h1 in readme_div.find_all('h1'):
2209 |                         h1.insert_before('# ')
2210 |                         h1.insert_after('\n\n')
2211 |                     
2212 |                     for h2 in readme_div.find_all('h2'):
2213 |                         h2.insert_before('## ')
2214 |                         h2.insert_after('\n\n')
2215 |                     
2216 |                     for h3 in readme_div.find_all('h3'):
2217 |                         h3.insert_before('### ')
2218 |                         h3.insert_after('\n\n')
2219 |                     
2220 |                     for code in readme_div.find_all('code'):
2221 |                         if code.parent.name != 'pre':
2222 |                             code.insert_before('`')
2223 |                             code.insert_after('`')
2224 |                     
2225 |                     for pre in readme_div.find_all('pre'):
2226 |                         code_block = pre.find('code')
2227 |                         if code_block:
2228 |                             lang_class = code_block.get('class', [])
2229 |                             lang = ''
2230 |                             for cls in lang_class if isinstance(lang_class, list) else [lang_class]:
2231 |                                 if cls and cls.startswith('language-'):
2232 |                                     lang = cls.replace('language-', '')
2233 |                                     break
2234 |                             pre.insert_before(f'\n```{lang}\n')
2235 |                             pre.insert_after('\n```\n')
2236 |                     
2237 |                     readme_text = readme_div.get_text()
2238 |                     result["readme"] = clean_readme_markdown(readme_text)
2239 |                 else:
2240 |                     result["readme"] = "README parsing failed - content structure not recognized"
2241 |                     
2242 |             except httpx.HTTPStatusError as e:
2243 |                 logger.warning("readme_fetch_failed", status_code=e.response.status_code)
2244 |                 result["readme"] = f"README not available (HTTP {e.response.status_code})"
2245 |             except Exception as e:
2246 |                 logger.warning("readme_fetch_error", error=str(e))
2247 |                 result["readme"] = f"Failed to fetch README: {str(e)}"
2248 |             
2249 |             # Cache for 12 hours
2250 |             cache_manager.set(cache_key, result, CACHE_DURATIONS["pub_package"])
2251 |             
2252 |             logger.info("package_fetched_success", has_readme="readme" in result)
2253 |             return result
2254 |             
2255 |     except httpx.HTTPStatusError as e:
2256 |         logger.error("http_error", status_code=e.response.status_code)
2257 |         return {
2258 |             "error": f"Package '{package_name}' not found on pub.dev",
2259 |             "status_code": e.response.status_code
2260 |         }
2261 |     except Exception as e:
2262 |         logger.error("fetch_error", error=str(e))
2263 |         return {
2264 |             "error": f"Failed to fetch package information: {str(e)}"
2265 |         }
2266 | 
2267 | 
2268 | @mcp.tool()
2269 | async def flutter_search(query: str, limit: int = 10, tokens: int = 5000) -> Dict[str, Any]:
2270 |     """
2271 |     Search across multiple Flutter/Dart documentation sources with unified results.
2272 |     
2273 |     Searches Flutter classes, Dart classes, pub packages, and concepts in parallel.
2274 |     Returns structured results with relevance scoring and documentation hints.
2275 |     
2276 |     Args:
2277 |         query: Search query (e.g., "state management", "Container", "http")
2278 |         limit: Maximum number of results to return (default: 10, max: 25)
2279 |         tokens: Maximum token limit for response (default: 5000, min: 500)
2280 |     
2281 |     Returns:
2282 |         Unified search results with type classification and relevance scores
2283 |     """
2284 |     bind_contextvars(tool="flutter_search", query=query, limit=limit)
2285 |     logger.info("unified_search_started")
2286 |     
2287 |     # Validate tokens parameter
2288 |     if tokens < 500:
2289 |         return {"error": "tokens parameter must be at least 500"}
2290 |     
2291 |     # Validate limit
2292 |     limit = min(max(limit, 1), 25)
2293 |     
2294 |     # Check cache for search results
2295 |     cache_key = get_cache_key("unified_search", f"{query}:{limit}")
2296 |     cached_data = cache_manager.get(cache_key)
2297 |     if cached_data:
2298 |         logger.info("unified_search_cache_hit")
2299 |         return cached_data
2300 |     
2301 |     # Prepare search tasks for parallel execution
2302 |     search_tasks = []
2303 |     results = []
2304 |     query_lower = query.lower()
2305 |     
2306 |     # Define search functions for parallel execution
2307 |     async def search_flutter_classes():
2308 |         """Search Flutter widget/class documentation"""
2309 |         flutter_results = []
2310 |         
2311 |         # Check if query is a direct Flutter class reference
2312 |         if url := resolve_flutter_url(query):
2313 |             # Extract class and library info from resolved URL
2314 |             library = "widgets"  # Default
2315 |             if "flutter/material" in url:
2316 |                 library = "material"
2317 |             elif "flutter/cupertino" in url:
2318 |                 library = "cupertino"
2319 |             elif "flutter/animation" in url:
2320 |                 library = "animation"
2321 |             elif "flutter/painting" in url:
2322 |                 library = "painting"
2323 |             elif "flutter/rendering" in url:
2324 |                 library = "rendering"
2325 |             elif "flutter/services" in url:
2326 |                 library = "services"
2327 |             elif "flutter/gestures" in url:
2328 |                 library = "gestures"
2329 |             elif "flutter/foundation" in url:
2330 |                 library = "foundation"
2331 |             
2332 |             class_match = re.search(r'/([^/]+)-class\.html$', url)
2333 |             if class_match:
2334 |                 class_name = class_match.group(1)
2335 |                 flutter_results.append({
2336 |                     "id": f"flutter:{library}:{class_name}",
2337 |                     "type": "flutter_class",
2338 |                     "relevance": 1.0,
2339 |                     "title": class_name,
2340 |                     "library": library,
2341 |                     "description": f"Flutter {library} class",
2342 |                     "doc_size": "large",
2343 |                     "url": url
2344 |                 })
2345 |         
2346 |         # Search common Flutter classes
2347 |         flutter_classes = [
2348 |             # State management
2349 |             ("StatefulWidget", "widgets", "Base class for widgets that have mutable state", ["state", "stateful", "widget"]),
2350 |             ("StatelessWidget", "widgets", "Base class for widgets that don't require mutable state", ["state", "stateless", "widget"]),
2351 |             ("State", "widgets", "Logic and internal state for a StatefulWidget", ["state", "lifecycle"]),
2352 |             ("InheritedWidget", "widgets", "Base class for widgets that propagate information down the tree", ["inherited", "propagate", "state"]),
2353 |             ("ValueListenableBuilder", "widgets", "Rebuilds when ValueListenable changes", ["value", "listenable", "builder", "state"]),
2354 |             
2355 |             # Layout widgets
2356 |             ("Container", "widgets", "A convenience widget that combines common painting, positioning, and sizing", ["container", "box", "layout"]),
2357 |             ("Row", "widgets", "Displays children in a horizontal array", ["row", "horizontal", "layout"]),
2358 |             ("Column", "widgets", "Displays children in a vertical array", ["column", "vertical", "layout"]),
2359 |             ("Stack", "widgets", "Positions children relative to the box edges", ["stack", "overlay", "position"]),
2360 |             ("Scaffold", "material", "Basic material design visual layout structure", ["scaffold", "material", "layout", "structure"]),
2361 |             
2362 |             # Navigation
2363 |             ("Navigator", "widgets", "Manages a stack of Route objects", ["navigator", "navigation", "route"]),
2364 |             ("MaterialPageRoute", "material", "A modal route that replaces the entire screen", ["route", "navigation", "page"]),
2365 |             
2366 |             # Input widgets
2367 |             ("TextField", "material", "A material design text field", ["text", "input", "field", "form"]),
2368 |             ("GestureDetector", "widgets", "Detects gestures on widgets", ["gesture", "touch", "tap", "click"]),
2369 |             
2370 |             # Lists
2371 |             ("ListView", "widgets", "Scrollable list of widgets", ["list", "scroll", "view"]),
2372 |             ("GridView", "widgets", "Scrollable 2D array of widgets", ["grid", "scroll", "view"]),
2373 |             
2374 |             # Visual
2375 |             ("AppBar", "material", "A material design app bar", ["app", "bar", "header", "material"]),
2376 |             ("Card", "material", "A material design card", ["card", "material"]),
2377 |             
2378 |             # Async
2379 |             ("FutureBuilder", "widgets", "Builds based on interaction with a Future", ["future", "async", "builder"]),
2380 |             ("StreamBuilder", "widgets", "Builds based on interaction with a Stream", ["stream", "async", "builder"]),
2381 |         ]
2382 |         
2383 |         for class_name, library, description, keywords in flutter_classes:
2384 |             # Calculate relevance based on query match
2385 |             relevance = 0.0
2386 |             
2387 |             # Direct match
2388 |             if query_lower == class_name.lower():
2389 |                 relevance = 1.0
2390 |             elif query_lower in class_name.lower():
2391 |                 relevance = 0.8
2392 |             elif class_name.lower() in query_lower:
2393 |                 relevance = 0.7
2394 |             
2395 |             # Keyword match
2396 |             if relevance < 0.3:
2397 |                 for keyword in keywords:
2398 |                     if keyword in query_lower or query_lower in keyword:
2399 |                         relevance = max(relevance, 0.5)
2400 |                         break
2401 |             
2402 |             # Description match
2403 |             if relevance < 0.3 and query_lower in description.lower():
2404 |                 relevance = 0.4
2405 |             
2406 |             if relevance > 0.3:
2407 |                 flutter_results.append({
2408 |                     "id": f"flutter:{library}:{class_name}",
2409 |                     "type": "flutter_class",
2410 |                     "relevance": relevance,
2411 |                     "title": class_name,
2412 |                     "library": library,
2413 |                     "description": description,
2414 |                     "doc_size": "large"
2415 |                 })
2416 |         
2417 |         return flutter_results
2418 |     
2419 |     async def search_dart_classes():
2420 |         """Search Dart core library documentation"""
2421 |         dart_results = []
2422 |         
2423 |         dart_classes = [
2424 |             ("List", "dart:core", "An indexable collection of objects with a length", ["list", "array", "collection"]),
2425 |             ("Map", "dart:core", "A collection of key/value pairs", ["map", "dictionary", "hash", "key", "value"]),
2426 |             ("Set", "dart:core", "A collection of objects with no duplicate elements", ["set", "unique", "collection"]),
2427 |             ("String", "dart:core", "A sequence of UTF-16 code units", ["string", "text"]),
2428 |             ("Future", "dart:async", "Represents a computation that completes with a value or error", ["future", "async", "promise"]),
2429 |             ("Stream", "dart:async", "A source of asynchronous data events", ["stream", "async", "event"]),
2430 |             ("Duration", "dart:core", "A span of time", ["duration", "time", "span"]),
2431 |             ("DateTime", "dart:core", "An instant in time", ["date", "time", "datetime"]),
2432 |             ("RegExp", "dart:core", "A regular expression pattern", ["regex", "regexp", "pattern"]),
2433 |             ("Iterable", "dart:core", "A collection of values that can be accessed sequentially", ["iterable", "collection", "sequence"]),
2434 |         ]
2435 |         
2436 |         for class_name, library, description, keywords in dart_classes:
2437 |             relevance = 0.0
2438 |             
2439 |             # Direct match
2440 |             if query_lower == class_name.lower():
2441 |                 relevance = 1.0
2442 |             elif query_lower in class_name.lower():
2443 |                 relevance = 0.8
2444 |             elif class_name.lower() in query_lower:
2445 |                 relevance = 0.7
2446 |             
2447 |             # Keyword match
2448 |             if relevance < 0.3:
2449 |                 for keyword in keywords:
2450 |                     if keyword in query_lower or query_lower in keyword:
2451 |                         relevance = max(relevance, 0.5)
2452 |                         break
2453 |             
2454 |             # Description match
2455 |             if relevance < 0.3 and query_lower in description.lower():
2456 |                 relevance = 0.4
2457 |             
2458 |             if relevance > 0.3:
2459 |                 dart_results.append({
2460 |                     "id": f"dart:{library.replace('dart:', '')}:{class_name}",
2461 |                     "type": "dart_class",
2462 |                     "relevance": relevance,
2463 |                     "title": class_name,
2464 |                     "library": library,
2465 |                     "description": description,
2466 |                     "doc_size": "medium"
2467 |                 })
2468 |         
2469 |         return dart_results
2470 |     
2471 |     async def search_pub_packages():
2472 |         """Search pub.dev packages"""
2473 |         package_results = []
2474 |         
2475 |         # Define popular packages with categories
2476 |         packages = [
2477 |             # State Management
2478 |             ("provider", "State management library that makes it easy to connect business logic to widgets", ["state", "management", "provider"], "state_management"),
2479 |             ("riverpod", "A reactive caching and data-binding framework", ["state", "management", "riverpod", "reactive"], "state_management"),
2480 |             ("bloc", "State management library implementing the BLoC design pattern", ["state", "management", "bloc", "pattern"], "state_management"),
2481 |             ("get", "Open source state management, navigation and utilities", ["state", "management", "get", "navigation"], "state_management"),
2482 |             
2483 |             # Networking
2484 |             ("dio", "Powerful HTTP client for Dart with interceptors and FormData", ["http", "network", "dio", "api"], "networking"),
2485 |             ("http", "A composable, multi-platform, Future-based API for HTTP requests", ["http", "network", "request"], "networking"),
2486 |             ("retrofit", "Type-safe HTTP client generator", ["http", "network", "retrofit", "generator"], "networking"),
2487 |             
2488 |             # Storage
2489 |             ("shared_preferences", "Flutter plugin for reading and writing simple key-value pairs", ["storage", "preferences", "settings"], "storage"),
2490 |             ("sqflite", "SQLite plugin for Flutter", ["database", "sqlite", "sql", "storage"], "storage"),
2491 |             ("hive", "Lightweight and blazing fast key-value database", ["database", "hive", "nosql", "storage"], "storage"),
2492 |             
2493 |             # Firebase
2494 |             ("firebase_core", "Flutter plugin to use Firebase Core API", ["firebase", "core", "backend"], "firebase"),
2495 |             ("firebase_auth", "Flutter plugin for Firebase Auth", ["firebase", "auth", "authentication"], "firebase"),
2496 |             ("cloud_firestore", "Flutter plugin for Cloud Firestore", ["firebase", "firestore", "database"], "firebase"),
2497 |             
2498 |             # UI/UX
2499 |             ("flutter_svg", "SVG rendering and widget library for Flutter", ["svg", "image", "vector", "ui"], "ui"),
2500 |             ("cached_network_image", "Flutter library to load and cache network images", ["image", "cache", "network", "ui"], "ui"),
2501 |             ("animations", "Beautiful pre-built animations for Flutter", ["animation", "transition", "ui"], "ui"),
2502 |             
2503 |             # Navigation
2504 |             ("go_router", "A declarative routing package for Flutter", ["navigation", "router", "routing"], "navigation"),
2505 |             ("auto_route", "Code generation for type-safe route navigation", ["navigation", "router", "generation"], "navigation"),
2506 |             
2507 |             # Platform
2508 |             ("url_launcher", "Flutter plugin for launching URLs", ["url", "launcher", "platform"], "platform"),
2509 |             ("path_provider", "Flutter plugin for getting commonly used locations on filesystem", ["path", "file", "platform"], "platform"),
2510 |             ("image_picker", "Flutter plugin for selecting images", ["image", "picker", "camera", "gallery"], "platform"),
2511 |         ]
2512 |         
2513 |         for package_name, description, keywords, category in packages:
2514 |             relevance = 0.0
2515 |             
2516 |             # Direct match
2517 |             if query_lower == package_name:
2518 |                 relevance = 1.0
2519 |             elif query_lower in package_name:
2520 |                 relevance = 0.8
2521 |             elif package_name in query_lower:
2522 |                 relevance = 0.7
2523 |             
2524 |             # Keyword match
2525 |             if relevance < 0.3:
2526 |                 for keyword in keywords:
2527 |                     if keyword in query_lower or query_lower in keyword:
2528 |                         relevance = max(relevance, 0.6)
2529 |                         break
2530 |             
2531 |             # Category match
2532 |             if relevance < 0.3 and category in query_lower:
2533 |                 relevance = 0.5
2534 |             
2535 |             # Description match
2536 |             if relevance < 0.3 and query_lower in description.lower():
2537 |                 relevance = 0.4
2538 |             
2539 |             if relevance > 0.3:
2540 |                 package_results.append({
2541 |                     "id": f"pub:{package_name}",
2542 |                     "type": "pub_package",
2543 |                     "relevance": relevance,
2544 |                     "title": package_name,
2545 |                     "category": category,
2546 |                     "description": description,
2547 |                     "doc_size": "variable",
2548 |                     "url": f"https://pub.dev/packages/{package_name}"
2549 |                 })
2550 |         
2551 |         return package_results
2552 |     
2553 |     async def search_concepts():
2554 |         """Search programming concepts and patterns"""
2555 |         concept_results = []
2556 |         
2557 |         concepts = {
2558 |             "state_management": {
2559 |                 "title": "State Management in Flutter",
2560 |                 "description": "Techniques for managing application state",
2561 |                 "keywords": ["state", "management", "provider", "bloc", "riverpod"],
2562 |                 "related": ["setState", "InheritedWidget", "provider", "bloc", "riverpod", "get"]
2563 |             },
2564 |             "navigation": {
2565 |                 "title": "Navigation & Routing",
2566 |                 "description": "Moving between screens and managing navigation stack",
2567 |                 "keywords": ["navigation", "routing", "navigator", "route", "screen"],
2568 |                 "related": ["Navigator", "MaterialPageRoute", "go_router", "deep linking"]
2569 |             },
2570 |             "async_programming": {
2571 |                 "title": "Asynchronous Programming",
2572 |                 "description": "Working with Futures, Streams, and async operations",
2573 |                 "keywords": ["async", "future", "stream", "await", "asynchronous"],
2574 |                 "related": ["Future", "Stream", "FutureBuilder", "StreamBuilder", "async/await"]
2575 |             },
2576 |             "http_networking": {
2577 |                 "title": "HTTP & Networking",
2578 |                 "description": "Making HTTP requests and handling network operations",
2579 |                 "keywords": ["http", "network", "api", "rest", "request"],
2580 |                 "related": ["http", "dio", "retrofit", "REST API", "JSON"]
2581 |             },
2582 |             "database_storage": {
2583 |                 "title": "Database & Storage",
2584 |                 "description": "Persisting data locally using various storage solutions",
2585 |                 "keywords": ["database", "storage", "sqlite", "persistence", "cache"],
2586 |                 "related": ["sqflite", "hive", "shared_preferences", "drift", "objectbox"]
2587 |             },
2588 |             "animation": {
2589 |                 "title": "Animations in Flutter",
2590 |                 "description": "Creating smooth animations and transitions",
2591 |                 "keywords": ["animation", "transition", "animate", "motion"],
2592 |                 "related": ["AnimationController", "AnimatedBuilder", "Hero", "Curves"]
2593 |             },
2594 |             "testing": {
2595 |                 "title": "Testing Flutter Apps",
2596 |                 "description": "Unit, widget, and integration testing strategies",
2597 |                 "keywords": ["test", "testing", "unit", "widget", "integration"],
2598 |                 "related": ["flutter_test", "mockito", "integration_test", "golden tests"]
2599 |             },
2600 |             "architecture": {
2601 |                 "title": "App Architecture Patterns",
2602 |                 "description": "Organizing code with architectural patterns",
2603 |                 "keywords": ["architecture", "pattern", "mvvm", "mvc", "clean"],
2604 |                 "related": ["BLoC Pattern", "MVVM", "Clean Architecture", "Repository Pattern"]
2605 |             },
2606 |             "performance": {
2607 |                 "title": "Performance Optimization",
2608 |                 "description": "Improving app performance and reducing jank",
2609 |                 "keywords": ["performance", "optimization", "speed", "jank", "profile"],
2610 |                 "related": ["Performance Profiling", "Widget Inspector", "const constructors"]
2611 |             },
2612 |             "platform_integration": {
2613 |                 "title": "Platform Integration",
2614 |                 "description": "Integrating with native platform features",
2615 |                 "keywords": ["platform", "native", "channel", "integration", "plugin"],
2616 |                 "related": ["Platform Channels", "Method Channel", "Plugin Development"]
2617 |             }
2618 |         }
2619 |         
2620 |         for concept_id, concept_data in concepts.items():
2621 |             relevance = 0.0
2622 |             
2623 |             # Check keywords
2624 |             for keyword in concept_data["keywords"]:
2625 |                 if keyword in query_lower or query_lower in keyword:
2626 |                     relevance = max(relevance, 0.7)
2627 |                     
2628 |             # Check title
2629 |             if query_lower in concept_data["title"].lower():
2630 |                 relevance = max(relevance, 0.8)
2631 |                 
2632 |             # Check description
2633 |             if relevance < 0.3 and query_lower in concept_data["description"].lower():
2634 |                 relevance = 0.5
2635 |             
2636 |             if relevance > 0.3:
2637 |                 concept_results.append({
2638 |                     "id": f"concept:{concept_id}",
2639 |                     "type": "concept",
2640 |                     "relevance": relevance,
2641 |                     "title": concept_data["title"],
2642 |                     "description": concept_data["description"],
2643 |                     "related_items": concept_data["related"],
2644 |                     "doc_size": "summary"
2645 |                 })
2646 |         
2647 |         return concept_results
2648 |     
2649 |     # Execute all searches in parallel
2650 |     flutter_task = asyncio.create_task(search_flutter_classes())
2651 |     dart_task = asyncio.create_task(search_dart_classes())
2652 |     pub_task = asyncio.create_task(search_pub_packages())
2653 |     concept_task = asyncio.create_task(search_concepts())
2654 |     
2655 |     # Wait for all searches to complete
2656 |     flutter_results, dart_results, pub_results, concept_results = await asyncio.gather(
2657 |         flutter_task, dart_task, pub_task, concept_task
2658 |     )
2659 |     
2660 |     # Combine all results
2661 |     all_results = flutter_results + dart_results + pub_results + concept_results
2662 |     
2663 |     # Sort by relevance and limit
2664 |     all_results.sort(key=lambda x: x["relevance"], reverse=True)
2665 |     results = all_results[:limit]
2666 |     
2667 |     # Add search metadata
2668 |     response = {
2669 |         "query": query,
2670 |         "total_results": len(all_results),
2671 |         "returned_results": len(results),
2672 |         "results": results,
2673 |         "result_types": {
2674 |             "flutter_classes": sum(1 for r in results if r["type"] == "flutter_class"),
2675 |             "dart_classes": sum(1 for r in results if r["type"] == "dart_class"),
2676 |             "pub_packages": sum(1 for r in results if r["type"] == "pub_package"),
2677 |             "concepts": sum(1 for r in results if r["type"] == "concept")
2678 |         },
2679 |         "timestamp": datetime.utcnow().isoformat()
2680 |     }
2681 |     
2682 |     # Add search suggestions if results are limited
2683 |     if len(results) < 5:
2684 |         suggestions = []
2685 |         if not any(r["type"] == "flutter_class" for r in results):
2686 |             suggestions.append("Try searching for specific widget names like 'Container' or 'Scaffold'")
2687 |         if not any(r["type"] == "pub_package" for r in results):
2688 |             suggestions.append("Search for package names like 'provider' or 'dio'")
2689 |         if not any(r["type"] == "concept" for r in results):
2690 |             suggestions.append("Try broader concepts like 'state management' or 'navigation'")
2691 |         
2692 |         response["suggestions"] = suggestions
2693 |     
2694 |     # Cache the results for 1 hour
2695 |     cache_manager.set(cache_key, response, 3600)
2696 |     
2697 |     logger.info("unified_search_completed", 
2698 |                 total_results=len(all_results),
2699 |                 returned_results=len(results))
2700 |     
2701 |     return response
2702 | 
2703 | 
2704 | @mcp.tool()
2705 | async def flutter_status() -> Dict[str, Any]:
2706 |     """
2707 |     Check the health status of all Flutter documentation services.
2708 |     
2709 |     Returns:
2710 |         Health status including individual service checks and cache statistics
2711 |     """
2712 |     checks = {}
2713 |     overall_status = "ok"
2714 |     timestamp = datetime.utcnow().isoformat()
2715 |     
2716 |     # Check Flutter docs scraper
2717 |     flutter_start = time.time()
2718 |     try:
2719 |         # Test with Container widget - a stable, core widget unlikely to be removed
2720 |         result = await get_flutter_docs("Container", "widgets")
2721 |         flutter_duration = int((time.time() - flutter_start) * 1000)
2722 |         
2723 |         if "error" in result:
2724 |             checks["flutter_docs"] = {
2725 |                 "status": "failed",
2726 |                 "target": "Container widget",
2727 |                 "duration_ms": flutter_duration,
2728 |                 "error": result["error"]
2729 |             }
2730 |             overall_status = "degraded"
2731 |         else:
2732 |             checks["flutter_docs"] = {
2733 |                 "status": "ok",
2734 |                 "target": "Container widget",
2735 |                 "duration_ms": flutter_duration,
2736 |                 "cached": result.get("source") == "cache"
2737 |             }
2738 |     except Exception as e:
2739 |         flutter_duration = int((time.time() - flutter_start) * 1000)
2740 |         checks["flutter_docs"] = {
2741 |             "status": "failed",
2742 |             "target": "Container widget",
2743 |             "duration_ms": flutter_duration,
2744 |             "error": str(e)
2745 |         }
2746 |         overall_status = "failed"
2747 |     
2748 |     # Check pub.dev scraper
2749 |     pub_start = time.time()
2750 |     try:
2751 |         # Test with provider package - extremely popular, unlikely to be removed
2752 |         result = await get_pub_package_info("provider")
2753 |         pub_duration = int((time.time() - pub_start) * 1000)
2754 |         
2755 |         if result is None:
2756 |             checks["pub_dev"] = {
2757 |                 "status": "timeout",
2758 |                 "target": "provider package",
2759 |                 "duration_ms": pub_duration,
2760 |                 "error": "Health check timed out after 10 seconds"
2761 |             }
2762 |             overall_status = "degraded" if overall_status == "ok" else overall_status
2763 |         elif result.get("error"):
2764 |             checks["pub_dev"] = {
2765 |                 "status": "failed",
2766 |                 "target": "provider package",
2767 |                 "duration_ms": pub_duration,
2768 |                 "error": result.get("message", "Unknown error"),
2769 |                 "error_type": result.get("error_type", "unknown")
2770 |             }
2771 |             overall_status = "degraded" if overall_status == "ok" else overall_status
2772 |         else:
2773 |             # Additional validation - check if we got expected fields
2774 |             has_version = "version" in result and result["version"] != "unknown"
2775 |             has_readme = "readme" in result and len(result.get("readme", "")) > 100
2776 |             
2777 |             if not has_version:
2778 |                 checks["pub_dev"] = {
2779 |                     "status": "degraded",
2780 |                     "target": "provider package",
2781 |                     "duration_ms": pub_duration,
2782 |                     "error": "Could not parse version information",
2783 |                     "cached": result.get("source") == "cache"
2784 |                 }
2785 |                 overall_status = "degraded" if overall_status == "ok" else overall_status
2786 |             elif not has_readme:
2787 |                 checks["pub_dev"] = {
2788 |                     "status": "degraded",
2789 |                     "target": "provider package",
2790 |                     "duration_ms": pub_duration,
2791 |                     "error": "Could not parse README content",
2792 |                     "cached": result.get("source") == "cache"
2793 |                 }
2794 |                 overall_status = "degraded" if overall_status == "ok" else overall_status
2795 |             else:
2796 |                 checks["pub_dev"] = {
2797 |                     "status": "ok",
2798 |                     "target": "provider package",
2799 |                     "duration_ms": pub_duration,
2800 |                     "version": result["version"],
2801 |                     "cached": result.get("source") == "cache"
2802 |                 }
2803 |     except Exception as e:
2804 |         pub_duration = int((time.time() - pub_start) * 1000)
2805 |         checks["pub_dev"] = {
2806 |             "status": "failed",
2807 |             "target": "provider package",
2808 |             "duration_ms": pub_duration,
2809 |             "error": str(e)
2810 |         }
2811 |         overall_status = "failed" if overall_status == "failed" else "degraded"
2812 |     
2813 |     # Check cache status
2814 |     try:
2815 |         cache_stats = cache_manager.get_stats()
2816 |         checks["cache"] = {
2817 |             "status": "ok",
2818 |             "message": "SQLite cache operational",
2819 |             "stats": cache_stats
2820 |         }
2821 |     except Exception as e:
2822 |         checks["cache"] = {
2823 |             "status": "degraded",
2824 |             "message": "Cache error",
2825 |             "error": str(e)
2826 |         }
2827 |         overall_status = "degraded"
2828 |     
2829 |     return {
2830 |         "status": overall_status,
2831 |         "timestamp": timestamp,
2832 |         "checks": checks,
2833 |         "message": get_health_message(overall_status)
2834 |     }
2835 | 
2836 | 
2837 | @mcp.tool()
2838 | async def health_check() -> Dict[str, Any]:
2839 |     """
2840 |     Check the health status of all scrapers and services.
2841 |     
2842 |     **DEPRECATED**: This tool is deprecated. Please use flutter_status() instead.
2843 |     
2844 |     Returns:
2845 |         Health status including individual scraper checks and overall status
2846 |     """
2847 |     logger.warning("deprecated_tool_usage", tool="health_check", replacement="flutter_status")
2848 |     
2849 |     # Simply call the new flutter_status function
2850 |     return await flutter_status()
2851 | 
2852 | 
2853 | def get_health_message(status: str) -> str:
2854 |     """Get a human-readable message for the health status"""
2855 |     messages = {
2856 |         "ok": "All systems operational",
2857 |         "degraded": "Service degraded - some features may be slow or unavailable",
2858 |         "failed": "Service failed - critical components are not working"
2859 |     }
2860 |     return messages.get(status, "Unknown status")
2861 | 
2862 | 
2863 | def main():
2864 |     """Main entry point for the Flutter MCP server"""
2865 |     import os
2866 |     
2867 |     # When running from CLI, the header is already printed
2868 |     # Only log when not running from CLI (e.g., direct execution)
2869 |     if not hasattr(sys, '_flutter_mcp_cli'):
2870 |         logger.info("flutter_mcp_starting", version="0.1.0")
2871 |     
2872 |     # Initialize cache and show stats
2873 |     try:
2874 |         cache_stats = cache_manager.get_stats()
2875 |         logger.info("cache_ready", stats=cache_stats)
2876 |     except Exception as e:
2877 |         logger.warning("cache_initialization_warning", error=str(e))
2878 |     
2879 |     # Get transport configuration from environment
2880 |     transport = os.environ.get('MCP_TRANSPORT', 'stdio')
2881 |     host = os.environ.get('MCP_HOST', '127.0.0.1')
2882 |     port = int(os.environ.get('MCP_PORT', '8000'))
2883 |     
2884 |     # Run the MCP server with appropriate transport
2885 |     if transport == 'stdio':
2886 |         mcp.run()
2887 |     elif transport == 'sse':
2888 |         logger.info("starting_sse_transport", host=host, port=port)
2889 |         mcp.run(transport='sse', host=host, port=port)
2890 |     elif transport == 'http':
2891 |         logger.info("starting_http_transport", host=host, port=port)
2892 |         # FastMCP handles HTTP transport internally
2893 |         mcp.run(transport='http', host=host, port=port, path='/mcp')
2894 | 
2895 | 
2896 | if __name__ == "__main__":
2897 |     main()
```
Page 3/3FirstPrevNextLast