@theupsider/lsp-mcp 1.0.1 → 1.0.7

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.
@@ -10,78 +10,78 @@ exports.formatHealth = formatHealth;
10
10
  exports.formatError = formatError;
11
11
  const uri_1 = require("../utils/uri");
12
12
  const SYMBOL_KIND_ICONS = {
13
- 1: '📄',
14
- 2: '📦',
15
- 3: '🔖',
16
- 4: '🧩',
17
- 5: '📦',
18
- 6: '🔧',
19
- 7: '🏗️',
20
- 8: '🧠',
21
- 9: '📐',
22
- 10: '📚',
23
- 11: '🔌',
24
- 12: 'ƒ',
25
- 13: '',
26
- 14: '🔒',
27
- 15: '📝',
28
- 16: '',
29
- 17: '🧮',
30
- 18: '📏',
31
- 19: '🧱',
32
- 20: '🔑',
33
- 21: '',
34
- 22: '🧩',
35
- 23: '➡️',
36
- 24: '🎯',
37
- 25: '📦',
38
- 26: '🔎'
13
+ 1: "📄",
14
+ 2: "📦",
15
+ 3: "🔖",
16
+ 4: "🧩",
17
+ 5: "📦",
18
+ 6: "🔧",
19
+ 7: "🏗️",
20
+ 8: "🧠",
21
+ 9: "📐",
22
+ 10: "📚",
23
+ 11: "🔌",
24
+ 12: "ƒ",
25
+ 13: "",
26
+ 14: "🔒",
27
+ 15: "📝",
28
+ 16: "",
29
+ 17: "🧮",
30
+ 18: "📏",
31
+ 19: "🧱",
32
+ 20: "🔑",
33
+ 21: "",
34
+ 22: "🧩",
35
+ 23: "➡️",
36
+ 24: "🎯",
37
+ 25: "📦",
38
+ 26: "🔎",
39
39
  };
40
40
  const COMPLETION_KIND_LABELS = {
41
- 2: 'Methods',
42
- 3: 'Functions',
43
- 4: 'Constructors',
44
- 5: 'Fields',
45
- 6: 'Variables',
46
- 7: 'Classes',
47
- 8: 'Interfaces',
48
- 9: 'Modules',
49
- 10: 'Properties',
50
- 11: 'Units',
51
- 12: 'Values',
52
- 13: 'Enums',
53
- 14: 'Keywords',
54
- 15: 'Snippets',
55
- 16: 'Colors',
56
- 17: 'Files',
57
- 18: 'References',
58
- 19: 'Folders',
59
- 20: 'Enum members',
60
- 21: 'Constants',
61
- 22: 'Structs',
62
- 23: 'Events',
63
- 24: 'Operators',
64
- 25: 'Type parameters'
41
+ 2: "Methods",
42
+ 3: "Functions",
43
+ 4: "Constructors",
44
+ 5: "Fields",
45
+ 6: "Variables",
46
+ 7: "Classes",
47
+ 8: "Interfaces",
48
+ 9: "Modules",
49
+ 10: "Properties",
50
+ 11: "Units",
51
+ 12: "Values",
52
+ 13: "Enums",
53
+ 14: "Keywords",
54
+ 15: "Snippets",
55
+ 16: "Colors",
56
+ 17: "Files",
57
+ 18: "References",
58
+ 19: "Folders",
59
+ 20: "Enum members",
60
+ 21: "Constants",
61
+ 22: "Structs",
62
+ 23: "Events",
63
+ 24: "Operators",
64
+ 25: "Type parameters",
65
65
  };
66
66
  const DIAGNOSTIC_SEVERITY_LABELS = {
67
- 1: 'Errors',
68
- 2: 'Warnings',
69
- 3: 'Information',
70
- 4: 'Hints'
67
+ 1: "Errors",
68
+ 2: "Warnings",
69
+ 3: "Information",
70
+ 4: "Hints",
71
71
  };
72
72
  function formatHover(result) {
73
73
  if (!result) {
74
- return 'No result';
74
+ return "No result";
75
75
  }
76
76
  const rawText = hoverContentsToText(result.contents).trim();
77
77
  if (!rawText) {
78
- return 'No result';
78
+ return "No result";
79
79
  }
80
80
  const codeBlocks = Array.from(rawText.matchAll(/```(?<lang>[^\n`]*)\n(?<code>[\s\S]*?)```/g));
81
81
  const firstCode = codeBlocks[0]?.groups?.code?.trim();
82
- const firstLang = codeBlocks[0]?.groups?.lang?.trim() ?? '';
83
- const summarySource = rawText.replace(/```[\s\S]*?```/g, '').trim();
84
- const summaryLine = summarySource.split(/\n+/).find(Boolean) ?? '';
82
+ const firstLang = codeBlocks[0]?.groups?.lang?.trim() ?? "";
83
+ const summarySource = rawText.replace(/```[\s\S]*?```/g, "").trim();
84
+ const summaryLine = summarySource.split(/\n+/).find(Boolean) ?? "";
85
85
  if (firstCode && summaryLine) {
86
86
  return `**${firstCode}** — ${summaryLine}\n\n\`\`\`${firstLang}\n${firstCode}\n\`\`\``;
87
87
  }
@@ -89,34 +89,44 @@ function formatHover(result) {
89
89
  }
90
90
  function formatDefinition(locations) {
91
91
  if (!locations || locations.length === 0) {
92
- return 'No result';
92
+ return "No result";
93
93
  }
94
94
  if (locations.length === 1) {
95
95
  return `Found 1 definition: \`${formatLocation(locations[0])}\``;
96
96
  }
97
- return ['Found definitions:', ...locations.map((location) => `- \`${formatLocation(location)}\``)].join('\n');
97
+ return [
98
+ "Found definitions:",
99
+ ...locations.map((location) => `- \`${formatLocation(location)}\``),
100
+ ].join("\n");
98
101
  }
99
102
  function formatReferences(locations) {
100
103
  if (!locations || locations.length === 0) {
101
- return 'No result';
104
+ return "No result";
102
105
  }
103
- return [`Found ${locations.length} references:`, ...locations.map((location) => `- \`${formatLocation(location)}\``)].join('\n');
106
+ return [
107
+ `Found ${locations.length} references:`,
108
+ ...locations.map((location) => `- \`${formatLocation(location)}\``),
109
+ ].join("\n");
104
110
  }
105
111
  function formatSymbols(symbols) {
106
112
  if (!symbols || symbols.length === 0) {
107
- return 'No result';
113
+ return "No result";
108
114
  }
109
- return symbols.map((symbol) => {
110
- const icon = SYMBOL_KIND_ICONS[symbol.kind] ?? '•';
111
- const detail = 'location' in symbol
115
+ return symbols
116
+ .map((symbol) => {
117
+ const icon = SYMBOL_KIND_ICONS[symbol.kind] ?? "•";
118
+ const detail = "location" in symbol
112
119
  ? formatLocation(symbol.location)
113
120
  : `${symbol.name}`;
114
- return `- ${icon} \`${symbol.name}\`${'location' in symbol ? ` — ${detail}` : ''}`;
115
- }).join('\n');
121
+ return `- ${icon} \`${symbol.name}\`${"location" in symbol ? ` — ${detail}` : ""}`;
122
+ })
123
+ .join("\n");
116
124
  }
117
125
  function formatDiagnostics(diagnostics, scope) {
118
126
  if (!diagnostics || diagnostics.length === 0) {
119
- return scope === 'workspace' ? 'No diagnostics found in workspace' : 'No diagnostics found';
127
+ return scope === "workspace"
128
+ ? "No diagnostics found in workspace"
129
+ : "No diagnostics found";
120
130
  }
121
131
  const grouped = new Map();
122
132
  const ordered = [...diagnostics].sort((left, right) => (left.severity ?? 4) - (right.severity ?? 4));
@@ -126,77 +136,85 @@ function formatDiagnostics(diagnostics, scope) {
126
136
  bucket.push(diagnostic);
127
137
  grouped.set(severity, bucket);
128
138
  }
129
- const lines = [`${scope === 'workspace' ? 'Workspace' : 'File'} diagnostics: ${diagnostics.length} issue(s)`];
139
+ const lines = [
140
+ `${scope === "workspace" ? "Workspace" : "File"} diagnostics: ${diagnostics.length} issue(s)`,
141
+ ];
130
142
  for (const severity of [1, 2, 3, 4]) {
131
143
  const bucket = grouped.get(severity);
132
144
  if (!bucket || bucket.length === 0) {
133
145
  continue;
134
146
  }
135
- lines.push('', `### ${DIAGNOSTIC_SEVERITY_LABELS[severity]}`);
147
+ lines.push("", `### ${DIAGNOSTIC_SEVERITY_LABELS[severity]}`);
136
148
  for (const diagnostic of bucket) {
137
149
  lines.push(`- ${formatDiagnostic(diagnostic)}`);
138
150
  }
139
151
  }
140
- return lines.join('\n');
152
+ return lines.join("\n");
141
153
  }
142
154
  function formatCompletion(items) {
143
155
  if (!items || items.length === 0) {
144
- return 'No result';
156
+ return "No result";
145
157
  }
146
158
  const limited = items.slice(0, 50);
147
159
  const grouped = new Map();
148
160
  for (const item of limited) {
149
- const label = COMPLETION_KIND_LABELS[item.kind ?? 1] ?? 'Other';
161
+ const label = COMPLETION_KIND_LABELS[item.kind ?? 1] ?? "Other";
150
162
  const bucket = grouped.get(label) ?? [];
151
163
  bucket.push(item);
152
164
  grouped.set(label, bucket);
153
165
  }
154
- const lines = [`Showing ${limited.length} of ${items.length} completion item(s)`];
166
+ const lines = [
167
+ `Showing ${limited.length} of ${items.length} completion item(s)`,
168
+ ];
155
169
  for (const [label, bucket] of grouped.entries()) {
156
- lines.push('', `### ${label}`);
170
+ lines.push("", `### ${label}`);
157
171
  for (const item of bucket) {
158
- lines.push(`- \`${item.label}\`${item.detail ? ` — ${item.detail}` : ''}`);
172
+ lines.push(`- \`${item.label}\`${item.detail ? ` — ${item.detail}` : ""}`);
159
173
  }
160
174
  }
161
- return lines.join('\n');
175
+ return lines.join("\n");
162
176
  }
163
177
  function formatHealth(healths) {
164
178
  if (healths.length === 0) {
165
- return 'No result';
179
+ return "No result";
166
180
  }
167
181
  return [
168
- '| Language | Status | Error |',
169
- '| --- | --- | --- |',
170
- ...healths.map((health) => `| ${health.language} | ${health.status} | ${health.error ?? ''} |`)
171
- ].join('\n');
182
+ "| Language | Status | Error |",
183
+ "| --- | --- | --- |",
184
+ ...healths.map((health) => `| ${health.language} | ${health.status} | ${health.error ?? ""} |`),
185
+ ].join("\n");
172
186
  }
173
187
  function formatError(error) {
174
188
  return {
175
189
  error: true,
176
190
  text: error instanceof Error ? error.message : String(error),
177
- raw: error
191
+ raw: error,
178
192
  };
179
193
  }
180
194
  function hoverContentsToText(contents) {
181
- if (typeof contents === 'string') {
195
+ if (typeof contents === "string") {
182
196
  return contents;
183
197
  }
184
198
  if (Array.isArray(contents)) {
185
- return contents.map(markedStringToText).join('\n\n');
199
+ return contents.map(markedStringToText).join("\n\n");
186
200
  }
187
- if ('kind' in contents) {
201
+ if ("kind" in contents) {
188
202
  return contents.value;
189
203
  }
190
204
  return markedStringToText(contents);
191
205
  }
192
206
  function markedStringToText(value) {
193
- return typeof value === 'string' ? value : `\`\`\`${value.language}\n${value.value}\n\`\`\``;
207
+ return typeof value === "string"
208
+ ? value
209
+ : `\`\`\`${value.language}\n${value.value}\n\`\`\``;
194
210
  }
195
211
  function formatLocation(location) {
196
212
  return `${(0, uri_1.uriToPath)(location.uri)}:${location.range.start.line + 1}:${location.range.start.character + 1}`;
197
213
  }
198
214
  function formatDiagnostic(diagnostic) {
199
- const location = diagnostic.uri ? `\`${(0, uri_1.uriToPath)(diagnostic.uri)}:${diagnostic.range.start.line + 1}:${diagnostic.range.start.character + 1}\` ` : '';
200
- const source = diagnostic.source ? `${diagnostic.source}: ` : '';
215
+ const location = diagnostic.uri
216
+ ? `\`${(0, uri_1.uriToPath)(diagnostic.uri)}:${diagnostic.range.start.line + 1}:${diagnostic.range.start.character + 1}\` `
217
+ : "";
218
+ const source = diagnostic.source ? `${diagnostic.source}: ` : "";
201
219
  return `${location}${source}${diagnostic.message}`.trim();
202
220
  }
@@ -18,19 +18,29 @@ class McpServer {
18
18
  createLifecycleManager;
19
19
  server;
20
20
  tools = new Map();
21
- constructor(logLevel = 'info', createLifecycleManager = (root, level) => new lifecycle_manager_1.LifecycleManager(root, level)) {
21
+ constructor(logLevel = "info", createLifecycleManager = (root, level) => new lifecycle_manager_1.LifecycleManager(root, level)) {
22
22
  this.logLevel = logLevel;
23
23
  this.createLifecycleManager = createLifecycleManager;
24
- this.server = new mcp_js_1.McpServer({ name: '@theupsider/lsp-mcp', version: '0.1.0' });
24
+ this.server = new mcp_js_1.McpServer({
25
+ name: "@theupsider/lsp-mcp",
26
+ version: "0.1.0",
27
+ });
25
28
  }
26
29
  async start() {
27
30
  const lifecycleProxy = this.createLifecycleProxy();
28
31
  const registrar = {
29
32
  registerTool: (name, config, handler) => {
30
- this.tools.set(name, { name, description: config.description, inputSchema: config.inputSchema, handler });
31
- }
33
+ this.tools.set(name, {
34
+ name,
35
+ description: config.description,
36
+ inputSchema: config.inputSchema,
37
+ handler,
38
+ });
39
+ },
32
40
  };
33
- (0, read_tools_1.registerReadTools)(registrar, lifecycleProxy, { initializeManager: async (root, languages) => await this.initializeManager(root, languages) });
41
+ (0, read_tools_1.registerReadTools)(registrar, lifecycleProxy, {
42
+ initializeManager: async (root, languages) => await this.initializeManager(root, languages),
43
+ });
34
44
  (0, write_tools_1.registerWriteTools)(registrar, lifecycleProxy);
35
45
  this.configureToolHandlers();
36
46
  const transport = new stdio_js_1.StdioServerTransport();
@@ -64,13 +74,15 @@ class McpServer {
64
74
  }
65
75
  configureToolHandlers() {
66
76
  this.server.server.registerCapabilities({ tools: {} });
67
- this.server.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: this.listTools() }));
77
+ this.server.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
78
+ tools: this.listTools(),
79
+ }));
68
80
  this.server.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
69
81
  const toolName = request.params.name;
70
- if (toolName === 'lsp_init' && this.initialized) {
71
- return (0, shared_1.failure)(`Already initialized at ${this.currentRoot ?? 'unknown'}. Call lsp_init again with a new root to switch projects.`);
82
+ if (toolName === "lsp_init" && this.initialized) {
83
+ return (0, shared_1.failure)(`Already initialized at ${this.currentRoot ?? "unknown"}. Call lsp_init again with a new root to switch projects.`);
72
84
  }
73
- if (toolName !== 'lsp_init' && !this.initialized) {
85
+ if (toolName !== "lsp_init" && !this.initialized) {
74
86
  return (0, shared_1.noProjectRootResult)();
75
87
  }
76
88
  const tool = this.tools.get(toolName);
@@ -84,15 +96,18 @@ class McpServer {
84
96
  return [...this.tools.values()].map((tool) => ({
85
97
  name: tool.name,
86
98
  description: tool.description,
87
- inputSchema: this.toInputSchema(tool.inputSchema)
99
+ inputSchema: this.toInputSchema(tool.inputSchema),
88
100
  }));
89
101
  }
90
102
  toInputSchema(schema) {
91
103
  const normalized = (0, zod_compat_js_1.normalizeObjectSchema)(schema);
92
104
  const result = normalized
93
- ? (0, zod_json_schema_compat_js_1.toJsonSchemaCompat)(normalized, { strictUnions: true, pipeStrategy: 'input' })
105
+ ? (0, zod_json_schema_compat_js_1.toJsonSchemaCompat)(normalized, {
106
+ strictUnions: true,
107
+ pipeStrategy: "input",
108
+ })
94
109
  : {};
95
- return { type: 'object', ...result };
110
+ return { type: "object", ...result };
96
111
  }
97
112
  createLifecycleProxy() {
98
113
  return {
@@ -102,7 +117,8 @@ class McpServer {
102
117
  getWorkspaceDiagnostics: (language) => this.requireManager().getWorkspaceDiagnostics(language),
103
118
  getHealth: () => this.requireManager().getHealth(),
104
119
  ensureLanguageForFile: async (filePath) => await this.requireManager().ensureLanguageForFile(filePath),
105
- ensureSeedFilesOpen: async () => await this.requireManager().ensureSeedFilesOpen()
120
+ ensureSeedFilesOpen: async () => await this.requireManager().ensureSeedFilesOpen(),
121
+ analyzeWorkspace: async (language) => await this.requireManager().analyzeWorkspace(language),
106
122
  };
107
123
  }
108
124
  requireManager() {
@@ -114,5 +130,5 @@ class McpServer {
114
130
  }
115
131
  exports.McpServer = McpServer;
116
132
  function isRecord(value) {
117
- return typeof value === 'object' && value !== null;
133
+ return typeof value === "object" && value !== null;
118
134
  }