fast-agent-mcp 0.2.56__py3-none-any.whl → 0.2.58__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fast-agent-mcp might be problematic. Click here for more details.

@@ -19,6 +19,61 @@ from mcp_agent.mcp.mcp_aggregator import MCPAggregator
19
19
  HUMAN_INPUT_TOOL_NAME = "__human_input__"
20
20
  CODE_STYLE = "native"
21
21
 
22
+ HTML_ESCAPE_CHARS = {"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"}
23
+
24
+
25
+ def _prepare_markdown_content(content: str, escape_xml: bool = True) -> str:
26
+ """Prepare content for markdown rendering by escaping HTML/XML tags
27
+ while preserving code blocks and inline code.
28
+
29
+ This ensures XML/HTML tags are displayed as visible text rather than
30
+ being interpreted as markup by the markdown renderer.
31
+
32
+ Note: This method does not handle overlapping code blocks (e.g., if inline
33
+ code appears within a fenced code block range). In practice, this is not
34
+ an issue since markdown syntax doesn't support such overlapping.
35
+ """
36
+ if not escape_xml or not isinstance(content, str):
37
+ return content
38
+
39
+ protected_ranges = []
40
+ import re
41
+
42
+ # Protect fenced code blocks (don't escape anything inside these)
43
+ code_block_pattern = r"```[\s\S]*?```"
44
+ for match in re.finditer(code_block_pattern, content):
45
+ protected_ranges.append((match.start(), match.end()))
46
+
47
+ # Protect inline code (don't escape anything inside these)
48
+ inline_code_pattern = r"(?<!`)`(?!``)[^`\n]+`(?!`)"
49
+ for match in re.finditer(inline_code_pattern, content):
50
+ protected_ranges.append((match.start(), match.end()))
51
+
52
+ protected_ranges.sort(key=lambda x: x[0])
53
+
54
+ # Build the escaped content
55
+ result = []
56
+ last_end = 0
57
+
58
+ for start, end in protected_ranges:
59
+ # Escape everything outside protected ranges
60
+ unprotected_text = content[last_end:start]
61
+ for char, replacement in HTML_ESCAPE_CHARS.items():
62
+ unprotected_text = unprotected_text.replace(char, replacement)
63
+ result.append(unprotected_text)
64
+
65
+ # Keep protected ranges (code blocks) as-is
66
+ result.append(content[start:end])
67
+ last_end = end
68
+
69
+ # Escape any remaining content after the last protected range
70
+ remainder_text = content[last_end:]
71
+ for char, replacement in HTML_ESCAPE_CHARS.items():
72
+ remainder_text = remainder_text.replace(char, replacement)
73
+ result.append(remainder_text)
74
+
75
+ return "".join(result)
76
+
22
77
 
23
78
  class ConsoleDisplay:
24
79
  """
@@ -35,6 +90,49 @@ class ConsoleDisplay:
35
90
  """
36
91
  self.config = config
37
92
  self._markup = config.logger.enable_markup if config else True
93
+ self._escape_xml = True
94
+
95
+ def _render_content_smartly(self, content: str, check_markdown_markers: bool = False) -> None:
96
+ """
97
+ Helper method to intelligently render content based on its type.
98
+
99
+ - Pure XML: Use syntax highlighting for readability
100
+ - Markdown (with markers): Use markdown rendering with proper escaping
101
+ - Plain text: Display as-is (when check_markdown_markers=True and no markers found)
102
+
103
+ Args:
104
+ content: The text content to render
105
+ check_markdown_markers: If True, only use markdown rendering when markers are present
106
+ """
107
+ import re
108
+
109
+ from rich.markdown import Markdown
110
+
111
+ # Check if content appears to be primarily XML
112
+ xml_pattern = r"^<[a-zA-Z_][a-zA-Z0-9_-]*[^>]*>"
113
+ is_xml_content = bool(re.match(xml_pattern, content.strip())) and content.count("<") > 5
114
+
115
+ if is_xml_content:
116
+ # Display XML content with syntax highlighting for better readability
117
+ from rich.syntax import Syntax
118
+
119
+ syntax = Syntax(content, "xml", theme=CODE_STYLE, line_numbers=False)
120
+ console.console.print(syntax, markup=self._markup)
121
+ elif check_markdown_markers:
122
+ # Check for markdown markers before deciding to use markdown rendering
123
+ if any(marker in content for marker in ["##", "**", "*", "`", "---", "###"]):
124
+ # Has markdown markers - render as markdown with escaping
125
+ prepared_content = _prepare_markdown_content(content, self._escape_xml)
126
+ md = Markdown(prepared_content, code_theme=CODE_STYLE)
127
+ console.console.print(md, markup=self._markup)
128
+ else:
129
+ # Plain text - display as-is
130
+ console.console.print(content, markup=self._markup)
131
+ else:
132
+ # Always treat as markdown with proper escaping
133
+ prepared_content = _prepare_markdown_content(content, self._escape_xml)
134
+ md = Markdown(prepared_content, code_theme=CODE_STYLE)
135
+ console.console.print(md, markup=self._markup)
38
136
 
39
137
  def show_tool_result(self, result: CallToolResult, name: str | None = None) -> None:
40
138
  """Display a tool result in the new visual style."""
@@ -341,7 +439,6 @@ class ConsoleDisplay:
341
439
  model: str | None = None,
342
440
  ) -> None:
343
441
  """Display an assistant message in a formatted panel."""
344
- from rich.markdown import Markdown
345
442
 
346
443
  if not self.config or not self.config.logger.show_chat:
347
444
  return
@@ -385,9 +482,8 @@ class ConsoleDisplay:
385
482
  json = JSON(message_text)
386
483
  console.console.print(json, markup=self._markup)
387
484
  except (JSONDecodeError, TypeError, ValueError):
388
- # Not JSON, treat as markdown
389
- md = Markdown(content, code_theme=CODE_STYLE)
390
- console.console.print(md, markup=self._markup)
485
+ # Use the smart rendering helper to handle XML vs Markdown
486
+ self._render_content_smartly(content)
391
487
  else:
392
488
  # Handle Rich Text objects directly
393
489
  console.console.print(message_text, markup=self._markup)
@@ -475,7 +571,6 @@ class ConsoleDisplay:
475
571
  self, message, model: str | None = None, chat_turn: int = 0, name: str | None = None
476
572
  ) -> None:
477
573
  """Display a user message in the new visual style."""
478
- from rich.markdown import Markdown
479
574
 
480
575
  if not self.config or not self.config.logger.show_chat:
481
576
  return
@@ -495,9 +590,8 @@ class ConsoleDisplay:
495
590
 
496
591
  # Display content as markdown if it looks like markdown, otherwise as text
497
592
  if isinstance(message, str):
498
- content = message
499
- md = Markdown(content, code_theme=CODE_STYLE)
500
- console.console.print(md, markup=self._markup)
593
+ # Use the smart rendering helper to handle XML vs Markdown
594
+ self._render_content_smartly(message)
501
595
  else:
502
596
  # Handle Text objects directly
503
597
  console.console.print(message, markup=self._markup)
@@ -587,7 +681,7 @@ class ConsoleDisplay:
587
681
  Args:
588
682
  parallel_agent: The parallel agent containing fan_out_agents with results
589
683
  """
590
- from rich.markdown import Markdown
684
+
591
685
  from rich.text import Text
592
686
 
593
687
  if self.config and not self.config.logger.show_chat:
@@ -674,13 +768,9 @@ class ConsoleDisplay:
674
768
  console.console.print(left + " " * padding + right, markup=self._markup)
675
769
  console.console.print()
676
770
 
677
- # Display content as markdown if it looks like markdown, otherwise as text
771
+ # Display content based on its type (check for markdown markers in parallel results)
678
772
  content = result["content"]
679
- if any(marker in content for marker in ["##", "**", "*", "`", "---", "###"]):
680
- md = Markdown(content, code_theme=CODE_STYLE)
681
- console.console.print(md, markup=self._markup)
682
- else:
683
- console.console.print(content, markup=self._markup)
773
+ self._render_content_smartly(content, check_markdown_markers=True)
684
774
 
685
775
  # Summary
686
776
  console.console.print()