@aliceshimada/mica 1.1.0 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.1 - 2026-06-16
4
+
5
+ - Fix `RestartKernelRequest`: kill kernel via `Quit[]` before restarting.
6
+ - Fix `realpathSync` crash when `process.argv[1]` doesn't resolve.
7
+ - Fix `$BridgeNotebookPermissions` memory leak: prune on notebook close.
8
+
3
9
  ## 1.1.0 - 2026-06-09
4
10
 
5
11
  - Add `mma_kill_kernel` tool: quit a notebook's Wolfram kernel (control agent kernel is protected).
@@ -3,7 +3,7 @@ import http from "node:http";
3
3
  import { executeBackendMcpTool } from "../mcp/backendTools.js";
4
4
  import { renderDashboard } from "./dashboard.js";
5
5
  const JSON_BODY_LIMIT_BYTES = 1024 * 1024;
6
- const DEFAULT_VERSION = "1.1.0";
6
+ const DEFAULT_VERSION = "1.1.1";
7
7
  export async function createBunHttpApp({ state, host = "127.0.0.1", port, authToken, version = DEFAULT_VERSION }) {
8
8
  const runtimeInfo = {
9
9
  host,
@@ -8,7 +8,7 @@ import { loadRuntimeConfig } from "../runtime/config.js";
8
8
  import { writeSessionFile } from "../runtime/session.js";
9
9
  import { createBunHttpApp } from "./httpServer.js";
10
10
  const MCP_SERVER_NAME = "mica-bun";
11
- const MICA_PACKAGE_VERSION = "1.1.0";
11
+ const MICA_PACKAGE_VERSION = "1.1.1";
12
12
  export async function startBunRuntime(deps = {}) {
13
13
  const config = deps.runtimeConfig ?? loadRuntimeConfig();
14
14
  const bridgeOnly = deps.bridgeOnly ?? config.bridgeOnly;
@@ -223,13 +223,18 @@ async function main() {
223
223
  process.exitCode = exitCode;
224
224
  }
225
225
  if (process.argv[1]) {
226
- const scriptReal = realpathSync(fileURLToPath(import.meta.url));
227
- const argReal = realpathSync(process.argv[1]);
228
- if (scriptReal === argReal) {
229
- main().catch((error) => {
230
- const message = error instanceof Error ? error.stack ?? error.message : String(error);
231
- process.stderr.write(`${message}\n`);
232
- process.exitCode = 1;
233
- });
226
+ try {
227
+ const scriptReal = realpathSync(fileURLToPath(import.meta.url));
228
+ const argReal = realpathSync(process.argv[1]);
229
+ if (scriptReal === argReal) {
230
+ main().catch((error) => {
231
+ const message = error instanceof Error ? error.stack ?? error.message : String(error);
232
+ process.stderr.write(`${message}\n`);
233
+ process.exitCode = 1;
234
+ });
235
+ }
236
+ }
237
+ catch {
238
+ // process.argv[1] may not resolve (e.g. node -e, shebang edge cases)
234
239
  }
235
240
  }
@@ -35,7 +35,7 @@ export const MICA_AGENT_INSTRUCTIONS = [
35
35
  "Tools:",
36
36
  ...TOOL_GUIDE.map(([name, description]) => `- ${name}: ${description}`),
37
37
  ].join("\n");
38
- export function createMicaMcpServer(name, version = "1.1.0") {
38
+ export function createMicaMcpServer(name, version = "1.1.1") {
39
39
  return new McpServer({ name, version }, { instructions: MICA_AGENT_INSTRUCTIONS });
40
40
  }
41
41
  export function registerMicaPrompts(server) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliceshimada/mica",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Local MCP bridge for controlling live Wolfram Desktop / Mathematica notebooks.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -122,11 +122,31 @@ If[!AssociationQ[Quiet @ Check[$BridgePermissions, None]],
122
122
  $BridgePermissions = $DefaultBridgePermissions
123
123
  ];
124
124
 
125
- PalettePermissionRow[label_String, key_String] := Row[{
126
- Checkbox[Dynamic[$BridgePermissions[key], ($BridgePermissions[key] = #; Quiet @ Check[PostPermissions[], Null]) &]],
127
- Spacer[8],
128
- Style[label, 11]
129
- }];
125
+ If[!AssociationQ[Quiet @ Check[$BridgeNotebookPermissions, None]],
126
+ $BridgeNotebookPermissions = <||>
127
+ ];
128
+
129
+ NotebookPermissions[notebookId_String] := Lookup[$BridgeNotebookPermissions, notebookId, $DefaultBridgePermissions];
130
+
131
+ SetNotebookPermissions[notebookId_String, perms_Association] := (
132
+ $BridgeNotebookPermissions[notebookId] = perms
133
+ );
134
+
135
+ PalettePermissionRow[label_String, key_String, notebookId_:None] := Module[{perms, setter},
136
+ perms = If[StringQ[notebookId] && StringLength[notebookId] > 0,
137
+ NotebookPermissions[notebookId],
138
+ $BridgePermissions
139
+ ];
140
+ setter = If[StringQ[notebookId] && StringLength[notebookId] > 0,
141
+ Function[val, SetNotebookPermissions[notebookId, Join[perms, <|key -> val|>]]],
142
+ Function[val, $BridgePermissions[key] = val; Quiet @ Check[PostPermissions[], Null]]
143
+ ];
144
+ Row[{
145
+ Checkbox[Dynamic[perms[key], (setter[#]) &]],
146
+ Spacer[8],
147
+ Style[label, 11]
148
+ }]
149
+ ];
130
150
 
131
151
  PaletteStatusSummary[] := Module[{server, paletteConnected, notebookAttached, pendingRequests, attachedNotebook, error},
132
152
  server = Lookup[$LastStatus, "server", "unknown"];
@@ -332,14 +352,14 @@ PermissionsPanel[] := Panel[
332
352
  Column[
333
353
  {
334
354
  Style["Permissions", Bold],
335
- Grid[
355
+ Dynamic @ Grid[
336
356
  {
337
- {PalettePermissionRow["Read notebook", "ReadNotebook"]},
338
- {PalettePermissionRow["Insert cell", "InsertCell"]},
339
- {PalettePermissionRow["Modify cell", "ModifyCell"]},
340
- {PalettePermissionRow["Delete cell", "DeleteCell"]},
341
- {PalettePermissionRow["Run cell", "RunCell"]},
342
- {PalettePermissionRow["Save notebook", "SaveNotebook"]}
357
+ {PalettePermissionRow["Read notebook", "ReadNotebook", $ActiveNotebookId]},
358
+ {PalettePermissionRow["Insert cell", "InsertCell", $ActiveNotebookId]},
359
+ {PalettePermissionRow["Modify cell", "ModifyCell", $ActiveNotebookId]},
360
+ {PalettePermissionRow["Delete cell", "DeleteCell", $ActiveNotebookId]},
361
+ {PalettePermissionRow["Run cell", "RunCell", $ActiveNotebookId]},
362
+ {PalettePermissionRow["Save notebook", "SaveNotebook", $ActiveNotebookId]}
343
363
  },
344
364
  Alignment -> Left,
345
365
  Spacings -> {1, 0.35}
@@ -590,10 +610,16 @@ PostPermissions[] := Module[{payload = <|"permissions" -> $BridgePermissions|>},
590
610
  Quiet @ Check[BridgePost["/permissions", payload], $Failed]
591
611
  ];
592
612
 
593
- NeedsConfirmationQ[action_String] := Not @ TrueQ[$BridgePermissions[action]];
613
+ NeedsConfirmationQ[action_String, notebookId_:None] := Module[{perms},
614
+ perms = If[StringQ[notebookId] && StringLength[notebookId] > 0,
615
+ NotebookPermissions[notebookId],
616
+ $BridgePermissions
617
+ ];
618
+ Not @ TrueQ[perms[action]]
619
+ ];
594
620
 
595
- ConfirmAction[action_String, message_String] := If[
596
- NeedsConfirmationQ[action],
621
+ ConfirmAction[action_String, message_String, notebookId_:None] := If[
622
+ NeedsConfirmationQ[action, notebookId],
597
623
  ChoiceDialog[message, {"Allow" -> True, "Deny" -> False}],
598
624
  True
599
625
  ];
@@ -606,8 +632,8 @@ FailedRequestCode[failure_Failure] := Module[{code = failure[[1]]},
606
632
 
607
633
  FailedRequestMessage[failure_Failure] := Lookup[failure[[2]], "Message", Lookup[failure[[2]], "message", "The Wolfram bridge rejected the request."]];
608
634
 
609
- RequireReadPermission[] := If[
610
- Not @ ConfirmAction["ReadNotebook", "AI requests reading the notebook. Allow?"],
635
+ RequireReadPermission[notebookId_:None] := If[
636
+ Not @ ConfirmAction["ReadNotebook", "AI requests reading the notebook. Allow?", notebookId],
611
637
  $Canceled,
612
638
  True
613
639
  ];
@@ -628,7 +654,7 @@ NotebookInfo[nb_NotebookObject, notebookId_String] := <|
628
654
  "wolframVersion" -> ToString @ $VersionNumber,
629
655
  "platform" -> $OperatingSystem,
630
656
  "paletteId" -> $PaletteId,
631
- "permissions" -> $BridgePermissions
657
+ "permissions" -> NotebookPermissions[notebookId]
632
658
  |>;
633
659
 
634
660
  NotebookRecord[notebookId_String] := Lookup[$BridgeNotebooks, notebookId, <||>];
@@ -1137,9 +1163,9 @@ RefreshCellMap[notebookId_String] := Module[{record, nb, cells, idByCell, previo
1137
1163
  payload
1138
1164
  ];
1139
1165
 
1140
- ReadCellById[args_Association] := Module[{notebookId, record, cellId, cell, maxBytes, payload},
1141
- If[RequireReadPermission[] === $Canceled, Return[$Canceled]];
1142
- notebookId = TargetNotebookId[args];
1166
+ ReadCellById[args_Association] := Module[{notebookId, record, cellId, cell, maxBytes, payload},
1167
+ notebookId = TargetNotebookId[args];
1168
+ If[RequireReadPermission[notebookId] === $Canceled, Return[$Canceled]];
1143
1169
  If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1144
1170
  record = NotebookRecord[notebookId];
1145
1171
  If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
@@ -1158,9 +1184,9 @@ ReadCellById[args_Association] := Module[{notebookId, record, cellId, cell, maxB
1158
1184
  ]
1159
1185
  ];
1160
1186
 
1161
- GetCellOutputById[args_Association] := Module[{notebookId, record, cellId, cell, artifacts, maxBytes, payload},
1162
- If[RequireReadPermission[] === $Canceled, Return[$Canceled]];
1163
- notebookId = TargetNotebookId[args];
1187
+ GetCellOutputById[args_Association] := Module[{notebookId, record, cellId, cell, artifacts, maxBytes, payload},
1188
+ notebookId = TargetNotebookId[args];
1189
+ If[RequireReadPermission[notebookId] === $Canceled, Return[$Canceled]];
1164
1190
  If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1165
1191
  record = NotebookRecord[notebookId];
1166
1192
  If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
@@ -1178,9 +1204,9 @@ GetCellOutputById[args_Association] := Module[{notebookId, record, cellId, cell,
1178
1204
  ]
1179
1205
  ];
1180
1206
 
1181
- ReadArtifactById[args_Association] := Module[{notebookId, record, artifactId, parts, cellId, kind, indexText, index, cell, artifacts, artifactList, text, offset, limit, page, nextOffset, done},
1182
- If[RequireReadPermission[] === $Canceled, Return[$Canceled]];
1183
- notebookId = TargetNotebookId[args];
1207
+ ReadArtifactById[args_Association] := Module[{notebookId, record, artifactId, parts, cellId, kind, indexText, index, cell, artifacts, artifactList, text, offset, limit, page, nextOffset, done},
1208
+ notebookId = TargetNotebookId[args];
1209
+ If[RequireReadPermission[notebookId] === $Canceled, Return[$Canceled]];
1184
1210
  If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1185
1211
  record = NotebookRecord[notebookId];
1186
1212
  If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
@@ -1255,8 +1281,8 @@ InsertCellAtBeginning[notebook_NotebookObject, newCell_] := Module[{beforeCount,
1255
1281
  ];
1256
1282
 
1257
1283
  InsertCellRequest[args_Association] := Module[{notebookId, record, afterId, style, content, newCell, notebook, anchor, cells, inserted, refreshed},
1258
- If[Not @ ConfirmAction["InsertCell", "AI requests inserting 1 cell. Allow?"], Return[$Canceled]];
1259
1284
  notebookId = TargetNotebookId[args];
1285
+ If[Not @ ConfirmAction["InsertCell", "AI requests inserting 1 cell. Allow?", notebookId], Return[$Canceled]];
1260
1286
  If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1261
1287
  record = NotebookRecord[notebookId];
1262
1288
  If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
@@ -1291,8 +1317,8 @@ InsertCellRequest[args_Association] := Module[{notebookId, record, afterId, styl
1291
1317
  ];
1292
1318
 
1293
1319
  ModifyCellRequest[args_Association] := Module[{notebookId, record, notebook, cellId, cellMap, cell, content, style, newCell, cells, cellIndex, updatedCells, updatedCell, writeResult},
1294
- If[Not @ ConfirmAction["ModifyCell", "AI requests modifying 1 cell. Allow?"], Return[$Canceled]];
1295
1320
  notebookId = TargetNotebookId[args];
1321
+ If[Not @ ConfirmAction["ModifyCell", "AI requests modifying 1 cell. Allow?", notebookId], Return[$Canceled]];
1296
1322
  If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1297
1323
  record = NotebookRecord[notebookId];
1298
1324
  If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
@@ -1322,8 +1348,8 @@ ModifyCellRequest[args_Association] := Module[{notebookId, record, notebook, cel
1322
1348
  ];
1323
1349
 
1324
1350
  DeleteCellRequest[args_Association] := Module[{notebookId, record, notebook, cellId, cell, cellMap, artifacts, artifactIds, newCellMap},
1325
- If[Not @ ConfirmAction["DeleteCell", "AI requests deleting 1 cell. Allow?"], Return[$Canceled]];
1326
1351
  notebookId = TargetNotebookId[args];
1352
+ If[Not @ ConfirmAction["DeleteCell", "AI requests deleting 1 cell. Allow?", notebookId], Return[$Canceled]];
1327
1353
  If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1328
1354
  record = NotebookRecord[notebookId];
1329
1355
  If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
@@ -1345,8 +1371,8 @@ DeleteCellRequest[args_Association] := Module[{notebookId, record, notebook, cel
1345
1371
  ];
1346
1372
 
1347
1373
  RunCellRequest[args_Association] := Module[{notebookId, record, notebook, cellId, cell, timeoutSec = Lookup[args, "timeoutSec", 120], installedEpilog, evaluateResult},
1348
- If[Not @ ConfirmAction["RunCell", "AI requests running 1 cell. Allow?"], Return[$Canceled]];
1349
1374
  notebookId = TargetNotebookId[args];
1375
+ If[Not @ ConfirmAction["RunCell", "AI requests running 1 cell. Allow?", notebookId], Return[$Canceled]];
1350
1376
  If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1351
1377
  record = NotebookRecord[notebookId];
1352
1378
  If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
@@ -1384,8 +1410,8 @@ RunCellRequest[args_Association] := Module[{notebookId, record, notebook, cellId
1384
1410
  ];
1385
1411
 
1386
1412
  AbortEvaluationRequest[args_Association] := Module[{notebookId, record, notebook, runningCellId, runningRequestId, wasRunning},
1387
- If[Not @ ConfirmAction["RunCell", "AI requests aborting the running evaluation. Allow?"], Return[$Canceled]];
1388
1413
  notebookId = TargetNotebookId[args];
1414
+ If[Not @ ConfirmAction["RunCell", "AI requests aborting the running evaluation. Allow?", notebookId], Return[$Canceled]];
1389
1415
  If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1390
1416
  record = NotebookRecord[notebookId];
1391
1417
  If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
@@ -1435,13 +1461,15 @@ RestartKernelRequest[args_Association] := Module[{notebookId, record, notebook,
1435
1461
  If[evaluatorName === $ControlAgentEvaluatorName,
1436
1462
  Return[Failure["PROTECTED_EVALUATOR", <|"message" -> "Cannot restart the MICA control agent evaluator."|>]]
1437
1463
  ];
1464
+ Quiet @ Check[NotebookEvaluate[notebook, "Quit[]", InsertResults -> False], Null];
1465
+ Pause[0.5];
1438
1466
  Quiet @ Check[NotebookEvaluate[notebook, "Null", InsertResults -> False], Null];
1439
1467
  <|"status" -> "restarted", "notebookId" -> notebookId|>
1440
1468
  ];
1441
1469
 
1442
- SaveNotebookRequest[args_Association] := Module[{notebookId, record, notebook},
1443
- If[Not @ ConfirmAction["SaveNotebook", "AI requests saving the notebook. Allow?"], Return[$Canceled]];
1470
+ SaveNotebookRequest[args_Association] := Module[{notebookId, record, notebook},
1444
1471
  notebookId = TargetNotebookId[args];
1472
+ If[Not @ ConfirmAction["SaveNotebook", "AI requests saving the notebook. Allow?", notebookId], Return[$Canceled]];
1445
1473
  If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1446
1474
  record = NotebookRecord[notebookId];
1447
1475
  If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
@@ -1496,11 +1524,15 @@ AgentHeartbeat[] := BridgePost[
1496
1524
  |>
1497
1525
  ];
1498
1526
 
1499
- NotebookHeartbeatPayload[nb_NotebookObject] := Module[{savedPath, windowTitle, displayName, frontendObjectKey},
1527
+ NotebookHeartbeatPayload[nb_NotebookObject, notebookId_:None] := Module[{savedPath, windowTitle, displayName, frontendObjectKey, perms},
1500
1528
  savedPath = Quiet @ Check[ToString[Replace[NotebookFileName[nb], $Failed -> ""]], ""];
1501
1529
  frontendObjectKey = FrontendObjectKey[nb];
1502
1530
  windowTitle = NotebookWindowTitle[nb];
1503
1531
  displayName = NotebookDisplayNameForHeartbeat[nb, savedPath, frontendObjectKey];
1532
+ perms = If[StringQ[notebookId] && StringLength[notebookId] > 0,
1533
+ NotebookPermissions[notebookId],
1534
+ $DefaultBridgePermissions
1535
+ ];
1504
1536
  <|
1505
1537
  "agentSessionId" -> $AgentSessionId,
1506
1538
  "frontendObjectKey" -> frontendObjectKey,
@@ -1510,7 +1542,7 @@ NotebookHeartbeatPayload[nb_NotebookObject] := Module[{savedPath, windowTitle, d
1510
1542
  "savedPath" -> savedPath,
1511
1543
  "wolframVersion" -> ToString[$VersionNumber],
1512
1544
  "platform" -> $OperatingSystem,
1513
- "permissions" -> $BridgePermissions,
1545
+ "permissions" -> perms,
1514
1546
  "seenAt" -> UnixTimeMilliseconds[]
1515
1547
  |>
1516
1548
  ];
@@ -1544,7 +1576,8 @@ AgentHeartbeatNotebookClosure[notebookId_String] := Module[{record = Lookup[$Bri
1544
1576
  response = Quiet @ Check[BridgePost["/notebooks/" <> URLComponentEncodeString[notebookId] <> "/closed", <|"agentSessionId" -> $AgentSessionId|>], $Failed];
1545
1577
  If[AssociationQ[record] && AssociationQ[response] && TrueQ[Lookup[response, "ok", False]],
1546
1578
  If[!TrueQ[Lookup[record, "closed", False]],
1547
- $BridgeNotebooks[notebookId] = Join[record, <|"closed" -> True|>]
1579
+ $BridgeNotebooks[notebookId] = Join[record, <|"closed" -> True|>];
1580
+ KeyDropFrom[$BridgeNotebookPermissions, notebookId]
1548
1581
  ]
1549
1582
  ];
1550
1583
  response
@@ -1568,7 +1601,7 @@ HeartbeatNotebooks[] := Module[{notebooks = AgentVisibleNotebooks[], visibleNote
1568
1601
  None
1569
1602
  ];
1570
1603
  If[StringQ[localNotebookId] && StringLength[localNotebookId] > 0, AppendTo[visibleNotebookIds, localNotebookId]];
1571
- payload = NotebookHeartbeatPayload[nb];
1604
+ payload = NotebookHeartbeatPayload[nb, localNotebookId];
1572
1605
  response = Quiet @ Check[BridgePost["/notebooks/heartbeat", payload], $Failed];
1573
1606
  If[AssociationQ[response],
1574
1607
  notebookId = Lookup[Lookup[response, "notebook", <||>], "notebookId", Lookup[response, "notebookId", None]];
@@ -1801,7 +1834,7 @@ ExecuteRequest[request_Association] := Module[{requestId, tool, args, result},
1801
1834
  !AssociationQ[args], Failure["BAD_REQUEST", <|"Message" -> "Request arguments must be an association."|>],
1802
1835
  True,
1803
1836
  Switch[tool,
1804
- "mma_list_cells", If[RequireReadPermission[] === $Canceled, $Canceled, Module[{notebookId = TargetNotebookId[args], refresh}, If[!StringQ[notebookId] || StringLength[notebookId] == 0, Failure["BAD_REQUEST", <|"Message" -> "No notebook is selected."|>], refresh = RefreshCellMap[notebookId]; If[MatchQ[refresh, _Failure], refresh, <|"cells" -> refresh|>]]]],
1837
+ "mma_list_cells", If[RequireReadPermission[TargetNotebookId[args]] === $Canceled, $Canceled, Module[{notebookId = TargetNotebookId[args], refresh}, If[!StringQ[notebookId] || StringLength[notebookId] == 0, Failure["BAD_REQUEST", <|"Message" -> "No notebook is selected."|>], refresh = RefreshCellMap[notebookId]; If[MatchQ[refresh, _Failure], refresh, <|"cells" -> refresh|>]]]],
1805
1838
  "mma_read_cell", ReadCellById[args],
1806
1839
  "mma_insert_cell", InsertCellRequest[args],
1807
1840
  "mma_modify_cell", ModifyCellRequest[args],