codemode-lsp 0.1.1 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +4 -1
  2. package/dist/index.js +81 -33
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -72,7 +72,10 @@ The API surface is 16 functions plus `getDiagnostics`: 8 read ops (`readFile`,
72
72
  `deleteSymbol`, `writeFile`, `deleteFile`). Symbols are addressed by
73
73
  slash-separated paths (`MyClass/myMethod`) discovered via `getSymbols`. The
74
74
  full type definitions are embedded in the tool description, generated straight
75
- from the source (`bun run generate:types`).
75
+ from the source (`bun run generate:types`). If a client truncates the
76
+ description, the script `await lsp.help()` returns the complete reference —
77
+ the description's first lines advertise this, so an agent can always recover
78
+ the full API without probing.
76
79
 
77
80
  See `PRD.md` for the complete spec.
78
81
 
package/dist/index.js CHANGED
@@ -46102,6 +46102,25 @@ function buildTracedLsp(api2, trace, makeSandboxError, wrapAsync, opNames) {
46102
46102
  return traced;
46103
46103
  }
46104
46104
  var hostPath = { join: join2, basename, dirname: dirname4, extname };
46105
+ var UNCAPTURED_STATEMENT_NAMES = {
46106
+ IfStatement: "an if statement",
46107
+ ForStatement: "a for loop",
46108
+ ForOfStatement: "a for...of loop",
46109
+ ForInStatement: "a for...in loop",
46110
+ WhileStatement: "a while loop",
46111
+ DoWhileStatement: "a do...while loop",
46112
+ TryStatement: "a try/catch block",
46113
+ SwitchStatement: "a switch statement",
46114
+ BlockStatement: "a block",
46115
+ VariableDeclaration: "a variable declaration",
46116
+ FunctionDeclaration: "a function declaration",
46117
+ ClassDeclaration: "a class declaration"
46118
+ };
46119
+ function describeUncapturedStatement(type) {
46120
+ if (type === "ReturnStatement")
46121
+ return;
46122
+ return UNCAPTURED_STATEMENT_NAMES[type] ?? `a ${type}`;
46123
+ }
46105
46124
  function isExpressionStatement(node) {
46106
46125
  return node.type === "ExpressionStatement";
46107
46126
  }
@@ -46137,8 +46156,14 @@ function normalizeCode(code) {
46137
46156
  return { source: `${head}
46138
46157
  return (${expr});` };
46139
46158
  }
46140
- return { source: `${trimmed}
46141
- return undefined;` };
46159
+ return {
46160
+ source: `${trimmed}
46161
+ return undefined;`,
46162
+ ...last ? (() => {
46163
+ const described = describeUncapturedStatement(last.type);
46164
+ return described ? { uncapturedLastStatement: described } : {};
46165
+ })() : {}
46166
+ };
46142
46167
  }
46143
46168
  function capText(text, cap, marker) {
46144
46169
  if (text.length <= cap)
@@ -46249,7 +46274,9 @@ async function runSandbox(code, options) {
46249
46274
  };
46250
46275
  const opNames = options.readonly ? [...READ_OP_NAMES] : [...READ_OP_NAMES, ...WRITE_OP_NAMES];
46251
46276
  sandbox.lsp = buildTracedLsp(options.lsp, trace, makeSandboxError, wrapAsync, opNames);
46252
- const { source } = normalizeCode(code);
46277
+ const helpText = options.helpText ?? "No extended help is available; the tool description is the full reference.";
46278
+ sandbox.lsp.help = wrapAsync(async () => helpText);
46279
+ const { source, uncapturedLastStatement } = normalizeCode(code);
46253
46280
  const wrapped = `(async () => {
46254
46281
  ${source}
46255
46282
  })()`;
@@ -46290,7 +46317,7 @@ ${source}
46290
46317
  }
46291
46318
  let result;
46292
46319
  try {
46293
- result = serializeResult(value);
46320
+ result = value === undefined && uncapturedLastStatement ? `undefined — note: the script's last statement is ${uncapturedLastStatement}, ` + "which produces no capturable value. Only a top-level trailing " + "expression becomes the result — end the script with a bare " + "expression (e.g. `({ count })`), or use a top-level `return`." : serializeResult(value);
46294
46321
  } catch (error51) {
46295
46322
  throw new SandboxError({
46296
46323
  error: error51 instanceof Error ? error51.message : String(error51),
@@ -46391,11 +46418,11 @@ var LSP_READ_OP_SIGNATURES = ` /** File contents as a raw string (no line numbe
46391
46418
  getSymbolBody(file: string, symbolPath: string): Promise<string>;
46392
46419
  /** Document symbol tree (file outline). Every path is a usable handle. */
46393
46420
  getSymbols(file: string): Promise<SymbolInfo[]>;
46394
- /** Workspace-wide symbol search; the index warms lazily, so early calls may be empty getSymbols(file) is exhaustive. */
46421
+ /** Workspace-wide symbol search; matches substrings, so filter for exact \`name\`. The index warms lazily early calls may be empty; getSymbols(file) is exhaustive. */
46395
46422
  findSymbol(query: string): Promise<WorkspaceSymbolInfo[]>;
46396
46423
  /** All references to a symbol across the workspace (incl. the declaration). */
46397
46424
  findReferences(file: string, symbolPath: string): Promise<Reference[]>;
46398
- /** Jump to a symbol's definition. */
46425
+ /** Jump to the definition of a symbol DEFINED in \`file\` — imports/callees in a body are not addressable; resolve those names with findSymbol. */
46399
46426
  goToDefinition(file: string, symbolPath: string): Promise<Location>;
46400
46427
  /** Regex search across project files — escape metacharacters for literal text; optional second arg is a glob string. */
46401
46428
  searchText(pattern: string, glob?: string): Promise<SearchResult[]>;
@@ -46465,17 +46492,25 @@ function renderLspTypes(readonly3) {
46465
46492
  const interfaces = readonly3 ? LSP_COMMON_INTERFACES : `${LSP_COMMON_INTERFACES}
46466
46493
 
46467
46494
  ${LSP_WRITE_INTERFACES}`;
46468
- const ops = readonly3 ? LSP_READ_OP_SIGNATURES : `${LSP_READ_OP_SIGNATURES}
46495
+ const helpOp = ` /** This full API reference as a string — call it if this description was truncated. */
46496
+ help(): Promise<string>;`;
46497
+ const ops = readonly3 ? `${LSP_READ_OP_SIGNATURES}
46498
+
46499
+ ${helpOp}` : `${LSP_READ_OP_SIGNATURES}
46469
46500
 
46470
46501
  // Write operations — buffered, applied atomically when the script succeeds.
46471
- ${LSP_WRITE_OP_SIGNATURES}`;
46472
- return `${interfaces}
46502
+ ${LSP_WRITE_OP_SIGNATURES}
46473
46503
 
46474
- declare const lsp: {
46504
+ ${helpOp}`;
46505
+ return `declare const lsp: {
46475
46506
  ${ops}
46476
- };`;
46507
+ };
46508
+
46509
+ ${interfaces}`;
46477
46510
  }
46478
46511
  var TEMPLATE = `Execute JavaScript to perform semantic code operations via LSP (TypeScript).
46512
+ If this description looks truncated, run the script \`await lsp.help()\` — it
46513
+ returns this complete reference. Ops — {{opInventory}}.
46479
46514
 
46480
46515
  Write one script that chains lsp.* calls — filter, loop, and branch in code
46481
46516
  instead of across many tool calls. The sandbox provides \`lsp\`,
@@ -46484,36 +46519,45 @@ instead of across many tool calls. The sandbox provides \`lsp\`,
46484
46519
  setTimeout). The tool returns { result, logs, changes }, where \`result\` is the
46485
46520
  script's last expression, JSON-serialized.
46486
46521
 
46487
- ## API
46488
-
46489
- {{types}}
46490
-
46491
- {{writeSemantics}}
46492
-
46493
- ## Examples
46494
-
46495
- {{examples}}
46496
-
46497
46522
  ## Rules
46498
46523
 
46499
- - The last expression is the return value — end the script with the value you
46500
- want back, e.g. \`({ count })\`.
46524
+ - The last TOP-LEVEL expression is the return value — end the script with a
46525
+ bare expression, e.g. \`({ count })\`. An expression inside a trailing
46526
+ if/for/try block is NOT captured; use a top-level \`return\` from inside
46527
+ blocks.
46501
46528
  - Every \`lsp.*\` call returns a Promise — always \`await\` it. An un-awaited
46502
46529
  call inside the result serializes as \`{}\`.
46503
46530
  - \`.filter()\`/\`.map()\` callbacks cannot be async — use \`for...of\` with \`await\`.
46504
- - Globs match the whole workspace-relative path: \`listFiles("src/**")\` for a
46505
- directory (a bare directory name like \`"src"\` is treated as \`src/**\`),
46506
- \`listFiles()\` for every file. \`searchText\`'s second argument is a glob
46507
- string there is no options object; filter results in your script.
46531
+ - \`searchText\`/\`listFiles\` cover the whole workspace (minus .gitignore);
46532
+ scope with a glob, which matches the full workspace-relative path:
46533
+ \`listFiles("src/**")\` for a directory (a bare name like \`"src"\` is treated
46534
+ as \`src/**\`), \`listFiles()\` for every file. \`searchText\`'s second argument
46535
+ is a glob string — there is no options object; filter results in your script.
46508
46536
  - Symbol paths are slash-separated (\`MyClass/myMethod\`); discover exact paths
46509
46537
  with \`getSymbols(file)\` rather than guessing. Symbol ops always take the
46510
46538
  pair \`(file, symbolPath)\` — a \`SymbolInfo.path\` belongs to the file you
46511
- called \`getSymbols\` on.
46539
+ called \`getSymbols\` on, and round-trips into findReferences/goToDefinition/
46540
+ the write ops.
46512
46541
  - \`searchText\` patterns are regexes — escape metacharacters for literal text:
46513
46542
  \`searchText("new NotFoundError\\\\(")\`.
46543
+ - \`goToDefinition\` only addresses symbols DEFINED in the given file — it cannot
46544
+ follow an imported or called name into another module. To resolve a name to
46545
+ its definition anywhere in the workspace, use \`findSymbol(name)\` and filter
46546
+ for an exact \`name\` match (it also returns substring matches).
46514
46547
  - File paths are relative to the workspace root; anything outside it is
46515
46548
  rejected.
46516
- - Diagnostics cover files touched this session only, never the whole project.`;
46549
+ - Diagnostics cover files touched this session only, never the whole project.
46550
+ \`Diagnostic.range\` is zero-based; every other line/column is 1-based.
46551
+
46552
+ ## API
46553
+
46554
+ {{types}}
46555
+
46556
+ {{writeSemantics}}
46557
+
46558
+ ## Examples
46559
+
46560
+ {{examples}}`;
46517
46561
  var WRITE_SEMANTICS = `Write operations available: writes are buffered during the script and applied
46518
46562
  atomically (flushed to disk) only when the whole script succeeds; if it throws,
46519
46563
  every buffered write is rolled back and the codebase is unchanged. The tool
@@ -46528,7 +46572,8 @@ ${example.code}
46528
46572
  \`\`\``).join(`
46529
46573
 
46530
46574
  `);
46531
- return TEMPLATE.replace("{{types}}", renderLspTypes(readonly3)).replace("{{writeSemantics}}", readonly3 ? READONLY_SEMANTICS : WRITE_SEMANTICS).replace("{{examples}}", examples);
46575
+ const opInventory = readonly3 ? `read: ${READ_OP_NAMES.join(", ")}` : `read: ${READ_OP_NAMES.join(", ")}; write: ${WRITE_OP_NAMES.join(", ")}`;
46576
+ return TEMPLATE.replace("{{opInventory}}", opInventory).replace("{{types}}", renderLspTypes(readonly3)).replace("{{writeSemantics}}", readonly3 ? READONLY_SEMANTICS : WRITE_SEMANTICS).replace("{{examples}}", examples);
46532
46577
  }
46533
46578
 
46534
46579
  // src/mcp-server.ts
@@ -46565,7 +46610,8 @@ function createExecuteRunner(deps) {
46565
46610
  const { result, logs } = await runSandbox(code, {
46566
46611
  lsp: deps.api,
46567
46612
  timeoutMs: deps.timeoutMs,
46568
- readonly: deps.readonly
46613
+ readonly: deps.readonly,
46614
+ helpText: deps.helpText ?? buildToolDescription(deps.readonly ?? false)
46569
46615
  });
46570
46616
  const flushed = buffer.flush();
46571
46617
  const changes = flushed.map((change) => ({
@@ -46629,20 +46675,22 @@ function createServer(options = {}) {
46629
46675
  warmup.catch(() => {
46630
46676
  return;
46631
46677
  });
46678
+ const description = buildToolDescription(readonly3);
46632
46679
  const { execute } = createExecuteRunner({
46633
46680
  api: api2,
46634
46681
  client,
46635
46682
  timeoutMs,
46636
46683
  warmup,
46637
46684
  rootDir,
46638
- readonly: readonly3
46685
+ readonly: readonly3,
46686
+ helpText: description
46639
46687
  });
46640
46688
  const server = new McpServer({
46641
46689
  name: "codemode-lsp",
46642
46690
  version: "0.1.0"
46643
46691
  });
46644
46692
  server.registerTool("execute", {
46645
- description: buildToolDescription(readonly3),
46693
+ description,
46646
46694
  inputSchema: {
46647
46695
  code: exports_external.string().describe("JavaScript to run in the LSP sandbox. The last expression is the return value.")
46648
46696
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemode-lsp",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "MCP server exposing a single code-mode tool backed by LSP",
5
5
  "keywords": [
6
6
  "mcp",