webnav-ts-mcp 0.1.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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Expected tool failures, rendered as agent-readable text.
3
+ *
4
+ * The MCP framework collapses any exception a tool throws into an opaque
5
+ * "Error executing tool …", hiding actionable causes like a mistyped path.
6
+ * Tools catch what `isToolError` accepts and return `formatToolError(exc)`.
7
+ */
8
+ /** The caller asked for something the tool can't serve (e.g. unsupported file type). */
9
+ export class ToolInputError extends Error {
10
+ name = "ToolInputError";
11
+ }
12
+ /** JSON-RPC error response from the language server. */
13
+ export class LspRequestError extends Error {
14
+ method;
15
+ code;
16
+ name = "LspRequestError";
17
+ constructor(method, code, message) {
18
+ super(message);
19
+ this.method = method;
20
+ this.code = code;
21
+ }
22
+ }
23
+ /** The language server process ended while a request was pending. */
24
+ export class LanguageServerExitedError extends Error {
25
+ name = "LanguageServerExitedError";
26
+ }
27
+ /** A tool was asked about a line/column that doesn't exist in the file. */
28
+ export class InvalidPositionError extends Error {
29
+ name = "InvalidPositionError";
30
+ }
31
+ /** A language-server request outlived its deadline. */
32
+ export class LspTimeoutError extends Error {
33
+ name = "LspTimeoutError";
34
+ }
35
+ /** A file that isn't valid UTF-8 text. */
36
+ export class Utf8DecodeError extends Error {
37
+ name = "Utf8DecodeError";
38
+ }
39
+ export function isFsError(exc) {
40
+ return exc instanceof Error && typeof exc.code === "string" && "syscall" in exc;
41
+ }
42
+ /** True for every failure a tool should report as text instead of throwing. */
43
+ export function isToolError(exc) {
44
+ return (exc instanceof LspRequestError ||
45
+ exc instanceof LanguageServerExitedError ||
46
+ exc instanceof ToolInputError ||
47
+ exc instanceof InvalidPositionError ||
48
+ exc instanceof LspTimeoutError ||
49
+ exc instanceof Utf8DecodeError ||
50
+ isFsError(exc));
51
+ }
52
+ export function formatToolError(exc) {
53
+ if (exc instanceof LspRequestError) {
54
+ return `LSP error on ${exc.method}: ${exc.message}`;
55
+ }
56
+ if (exc instanceof LspTimeoutError) {
57
+ return "Language server timed out (it may still be indexing the workspace); retry shortly.";
58
+ }
59
+ if (exc instanceof Utf8DecodeError) {
60
+ return `Cannot read file as UTF-8 text: ${exc.message}.`;
61
+ }
62
+ if (isFsError(exc)) {
63
+ const spawnFailure = exc.syscall?.startsWith("spawn") ?? false;
64
+ const reason = exc.message.replace(/^[A-Z_]+: /, "").replace(/, (?:open|stat|spawn|scandir|read)\b.*$/, "");
65
+ if (exc.code === "ENOENT" && !spawnFailure && exc.path) {
66
+ return `File not found: ${exc.path} (relative paths resolve against the workspace root).`;
67
+ }
68
+ if (exc.code === "ENOENT") {
69
+ return `Cannot start language server (file not found): ${reason}.`;
70
+ }
71
+ if (spawnFailure) {
72
+ return `Cannot start language server: ${reason}.`;
73
+ }
74
+ if (exc.path) {
75
+ return `Cannot read ${exc.path}: ${reason}.`;
76
+ }
77
+ return `${reason}.`;
78
+ }
79
+ return exc.message;
80
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Directory names every workspace scan skips: vendored/generated/virtualenv
3
+ * trees that are never a project's own source, so scanning them wastes time
4
+ * at best and can abort a whole call on the first non-UTF-8 file at worst.
5
+ */
6
+ import path from "node:path";
7
+ export const EXCLUDED_DIR_NAMES = new Set([
8
+ "node_modules",
9
+ ".git",
10
+ "vendor",
11
+ "dist",
12
+ "build",
13
+ "__pycache__",
14
+ ".venv",
15
+ "venv",
16
+ ".venv-bare",
17
+ "env",
18
+ ".env",
19
+ "site-packages",
20
+ ".tox",
21
+ ".mypy_cache",
22
+ ".pytest_cache",
23
+ ".ruff_cache",
24
+ ".pytest-testmon",
25
+ ".eggs",
26
+ ]);
27
+ /** Whether `p` (absolute or root-relative) sits under a directory name in `EXCLUDED_DIR_NAMES` anywhere below `root`. */
28
+ export function isExcluded(p, root) {
29
+ const rel = path.relative(root, p);
30
+ const parts = (rel.startsWith("..") || path.isAbsolute(rel) ? p : rel).split(path.sep);
31
+ return parts.some((part) => EXCLUDED_DIR_NAMES.has(part));
32
+ }