Code intelligence
Text search finds every line that mentions parse. A language server knows which parse you mean: where it is defined, who calls it, and what type it returns. Syntic Code can use language servers through its Lsp tool, and plugins are how you give it one.
A plugin that ships a .lsp.json file adds one or more language servers. Once that plugin is enabled, the agent can ask precise questions about code in matching files instead of guessing from text matches.
What the agent gains
The Lsp tool is read-only, so it never needs approval. It takes a file, an action and, for some actions, a position:
| Action | What comes back |
|---|---|
diagnostics | Errors and warnings the server reports for the file. |
hover | The type or signature at a position. |
definition | Where the symbol at a position is defined. |
references | Every place the symbol is used, including its definition. |
implementation | Where an interface or trait method is implemented. |
symbols | An outline of the file. |
workspace_symbols | Symbols across the project whose name matches query. |
incoming_calls | Functions that call the one at a position. |
outgoing_calls | Functions that the one at a position calls. |
Positions are zero-based { "line": 12, "character": 4 }. hover, definition, references, implementation and both call actions need one; the rest do not.
Without a language server for the file type, the tool answers that no server is connected, and the agent falls back to Grep and Read. The tools reference lists Lsp with the other built-in tools.
Add a language server with a plugin
There is no catalog of ready-made language-server plugins yet, but writing one takes a few minutes. You need the language server installed on your machine (or inside the plugin’s bin/ folder) and a plugin with a .lsp.json at its root.
rust-intel/
.syntic-plugin/plugin.json
.lsp.jsonplugin.json:
{
"name": "rust-intel",
"version": "0.1.0",
"description": "Rust code intelligence"
}.lsp.json:
{
"rust": {
"command": "rust-analyzer",
"extensionToLanguage": { ".rs": "rust" },
"startupTimeout": 20000
}
}Try it without installing:
syntic --plugin-dir ./rust-intelThen ask something that needs precise navigation, such as “where is Config::load called?”. When you are happy with it, publish it through a marketplace (Release a plugin) so others can install it.
Server entry fields
Each key in .lsp.json names a server; its value describes how to start it. A { "lspServers": { ... } } wrapper around the map is also accepted.
| Field | Type | Meaning |
|---|---|---|
command | string | Required. The program to run. |
args | array of strings | Arguments passed to it. |
env | object | Extra environment variables for the server process. |
extensionToLanguage | object | Maps file extensions to language ids, for example { ".ts": "typescript", ".tsx": "typescriptreact" }. |
fileExtensions or extensions | array of strings | A shorter form: each extension is served, and its language id is the extension without the dot. |
startupTimeout | number (ms) | How long the server has to finish starting. Default 30 seconds. |
A server needs a command and at least one file extension, or it is ignored. Extensions are matched without regard to case, with or without a leading dot.
${SYNTIC_PLUGIN_ROOT} and ${SYNTIC_PLUGIN_DATA} work in command, args and env, so a plugin can run a server it bundles:
{
"proto": {
"command": "${SYNTIC_PLUGIN_ROOT}/bin/proto-ls",
"args": ["--stdio"],
"fileExtensions": ["proto"]
}
}A plugin’s bin/ folder is also put first on the server’s PATH. You can point the manifest’s lspServers field at a different file if you prefer; see the manifest reference.
How servers run
- Started on demand. Nothing launches at startup. The first
Lspcall on a matching file starts that server for the session’s working folder, and later calls reuse it. - One file at a time. Before a question, the file is opened on the server (or its current text is sent again), so answers reflect your latest edits.
- Time limits. Each request may take up to 30 seconds. After opening a file, the tool waits up to 5 seconds for diagnostics to arrive.
- Restarts. If a server exits, the next call starts it again, up to three times per session.
- First match wins. When two plugins serve the same extension, the first one loaded answers.
- No sign-in secrets. A language server is someone else’s program. It gets the variables its entry sets, but never the CLI’s own credentials from your environment.
Check what is configured
/plugin lists the language servers each enabled plugin declares, with the command it will run and the languages it covers. From the shell, syntic plugin details <plugin> shows a plugin’s language servers by name.
Troubleshooting
| Symptom | Likely cause and fix |
|---|---|
| ”no language server is connected in this session” | No enabled plugin declares a server for that file extension. Check extensionToLanguage or fileExtensions, then run /reload-plugins. |
| The server is listed but never answers | The command is not on your PATH. Run it yourself in a terminal; use an absolute path or ship it in the plugin’s bin/. |
| Start-up times out on a large project | Raise startupTimeout (milliseconds). Some servers index the whole workspace before they answer. |
| Answers look stale | The tool re-sends the file before each question, but some servers need a project build first. Run the build, then ask again. |
| The server needs a token | Put it in the entry’s env, ideally through a plugin option marked sensitive and ${user_config.KEY}. See Plugin components. |
Language servers in .lsp.json only work through a plugin. A .lsp.json in your project folder is not read on its own.
Related pages
- Plugin components: every other kind of component a plugin can ship.
- Plugin safety: what a plugin’s programs can do on your machine.