@plutojl/mcp 0.2.0 → 0.4.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.
- package/dist/cli.cjs +347 -61
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -31779,7 +31779,7 @@ var require_cookie = __commonJS({
|
|
|
31779
31779
|
"../../node_modules/cookie/index.js"(exports2) {
|
|
31780
31780
|
"use strict";
|
|
31781
31781
|
exports2.parse = parse6;
|
|
31782
|
-
exports2.serialize =
|
|
31782
|
+
exports2.serialize = serialize2;
|
|
31783
31783
|
var __toString = Object.prototype.toString;
|
|
31784
31784
|
var __hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
31785
31785
|
var cookieNameRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
@@ -31838,7 +31838,7 @@ var require_cookie = __commonJS({
|
|
|
31838
31838
|
}
|
|
31839
31839
|
return min;
|
|
31840
31840
|
}
|
|
31841
|
-
function
|
|
31841
|
+
function serialize2(name, val, opt) {
|
|
31842
31842
|
var enc = opt && opt.encode || encodeURIComponent;
|
|
31843
31843
|
if (typeof enc !== "function") {
|
|
31844
31844
|
throw new TypeError("option encode is invalid");
|
|
@@ -33743,7 +33743,7 @@ var require_uri_all = __commonJS({
|
|
|
33743
33743
|
}
|
|
33744
33744
|
return output.join("");
|
|
33745
33745
|
}
|
|
33746
|
-
function
|
|
33746
|
+
function serialize2(components) {
|
|
33747
33747
|
var options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
|
|
33748
33748
|
var protocol = options.iri ? IRI_PROTOCOL : URI_PROTOCOL;
|
|
33749
33749
|
var uriTokens = [];
|
|
@@ -33799,8 +33799,8 @@ var require_uri_all = __commonJS({
|
|
|
33799
33799
|
var skipNormalization = arguments[3];
|
|
33800
33800
|
var target = {};
|
|
33801
33801
|
if (!skipNormalization) {
|
|
33802
|
-
base2 = parse6(
|
|
33803
|
-
relative = parse6(
|
|
33802
|
+
base2 = parse6(serialize2(base2, options), options);
|
|
33803
|
+
relative = parse6(serialize2(relative, options), options);
|
|
33804
33804
|
}
|
|
33805
33805
|
options = options || {};
|
|
33806
33806
|
if (!options.tolerant && relative.scheme) {
|
|
@@ -33851,26 +33851,26 @@ var require_uri_all = __commonJS({
|
|
|
33851
33851
|
}
|
|
33852
33852
|
function resolve(baseURI, relativeURI, options) {
|
|
33853
33853
|
var schemelessOptions = assign2({ scheme: "null" }, options);
|
|
33854
|
-
return
|
|
33854
|
+
return serialize2(resolveComponents(parse6(baseURI, schemelessOptions), parse6(relativeURI, schemelessOptions), schemelessOptions, true), schemelessOptions);
|
|
33855
33855
|
}
|
|
33856
33856
|
function normalize(uri, options) {
|
|
33857
33857
|
if (typeof uri === "string") {
|
|
33858
|
-
uri =
|
|
33858
|
+
uri = serialize2(parse6(uri, options), options);
|
|
33859
33859
|
} else if (typeOf(uri) === "object") {
|
|
33860
|
-
uri = parse6(
|
|
33860
|
+
uri = parse6(serialize2(uri, options), options);
|
|
33861
33861
|
}
|
|
33862
33862
|
return uri;
|
|
33863
33863
|
}
|
|
33864
33864
|
function equal(uriA, uriB, options) {
|
|
33865
33865
|
if (typeof uriA === "string") {
|
|
33866
|
-
uriA =
|
|
33866
|
+
uriA = serialize2(parse6(uriA, options), options);
|
|
33867
33867
|
} else if (typeOf(uriA) === "object") {
|
|
33868
|
-
uriA =
|
|
33868
|
+
uriA = serialize2(uriA, options);
|
|
33869
33869
|
}
|
|
33870
33870
|
if (typeof uriB === "string") {
|
|
33871
|
-
uriB =
|
|
33871
|
+
uriB = serialize2(parse6(uriB, options), options);
|
|
33872
33872
|
} else if (typeOf(uriB) === "object") {
|
|
33873
|
-
uriB =
|
|
33873
|
+
uriB = serialize2(uriB, options);
|
|
33874
33874
|
}
|
|
33875
33875
|
return uriA === uriB;
|
|
33876
33876
|
}
|
|
@@ -33889,7 +33889,7 @@ var require_uri_all = __commonJS({
|
|
|
33889
33889
|
}
|
|
33890
33890
|
return components;
|
|
33891
33891
|
},
|
|
33892
|
-
serialize: function
|
|
33892
|
+
serialize: function serialize3(components, options) {
|
|
33893
33893
|
var secure = String(components.scheme).toLowerCase() === "https";
|
|
33894
33894
|
if (components.port === (secure ? 443 : 80) || components.port === "") {
|
|
33895
33895
|
components.port = void 0;
|
|
@@ -33920,7 +33920,7 @@ var require_uri_all = __commonJS({
|
|
|
33920
33920
|
wsComponents.query = void 0;
|
|
33921
33921
|
return wsComponents;
|
|
33922
33922
|
},
|
|
33923
|
-
serialize: function
|
|
33923
|
+
serialize: function serialize3(wsComponents, options) {
|
|
33924
33924
|
if (wsComponents.port === (isSecure(wsComponents) ? 443 : 80) || wsComponents.port === "") {
|
|
33925
33925
|
wsComponents.port = void 0;
|
|
33926
33926
|
}
|
|
@@ -34094,7 +34094,7 @@ var require_uri_all = __commonJS({
|
|
|
34094
34094
|
}
|
|
34095
34095
|
return uuidComponents;
|
|
34096
34096
|
},
|
|
34097
|
-
serialize: function
|
|
34097
|
+
serialize: function serialize3(uuidComponents, options) {
|
|
34098
34098
|
var urnComponents = uuidComponents;
|
|
34099
34099
|
urnComponents.nss = (uuidComponents.uuid || "").toLowerCase();
|
|
34100
34100
|
return urnComponents;
|
|
@@ -34112,7 +34112,7 @@ var require_uri_all = __commonJS({
|
|
|
34112
34112
|
exports3.pctDecChars = pctDecChars;
|
|
34113
34113
|
exports3.parse = parse6;
|
|
34114
34114
|
exports3.removeDotSegments = removeDotSegments;
|
|
34115
|
-
exports3.serialize =
|
|
34115
|
+
exports3.serialize = serialize2;
|
|
34116
34116
|
exports3.resolveComponents = resolveComponents;
|
|
34117
34117
|
exports3.resolve = resolve;
|
|
34118
34118
|
exports3.normalize = normalize;
|
|
@@ -39140,8 +39140,8 @@ var require_ajv = __commonJS({
|
|
|
39140
39140
|
delete this._refs[schemaKeyRef];
|
|
39141
39141
|
return this;
|
|
39142
39142
|
case "object":
|
|
39143
|
-
var
|
|
39144
|
-
var cacheKey =
|
|
39143
|
+
var serialize2 = this._opts.serialize;
|
|
39144
|
+
var cacheKey = serialize2 ? serialize2(schemaKeyRef) : schemaKeyRef;
|
|
39145
39145
|
this._cache.del(cacheKey);
|
|
39146
39146
|
var id = this._getId(schemaKeyRef);
|
|
39147
39147
|
if (id) {
|
|
@@ -39164,8 +39164,8 @@ var require_ajv = __commonJS({
|
|
|
39164
39164
|
function _addSchema(schema, skipValidation, meta, shouldAddSchema) {
|
|
39165
39165
|
if (typeof schema != "object" && typeof schema != "boolean")
|
|
39166
39166
|
throw new Error("schema should be object or boolean");
|
|
39167
|
-
var
|
|
39168
|
-
var cacheKey =
|
|
39167
|
+
var serialize2 = this._opts.serialize;
|
|
39168
|
+
var cacheKey = serialize2 ? serialize2(schema) : schema;
|
|
39169
39169
|
var cached = this._cache.get(cacheKey);
|
|
39170
39170
|
if (cached) return cached;
|
|
39171
39171
|
shouldAddSchema = shouldAddSchema || this._opts.addUsedSchema !== false;
|
|
@@ -39349,6 +39349,7 @@ Run options:
|
|
|
39349
39349
|
--pluto-port <port> Pluto server port (default: ${DEFAULTS.plutoPort})
|
|
39350
39350
|
--pluto-url <url> Connect to existing Pluto server (skip starting one)
|
|
39351
39351
|
--julia-version <ver> Julia version via juliaup (default: ${DEFAULTS.juliaVersion})
|
|
39352
|
+
--no-pluto Start MCP server only, without starting Pluto
|
|
39352
39353
|
|
|
39353
39354
|
Install options:
|
|
39354
39355
|
--target <target> Config target: claude-code, copilot, all (default: claude-code)
|
|
@@ -39440,6 +39441,8 @@ function parseArgs(argv) {
|
|
|
39440
39441
|
args.force = true;
|
|
39441
39442
|
} else if (flag === "--raw") {
|
|
39442
39443
|
args.raw = true;
|
|
39444
|
+
} else if (flag === "--no-pluto") {
|
|
39445
|
+
args.noPluto = true;
|
|
39443
39446
|
} else {
|
|
39444
39447
|
console.warn(`Unknown flag: ${flag}`);
|
|
39445
39448
|
}
|
|
@@ -39478,7 +39481,8 @@ function resolveRunConfig(args) {
|
|
|
39478
39481
|
plutoPort: args.plutoPort ?? envInt("PLUTO_PORT") ?? file.plutoPort ?? DEFAULTS.plutoPort,
|
|
39479
39482
|
plutoUrl: args.plutoUrl ?? process.env.PLUTO_SERVER_URL ?? file.serverUrl ?? void 0,
|
|
39480
39483
|
juliaVersion: args.juliaVersion ?? process.env.JULIA_VERSION ?? file.juliaVersion ?? DEFAULTS.juliaVersion,
|
|
39481
|
-
workDir
|
|
39484
|
+
workDir,
|
|
39485
|
+
noPluto: args.noPluto ?? false
|
|
39482
39486
|
};
|
|
39483
39487
|
}
|
|
39484
39488
|
function resolveInstallArgs(args) {
|
|
@@ -54678,6 +54682,116 @@ end`,
|
|
|
54678
54682
|
});
|
|
54679
54683
|
}
|
|
54680
54684
|
};
|
|
54685
|
+
var NOTEBOOK_HEADER = "### A Pluto.jl notebook ###";
|
|
54686
|
+
var CELL_ID_DELIMITER = "# \u2554\u2550\u2561 ";
|
|
54687
|
+
var CELL_METADATA_PREFIX = "# \u2560\u2550\u2561 ";
|
|
54688
|
+
var ORDER_DELIMITER = "# \u2560\u2550";
|
|
54689
|
+
var ORDER_DELIMITER_FOLDED = "# \u255F\u2500";
|
|
54690
|
+
var DISABLED_PREFIX = "#=\u2560\u2550\u2561\n";
|
|
54691
|
+
var DISABLED_SUFFIX = "\n \u2560\u2550\u2561 =#";
|
|
54692
|
+
var PTOML_CELL_ID = "00000000-0000-0000-0000-000000000001";
|
|
54693
|
+
var MTOML_CELL_ID$1 = "00000000-0000-0000-0000-000000000002";
|
|
54694
|
+
var DEFAULT_CELL_METADATA = {
|
|
54695
|
+
disabled: false,
|
|
54696
|
+
show_logs: true,
|
|
54697
|
+
skip_as_script: false
|
|
54698
|
+
};
|
|
54699
|
+
function generateBindMacro() {
|
|
54700
|
+
return [
|
|
54701
|
+
"",
|
|
54702
|
+
"# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).",
|
|
54703
|
+
"macro bind(def, element)",
|
|
54704
|
+
" quote",
|
|
54705
|
+
' local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end',
|
|
54706
|
+
" local el = $(esc(element))",
|
|
54707
|
+
" global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)",
|
|
54708
|
+
" el",
|
|
54709
|
+
" end",
|
|
54710
|
+
"end"
|
|
54711
|
+
];
|
|
54712
|
+
}
|
|
54713
|
+
function serializeCellMetadata(metadata) {
|
|
54714
|
+
const lines = [];
|
|
54715
|
+
if (metadata.disabled && !metadata._implicit_disabled) {
|
|
54716
|
+
lines.push(CELL_METADATA_PREFIX + "disabled = true");
|
|
54717
|
+
}
|
|
54718
|
+
if (metadata.show_logs !== void 0 && metadata.show_logs !== DEFAULT_CELL_METADATA.show_logs) {
|
|
54719
|
+
lines.push(CELL_METADATA_PREFIX + `show_logs = ${metadata.show_logs}`);
|
|
54720
|
+
}
|
|
54721
|
+
if (metadata.skip_as_script !== void 0 && metadata.skip_as_script !== DEFAULT_CELL_METADATA.skip_as_script) {
|
|
54722
|
+
lines.push(
|
|
54723
|
+
CELL_METADATA_PREFIX + `skip_as_script = ${metadata.skip_as_script}`
|
|
54724
|
+
);
|
|
54725
|
+
}
|
|
54726
|
+
return [
|
|
54727
|
+
...lines,
|
|
54728
|
+
...Object.entries(metadata).filter(([name, entry]) => {
|
|
54729
|
+
return !["skip_as_script", "show_logs", "disabled"].includes(name);
|
|
54730
|
+
}).map(
|
|
54731
|
+
([name, entry]) => `${CELL_METADATA_PREFIX}${name} = ${JSON.stringify(entry)}`
|
|
54732
|
+
)
|
|
54733
|
+
];
|
|
54734
|
+
}
|
|
54735
|
+
function serialize(notebookData) {
|
|
54736
|
+
const lines = [];
|
|
54737
|
+
lines.push(NOTEBOOK_HEADER);
|
|
54738
|
+
lines.push(`# ${notebookData.pluto_version || "v0.20.10"}`);
|
|
54739
|
+
lines.push("");
|
|
54740
|
+
if (notebookData.metadata && notebookData.metadata._raw_metadata_lines) {
|
|
54741
|
+
for (const metadataLine of notebookData.metadata._raw_metadata_lines) {
|
|
54742
|
+
lines.push("#> " + metadataLine);
|
|
54743
|
+
}
|
|
54744
|
+
lines.push("");
|
|
54745
|
+
}
|
|
54746
|
+
lines.push("using Markdown");
|
|
54747
|
+
lines.push("using InteractiveUtils");
|
|
54748
|
+
if (notebookData._has_bind_macro) {
|
|
54749
|
+
lines.push(...generateBindMacro());
|
|
54750
|
+
}
|
|
54751
|
+
lines.push("");
|
|
54752
|
+
const topologicalOrder = notebookData._topological_order || notebookData.cell_order || [];
|
|
54753
|
+
const cellInputs = notebookData.cell_inputs || {};
|
|
54754
|
+
for (const cellId of topologicalOrder) {
|
|
54755
|
+
const cellInput = cellInputs[cellId];
|
|
54756
|
+
if (!cellInput) continue;
|
|
54757
|
+
lines.push(CELL_ID_DELIMITER + cellId);
|
|
54758
|
+
const metadata = cellInput.metadata || {};
|
|
54759
|
+
const metadataLines = serializeCellMetadata(metadata);
|
|
54760
|
+
lines.push(...metadataLines);
|
|
54761
|
+
let code = cellInput.code || "";
|
|
54762
|
+
if (metadata.disabled && !code.startsWith(DISABLED_PREFIX)) {
|
|
54763
|
+
code = DISABLED_PREFIX + code + DISABLED_SUFFIX;
|
|
54764
|
+
}
|
|
54765
|
+
lines.push(code);
|
|
54766
|
+
lines.push("");
|
|
54767
|
+
}
|
|
54768
|
+
const packageCells = notebookData._package_cells || {};
|
|
54769
|
+
if (packageCells[PTOML_CELL_ID]) {
|
|
54770
|
+
lines.push(CELL_ID_DELIMITER + PTOML_CELL_ID);
|
|
54771
|
+
lines.push(packageCells[PTOML_CELL_ID]);
|
|
54772
|
+
lines.push("");
|
|
54773
|
+
}
|
|
54774
|
+
if (packageCells[MTOML_CELL_ID$1]) {
|
|
54775
|
+
lines.push(CELL_ID_DELIMITER + MTOML_CELL_ID$1);
|
|
54776
|
+
lines.push(packageCells[MTOML_CELL_ID$1]);
|
|
54777
|
+
lines.push("");
|
|
54778
|
+
}
|
|
54779
|
+
lines.push(CELL_ID_DELIMITER + "Cell order:");
|
|
54780
|
+
const cellOrder = notebookData.cell_order || [];
|
|
54781
|
+
for (const cellId of cellOrder) {
|
|
54782
|
+
const cellInput = cellInputs[cellId];
|
|
54783
|
+
if (!cellInput) continue;
|
|
54784
|
+
const delimiter = cellInput.code_folded ? ORDER_DELIMITER_FOLDED : ORDER_DELIMITER;
|
|
54785
|
+
lines.push(delimiter + cellId);
|
|
54786
|
+
}
|
|
54787
|
+
if (packageCells[PTOML_CELL_ID]) {
|
|
54788
|
+
lines.push(ORDER_DELIMITER_FOLDED + PTOML_CELL_ID);
|
|
54789
|
+
}
|
|
54790
|
+
if (packageCells[MTOML_CELL_ID$1]) {
|
|
54791
|
+
lines.push(ORDER_DELIMITER_FOLDED + MTOML_CELL_ID$1);
|
|
54792
|
+
}
|
|
54793
|
+
return lines.join("\n") + "\n";
|
|
54794
|
+
}
|
|
54681
54795
|
|
|
54682
54796
|
// ../../src/plutoManager.ts
|
|
54683
54797
|
var import_events = require("events");
|
|
@@ -54949,6 +55063,16 @@ var PlutoManager = class {
|
|
|
54949
55063
|
await worker2.deleteSnippets([result.cell_id]);
|
|
54950
55064
|
return result;
|
|
54951
55065
|
}
|
|
55066
|
+
/**
|
|
55067
|
+
* Get the serialized notebook content (.jl format) for saving to disk
|
|
55068
|
+
*/
|
|
55069
|
+
getNotebookContent(worker2) {
|
|
55070
|
+
const state = worker2.getState();
|
|
55071
|
+
if (!state) {
|
|
55072
|
+
throw new Error("Notebook state not available");
|
|
55073
|
+
}
|
|
55074
|
+
return serialize(state);
|
|
55075
|
+
}
|
|
54952
55076
|
/**
|
|
54953
55077
|
* Close all notebook connections
|
|
54954
55078
|
*/
|
|
@@ -54984,6 +55108,7 @@ var PlutoManager = class {
|
|
|
54984
55108
|
|
|
54985
55109
|
// ../../src/mcp-server-http.ts
|
|
54986
55110
|
var import_express = __toESM(require_express2(), 1);
|
|
55111
|
+
var import_promises = require("fs/promises");
|
|
54987
55112
|
|
|
54988
55113
|
// ../../node_modules/zod/v3/external.js
|
|
54989
55114
|
var external_exports = {};
|
|
@@ -62666,7 +62791,7 @@ data: ${JSON.stringify(message)}
|
|
|
62666
62791
|
};
|
|
62667
62792
|
|
|
62668
62793
|
// ../../src/PLUTO_GUIDE.md
|
|
62669
|
-
var PLUTO_GUIDE_default = '# Pluto.jl Notebook Guide for AI Tools\n\nThis guide explains how to work with Pluto.jl notebooks, including cell structure, reactivity rules, PlutoUI components, and best practices.\n\n## Table of Contents\n\n- [Notebook Structure](#notebook-structure)\n- [Cell Rules and Reactivity](#cell-rules-and-reactivity)\n- [Markdown Cells](#markdown-cells)\n- [PlutoUI Components](#plutoui-components)\n- [Combining Markdown and PlutoUI](#combining-markdown-and-plutoui)\n- [Best Practices](#best-practices)\n- [Common Patterns](#common-patterns)\n\n---\n\n## Notebook Structure\n\n### File Format\n\nA Pluto notebook is a Julia file (`.jl`) with a specific structure:\n\n```julia\n### A Pluto.jl notebook ###\n# v0.20.19\n\nusing Markdown\nusing InteractiveUtils\n\n# \u2554\u2550\u2561 cell-uuid-1\n# \u2560\u2550\u2561 disabled = false\n# \u2560\u2550\u2561 show_logs = true\n# \u2560\u2550\u2561 skip_as_script = false\nmd"""\n# Your content here\n"""\n\n# \u2554\u2550\u2561 cell-uuid-2\nx = 10\n\n# \u2554\u2550\u2561 Cell order:\n# \u2560\u2550cell-uuid-1\n# \u2560\u2550cell-uuid-2\n```\n\n### Cell Markers\n\nEach cell starts with a unique identifier:\n\n- `# \u2554\u2550\u2561 <uuid>` - Cell boundary marker\n- Cell UUIDs are automatically generated\n- Cells contain Julia code, markdown, or expressions\n\n### Cell Metadata\n\nCells can have metadata comments:\n\n- `# \u2560\u2550\u2561 disabled = false` - Cell execution state\n- `# \u2560\u2550\u2561 show_logs = true` - Show cell output logs\n- `# \u2560\u2550\u2561 skip_as_script = false` - Include cell when exporting as script\n\n### Cell Order Section\n\nAt the end of the notebook:\n\n```julia\n# \u2554\u2550\u2561 Cell order:\n# \u2560\u2550cell-uuid-1\n# \u2560\u2550cell-uuid-2\n# \u2560\u2550cell-uuid-3\n```\n\nThis defines the display order of cells (not execution order).\n\n---\n\n## Cell Rules and Reactivity\n\n### Reactive Execution Model\n\n**Key Principle**: Pluto automatically determines execution order based on variable dependencies.\n\n#### Rules\n\n1. **One Variable per Cell (Assignment)**\n\n ```julia\n # \u2705 CORRECT\n x = 10\n\n # \u274C WRONG - Cannot assign same variable in another cell\n x = 20 # Error: Multiple definitions of x\n ```\n\n2. **Use `begin...end` for Multiple Statements**\n\n ```julia\n # \u2705 CORRECT\n begin\n x = 10\n y = 20\n z = x + y\n end\n ```\n\n3. **Automatic Dependency Tracking**\n\n ```julia\n # Cell 1\n a = 5\n\n # Cell 2 (depends on Cell 1)\n b = a * 2 # Automatically re-runs when \'a\' changes\n\n # Cell 3 (depends on Cell 2)\n c = b + 10 # Automatically re-runs when \'b\' changes\n ```\n\n4. **No Hidden State**\n - Every variable is defined exactly once\n - Execution order is determined by dependencies, not cell order\n - Deleting a cell removes its variables completely\n\n5. **Import/Package Management**\n\n ```julia\n begin\n import Pkg\n Pkg.activate(; temp=true)\n Pkg.add(["Plots", "DataFrames", "PlutoUI"])\n using Plots\n using PlutoUI\n end\n ```\n\n---\n\n## Markdown Cells\n\n### Basic Markdown Syntax\n\nMarkdown cells use triple-quote syntax:\n\n```julia\nmd"""\n# Heading 1\n## Heading 2\n### Heading 3\n\n**Bold text**\n*Italic text*\n\n- Bullet point 1\n- Bullet point 2\n\n1. Numbered item 1\n2. Numbered item 2\n\n[Link text](https://example.com)\n"""\n```\n\n### LaTeX Math\n\n```julia\nmd"""\nInline math: $E = mc^2$\n\nDisplay math:\n$$\n\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}\n$$\n"""\n```\n\n### Interpolating Variables\n\nUse `$()` to embed Julia expressions:\n\n```julia\nx = 42\n\nmd"""\nThe value of x is $(x).\n\nComputed value: $(x * 2)\n"""\n```\n\n### Tables in Markdown\n\n```julia\nmd"""\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Value A | Value B | Value C |\n| Value D | Value E | Value F |\n"""\n```\n\n---\n\n## PlutoUI Components\n\nPlutoUI provides interactive widgets using the `@bind` macro.\n\n### Installation\n\n```julia\nbegin\n import Pkg\n Pkg.add("PlutoUI")\n using PlutoUI\nend\n```\n\n### @bind Macro\n\nThe `@bind` macro connects a UI element to a variable:\n\n```julia\n@bind variable_name Widget(options...)\n```\n\n### Available Components\n\n#### 1. Slider\n\nCreates a slider for numeric input.\n\n```julia\n# Basic slider\n@bind x Slider(1:100)\n\n# Slider with default value and display\n@bind temperature Slider(0:0.1:100, default=25, show_value=true)\n\n# Slider with custom range\n@bind alpha Slider(0.0:0.01:1.0, default=0.5)\n```\n\n**Parameters:**\n\n- `range` - Range of values (e.g., `1:10` or `0.0:0.1:1.0`)\n- `default` - Initial value\n- `show_value` - Display current value (true/false)\n\n#### 2. TextField\n\nText input field.\n\n```julia\n# Single-line text field\n@bind name TextField()\n\n# Text field with default value\n@bind description TextField(default="Enter description")\n\n# Multi-line textarea\n@bind notes TextField((50, 10)) # (cols, rows)\n```\n\n**Parameters:**\n\n- `default` - Initial text value\n- `dims=(cols, rows)` - For multi-line textarea\n\n#### 3. NumberField\n\nNumeric input field.\n\n```julia\n# Number field with range\n@bind count NumberField(1:100, default=10)\n\n# Float number field\n@bind value NumberField(0.0:0.1:10.0, default=5.0)\n```\n\n**Parameters:**\n\n- `range` - Valid number range\n- `default` - Initial value\n\n#### 4. CheckBox\n\nBoolean checkbox.\n\n```julia\n# Simple checkbox\n@bind enabled CheckBox()\n\n# Checkbox with default value\n@bind show_details CheckBox(default=true)\n```\n\n**Parameters:**\n\n- `default` - Initial state (true/false)\n\n#### 5. Select\n\nDropdown selection.\n\n```julia\n# Select from array\n@bind color Select(["red", "green", "blue"])\n\n# Select with default\n@bind option Select(["Option A", "Option B", "Option C"], default="Option B")\n\n# Select from pairs (value => label)\n@bind choice Select([1 => "First", 2 => "Second", 3 => "Third"])\n```\n\n**Parameters:**\n\n- `options` - Array of options or pairs\n- `default` - Initially selected value\n\n#### 6. MultiSelect\n\nMultiple selection dropdown.\n\n```julia\n# Multi-select\n@bind colors MultiSelect(["red", "green", "blue", "yellow"])\n\n# Multi-select with defaults\n@bind selected MultiSelect(\n ["A", "B", "C", "D"],\n default=["A", "C"]\n)\n```\n\n#### 7. Button\n\nClickable button.\n\n```julia\n# Simple button\n@bind clicked Button("Click me!")\n\n# Button that increments on each click\n@bind click_count Button("Increment")\n```\n\n**Note**: Button sends the same value each time clicked, triggering reactive cells.\n\n#### 8. Radio\n\nRadio button group (single selection).\n\n```julia\n@bind choice Radio(["Option 1", "Option 2", "Option 3"])\n\n@bind size Radio(["Small", "Medium", "Large"], default="Medium")\n```\n\n#### 9. FilePicker\n\nFile upload widget.\n\n```julia\n@bind uploaded_file FilePicker()\n\n# Access file content\nfile_data = uploaded_file["data"]\nfile_name = uploaded_file["name"]\n```\n\n#### 10. Clock\n\nTimer that ticks at regular intervals.\n\n```julia\n# Tick every second\n@bind tick Clock()\n\n# Tick every 0.5 seconds\n@bind tick Clock(0.5)\n```\n\n#### 11. DateField\n\nDate picker.\n\n```julia\n@bind selected_date DateField()\n\n@bind start_date DateField(default=Dates.today())\n```\n\n---\n\n## Combining Markdown and PlutoUI\n\n### Inline Widgets in Tables\n\nYou can embed PlutoUI widgets directly in markdown tables:\n\n```julia\nmd"""\n| Parameter | Description | Units | Value |\n| --------- | ----------- | ----- | ----- |\n| `T_inf` | Ambient temperature | K | $(@bind T_inf NumberField(0:500, default=300)) |\n| `h` | Heat transfer coefficient | W/(m\xB2\xB7K) | $(@bind h Slider(0.0:0.1:1.0, default=0.7)) |\n| `mass` | Mass | kg | $(@bind m NumberField(0.0:0.1:10.0, default=1.0)) |\n"""\n```\n\n### Using Widget Values in Markdown\n\n```julia\n@bind speed Slider(1:100, default=50, show_value=true)\n\nmd"""\n## Speed Control\n\nCurrent speed: $(speed) km/h\n\nStatus: $(speed > 80 ? "\u{1F534} Fast" : "\u{1F7E2} Normal")\n"""\n```\n\n### Embedded Plots and Computations\n\n```julia\nmd"""\n# Wave Parameters\n\n| Parameter | Value |\n| --------- | ----- |\n| Amplitude | $(@bind amplitude Slider(0.1:0.1:5.0, default=1.0, show_value=true)) |\n| Frequency | $(@bind frequency Slider(0.1:0.1:10.0, default=1.0, show_value=true)) |\n\n## Waveform\n\n$(begin\n x = 0:0.01:2\u03C0\n y = amplitude .* sin.(frequency .* x)\n plot(x, y, label="sin wave", xlabel="x", ylabel="y")\nend)\n"""\n```\n\n### Dynamic Content Generation\n\n```julia\nbegin\n @bind param1 NumberField(0:100, default=50)\n @bind param2 Slider(0.0:0.1:1.0, default=0.5)\n\n result = param1 * param2\n\n md"""\n # Interactive Calculator\n\n | Input | Value |\n | ----- | ----- |\n | Parameter 1 | $(param1) |\n | Parameter 2 | $(param2) |\n | **Result** | **$(result)** |\n\n The computation shows: $(param1) \xD7 $(param2) = $(result)\n """\nend\n```\n\n---\n\n## Best Practices\n\n### 1. Package Management\n\nAlways use a cell at the beginning for package management:\n\n```julia\nbegin\n import Pkg\n Pkg.activate(; temp=true)\n Pkg.add(["Plots", "DataFrames", "PlutoUI"])\n using Plots\n using DataFrames\n using PlutoUI\nend\n```\n\n### 2. Organize with Markdown Headers\n\n```julia\nmd"""\n# Section 1: Data Loading\n\nLoad and prepare the data.\n"""\n\n# ... code cells ...\n\nmd"""\n# Section 2: Analysis\n\nPerform the analysis.\n"""\n\n# ... code cells ...\n```\n\n### 3. Use `begin...end` for Complex Logic\n\n```julia\nbegin\n # Multiple related computations\n data = load_data()\n cleaned = clean_data(data)\n result = analyze(cleaned)\n result\nend\n```\n\n### 4. Display the Last Expression\n\nThe last expression in a cell is automatically displayed:\n\n```julia\nbegin\n x = 10\n y = 20\n x + y # This value is displayed\nend\n```\n\n### 5. Suppress Output with Semicolon\n\n```julia\n# Suppress display\nlarge_data = load_large_dataset();\n\n# Show specific output\nprintln("Data loaded successfully")\n```\n\n### 6. Use @doc for Documentation\n\n```julia\nmd"""\n## Function Documentation\n\n$(@doc my_function)\n"""\n```\n\n### 7. Bind Multiple Related Widgets\n\n```julia\nmd"""\n## Configuration\n\n| Parameter | Value |\n| --------- | ----- |\n| X | $(@bind x Slider(1:10, default=5)) |\n| Y | $(@bind y Slider(1:10, default=5)) |\n\nSum: $(x + y)\nProduct: $(x * y)\n"""\n```\n\n---\n\n## Common Patterns\n\n### Pattern 1: Interactive Parameter Sweep\n\n```julia\n# Define parameters with widgets\n@bind param Slider(1:100, default=50, show_value=true)\n\n# Compute based on parameter\nresult = expensive_computation(param)\n\n# Display results\nplot(result)\n```\n\n### Pattern 2: Conditional Display\n\n```julia\n@bind show_advanced CheckBox(default=false)\n```\n\n```julia\n\nmd"""\nShow advanced options: $(show_advanced ? "\u2705 Enabled" : "\u274C Disabled")\n\n$(if show_advanced\n md"## Advanced Settings\n\n Configure advanced parameters here."\nelse\n md""\nend)\n"""\n```\n\n### Pattern 3: Multi-Step Workflow\n\n```julia\nmd"""\n# Step 1: Select Dataset\n$(@bind dataset Select(["Dataset A", "Dataset B", "Dataset C"]))\n"""\n\n# Load selected dataset\ndata = load_dataset(dataset)\n\nmd"""\n# Step 2: Configure Analysis\n$(@bind threshold Slider(0:0.1:1, default=0.5, show_value=true))\n"""\n\n# Perform analysis\nresults = analyze(data, threshold)\n\nmd"""\n# Step 3: Results\n\n$(plot(results))\n"""\n```\n\n### Pattern 4: Table with Embedded Widgets\n\n```julia\nmd"""\n# Parameter Configuration\n\n| Parameter | Description | Units | Value |\n| --------- | ----------- | ----- | ----- |\n| `temperature` | Operating temperature | \xB0C | $(@bind temp NumberField(0:200, default=25)) |\n| `pressure` | Operating pressure | bar | $(@bind pres NumberField(0:100, default=1)) |\n| `enabled` | Enable feature | - | $(@bind enabled CheckBox(default=true)) |\n"""\n```\n\n```julia\n# Use the bound values\nconfig = (temperature=temp, pressure=pres, enabled=enabled)\n```\n\n### Pattern 5: Real-time Visualization\n\n```julia\n@bind time Clock(0.1) # Update every 0.1 seconds\n\nbegin\n # Generate time-varying data\n t = time\n x = 0:0.1:2\u03C0\n y = sin.(x .+ t)\n\n plot(x, y, label="sin(x + t)", ylims=(-1.5, 1.5))\nend\n```\n\n### Pattern 6: Form-like Interface\n\n```julia\nmd"""\n# User Profile\n\n| Field | Input |\n| ----- | ----- |\n| Name | $(@bind user_name TextField(default="")) |\n| Age | $(@bind user_age NumberField(1:120, default=25)) |\n| Country | $(@bind country Select(["USA", "UK", "Canada", "Other"])) |\n| Subscribe | $(@bind subscribe CheckBox(default=false)) |\n\n$(@bind submit_button Button("Submit"))\n"""\n```\n\n```julia\nbegin\n profile = (\n name = user_name,\n age = user_age,\n country = country,\n subscribe = subscribe\n )\n\n md"""\n ## Submitted Profile\n\n - **Name**: $(profile.name)\n - **Age**: $(profile.age)\n - **Country**: $(profile.country)\n - **Newsletter**: $(profile.subscribe ? "\u2705 Yes" : "\u274C No")\n """\nend\n```\n\n---\n\n## Summary\n\n### Key Takeaways\n\n1. **Reactivity**: Cells automatically re-run based on dependencies\n2. **One Variable Rule**: Each variable can only be defined once across all cells\n3. **`@bind` Macro**: Connects UI widgets to variables\n4. **Markdown Integration**: Use `$()` to embed Julia expressions in markdown\n5. **Tables + Widgets**: Combine markdown tables with PlutoUI for interactive forms\n6. **No Hidden State**: All variables are explicit and traceable\n\n### Quick Reference\n\n**Slider**: `@bind x Slider(1:100, default=50, show_value=true)`\n\n**TextField**: `@bind text TextField(default="")`\n\n**NumberField**: `@bind n NumberField(0:100, default=10)`\n\n**CheckBox**: `@bind checked CheckBox(default=false)`\n\n**Select**: `@bind choice Select(["A", "B", "C"])`\n\n**Button**: `@bind clicked Button("Click")`\n\n**Markdown + Widget**: `$(@bind x Slider(1:10))`\n\n**Markdown + Variable**: `The value is $(x)`\n\n---\n\n## Additional Resources\n\n- Official Pluto.jl: <https://plutojl.org/>\n- PlutoUI Documentation: <https://github.com/JuliaPluto/PlutoUI.jl>\n- Featured Examples: <https://featured.plutojl.org/>\n\n---\n\n**Note for AI Tools**: When creating or modifying Pluto notebooks:\n\n- Always respect the one-variable-per-cell rule\n- Use `begin...end` blocks for multiple statements\n- Ensure cell UUIDs are unique\n- Maintain the cell order section at the end\n- Use `@bind` for all interactive widgets\n- Test reactivity by changing dependent variables\n';
|
|
62794
|
+
var PLUTO_GUIDE_default = '# Pluto.jl Notebook Guide for AI Tools\n\nThis guide explains how to work with Pluto.jl notebooks, including cell structure, reactivity rules, PlutoUI components, and best practices.\n\n## Important: Server Startup Times\n\nStarting a Pluto server involves installing Julia packages and precompiling them. **The first run can take several minutes** (2-10 minutes depending on the system). If `start_pluto_server` or `open_notebook` appears to hang or times out:\n\n1. **Do not retry immediately** \u2014 the server is likely still starting up.\n2. **Wait 30-60 seconds**, then call `get_notebook_status` to check progress.\n3. **Subsequent runs are much faster** since packages are cached.\n\nIf a tool call times out, that does NOT mean it failed \u2014 the server may still be starting in the background. Check `get_notebook_status` before retrying.\n\n## Working with Notebooks via MCP\n\nThese rules are critical when interacting with Pluto notebooks through the MCP API:\n\n### File Ownership\n\n- **Never edit the `.pluto.jl` file on disk while the notebook is open** \u2014 Pluto owns that file. Changes made outside the MCP API will be ignored or overwritten.\n- All cell mutations (create, edit, delete) must go through the MCP tools.\n- To persist changes to disk, call `save_notebook` explicitly. **Notebooks are NOT auto-saved.**\n\n### Handling Timeouts\n\n- If `create_cell` times out, the cell was likely still created in Pluto. Use `list_cells` to check before retrying \u2014 creating the same cell twice causes "Multiple definitions" errors.\n- For slow operations (e.g. `import Pkg; Pkg.add(...)`), prefer: (1) `edit_cell` with `run=false` to set the code, then (2) `execute_cell` to run it. This avoids timeout-induced phantom cells.\n- Use `delete_cell` to remove any accidental duplicate cells.\n\n### Pluto Reactivity Rules\n\n- **Each variable can only be defined in one cell.** If you get a "Multiple definitions" error, use `list_cells` to find the duplicate, then `delete_cell` to remove it.\n- When you edit a cell, Pluto automatically re-runs all cells that depend on the changed variables.\n- Prefer creating package cells with `import Pkg; Pkg.activate(; temp=true); Pkg.add([...])` in one cell, and `using PackageName` in a separate cell.\n\n### Recommended Workflow\n\n1. `open_notebook` (file must exist on disk first)\n2. `list_cells` to see current state\n3. `create_cell` / `edit_cell` / `delete_cell` to make changes\n4. `read_cell` to inspect outputs\n5. `save_notebook` to persist to disk when done\n6. `get_notebook_url` to give the user a browser link\n\n## Table of Contents\n\n- [Notebook Structure](#notebook-structure)\n- [Cell Rules and Reactivity](#cell-rules-and-reactivity)\n- [Markdown Cells](#markdown-cells)\n- [PlutoUI Components](#plutoui-components)\n- [Combining Markdown and PlutoUI](#combining-markdown-and-plutoui)\n- [Best Practices](#best-practices)\n- [Common Patterns](#common-patterns)\n\n---\n\n## Notebook Structure\n\n### File Format\n\nA Pluto notebook is a Julia file (`.jl`) with a specific structure:\n\n```julia\n### A Pluto.jl notebook ###\n# v0.20.19\n\nusing Markdown\nusing InteractiveUtils\n\n# \u2554\u2550\u2561 cell-uuid-1\n# \u2560\u2550\u2561 disabled = false\n# \u2560\u2550\u2561 show_logs = true\n# \u2560\u2550\u2561 skip_as_script = false\nmd"""\n# Your content here\n"""\n\n# \u2554\u2550\u2561 cell-uuid-2\nx = 10\n\n# \u2554\u2550\u2561 Cell order:\n# \u2560\u2550cell-uuid-1\n# \u2560\u2550cell-uuid-2\n```\n\n### Cell Markers\n\nEach cell starts with a unique identifier:\n\n- `# \u2554\u2550\u2561 <uuid>` - Cell boundary marker\n- Cell UUIDs are automatically generated\n- Cells contain Julia code, markdown, or expressions\n\n### Cell Metadata\n\nCells can have metadata comments:\n\n- `# \u2560\u2550\u2561 disabled = false` - Cell execution state\n- `# \u2560\u2550\u2561 show_logs = true` - Show cell output logs\n- `# \u2560\u2550\u2561 skip_as_script = false` - Include cell when exporting as script\n\n### Cell Order Section\n\nAt the end of the notebook:\n\n```julia\n# \u2554\u2550\u2561 Cell order:\n# \u2560\u2550cell-uuid-1\n# \u2560\u2550cell-uuid-2\n# \u2560\u2550cell-uuid-3\n```\n\nThis defines the display order of cells (not execution order).\n\n---\n\n## Cell Rules and Reactivity\n\n### Reactive Execution Model\n\n**Key Principle**: Pluto automatically determines execution order based on variable dependencies.\n\n#### Rules\n\n1. **One Variable per Cell (Assignment)**\n\n ```julia\n # \u2705 CORRECT\n x = 10\n\n # \u274C WRONG - Cannot assign same variable in another cell\n x = 20 # Error: Multiple definitions of x\n ```\n\n2. **Use `begin...end` for Multiple Statements**\n\n ```julia\n # \u2705 CORRECT\n begin\n x = 10\n y = 20\n z = x + y\n end\n ```\n\n3. **Automatic Dependency Tracking**\n\n ```julia\n # Cell 1\n a = 5\n\n # Cell 2 (depends on Cell 1)\n b = a * 2 # Automatically re-runs when \'a\' changes\n\n # Cell 3 (depends on Cell 2)\n c = b + 10 # Automatically re-runs when \'b\' changes\n ```\n\n4. **No Hidden State**\n - Every variable is defined exactly once\n - Execution order is determined by dependencies, not cell order\n - Deleting a cell removes its variables completely\n\n5. **Import/Package Management**\n\n ```julia\n begin\n import Pkg\n Pkg.activate(; temp=true)\n Pkg.add(["Plots", "DataFrames", "PlutoUI"])\n using Plots\n using PlutoUI\n end\n ```\n\n---\n\n## Markdown Cells\n\n### Basic Markdown Syntax\n\nMarkdown cells use triple-quote syntax:\n\n```julia\nmd"""\n# Heading 1\n## Heading 2\n### Heading 3\n\n**Bold text**\n*Italic text*\n\n- Bullet point 1\n- Bullet point 2\n\n1. Numbered item 1\n2. Numbered item 2\n\n[Link text](https://example.com)\n"""\n```\n\n### LaTeX Math\n\n```julia\nmd"""\nInline math: $E = mc^2$\n\nDisplay math:\n$$\n\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}\n$$\n"""\n```\n\n### Interpolating Variables\n\nUse `$()` to embed Julia expressions:\n\n```julia\nx = 42\n\nmd"""\nThe value of x is $(x).\n\nComputed value: $(x * 2)\n"""\n```\n\n### Tables in Markdown\n\n```julia\nmd"""\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Value A | Value B | Value C |\n| Value D | Value E | Value F |\n"""\n```\n\n---\n\n## PlutoUI Components\n\nPlutoUI provides interactive widgets using the `@bind` macro.\n\n### Installation\n\n```julia\nbegin\n import Pkg\n Pkg.add("PlutoUI")\n using PlutoUI\nend\n```\n\n### @bind Macro\n\nThe `@bind` macro connects a UI element to a variable:\n\n```julia\n@bind variable_name Widget(options...)\n```\n\n### Available Components\n\n#### 1. Slider\n\nCreates a slider for numeric input.\n\n```julia\n# Basic slider\n@bind x Slider(1:100)\n\n# Slider with default value and display\n@bind temperature Slider(0:0.1:100, default=25, show_value=true)\n\n# Slider with custom range\n@bind alpha Slider(0.0:0.01:1.0, default=0.5)\n```\n\n**Parameters:**\n\n- `range` - Range of values (e.g., `1:10` or `0.0:0.1:1.0`)\n- `default` - Initial value\n- `show_value` - Display current value (true/false)\n\n#### 2. TextField\n\nText input field.\n\n```julia\n# Single-line text field\n@bind name TextField()\n\n# Text field with default value\n@bind description TextField(default="Enter description")\n\n# Multi-line textarea\n@bind notes TextField((50, 10)) # (cols, rows)\n```\n\n**Parameters:**\n\n- `default` - Initial text value\n- `dims=(cols, rows)` - For multi-line textarea\n\n#### 3. NumberField\n\nNumeric input field.\n\n```julia\n# Number field with range\n@bind count NumberField(1:100, default=10)\n\n# Float number field\n@bind value NumberField(0.0:0.1:10.0, default=5.0)\n```\n\n**Parameters:**\n\n- `range` - Valid number range\n- `default` - Initial value\n\n#### 4. CheckBox\n\nBoolean checkbox.\n\n```julia\n# Simple checkbox\n@bind enabled CheckBox()\n\n# Checkbox with default value\n@bind show_details CheckBox(default=true)\n```\n\n**Parameters:**\n\n- `default` - Initial state (true/false)\n\n#### 5. Select\n\nDropdown selection.\n\n```julia\n# Select from array\n@bind color Select(["red", "green", "blue"])\n\n# Select with default\n@bind option Select(["Option A", "Option B", "Option C"], default="Option B")\n\n# Select from pairs (value => label)\n@bind choice Select([1 => "First", 2 => "Second", 3 => "Third"])\n```\n\n**Parameters:**\n\n- `options` - Array of options or pairs\n- `default` - Initially selected value\n\n#### 6. MultiSelect\n\nMultiple selection dropdown.\n\n```julia\n# Multi-select\n@bind colors MultiSelect(["red", "green", "blue", "yellow"])\n\n# Multi-select with defaults\n@bind selected MultiSelect(\n ["A", "B", "C", "D"],\n default=["A", "C"]\n)\n```\n\n#### 7. Button\n\nClickable button.\n\n```julia\n# Simple button\n@bind clicked Button("Click me!")\n\n# Button that increments on each click\n@bind click_count Button("Increment")\n```\n\n**Note**: Button sends the same value each time clicked, triggering reactive cells.\n\n#### 8. Radio\n\nRadio button group (single selection).\n\n```julia\n@bind choice Radio(["Option 1", "Option 2", "Option 3"])\n\n@bind size Radio(["Small", "Medium", "Large"], default="Medium")\n```\n\n#### 9. FilePicker\n\nFile upload widget.\n\n```julia\n@bind uploaded_file FilePicker()\n\n# Access file content\nfile_data = uploaded_file["data"]\nfile_name = uploaded_file["name"]\n```\n\n#### 10. Clock\n\nTimer that ticks at regular intervals.\n\n```julia\n# Tick every second\n@bind tick Clock()\n\n# Tick every 0.5 seconds\n@bind tick Clock(0.5)\n```\n\n#### 11. DateField\n\nDate picker.\n\n```julia\n@bind selected_date DateField()\n\n@bind start_date DateField(default=Dates.today())\n```\n\n---\n\n## Combining Markdown and PlutoUI\n\n### Inline Widgets in Tables\n\nYou can embed PlutoUI widgets directly in markdown tables:\n\n```julia\nmd"""\n| Parameter | Description | Units | Value |\n| --------- | ----------- | ----- | ----- |\n| `T_inf` | Ambient temperature | K | $(@bind T_inf NumberField(0:500, default=300)) |\n| `h` | Heat transfer coefficient | W/(m\xB2\xB7K) | $(@bind h Slider(0.0:0.1:1.0, default=0.7)) |\n| `mass` | Mass | kg | $(@bind m NumberField(0.0:0.1:10.0, default=1.0)) |\n"""\n```\n\n### Using Widget Values in Markdown\n\n```julia\n@bind speed Slider(1:100, default=50, show_value=true)\n\nmd"""\n## Speed Control\n\nCurrent speed: $(speed) km/h\n\nStatus: $(speed > 80 ? "\u{1F534} Fast" : "\u{1F7E2} Normal")\n"""\n```\n\n### Embedded Plots and Computations\n\n```julia\nmd"""\n# Wave Parameters\n\n| Parameter | Value |\n| --------- | ----- |\n| Amplitude | $(@bind amplitude Slider(0.1:0.1:5.0, default=1.0, show_value=true)) |\n| Frequency | $(@bind frequency Slider(0.1:0.1:10.0, default=1.0, show_value=true)) |\n\n## Waveform\n\n$(begin\n x = 0:0.01:2\u03C0\n y = amplitude .* sin.(frequency .* x)\n plot(x, y, label="sin wave", xlabel="x", ylabel="y")\nend)\n"""\n```\n\n### Dynamic Content Generation\n\n```julia\nbegin\n @bind param1 NumberField(0:100, default=50)\n @bind param2 Slider(0.0:0.1:1.0, default=0.5)\n\n result = param1 * param2\n\n md"""\n # Interactive Calculator\n\n | Input | Value |\n | ----- | ----- |\n | Parameter 1 | $(param1) |\n | Parameter 2 | $(param2) |\n | **Result** | **$(result)** |\n\n The computation shows: $(param1) \xD7 $(param2) = $(result)\n """\nend\n```\n\n---\n\n## Best Practices\n\n### 1. Package Management\n\nAlways use a cell at the beginning for package management:\n\n```julia\nbegin\n import Pkg\n Pkg.activate(; temp=true)\n Pkg.add(["Plots", "DataFrames", "PlutoUI"])\n using Plots\n using DataFrames\n using PlutoUI\nend\n```\n\n### 2. Organize with Markdown Headers\n\n```julia\nmd"""\n# Section 1: Data Loading\n\nLoad and prepare the data.\n"""\n\n# ... code cells ...\n\nmd"""\n# Section 2: Analysis\n\nPerform the analysis.\n"""\n\n# ... code cells ...\n```\n\n### 3. Use `begin...end` for Complex Logic\n\n```julia\nbegin\n # Multiple related computations\n data = load_data()\n cleaned = clean_data(data)\n result = analyze(cleaned)\n result\nend\n```\n\n### 4. Display the Last Expression\n\nThe last expression in a cell is automatically displayed:\n\n```julia\nbegin\n x = 10\n y = 20\n x + y # This value is displayed\nend\n```\n\n### 5. Suppress Output with Semicolon\n\n```julia\n# Suppress display\nlarge_data = load_large_dataset();\n\n# Show specific output\nprintln("Data loaded successfully")\n```\n\n### 6. Use @doc for Documentation\n\n```julia\nmd"""\n## Function Documentation\n\n$(@doc my_function)\n"""\n```\n\n### 7. Bind Multiple Related Widgets\n\n```julia\nmd"""\n## Configuration\n\n| Parameter | Value |\n| --------- | ----- |\n| X | $(@bind x Slider(1:10, default=5)) |\n| Y | $(@bind y Slider(1:10, default=5)) |\n\nSum: $(x + y)\nProduct: $(x * y)\n"""\n```\n\n---\n\n## Common Patterns\n\n### Pattern 1: Interactive Parameter Sweep\n\n```julia\n# Define parameters with widgets\n@bind param Slider(1:100, default=50, show_value=true)\n\n# Compute based on parameter\nresult = expensive_computation(param)\n\n# Display results\nplot(result)\n```\n\n### Pattern 2: Conditional Display\n\n```julia\n@bind show_advanced CheckBox(default=false)\n```\n\n```julia\n\nmd"""\nShow advanced options: $(show_advanced ? "\u2705 Enabled" : "\u274C Disabled")\n\n$(if show_advanced\n md"## Advanced Settings\n\n Configure advanced parameters here."\nelse\n md""\nend)\n"""\n```\n\n### Pattern 3: Multi-Step Workflow\n\n```julia\nmd"""\n# Step 1: Select Dataset\n$(@bind dataset Select(["Dataset A", "Dataset B", "Dataset C"]))\n"""\n\n# Load selected dataset\ndata = load_dataset(dataset)\n\nmd"""\n# Step 2: Configure Analysis\n$(@bind threshold Slider(0:0.1:1, default=0.5, show_value=true))\n"""\n\n# Perform analysis\nresults = analyze(data, threshold)\n\nmd"""\n# Step 3: Results\n\n$(plot(results))\n"""\n```\n\n### Pattern 4: Table with Embedded Widgets\n\n```julia\nmd"""\n# Parameter Configuration\n\n| Parameter | Description | Units | Value |\n| --------- | ----------- | ----- | ----- |\n| `temperature` | Operating temperature | \xB0C | $(@bind temp NumberField(0:200, default=25)) |\n| `pressure` | Operating pressure | bar | $(@bind pres NumberField(0:100, default=1)) |\n| `enabled` | Enable feature | - | $(@bind enabled CheckBox(default=true)) |\n"""\n```\n\n```julia\n# Use the bound values\nconfig = (temperature=temp, pressure=pres, enabled=enabled)\n```\n\n### Pattern 5: Real-time Visualization\n\n```julia\n@bind time Clock(0.1) # Update every 0.1 seconds\n\nbegin\n # Generate time-varying data\n t = time\n x = 0:0.1:2\u03C0\n y = sin.(x .+ t)\n\n plot(x, y, label="sin(x + t)", ylims=(-1.5, 1.5))\nend\n```\n\n### Pattern 6: Form-like Interface\n\n```julia\nmd"""\n# User Profile\n\n| Field | Input |\n| ----- | ----- |\n| Name | $(@bind user_name TextField(default="")) |\n| Age | $(@bind user_age NumberField(1:120, default=25)) |\n| Country | $(@bind country Select(["USA", "UK", "Canada", "Other"])) |\n| Subscribe | $(@bind subscribe CheckBox(default=false)) |\n\n$(@bind submit_button Button("Submit"))\n"""\n```\n\n```julia\nbegin\n profile = (\n name = user_name,\n age = user_age,\n country = country,\n subscribe = subscribe\n )\n\n md"""\n ## Submitted Profile\n\n - **Name**: $(profile.name)\n - **Age**: $(profile.age)\n - **Country**: $(profile.country)\n - **Newsletter**: $(profile.subscribe ? "\u2705 Yes" : "\u274C No")\n """\nend\n```\n\n---\n\n## Summary\n\n### Key Takeaways\n\n1. **Reactivity**: Cells automatically re-run based on dependencies\n2. **One Variable Rule**: Each variable can only be defined once across all cells\n3. **`@bind` Macro**: Connects UI widgets to variables\n4. **Markdown Integration**: Use `$()` to embed Julia expressions in markdown\n5. **Tables + Widgets**: Combine markdown tables with PlutoUI for interactive forms\n6. **No Hidden State**: All variables are explicit and traceable\n\n### Quick Reference\n\n**Slider**: `@bind x Slider(1:100, default=50, show_value=true)`\n\n**TextField**: `@bind text TextField(default="")`\n\n**NumberField**: `@bind n NumberField(0:100, default=10)`\n\n**CheckBox**: `@bind checked CheckBox(default=false)`\n\n**Select**: `@bind choice Select(["A", "B", "C"])`\n\n**Button**: `@bind clicked Button("Click")`\n\n**Markdown + Widget**: `$(@bind x Slider(1:10))`\n\n**Markdown + Variable**: `The value is $(x)`\n\n---\n\n## Additional Resources\n\n- Official Pluto.jl: <https://plutojl.org/>\n- PlutoUI Documentation: <https://github.com/JuliaPluto/PlutoUI.jl>\n- Featured Examples: <https://featured.plutojl.org/>\n\n---\n\n**Note for AI Tools**: When creating or modifying Pluto notebooks:\n\n- Always respect the one-variable-per-cell rule\n- Use `begin...end` blocks for multiple statements\n- Ensure cell UUIDs are unique\n- Maintain the cell order section at the end\n- Use `@bind` for all interactive widgets\n- Test reactivity by changing dependent variables\n';
|
|
62670
62795
|
|
|
62671
62796
|
// ../../src/mcp-server-http.ts
|
|
62672
62797
|
var PlutoMCPHttpServer = class {
|
|
@@ -62800,7 +62925,7 @@ var PlutoMCPHttpServer = class {
|
|
|
62800
62925
|
);
|
|
62801
62926
|
server.tool(
|
|
62802
62927
|
"open_notebook",
|
|
62803
|
-
"Open a Pluto notebook file and create a worker session",
|
|
62928
|
+
"Open a Pluto notebook file and create a worker session. The .jl file must already exist on disk \u2014 Pluto will not create a new file from a nonexistent path. Create the file first if needed.",
|
|
62804
62929
|
{
|
|
62805
62930
|
path: external_exports.string().describe("Path to the .jl notebook file")
|
|
62806
62931
|
},
|
|
@@ -62870,7 +62995,7 @@ Notebook ID: ${worker2.notebook_id}`
|
|
|
62870
62995
|
);
|
|
62871
62996
|
server.tool(
|
|
62872
62997
|
"create_cell",
|
|
62873
|
-
"Create and execute a new cell in a notebook",
|
|
62998
|
+
"Create and execute a new cell in a notebook. WARNING: This always executes the code. If the code is slow (e.g. package installs), the call may time out but the cell IS still created in Pluto. Use list_cells to check before retrying. For slow operations, prefer edit_cell with run=false then execute_cell separately.",
|
|
62874
62999
|
{
|
|
62875
63000
|
path: external_exports.string().describe("Path to the notebook"),
|
|
62876
63001
|
code: external_exports.string().describe("Julia code for the new cell"),
|
|
@@ -62907,7 +63032,7 @@ Notebook ID: ${worker2.notebook_id}`
|
|
|
62907
63032
|
);
|
|
62908
63033
|
server.tool(
|
|
62909
63034
|
"edit_cell",
|
|
62910
|
-
"Update the code of an existing cell",
|
|
63035
|
+
"Update the code of an existing cell. Note: editing the .pluto.jl file on disk has NO effect on the running notebook \u2014 all mutations must go through the MCP API. Use save_notebook to persist changes to disk.",
|
|
62911
63036
|
{
|
|
62912
63037
|
path: external_exports.string().describe("Path to the notebook"),
|
|
62913
63038
|
cell_id: external_exports.string().describe("UUID of the cell to edit"),
|
|
@@ -63218,6 +63343,120 @@ Notebook ID: ${worker2.notebook_id}`
|
|
|
63218
63343
|
}
|
|
63219
63344
|
}
|
|
63220
63345
|
);
|
|
63346
|
+
server.tool(
|
|
63347
|
+
"save_notebook",
|
|
63348
|
+
"Save the running notebook to disk as a .pluto.jl file. Notebooks are NOT auto-saved \u2014 you must call this explicitly to persist changes made via create_cell, edit_cell, or delete_cell.",
|
|
63349
|
+
{
|
|
63350
|
+
path: external_exports.string().describe("Path of the open notebook"),
|
|
63351
|
+
output_path: external_exports.string().describe(
|
|
63352
|
+
"Optional alternative file path to save to (defaults to the notebook's original path)"
|
|
63353
|
+
).optional()
|
|
63354
|
+
},
|
|
63355
|
+
async ({ path: path4, output_path }) => {
|
|
63356
|
+
if (!this.plutoManager.isConnected()) {
|
|
63357
|
+
throw new Error("Pluto server is not running");
|
|
63358
|
+
}
|
|
63359
|
+
const worker2 = await this.plutoManager.getWorker(path4);
|
|
63360
|
+
if (!worker2) {
|
|
63361
|
+
throw new Error(`Notebook ${path4} is not open`);
|
|
63362
|
+
}
|
|
63363
|
+
const content = this.plutoManager.getNotebookContent(worker2);
|
|
63364
|
+
const savePath = output_path ?? path4;
|
|
63365
|
+
await (0, import_promises.writeFile)(savePath, content, "utf-8");
|
|
63366
|
+
return {
|
|
63367
|
+
content: [
|
|
63368
|
+
{
|
|
63369
|
+
type: "text",
|
|
63370
|
+
text: `Notebook saved to ${savePath} (${content.length} bytes)`
|
|
63371
|
+
}
|
|
63372
|
+
]
|
|
63373
|
+
};
|
|
63374
|
+
}
|
|
63375
|
+
);
|
|
63376
|
+
server.tool(
|
|
63377
|
+
"delete_cell",
|
|
63378
|
+
"Permanently remove a cell from the notebook by its ID. Use list_cells to find cell IDs.",
|
|
63379
|
+
{
|
|
63380
|
+
path: external_exports.string().describe("Path to the notebook"),
|
|
63381
|
+
cell_id: external_exports.string().describe("UUID of the cell to delete")
|
|
63382
|
+
},
|
|
63383
|
+
async ({ path: path4, cell_id }) => {
|
|
63384
|
+
if (!this.plutoManager.isConnected()) {
|
|
63385
|
+
throw new Error("Pluto server is not running");
|
|
63386
|
+
}
|
|
63387
|
+
const worker2 = await this.plutoManager.getWorker(path4);
|
|
63388
|
+
if (!worker2) {
|
|
63389
|
+
throw new Error(`Notebook ${path4} is not open`);
|
|
63390
|
+
}
|
|
63391
|
+
await this.plutoManager.deleteCell(worker2, cell_id);
|
|
63392
|
+
return {
|
|
63393
|
+
content: [
|
|
63394
|
+
{
|
|
63395
|
+
type: "text",
|
|
63396
|
+
text: `Cell ${cell_id} deleted`
|
|
63397
|
+
}
|
|
63398
|
+
]
|
|
63399
|
+
};
|
|
63400
|
+
}
|
|
63401
|
+
);
|
|
63402
|
+
server.tool(
|
|
63403
|
+
"list_cells",
|
|
63404
|
+
"List all cells in a notebook with their IDs, code preview, and execution status. Use this to find cell IDs for read_cell, edit_cell, execute_cell, or delete_cell.",
|
|
63405
|
+
{
|
|
63406
|
+
path: external_exports.string().describe("Path to the notebook")
|
|
63407
|
+
},
|
|
63408
|
+
async ({ path: path4 }) => {
|
|
63409
|
+
if (!this.plutoManager.isConnected()) {
|
|
63410
|
+
throw new Error("Pluto server is not running");
|
|
63411
|
+
}
|
|
63412
|
+
const worker2 = await this.plutoManager.getWorker(path4);
|
|
63413
|
+
if (!worker2) {
|
|
63414
|
+
throw new Error(`Notebook ${path4} is not open`);
|
|
63415
|
+
}
|
|
63416
|
+
const snippets = worker2.getSnippets();
|
|
63417
|
+
const cells = snippets.map((snippet, index) => ({
|
|
63418
|
+
cell_id: snippet.cell_id,
|
|
63419
|
+
index,
|
|
63420
|
+
code_preview: snippet.input.code.split("\n")[0].slice(0, 80),
|
|
63421
|
+
errored: snippet.result.errored,
|
|
63422
|
+
running: snippet.result.running,
|
|
63423
|
+
queued: snippet.result.queued
|
|
63424
|
+
}));
|
|
63425
|
+
return {
|
|
63426
|
+
content: [
|
|
63427
|
+
{
|
|
63428
|
+
type: "text",
|
|
63429
|
+
text: JSON.stringify({ count: cells.length, cells }, null, 2)
|
|
63430
|
+
}
|
|
63431
|
+
]
|
|
63432
|
+
};
|
|
63433
|
+
}
|
|
63434
|
+
);
|
|
63435
|
+
server.tool(
|
|
63436
|
+
"get_notebook_url",
|
|
63437
|
+
"Get the browser URL to open the notebook in Pluto's web interface",
|
|
63438
|
+
{
|
|
63439
|
+
path: external_exports.string().describe("Path to the notebook")
|
|
63440
|
+
},
|
|
63441
|
+
async ({ path: path4 }) => {
|
|
63442
|
+
if (!this.plutoManager.isConnected()) {
|
|
63443
|
+
throw new Error("Pluto server is not running");
|
|
63444
|
+
}
|
|
63445
|
+
const worker2 = await this.plutoManager.getWorker(path4);
|
|
63446
|
+
if (!worker2) {
|
|
63447
|
+
throw new Error(`Notebook ${path4} is not open`);
|
|
63448
|
+
}
|
|
63449
|
+
const url2 = `${this.plutoManager.getServerUrl()}/edit?id=${worker2.notebook_id}`;
|
|
63450
|
+
return {
|
|
63451
|
+
content: [
|
|
63452
|
+
{
|
|
63453
|
+
type: "text",
|
|
63454
|
+
text: url2
|
|
63455
|
+
}
|
|
63456
|
+
]
|
|
63457
|
+
};
|
|
63458
|
+
}
|
|
63459
|
+
);
|
|
63221
63460
|
}
|
|
63222
63461
|
setupRoutes() {
|
|
63223
63462
|
this.app.get("/mcp", async (_req, res) => {
|
|
@@ -63623,10 +63862,10 @@ var NodeServerManager = class {
|
|
|
63623
63862
|
};
|
|
63624
63863
|
|
|
63625
63864
|
// ../../src/cli/nodeFileReader.ts
|
|
63626
|
-
var
|
|
63865
|
+
var import_promises2 = require("fs/promises");
|
|
63627
63866
|
var NodeFileReader = class {
|
|
63628
63867
|
async readFile(path4) {
|
|
63629
|
-
return await (0,
|
|
63868
|
+
return await (0, import_promises2.readFile)(path4, "utf-8");
|
|
63630
63869
|
}
|
|
63631
63870
|
};
|
|
63632
63871
|
|
|
@@ -63649,45 +63888,52 @@ var consoleLogger = {
|
|
|
63649
63888
|
// ../../src/cli/run.ts
|
|
63650
63889
|
async function run(config) {
|
|
63651
63890
|
console.log("@plutojl/mcp \u2014 Standalone MCP server for Pluto.jl\n");
|
|
63652
|
-
|
|
63653
|
-
|
|
63654
|
-
|
|
63655
|
-
|
|
63656
|
-
|
|
63657
|
-
|
|
63658
|
-
|
|
63659
|
-
|
|
63660
|
-
|
|
63661
|
-
|
|
63662
|
-
|
|
63663
|
-
|
|
63664
|
-
config.plutoPort,
|
|
63665
|
-
consoleLogger,
|
|
63666
|
-
serverManager,
|
|
63667
|
-
new NodeFileReader(),
|
|
63668
|
-
config.plutoUrl
|
|
63669
|
-
);
|
|
63670
|
-
} else {
|
|
63671
|
-
serverManager = new NodeServerManager(
|
|
63672
|
-
config.plutoPort,
|
|
63673
|
-
config.juliaVersion,
|
|
63674
|
-
config.workDir
|
|
63675
|
-
);
|
|
63676
|
-
plutoManager = new PlutoManager(
|
|
63677
|
-
config.plutoPort,
|
|
63678
|
-
consoleLogger,
|
|
63679
|
-
serverManager,
|
|
63680
|
-
new NodeFileReader()
|
|
63681
|
-
);
|
|
63682
|
-
}
|
|
63891
|
+
const serverManager = new NodeServerManager(
|
|
63892
|
+
config.plutoPort,
|
|
63893
|
+
config.juliaVersion,
|
|
63894
|
+
config.workDir
|
|
63895
|
+
);
|
|
63896
|
+
const plutoManager = new PlutoManager(
|
|
63897
|
+
config.plutoPort,
|
|
63898
|
+
consoleLogger,
|
|
63899
|
+
serverManager,
|
|
63900
|
+
new NodeFileReader(),
|
|
63901
|
+
config.plutoUrl
|
|
63902
|
+
);
|
|
63683
63903
|
const mcpServer = new PlutoMCPHttpServer(plutoManager, config.mcpPort, true);
|
|
63684
63904
|
await mcpServer.start();
|
|
63685
63905
|
console.log(
|
|
63686
|
-
`
|
|
63687
|
-
[cli] MCP server listening at http://localhost:${config.mcpPort}/mcp`
|
|
63906
|
+
`[cli] MCP server listening at http://localhost:${config.mcpPort}/mcp`
|
|
63688
63907
|
);
|
|
63689
63908
|
console.log(`[cli] Health check: http://localhost:${config.mcpPort}/health`);
|
|
63690
|
-
|
|
63909
|
+
if (!config.noPluto) {
|
|
63910
|
+
if (config.plutoUrl) {
|
|
63911
|
+
console.log(
|
|
63912
|
+
`[cli] Connecting to existing Pluto server at ${config.plutoUrl}...`
|
|
63913
|
+
);
|
|
63914
|
+
} else {
|
|
63915
|
+
console.log(`[cli] Starting Pluto server on port ${config.plutoPort}...`);
|
|
63916
|
+
}
|
|
63917
|
+
try {
|
|
63918
|
+
await plutoManager.start();
|
|
63919
|
+
console.log(`[cli] Pluto server is ready.`);
|
|
63920
|
+
} catch (err) {
|
|
63921
|
+
console.error(
|
|
63922
|
+
`[cli] Failed to start Pluto: ${err instanceof Error ? err.message : String(err)}`
|
|
63923
|
+
);
|
|
63924
|
+
console.error(
|
|
63925
|
+
`[cli] MCP server is still running \u2014 you can start Pluto later via:`
|
|
63926
|
+
);
|
|
63927
|
+
console.error(`[cli] npx @plutojl/mcp call start_pluto_server`);
|
|
63928
|
+
}
|
|
63929
|
+
} else {
|
|
63930
|
+
console.log(`[cli] Pluto auto-start skipped (--no-pluto).`);
|
|
63931
|
+
console.log(
|
|
63932
|
+
`[cli] Start it later: npx @plutojl/mcp call start_pluto_server`
|
|
63933
|
+
);
|
|
63934
|
+
}
|
|
63935
|
+
console.log(`
|
|
63936
|
+
[cli] Press Ctrl+C to stop
|
|
63691
63937
|
`);
|
|
63692
63938
|
console.log(`Tip: Run 'npx @plutojl/mcp install' to configure Claude Code
|
|
63693
63939
|
`);
|
|
@@ -63944,6 +64190,44 @@ async function callTool(port, toolName, argsJson, raw2) {
|
|
|
63944
64190
|
}
|
|
63945
64191
|
}
|
|
63946
64192
|
|
|
64193
|
+
// ../../src/cli/preflight.ts
|
|
64194
|
+
async function preflight(port, opts = {}) {
|
|
64195
|
+
const mcpUrl = `http://localhost:${port}`;
|
|
64196
|
+
let health;
|
|
64197
|
+
try {
|
|
64198
|
+
const res = await fetch(`${mcpUrl}/health`, {
|
|
64199
|
+
signal: AbortSignal.timeout(3e3)
|
|
64200
|
+
});
|
|
64201
|
+
health = await res.json();
|
|
64202
|
+
} catch {
|
|
64203
|
+
console.error(`Error: Cannot reach MCP server at ${mcpUrl}`);
|
|
64204
|
+
console.error(``);
|
|
64205
|
+
console.error(`The MCP server is not running. Start it first:`);
|
|
64206
|
+
console.error(``);
|
|
64207
|
+
console.error(` npx @plutojl/mcp run`);
|
|
64208
|
+
console.error(``);
|
|
64209
|
+
console.error(
|
|
64210
|
+
`Or, if you are using a different port, pass --mcp-port <port>.`
|
|
64211
|
+
);
|
|
64212
|
+
process.exit(1);
|
|
64213
|
+
}
|
|
64214
|
+
if (opts.requirePluto && !health.plutoServerRunning) {
|
|
64215
|
+
console.error(`Error: MCP server is running but Pluto is not connected.`);
|
|
64216
|
+
console.error(``);
|
|
64217
|
+
console.error(`Start Pluto via the MCP server:`);
|
|
64218
|
+
console.error(``);
|
|
64219
|
+
console.error(` npx @plutojl/mcp call start_pluto_server`);
|
|
64220
|
+
console.error(``);
|
|
64221
|
+
console.error(`Or connect to an existing Pluto server:`);
|
|
64222
|
+
console.error(``);
|
|
64223
|
+
console.error(
|
|
64224
|
+
` npx @plutojl/mcp call connect_to_pluto_server '{"port": 1234}'`
|
|
64225
|
+
);
|
|
64226
|
+
process.exit(1);
|
|
64227
|
+
}
|
|
64228
|
+
return { mcpRunning: true, plutoRunning: health.plutoServerRunning };
|
|
64229
|
+
}
|
|
64230
|
+
|
|
63947
64231
|
// ../../src/cli/index.ts
|
|
63948
64232
|
async function main() {
|
|
63949
64233
|
const args = parseArgs(process.argv.slice(2));
|
|
@@ -63960,6 +64244,7 @@ async function main() {
|
|
|
63960
64244
|
break;
|
|
63961
64245
|
}
|
|
63962
64246
|
case "tools": {
|
|
64247
|
+
await preflight(mcpPort);
|
|
63963
64248
|
await listTools(mcpPort);
|
|
63964
64249
|
break;
|
|
63965
64250
|
}
|
|
@@ -63968,6 +64253,7 @@ async function main() {
|
|
|
63968
64253
|
console.error("Usage: npx @plutojl/mcp call <tool_name> [json_args]");
|
|
63969
64254
|
process.exit(1);
|
|
63970
64255
|
}
|
|
64256
|
+
await preflight(mcpPort);
|
|
63971
64257
|
await callTool(
|
|
63972
64258
|
mcpPort,
|
|
63973
64259
|
args.toolName,
|