@mseep/csv-editor 1.0.0

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.
Files changed (106) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +38 -0
  3. package/.github/workflows/deploy-docs.yml +62 -0
  4. package/.github/workflows/publish-github.yml +52 -0
  5. package/.github/workflows/publish.yml +44 -0
  6. package/.github/workflows/test.yml +32 -0
  7. package/.pre-commit-config.yaml +157 -0
  8. package/ALTERNATIVE_PUBLISHING.md +175 -0
  9. package/ARCHITECTURE.md +1011 -0
  10. package/CHANGELOG.md +99 -0
  11. package/CODE_OF_CONDUCT.md +41 -0
  12. package/CONTRIBUTING.md +427 -0
  13. package/Dockerfile +22 -0
  14. package/LICENSE +21 -0
  15. package/MCP_CONFIG.md +505 -0
  16. package/PUBLISHING.md +210 -0
  17. package/README.md +400 -0
  18. package/SECURITY.md +61 -0
  19. package/docs/README.md +41 -0
  20. package/docs/blog/2019-05-28-first-blog-post.md +12 -0
  21. package/docs/blog/2019-05-29-long-blog-post.md +44 -0
  22. package/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
  23. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  24. package/docs/blog/2021-08-26-welcome/index.md +29 -0
  25. package/docs/blog/authors.yml +25 -0
  26. package/docs/blog/tags.yml +19 -0
  27. package/docs/docs/api/overview.md +183 -0
  28. package/docs/docs/installation.md +252 -0
  29. package/docs/docs/intro.md +87 -0
  30. package/docs/docs/tutorial-basics/_category_.json +8 -0
  31. package/docs/docs/tutorial-basics/congratulations.md +23 -0
  32. package/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
  33. package/docs/docs/tutorial-basics/create-a-document.md +57 -0
  34. package/docs/docs/tutorial-basics/create-a-page.md +43 -0
  35. package/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
  36. package/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
  37. package/docs/docs/tutorial-extras/_category_.json +7 -0
  38. package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
  39. package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
  40. package/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
  41. package/docs/docs/tutorial-extras/translate-your-site.md +88 -0
  42. package/docs/docs/tutorials/quickstart.md +365 -0
  43. package/docs/docusaurus.config.ts +163 -0
  44. package/docs/package-lock.json +17493 -0
  45. package/docs/package.json +48 -0
  46. package/docs/sidebars.ts +33 -0
  47. package/docs/src/components/HomepageFeatures/index.tsx +71 -0
  48. package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  49. package/docs/src/css/custom.css +30 -0
  50. package/docs/src/pages/index.module.css +23 -0
  51. package/docs/src/pages/index.tsx +44 -0
  52. package/docs/src/pages/markdown-page.md +7 -0
  53. package/docs/static/.nojekyll +0 -0
  54. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  55. package/docs/static/img/docusaurus.png +0 -0
  56. package/docs/static/img/favicon.ico +0 -0
  57. package/docs/static/img/logo.svg +1 -0
  58. package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  59. package/docs/static/img/undraw_docusaurus_react.svg +170 -0
  60. package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  61. package/docs/tsconfig.json +8 -0
  62. package/examples/README.md +48 -0
  63. package/examples/auto_save_demo.py +206 -0
  64. package/examples/auto_save_overwrite.py +201 -0
  65. package/examples/basic_usage.py +135 -0
  66. package/examples/demo.py +139 -0
  67. package/examples/history_demo.py +317 -0
  68. package/examples/test_default_autosave.py +124 -0
  69. package/examples/update_consignee_example.py +179 -0
  70. package/package.json +51 -0
  71. package/plans/2026-04-19-fastmcp3-migration-plan.md +1045 -0
  72. package/pyproject.toml +331 -0
  73. package/requirements-dev.txt +30 -0
  74. package/requirements.txt +22 -0
  75. package/scripts/publish.py +67 -0
  76. package/smithery.yaml +15 -0
  77. package/specs/2026-04-19-fastmcp3-migration-design.md +243 -0
  78. package/src/csv_editor/__init__.py +8 -0
  79. package/src/csv_editor/models/__init__.py +39 -0
  80. package/src/csv_editor/models/auto_save.py +246 -0
  81. package/src/csv_editor/models/csv_session.py +468 -0
  82. package/src/csv_editor/models/data_models.py +244 -0
  83. package/src/csv_editor/models/history_manager.py +456 -0
  84. package/src/csv_editor/prompts/__init__.py +0 -0
  85. package/src/csv_editor/prompts/data_prompts.py +13 -0
  86. package/src/csv_editor/resources/__init__.py +0 -0
  87. package/src/csv_editor/resources/csv_resources.py +22 -0
  88. package/src/csv_editor/server.py +640 -0
  89. package/src/csv_editor/tools/__init__.py +5 -0
  90. package/src/csv_editor/tools/analytics.py +700 -0
  91. package/src/csv_editor/tools/auto_save_operations.py +235 -0
  92. package/src/csv_editor/tools/data_operations.py +3 -0
  93. package/src/csv_editor/tools/history_operations.py +315 -0
  94. package/src/csv_editor/tools/io_operations.py +431 -0
  95. package/src/csv_editor/tools/transformations.py +663 -0
  96. package/src/csv_editor/tools/validation.py +822 -0
  97. package/src/csv_editor/utils/__init__.py +0 -0
  98. package/src/csv_editor/utils/validators.py +205 -0
  99. package/tests/README.md +65 -0
  100. package/tests/__init__.py +7 -0
  101. package/tests/conftest.py +50 -0
  102. package/tests/test_auto_save.py +378 -0
  103. package/tests/test_basic.py +103 -0
  104. package/tests/test_integration.py +356 -0
  105. package/tests/test_server_boot.py +50 -0
  106. package/tests/test_settings.py +184 -0
@@ -0,0 +1,13 @@
1
+ """Prompt templates - placeholder implementation."""
2
+
3
+
4
+ def analyze_csv_prompt(session_id: str, analysis_type: str) -> str:
5
+ return f"Analyze CSV data in session {session_id} for {analysis_type}"
6
+
7
+
8
+ def suggest_transformations_prompt(session_id: str, goal: str) -> str:
9
+ return f"Suggest transformations for session {session_id} to achieve: {goal}"
10
+
11
+
12
+ def data_cleaning_prompt(session_id: str, issues: list[str]) -> str:
13
+ return f"Suggest cleaning for session {session_id} with issues: {', '.join(issues)}"
File without changes
@@ -0,0 +1,22 @@
1
+ """Resource definitions - placeholder implementation."""
2
+
3
+ from typing import Any
4
+
5
+ from fastmcp import Context
6
+
7
+
8
+ # Placeholder resources - will implement next
9
+ async def get_csv_data(session_id: str, ctx: Context) -> dict[str, Any]:
10
+ return {"session_id": session_id, "data": "Not yet implemented"}
11
+
12
+
13
+ async def get_csv_schema(session_id: str, ctx: Context) -> dict[str, Any]:
14
+ return {"session_id": session_id, "schema": "Not yet implemented"}
15
+
16
+
17
+ async def get_csv_preview(session_id: str, ctx: Context) -> dict[str, Any]:
18
+ return {"session_id": session_id, "preview": "Not yet implemented"}
19
+
20
+
21
+ async def list_active_sessions(ctx: Context) -> list[dict[str, Any]]:
22
+ return []
@@ -0,0 +1,640 @@
1
+ """Main FastMCP server for CSV Editor."""
2
+
3
+ import logging
4
+ import os
5
+ from typing import Any
6
+
7
+ from fastmcp import Context, FastMCP
8
+
9
+ # Configure logging
10
+ logging.basicConfig(
11
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
12
+ )
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Initialize FastMCP server
16
+ mcp = FastMCP("CSV Editor")
17
+
18
+ # Import our models
19
+ from .models import get_session_manager
20
+
21
+ # ============================================================================
22
+ # HEALTH AND INFO TOOLS
23
+ # ============================================================================
24
+
25
+
26
+ @mcp.tool
27
+ async def health_check(ctx: Context) -> dict[str, Any]:
28
+ """Check the health status of the CSV Editor."""
29
+ session_manager = get_session_manager()
30
+
31
+ try:
32
+ active_sessions = len(session_manager.sessions)
33
+
34
+ if ctx:
35
+ await ctx.info("Health check performed successfully")
36
+
37
+ return {
38
+ "success": True,
39
+ "status": "healthy",
40
+ "version": "2.0.0",
41
+ "active_sessions": active_sessions,
42
+ "max_sessions": session_manager.max_sessions,
43
+ "session_ttl_minutes": session_manager.ttl_minutes,
44
+ }
45
+ except Exception as e:
46
+ if ctx:
47
+ await ctx.error(f"Health check failed: {e!s}")
48
+ return {"success": False, "status": "error", "error": str(e)}
49
+
50
+
51
+ @mcp.tool
52
+ async def get_server_info(ctx: Context) -> dict[str, Any]:
53
+ """Get information about the CSV Editor capabilities."""
54
+ if ctx:
55
+ await ctx.info("Server information requested")
56
+
57
+ return {
58
+ "name": "CSV Editor",
59
+ "version": "2.0.0",
60
+ "description": "A comprehensive MCP server for CSV file operations and data analysis",
61
+ "capabilities": {
62
+ "data_io": [
63
+ "load_csv",
64
+ "load_csv_from_url",
65
+ "load_csv_from_content",
66
+ "export_csv",
67
+ "multiple_export_formats",
68
+ ],
69
+ "data_manipulation": [
70
+ "filter_rows",
71
+ "sort_data",
72
+ "select_columns",
73
+ "rename_columns",
74
+ "add_column",
75
+ "remove_columns",
76
+ "change_column_type",
77
+ "fill_missing_values",
78
+ "remove_duplicates",
79
+ ],
80
+ "data_analysis": [
81
+ "get_statistics",
82
+ "correlation_matrix",
83
+ "group_by_aggregate",
84
+ "value_counts",
85
+ "detect_outliers",
86
+ "profile_data",
87
+ ],
88
+ "data_validation": ["validate_schema", "check_data_quality", "find_anomalies"],
89
+ "session_management": ["multi_session_support", "session_isolation", "auto_cleanup"],
90
+ },
91
+ "supported_formats": ["csv", "tsv", "json", "excel", "parquet", "html", "markdown"],
92
+ "max_file_size_mb": int(os.getenv("CSV_MAX_FILE_SIZE", "1024")),
93
+ "session_timeout_minutes": int(os.getenv("CSV_SESSION_TIMEOUT", "60")),
94
+ }
95
+
96
+
97
+ # ============================================================================
98
+ # DATA I/O TOOLS
99
+ # ============================================================================
100
+
101
+ from .tools.io_operations import close_session as _close_session
102
+ from .tools.io_operations import export_csv as _export_csv
103
+ from .tools.io_operations import get_session_info as _get_session_info
104
+ from .tools.io_operations import list_sessions as _list_sessions
105
+ from .tools.io_operations import load_csv as _load_csv
106
+ from .tools.io_operations import load_csv_from_content as _load_csv_from_content
107
+ from .tools.io_operations import load_csv_from_url as _load_csv_from_url
108
+
109
+
110
+ # Register I/O tools with decorators
111
+ @mcp.tool
112
+ async def load_csv(
113
+ file_path: str,
114
+ encoding: str = "utf-8",
115
+ delimiter: str = ",",
116
+ session_id: str | None = None,
117
+ ctx: Context = None,
118
+ ) -> dict[str, Any]:
119
+ """Load a CSV file into a session."""
120
+ return await _load_csv(file_path, encoding, delimiter, session_id, ctx=ctx)
121
+
122
+
123
+ @mcp.tool
124
+ async def load_csv_from_url(
125
+ url: str,
126
+ encoding: str = "utf-8",
127
+ delimiter: str = ",",
128
+ session_id: str | None = None,
129
+ ctx: Context = None,
130
+ ) -> dict[str, Any]:
131
+ """Load a CSV file from a URL."""
132
+ return await _load_csv_from_url(url, encoding, delimiter, session_id, ctx)
133
+
134
+
135
+ @mcp.tool
136
+ async def load_csv_from_content(
137
+ content: str,
138
+ delimiter: str = ",",
139
+ session_id: str | None = None,
140
+ has_header: bool = True,
141
+ ctx: Context = None,
142
+ ) -> dict[str, Any]:
143
+ """Load CSV data from string content."""
144
+ return await _load_csv_from_content(content, delimiter, session_id, has_header, ctx)
145
+
146
+
147
+ @mcp.tool
148
+ async def export_csv(
149
+ session_id: str,
150
+ file_path: str | None = None,
151
+ format: str = "csv",
152
+ encoding: str = "utf-8",
153
+ index: bool = False,
154
+ ctx: Context = None,
155
+ ) -> dict[str, Any]:
156
+ """Export session data to various formats."""
157
+ from .models import ExportFormat
158
+
159
+ format_enum = ExportFormat(format)
160
+ return await _export_csv(session_id, file_path, format_enum, encoding, index, ctx)
161
+
162
+
163
+ @mcp.tool
164
+ async def get_session_info(session_id: str, ctx: Context = None) -> dict[str, Any]:
165
+ """Get information about a specific session."""
166
+ return await _get_session_info(session_id, ctx)
167
+
168
+
169
+ @mcp.tool
170
+ async def list_sessions(ctx: Context = None) -> dict[str, Any]:
171
+ """List all active sessions."""
172
+ return await _list_sessions(ctx)
173
+
174
+
175
+ @mcp.tool
176
+ async def close_session(session_id: str, ctx: Context = None) -> dict[str, Any]:
177
+ """Close and clean up a session."""
178
+ return await _close_session(session_id, ctx)
179
+
180
+
181
+ # ============================================================================
182
+ # DATA TRANSFORMATION TOOLS
183
+ # ============================================================================
184
+
185
+ from .tools.transformations import add_column as _add_column
186
+ from .tools.transformations import change_column_type as _change_column_type
187
+ from .tools.transformations import fill_missing_values as _fill_missing_values
188
+ from .tools.transformations import filter_rows as _filter_rows
189
+ from .tools.transformations import remove_columns as _remove_columns
190
+ from .tools.transformations import remove_duplicates as _remove_duplicates
191
+ from .tools.transformations import rename_columns as _rename_columns
192
+ from .tools.transformations import select_columns as _select_columns
193
+ from .tools.transformations import sort_data as _sort_data
194
+ from .tools.transformations import update_column as _update_column
195
+
196
+
197
+ @mcp.tool
198
+ async def filter_rows(
199
+ session_id: str, conditions: list[dict[str, Any]], mode: str = "and", ctx: Context = None
200
+ ) -> dict[str, Any]:
201
+ """Filter rows based on conditions."""
202
+ return await _filter_rows(session_id, conditions, mode, ctx)
203
+
204
+
205
+ @mcp.tool
206
+ async def sort_data(session_id: str, columns: list[Any], ctx: Context = None) -> dict[str, Any]:
207
+ """Sort data by columns."""
208
+ return await _sort_data(session_id, columns, ctx)
209
+
210
+
211
+ @mcp.tool
212
+ async def select_columns(
213
+ session_id: str, columns: list[str], ctx: Context = None
214
+ ) -> dict[str, Any]:
215
+ """Select specific columns from the dataframe."""
216
+ return await _select_columns(session_id, columns, ctx)
217
+
218
+
219
+ @mcp.tool
220
+ async def rename_columns(
221
+ session_id: str, mapping: dict[str, str], ctx: Context = None
222
+ ) -> dict[str, Any]:
223
+ """Rename columns in the dataframe."""
224
+ return await _rename_columns(session_id, mapping, ctx)
225
+
226
+
227
+ @mcp.tool
228
+ async def add_column(
229
+ session_id: str, name: str, value: Any = None, formula: str | None = None, ctx: Context = None
230
+ ) -> dict[str, Any]:
231
+ """Add a new column to the dataframe."""
232
+ return await _add_column(session_id, name, value, formula, ctx)
233
+
234
+
235
+ @mcp.tool
236
+ async def remove_columns(
237
+ session_id: str, columns: list[str], ctx: Context = None
238
+ ) -> dict[str, Any]:
239
+ """Remove columns from the dataframe."""
240
+ return await _remove_columns(session_id, columns, ctx)
241
+
242
+
243
+ @mcp.tool
244
+ async def change_column_type(
245
+ session_id: str, column: str, dtype: str, errors: str = "coerce", ctx: Context = None
246
+ ) -> dict[str, Any]:
247
+ """Change the data type of a column."""
248
+ return await _change_column_type(session_id, column, dtype, errors, ctx)
249
+
250
+
251
+ @mcp.tool
252
+ async def fill_missing_values(
253
+ session_id: str,
254
+ strategy: str = "drop",
255
+ value: Any = None,
256
+ columns: list[str] | None = None,
257
+ ctx: Context = None,
258
+ ) -> dict[str, Any]:
259
+ """Fill or remove missing values."""
260
+ return await _fill_missing_values(session_id, strategy, value, columns, ctx)
261
+
262
+
263
+ @mcp.tool
264
+ async def remove_duplicates(
265
+ session_id: str, subset: list[str] | None = None, keep: str = "first", ctx: Context = None
266
+ ) -> dict[str, Any]:
267
+ """Remove duplicate rows."""
268
+ return await _remove_duplicates(session_id, subset, keep, ctx)
269
+
270
+
271
+ @mcp.tool
272
+ async def update_column(
273
+ session_id: str,
274
+ column: str,
275
+ operation: str,
276
+ value: Any | None = None,
277
+ pattern: str | None = None,
278
+ replacement: str | None = None,
279
+ ctx: Context = None,
280
+ ) -> dict[str, Any]:
281
+ """Update values in a specific column with simple operations like replace, extract, split, etc."""
282
+ return await _update_column(session_id, column, operation, value, pattern, replacement, ctx)
283
+
284
+
285
+ # ============================================================================
286
+ # DATA ANALYTICS TOOLS
287
+ # ============================================================================
288
+
289
+ from .tools.analytics import detect_outliers as _detect_outliers
290
+ from .tools.analytics import get_column_statistics as _get_column_statistics
291
+ from .tools.analytics import get_correlation_matrix as _get_correlation_matrix
292
+ from .tools.analytics import get_statistics as _get_statistics
293
+ from .tools.analytics import get_value_counts as _get_value_counts
294
+ from .tools.analytics import group_by_aggregate as _group_by_aggregate
295
+ from .tools.analytics import profile_data as _profile_data
296
+
297
+
298
+ @mcp.tool
299
+ async def get_statistics(
300
+ session_id: str,
301
+ columns: list[str] | None = None,
302
+ include_percentiles: bool = True,
303
+ ctx: Context = None,
304
+ ) -> dict[str, Any]:
305
+ """Get statistical summary of numerical columns."""
306
+ return await _get_statistics(session_id, columns, include_percentiles, ctx)
307
+
308
+
309
+ @mcp.tool
310
+ async def get_column_statistics(
311
+ session_id: str, column: str, ctx: Context = None
312
+ ) -> dict[str, Any]:
313
+ """Get detailed statistics for a specific column."""
314
+ return await _get_column_statistics(session_id, column, ctx)
315
+
316
+
317
+ @mcp.tool
318
+ async def get_correlation_matrix(
319
+ session_id: str,
320
+ method: str = "pearson",
321
+ columns: list[str] | None = None,
322
+ min_correlation: float | None = None,
323
+ ctx: Context = None,
324
+ ) -> dict[str, Any]:
325
+ """Calculate correlation matrix for numeric columns."""
326
+ return await _get_correlation_matrix(session_id, method, columns, min_correlation, ctx)
327
+
328
+
329
+ @mcp.tool
330
+ async def group_by_aggregate(
331
+ session_id: str, group_by: list[str], aggregations: dict[str, Any], ctx: Context = None
332
+ ) -> dict[str, Any]:
333
+ """Group data and apply aggregation functions."""
334
+ return await _group_by_aggregate(session_id, group_by, aggregations, ctx)
335
+
336
+
337
+ @mcp.tool
338
+ async def get_value_counts(
339
+ session_id: str,
340
+ column: str,
341
+ normalize: bool = False,
342
+ sort: bool = True,
343
+ ascending: bool = False,
344
+ top_n: int | None = None,
345
+ ctx: Context = None,
346
+ ) -> dict[str, Any]:
347
+ """Get value counts for a column."""
348
+ return await _get_value_counts(session_id, column, normalize, sort, ascending, top_n, ctx)
349
+
350
+
351
+ @mcp.tool
352
+ async def detect_outliers(
353
+ session_id: str,
354
+ columns: list[str] | None = None,
355
+ method: str = "iqr",
356
+ threshold: float = 1.5,
357
+ ctx: Context = None,
358
+ ) -> dict[str, Any]:
359
+ """Detect outliers in numeric columns."""
360
+ return await _detect_outliers(session_id, columns, method, threshold, ctx)
361
+
362
+
363
+ @mcp.tool
364
+ async def profile_data(
365
+ session_id: str,
366
+ include_correlations: bool = True,
367
+ include_outliers: bool = True,
368
+ ctx: Context = None,
369
+ ) -> dict[str, Any]:
370
+ """Generate comprehensive data profile."""
371
+ return await _profile_data(session_id, include_correlations, include_outliers, ctx)
372
+
373
+
374
+ # ============================================================================
375
+ # DATA VALIDATION TOOLS
376
+ # ============================================================================
377
+
378
+ from .tools.validation import check_data_quality as _check_data_quality
379
+ from .tools.validation import find_anomalies as _find_anomalies
380
+ from .tools.validation import validate_schema as _validate_schema
381
+
382
+
383
+ @mcp.tool
384
+ async def validate_schema(
385
+ session_id: str, schema: dict[str, dict[str, Any]], ctx: Context = None
386
+ ) -> dict[str, Any]:
387
+ """Validate data against a schema definition."""
388
+ return await _validate_schema(session_id, schema, ctx)
389
+
390
+
391
+ @mcp.tool
392
+ async def check_data_quality(
393
+ session_id: str, rules: list[dict[str, Any]] | None = None, ctx: Context = None
394
+ ) -> dict[str, Any]:
395
+ """Check data quality based on predefined or custom rules."""
396
+ return await _check_data_quality(session_id, rules, ctx)
397
+
398
+
399
+ @mcp.tool
400
+ async def find_anomalies(
401
+ session_id: str,
402
+ columns: list[str] | None = None,
403
+ sensitivity: float = 0.95,
404
+ methods: list[str] | None = None,
405
+ ctx: Context = None,
406
+ ) -> dict[str, Any]:
407
+ """Find anomalies in the data using multiple detection methods."""
408
+ return await _find_anomalies(session_id, columns, sensitivity, methods, ctx)
409
+
410
+
411
+ # ============================================================================
412
+ # AUTO-SAVE TOOLS
413
+ # ============================================================================
414
+
415
+ from .tools.auto_save_operations import configure_auto_save as _configure_auto_save
416
+ from .tools.auto_save_operations import disable_auto_save as _disable_auto_save
417
+ from .tools.auto_save_operations import get_auto_save_status as _get_auto_save_status
418
+ from .tools.auto_save_operations import trigger_manual_save as _trigger_manual_save
419
+
420
+
421
+ @mcp.tool
422
+ async def configure_auto_save(
423
+ session_id: str,
424
+ enabled: bool = True,
425
+ mode: str = "after_operation",
426
+ strategy: str = "backup",
427
+ interval_seconds: int | None = None,
428
+ max_backups: int | None = None,
429
+ backup_dir: str | None = None,
430
+ custom_path: str | None = None,
431
+ format: str = "csv",
432
+ encoding: str = "utf-8",
433
+ ctx: Context = None,
434
+ ) -> dict[str, Any]:
435
+ """Configure auto-save settings for a session."""
436
+ return await _configure_auto_save(
437
+ session_id,
438
+ enabled,
439
+ mode,
440
+ strategy,
441
+ interval_seconds,
442
+ max_backups,
443
+ backup_dir,
444
+ custom_path,
445
+ format,
446
+ encoding,
447
+ ctx,
448
+ )
449
+
450
+
451
+ @mcp.tool
452
+ async def disable_auto_save(session_id: str, ctx: Context = None) -> dict[str, Any]:
453
+ """Disable auto-save for a session."""
454
+ return await _disable_auto_save(session_id, ctx)
455
+
456
+
457
+ @mcp.tool
458
+ async def get_auto_save_status(session_id: str, ctx: Context = None) -> dict[str, Any]:
459
+ """Get auto-save status for a session."""
460
+ return await _get_auto_save_status(session_id, ctx)
461
+
462
+
463
+ @mcp.tool
464
+ async def trigger_manual_save(session_id: str, ctx: Context = None) -> dict[str, Any]:
465
+ """Manually trigger a save for a session."""
466
+ return await _trigger_manual_save(session_id, ctx)
467
+
468
+
469
+ # ============================================================================
470
+ # HISTORY OPERATIONS
471
+ # ============================================================================
472
+
473
+ from .tools.history_operations import clear_history as _clear_history
474
+ from .tools.history_operations import export_history as _export_history
475
+ from .tools.history_operations import get_operation_history as _get_operation_history
476
+ from .tools.history_operations import redo_operation as _redo_operation
477
+ from .tools.history_operations import restore_to_operation as _restore_to_operation
478
+ from .tools.history_operations import undo_operation as _undo_operation
479
+
480
+
481
+ @mcp.tool
482
+ async def undo(session_id: str, ctx: Context = None) -> dict[str, Any]:
483
+ """Undo the last operation in a session."""
484
+ return await _undo_operation(session_id, ctx)
485
+
486
+
487
+ @mcp.tool
488
+ async def redo(session_id: str, ctx: Context = None) -> dict[str, Any]:
489
+ """Redo a previously undone operation."""
490
+ return await _redo_operation(session_id, ctx)
491
+
492
+
493
+ @mcp.tool
494
+ async def get_history(
495
+ session_id: str, limit: int | None = None, ctx: Context = None
496
+ ) -> dict[str, Any]:
497
+ """Get operation history for a session."""
498
+ return await _get_operation_history(session_id, limit, ctx)
499
+
500
+
501
+ @mcp.tool
502
+ async def restore_to_operation(
503
+ session_id: str, operation_id: str, ctx: Context = None
504
+ ) -> dict[str, Any]:
505
+ """Restore session data to a specific operation point."""
506
+ return await _restore_to_operation(session_id, operation_id, ctx)
507
+
508
+
509
+ @mcp.tool
510
+ async def clear_history(session_id: str, ctx: Context = None) -> dict[str, Any]:
511
+ """Clear all operation history for a session."""
512
+ return await _clear_history(session_id, ctx)
513
+
514
+
515
+ @mcp.tool
516
+ async def export_history(
517
+ session_id: str, file_path: str, format: str = "json", ctx: Context = None
518
+ ) -> dict[str, Any]:
519
+ """Export operation history to a file."""
520
+ return await _export_history(session_id, file_path, format, ctx)
521
+
522
+
523
+ # ============================================================================
524
+ # RESOURCES
525
+ # ============================================================================
526
+
527
+
528
+ @mcp.resource("csv://{session_id}/data")
529
+ async def get_csv_data(session_id: str) -> dict[str, Any]:
530
+ """Get current CSV data from a session."""
531
+ session_manager = get_session_manager()
532
+ session = session_manager.get_session(session_id)
533
+
534
+ if not session or session.df is None:
535
+ return {"error": "Session not found or no data loaded"}
536
+
537
+ return {
538
+ "session_id": session_id,
539
+ "data": session.df.to_dict("records"),
540
+ "shape": session.df.shape,
541
+ }
542
+
543
+
544
+ @mcp.resource("csv://{session_id}/schema")
545
+ async def get_csv_schema(session_id: str) -> dict[str, Any]:
546
+ """Get CSV schema information."""
547
+ session_manager = get_session_manager()
548
+ session = session_manager.get_session(session_id)
549
+
550
+ if not session or session.df is None:
551
+ return {"error": "Session not found or no data loaded"}
552
+
553
+ return {
554
+ "session_id": session_id,
555
+ "columns": session.df.columns.tolist(),
556
+ "dtypes": {col: str(dtype) for col, dtype in session.df.dtypes.items()},
557
+ "shape": session.df.shape,
558
+ }
559
+
560
+
561
+ @mcp.resource("sessions://active")
562
+ async def list_active_sessions() -> list[dict[str, Any]]:
563
+ """List all active CSV sessions."""
564
+ session_manager = get_session_manager()
565
+ sessions = session_manager.list_sessions()
566
+ return [s.dict() for s in sessions]
567
+
568
+
569
+ # ============================================================================
570
+ # PROMPTS
571
+ # ============================================================================
572
+
573
+
574
+ @mcp.prompt
575
+ def analyze_csv_prompt(session_id: str, analysis_type: str = "summary") -> str:
576
+ """Generate a prompt to analyze CSV data."""
577
+ return f"""Please analyze the CSV data in session {session_id}.
578
+
579
+ Analysis type: {analysis_type}
580
+
581
+ Provide insights about:
582
+ 1. Data quality and completeness
583
+ 2. Statistical patterns
584
+ 3. Potential issues or anomalies
585
+ 4. Recommended transformations or cleanups
586
+ """
587
+
588
+
589
+ @mcp.prompt
590
+ def data_cleaning_prompt(session_id: str) -> str:
591
+ """Generate a prompt for data cleaning suggestions."""
592
+ return f"""Review the data in session {session_id} and suggest cleaning operations.
593
+
594
+ Consider:
595
+ - Missing values and how to handle them
596
+ - Duplicate rows
597
+ - Data type conversions needed
598
+ - Outliers that may need attention
599
+ - Column naming conventions
600
+ """
601
+
602
+
603
+ # ============================================================================
604
+ # MAIN ENTRY POINT
605
+ # ============================================================================
606
+
607
+
608
+ def main(argv: list[str] | None = None):
609
+ """Main entry point for the server."""
610
+ import argparse
611
+
612
+ parser = argparse.ArgumentParser(description="CSV Editor")
613
+ parser.add_argument(
614
+ "--transport", choices=["stdio", "http"], default="stdio", help="Transport method (stdio for local clients, http for Streamable HTTP remote)"
615
+ )
616
+ parser.add_argument("--host", default="0.0.0.0", help="Host for HTTP/SSE transport")
617
+ parser.add_argument("--port", type=int, default=8000, help="Port for HTTP/SSE transport")
618
+ parser.add_argument(
619
+ "--log-level",
620
+ choices=["DEBUG", "INFO", "WARNING", "ERROR"],
621
+ default="INFO",
622
+ help="Logging level",
623
+ )
624
+
625
+ args = parser.parse_args(argv)
626
+
627
+ # Set logging level
628
+ logging.getLogger().setLevel(getattr(logging, args.log_level))
629
+
630
+ logger.info(f"Starting CSV Editor with {args.transport} transport")
631
+
632
+ # Run the server
633
+ if args.transport == "stdio":
634
+ mcp.run()
635
+ else:
636
+ mcp.run(transport=args.transport, host=args.host, port=args.port)
637
+
638
+
639
+ if __name__ == "__main__":
640
+ main()
@@ -0,0 +1,5 @@
1
+ """Tools for CSV Editor MCP Server."""
2
+
3
+ from . import analytics, data_operations, io_operations, transformations, validation
4
+
5
+ __all__ = ["analytics", "data_operations", "io_operations", "transformations", "validation"]