mseep-rmcp 0.3.3__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,590 @@
1
+ """
2
+ Visualization tools for RMCP.
3
+
4
+ Statistical plotting and data visualization capabilities.
5
+ """
6
+
7
+ from typing import Dict, Any
8
+ from ..registries.tools import tool
9
+ from ..core.schemas import table_schema, formula_schema
10
+ from ..r_integration import execute_r_script
11
+
12
+
13
+ @tool(
14
+ name="scatter_plot",
15
+ input_schema={
16
+ "type": "object",
17
+ "properties": {
18
+ "data": table_schema(),
19
+ "x": {"type": "string"},
20
+ "y": {"type": "string"},
21
+ "group": {"type": "string"},
22
+ "title": {"type": "string"},
23
+ "file_path": {"type": "string"},
24
+ "width": {"type": "integer", "minimum": 100, "default": 800},
25
+ "height": {"type": "integer", "minimum": 100, "default": 600}
26
+ },
27
+ "required": ["data", "x", "y", "file_path"]
28
+ },
29
+ description="Create scatter plot with optional grouping and trend lines"
30
+ )
31
+ async def scatter_plot(context, params):
32
+ """Create scatter plot."""
33
+
34
+ await context.info("Creating scatter plot")
35
+
36
+ r_script = '''
37
+ if (!require(ggplot2)) install.packages("ggplot2", quietly = TRUE)
38
+ library(ggplot2)
39
+
40
+ data <- as.data.frame(args$data)
41
+ x_var <- args$x
42
+ y_var <- args$y
43
+ group_var <- args$group
44
+ title <- args$title %||% paste("Scatter plot:", y_var, "vs", x_var)
45
+ file_path <- args$file_path
46
+ width <- args$width %||% 800
47
+ height <- args$height %||% 600
48
+
49
+ # Create base plot
50
+ p <- ggplot(data, aes_string(x = x_var, y = y_var))
51
+
52
+ if (!is.null(group_var)) {
53
+ p <- p + geom_point(aes_string(color = group_var), alpha = 0.7) +
54
+ geom_smooth(aes_string(color = group_var), method = "lm", se = TRUE)
55
+ } else {
56
+ p <- p + geom_point(alpha = 0.7) +
57
+ geom_smooth(method = "lm", se = TRUE, color = "blue")
58
+ }
59
+
60
+ p <- p + labs(title = title, x = x_var, y = y_var) +
61
+ theme_minimal() +
62
+ theme(plot.title = element_text(hjust = 0.5))
63
+
64
+ # Save plot
65
+ ggsave(file_path, plot = p, width = width/100, height = height/100, dpi = 100)
66
+
67
+ # Basic correlation
68
+ correlation <- cor(data[[x_var]], data[[y_var]], use = "complete.obs")
69
+
70
+ result <- list(
71
+ file_path = file_path,
72
+ x_variable = x_var,
73
+ y_variable = y_var,
74
+ group_variable = group_var,
75
+ correlation = correlation,
76
+ title = title,
77
+ n_points = sum(!is.na(data[[x_var]]) & !is.na(data[[y_var]])),
78
+ plot_saved = file.exists(file_path)
79
+ )
80
+ '''
81
+
82
+ try:
83
+ result = execute_r_script(r_script, params)
84
+ await context.info("Scatter plot created successfully")
85
+ return result
86
+
87
+ except Exception as e:
88
+ await context.error("Scatter plot creation failed", error=str(e))
89
+ raise
90
+
91
+
92
+ @tool(
93
+ name="histogram",
94
+ input_schema={
95
+ "type": "object",
96
+ "properties": {
97
+ "data": table_schema(),
98
+ "variable": {"type": "string"},
99
+ "group": {"type": "string"},
100
+ "bins": {"type": "integer", "minimum": 5, "maximum": 100, "default": 30},
101
+ "title": {"type": "string"},
102
+ "file_path": {"type": "string"},
103
+ "width": {"type": "integer", "minimum": 100, "default": 800},
104
+ "height": {"type": "integer", "minimum": 100, "default": 600}
105
+ },
106
+ "required": ["data", "variable", "file_path"]
107
+ },
108
+ description="Create histogram with optional grouping and density overlay"
109
+ )
110
+ async def histogram(context, params):
111
+ """Create histogram."""
112
+
113
+ await context.info("Creating histogram")
114
+
115
+ r_script = '''
116
+ if (!require(ggplot2)) install.packages("ggplot2", quietly = TRUE)
117
+ library(ggplot2)
118
+
119
+ data <- as.data.frame(args$data)
120
+ variable <- args$variable
121
+ group_var <- args$group
122
+ bins <- args$bins %||% 30
123
+ title <- args$title %||% paste("Histogram of", variable)
124
+ file_path <- args$file_path
125
+ width <- args$width %||% 800
126
+ height <- args$height %||% 600
127
+
128
+ # Create base plot
129
+ p <- ggplot(data, aes_string(x = variable))
130
+
131
+ if (!is.null(group_var)) {
132
+ p <- p + geom_histogram(aes_string(fill = group_var), bins = bins, alpha = 0.7, position = "identity") +
133
+ geom_density(aes_string(color = group_var), alpha = 0.8)
134
+ } else {
135
+ p <- p + geom_histogram(bins = bins, alpha = 0.7, fill = "steelblue") +
136
+ geom_density(alpha = 0.8, color = "red")
137
+ }
138
+
139
+ p <- p + labs(title = title, x = variable, y = "Frequency") +
140
+ theme_minimal() +
141
+ theme(plot.title = element_text(hjust = 0.5))
142
+
143
+ # Save plot
144
+ ggsave(file_path, plot = p, width = width/100, height = height/100, dpi = 100)
145
+
146
+ # Basic statistics
147
+ values <- data[[variable]][!is.na(data[[variable]])]
148
+ stats <- list(
149
+ mean = mean(values),
150
+ median = median(values),
151
+ sd = sd(values),
152
+ skewness = (sum((values - mean(values))^3) / length(values)) / (sd(values)^3),
153
+ kurtosis = (sum((values - mean(values))^4) / length(values)) / (sd(values)^4) - 3
154
+ )
155
+
156
+ result <- list(
157
+ file_path = file_path,
158
+ variable = variable,
159
+ group_variable = group_var,
160
+ bins = bins,
161
+ statistics = stats,
162
+ title = title,
163
+ n_obs = length(values),
164
+ plot_saved = file.exists(file_path)
165
+ )
166
+ '''
167
+
168
+ try:
169
+ result = execute_r_script(r_script, params)
170
+ await context.info("Histogram created successfully")
171
+ return result
172
+
173
+ except Exception as e:
174
+ await context.error("Histogram creation failed", error=str(e))
175
+ raise
176
+
177
+
178
+ @tool(
179
+ name="boxplot",
180
+ input_schema={
181
+ "type": "object",
182
+ "properties": {
183
+ "data": table_schema(),
184
+ "variable": {"type": "string"},
185
+ "group": {"type": "string"},
186
+ "title": {"type": "string"},
187
+ "file_path": {"type": "string"},
188
+ "width": {"type": "integer", "minimum": 100, "default": 800},
189
+ "height": {"type": "integer", "minimum": 100, "default": 600}
190
+ },
191
+ "required": ["data", "variable", "file_path"]
192
+ },
193
+ description="Create box plot with optional grouping"
194
+ )
195
+ async def boxplot(context, params):
196
+ """Create box plot."""
197
+
198
+ await context.info("Creating box plot")
199
+
200
+ r_script = '''
201
+ if (!require(ggplot2)) install.packages("ggplot2", quietly = TRUE)
202
+ library(ggplot2)
203
+
204
+ data <- as.data.frame(args$data)
205
+ variable <- args$variable
206
+ group_var <- args$group
207
+ title <- args$title %||% paste("Box plot of", variable)
208
+ file_path <- args$file_path
209
+ width <- args$width %||% 800
210
+ height <- args$height %||% 600
211
+
212
+ # Create plot
213
+ if (!is.null(group_var)) {
214
+ p <- ggplot(data, aes_string(x = group_var, y = variable, fill = group_var)) +
215
+ geom_boxplot(alpha = 0.7) +
216
+ geom_jitter(width = 0.2, alpha = 0.5) +
217
+ labs(title = title, x = group_var, y = variable)
218
+ } else {
219
+ p <- ggplot(data, aes_string(y = variable)) +
220
+ geom_boxplot(fill = "steelblue", alpha = 0.7) +
221
+ geom_jitter(width = 0.1, alpha = 0.5) +
222
+ labs(title = title, x = "", y = variable)
223
+ }
224
+
225
+ p <- p + theme_minimal() +
226
+ theme(plot.title = element_text(hjust = 0.5))
227
+
228
+ # Save plot
229
+ ggsave(file_path, plot = p, width = width/100, height = height/100, dpi = 100)
230
+
231
+ # Summary statistics
232
+ if (!is.null(group_var)) {
233
+ stats <- by(data[[variable]], data[[group_var]], function(x) {
234
+ x_clean <- x[!is.na(x)]
235
+ list(
236
+ median = median(x_clean),
237
+ q1 = quantile(x_clean, 0.25),
238
+ q3 = quantile(x_clean, 0.75),
239
+ iqr = IQR(x_clean),
240
+ n = length(x_clean),
241
+ outliers = length(boxplot.stats(x_clean)$out)
242
+ )
243
+ })
244
+ summary_stats <- lapply(stats, identity)
245
+ } else {
246
+ x_clean <- data[[variable]][!is.na(data[[variable]])]
247
+ summary_stats <- list(
248
+ overall = list(
249
+ median = median(x_clean),
250
+ q1 = quantile(x_clean, 0.25),
251
+ q3 = quantile(x_clean, 0.75),
252
+ iqr = IQR(x_clean),
253
+ n = length(x_clean),
254
+ outliers = length(boxplot.stats(x_clean)$out)
255
+ )
256
+ )
257
+ }
258
+
259
+ result <- list(
260
+ file_path = file_path,
261
+ variable = variable,
262
+ group_variable = group_var,
263
+ summary_statistics = summary_stats,
264
+ title = title,
265
+ plot_saved = file.exists(file_path)
266
+ )
267
+ '''
268
+
269
+ try:
270
+ result = execute_r_script(r_script, params)
271
+ await context.info("Box plot created successfully")
272
+ return result
273
+
274
+ except Exception as e:
275
+ await context.error("Box plot creation failed", error=str(e))
276
+ raise
277
+
278
+
279
+ @tool(
280
+ name="time_series_plot",
281
+ input_schema={
282
+ "type": "object",
283
+ "properties": {
284
+ "data": {
285
+ "type": "object",
286
+ "properties": {
287
+ "values": {"type": "array", "items": {"type": "number"}},
288
+ "dates": {"type": "array", "items": {"type": "string"}}
289
+ },
290
+ "required": ["values"]
291
+ },
292
+ "title": {"type": "string"},
293
+ "file_path": {"type": "string"},
294
+ "show_trend": {"type": "boolean", "default": True},
295
+ "width": {"type": "integer", "minimum": 100, "default": 1000},
296
+ "height": {"type": "integer", "minimum": 100, "default": 600}
297
+ },
298
+ "required": ["data", "file_path"]
299
+ },
300
+ description="Create time series plot with optional trend line"
301
+ )
302
+ async def time_series_plot(context, params):
303
+ """Create time series plot."""
304
+
305
+ await context.info("Creating time series plot")
306
+
307
+ r_script = '''
308
+ if (!require(ggplot2)) install.packages("ggplot2", quietly = TRUE)
309
+ library(ggplot2)
310
+
311
+ values <- args$data$values
312
+ dates <- args$data$dates
313
+ title <- args$title %||% "Time Series Plot"
314
+ file_path <- args$file_path
315
+ show_trend <- args$show_trend %||% TRUE
316
+ width <- args$width %||% 1000
317
+ height <- args$height %||% 600
318
+
319
+ # Create time index if dates not provided
320
+ if (is.null(dates)) {
321
+ time_index <- 1:length(values)
322
+ x_label <- "Time Index"
323
+ } else {
324
+ time_index <- as.Date(dates)
325
+ x_label <- "Date"
326
+ }
327
+
328
+ # Create data frame
329
+ ts_data <- data.frame(
330
+ time = time_index,
331
+ value = values
332
+ )
333
+
334
+ # Create plot
335
+ p <- ggplot(ts_data, aes(x = time, y = value)) +
336
+ geom_line(color = "steelblue", size = 1) +
337
+ geom_point(alpha = 0.6, size = 1.5)
338
+
339
+ if (show_trend) {
340
+ p <- p + geom_smooth(method = "loess", se = TRUE, color = "red", alpha = 0.3)
341
+ }
342
+
343
+ p <- p + labs(title = title, x = x_label, y = "Value") +
344
+ theme_minimal() +
345
+ theme(plot.title = element_text(hjust = 0.5))
346
+
347
+ # Save plot
348
+ ggsave(file_path, plot = p, width = width/100, height = height/100, dpi = 100)
349
+
350
+ # Basic time series statistics
351
+ ts_stats <- list(
352
+ mean = mean(values, na.rm = TRUE),
353
+ sd = sd(values, na.rm = TRUE),
354
+ min = min(values, na.rm = TRUE),
355
+ max = max(values, na.rm = TRUE),
356
+ n_obs = length(values[!is.na(values)]),
357
+ range = max(values, na.rm = TRUE) - min(values, na.rm = TRUE)
358
+ )
359
+
360
+ result <- list(
361
+ file_path = file_path,
362
+ title = title,
363
+ statistics = ts_stats,
364
+ has_dates = !is.null(dates),
365
+ show_trend = show_trend,
366
+ plot_saved = file.exists(file_path)
367
+ )
368
+ '''
369
+
370
+ try:
371
+ result = execute_r_script(r_script, params)
372
+ await context.info("Time series plot created successfully")
373
+ return result
374
+
375
+ except Exception as e:
376
+ await context.error("Time series plot creation failed", error=str(e))
377
+ raise
378
+
379
+
380
+ @tool(
381
+ name="correlation_heatmap",
382
+ input_schema={
383
+ "type": "object",
384
+ "properties": {
385
+ "data": table_schema(),
386
+ "variables": {"type": "array", "items": {"type": "string"}},
387
+ "method": {"type": "string", "enum": ["pearson", "spearman", "kendall"], "default": "pearson"},
388
+ "title": {"type": "string"},
389
+ "file_path": {"type": "string"},
390
+ "width": {"type": "integer", "minimum": 100, "default": 800},
391
+ "height": {"type": "integer", "minimum": 100, "default": 800}
392
+ },
393
+ "required": ["data", "file_path"]
394
+ },
395
+ description="Create correlation heatmap matrix"
396
+ )
397
+ async def correlation_heatmap(context, params):
398
+ """Create correlation heatmap."""
399
+
400
+ await context.info("Creating correlation heatmap")
401
+
402
+ r_script = '''
403
+ if (!require(ggplot2)) install.packages("ggplot2", quietly = TRUE)
404
+ if (!require(reshape2)) install.packages("reshape2", quietly = TRUE)
405
+ library(ggplot2)
406
+ library(reshape2)
407
+
408
+ data <- as.data.frame(args$data)
409
+ variables <- args$variables
410
+ method <- args$method %||% "pearson"
411
+ title <- args$title %||% paste("Correlation Heatmap (", method, ")")
412
+ file_path <- args$file_path
413
+ width <- args$width %||% 800
414
+ height <- args$height %||% 800
415
+
416
+ # Select variables
417
+ if (is.null(variables)) {
418
+ numeric_vars <- names(data)[sapply(data, is.numeric)]
419
+ if (length(numeric_vars) == 0) {
420
+ stop("No numeric variables found")
421
+ }
422
+ variables <- numeric_vars
423
+ }
424
+
425
+ # Calculate correlation matrix
426
+ cor_data <- data[, variables, drop = FALSE]
427
+ cor_matrix <- cor(cor_data, use = "complete.obs", method = method)
428
+
429
+ # Melt for ggplot
430
+ cor_melted <- melt(cor_matrix)
431
+ colnames(cor_melted) <- c("Var1", "Var2", "value")
432
+
433
+ # Create heatmap
434
+ p <- ggplot(cor_melted, aes(Var1, Var2, fill = value)) +
435
+ geom_tile(color = "white") +
436
+ scale_fill_gradient2(low = "blue", high = "red", mid = "white",
437
+ midpoint = 0, limit = c(-1, 1), space = "Lab",
438
+ name = "Correlation") +
439
+ theme_minimal() +
440
+ theme(axis.text.x = element_text(angle = 45, vjust = 1, size = 12, hjust = 1),
441
+ plot.title = element_text(hjust = 0.5)) +
442
+ coord_fixed() +
443
+ labs(title = title, x = "", y = "") +
444
+ geom_text(aes(label = round(value, 2)), color = "black", size = 3)
445
+
446
+ # Save plot
447
+ ggsave(file_path, plot = p, width = width/100, height = height/100, dpi = 100)
448
+
449
+ result <- list(
450
+ file_path = file_path,
451
+ correlation_matrix = as.matrix(cor_matrix),
452
+ variables = variables,
453
+ method = method,
454
+ title = title,
455
+ n_variables = length(variables),
456
+ plot_saved = file.exists(file_path)
457
+ )
458
+ '''
459
+
460
+ try:
461
+ result = execute_r_script(r_script, params)
462
+ await context.info("Correlation heatmap created successfully")
463
+ return result
464
+
465
+ except Exception as e:
466
+ await context.error("Correlation heatmap creation failed", error=str(e))
467
+ raise
468
+
469
+
470
+ @tool(
471
+ name="regression_plot",
472
+ input_schema={
473
+ "type": "object",
474
+ "properties": {
475
+ "data": table_schema(),
476
+ "formula": formula_schema(),
477
+ "title": {"type": "string"},
478
+ "file_path": {"type": "string"},
479
+ "residual_plots": {"type": "boolean", "default": True},
480
+ "width": {"type": "integer", "minimum": 100, "default": 1200},
481
+ "height": {"type": "integer", "minimum": 100, "default": 800}
482
+ },
483
+ "required": ["data", "formula", "file_path"]
484
+ },
485
+ description="Create regression diagnostic plots (fitted vs residuals, Q-Q plot, etc.)"
486
+ )
487
+ async def regression_plot(context, params):
488
+ """Create regression diagnostic plots."""
489
+
490
+ await context.info("Creating regression plots")
491
+
492
+ r_script = '''
493
+ if (!require(ggplot2)) install.packages("ggplot2", quietly = TRUE)
494
+ if (!require(gridExtra)) install.packages("gridExtra", quietly = TRUE)
495
+ library(ggplot2)
496
+ library(gridExtra)
497
+
498
+ data <- as.data.frame(args$data)
499
+ formula <- as.formula(args$formula)
500
+ title <- args$title %||% "Regression Diagnostics"
501
+ file_path <- args$file_path
502
+ residual_plots <- args$residual_plots %||% TRUE
503
+ width <- args$width %||% 1200
504
+ height <- args$height %||% 800
505
+
506
+ # Fit model
507
+ model <- lm(formula, data = data)
508
+
509
+ # Extract model data
510
+ fitted_values <- fitted(model)
511
+ residuals <- residuals(model)
512
+ standardized_residuals <- rstandard(model)
513
+
514
+ if (residual_plots) {
515
+ # Create diagnostic plots
516
+ p1 <- ggplot(data.frame(fitted = fitted_values, residuals = residuals),
517
+ aes(x = fitted, y = residuals)) +
518
+ geom_point(alpha = 0.6) +
519
+ geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
520
+ geom_smooth(se = FALSE, color = "blue") +
521
+ labs(title = "Residuals vs Fitted", x = "Fitted Values", y = "Residuals") +
522
+ theme_minimal()
523
+
524
+ p2 <- ggplot(data.frame(residuals = standardized_residuals), aes(sample = residuals)) +
525
+ stat_qq() + stat_qq_line(color = "red") +
526
+ labs(title = "Q-Q Plot", x = "Theoretical Quantiles", y = "Sample Quantiles") +
527
+ theme_minimal()
528
+
529
+ p3 <- ggplot(data.frame(fitted = fitted_values, sqrt_abs_residuals = sqrt(abs(standardized_residuals))),
530
+ aes(x = fitted, y = sqrt_abs_residuals)) +
531
+ geom_point(alpha = 0.6) +
532
+ geom_smooth(se = FALSE, color = "red") +
533
+ labs(title = "Scale-Location", x = "Fitted Values", y = "√|Standardized Residuals|") +
534
+ theme_minimal()
535
+
536
+ p4 <- ggplot(data.frame(leverage = hatvalues(model), std_residuals = standardized_residuals),
537
+ aes(x = leverage, y = std_residuals)) +
538
+ geom_point(alpha = 0.6) +
539
+ geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
540
+ geom_smooth(se = FALSE, color = "blue") +
541
+ labs(title = "Residuals vs Leverage", x = "Leverage", y = "Standardized Residuals") +
542
+ theme_minimal()
543
+
544
+ # Combine plots
545
+ combined_plot <- grid.arrange(p1, p2, p3, p4, ncol = 2,
546
+ top = textGrob(title, gp = gpar(fontsize = 16, font = 2)))
547
+
548
+ # Save combined plot
549
+ ggsave(file_path, plot = combined_plot, width = width/100, height = height/100, dpi = 100)
550
+
551
+ } else {
552
+ # Simple fitted vs actual plot
553
+ response_var <- all.vars(formula)[1]
554
+ actual_values <- data[[response_var]]
555
+
556
+ p <- ggplot(data.frame(actual = actual_values, fitted = fitted_values),
557
+ aes(x = actual, y = fitted)) +
558
+ geom_point(alpha = 0.6) +
559
+ geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
560
+ labs(title = title, x = "Actual Values", y = "Fitted Values") +
561
+ theme_minimal() +
562
+ theme(plot.title = element_text(hjust = 0.5))
563
+
564
+ ggsave(file_path, plot = p, width = width/100, height = height/100, dpi = 100)
565
+ }
566
+
567
+ # Model summary
568
+ model_summary <- summary(model)
569
+
570
+ result <- list(
571
+ file_path = file_path,
572
+ title = title,
573
+ r_squared = model_summary$r.squared,
574
+ adj_r_squared = model_summary$adj.r.squared,
575
+ residual_se = model_summary$sigma,
576
+ formula = deparse(formula),
577
+ residual_plots = residual_plots,
578
+ n_obs = nobs(model),
579
+ plot_saved = file.exists(file_path)
580
+ )
581
+ '''
582
+
583
+ try:
584
+ result = execute_r_script(r_script, params)
585
+ await context.info("Regression plots created successfully")
586
+ return result
587
+
588
+ except Exception as e:
589
+ await context.error("Regression plot creation failed", error=str(e))
590
+ raise
@@ -0,0 +1,16 @@
1
+ """
2
+ Transport layer for MCP server.
3
+
4
+ Implements transport-agnostic message handling:
5
+ - stdio transport (JSON-RPC over stdin/stdout)
6
+ - Optional HTTP transport with streaming support
7
+ - Clean separation from business logic
8
+
9
+ Following the principle: "Business logic never cares whether messages arrive over stdio or HTTP."
10
+ """
11
+
12
+ from .stdio import StdioTransport
13
+ from .jsonrpc import JSONRPCEnvelope, JSONRPCError
14
+ from .base import Transport
15
+
16
+ __all__ = ["StdioTransport", "JSONRPCEnvelope", "JSONRPCError", "Transport"]