MCP server exposing NetSuite saved search exports as a structured query interface for Claude
Strong tool definitions with clear naming, comprehensive descriptions (avg 250+ chars), and well-structured JSON schemas. All 7 tools follow verb_noun pattern (list_, get_, query_, aggregate_, categorize_, detect_, get_). Descriptions are LLM-optimized and include WHEN to call each tool. Input schemas are complete with types and descriptions. However, output schemas are not explicitly documented in the source code provided, and error handling lacks recovery guidance. No tool annotations (readOnlyHint) despite all being read-only operations.
Group rows from a NetSuite export by one or more columns and compute aggregations per group. Each Measure carries column, op (sum/count/avg/min/max), and optional alias for the output key (defaults to {op}_{column}). Groups are returned in first-seen order. Use this instead of query_export when you want summary statistics rather than raw rows.
Tag every row with a derived `_category` based on case-insensitive substring matches across one or more memo columns. NetSuite GL exports usually carry both 'Memo (main)' and 'Memo (line)'; pass both so the keyword sweep covers all the prose. `rules` maps category name to a list of keywords; the first rule whose keyword appears in any memo wins; rows matching nothing fall into 'Uncategorized'. Returns the tagged rows plus a per-category count breakdown.
Run three anomaly checks against a NetSuite GL-style export and return Findings: (1) zero_activity_period — month gaps inside the observed period range (HIGH); (2) ratio_anomaly — (account, period) total greater than 2x the account's median total across periods (MEDIUM); (3) document_count_variance — period row count more than 2 stdev from the mean across periods (MEDIUM). Each Finding includes severity, description, up to 10 supporting_rows, and total_supporting_count for the true un-truncated count. The period column should contain labels like 'Jan 2024', 'January 2024', or '2024-01'.
Return the column headers of a NetSuite saved-search export plus their spreadsheet column letters (A, B, ..., AA, AB) and the 0-indexed header_row. Call this before query_export or aggregate_export when you don't already know the column names — every other tool takes column names verbatim and errors on typos with a difflib suggestion.
Output schemas not documented in source code. While input schemas are complete, return types (ExportSummary, HeadersResponse, QueryResponse, etc.) are referenced but their field definitions are not visible in the provided code excerpt. LLMs cannot plan downstream tool calls without knowing what fields to expect.
No tool annotations present. All 7 tools are read-only (Risk: READ_ONLY noted in metadata), but readOnlyHint is not declared in tool definitions. This prevents clients from optimizing caching and retry logic.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 63 | <=2025-11-25 | v2 |
Return the parse warnings for the export at `file_path`, parsing it on demand if it isn't already cached. Warning kinds: phantom_column (cell at a column index beyond the header count), bad_datetime (DateTime cell that wouldn't parse — raw string is preserved in the row), encoding_recovery (lxml had to recover from invalid XML), empty_row_skipped. Call this after any other tool reports a non-zero warning_count to see exactly which rows are affected.
List every NetSuite saved-search export (.xls) in a directory under NSMCP_ROOT. Returns one ExportSummary per file with row count, header count, 0-indexed header_row, warning count, and detected date range. Files that can't be parsed even with lxml recovery come back with parse_error populated and the other fields None. Call this first when you don't already know which exports are available.
Filter rows from a NetSuite export by a list of predicates (AND-combined; empty list returns everything). Predicate ops: eq/ne, gt/gte/lt/lte, contains/not_contains (case-insensitive by default), regex, date_range (ISO 8601 start/end, inclusive by default). Optionally project to a subset of columns via the `columns` argument. Implicit limit=1000; pass limit=0 to get total_matched without fetching any rows. Returns rows, total_matched, and a truncated flag.
Error handling lacks recovery guidance. Custom exceptions (ColumnNotFoundError, ExportNotFoundError, ParseError, PathTraversalError) are defined but error responses do not include actionable next steps. E.g., ColumnNotFoundError should suggest 'Call get_headers() to see available columns' with difflib suggestions visible.
Predicate and Measure objects in query_export and aggregate_export lack inline documentation. While the parent tool description explains ops (eq/ne/gt/gte/lt/lte/contains/not_contains/regex/date_range), the nested object schemas should include descriptions for each field (column, op, value, start, end, alias).
categorize_by_memo parameter 'rules' uses additionalProperties without field-level descriptions. The schema shows 'additionalProperties: {type: array, items: {type: string}}' but does not explain that keys are category names and values are keyword lists. This forces LLMs to infer structure.