1use std::sync::Arc;
4use tokio::sync::RwLock;
5
6use rmcp::{
7 handler::server::{router::tool::ToolRouter, wrapper::Parameters},
8 model::{ServerCapabilities, ServerInfo},
9 tool, tool_handler, tool_router, ServerHandler,
10};
11
12use crate::store::WorkbookStore;
13use crate::tools;
14use crate::types::inputs::*;
15
16#[derive(Debug, Clone)]
17pub struct ExcelMcpServer {
18 tool_router: ToolRouter<Self>,
19 store: Arc<RwLock<WorkbookStore>>,
20}
21
22impl ExcelMcpServer {
23 pub fn new(store: Arc<RwLock<WorkbookStore>>) -> Self {
24 Self {
25 tool_router: Self::tool_router(),
26 store,
27 }
28 }
29}
30
31fn unexpected_error(e: anyhow::Error) -> String {
32 crate::types::responses::error(
33 crate::types::responses::ErrorCategory::IoError,
34 &format!("Unexpected error: {}", e),
35 "Please try again.",
36 )
37}
38
39macro_rules! tool_fn {
40 ($store:expr, $module:path, $input:expr) => {{
41 let mut store = $store.write().await;
42 match $module(&mut store, $input) {
43 Ok(j) => j,
44 Err(e) => unexpected_error(e),
45 }
46 }};
47}
48
49#[tool_router]
50impl ExcelMcpServer {
51 #[tool(description = "Create a new empty Excel workbook in memory")]
54 async fn create_workbook(&self, _p: Parameters<CreateWorkbookInput>) -> String {
55 let mut store = self.store.write().await;
56 match tools::workbook::create_workbook(&mut store) {
57 Ok(j) => j,
58 Err(e) => unexpected_error(e),
59 }
60 }
61
62 #[tool(description = "Open an existing Excel file for reading or editing")]
63 async fn open_workbook(&self, Parameters(i): Parameters<OpenWorkbookInput>) -> String {
64 tool_fn!(self.store, tools::workbook::open_workbook, i)
65 }
66
67 #[tool(description = "Save a workbook to disk as an xlsx file")]
68 async fn save_workbook(&self, Parameters(i): Parameters<SaveWorkbookInput>) -> String {
69 tool_fn!(self.store, tools::workbook::save_workbook, i)
70 }
71
72 #[tool(description = "Close a workbook and free its memory")]
73 async fn close_workbook(&self, Parameters(i): Parameters<CloseWorkbookInput>) -> String {
74 tool_fn!(self.store, tools::workbook::close_workbook, i)
75 }
76
77 #[tool(
80 description = "Configure workbook: set calc mode (auto/manual), active sheet, document properties (title, author, company)"
81 )]
82 async fn configure_workbook(
83 &self,
84 Parameters(i): Parameters<ConfigureWorkbookInput>,
85 ) -> String {
86 tool_fn!(self.store, tools::expanded::configure_workbook, i)
87 }
88
89 #[tool(description = "List all sheet names in a workbook")]
92 async fn list_sheets(&self, Parameters(i): Parameters<ListSheetsInput>) -> String {
93 tool_fn!(self.store, tools::sheets::list_sheets, i)
94 }
95
96 #[tool(description = "Get the dimensions of a sheet (used range, row count, column count)")]
97 async fn get_sheet_dimensions(
98 &self,
99 Parameters(i): Parameters<GetSheetDimensionsInput>,
100 ) -> String {
101 tool_fn!(self.store, tools::sheets::get_sheet_dimensions, i)
102 }
103
104 #[tool(
105 description = "Describe a workbook's structure including sheet names, dimensions, and sample data"
106 )]
107 async fn describe_workbook(&self, Parameters(i): Parameters<DescribeWorkbookInput>) -> String {
108 tool_fn!(self.store, tools::sheets::describe_workbook, i)
109 }
110
111 #[tool(description = "Add a new empty worksheet to a workbook")]
112 async fn add_sheet(&self, Parameters(i): Parameters<AddSheetInput>) -> String {
113 tool_fn!(self.store, tools::sheets::add_sheet, i)
114 }
115
116 #[tool(description = "Rename an existing worksheet")]
117 async fn rename_sheet(&self, Parameters(i): Parameters<RenameSheetInput>) -> String {
118 tool_fn!(self.store, tools::sheets::rename_sheet, i)
119 }
120
121 #[tool(description = "Delete a worksheet from a workbook")]
122 async fn delete_sheet(&self, Parameters(i): Parameters<DeleteSheetInput>) -> String {
123 tool_fn!(self.store, tools::sheets::delete_sheet, i)
124 }
125
126 #[tool(description = "Move a worksheet to a different position in the workbook")]
127 async fn move_worksheet(&self, Parameters(i): Parameters<MoveWorksheetInput>) -> String {
128 tool_fn!(self.store, tools::expanded::move_worksheet, i)
129 }
130
131 #[tool(description = "Read data from a worksheet with optional range and pagination")]
134 async fn read_sheet(&self, Parameters(i): Parameters<ReadSheetInput>) -> String {
135 tool_fn!(self.store, tools::read::read_sheet, i)
136 }
137
138 #[tool(description = "Read a single cell's value, type, and formula")]
139 async fn read_cell(&self, Parameters(i): Parameters<ReadCellInput>) -> String {
140 tool_fn!(self.store, tools::read::read_cell, i)
141 }
142
143 #[tool(description = "Search for cells matching a value or pattern across sheets")]
144 async fn search_cells(&self, Parameters(i): Parameters<SearchCellsInput>) -> String {
145 tool_fn!(self.store, tools::read::search_cells, i)
146 }
147
148 #[tool(description = "Export a sheet as a CSV-formatted string")]
149 async fn sheet_to_csv(&self, Parameters(i): Parameters<SheetToCsvInput>) -> String {
150 tool_fn!(self.store, tools::read::sheet_to_csv, i)
151 }
152
153 #[tool(
156 description = "Write values to multiple cells. Strings starting with '=' are formulas. Numbers, booleans, ISO dates auto-detected."
157 )]
158 async fn write_cells(&self, Parameters(i): Parameters<WriteCellsInput>) -> String {
159 tool_fn!(self.store, tools::write::write_cells, i)
160 }
161
162 #[tool(description = "Write a row of values starting from a cell, filling rightward")]
163 async fn write_row(&self, Parameters(i): Parameters<WriteRowInput>) -> String {
164 tool_fn!(self.store, tools::write::write_row, i)
165 }
166
167 #[tool(description = "Write a column of values starting from a cell, filling downward")]
168 async fn write_column(&self, Parameters(i): Parameters<WriteColumnInput>) -> String {
169 tool_fn!(self.store, tools::write::write_column, i)
170 }
171
172 #[tool(description = "Write rich text (mixed bold/italic/color runs) to a cell")]
173 async fn write_rich_text(&self, Parameters(i): Parameters<WriteRichTextInput>) -> String {
174 tool_fn!(self.store, tools::expanded::write_rich_text, i)
175 }
176
177 #[tool(
180 description = "Write a formula. Set formula_type to 'array' for CSE array formulas (cell = range), 'dynamic' for Excel 365 spill formulas, or omit for regular. Optional cached_result."
181 )]
182 async fn write_formula(
183 &self,
184 Parameters(i): Parameters<WriteFormulaConsolidatedInput>,
185 ) -> String {
186 tool_fn!(self.store, tools::expanded::write_formula_consolidated, i)
187 }
188
189 #[tool(
192 description = "Manage a cell: action='blank' writes a formatted blank cell, action='clear' removes content and formatting"
193 )]
194 async fn manage_cell(&self, Parameters(i): Parameters<ManageCellInput>) -> String {
195 tool_fn!(self.store, tools::expanded::manage_cell, i)
196 }
197
198 #[tool(
201 description = "Apply formatting (bold, italic, colors, borders, number format, alignment) to a range of cells"
202 )]
203 async fn set_cell_format(&self, Parameters(i): Parameters<SetCellFormatInput>) -> String {
204 tool_fn!(self.store, tools::format::set_cell_format, i)
205 }
206
207 #[tool(description = "Merge a range of cells into a single cell")]
208 async fn merge_cells(&self, Parameters(i): Parameters<MergeCellsInput>) -> String {
209 tool_fn!(self.store, tools::format::merge_cells, i)
210 }
211
212 #[tool(
215 description = "Apply formatting to an entire row or column. Set target='row' with identifier='5' or target='column' with identifier='B'."
216 )]
217 async fn set_row_column_format(
218 &self,
219 Parameters(i): Parameters<SetRowColumnFormatInput>,
220 ) -> String {
221 tool_fn!(self.store, tools::expanded::set_row_column_format, i)
222 }
223
224 #[tool(
227 description = "Set dimensions: target='column_width' (column+value), 'row_height' (row+value), 'column_range_width' (first_column+last_column+value), 'default_row_height' (value)"
228 )]
229 async fn set_dimensions(&self, Parameters(i): Parameters<SetDimensionsInput>) -> String {
230 tool_fn!(self.store, tools::expanded::set_dimensions, i)
231 }
232
233 #[tool(
236 description = "Freeze panes at a cell position so rows above and columns left remain visible while scrolling"
237 )]
238 async fn freeze_panes(&self, Parameters(i): Parameters<FreezePanesInput>) -> String {
239 tool_fn!(self.store, tools::layout::freeze_panes, i)
240 }
241
242 #[tool(description = "Auto-fit all column widths based on cell content")]
243 async fn autofit_columns(&self, Parameters(i): Parameters<AutofitColumnsInput>) -> String {
244 tool_fn!(self.store, tools::expanded::autofit_columns, i)
245 }
246
247 #[tool(description = "Set the selected/active cell in a sheet")]
248 async fn set_selection(&self, Parameters(i): Parameters<SetSelectionInput>) -> String {
249 tool_fn!(self.store, tools::expanded::set_selection, i)
250 }
251
252 #[tool(
253 description = "Hide or unhide a row or column. Set target='row' or 'column', identifier='5' or 'B'."
254 )]
255 async fn set_visibility(&self, Parameters(i): Parameters<SetVisibilityInput>) -> String {
256 tool_fn!(self.store, tools::expanded::set_visibility, i)
257 }
258
259 #[tool(
262 description = "Configure sheet display: hidden, very_hidden, zoom, hide_gridlines, hide_headings, tab_color, right_to_left"
263 )]
264 async fn set_sheet_settings(&self, Parameters(i): Parameters<SetSheetSettingsInput>) -> String {
265 tool_fn!(self.store, tools::expanded::set_sheet_settings, i)
266 }
267
268 #[tool(
271 description = "Add a chart with full control: type (bar/column/line/pie/scatter/area/doughnut), multiple series with colors/labels/trendlines/markers, pivot source, position cell"
272 )]
273 async fn add_chart(&self, Parameters(i): Parameters<AddChartEnhancedInput>) -> String {
274 tool_fn!(self.store, tools::expanded::add_chart_enhanced, i)
275 }
276
277 #[tool(description = "Create an Excel Table with headers, autofilter, and style")]
280 async fn add_table(&self, Parameters(i): Parameters<AddTableInput>) -> String {
281 tool_fn!(self.store, tools::tables::add_table, i)
282 }
283
284 #[tool(
285 description = "Add conditional formatting: cell_value, color_scale_2, color_scale_3, data_bar, or icon_set"
286 )]
287 async fn add_conditional_format(
288 &self,
289 Parameters(i): Parameters<AddConditionalFormatInput>,
290 ) -> String {
291 tool_fn!(self.store, tools::conditional::add_conditional_format, i)
292 }
293
294 #[tool(
295 description = "Add data validation: list, list_range, whole_number, decimal, date_range, text_length, or custom_formula"
296 )]
297 async fn add_data_validation(
298 &self,
299 Parameters(i): Parameters<AddDataValidationInput>,
300 ) -> String {
301 tool_fn!(self.store, tools::validation::add_data_validation, i)
302 }
303
304 #[tool(description = "Add a sparkline (line, column, or win/loss) to a cell")]
305 async fn add_sparkline(&self, Parameters(i): Parameters<AddSparklineInput>) -> String {
306 tool_fn!(self.store, tools::sparklines::add_sparkline, i)
307 }
308
309 #[tool(description = "Embed a PNG or JPEG image into a worksheet at a specified cell")]
312 async fn add_image(&self, Parameters(i): Parameters<AddImageInput>) -> String {
313 tool_fn!(self.store, tools::images::add_image, i)
314 }
315
316 #[tool(
319 description = "Create a pivot table with row/column/value/filter fields, aggregation (sum/count/average/max/min), layout (compact/outline/tabular)"
320 )]
321 async fn add_pivot_table(&self, Parameters(i): Parameters<AddPivotTableInput>) -> String {
322 tool_fn!(self.store, tools::expanded::add_pivot_table, i)
323 }
324
325 #[tool(
328 description = "Configure page setup: landscape, paper_size, margins, fit_to_pages, print_scale, print_area, repeat_rows, header, footer, print_gridlines, center, page_breaks"
329 )]
330 async fn set_page_setup(&self, Parameters(i): Parameters<SetPageSetupInput>) -> String {
331 tool_fn!(self.store, tools::expanded::set_page_setup, i)
332 }
333
334 #[tool(
337 description = "Manage comments: action='add' (cell, text, author) or action='read' to list all comments"
338 )]
339 async fn manage_comments(&self, Parameters(i): Parameters<ManageCommentsInput>) -> String {
340 tool_fn!(self.store, tools::expanded::manage_comments, i)
341 }
342
343 #[tool(
344 description = "Add a link: link_type='url' for external URLs, link_type='internal' for sheet references (e.g. 'Sheet2!A1')"
345 )]
346 async fn add_link(&self, Parameters(i): Parameters<AddLinkInput>) -> String {
347 tool_fn!(self.store, tools::expanded::add_link, i)
348 }
349
350 #[tool(
353 description = "Manage defined names: action='add' (name, formula) or action='list' to show all"
354 )]
355 async fn manage_defined_names(
356 &self,
357 Parameters(i): Parameters<ManageDefinedNamesInput>,
358 ) -> String {
359 tool_fn!(self.store, tools::expanded::manage_defined_names, i)
360 }
361
362 #[tool(
365 description = "Insert or delete rows: action='insert' or 'delete', at_row (1-based), count"
366 )]
367 async fn modify_rows(&self, Parameters(i): Parameters<ModifyRowsInput>) -> String {
368 tool_fn!(self.store, tools::expanded::modify_rows, i)
369 }
370
371 #[tool(
372 description = "Insert or delete columns: action='insert' or 'delete', at_column (letter), count"
373 )]
374 async fn modify_columns(&self, Parameters(i): Parameters<ModifyColumnsInput>) -> String {
375 tool_fn!(self.store, tools::expanded::modify_columns, i)
376 }
377
378 #[tool(
381 description = "Group rows or columns into expandable outlines. target='rows' (start/end as numbers) or 'columns' (start/end as letters)"
382 )]
383 async fn group(&self, Parameters(i): Parameters<GroupInput>) -> String {
384 tool_fn!(self.store, tools::expanded::group_consolidated, i)
385 }
386
387 #[tool(
390 description = "Protect: target='sheet' (sheet_name, password), 'workbook' (password), or 'unprotect_range' (sheet_name, range, range_title, password)"
391 )]
392 async fn protect(&self, Parameters(i): Parameters<ProtectInput>) -> String {
393 tool_fn!(self.store, tools::expanded::protect_consolidated, i)
394 }
395
396 #[tool(
399 description = "Set autofilter on a range. Optionally filter a specific column with filter_column and filter_values."
400 )]
401 async fn manage_autofilter(&self, Parameters(i): Parameters<ManageAutofilterInput>) -> String {
402 tool_fn!(self.store, tools::expanded::manage_autofilter, i)
403 }
404
405 #[tool(
408 description = "Suppress Excel error indicators (green triangles) on a range for a specific error type"
409 )]
410 async fn ignore_error(&self, Parameters(i): Parameters<IgnoreErrorInput>) -> String {
411 tool_fn!(self.store, tools::expanded::ignore_error, i)
412 }
413
414 #[tool(
417 description = "Add a waterfall chart (Excel 2016+ ChartEx). Points have category, value, and point_type (increase/decrease/total)."
418 )]
419 async fn add_waterfall_chart(
420 &self,
421 Parameters(i): Parameters<AddWaterfallChartInput>,
422 ) -> String {
423 tool_fn!(self.store, tools::expanded::add_waterfall_chart, i)
424 }
425
426 #[tool(
427 description = "Add a funnel chart (Excel 2016+ ChartEx). Points have category and value, rendered as a narrowing funnel."
428 )]
429 async fn add_funnel_chart(&self, Parameters(i): Parameters<AddFunnelChartInput>) -> String {
430 tool_fn!(self.store, tools::expanded::add_funnel_chart, i)
431 }
432
433 #[tool(
434 description = "Add a treemap chart (Excel 2016+ ChartEx). Points have category, value, and optional color."
435 )]
436 async fn add_treemap_chart(&self, Parameters(i): Parameters<AddTreemapChartInput>) -> String {
437 tool_fn!(self.store, tools::expanded::add_treemap_chart, i)
438 }
439
440 #[tool(
443 description = "Add a drawing shape (rectangle, rounded_rectangle, ellipse, triangle, diamond, arrow, callout, text_box) with optional text, fill, outline, and font settings."
444 )]
445 async fn add_shape(&self, Parameters(i): Parameters<AddShapeInput>) -> String {
446 tool_fn!(self.store, tools::expanded::add_shape, i)
447 }
448
449 #[tool(
452 description = "Set document properties: title, author, subject, description, keywords, category, company"
453 )]
454 async fn set_doc_properties(&self, Parameters(i): Parameters<SetDocPropertiesInput>) -> String {
455 tool_fn!(self.store, tools::expanded::set_doc_properties, i)
456 }
457
458 #[tool(
461 description = "Add a sunburst chart (Excel 2016+ ChartEx). Hierarchical data with category labels and values."
462 )]
463 async fn add_sunburst_chart(
464 &self,
465 Parameters(i): Parameters<AddSunburstChartInput>,
466 ) -> String {
467 tool_fn!(self.store, tools::expanded::add_sunburst_chart, i)
468 }
469
470 #[tool(
471 description = "Add a histogram chart (Excel 2016+ ChartEx). Raw data values with optional bin_count, bin_width, and pareto overlay."
472 )]
473 async fn add_histogram_chart(
474 &self,
475 Parameters(i): Parameters<AddHistogramChartInput>,
476 ) -> String {
477 tool_fn!(self.store, tools::expanded::add_histogram_chart, i)
478 }
479
480 #[tool(
481 description = "Add a box & whisker chart (Excel 2016+ ChartEx). Data sets with outliers, mean markers, and inner points options."
482 )]
483 async fn add_box_whisker_chart(
484 &self,
485 Parameters(i): Parameters<AddBoxWhiskerChartInput>,
486 ) -> String {
487 tool_fn!(self.store, tools::expanded::add_box_whisker_chart, i)
488 }
489
490 #[tool(
491 description = "Add a map chart (Excel 2016+ ChartEx). Geographic data with location names and values. map_level: 'country' or 'region'."
492 )]
493 async fn add_map_chart(&self, Parameters(i): Parameters<AddMapChartInput>) -> String {
494 tool_fn!(self.store, tools::expanded::add_map_chart, i)
495 }
496
497 #[tool(
500 description = "Add a slicer — interactive filter for a pivot table. Specify pivot_table_name and field_name."
501 )]
502 async fn add_slicer(&self, Parameters(i): Parameters<AddSlicerInput>) -> String {
503 tool_fn!(self.store, tools::expanded::add_slicer, i)
504 }
505
506 #[tool(
507 description = "Add a timeline — date filter for a pivot table. Specify pivot_table_name and date field_name."
508 )]
509 async fn add_timeline(&self, Parameters(i): Parameters<AddTimelineInput>) -> String {
510 tool_fn!(self.store, tools::expanded::add_timeline, i)
511 }
512
513 #[tool(
514 description = "Add a form control: control_type='button', 'checkbox', 'dropdown', or 'spinner'. Checkbox supports cell_link, dropdown takes comma-separated input_range."
515 )]
516 async fn add_form_control(&self, Parameters(i): Parameters<AddFormControlInput>) -> String {
517 tool_fn!(self.store, tools::expanded::add_form_control, i)
518 }
519
520 #[tool(
523 description = "Save workbook in different formats: 'xlsx' (default), 'template' (.xltx), 'encrypted' (password-protected), 'parallel' (fast compression). Formulas are recalculated before save."
524 )]
525 async fn save_workbook_advanced(
526 &self,
527 Parameters(i): Parameters<SaveWorkbookAdvancedInput>,
528 ) -> String {
529 tool_fn!(self.store, tools::expanded::save_workbook_advanced, i)
530 }
531
532 #[tool(description = "Open a password-protected (encrypted) Excel workbook")]
533 async fn open_workbook_encrypted(
534 &self,
535 Parameters(i): Parameters<OpenWorkbookEncryptedInput>,
536 ) -> String {
537 tool_fn!(self.store, tools::expanded::open_workbook_encrypted, i)
538 }
539
540 #[tool(
543 description = "Manage named ranges: action='add' (name, formula), 'add_scoped' (name, formula, sheet_index), 'update' (name, formula), 'remove' (name, sheet_index), 'list'"
544 )]
545 async fn manage_named_ranges(
546 &self,
547 Parameters(i): Parameters<ManageNamedRangesInput>,
548 ) -> String {
549 tool_fn!(self.store, tools::expanded::manage_named_ranges, i)
550 }
551
552 #[tool(
555 description = "Read sheet metadata: info='used_range', 'hyperlinks', 'merge_ranges', 'charts', or 'all'"
556 )]
557 async fn read_sheet_metadata(
558 &self,
559 Parameters(i): Parameters<ReadSheetMetadataInput>,
560 ) -> String {
561 tool_fn!(self.store, tools::expanded::read_sheet_metadata, i)
562 }
563
564 #[tool(
567 description = "Add a dedicated chart-only sheet (no cells, just a chart). Specify chart_type, series or data_range, title."
568 )]
569 async fn add_chart_sheet(&self, Parameters(i): Parameters<AddChartSheetInput>) -> String {
570 tool_fn!(self.store, tools::expanded::add_chart_sheet, i)
571 }
572
573 #[tool(
576 description = "Add a modern threaded comment with author, text, optional timestamp, and replies. Unlike legacy comments, these support conversation threads."
577 )]
578 async fn add_threaded_comment(
579 &self,
580 Parameters(i): Parameters<AddThreadedCommentInput>,
581 ) -> String {
582 tool_fn!(self.store, tools::expanded::add_threaded_comment, i)
583 }
584
585 #[tool(
588 description = "Protect a sheet with granular options: allow_insert_rows, allow_delete_rows, allow_format_cells, allow_sort, etc. Optional password."
589 )]
590 async fn protect_sheet_advanced(
591 &self,
592 Parameters(i): Parameters<ProtectSheetAdvancedInput>,
593 ) -> String {
594 tool_fn!(self.store, tools::expanded::protect_sheet_advanced, i)
595 }
596
597 #[tool(
600 description = "Set a custom document property. value_type: 'text' (default), 'number', 'integer', 'bool', 'datetime'."
601 )]
602 async fn set_custom_property(
603 &self,
604 Parameters(i): Parameters<SetCustomPropertyInput>,
605 ) -> String {
606 tool_fn!(self.store, tools::expanded::set_custom_property, i)
607 }
608
609 #[tool(description = "Read a single cell's comment (author and text). Returns null if no comment exists.")]
612 async fn read_cell_comment(
613 &self,
614 Parameters(i): Parameters<ReadCellCommentInput>,
615 ) -> String {
616 tool_fn!(self.store, tools::expanded::read_cell_comment, i)
617 }
618
619 #[tool(description = "Read a cell's format (bold, italic, colors, number format, etc.)")]
620 async fn read_cell_format(
621 &self,
622 Parameters(i): Parameters<ReadCellFormatInput>,
623 ) -> String {
624 tool_fn!(self.store, tools::expanded::read_cell_format, i)
625 }
626
627 #[tool(
630 description = "Manage custom XML parts: action='add' (namespace, content) or action='read' (namespace)"
631 )]
632 async fn manage_custom_xml(
633 &self,
634 Parameters(i): Parameters<ManageCustomXmlInput>,
635 ) -> String {
636 tool_fn!(self.store, tools::expanded::manage_custom_xml, i)
637 }
638
639 #[tool(description = "Add an external data connection (connection_string, command)")]
642 async fn add_connection(&self, Parameters(i): Parameters<AddConnectionInput>) -> String {
643 tool_fn!(self.store, tools::expanded::add_connection, i)
644 }
645
646 #[tool(description = "Set the shared string table threshold for optimization. Lower values use more memory but faster writes.")]
649 async fn set_sst_threshold(
650 &self,
651 Parameters(i): Parameters<SetSstThresholdInput>,
652 ) -> String {
653 tool_fn!(self.store, tools::expanded::set_sst_threshold, i)
654 }
655
656 #[tool(
659 description = "Write JSON objects as rows. Each object's keys become column headers (if write_headers=true), values become cells. Auto-detects types."
660 )]
661 async fn write_json_rows(&self, Parameters(i): Parameters<WriteJsonRowsInput>) -> String {
662 tool_fn!(self.store, tools::expanded::write_json_rows, i)
663 }
664}
665
666#[tool_handler]
667impl ServerHandler for ExcelMcpServer {
668 fn get_info(&self) -> ServerInfo {
669 ServerInfo::new(ServerCapabilities::builder().enable_tools().build()).with_instructions(
670 "Excel file manipulation server powered by zavora-xlsx. 74 tools covering: \
671 workbook lifecycle, sheet management, cell reading/writing, formatting, \
672 charts (11 types + pivot charts + waterfall/funnel/treemap/sunburst/histogram/box-whisker/map \
673 with data tables, 3D views, error bars, axis formatting, drop/high-low lines, gradients), \
674 images, shapes, tables, conditional formatting, data validation, sparklines, \
675 pivot tables (calculated fields, date/range grouping, subtotals, grand totals, value formats), \
676 slicers, timelines, form controls, threaded comments, page setup, hyperlinks, \
677 named ranges (CRUD with scoping), row/column manipulation, grouping, \
678 protection (basic + granular with per-feature allow/deny), autofilter, \
679 formulas (regular/array/dynamic with recalculation), rich text, \
680 document properties, custom properties, sheet metadata, chart sheets, \
681 encrypted open/save, template save, parallel save, and CSV export."
682 .to_string(),
683 )
684 }
685}