The Language Server Protocol (LSP) is the protocol an editor or IDE uses to talk to a language server, a separate process that supplies completion, go to definition, find references and diagnostics. Messages are JSON-RPC 2.0, framed with a Content-Length header and usually carried over the server's standard input and output.
The current version on the official specification site, checked on 23 October 2026, is 3.18, with a change log date of 06/04/2026; 3.17 (05/10/2022) is now labelled the previous version. This guide covers the wire format, what changed across 3.x, and the part most explainers skip: a language server reads, and often builds, whatever repository you open, which is why editors added workspace trust.
Key takeaways
- LSP moves language smarts out of the editor, so M editors and N languages need M plus N integrations instead of M times N.
- Each message is an ASCII header with a required
Content-Lengthin bytes, a blank line, then one UTF-8 JSON-RPC 2.0 message. Batches are not supported. - Sessions start with
initialize(a capability exchange) andinitialized, and end withshutdownandexit. - LSP has no authentication, encryption or authorization. It assumes the client started a trusted local process.
- The real risk is what the server does with your project. VS Code's documentation warns that a language extension "may execute code from the currently loaded workspace".
- VS Code's Workspace Trust opens unfamiliar folders in Restricted Mode, limiting extensions, tasks, debugging, the terminal and AI agents. It cannot stop a malicious extension.
- LSP, the Debug Adapter Protocol (DAP) and the Model Context Protocol (MCP) share one design idea. AI coding tools now use all three.
What LSP is for, where it sits and which document defines it
The M times N problem
Before LSP, every editor that wanted completion or go to definition for a language built it against its own plugin API. The official overview says this work "must be repeated for each development tool, as each provides different APIs for implementing the same features." With M editors and N languages, that is M times N integrations.
LSP puts the language knowledge in a server that speaks a standard protocol. Each language needs one server and each editor one LSP client, so the total becomes M plus N. The trick is the data model: LSP talks in document URIs and cursor positions, not syntax trees or compiler symbols. A URI and a line and character offset mean the same thing in every language, so the protocol stays small.
Clients, servers and who starts whom
| Role | What it is | Examples |
|---|---|---|
| Client | The development tool that wants code intelligence | VS Code, Neovim, Emacs, Eclipse, or a coding agent such as Claude Code |
| Server | A process that provides language features | gopls, clangd, typescript-language-server |
The specification says the client owns the server's life: "It is up to the client to decide when to start (process-wise) and when to shutdown a server." In practice the editor, or one of its extensions, launches the server as a child process the moment you open a matching file. The user rarely sees it happen, which matters later.
Where it sits in the stack
LSP is an application layer protocol with no transport of its own. The specification recommends servers support four channels, picked by command line argument: stdio; pipe (named pipes on Windows, socket files on Linux and macOS); socket, where "the port is passed as the next arg", so there is no default port; and node IPC, only when both sides run under Node.js. It also recommends passing the editor's process ID so the server can exit if the editor dies.
The defining documents
LSP is not an IETF, ISO or W3C standard. The normative text is the specification Microsoft publishes at microsoft.github.io/language-server-protocol, one page per version, with data structures written as TypeScript interfaces. Since 3.17 it also ships a machine readable meta model.
| Document | What it covers |
|---|---|
| LSP specification 3.18 | Current version: base protocol, lifecycle, every message |
| LSP specification 3.17 | Previous version, still the baseline many servers target |
| Base Protocol 0.9 | Header, JSON-RPC structures, capabilities and lifecycle as a separate, experimental spec |
| JSON-RPC 2.0 (jsonrpc.org, origin 2010-03-26, updated 2013-01-04) | The message format |
LSP obsoletes no external standard. Within the 3.x line, later versions deprecate fields: rootPath in favour of rootUri, and rootUri in favour of workspaceFolders.
How LSP works on the wire
The base protocol: headers and content
Every message has a header part and a content part, "comparable to HTTP". Header fields are a name, a colon and space, and a value, each ending in CRLF; an empty line ends the header.
| Header field | Type | Meaning |
|---|---|---|
Content-Length | number | Length of the content in bytes. Required. |
Content-Type | string | Defaults to application/vscode-jsonrpc; charset=utf-8. Optional. |
The header is ASCII. The content defaults to UTF-8, which the spec says "is the only encoding supported right now"; implementations should also accept the older, incorrect utf8 label. A framed request (length shown as a placeholder):
Content-Length: ...\r\n
\r\n
{
"jsonrpc": "2.0",
"id": 1,
"method": "textDocument/completion",
"params": { ... }
}
The reader reads header lines until the empty one, reads exactly that many bytes, then decodes JSON. There is no delimiter inside the content and no checksum, so a wrong byte count or stray output on the stream loses both sides their place.
JSON-RPC 2.0 inside the frame
JSON-RPC describes itself as "a stateless, light-weight remote procedure call (RPC) protocol" that is transport agnostic. LSP uses three shapes:
| Message | Fields | Rule |
|---|---|---|
| Request | jsonrpc ("2.0"), id (integer or string), method, optional params | Every processed request gets a response |
| Response | jsonrpc, id, exactly one of result or error | No result value means result is null |
| Notification | jsonrpc, method, optional params, no id | Must not be answered |
LSP adds two conventions: params should be objects rather than positional arrays, and the protocol "currently does not support JSON-RPC batch messages". Both sides send requests; LSP is not a one way query interface. Methods starting with $/ are implementation dependent: a peer may ignore such notifications and must answer such a request it does not handle with MethodNotFound (-32601).
Lifecycle: initialize, initialized, shutdown, exit
Client (editor) Server (language server)
| starts server process (stdio, pipe, socket or node IPC)
|--- initialize (processId, rootUri, workspaceFolders,
| capabilities, initializationOptions, trace) -->|
|<-- InitializeResult (server capabilities, serverInfo) ---------|
|--- initialized (notification) -------------------------------->|
|<-- client/registerCapability (optional) ----------------------|
|--- textDocument/didOpen (uri, languageId, version, text) ------>|
|--- textDocument/didChange ------------------------------------->|
|<-- textDocument/publishDiagnostics ----------------------------|
|--- textDocument/completion ------------------------------------>|
|<-- CompletionList ----------------------------------------------|
|--- textDocument/didClose -------------------------------------->|
|--- shutdown (request) ----------------------------------------->|
|<-- null --------------------------------------------------------|
|--- exit (notification) ---------------------------------------->|
| server exits with code 0
The rules are strict. initialize is the first request and is sent once; any other request before it gets error -32002 (ServerNotInitialized), and early notifications are dropped except exit. Until the server answers, the client sends nothing else, and the server may send only window/showMessage, window/logMessage, telemetry/event, window/showMessageRequest and progress on a token the client supplied. initialized follows once. shutdown asks the server to stop work but not exit; afterwards the client may send only exit, and the server exits with code 0 if shutdown came first, 1 otherwise.
The initialize parameters tell the server where the project is: processId, clientInfo, locale, rootPath and rootUri (both deprecated), workspaceFolders, free form initializationOptions, the client's capabilities and a trace level (off, messages or verbose).
Capabilities and position encodings
Capability flags in initialize say what each side supports: the client declares, for example, dynamic registration support and position encodings; the server answers with providers such as definitionProvider, completionProvider or executeCommandProvider with its command names. Since 3.0, client/registerCapability and client/unregisterCapability let servers change capabilities after start up. The spec says features are kept compatible "using so called capability flags", so a 3.18 client and a 3.16 server negotiate down to what both understand.
Positions are a zero based line and character offset. Before 3.17 offsets always counted UTF-16 code units. Since 3.17 the client lists encodings in general.positionEncodings and the server picks one; UTF-16 remains mandatory and the default.
Cancellation, progress and error codes
$/cancelRequest carries the ID to cancel, but the server must still respond, because JSON-RPC requires a response to every request. Since 3.15, $/progress reports work done and partial results.
| Code | Name | Source |
|---|---|---|
| -32700, -32600, -32601, -32602, -32603 | Parse error, invalid request, method not found, invalid params, internal error | JSON-RPC |
| -32002 | ServerNotInitialized | LSP |
| -32803 | RequestFailed (since 3.17) | LSP |
| -32802 | ServerCancelled (since 3.17) | LSP |
| -32801 | ContentModified | LSP |
| -32800 | RequestCancelled | LSP |
The main requests and notifications
didOpen, didChange, didClose
Client support for these three notifications is mandatory, and a server implements all three or none. textDocument/didOpen carries the URI, language ID, version and full text; from then on "the server must not try to read the document's content using the document's Uri", because the editor's buffer is the truth. didChange sends full text or incremental edits, depending on the sync kind the server chose. didClose hands ownership back to the file system.
completion and definition
textDocument/completion returns items for a cursor position. The client usually filters and sorts as the user types; expensive details can wait for completionItem/resolve. textDocument/definition sends a URI and position and gets back locations, with declaration, typeDefinition, implementation and references following the same pattern.
Diagnostics: push and pull
In the push model the server sends textDocument/publishDiagnostics when it chooses. New diagnostics replace old ones for that document, and the server clears them by pushing an empty array. Version 3.17 added the pull model, textDocument/diagnostic and workspace/diagnostic, so the client can ask for the file being edited first.
codeAction, executeCommand and applyEdit
textDocument/codeAction returns fixes and refactorings for a range, as edits, commands or both. The spec recommends commands be handled by the server. That creates the most powerful pair in the protocol: workspace/executeCommand (client to server) runs a named command, and workspace/applyEdit (server to client) asks the client to change files. In the spec's words, "the server creates a WorkspaceEdit structure and applies the changes to the workspace using the request workspace/applyEdit." Workspace edits can create, rename and delete files.
Requests from server to client
| Method | What it asks the client to do |
|---|---|
client/registerCapability | Add a capability after start up |
workspace/configuration | Return configuration values the server names (since 3.6) |
workspace/applyEdit | Apply text and file edits |
window/showMessageRequest | Show a message with action buttons |
window/showDocument | Open a URI, optionally "in an external program" (since 3.16) |
workspace/diagnostic/refresh | Re-pull diagnostics |
telemetry/event (notification) | Log a telemetry event |
Each row is a place where the client acts on the server's say so.
Versions and history
The DAP overview dates LSP's introduction to 2016. The change log writes dates month first (12/14/2020):
| Version | Date | Highlights |
|---|---|---|
| 2.x | Before 3.0 | Original public version; 3.1 notes describe breaking changes from 2.x |
| 3.0 | Not dated in the log | Capability flags, experimental capabilities, dynamic registration, willSaveWaitUntil, rootUri |
| 3.1 to 3.4 | 02/28/2017 to 11/27/2017 | Commit characters, CompletionContext, MarkupContent |
| 3.5 | Skipped | Aligned with the vscode-languageserver-protocol npm module version |
| 3.6 | 02/22/2018 | Workspace folders, workspace/configuration, type definition, implementation |
| 3.7 to 3.14 | 04/05/2018 to 12/13/2018 | Code action literals, folding ranges, prepare rename, file operations in edits, declarations |
| 3.15 | 01/14/2020 | Generic progress, selection ranges, client and server info |
| 3.16 | 12/14/2020 | Semantic tokens, call hierarchy, tracing, window/showDocument, file operation events |
| 3.17 | 05/10/2022 | Pull diagnostics, inlay hints, type hierarchy, notebooks, position encoding negotiation, meta model |
| 3.18 | 06/04/2026 | Inline completions, dynamic text document content, multi range formatting, snippets in workspace edits |
3.18 status and what it adds
The site's home page says "The latest version of the LSP specification is version 3.18", and the 3.17 page calls itself "the previous 3.17.x version". The 3.18 change log lists fourteen items, including textDocument/inlineCompletion, workspace/textDocumentContent for virtual documents (clients "should treat the content returned from this requests as readonly"), snippets and metadata in workspace edits, relative patterns in document filters, and a debug message kind. Adoption is gradual because every feature sits behind a capability.
The separate Base Protocol 0.9 splits the shared plumbing out of LSP, motivated by the Build Server Protocol, whose messages "match almost exactly with their LSP counterparts". The page says it is "under experimental construction and subject to breaking changes".
Security properties and known weakness classes
The LSP specification has no Security Considerations section. What follows comes from the protocol's design, the JSON-RPC specification and the editors' own security documentation, described as concepts.
What the protocol does and does not guarantee
| Property | In LSP? | What actually provides it |
|---|---|---|
| Confidentiality | No | Local pipes and process boundaries |
| Integrity | No; byte counted framing only | The operating system's pipe semantics |
| Authentication | No | The client chose which binary to start |
| Authorization | No | Editor policy on servers, extensions and settings |
| Availability | Partial: cancellation, ContentModified, restart guidance | Client crash and timeout handling |
LSP assumes the client launched a process it trusts over a private channel. That holds for stdio. It is weaker for the socket option: anything that can reach the socket can speak the protocol, so keep listeners on loopback or behind a socket file with tight permissions. The bigger point is that the protocol is not where most risk lives. It lives in what the server may do, and what the client does when the server asks.
Language servers run project code
A server has to understand the whole project. Depending on the language, that can mean evaluating build files, resolving dependencies, running code generation, expanding compile time macros or loading plugins the project declares. Some of that is code from the repository you just cloned.
VS Code's Workspace Trust extension guide names this: "a language extension, in order to provide functionality, may execute code from the currently loaded workspace." The user guide adds that "an unethical person could craft an innocent looking project that would run malicious code without your knowledge." Nothing in LSP limits this; a server is an ordinary process with your permissions.
Workspace settings that point to executables
Servers and the extensions that launch them read settings naming a compiler, linter or interpreter. If a repository's own configuration can set those, the repository chooses what your editor runs. VS Code warns that some workspace settings "contain paths to executables (for example, linter binaries), which if set to point to malicious code, could cause damage", and its built in PHP extension limits php.validate.executablePath to trusted folders. Many servers fetch such values through workspace/configuration, so the weakness flows through the protocol even though the protocol is neutral.
Untrusted repositories and project local configuration
Neovim's exrc option, off by default, makes the editor execute .nvim.lua, .nvimrc or .exrc files from the current and parent directories, but only if they are on a trust list managed with :trust. Its documentation says project local LSP configuration is done through exrc, so a repository's LSP setup is code, not data. The weakness class: opening a folder is treated as reading, but it can cause execution. Workspace Trust and the exrc trust list both restore that line.
Extensions that bundle servers
Language servers often arrive inside an extension. VS Code's extension runtime security page says the extension host "has the same permissions as VS Code itself" and an extension can "read and write files on your machine, make network requests, run external processes, and modify workspace settings." Workspace Trust "can't prevent a malicious extension from executing code and ignoring Restricted Mode." Since release 1.97, VS Code asks you to trust a third party publisher on first install, though installs from the command line do not trust the publisher automatically. See VS Code extension marketplace security and VS Code extension supply chain risk in 2026.
Server to client authority
A server can ask the client to change, create, rename or delete files, to open a URI in an external program, and to show prompts. A compromised server can ask at the wrong time. The weakness class is over trusting the peer; the client is the enforcement point, and good clients tie edits to user actions, restrict external opens to safe schemes, and log requests.
Framing, encoding and desynchronisation
- Stray output. With stdio, anything else a server prints to standard output corrupts the stream. Claude Code runs every language server over stdio, "so the stdout protocol rules apply to all servers."
- Bytes versus characters.
Content-Lengthcounts bytes; positions count UTF-16 code units unless negotiated. Mixing them truncates messages or misplaces edits. - Unbounded sizes. The base protocol sets no maximum message size, so a parser that trusts a huge length invites resource exhaustion.
These are implementation bugs, which is why they appear as CVEs in LSP client and server libraries.
Availability and information exposure
The spec tells clients to restart a crashed server carefully: "VS Code, for example, doesn't restart a server which has crashed 5 times in the last 180 seconds." Since every request needs a response, a server that never answers can stall a client without timeouts. On exposure, didOpen and didChange carry source text, so verbose traces left on and shipped to a log collector quietly leak code.
AI coding tools: diagnostics as model input
Claude Code's code intelligence plugins push a server's diagnostics into Claude's context after each edit and add a read only LSP tool for definitions, references, symbols and call hierarchies; its documentation says these servers are not started in cloud sessions. The weakness class is old: untrusted text becomes model input. Diagnostic messages, hover text and symbol names come from repository content. VS Code's Workspace Trust page says "Any file could be pulled into the context by using agents and could theoretically result in a prompt injection attack", and Restricted Mode disables agents. See what prompt injection is and our AI coding assistant security checklist.
Secure configuration and defender side checks
Everything here comes from the editors' official documentation, listed under Sources. Only run these checks on machines and repositories you own or are authorised to test.
VS Code: keep Workspace Trust on and tighten its defaults
Workspace Trust arrived in VS Code 1.57. The documented settings:
| Setting | Default | Safer choice |
|---|---|---|
security.workspace.trust.enabled | true | Leave on |
security.workspace.trust.startupPrompt | never (Restricted Mode with a banner) | never or once |
security.workspace.trust.emptyWindow | true | false, so empty windows start restricted |
security.workspace.trust.untrustedFiles | prompt | newWindow for loose files |
extensions.supportUntrustedWorkspaces | no overrides | Override per extension and version, with care |
{
"security.workspace.trust.enabled": true,
"security.workspace.trust.emptyWindow": false,
"security.workspace.trust.untrustedFiles": "newWindow"
}
Restricted Mode disables or limits AI agents, the terminal, tasks, debugging, settings tagged requireTrustedWorkspace, and extensions that have not opted in. The documentation suggests cloning unfamiliar code into a separate parent folder (its example is ForEvaluation) and trusting only the parent folder that holds your own repositories, since trusting a parent trusts every subfolder.
Extensions declare their stance in package.json through the untrustedWorkspaces capability (supported set to true, false or limited), and can list restrictedConfigurations so workspace values for risky settings are ignored until trust is granted. When you review an extension that launches a language server, that declaration shows whether its author thinks the server is safe in an untrusted folder. Do not use the command line switch that disables Workspace Trust.
VS Code: inventory extensions and processes
From the VS Code command line documentation:
# List installed extensions with their versions
code --list-extensions --show-versions
# Print process usage and diagnostics information
code --status
# Open a session with every extension disabled, to review a repository
code --disable-extensions
Language servers launched by extensions run as separate processes, so review the status output for any process you do not recognise. In the Workspace Trust editor, a link opens the Extensions view filtered with @workspaceUnsupported, listing extensions disabled or limited in Restricted Mode.
For evidence of what a server asks the editor to do, the language server extension guide documents a per client trace setting for vscode-languageclient, named after the client ID; its sample sets languageServerExample.trace.server to verbose and shows traffic in the Output panel. Turn it off afterwards, because it captures your code.
Neovim: see and restrict language servers
From Neovim's LSP documentation:
" Show enabled LSP configurations and client status
:checkhealth vim.lsp
" Show what the first attached server says it can do
:lua =vim.lsp.get_clients()[1].server_capabilities
" Stop clients for this buffer, or disable a config
:lsp stop
:lsp disable
A config enabled with vim.lsp.enable() attaches only when the filetype matches and a workspace root is found through root_markers or root_dir(), unless it sets workspace_required=false. Keep exrc off unless you need it, and read files before adding them with :trust.
Claude Code: code intelligence plugins
Claude Code configures servers through a plugin's .lsp.json or lspServers entry. The plugins reference example:
{
"lspServers": {
"go": {
"command": "gopls",
"args": ["serve"],
"extensionToLanguage": { ".go": "go" }
}
}
}
Defender relevant fields: requestTimeout (default 60,000 ms), restartOnCrash (default true), maxRestarts, shutdownTimeout, and diagnostics (default true; false stops pushing diagnostics into context). The code intelligence page says a plugin "doesn't include the language server", so you install the binary yourself and can confirm which one will run:
which typescript-language-server
In a session, /plugin has an Errors tab naming any binary it could not find. See also Claude Code security posture and MCP vs skills vs hooks vs rules.
Supply chain hygiene for the servers
Language servers are installed software: global npm packages, Go and Rust binaries, Python packages, or files inside an extension. Pin versions in dev containers and setup scripts (see our dev container security baseline), install from the language project's own release channel, keep a team inventory of extensions, and track CVEs in JSON-RPC and LSP libraries like any other dependency.
When to use LSP versus DAP, MCP and the alternatives
How the three protocols relate in AI coding tools
The DAP overview says its approach "was already introduced in 2016 with the Language Server Protocol"; the MCP specification says MCP "takes some inspiration from the Language Server Protocol".
| LSP | DAP | MCP | |
|---|---|---|---|
| Connects | Editor or agent to a language server | Editor to a debug adapter for a debugger or runtime | AI host to tools, resources and prompts |
| Messages | JSON-RPC 2.0 in Content-Length frames | Its own JSON messages (seq, request, response, event) in Content-Length frames | JSON-RPC 2.0 |
| Transport | stdio, pipes, sockets, node IPC | stdio per session, or a port in multi session mode | stdio or Streamable HTTP |
| Authentication | None | None | OAuth 2.1 based authorization for HTTP |
| Peer can make you | Apply edits, open URIs | Run a program; runInTerminal asks the client to run a command | Call tools, request input |
A coding agent reads code through LSP, runs and inspects programs through DAP or a terminal, and reaches outside systems through MCP. Each widens what a repository can influence, which is why Restricted Mode turns off debugging, tasks and agents together. For MCP in depth, see Model Context Protocol explained; for agent delegation, the A2A explainer.
Decision table
| Use case | Best fit | Security properties | Operational cost |
|---|---|---|---|
| Completion, navigation and diagnostics in several editors | LSP | No auth or encryption; relies on a trusted local process and editor trust | Low to moderate: one server per language |
| Breakpoints, stepping, variable inspection | DAP | No auth; debugging launches code, so editors gate it behind trust | Moderate: one adapter per debugger |
| AI access to tickets, databases or APIs | MCP | OAuth 2.1 for remote servers; host mediated consent | Moderate: run and version a server |
| Code navigation without a local checkout | LSIF index | Static data, no live process reading the repository | Low at read time; indexing in CI |
| A feature for one editor only | That editor's plugin API | The editor's own process permissions | Low once; repeated per editor |
Rule of thumb: understanding code in an editor is LSP; running a program is DAP; a model choosing an outside capability is MCP. Whatever you pick, the trust decision belongs to the folder, not the protocol.
Where Safeguard fits
Safeguard does not sit in the LSP channel and does not replace Workspace Trust or extension controls. It covers the software around the protocol:
- Software composition analysis with full transitive inventory (generally available) covers language servers and JSON-RPC or LSP libraries declared as project or tooling dependencies.
- SBOM generation and analysis in SPDX and CycloneDX and container visibility and scanning (both generally available) record which server versions ship in dev container and CI images.
- Vulnerability management with EPSS and KEV enrichment and reachability analysis (generally available) rank those findings, and policies and gates (generally available) stop known vulnerable versions returning.
- For AI coding tools, the Safeguard MCP server at mcp.safeguard.sh and agent identity are generally available. MCP Guardrail and the AI Gateway are in early access.
Safeguard does not inspect, filter or block LSP or DAP traffic.
Common mistakes
- "I only opened it." A server may build the project to answer your first hover.
- Disabling Workspace Trust instead of using a trusted parent folder.
- Overriding
extensions.supportUntrustedWorkspacesbroadly. The author may have "a good reason for disabling their extension in Restricted Mode". - Assuming Restricted Mode stops a malicious extension. It does not.
- Committing workspace settings that point to binaries.
- Binding a socket transport to all interfaces. LSP has no authentication.
- Printing debug output to stdout in a stdio server.
- Confusing byte lengths with character offsets.
- Leaving verbose traces on where logs are shared.
- Treating diagnostics as trusted when an agent reads them.
- Unpinned server installs, so nobody can answer "are we affected?".
FAQ
What is the Language Server Protocol in simple terms?
A standard way for an editor to ask a separate program, the language server, for completion, go to definition and error checking. One server works in every editor that speaks LSP.
What port does LSP use?
None by default. Most servers use stdio; the socket option takes whatever port the client passes.
Is LSP encrypted?
No. It has no encryption or authentication and relies on a private local channel to a process the editor started.
Does LSP use JSON-RPC?
Yes. Every message is a JSON-RPC 2.0 request, response or notification behind a Content-Length header. Batches are not supported.
What is the latest LSP version?
On 23 October 2026 the official site lists 3.18 (change log date 06/04/2026). 3.17 (05/10/2022) is the previous version.
Can a language server run code from my project?
Yes. VS Code's extension guide says a language extension "may execute code from the currently loaded workspace", and workspace settings can point tools at executables.
Is it safe to open an untrusted repository in VS Code?
Open it in Restricted Mode and review it first. Restricted Mode limits extensions, tasks, debugging, the terminal and agents, but not an extension that is itself malicious.
LSP vs DAP: what is the difference?
LSP gives editors code intelligence; DAP connects editors to debuggers. Both use Content-Length framing, but DAP uses its own JSON messages rather than JSON-RPC.
LSP vs MCP: what is the difference?
LSP connects a development tool to a language server. MCP connects an AI application to tools and data, with OAuth based authorization for remote servers. MCP's specification credits LSP as inspiration.
How do I see which language servers my editor runs?
VS Code: list extensions with versions and review the status output. Neovim: :checkhealth vim.lsp. Claude Code: /plugin and its Errors tab.
Fix it without waiting for upstream
LSP security bugs rarely live in the specification. They live in a language server, an editor extension, or the JSON-RPC and LSP libraries underneath, which makes most of them ordinary dependency problems.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
These demos show dependency remediation, local scanning and agent controls, not an LSP traffic filter. Download the CLI from https://safeguard.sh/download and read the setup guide at https://docs.safeguard.sh/docs/cli .
The first demo is a real Safeguard CLI session. Asked to fix the lodash CVE in a repo, it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, prints a summary and pushes nothing, then shows /permissions, /model and /scan. The same approval gated flow applies when a language server package or JSON-RPC library in your tooling needs an upgrade.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine, approves it with its device code, then scans from the terminal. Scanning where your editor tooling and dev containers live keeps language server versions in the same inventory as your other dependencies.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and chooses which tools Claude can use. If your coding agent already reads code through LSP, this adds vulnerability context through MCP while keeping the tool list deliberately small.
Click through it at your own pace, or open it full screen.
Quick check
A language server receives a textDocument/completion request before any initialize request. What should it do?
Answer with error code -32002 (ServerNotInitialized). The specification makes initialize the first request; early notifications are dropped, except exit, so a server can still be told to quit.
You open an unfamiliar repository in VS Code with Workspace Trust on. What runs, and what does not?
The folder opens in Restricted Mode. You can browse and edit, but extensions that have not opted in are disabled or limited, trust gated workspace settings are ignored, and tasks, debugging, the terminal and AI agents are blocked until you trust the folder. An already installed malicious extension is not stopped.
Sources
- Language Server Protocol Specification 3.18
- Language Server Protocol Specification 3.17
- Base Protocol Specification 0.9
- Language Server Protocol: official home page
- Language Server Protocol: overview
- JSON-RPC 2.0 Specification
- Debug Adapter Protocol: overview
- Debug Adapter Protocol: specification
- VS Code: Workspace Trust
- VS Code: Workspace Trust Extension Guide
- VS Code: Extension runtime security
- VS Code: Command Line Interface
- VS Code: Language Server Extension Guide
- Neovim: LSP documentation
- Neovim: options (exrc)
- Claude Code: Plugins reference
- Claude Code: Code intelligence plugins
- Claude Code: Tools reference
- Model Context Protocol Specification 2026-07-28
Last reviewed: 23 October 2026. LSP versions and editor trust settings change; check the official specification and your editor's documentation for the current state.