apple-numbers-mcp 1.1.3 → 1.1.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-numbers-mcp",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "MCP server for Apple Numbers - read, write, search, and format .numbers spreadsheets via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -320,10 +320,62 @@ def _coerce_value(raw, type_hint=None, where=None):
320
320
  f"Dates must be ISO 8601 (e.g. \"2025-06-01\" or a full ISO datetime)."
321
321
  )
322
322
  return raw
323
+ # No explicit type: auto-detect a clean numeric string as a number so a bare
324
+ # numeric value stores as a real number cell, matching create-spreadsheet /
325
+ # add-rows / update-rows / import-csv (all of which store the same value
326
+ # numerically). Some MCP clients deliver a JSON number as its string form
327
+ # (e.g. "30"), which would otherwise land here and be written as text.
328
+ if type_hint is None and isinstance(raw, str):
329
+ num = _numeric_str_to_number(raw)
330
+ if num is not None:
331
+ return num
323
332
  # Default: string
324
333
  return str(raw)
325
334
 
326
335
 
336
+ def _numeric_str_to_number(s):
337
+ """Return int/float if `s` is a clean numeric literal, else None.
338
+
339
+ Conservative on purpose: only a plain decimal integer or float is converted.
340
+ Strings that a user almost certainly means as text are left alone — a leading
341
+ zero (zip codes, IDs like "007"), surrounding whitespace, thousands
342
+ separators, currency symbols, hex/binary, inf/nan, and complex/exponent-only
343
+ forms all stay text. An explicit type="number" override still forces numeric
344
+ coercion via the branch above.
345
+ """
346
+ if not isinstance(s, str) or s == "":
347
+ return None
348
+ body = s[1:] if s[0] in "+-" else s
349
+ if body == "":
350
+ return None
351
+ # Reject leading-zero integers ("007", "0123") so identifier-like strings
352
+ # keep their exact text; a lone "0" and decimals like "0.5" are fine.
353
+ if body.isdigit():
354
+ if len(body) > 1 and body[0] == "0":
355
+ return None
356
+ try:
357
+ return int(s)
358
+ except ValueError:
359
+ return None
360
+ # Float form: require a decimal point and digits on at least one side; this
361
+ # naturally excludes "1e5", "inf", "nan", "1,000", "$5", "0x1f", etc.
362
+ if "." in body:
363
+ int_part, _, frac_part = body.partition(".")
364
+ if "." in frac_part:
365
+ return None
366
+ if not (int_part + frac_part).isdigit():
367
+ return None
368
+ if int_part == "" and frac_part == "":
369
+ return None
370
+ if len(int_part) > 1 and int_part[0] == "0":
371
+ return None
372
+ try:
373
+ return float(s)
374
+ except ValueError:
375
+ return None
376
+ return None
377
+
378
+
327
379
  def cmd_create(args):
328
380
  """Create a new .numbers file with headers and optional data rows."""
329
381
  headers = json.loads(args.headers)