on main help sign in
feat(lsp): allow to set custom env to lsp servers via config (#778)
commit82c0aff87ab3a9d610662a732065e564ad304cfb authorbbrodriges <bender.rodriges@gmail.com> dateSat 16 Aug 2025, 00:09 (1 year ago) parente2dc8e1
4 files changed+45−24
README.md+5−2 internal/config/config.go+29−20 internal/lsp/client.go+3−1 schema.json+8−1
README.md+5−2
@@ -182,7 +182,10 @@ like you would. LSPs can be added manually like so:
182182 "$schema": "https://charm.land/crush.json",
183183 "lsp": {
184184 "go": {
185− "command": "gopls"
185+ "command": "gopls",
186+ "env": {
187+ "GOTOOLCHAIN": "go1.24.5"
188+ }
186189 },
187190 "typescript": {
188191 "command": "typescript-language-server",
@@ -433,7 +436,7 @@ accounts or OAuth workarounds, which may violate Anthropic and Microsoft’s
433436Terms of Service.
434437
435438We’re committed to building sustainable, trusted integrations with model
436−providers. If you’re a provider interested in working with us,
439+providers. If you’re a provider interested in working with us,
437440[reach out](mailto:vt100@charm.sh).
438441
439442## Logging
internal/config/config.go+29−20
@@ -117,11 +117,12 @@ type MCPConfig struct {
117117}
118118
119119type LSPConfig struct {
120− Disabled bool `json:"enabled,omitempty" jsonschema:"description=Whether this LSP server is disabled,default=false"`
121− Command string `json:"command" jsonschema:"required,description=Command to execute for the LSP server,example=gopls"`
122− Args []string `json:"args,omitempty" jsonschema:"description=Arguments to pass to the LSP server command"`
123− Options any `json:"options,omitempty" jsonschema:"description=LSP server-specific configuration options"`
124− FileTypes []string `json:"filetypes,omitempty" jsonschema:"description=File types this LSP server handles,example=go,example=mod,example=rs,example=c,example=js,example=ts"`
120+ Disabled bool `json:"enabled,omitempty" jsonschema:"description=Whether this LSP server is disabled,default=false"`
121+ Command string `json:"command" jsonschema:"required,description=Command to execute for the LSP server,example=gopls"`
122+ Args []string `json:"args,omitempty" jsonschema:"description=Arguments to pass to the LSP server command"`
123+ Env map[string]string `json:"env,omitempty" jsonschema:"description=Environment variables to set to the LSP server command"`
124+ Options any `json:"options,omitempty" jsonschema:"description=LSP server-specific configuration options"`
125+ FileTypes []string `json:"filetypes,omitempty" jsonschema:"description=File types this LSP server handles,example=go,example=mod,example=rs,example=c,example=js,example=ts"`
125126}
126127
127128type TUIOptions struct {
@@ -186,22 +187,12 @@ func (l LSPs) Sorted() []LSP {
186187 return sorted
187188}
188189
189−func (m MCPConfig) ResolvedEnv() []string {
190− resolver := NewShellVariableResolver(env.New())
191− for e, v := range m.Env {
192− var err error
193− m.Env[e], err = resolver.ResolveValue(v)
194− if err != nil {
195− slog.Error("error resolving environment variable", "error", err, "variable", e, "value", v)
196− continue
197− }
198− }
190+func (l LSPConfig) ResolvedEnv() []string {
191+ return resolveEnvs(l.Env)
192+}
199193
200− env := make([]string, 0, len(m.Env))
201− for k, v := range m.Env {
202− env = append(env, fmt.Sprintf("%s=%s", k, v))
203− }
204− return env
194+func (m MCPConfig) ResolvedEnv() []string {
195+ return resolveEnvs(m.Env)
205196}
206197
207198func (m MCPConfig) ResolvedHeaders() map[string]string {
@@ -509,3 +500,21 @@ func (c *ProviderConfig) TestConnection(resolver VariableResolver) error {
509500 _ = b.Body.Close()
510501 return nil
511502}
503+
504+func resolveEnvs(envs map[string]string) []string {
505+ resolver := NewShellVariableResolver(env.New())
506+ for e, v := range envs {
507+ var err error
508+ envs[e], err = resolver.ResolveValue(v)
509+ if err != nil {
510+ slog.Error("error resolving environment variable", "error", err, "variable", e, "value", v)
511+ continue
512+ }
513+ }
514+
515+ res := make([]string, 0, len(envs))
516+ for k, v := range envs {
517+ res = append(res, fmt.Sprintf("%s=%s", k, v))
518+ }
519+ return res
520+}
internal/lsp/client.go+3−1
@@ -11,6 +11,7 @@ import (
1111 "os"
1212 "os/exec"
1313 "path/filepath"
14+ "slices"
1415 "strings"
1516 "sync"
1617 "sync/atomic"
@@ -66,8 +67,9 @@ type Client struct {
6667// NewClient creates a new LSP client.
6768func NewClient(ctx context.Context, name string, config config.LSPConfig) (*Client, error) {
6869 cmd := exec.CommandContext(ctx, config.Command, config.Args...)
70+
6971 // Copy env
70− cmd.Env = os.Environ()
72+ cmd.Env = slices.Concat(os.Environ(), config.ResolvedEnv())
7173
7274 stdin, err := cmd.StdinPipe()
7375 if err != nil {
schema.json+8−1
@@ -63,6 +63,13 @@
6363 "type": "array",
6464 "description": "Arguments to pass to the LSP server command"
6565 },
66+ "env": {
67+ "additionalProperties": {
68+ "type": "string"
69+ },
70+ "type": "object",
71+ "description": "Environment variables to set to the LSP server command"
72+ },
6673 "options": {
6774 "description": "LSP server-specific configuration options"
6875 },
@@ -426,4 +433,4 @@
426433 "type": "object"
427434 }
428435 }
429−}
436+}