lsp

pez 2025-05-27T09:23:04.287829Z

I found myself starting to write a lint tool for Backseat Driver (which is a CoPilot extension as well as an MCP server). However, then I thought that it would be better if clojure-lsp provided this tool. Any plans/ideas for language model and MCP extensions for clojure-lsp cooking, @ericdallo?

Eric Dvorsak 2025-05-28T11:03:49.762419Z

alternatively as long as relevant features are available as functions, clojure-lsp can just be used as a library by clojure-mcp to expose them as tools

👍 1
ericdallo 2025-05-27T11:27:48.397679Z

next release (today hehe) will add support for custom linters, but they are custom using clojure-lsp analysis, not supported something external, I'm yet to explore that @pez since MCPs are exploding I just to avoid adding too much complexity to clojure-lsp, especially that MCP is a LSP like, I know there are people using lsp4clj to create MCPs but that's is more related to the infra than linting capabilities

pez 2025-05-27T11:51:05.096969Z

Makes sense. I chose to make Backseat Driver a separate extension to get a cleaner cut.

ericdallo 2025-05-27T11:52:18.205299Z

Yeah, Nubank is pretty aggressive with AI, so I believe we will check MCPs and all of that integration with clojure soon

ericdallo 2025-05-27T11:52:51.273749Z

But at start, I don't see that much sense merge this AI stuff with clojure-lsp unless in a generic way like external linters and so

pez 2025-05-27T11:55:21.478649Z

@bhauman has built what amounts to an editor for AI with his Clojure MCP tooling. It lints the code too.

pez 2025-05-27T11:56:34.525309Z

Thinking about it, someone may have already made an LSP MCP server…

borkdude 2025-05-27T11:57:45.884849Z

I think in addition to the command line interface Clojure LSP could also have an MCP interface, similar to how Playwright provides an interface to their browser stuff, maybe that’s the best way how to think about it? So nothing is added to Clojure-LSP, it’s just one extra layer around how to access it?

👍 1
ericdallo 2025-05-27T11:59:17.236659Z

It's not clear to me how those things work yet, but if it's generic enough I'd agree it may make sense

pez 2025-05-27T12:09:36.567199Z

My first web search hit: https://github.com/isaacphi/mcp-language-server

ericdallo 2025-05-27T12:43:49.463889Z

I think a issue would be nice so we can map and explore more, I have MCP protocol page opened for 2 weeks on my machine 😂 need to stop and learn about it

borkdude 2025-05-27T12:45:16.460869Z

since lsp is already a standard protocol I wouldn't be surprised if there would be a generic MCP <-> LSP converter thing somewhere, without being specific to Clojure

👍 1
borkdude 2025-05-27T12:45:55.346229Z

oh that is the thing that @pez linked to

ericdallo 2025-05-27T12:46:39.140199Z

would be nice to test that with clojure-lsp

pez 2025-05-27T12:47:26.722519Z

CoPilot can use it.

ericdallo 2025-05-27T12:48:22.939959Z

That's nice, Mr Clojure fan 😂

borkdude 2025-05-27T12:48:45.461689Z

do you mean copilot can use lsp out of the box?

pez 2025-05-27T12:49:52.218069Z

I mean copilot can use the MCP Language Server (the MCP <-> LSP) server.

🎉 1
pez 2025-05-27T12:51:33.898059Z

CoPilot also has built in tools for all of this, so it is not actually adding features to CoPilot that it doesn’t have. But as a general answer to “can clojure-lsp features be exposed to MCP clients this way?” it works as a test. 😃

pez 2025-05-27T12:52:10.495289Z

CoPilot is an LSP client too, I guess we can think of it as.

pez 2025-05-27T12:56:35.917359Z

For the problem I am trying to solve, I may still need to make a direct use of clojure-lsp linting from Backseat Driver, but this was enlightening anyway. 😃

pez 2025-05-27T12:58:03.501269Z

It’s beautiful: CoPilot MCP Client -> MCP Language Server -> clojure-lsp -> clj-kondo

pez 2025-05-27T12:59:32.609549Z

My mcp.json, for anyone who wants to repeat the experiment:

{
  "servers": {
    "lsp": {
      "type": "stdio",
      "command": "/Users/pez/go/bin/mcp-language-server",
      "args": [
        "--workspace",
        "${workspaceFolder}",
        "--lsp",
        "${extensionInstallFolder:betterthantomorrow.calva}/clojure-lsp"
      ]
    }
  }
}

ericdallo 2025-05-27T13:26:27.359979Z

that mcp-language-server is interesting indeed, @pez could you elabora a little bit more your case for backseat driver?

pez 2025-05-27T13:32:14.641619Z

1. Inspired y Clojure MCP, I want to add an edit_file tool to Backseat Driver. Because the built in tool for this too often ends up trashing the bracket balance. I have a tool for balancing brackets, but it doesn’t work because it seems the main breakage happens at applying edits. 2. CoPilot has tools for checking the linters reports, but it most often the agent choses to ignore to use it. So it happily will announce that it has done stuff while clj-kondo is screaming “THIS WILL NOT WORK!“. So as part of editing I want to lint and feed this back to the agent, liek “file edited, but the linter doesn’t like it, here’s what it says: …“.

ericdallo 2025-05-27T13:33:56.049329Z

got it, and IIRC using the mcp-language-server you can have this MCP that communicates with clojure-lsp and then your and ask to check the diagnostics of it, right?

pez 2025-05-27T13:45:11.032609Z

I don’t think I have much use for the MCP server for this. Backseat Driver is a VS Code extension, I will try to go straight at clojure-lsp, or straight at the error reports (I will try the latter first). Also: a value prop of Backseat Driver is zero conf. It doesn’t require any MCP things, and certainly not going through the loops and hoops installing go and then the server and doing all the conf for it.

ericdallo 2025-05-27T13:48:41.253519Z

I see, agree about the zero conf rationale

2025-05-27T16:09:54.376119Z

MCP is a way to describe a set of interfaces to the LLM, so it knows how to use it with a layer to then call those interfaces. So clojure-lsp could expose an MCP server endpoint, that an LLM can then call to retrieve a description of how it can go ahead and call it back in order to perform various tasks. It could tell the LLM how to use clojure-lsp to perform a code refactor, or to retrieve code information, and so on. Then an MCP client, something that wraps an LLM provides a layer to actually call the MCP server. The LLM fetches the interface descriptions from all the configured MCP servers (using the client) and then from that description it knows how to generate a "JSON" CALL, the client interprets that JSON and makes the actual call based on it, and then returns the results back to the LLM.

2025-05-27T16:11:27.853959Z

But clojure-lsp doesn't have to expose it's functionality through MCP, another thing could wrap it in an MCP server. And since lsp is already standard, maybe a generic MCP server for lsp could do it. That said, the description of the available functions maybe could benefit to be specialized to clojure-lsp so the model better understands when/how to leverage them.

ericdallo 2025-05-27T16:40:09.254109Z

thanks for the explanation @didibus! > And since lsp is already standard, maybe a generic MCP server for lsp could do it. agreed, as I mentioned https://clojurians.slack.com/archives/CPABC1H61/p1748352836049329?thread_ts=1748337784.287829&amp;cid=CPABC1H61, that's how I see it, just not sure how smooth is that integration or if having that exposed in clojure-lsp would help further more > That said, the description of the available functions maybe could benefit to be specialized to clojure-lsp so the model better understands when/how to leverage them. Not sure what means making this description of the available functions available

2025-05-27T17:18:35.030509Z

The MCP server returns available tools like:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "get_weather",
        "description": "Retrieve the current weather for a specified city.",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {
              "type": "string",
              "description": "Name of the city to get the weather for."
            }
          },
          "required": ["city"]
        }
      },
      {
        "name": "calculate_sum",
        "description": "Calculate the sum of two numbers.",
        "parameters": {
          "type": "object",
          "properties": {
            "a": {
              "type": "number",
              "description": "First number."
            },
            "b": {
              "type": "number",
              "description": "Second number."
            }
          },
          "required": ["a", "b"]
        }
      }
    ]
  }
}
So making those specialized to Clojure might help the LLM decide when to use each and how best to use them. Like if lsp just has something like: rename_module and a generic description "Renames the module" It might work better if it is exposed to the LLM as rename_namespace "Rename the Clojure namespace and automatically rename all use of it throughout the Clojure project"

2025-05-27T17:19:39.476779Z

Now if you prompt the LLM with: Can you rename my namespace foo to bar? It'll probably better understand that it can use the above tool for it.

ericdallo 2025-05-27T17:21:00.907679Z

Got it, that makes sense, actually there are multiple clojure-lsp commands that handle paredit cases and so on that I think would help LLM as well.

ericdallo 2025-05-27T17:21:45.189689Z

Yeah, interesting, I can see clojure-lsp working as a mcp server to enhance that, Ill take a closer look at this soon

pez 2025-05-27T21:11:29.544429Z

> And since lsp is already standard, maybe a generic MCP server for lsp could do it. That has been confirmed to hold true earlier in this thread. 😃

ericdallo 2025-05-27T21:23:14.528979Z

yes for most features it works, but I believe is not great as there are custom commands and custom lsp features not included in default protocol (ex paredit custom commands) that we could describe better, well, needs some testing to confirm that too

ericdallo 2025-05-27T14:28:19.221989Z

clojure-lsp Released clojure-lsp https://github.com/clojure-lsp/clojure-lsp/releases/tag/2025.05.27-13.56.57 with exciting new features, performance improvements and fixes 🎉 • General ◦ Consider .lsp/config.edn as part of project code, removing false positives of unused-public-var linter. ◦ Consider full qualified symbols in edn files when checking for var references. ◦ Improve clojure-lsp linter capabilities, migrating unused-public-var and different-aliases linters to be built-in linters. https://github.com/clojure-lsp/clojure-lsp/pull/2050 ▪︎ Migrate from clj-kondo custom-lint-fn but considering kondo settings to avoid breaking changes. ◦ Considerably improve performance of unused-public-var and different-aliases linters. ◦ Bump rewrite-clj to 1.2.50. ◦ New feature: Add support for custom project linters. https://github.com/clojure-lsp/clojure-lsp/issues/2043 https://github.com/clojure-lsp/clojure-lsp/issues/2058 ◦ Publish to clojars com.github.clojure-lsp/clojure-lsp-test-helper to be able to test created custom linters. ◦ Bump clj-kondo to 2025.04.08-20250526.195207-12. ◦ Small performance improvements across clojure-lsp, especially on places with comparassions inside big loops. ◦ Bump clj-depend to 0.11.1. ◦ Provide analysis of unresolved namespaces, making features like definition, hover, references work. • Editor ◦ Add support for LSP feature textDocument/selectionRange. https://github.com/clojure-lsp/clojure-lsp/issues/1961 ◦ Fix outdated analysis for watched created/deleted files (git branch switchs for example). https://github.com/clojure-lsp/clojure-lsp/issues/2046 • API/CLI ◦ Replace tools.cli with babashka.cli. https://github.com/clojure-lsp/clojure-lsp/issues/2036 ◦ Include :clj-kondo-settings to dump data. Main highlights: • Have you ever wanted to create custom linters about your whole project with awareness about your codebase? Like imagine linters about missing tests in your codebase (pic1) 🚀 ◦ Now it's possible via clojure-lsp leveraging sci, more details https://clojure-lsp.io/features/#custom-linters, this was a huge work that @andreribeirocamargo and I did and I'm very excited about it, making possible to create complex linters for better code quality, with support for tests! • No need to restart LSP anymore when changing git branches, clojure-lsp should lint the changed files automatically staying consistent. • clojure-lsp now consider references of vars in edn files, avoiding false-positives of unused-public-var. • We improved performance of LSP linters, completely migrating then to be built-in clojure-lsp, decoupling them from clj-kondo. happy coding! 👋

4
🎉 12
2025-05-27T18:09:55.284539Z

the custom linter stuff looks very cool

2025-05-27T18:10:12.384549Z

i see i commented on one of the issues 3 weeks ago and never responded 🙈

ericdallo 2025-05-27T19:52:07.492269Z

ah yeah, that idea is interesting, but needs more thoughs