Skip to content

Commit 881c27c

Browse files
feat: add scan source to metrics [IDE-924] (#781)
Co-authored-by: bastiandoetsch <[email protected]>
1 parent 0f03ea6 commit 881c27c

24 files changed

+396
-109
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,17 @@ Right now the language server supports the following actions:
176176
- params: `types.PublishDiagnosticsParams`
177177
- note: alias for textDocument/publishDiagnostics
178178

179+
180+
- MCP Server URL Notification to publish the listening address. The server listens for `POST` requests on `/messages` and for SSE subscriptions on `/sse`. An example can be found in the mcp package in the smoke test.
181+
- method: `$/snyk.mcpServerURL`
182+
- params: `types.McpServerURLParams`
183+
- example:
184+
```json5
185+
{
186+
"url": "https://127.0.0.1:7595"
187+
}
188+
```
189+
179190
- Authentication Notification
180191
- method: `$/snyk.hasAuthenticated`
181192
- params: `types.AuthenticationParams`

application/config/config.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,8 @@ import (
3333

3434
"github.com/adrg/xdg"
3535
"github.com/denisbrodbeck/machineid"
36+
"github.com/google/uuid"
3637
"github.com/rs/zerolog"
37-
"github.com/xtgo/uuid"
38-
"golang.org/x/oauth2"
39-
4038
"github.com/snyk/go-application-framework/pkg/app"
4139
"github.com/snyk/go-application-framework/pkg/auth"
4240
"github.com/snyk/go-application-framework/pkg/configuration"
@@ -46,6 +44,8 @@ import (
4644
"github.com/snyk/go-application-framework/pkg/runtimeinfo"
4745
"github.com/snyk/go-application-framework/pkg/workflow"
4846

47+
"golang.org/x/oauth2"
48+
4949
"github.com/snyk/snyk-ls/infrastructure/cli/cli_constants"
5050
"github.com/snyk/snyk-ls/infrastructure/cli/filename"
5151
"github.com/snyk/snyk-ls/internal/logging"
@@ -200,6 +200,8 @@ type Config struct {
200200
offline bool
201201
ws types.Workspace
202202
mcpServerEnabled bool
203+
mcpBaseURL *url.URL
204+
isLSPInitialized bool
203205
}
204206

205207
func CurrentConfig() *Config {
@@ -329,7 +331,7 @@ func (c *Config) determineDeviceId() string {
329331
if c.token != "" {
330332
return util.Hash([]byte(c.token))
331333
} else {
332-
return uuid.NewTime().String()
334+
return uuid.NewString()
333335
}
334336
} else {
335337
return id
@@ -1221,3 +1223,28 @@ func (c *Config) McpServerEnabled() bool {
12211223

12221224
return c.mcpServerEnabled
12231225
}
1226+
1227+
func (c *Config) SetMCPServerURL(baseURL *url.URL) {
1228+
c.m.Lock()
1229+
defer c.m.Unlock()
1230+
c.mcpBaseURL = baseURL
1231+
}
1232+
1233+
func (c *Config) GetMCPServerURL() *url.URL {
1234+
c.m.RLock()
1235+
defer c.m.RUnlock()
1236+
1237+
return c.mcpBaseURL
1238+
}
1239+
1240+
func (c *Config) IsLSPInitialized() bool {
1241+
c.m.RLock()
1242+
defer c.m.RUnlock()
1243+
return c.isLSPInitialized
1244+
}
1245+
1246+
func (c *Config) SetLSPInitialized(initialized bool) {
1247+
c.m.Lock()
1248+
defer c.m.Unlock()
1249+
c.isLSPInitialized = initialized
1250+
}

application/server/execute_command_test.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,19 +125,25 @@ func Test_executeWorkspaceScanCommand_shouldAskForTrust(t *testing.T) {
125125
func Test_loginCommand_StartsAuthentication(t *testing.T) {
126126
c := testutil.UnitTest(t)
127127
loc, jsonRPCRecorder := setupServer(t, c)
128+
c.SetAutomaticAuthentication(false)
129+
c.SetAuthenticationMethod(types.FakeAuthentication)
130+
131+
authenticationService := di.AuthenticationService()
132+
fakeAuthenticationProvider := authenticationService.Provider().(*authentication.FakeAuthenticationProvider)
133+
fakeAuthenticationProvider.IsAuthenticated = false
128134

129135
// reset to use real service
130-
command.SetService(command.NewService(di.AuthenticationService(), nil, nil, nil, nil, nil, nil))
136+
command.SetService(command.NewService(authenticationService, di.Notifier(), di.LearnService(), nil, nil, nil, nil))
131137

132-
config.CurrentConfig().SetAutomaticAuthentication(false)
133138
_, err := loc.Client.Call(ctx, "initialize", nil)
134139
if err != nil {
135140
t.Fatal(err)
136141
}
137-
fakeAuthenticationProvider := di.AuthenticationService().Provider().(*authentication.FakeAuthenticationProvider)
138-
fakeAuthenticationProvider.IsAuthenticated = false
139142
params := lsp.ExecuteCommandParams{Command: types.LoginCommand}
140143

144+
_, err = loc.Client.Call(ctx, "initialized", types.InitializedParams{})
145+
assert.NoError(t, err)
146+
141147
// Act
142148
tokenResponse, err := loc.Client.Call(ctx, "workspace/executeCommand", params)
143149
if err != nil {
@@ -147,7 +153,7 @@ func Test_loginCommand_StartsAuthentication(t *testing.T) {
147153
// Assert
148154
assert.NotEmpty(t, tokenResponse.ResultString())
149155
assert.True(t, fakeAuthenticationProvider.IsAuthenticated)
150-
assert.Eventually(t, func() bool { return len(jsonRPCRecorder.Notifications()) > 0 }, 5*time.Second, 50*time.Millisecond)
156+
assert.Eventually(t, func() bool { return len(jsonRPCRecorder.Notifications()) > 0 }, 10*time.Second, 50*time.Millisecond)
151157
notifications := jsonRPCRecorder.FindNotificationsByMethod("$/snyk.hasAuthenticated")
152158
assert.Equal(t, 1, len(notifications))
153159
var hasAuthenticatedNotification types.AuthenticationParams

application/server/notification.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,14 @@ func disposeProgressListener() {
8080
progressStopChan <- true
8181
}
8282

83+
//nolint:gocyclo // this is ok, as it's so high because of forwarding the calls
8384
func registerNotifier(c *config.Config, srv types.Server) {
8485
logger := c.Logger().With().Str("method", "registerNotifier").Logger()
8586
callbackFunction := func(params any) {
87+
for !c.IsLSPInitialized() {
88+
logger.Debug().Msg("waiting for lsp initialization to be finished...")
89+
time.Sleep(300 * time.Millisecond)
90+
}
8691
switch params := params.(type) {
8792
case types.GetSdk:
8893
handleGetSdks(params, logger, srv)
@@ -140,6 +145,9 @@ func registerNotifier(c *config.Config, srv types.Server) {
140145
handleInlineValueRefresh(srv, &logger)
141146
logger.Debug().
142147
Msg("sending inline value refresh request to client")
148+
case types.McpServerURLParams:
149+
logger.Debug().Msgf("sending mcp url %s", params.URL)
150+
notifier(c, srv, "$/snyk.mcpServerURL", params)
143151
default:
144152
logger.Warn().
145153
Interface("params", params).
@@ -165,14 +173,14 @@ func handleGetSdks(params types.GetSdk, logger zerolog.Logger, srv types.Server)
165173

166174
callback, err := srv.Callback(ctx, "workspace/snyk.sdks", folder)
167175
if err != nil {
168-
logger.Warn().Err(err).Str("folderPath", params.FolderPath).Msg("could not retrieve sdk")
176+
logger.Debug().Str("folderPath", params.FolderPath).Msg("could not retrieve sdk, most likely not yet supported by IDE, continuing...")
169177
return
170178
}
171179

172180
// unmarshall into array that is transferred back via the channel on exit
173181
err = callback.UnmarshalResult(&sdks)
174182
if err != nil {
175-
logger.Warn().Err(err).Str("resultString", callback.ResultString()).Msg("could not unmarshal sdk response")
183+
logger.Debug().Str("resultString", callback.ResultString()).Msg("could not get sdk response, most likely not yet supported by IDE, continuing...")
176184
return
177185
}
178186
}

application/server/notification_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ func Test_NotifierShouldSendNotificationToClient(t *testing.T) {
149149
}
150150
var expected = types.AuthenticationParams{Token: "test token", ApiUrl: "https://api.snyk.io"}
151151

152+
c.SetLSPInitialized(true)
153+
152154
di.Notifier().Send(expected)
153155
assert.Eventually(
154156
t,
@@ -180,7 +182,7 @@ func Test_IsAvailableCliNotification(t *testing.T) {
180182
t.Fatal(err)
181183
}
182184
var expected = types.SnykIsAvailableCli{CliPath: filepath.Join(t.TempDir(), "cli")}
183-
185+
c.SetLSPInitialized(true)
184186
di.Notifier().Send(expected)
185187
assert.Eventually(
186188
t,
@@ -212,7 +214,7 @@ func TestShowMessageRequest(t *testing.T) {
212214
if err != nil {
213215
t.Fatal(err)
214216
}
215-
217+
c.SetLSPInitialized(true)
216218
actionCommandMap := data_structure.NewOrderedMap[types.MessageAction, types.CommandData]()
217219
expectedTitle := "test title"
218220
// data, err := command.CreateFromCommandData(snyk.CommandData{
@@ -270,7 +272,7 @@ func TestShowMessageRequest(t *testing.T) {
270272
actionCommandMap.Add(types.MessageAction(selectedAction), types.CommandData{CommandId: types.OpenBrowserCommand, Arguments: []any{"https://snyk.io"}})
271273

272274
request := types.ShowMessageRequest{Message: "message", Type: types.Info, Actions: actionCommandMap}
273-
275+
c.SetLSPInitialized(true)
274276
di.Notifier().Send(request)
275277

276278
assert.Eventually(

application/server/server.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func Start(c *config.Config) {
8282
logger.Info().Msg("Starting up MCP Server...")
8383
var mcpServer *mcp2.McpLLMBinding
8484
go func() {
85-
mcpServer = mcp2.NewMcpServer(c, mcp2.WithScanner(di.Scanner()), mcp2.WithLogger(c.Logger()))
85+
mcpServer = mcp2.NewMcpLLMBinding(c, mcp2.WithScanner(di.Scanner()), mcp2.WithLogger(c.Logger()))
8686
err := mcpServer.Start()
8787
if err != nil {
8888
c.Logger().Err(err).Msg("failed to start mcp server")
@@ -408,6 +408,10 @@ func initializedHandler(srv *jrpc2.Server) handler.Func {
408408
// looks weird when including the method name.
409409
c := config.CurrentConfig()
410410
initialLogger := c.Logger()
411+
// only set our config to initialized after leaving the func
412+
defer func() {
413+
c.SetLSPInitialized(true)
414+
}()
411415
initialLogger.Info().Msg("snyk-ls: " + config.Version + " (" + util.Result(os.Executable()) + ")")
412416
initialLogger.Info().Msgf("CLI Path: %s", c.CliSettings().Path())
413417
initialLogger.Info().Msgf("CLI Installed? %t", c.CliSettings().Installed())
@@ -460,8 +464,10 @@ func initializedHandler(srv *jrpc2.Server) handler.Func {
460464
)
461465
logger.Info().Msg(msg)
462466
}
463-
464-
logger.Debug().Msg("trying to get trusted status for untrusted folders")
467+
mcpServerURL := c.GetMCPServerURL()
468+
if mcpServerURL != nil {
469+
di.Notifier().Send(types.McpServerURLParams{URL: mcpServerURL.String()})
470+
}
465471
return nil, nil
466472
})
467473
}

application/server/server_smoke_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,8 @@ func Test_SmokeSnykCodeFileScan(t *testing.T) {
861861
f := workspace.NewFolder(c, cloneTargetDir, "Test", di.Scanner(), di.HoverService(), di.ScanNotifier(), di.Notifier(), di.ScanPersister(), di.ScanStateAggregator())
862862
w.AddFolder(f)
863863

864+
c.SetLSPInitialized(true)
865+
864866
_ = textDocumentDidSave(t, &loc, testPath)
865867

866868
assert.Eventually(t, checkForPublishedDiagnostics(t, c, testPath, -1, jsonRPCRecorder), 2*time.Minute, 10*time.Millisecond)

application/server/server_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package server
1818

1919
import (
2020
"context"
21+
"net/url"
2122
"os"
2223
"os/exec"
2324
"path/filepath"
@@ -238,6 +239,44 @@ func Test_initialized_shouldCheckRequiredProtocolVersion(t *testing.T) {
238239
"did not receive callback because of wrong protocol version")
239240
}
240241

242+
func Test_initialized_shouldSendMcpServerAddress(t *testing.T) {
243+
c := testutil.UnitTest(t)
244+
loc, jsonRpcRecorder := setupServer(t, c)
245+
246+
params := types.InitializeParams{
247+
InitializationOptions: types.Settings{RequiredProtocolVersion: config.LsProtocolVersion},
248+
}
249+
250+
rsp, err := loc.Client.Call(ctx, "initialize", params)
251+
require.NoError(t, err)
252+
var result types.InitializeResult
253+
err = rsp.UnmarshalResult(&result)
254+
require.NoError(t, err)
255+
256+
testURL, err := url.Parse("http://localhost:1234")
257+
require.NoError(t, err)
258+
259+
c.SetMCPServerURL(testURL)
260+
261+
_, err = loc.Client.Call(ctx, "initialized", params)
262+
require.NoError(t, err)
263+
require.Eventuallyf(t, func() bool {
264+
n := jsonRpcRecorder.FindNotificationsByMethod("$/snyk.mcpServerURL")
265+
if n == nil {
266+
return false
267+
}
268+
if len(n) > 1 {
269+
t.Fatal("can't succeed anymore, too many notifications ", n)
270+
}
271+
272+
var param types.McpServerURLParams
273+
err = n[0].UnmarshalParams(&param)
274+
require.NoError(t, err)
275+
return param.URL == testURL.String()
276+
}, time.Minute, time.Millisecond,
277+
"did not receive mcp server url")
278+
}
279+
241280
func Test_initialize_shouldSupportAllCommands(t *testing.T) {
242281
c := testutil.UnitTest(t)
243282
loc, _ := setupServer(t, c)
@@ -750,6 +789,8 @@ func Test_textDocumentDidSaveHandler_shouldAcceptDocumentItemAndPublishDiagnosti
750789
t.Fatal(err)
751790
}
752791

792+
c.SetLSPInitialized(true)
793+
753794
filePath, fileDir := code.TempWorkdirWithIssues(t)
754795
fileUri := sendFileSavedMessage(t, filePath, fileDir, loc)
755796

@@ -802,6 +843,8 @@ func Test_textDocumentDidSaveHandler_shouldTriggerScanForDotSnykFile(t *testing.
802843
t.Fatalf("initialization failed: %v", err)
803844
}
804845

846+
c.SetLSPInitialized(true)
847+
805848
snykFilePath, folderPath := createTemporaryDirectoryWithSnykFile(t)
806849

807850
sendFileSavedMessage(t, snykFilePath, folderPath, loc)
@@ -854,6 +897,8 @@ func Test_textDocumentDidOpenHandler_shouldPublishIfCached(t *testing.T) {
854897
t.Fatal(err)
855898
}
856899

900+
c.SetLSPInitialized(true)
901+
857902
filePath, fileDir := code.TempWorkdirWithIssues(t)
858903
fileUri := sendFileSavedMessage(t, filePath, fileDir, loc)
859904

application/server/trust_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func Test_handleUntrustedFolders_shouldTriggerTrustRequestAndScanAfterConfirmati
7878
w := c.Workspace()
7979
sc := &scanner.TestScanner{}
8080
c.SetTrustedFolderFeatureEnabled(true)
81+
c.SetLSPInitialized(true)
8182
w.AddFolder(workspace.NewFolder(c, "/trusted/dummy", "dummy", sc, di.HoverService(), di.ScanNotifier(), di.Notifier(), di.ScanPersister(), di.ScanStateAggregator()))
8283

8384
command.HandleUntrustedFolders(context.Background(), c, loc.Server)

domain/ide/command/command_service.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func Service() types.CommandService {
6767
return instance
6868
}
6969

70-
func (service *serviceImpl) ExecuteCommandData(ctx context.Context, commandData types.CommandData, server types.Server) (any, error) {
70+
func (s *serviceImpl) ExecuteCommandData(ctx context.Context, commandData types.CommandData, server types.Server) (any, error) {
7171
c := config.CurrentConfig()
7272
logger := c.Logger().With().Str("method", "command.serviceImpl.ExecuteCommandData").Logger()
7373
if c.Offline() {
@@ -77,7 +77,7 @@ func (service *serviceImpl) ExecuteCommandData(ctx context.Context, commandData
7777

7878
logger.Debug().Msgf("executing command %s", commandData.CommandId)
7979

80-
command, err := CreateFromCommandData(c, commandData, server, service.authService, service.learnService, service.notifier, service.issueProvider, service.codeApiClient, service.codeScanner, service.cli)
80+
command, err := CreateFromCommandData(c, commandData, server, s.authService, s.learnService, s.notifier, s.issueProvider, s.codeApiClient, s.codeScanner, s.cli)
8181
if err != nil {
8282
logger.Err(err).Msg("failed to create command")
8383
return nil, err
@@ -90,8 +90,8 @@ func (service *serviceImpl) ExecuteCommandData(ctx context.Context, commandData
9090
}
9191

9292
if err != nil && strings.Contains(err.Error(), "400 Bad Request") {
93-
service.notifier.SendShowMessage(sglsp.MTWarning, "Logging out automatically, available credentials are invalid. Please re-authenticate.")
94-
service.authService.Logout(ctx)
93+
s.notifier.SendShowMessage(sglsp.MTWarning, "Logging out automatically, available credentials are invalid. Please re-authenticate.")
94+
s.authService.Logout(ctx)
9595
return nil, nil
9696
}
9797

0 commit comments

Comments
 (0)