sa-assistant 0.1.1__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.
cli/components.py ADDED
@@ -0,0 +1,346 @@
1
+ """SA Assistant CLI Components - UI 컴포넌트
2
+
3
+ Rich 기반 UI 컴포넌트들을 제공합니다.
4
+ """
5
+
6
+ from typing import Optional, Dict, Any
7
+ from rich.console import Console, Group
8
+ from rich.panel import Panel
9
+ from rich.table import Table
10
+ from rich.text import Text
11
+ from rich.markdown import Markdown
12
+ from rich.syntax import Syntax
13
+ from rich.live import Live
14
+ from rich.spinner import Spinner
15
+ from rich.progress import Progress, SpinnerColumn, TextColumn
16
+
17
+ from .console import get_console, SA_THEME
18
+
19
+
20
+ # =============================================================================
21
+ # 배너 및 헬프
22
+ # =============================================================================
23
+
24
+ BANNER = """
25
+ [aws.orange]╔══════════════════════════════════════════════════════════════════════╗[/]
26
+ [aws.orange]║[/] [aws.orange]║[/]
27
+ [aws.orange]║[/] [bold white]AWS SA Assistant[/] [aws.orange]║[/]
28
+ [aws.orange]║[/] [dim]Solutions Architect Professional Agent[/] [aws.orange]║[/]
29
+ [aws.orange]║[/] [aws.orange]║[/]
30
+ [aws.orange]║[/] [cyan]Powered by Claude with Extended Thinking[/] [aws.orange]║[/]
31
+ [aws.orange]║[/] [cyan]+ Guru Sub-agents (Bezos, Vogels, Naval, Feynman)[/] [aws.orange]║[/]
32
+ [aws.orange]║[/] [cyan]+ Specialist Agents (Explorer, Researcher, Reviewer)[/] [aws.orange]║[/]
33
+ [aws.orange]║[/] [aws.orange]║[/]
34
+ [aws.orange]╚══════════════════════════════════════════════════════════════════════╝[/]
35
+ """
36
+
37
+
38
+ def print_banner() -> None:
39
+ """SA Assistant 배너를 출력합니다."""
40
+ console = get_console()
41
+ console.print(BANNER)
42
+
43
+
44
+ def print_help() -> None:
45
+ """도움말을 출력합니다."""
46
+ console = get_console()
47
+
48
+ help_table = Table(show_header=False, box=None, padding=(0, 2))
49
+ help_table.add_column("Command", style="cyan")
50
+ help_table.add_column("Description", style="dim")
51
+
52
+ help_table.add_row("Enter", "메시지 전송")
53
+ help_table.add_row("Esc+Enter / Alt+Enter", "줄바꿈")
54
+ help_table.add_row("↑ / ↓", "히스토리 탐색")
55
+ help_table.add_row("Ctrl+C", "입력 취소")
56
+ help_table.add_row("exit / quit", "종료")
57
+
58
+ console.print()
59
+ console.print(
60
+ Panel(
61
+ help_table,
62
+ title="[bold]키보드 단축키[/]",
63
+ border_style="dim",
64
+ padding=(0, 1),
65
+ )
66
+ )
67
+
68
+
69
+ def print_specialists_info(specialists: list[str]) -> None:
70
+ """사용 가능한 Specialist 정보를 출력합니다."""
71
+ console = get_console()
72
+
73
+ specialist_descriptions = {
74
+ "explorer": ("🔍", "아키텍처 분석, 다이어그램 해석, 코드 탐색"),
75
+ "researcher": ("📚", "AWS 문서 검색, Best Practice, 가격 정보"),
76
+ "reviewer": ("✅", "Well-Architected 검토, 보안 분석, 개선 권장"),
77
+ }
78
+
79
+ guru_descriptions = {
80
+ "bezos": ("👔", "고객 중심 사고, 장기 비전"),
81
+ "vogels": ("🔧", "분산 시스템, 확장성"),
82
+ "naval": ("💡", "레버리지, ROI 분석"),
83
+ "feynman": ("🎓", "개념 단순화, 설명"),
84
+ }
85
+
86
+ table = Table(show_header=True, header_style="bold", box=None)
87
+ table.add_column("Agent", style="cyan")
88
+ table.add_column("Type", style="dim")
89
+ table.add_column("전문 분야")
90
+
91
+ for name, (emoji, desc) in specialist_descriptions.items():
92
+ table.add_row(f"{emoji} {name}", "Specialist", desc)
93
+
94
+ for name, (emoji, desc) in guru_descriptions.items():
95
+ table.add_row(f"{emoji} {name}", "Guru", desc)
96
+
97
+ console.print()
98
+ console.print(
99
+ Panel(
100
+ table,
101
+ title="[bold]사용 가능한 에이전트[/]",
102
+ border_style="cyan",
103
+ padding=(0, 1),
104
+ )
105
+ )
106
+
107
+
108
+ # =============================================================================
109
+ # 에이전트 패널
110
+ # =============================================================================
111
+
112
+
113
+ class AgentPanel:
114
+ """에이전트 실행 상태를 표시하는 패널"""
115
+
116
+ AGENT_COLORS = {
117
+ "orchestrator": "aws.orange",
118
+ "guru": "agent.guru",
119
+ "specialist": "agent.specialist",
120
+ "bezos": "agent.guru",
121
+ "vogels": "agent.guru",
122
+ "naval": "agent.guru",
123
+ "feynman": "agent.guru",
124
+ "explorer": "agent.explorer",
125
+ "researcher": "agent.researcher",
126
+ "reviewer": "agent.reviewer",
127
+ }
128
+
129
+ AGENT_EMOJIS = {
130
+ "orchestrator": "🎯",
131
+ "bezos": "👔",
132
+ "vogels": "🔧",
133
+ "naval": "💡",
134
+ "feynman": "🎓",
135
+ "explorer": "🔍",
136
+ "researcher": "📚",
137
+ "reviewer": "✅",
138
+ }
139
+
140
+ def __init__(self, agent_name: str, agent_type: str = "specialist"):
141
+ self.agent_name = agent_name
142
+ self.agent_type = agent_type
143
+ self.console = get_console()
144
+
145
+ def print_start(self, task: str = None) -> None:
146
+ """에이전트 시작을 표시합니다."""
147
+ emoji = self.AGENT_EMOJIS.get(self.agent_name.lower(), "🤖")
148
+ color = self.AGENT_COLORS.get(self.agent_name.lower(), "cyan")
149
+
150
+ text = f"{emoji} [{color}]{self.agent_name.upper()}[/] 에이전트 호출 중..."
151
+ if task:
152
+ text += f" ({task[:50]}...)" if len(task) > 50 else f" ({task})"
153
+
154
+ self.console.print(text)
155
+
156
+ def print_result(self, success: bool = True, message: str = None) -> None:
157
+ """에이전트 결과를 표시합니다."""
158
+ emoji = self.AGENT_EMOJIS.get(self.agent_name.lower(), "🤖")
159
+ color = self.AGENT_COLORS.get(self.agent_name.lower(), "cyan")
160
+
161
+ if success:
162
+ status = "[success]완료[/]"
163
+ else:
164
+ status = "[error]실패[/]"
165
+
166
+ text = f"{emoji} [{color}]{self.agent_name.upper()}[/] {status}"
167
+ if message:
168
+ text += f": {message}"
169
+
170
+ self.console.print(text)
171
+
172
+
173
+ # =============================================================================
174
+ # 도구 상태
175
+ # =============================================================================
176
+
177
+
178
+ class ToolStatus:
179
+ """도구 실행 상태를 관리합니다."""
180
+
181
+ TOOL_EMOJIS = {
182
+ "consult_guru": "🧙",
183
+ "consult_specialist": "🔬",
184
+ "file_read": "📄",
185
+ "file_write": "💾",
186
+ "shell": "🖥️",
187
+ "image_reader": "🖼️",
188
+ "code_interpreter": "⚡",
189
+ "skill": "📚",
190
+ "list_gurus": "📋",
191
+ "list_specialists": "📋",
192
+ }
193
+
194
+ def __init__(self):
195
+ self.console = get_console()
196
+ self._active_tools: Dict[str, str] = {} # tool_id -> tool_name
197
+
198
+ def tool_start(
199
+ self, tool_id: str, tool_name: str, args: Dict[str, Any] = None
200
+ ) -> None:
201
+ """도구 시작을 표시합니다."""
202
+ if tool_id in self._active_tools:
203
+ return # 이미 표시됨
204
+
205
+ self._active_tools[tool_id] = tool_name
206
+ emoji = self.TOOL_EMOJIS.get(tool_name, "🔧")
207
+
208
+ # 특별한 도구들에 대한 상세 정보
209
+ detail = ""
210
+ if args:
211
+ if tool_name == "consult_guru":
212
+ guru_name = args.get("guru_name", "")
213
+ detail = f" → {guru_name.upper()}"
214
+ elif tool_name == "consult_specialist":
215
+ specialist_name = args.get("specialist_name", "")
216
+ detail = f" → {specialist_name.upper()}"
217
+ elif tool_name == "skill":
218
+ skill_name = args.get("skill_name", "")
219
+ detail = f" → {skill_name}"
220
+ elif tool_name in ("file_read", "file_write"):
221
+ path = args.get("path", args.get("file_path", ""))
222
+ if path:
223
+ # 경로가 길면 축약
224
+ if len(path) > 40:
225
+ path = "..." + path[-37:]
226
+ detail = f" → {path}"
227
+
228
+ self.console.print(f"[tool.running]{emoji} {tool_name}{detail}[/]")
229
+
230
+ def tool_end(self, tool_id: str, success: bool = True, error: str = None) -> None:
231
+ """도구 종료를 표시합니다. (선택적)"""
232
+ if tool_id in self._active_tools:
233
+ del self._active_tools[tool_id]
234
+
235
+ # 에러가 있을 때만 표시
236
+ if not success and error:
237
+ self.console.print(f"[tool.error]✗ 오류: {error}[/]")
238
+
239
+
240
+ # =============================================================================
241
+ # 응답 렌더러
242
+ # =============================================================================
243
+
244
+
245
+ class ResponseRenderer:
246
+ """에이전트 응답을 렌더링합니다."""
247
+
248
+ def __init__(self):
249
+ self.console = get_console()
250
+ self._buffer = ""
251
+
252
+ def stream_text(self, text: str) -> None:
253
+ """스트리밍 텍스트를 출력합니다."""
254
+ print(text, end="", flush=True)
255
+ self._buffer += text
256
+
257
+ def render_markdown(self, text: str) -> None:
258
+ """마크다운을 렌더링합니다."""
259
+ md = Markdown(text)
260
+ self.console.print(md)
261
+
262
+ def render_code(self, code: str, language: str = "python") -> None:
263
+ """코드를 구문 강조하여 렌더링합니다."""
264
+ syntax = Syntax(code, language, theme="monokai", line_numbers=True)
265
+ self.console.print(syntax)
266
+
267
+ def render_thinking(self, text: str) -> None:
268
+ """Thinking 텍스트를 렌더링합니다."""
269
+ self.console.print(
270
+ Panel(
271
+ Text(text, style="text.thinking"),
272
+ title="[dim]💭 Thinking[/]",
273
+ border_style="dim",
274
+ )
275
+ )
276
+
277
+ def clear_buffer(self) -> str:
278
+ """버퍼를 비우고 내용을 반환합니다."""
279
+ content = self._buffer
280
+ self._buffer = ""
281
+ return content
282
+
283
+
284
+ # =============================================================================
285
+ # 프로그레스 및 스피너
286
+ # =============================================================================
287
+
288
+
289
+ def create_spinner(text: str = "처리 중...") -> Progress:
290
+ """스피너 Progress 인스턴스를 생성합니다."""
291
+ return Progress(
292
+ SpinnerColumn(),
293
+ TextColumn("[progress.description]{task.description}"),
294
+ console=get_console(),
295
+ transient=True,
296
+ )
297
+
298
+
299
+ class ThinkingSpinner:
300
+ """Thinking 상태를 표시하는 스피너"""
301
+
302
+ def __init__(self):
303
+ self.console = get_console()
304
+ self._live: Optional[Live] = None
305
+
306
+ def start(self) -> None:
307
+ """스피너를 시작합니다."""
308
+ spinner = Spinner("dots", text="[text.thinking]생각 중...[/]")
309
+ self._live = Live(spinner, console=self.console, transient=True)
310
+ self._live.start()
311
+
312
+ def update(self, text: str) -> None:
313
+ """스피너 텍스트를 업데이트합니다."""
314
+ if self._live:
315
+ spinner = Spinner("dots", text=f"[text.thinking]{text}[/]")
316
+ self._live.update(spinner)
317
+
318
+ def stop(self) -> None:
319
+ """스피너를 중지합니다."""
320
+ if self._live:
321
+ self._live.stop()
322
+ self._live = None
323
+
324
+
325
+ # =============================================================================
326
+ # 유틸리티
327
+ # =============================================================================
328
+
329
+
330
+ def format_duration(seconds: float) -> str:
331
+ """시간을 포맷팅합니다."""
332
+ if seconds < 1:
333
+ return f"{seconds * 1000:.0f}ms"
334
+ elif seconds < 60:
335
+ return f"{seconds:.1f}s"
336
+ else:
337
+ minutes = int(seconds // 60)
338
+ secs = seconds % 60
339
+ return f"{minutes}m {secs:.0f}s"
340
+
341
+
342
+ def truncate_text(text: str, max_length: int = 100) -> str:
343
+ """텍스트를 축약합니다."""
344
+ if len(text) <= max_length:
345
+ return text
346
+ return text[: max_length - 3] + "..."
cli/console.py ADDED
@@ -0,0 +1,106 @@
1
+ """SA Assistant Console - Rich 기반 콘솔 래퍼
2
+
3
+ Rich 라이브러리를 사용하여 터미널 UI를 개선합니다.
4
+ """
5
+
6
+ from typing import Optional
7
+ from rich.console import Console
8
+ from rich.theme import Theme
9
+ from rich.style import Style
10
+
11
+ # SA Assistant 커스텀 테마 - AWS 오렌지 기반
12
+ SA_THEME = Theme(
13
+ {
14
+ # AWS 컬러 스킴
15
+ "aws.orange": "bold #FF9900",
16
+ "aws.dark": "#232F3E",
17
+ "aws.blue": "#1A73E8",
18
+ # 에이전트 타입별 색상
19
+ "agent.orchestrator": "bold #FF9900",
20
+ "agent.guru": "bold magenta",
21
+ "agent.specialist": "bold cyan",
22
+ "agent.explorer": "bold green",
23
+ "agent.researcher": "bold blue",
24
+ "agent.reviewer": "bold yellow",
25
+ # 도구 상태 색상
26
+ "tool.running": "cyan",
27
+ "tool.success": "green",
28
+ "tool.error": "red",
29
+ "tool.pending": "dim",
30
+ # 텍스트 스타일
31
+ "text.user": "bold green",
32
+ "text.assistant": "bold #FF9900",
33
+ "text.thinking": "dim italic",
34
+ "text.error": "bold red",
35
+ "text.warning": "yellow",
36
+ "text.info": "cyan",
37
+ "text.dim": "dim",
38
+ # 특수 용도
39
+ "highlight": "bold #FF9900",
40
+ "success": "bold green",
41
+ "error": "bold red",
42
+ "warning": "yellow",
43
+ }
44
+ )
45
+
46
+
47
+ class SAConsole:
48
+ """SA Assistant 전용 Rich Console 래퍼
49
+
50
+ 싱글톤 패턴으로 전역 콘솔 인스턴스를 관리합니다.
51
+ """
52
+
53
+ _instance: Optional[Console] = None
54
+
55
+ @classmethod
56
+ def get_console(cls) -> Console:
57
+ """전역 Console 인스턴스를 반환합니다."""
58
+ if cls._instance is None:
59
+ cls._instance = Console(
60
+ theme=SA_THEME,
61
+ highlight=True,
62
+ markup=True,
63
+ emoji=True,
64
+ )
65
+ return cls._instance
66
+
67
+ @classmethod
68
+ def reset(cls) -> None:
69
+ """Console 인스턴스를 리셋합니다. (테스트용)"""
70
+ cls._instance = None
71
+
72
+
73
+ # 편의 함수들
74
+ def get_console() -> Console:
75
+ """전역 Console 인스턴스를 반환합니다."""
76
+ return SAConsole.get_console()
77
+
78
+
79
+ def print_styled(text: str, style: str = None, **kwargs) -> None:
80
+ """스타일이 적용된 텍스트를 출력합니다."""
81
+ console = get_console()
82
+ console.print(text, style=style, **kwargs)
83
+
84
+
85
+ def print_error(text: str) -> None:
86
+ """에러 메시지를 출력합니다."""
87
+ console = get_console()
88
+ console.print(f"[text.error]✗ {text}[/]")
89
+
90
+
91
+ def print_warning(text: str) -> None:
92
+ """경고 메시지를 출력합니다."""
93
+ console = get_console()
94
+ console.print(f"[text.warning]⚠ {text}[/]")
95
+
96
+
97
+ def print_info(text: str) -> None:
98
+ """정보 메시지를 출력합니다."""
99
+ console = get_console()
100
+ console.print(f"[text.info]ℹ {text}[/]")
101
+
102
+
103
+ def print_success(text: str) -> None:
104
+ """성공 메시지를 출력합니다."""
105
+ console = get_console()
106
+ console.print(f"[success]✓ {text}[/]")
cli/mdstream.py ADDED
@@ -0,0 +1,236 @@
1
+ """SA Assistant Markdown Stream - Streaming markdown renderer with Rich Live display."""
2
+
3
+ import io
4
+ import time
5
+ from typing import Optional
6
+
7
+ from rich import box
8
+ from rich.console import Console
9
+ from rich.live import Live
10
+ from rich.markdown import Markdown, CodeBlock, Heading
11
+ from rich.panel import Panel
12
+ from rich.syntax import Syntax
13
+ from rich.text import Text
14
+
15
+ from .console import get_console
16
+
17
+
18
+ class SACodeBlock(CodeBlock):
19
+ """Code block with minimal padding and monokai theme."""
20
+
21
+ def __rich_console__(self, console, options):
22
+ code = str(self.text).rstrip()
23
+ syntax = Syntax(
24
+ code,
25
+ self.lexer_name,
26
+ theme="monokai",
27
+ word_wrap=True,
28
+ padding=(1, 2),
29
+ background_color="#1a1a1a",
30
+ )
31
+ yield syntax
32
+
33
+
34
+ class SAHeading(Heading):
35
+ """Left-aligned heading with AWS color scheme."""
36
+
37
+ def __rich_console__(self, console, options):
38
+ text = self.text
39
+ text.justify = "left"
40
+
41
+ if self.tag == "h1":
42
+ yield Panel(
43
+ text,
44
+ box=box.HEAVY,
45
+ style="bold #FF9900",
46
+ border_style="#FF9900",
47
+ )
48
+ elif self.tag == "h2":
49
+ yield Text("")
50
+ text.stylize("bold cyan")
51
+ yield text
52
+ else:
53
+ text.stylize("bold")
54
+ yield text
55
+
56
+
57
+ class SAMarkdown(Markdown):
58
+ """Custom markdown renderer with SA Assistant styling."""
59
+
60
+ elements = {
61
+ **Markdown.elements,
62
+ "fence": SACodeBlock,
63
+ "code_block": SACodeBlock,
64
+ "heading_open": SAHeading,
65
+ }
66
+
67
+
68
+ class MarkdownStream:
69
+ """Streaming markdown renderer using Rich Live display.
70
+
71
+ Renders LLM responses in real-time with proper markdown formatting.
72
+ Uses sliding window approach: stable lines go to console,
73
+ unstable lines stay in Live area for smooth updates.
74
+
75
+ Usage:
76
+ stream = MarkdownStream()
77
+ for chunk in llm_response:
78
+ stream.update(accumulated_text)
79
+ stream.update(final_text, final=True)
80
+ """
81
+
82
+ MIN_FPS = 20
83
+ MAX_DELAY = 2.0
84
+
85
+ def __init__(
86
+ self,
87
+ console: Optional[Console] = None,
88
+ live_window: int = 6,
89
+ min_delay: float = 1.0 / 20,
90
+ ):
91
+ self.console = console or get_console()
92
+ self.live_window = live_window
93
+ self.min_delay = min_delay
94
+
95
+ self.live: Optional[Live] = None
96
+ self.when: float = 0
97
+ self.printed: list = []
98
+ self._live_started: bool = False
99
+ self._text_buffer: str = ""
100
+
101
+ def _render_markdown_to_lines(self, text: str) -> list:
102
+ string_io = io.StringIO()
103
+ temp_console = Console(
104
+ file=string_io, force_terminal=True, width=self.console.width
105
+ )
106
+
107
+ markdown = SAMarkdown(text)
108
+ temp_console.print(markdown)
109
+ output = string_io.getvalue()
110
+
111
+ return output.splitlines(keepends=True)
112
+
113
+ def update(self, text: str, final: bool = False) -> None:
114
+ """Update displayed markdown content."""
115
+ self._text_buffer = text
116
+
117
+ if not self._live_started:
118
+ self.live = Live(
119
+ Text(""),
120
+ console=self.console,
121
+ refresh_per_second=1.0 / self.min_delay,
122
+ transient=True,
123
+ )
124
+ self.live.start()
125
+ self._live_started = True
126
+
127
+ now = time.time()
128
+ if not final and now - self.when < self.min_delay:
129
+ return
130
+ self.when = now
131
+
132
+ start = time.time()
133
+ lines = self._render_markdown_to_lines(text)
134
+ render_time = time.time() - start
135
+
136
+ self.min_delay = min(max(render_time * 10, 1.0 / self.MIN_FPS), self.MAX_DELAY)
137
+
138
+ num_lines = len(lines)
139
+
140
+ if not final:
141
+ num_lines -= self.live_window
142
+
143
+ if final or num_lines > 0:
144
+ num_printed = len(self.printed)
145
+ show_count = num_lines - num_printed
146
+
147
+ if show_count > 0 and self.live is not None:
148
+ show = lines[num_printed:num_lines]
149
+ show_text = "".join(show)
150
+ show_rich = Text.from_ansi(show_text)
151
+ self.live.console.print(show_rich, end="")
152
+
153
+ self.printed = lines[:num_lines]
154
+
155
+ if final:
156
+ if self.live:
157
+ self.live.update(Text(""))
158
+ self.live.stop()
159
+ self.live = None
160
+ return
161
+
162
+ rest = lines[num_lines:]
163
+ rest_text = "".join(rest)
164
+ rest_rich = Text.from_ansi(rest_text)
165
+ if self.live:
166
+ self.live.update(rest_rich)
167
+
168
+ def finish(self) -> None:
169
+ """Finalize stream and output remaining content."""
170
+ if self._text_buffer:
171
+ self.update(self._text_buffer, final=True)
172
+ elif self.live:
173
+ self.live.stop()
174
+ self.live = None
175
+
176
+ def __del__(self):
177
+ if self.live:
178
+ try:
179
+ self.live.stop()
180
+ except Exception:
181
+ pass
182
+
183
+
184
+ class SimpleMarkdownStream:
185
+ """Simple markdown stream - renders final result only.
186
+
187
+ Outputs plain text during streaming, renders full markdown on completion.
188
+ """
189
+
190
+ def __init__(self, console: Optional[Console] = None):
191
+ self.console = console or get_console()
192
+ self._buffer = ""
193
+ self._last_printed_len = 0
194
+
195
+ def update(self, text: str, final: bool = False) -> None:
196
+ """Update text content."""
197
+ if final:
198
+ print()
199
+ self.console.print(SAMarkdown(text))
200
+ else:
201
+ new_text = text[self._last_printed_len :]
202
+ if new_text:
203
+ print(new_text, end="", flush=True)
204
+ self._last_printed_len = len(text)
205
+
206
+ self._buffer = text
207
+
208
+ def finish(self) -> None:
209
+ """Finalize stream."""
210
+ if self._buffer:
211
+ self.update(self._buffer, final=True)
212
+
213
+
214
+ def render_markdown(text: str, console: Optional[Console] = None) -> None:
215
+ """Render markdown text to console."""
216
+ console = console or get_console()
217
+ console.print(SAMarkdown(text))
218
+
219
+
220
+ def render_code(
221
+ code: str,
222
+ language: str = "python",
223
+ console: Optional[Console] = None,
224
+ line_numbers: bool = True,
225
+ ) -> None:
226
+ """Render code with syntax highlighting."""
227
+ console = console or get_console()
228
+ syntax = Syntax(
229
+ code,
230
+ language,
231
+ theme="monokai",
232
+ line_numbers=line_numbers,
233
+ word_wrap=True,
234
+ background_color="#1a1a1a",
235
+ )
236
+ console.print(syntax)
mcp_client/client.py ADDED
@@ -0,0 +1,12 @@
1
+ from mcp.client.streamable_http import streamablehttp_client
2
+ from strands.tools.mcp.mcp_client import MCPClient
3
+
4
+ # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication
5
+ EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp"
6
+
7
+ def get_streamable_http_mcp_client() -> MCPClient:
8
+ """
9
+ Returns an MCP Client compatible with Strands
10
+ """
11
+ # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"}
12
+ return MCPClient(lambda: streamablehttp_client(EXAMPLE_MCP_ENDPOINT))