Skip to content

Commit 06d5e67

Browse files
authored
feat: Add create_notebook and get_cell_outputs tools (#27)
Co-authored-by: snehshah22 <snehshah22@users.noreply.github.com>
1 parent 915aaf1 commit 06d5e67

4 files changed

Lines changed: 422 additions & 17 deletions

File tree

mcp/dist/index.js

Lines changed: 197 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3238,8 +3238,8 @@ var require_utils = __commonJS({
32383238
}
32393239
return ind;
32403240
}
3241-
function removeDotSegments(path2) {
3242-
let input = path2;
3241+
function removeDotSegments(path3) {
3242+
let input = path3;
32433243
const output = [];
32443244
let nextSlash = -1;
32453245
let len = 0;
@@ -3438,8 +3438,8 @@ var require_schemes = __commonJS({
34383438
wsComponent.secure = void 0;
34393439
}
34403440
if (wsComponent.resourceName) {
3441-
const [path2, query] = wsComponent.resourceName.split("?");
3442-
wsComponent.path = path2 && path2 !== "/" ? path2 : void 0;
3441+
const [path3, query] = wsComponent.resourceName.split("?");
3442+
wsComponent.path = path3 && path3 !== "/" ? path3 : void 0;
34433443
wsComponent.query = query;
34443444
wsComponent.resourceName = void 0;
34453445
}
@@ -6801,12 +6801,12 @@ var require_dist = __commonJS({
68016801
throw new Error(`Unknown format "${name}"`);
68026802
return f;
68036803
};
6804-
function addFormats(ajv, list, fs8, exportName) {
6804+
function addFormats(ajv, list, fs10, exportName) {
68056805
var _a;
68066806
var _b;
68076807
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
68086808
for (const f of list)
6809-
ajv.addFormat(f, fs8[f]);
6809+
ajv.addFormat(f, fs10[f]);
68106810
}
68116811
module.exports = exports = formatsPlugin;
68126812
Object.defineProperty(exports, "__esModule", { value: true });
@@ -7292,8 +7292,8 @@ function getErrorMap() {
72927292

72937293
// node_modules/zod/v3/helpers/parseUtil.js
72947294
var makeIssue = (params) => {
7295-
const { data, path: path2, errorMaps, issueData } = params;
7296-
const fullPath = [...path2, ...issueData.path || []];
7295+
const { data, path: path3, errorMaps, issueData } = params;
7296+
const fullPath = [...path3, ...issueData.path || []];
72977297
const fullIssue = {
72987298
...issueData,
72997299
path: fullPath
@@ -7409,11 +7409,11 @@ var errorUtil;
74097409

74107410
// node_modules/zod/v3/types.js
74117411
var ParseInputLazyPath = class {
7412-
constructor(parent, value, path2, key) {
7412+
constructor(parent, value, path3, key) {
74137413
this._cachedPath = [];
74147414
this.parent = parent;
74157415
this.data = value;
7416-
this._path = path2;
7416+
this._path = path3;
74177417
this._key = key;
74187418
}
74197419
get path() {
@@ -11050,10 +11050,10 @@ function assignProp(target, prop, value) {
1105011050
configurable: true
1105111051
});
1105211052
}
11053-
function getElementAtPath(obj, path2) {
11054-
if (!path2)
11053+
function getElementAtPath(obj, path3) {
11054+
if (!path3)
1105511055
return obj;
11056-
return path2.reduce((acc, key) => acc?.[key], obj);
11056+
return path3.reduce((acc, key) => acc?.[key], obj);
1105711057
}
1105811058
function promiseAllObject(promisesObj) {
1105911059
const keys = Object.keys(promisesObj);
@@ -11373,11 +11373,11 @@ function aborted(x, startIndex = 0) {
1137311373
}
1137411374
return false;
1137511375
}
11376-
function prefixIssues(path2, issues) {
11376+
function prefixIssues(path3, issues) {
1137711377
return issues.map((iss) => {
1137811378
var _a;
1137911379
(_a = iss).path ?? (_a.path = []);
11380-
iss.path.unshift(path2);
11380+
iss.path.unshift(path3);
1138111381
return iss;
1138211382
});
1138311383
}
@@ -18780,7 +18780,7 @@ var StdioClientTransport = class {
1878018780
};
1878118781

1878218782
// server.ts
18783-
import path from "path";
18783+
import path2 from "path";
1878418784
import { fileURLToPath } from "url";
1878518785

1878618786
// tools/delete_cell.ts
@@ -18982,6 +18982,132 @@ async function searchCells(notebookPath, query, caseSensitive = false) {
1898218982
}
1898318983
}
1898418984

18985+
// tools/create_notebook.ts
18986+
import * as fs8 from "fs/promises";
18987+
import * as path from "path";
18988+
async function createNotebook(directory, filename) {
18989+
try {
18990+
const extension = "ipynb";
18991+
const fullFilename = filename.endsWith(`.${extension}`) ? filename : `${filename}.${extension}`;
18992+
const notebookPath = path.join(directory, fullFilename);
18993+
try {
18994+
await fs8.stat(notebookPath);
18995+
throw new Error(`Notebook already exists at ${notebookPath}`);
18996+
} catch (err) {
18997+
if (err.code !== "ENOENT") {
18998+
throw err;
18999+
}
19000+
}
19001+
const minimalNotebook = {
19002+
cells: [],
19003+
metadata: {},
19004+
nbformat: 4,
19005+
nbformat_minor: 2
19006+
};
19007+
await fs8.writeFile(notebookPath, JSON.stringify(minimalNotebook, null, 2), "utf8");
19008+
return {
19009+
success: true,
19010+
message: `Created Jupyter notebook at ${notebookPath}`,
19011+
notebookPath
19012+
};
19013+
} catch (error2) {
19014+
throw new Error(`Failed to create notebook: ${error2.message}`);
19015+
}
19016+
}
19017+
19018+
// tools/get_cell_outputs.ts
19019+
import * as fs9 from "fs/promises";
19020+
async function getCellOutputs(notebookPath, cellIndex) {
19021+
try {
19022+
const data = await fs9.readFile(notebookPath, "utf8");
19023+
const notebook = JSON.parse(data);
19024+
if (!notebook.cells || !Array.isArray(notebook.cells)) {
19025+
throw new Error("Invalid notebook format: missing cells array");
19026+
}
19027+
if (cellIndex < 0 || cellIndex >= notebook.cells.length) {
19028+
throw new Error(`Cell index out of bounds: ${cellIndex}. Total cells: ${notebook.cells.length}`);
19029+
}
19030+
const cell = notebook.cells[cellIndex];
19031+
if (cell.cell_type !== "code") {
19032+
throw new Error(`Cell at index ${cellIndex} is not a code cell; it has no execution outputs.`);
19033+
}
19034+
const prefixText = `Outputs for cell ${cellIndex} in ${notebookPath}:`;
19035+
const contentPayload = parseCellOutputs(cell, cellIndex, notebookPath, prefixText);
19036+
return contentPayload;
19037+
} catch (error2) {
19038+
throw new Error(`Failed to get cell outputs: ${error2.message}`);
19039+
}
19040+
}
19041+
function parseCellOutputs(cell, index, path3, prefixText) {
19042+
const contentPayload = [];
19043+
let textBuffer = `${prefixText}
19044+
`;
19045+
const executionCount = cell.execution_count ?? null;
19046+
textBuffer += `Execution Count: ${executionCount ?? "N/A"}
19047+
`;
19048+
if (cell.outputs && cell.outputs.length > 0) {
19049+
textBuffer += `
19050+
Outputs:
19051+
`;
19052+
for (const o of cell.outputs) {
19053+
const outputType = o.output_type;
19054+
if (outputType === "stream") {
19055+
const text = Array.isArray(o.text) ? o.text.join("") : o.text || "";
19056+
const textPreview = text.length > 3e3 ? `${text.slice(0, 3e3)}
19057+
...[Truncated]` : text;
19058+
textBuffer += `
19059+
[stream:${o.name}]
19060+
${textPreview}
19061+
`;
19062+
} else if (outputType === "execute_result" || outputType === "display_data") {
19063+
for (const mime in o.data) {
19064+
const data = o.data[mime];
19065+
const textData = Array.isArray(data) ? data.join("") : data || "";
19066+
if (mime.startsWith("text/") || mime.includes("json")) {
19067+
const textPreview = textData.length > 3e3 ? `${textData.slice(0, 3e3)}
19068+
...[Truncated]` : textData;
19069+
textBuffer += `
19070+
[${mime}]
19071+
${textPreview}
19072+
`;
19073+
} else if (mime === "image/png" || mime === "image/jpeg") {
19074+
if (textBuffer.trim().length > 0) {
19075+
contentPayload.push({ type: "text", text: textBuffer });
19076+
textBuffer = "";
19077+
}
19078+
if (typeof textData !== "string") {
19079+
textBuffer += `
19080+
[${mime}]
19081+
(Warning: Image data is not a string. Type: ${typeof textData})
19082+
`;
19083+
} else {
19084+
contentPayload.push({
19085+
type: "image",
19086+
data: textData,
19087+
mimeType: mime
19088+
});
19089+
}
19090+
}
19091+
}
19092+
} else if (outputType === "error") {
19093+
const traceback = Array.isArray(o.traceback) ? o.traceback.join("\n") : o.traceback || "";
19094+
textBuffer += `
19095+
[error]
19096+
${traceback}
19097+
`;
19098+
}
19099+
}
19100+
} else {
19101+
textBuffer += `
19102+
(No output generated)
19103+
`;
19104+
}
19105+
if (textBuffer.length > 0) {
19106+
contentPayload.push({ type: "text", text: textBuffer });
19107+
}
19108+
return contentPayload;
19109+
}
19110+
1898519111
// server.ts
1898619112
var server = new Server(
1898719113
{
@@ -19129,6 +19255,42 @@ var LOCAL_TOOLS = [
1912919255
},
1913019256
required: ["notebookPath", "query"]
1913119257
}
19258+
},
19259+
{
19260+
name: "create_notebook",
19261+
description: "Create a new notebook file in the workspace",
19262+
inputSchema: {
19263+
type: "object",
19264+
properties: {
19265+
directory: {
19266+
type: "string",
19267+
description: "Absolute path to the directory where the notebook should be created"
19268+
},
19269+
filename: {
19270+
type: "string",
19271+
description: "Name of the notebook file (without extension)"
19272+
}
19273+
},
19274+
required: ["directory", "filename"]
19275+
}
19276+
},
19277+
{
19278+
name: "get_cell_outputs",
19279+
description: "Read outputs from a code cell by index",
19280+
inputSchema: {
19281+
type: "object",
19282+
properties: {
19283+
notebookPath: {
19284+
type: "string",
19285+
description: "Path to the notebook file"
19286+
},
19287+
cellIndex: {
19288+
type: "number",
19289+
description: "0-based index of the cell to inspect"
19290+
}
19291+
},
19292+
required: ["notebookPath", "cellIndex"]
19293+
}
1913219294
}
1913319295
];
1913419296
var toolOwnerMap = /* @__PURE__ */ new Map();
@@ -19179,6 +19341,10 @@ var SearchCellsSchema = NotebookPathSchema.extend({
1917919341
query: external_exports.string(),
1918019342
caseSensitive: external_exports.boolean().optional()
1918119343
});
19344+
var CreateNotebookSchema = external_exports.object({
19345+
directory: external_exports.string(),
19346+
filename: external_exports.string()
19347+
});
1918219348
server.setRequestHandler(CallToolRequestSchema, async (request) => {
1918319349
const { name, arguments: args } = request.params;
1918419350
const owner = toolOwnerMap.get(name);
@@ -19248,6 +19414,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1924819414
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
1924919415
};
1925019416
}
19417+
case "create_notebook": {
19418+
const parsed = CreateNotebookSchema.parse(args);
19419+
const result = await createNotebook(parsed.directory, parsed.filename);
19420+
return {
19421+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
19422+
};
19423+
}
19424+
case "get_cell_outputs": {
19425+
const parsed = CellIndexSchema.parse(args);
19426+
const result = await getCellOutputs(parsed.notebookPath, parsed.cellIndex);
19427+
return {
19428+
content: result
19429+
};
19430+
}
1925119431
default:
1925219432
throw new Error(`Unknown tool: ${name}`);
1925319433
}
@@ -19266,7 +19446,7 @@ async function startStandaloneServer() {
1926619446
async function run() {
1926719447
const ideName = process.env.DATA_CLOUD_CURR_IDE_NAME;
1926819448
if (ideName) {
19269-
const proxyCmd = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../bin/mcp_proxy_bundle.cjs");
19449+
const proxyCmd = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "../bin/mcp_proxy_bundle.cjs");
1927019450
try {
1927119451
const notebookTransport = new StdioClientTransport({
1927219452
command: process.execPath,

0 commit comments

Comments
 (0)