11--- Tool implementation for getting diagnostics.
22
3- -- NOTE: Its important we don't tip off Claude that we're dealing with Neovim LSP diagnostics because it may adjust
4- -- line and col numbers by 1 on its own (since it knows nvim LSP diagnostics are 0-indexed). By calling these
5- -- "editor diagnostics" and converting to 1-indexed ourselves we (hopefully) avoid incorrect line and column numbers
6- -- in Claude's responses.
73local schema = {
84 description = " Get language diagnostics (errors, warnings) from the editor" ,
95 inputSchema = {
@@ -19,14 +15,81 @@ local schema = {
1915 },
2016}
2117
18+ local severity_names = {
19+ [1 ] = " Error" ,
20+ [2 ] = " Warning" ,
21+ [3 ] = " Info" ,
22+ [4 ] = " Hint" ,
23+ }
24+
25+ local function starts_with (str , prefix )
26+ return type (str ) == " string" and str :sub (1 , # prefix ) == prefix
27+ end
28+
29+ local function path_to_uri (path )
30+ if vim .uri_from_fname then
31+ return vim .uri_from_fname (path )
32+ end
33+ return " file://" .. path
34+ end
35+
36+ local function severity_name (severity )
37+ if type (severity ) == " string" then
38+ return severity
39+ end
40+ return severity_names [severity ] or " Info"
41+ end
42+
43+ local function diagnostic_range (diagnostic )
44+ local start_line = diagnostic .lnum or 0
45+ local start_col = diagnostic .col or 0
46+ local end_line = diagnostic .end_lnum or start_line
47+ local end_col = diagnostic .end_col or (start_col + 1 )
48+
49+ return {
50+ start = { line = start_line , character = start_col },
51+ [" end" ] = { line = end_line , character = end_col },
52+ }
53+ end
54+
55+ local function get_diagnostic_path (diagnostic , fallback_path )
56+ if diagnostic .bufnr then
57+ local file_path = vim .api .nvim_buf_get_name (diagnostic .bufnr )
58+ if file_path and file_path ~= " " then
59+ return file_path
60+ end
61+ end
62+ return fallback_path
63+ end
64+
65+ local function append_diagnostic (files , file_by_uri , file_path , diagnostic )
66+ if not file_path or file_path == " " then
67+ return
68+ end
69+
70+ local uri = path_to_uri (file_path )
71+ local file = file_by_uri [uri ]
72+ if not file then
73+ file = { uri = uri , diagnostics = {} }
74+ file_by_uri [uri ] = file
75+ table.insert (files , file )
76+ end
77+
78+ table.insert (file .diagnostics , {
79+ message = diagnostic .message or " " ,
80+ severity = severity_name (diagnostic .severity ),
81+ range = diagnostic_range (diagnostic ),
82+ source = diagnostic .source ,
83+ code = diagnostic .code and tostring (diagnostic .code ) or nil ,
84+ })
85+ end
86+
2287--- Handles the getDiagnostics tool invocation.
2388--- Retrieves diagnostics from Neovim's diagnostic system.
2489--- @param params table The input parameters for the tool
2590--- @return table diagnostics MCP-compliant response with diagnostics data
2691local function handler (params )
2792 if not vim .lsp or not vim .diagnostic or not vim .diagnostic .get then
28- -- Returning an empty list or a specific status could be an alternative.
29- -- For now, let's align with the error pattern for consistency if the feature is unavailable.
3093 error ({
3194 code = - 32000 ,
3295 message = " Feature unavailable" ,
@@ -38,58 +101,44 @@ local function handler(params)
38101
39102 logger .debug (" getDiagnostics handler called with params: " .. vim .inspect (params ))
40103
41- -- Extract the uri parameter
42104 local diagnostics
105+ local fallback_path
43106
44107 if not params .uri then
45- -- Get diagnostics for all buffers
46108 logger .debug (" Getting diagnostics for all open buffers" )
47109 diagnostics = vim .diagnostic .get (nil )
48110 else
49111 local uri = params .uri
50- local filepath = vim . startswith (uri , " file://" ) and vim .uri_to_fname (uri ) or uri
112+ fallback_path = starts_with (uri , " file://" ) and vim .uri_to_fname (uri ) or uri
51113
52- -- Get buffer number for the specific file
53- local bufnr = vim .fn .bufnr (filepath )
114+ local bufnr = vim .fn .bufnr (fallback_path )
54115 if bufnr == - 1 then
55- -- File is not open in any buffer, throw an error
56- logger .debug (" File buffer must be open to get diagnostics: " .. filepath )
116+ logger .debug (" File buffer must be open to get diagnostics: " .. fallback_path )
57117 error ({
58118 code = - 32001 ,
59119 message = " File not open" ,
60- data = " File must be open to retrieve diagnostics: " .. filepath ,
120+ data = " File must be open to retrieve diagnostics: " .. fallback_path ,
61121 })
62- else
63- -- Get diagnostics for the specific buffer
64- logger .debug (" Getting diagnostics for bufnr: " .. bufnr )
65- diagnostics = vim .diagnostic .get (bufnr )
66122 end
123+
124+ logger .debug (" Getting diagnostics for bufnr: " .. bufnr )
125+ diagnostics = vim .diagnostic .get (bufnr )
67126 end
68127
69- local formatted_diagnostics = {}
70- for _ , diagnostic in ipairs (diagnostics ) do
71- local file_path = vim .api .nvim_buf_get_name (diagnostic .bufnr )
72- -- Ensure we only include diagnostics with valid file paths
73- if file_path and file_path ~= " " then
74- table.insert (formatted_diagnostics , {
75- type = " text" ,
76- -- json encode this
77- text = vim .json .encode ({
78- -- Use the file path and diagnostic information
79- filePath = file_path ,
80- -- Convert line and column to 1-indexed
81- line = diagnostic .lnum + 1 ,
82- character = diagnostic .col + 1 ,
83- severity = diagnostic .severity , -- e.g., vim.diagnostic.severity.ERROR
84- message = diagnostic .message ,
85- source = diagnostic .source ,
86- }),
87- })
88- end
128+ local files = {}
129+ local file_by_uri = {}
130+
131+ for _ , diagnostic in ipairs (diagnostics or {}) do
132+ append_diagnostic (files , file_by_uri , get_diagnostic_path (diagnostic , fallback_path ), diagnostic )
89133 end
90134
91135 return {
92- content = formatted_diagnostics ,
136+ content = {
137+ {
138+ type = " text" ,
139+ text = vim .json .encode (files ),
140+ },
141+ },
93142 }
94143end
95144
0 commit comments