plotext-plus 1.0.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.
@@ -0,0 +1,505 @@
1
+ # /usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Plotext Plus MCP Server - Model Context Protocol Integration
6
+ ==========================================================
7
+
8
+ This module provides a Model Context Protocol (MCP) server that exposes
9
+ the plotext_plus API as MCP tools for use with AI clients like Claude.
10
+
11
+ The server uses chuk-mcp-server for zero-configuration MCP functionality.
12
+ """
13
+
14
+ try:
15
+ from chuk_mcp_server import tool, resource, prompt, run
16
+ except ImportError:
17
+ raise ImportError(
18
+ "chuk-mcp-server is required for MCP functionality. "
19
+ "Install it with: uv add --optional mcp plotext_plus"
20
+ )
21
+
22
+ import asyncio
23
+ from typing import List, Optional, Union, Dict, Any
24
+ import json
25
+ import base64
26
+ from io import StringIO
27
+ import sys
28
+
29
+ # Import public plotext_plus APIs
30
+ from . import plotting
31
+ from . import charts
32
+ from . import themes
33
+ from . import utilities
34
+
35
+ # Keep track of the current plot state
36
+ _current_plot_buffer = StringIO()
37
+
38
+
39
+ def _capture_plot_output(func, *args, **kwargs):
40
+ """Capture plot output and return as string"""
41
+ # Save current stdout
42
+ old_stdout = sys.stdout
43
+
44
+ try:
45
+ # Redirect stdout to capture plot output
46
+ sys.stdout = _current_plot_buffer
47
+ result = func(*args, **kwargs)
48
+ plot_output = _current_plot_buffer.getvalue()
49
+ _current_plot_buffer.truncate(0)
50
+ _current_plot_buffer.seek(0)
51
+ return result, plot_output
52
+ finally:
53
+ # Restore stdout
54
+ sys.stdout = old_stdout
55
+
56
+
57
+ # Core Plotting Tools
58
+ @tool
59
+ async def scatter_plot(x: List[Union[int, float]], y: List[Union[int, float]],
60
+ marker: Optional[str] = None, color: Optional[str] = None,
61
+ title: Optional[str] = None) -> str:
62
+ """Create a scatter plot with given x and y data points.
63
+
64
+ Args:
65
+ x: List of x-coordinates
66
+ y: List of y-coordinates
67
+ marker: Marker style (optional)
68
+ color: Plot color (optional)
69
+ title: Plot title (optional)
70
+
71
+ Returns:
72
+ The rendered plot as text
73
+ """
74
+ plotting.clear_figure()
75
+ if title:
76
+ plotting.title(title)
77
+
78
+ _, output = _capture_plot_output(plotting.scatter, x, y, marker=marker, color=color)
79
+ _, show_output = _capture_plot_output(plotting.show)
80
+
81
+ return output + show_output
82
+
83
+
84
+ @tool
85
+ async def line_plot(x: List[Union[int, float]], y: List[Union[int, float]],
86
+ color: Optional[str] = None, title: Optional[str] = None) -> str:
87
+ """Create a line plot with given x and y data points.
88
+
89
+ Args:
90
+ x: List of x-coordinates
91
+ y: List of y-coordinates
92
+ color: Line color (optional)
93
+ title: Plot title (optional)
94
+
95
+ Returns:
96
+ The rendered plot as text
97
+ """
98
+ plotting.clear_figure()
99
+ if title:
100
+ plotting.title(title)
101
+
102
+ _, output = _capture_plot_output(plotting.plot, x, y, color=color)
103
+ _, show_output = _capture_plot_output(plotting.show)
104
+
105
+ return output + show_output
106
+
107
+
108
+ @tool
109
+ async def bar_chart(labels: List[str], values: List[Union[int, float]],
110
+ color: Optional[str] = None, title: Optional[str] = None) -> str:
111
+ """Create a bar chart with given labels and values.
112
+
113
+ Args:
114
+ labels: List of bar labels
115
+ values: List of bar values
116
+ color: Bar color (optional)
117
+ title: Plot title (optional)
118
+
119
+ Returns:
120
+ The rendered plot as text
121
+ """
122
+ plotting.clear_figure()
123
+ if title:
124
+ plotting.title(title)
125
+
126
+ _, output = _capture_plot_output(plotting.bar, labels, values, color=color)
127
+ _, show_output = _capture_plot_output(plotting.show)
128
+
129
+ return output + show_output
130
+
131
+
132
+ @tool
133
+ async def matrix_plot(data: List[List[Union[int, float]]], title: Optional[str] = None) -> str:
134
+ """Create a matrix/heatmap plot from 2D data.
135
+
136
+ Args:
137
+ data: 2D list representing matrix data
138
+ title: Plot title (optional)
139
+
140
+ Returns:
141
+ The rendered plot as text
142
+ """
143
+ plotting.clear_figure()
144
+ if title:
145
+ plotting.title(title)
146
+
147
+ _, output = _capture_plot_output(plotting.matrix_plot, data)
148
+ _, show_output = _capture_plot_output(plotting.show)
149
+
150
+ return output + show_output
151
+
152
+
153
+ # Chart Class Tools
154
+ @tool
155
+ async def quick_scatter(x: List[Union[int, float]], y: List[Union[int, float]],
156
+ title: Optional[str] = None, theme_name: Optional[str] = None) -> str:
157
+ """Create a quick scatter chart using the chart classes API.
158
+
159
+ Args:
160
+ x: List of x-coordinates
161
+ y: List of y-coordinates
162
+ title: Chart title (optional)
163
+ theme_name: Theme to apply (optional)
164
+
165
+ Returns:
166
+ The rendered chart as text
167
+ """
168
+ _, output = _capture_plot_output(charts.quick_scatter, x, y, title=title, theme=theme_name)
169
+ return output
170
+
171
+
172
+ @tool
173
+ async def quick_line(x: List[Union[int, float]], y: List[Union[int, float]],
174
+ title: Optional[str] = None, theme_name: Optional[str] = None) -> str:
175
+ """Create a quick line chart using the chart classes API.
176
+
177
+ Args:
178
+ x: List of x-coordinates
179
+ y: List of y-coordinates
180
+ title: Chart title (optional)
181
+ theme_name: Theme to apply (optional)
182
+
183
+ Returns:
184
+ The rendered chart as text
185
+ """
186
+ _, output = _capture_plot_output(charts.quick_line, x, y, title=title, theme=theme_name)
187
+ return output
188
+
189
+
190
+ @tool
191
+ async def quick_bar(labels: List[str], values: List[Union[int, float]],
192
+ title: Optional[str] = None, theme_name: Optional[str] = None) -> str:
193
+ """Create a quick bar chart using the chart classes API.
194
+
195
+ Args:
196
+ labels: List of bar labels
197
+ values: List of bar values
198
+ title: Chart title (optional)
199
+ theme_name: Theme to apply (optional)
200
+
201
+ Returns:
202
+ The rendered chart as text
203
+ """
204
+ _, output = _capture_plot_output(charts.quick_bar, labels, values, title=title, theme=theme_name)
205
+ return output
206
+
207
+
208
+ # Theme Tools
209
+ @tool
210
+ async def get_available_themes() -> Dict[str, Any]:
211
+ """Get information about available themes.
212
+
213
+ Returns:
214
+ Dictionary containing theme information
215
+ """
216
+ from .themes import get_theme_info
217
+ return get_theme_info()
218
+
219
+
220
+ @tool
221
+ async def apply_plot_theme(theme_name: str) -> str:
222
+ """Apply a theme to the current plot.
223
+
224
+ Args:
225
+ theme_name: Name of the theme to apply
226
+
227
+ Returns:
228
+ Confirmation message
229
+ """
230
+ plotting.clear_figure()
231
+ plotting.theme(theme_name)
232
+ return f"Applied theme: {theme_name}"
233
+
234
+
235
+ # Utility Tools
236
+ @tool
237
+ async def get_terminal_width() -> int:
238
+ """Get the current terminal width.
239
+
240
+ Returns:
241
+ Terminal width in characters
242
+ """
243
+ return utilities.terminal_width()
244
+
245
+
246
+ @tool
247
+ async def colorize_text(text: str, color: str) -> str:
248
+ """Apply color formatting to text.
249
+
250
+ Args:
251
+ text: Text to colorize
252
+ color: Color name or code
253
+
254
+ Returns:
255
+ Colorized text
256
+ """
257
+ return utilities.colorize(text, color)
258
+
259
+
260
+ @tool
261
+ async def log_info(message: str) -> str:
262
+ """Log an informational message.
263
+
264
+ Args:
265
+ message: Message to log
266
+
267
+ Returns:
268
+ Formatted log message
269
+ """
270
+ utilities.log_info(message)
271
+ return f"INFO: {message}"
272
+
273
+
274
+ @tool
275
+ async def log_success(message: str) -> str:
276
+ """Log a success message.
277
+
278
+ Args:
279
+ message: Message to log
280
+
281
+ Returns:
282
+ Formatted log message
283
+ """
284
+ utilities.log_success(message)
285
+ return f"SUCCESS: {message}"
286
+
287
+
288
+ @tool
289
+ async def log_warning(message: str) -> str:
290
+ """Log a warning message.
291
+
292
+ Args:
293
+ message: Message to log
294
+
295
+ Returns:
296
+ Formatted log message
297
+ """
298
+ utilities.log_warning(message)
299
+ return f"WARNING: {message}"
300
+
301
+
302
+ @tool
303
+ async def log_error(message: str) -> str:
304
+ """Log an error message.
305
+
306
+ Args:
307
+ message: Message to log
308
+
309
+ Returns:
310
+ Formatted log message
311
+ """
312
+ utilities.log_error(message)
313
+ return f"ERROR: {message}"
314
+
315
+
316
+ # Configuration and Plot Management
317
+ @tool
318
+ async def set_plot_size(width: int, height: int) -> str:
319
+ """Set the plot size.
320
+
321
+ Args:
322
+ width: Plot width
323
+ height: Plot height
324
+
325
+ Returns:
326
+ Confirmation message
327
+ """
328
+ plotting.plotsize(width, height)
329
+ return f"Plot size set to {width}x{height}"
330
+
331
+
332
+ @tool
333
+ async def enable_banner_mode(enabled: bool = True, title: Optional[str] = None,
334
+ subtitle: Optional[str] = None) -> str:
335
+ """Enable or disable banner mode.
336
+
337
+ Args:
338
+ enabled: Whether to enable banner mode
339
+ title: Banner title (optional)
340
+ subtitle: Banner subtitle (optional)
341
+
342
+ Returns:
343
+ Confirmation message
344
+ """
345
+ plotting.banner_mode(enabled, title=title, subtitle=subtitle)
346
+ status = "enabled" if enabled else "disabled"
347
+ return f"Banner mode {status}"
348
+
349
+
350
+ @tool
351
+ async def clear_plot() -> str:
352
+ """Clear the current plot.
353
+
354
+ Returns:
355
+ Confirmation message
356
+ """
357
+ plotting.clear_figure()
358
+ return "Plot cleared"
359
+
360
+
361
+ # Resource for plot configuration
362
+ @resource("config://plotext")
363
+ async def get_plot_config() -> Dict[str, Any]:
364
+ """Get current plot configuration."""
365
+ from .themes import get_theme_info
366
+ return {
367
+ "terminal_width": utilities.terminal_width(),
368
+ "available_themes": get_theme_info(),
369
+ "library_version": "plotext_plus",
370
+ "mcp_enabled": True
371
+ }
372
+
373
+
374
+ # MCP Prompts for common plotting scenarios
375
+ @prompt("basic_scatter")
376
+ async def basic_scatter_prompt() -> str:
377
+ """Create a simple scatter plot example"""
378
+ return "Create a scatter plot showing the relationship between x=[1,2,3,4,5] and y=[1,4,9,16,25] with the title 'Quadratic Function'."
379
+
380
+
381
+ @prompt("basic_bar_chart")
382
+ async def basic_bar_chart_prompt() -> str:
383
+ """Generate a bar chart example"""
384
+ return "Make a bar chart showing sales data: categories=['Q1','Q2','Q3','Q4'] and values=[120,150,180,200] with title 'Quarterly Sales'."
385
+
386
+
387
+ @prompt("line_plot_with_theme")
388
+ async def line_plot_with_theme_prompt() -> str:
389
+ """Create a line plot with theme example"""
390
+ return "Plot a line chart of temperature data over time: x=[1,2,3,4,5,6,7] and y=[20,22,25,28,26,24,21] using the 'dark' theme with title 'Weekly Temperature'."
391
+
392
+
393
+ @prompt("matrix_heatmap")
394
+ async def matrix_heatmap_prompt() -> str:
395
+ """Matrix heatmap visualization example"""
396
+ return "Create a heatmap from this 3x3 correlation matrix: [[1.0,0.8,0.3],[0.8,1.0,0.5],[0.3,0.5,1.0]] with title 'Feature Correlation'."
397
+
398
+
399
+ @prompt("multi_step_workflow")
400
+ async def multi_step_workflow_prompt() -> str:
401
+ """Multi-step visualization workflow example"""
402
+ return """1. First, show me available themes
403
+ 2. Set the plot size to 100x30
404
+ 3. Apply the 'elegant' theme
405
+ 4. Create a scatter plot comparing dataset A=[1,3,5,7,9] vs B=[2,6,10,14,18]
406
+ 5. Add title 'Linear Relationship Analysis'"""
407
+
408
+
409
+ @prompt("professional_bar_chart")
410
+ async def professional_bar_chart_prompt() -> str:
411
+ """Custom styling and configuration example"""
412
+ return """Create a professional-looking bar chart with:
413
+ - Data: ['Product A', 'Product B', 'Product C'] with values [45, 67, 23]
414
+ - Enable banner mode with title 'Sales Report' and subtitle 'Q3 2024'
415
+ - Use a custom color scheme
416
+ - Set appropriate plot dimensions"""
417
+
418
+
419
+ @prompt("theme_exploration")
420
+ async def theme_exploration_prompt() -> str:
421
+ """Theme exploration example"""
422
+ return "Show me all available themes, then create the same scatter plot [1,2,3,4] vs [10,20,15,25] using three different themes for comparison."
423
+
424
+
425
+ @prompt("banner_mode_demo")
426
+ async def banner_mode_demo_prompt() -> str:
427
+ """Banner mode demonstration example"""
428
+ return "Enable banner mode with title 'Data Analysis Dashboard' and create a line plot showing trend data: months=['Jan','Feb','Mar','Apr','May'] and growth=[100,110,125,140,160]."
429
+
430
+
431
+ @prompt("terminal_width_optimization")
432
+ async def terminal_width_optimization_prompt() -> str:
433
+ """Terminal and environment info example"""
434
+ return "What's my current terminal width? Then create a plot that optimally uses the full width for displaying time series data."
435
+
436
+
437
+ @prompt("colorized_output")
438
+ async def colorized_output_prompt() -> str:
439
+ """Colorized output example"""
440
+ return "Use the colorize function to create colored status messages, then generate a plot showing system performance metrics."
441
+
442
+
443
+ @prompt("regional_sales_analysis")
444
+ async def regional_sales_analysis_prompt() -> str:
445
+ """Data analysis workflow example"""
446
+ return """I have sales data by region: East=[100,120,110], West=[80,95,105], North=[60,75,85], South=[90,100,115] over 3 quarters.
447
+
448
+ Please:
449
+ 1. Create individual plots for each region
450
+ 2. Show a comparative bar chart
451
+ 3. Use appropriate themes and titles
452
+ 4. Provide insights on the trends"""
453
+
454
+
455
+ @prompt("comparative_visualization")
456
+ async def comparative_visualization_prompt() -> str:
457
+ """Comparative visualization example"""
458
+ return """Compare two datasets using multiple visualization types:
459
+ - Dataset 1: [5,10,15,20,25]
460
+ - Dataset 2: [3,8,18,22,28]
461
+ - Show both as scatter plot and line plot
462
+ - Use different colors and add meaningful titles"""
463
+
464
+
465
+ @prompt("error_handling_test")
466
+ async def error_handling_test_prompt() -> str:
467
+ """Error handling example"""
468
+ return """Try to create plots with various data scenarios and show how the system handles edge cases:
469
+ - Empty datasets
470
+ - Mismatched array lengths
471
+ - Invalid color names
472
+ - Non-existent themes"""
473
+
474
+
475
+ @prompt("performance_testing")
476
+ async def performance_testing_prompt() -> str:
477
+ """Performance testing example"""
478
+ return """Generate and plot large datasets (100+ points) to test performance:
479
+ - Create random data arrays
480
+ - Time the plotting operations
481
+ - Show memory usage if possible
482
+ - Compare different plot types"""
483
+
484
+
485
+ @prompt("complete_workflow")
486
+ async def complete_workflow_prompt() -> str:
487
+ """Complete workflow test example"""
488
+ return """Execute a complete visualization workflow:
489
+ 1. Check system configuration
490
+ 2. List available themes
491
+ 3. Set optimal plot size for terminal
492
+ 4. Create multiple chart types with sample data
493
+ 5. Apply different themes to each
494
+ 6. Generate a summary report"""
495
+
496
+
497
+ # Main server entry point
498
+ def start_server():
499
+ """Start the MCP server."""
500
+ print("Starting Plotext Plus MCP Server...")
501
+ run()
502
+
503
+
504
+ if __name__ == "__main__":
505
+ start_server()