From 02cf59afb6b166be303d86ec5ad78325c21d8533 Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Fri, 4 Jul 2025 18:04:57 -0300 Subject: [PATCH 1/2] feat(mcp): enhances MCP servers usage with Cursor --- lib/client/db/mcp/server.go | 18 +++++++++--------- lib/client/db/mcp/server_test.go | 15 ++++++++------- lib/client/mcp/claude/config.go | 23 +++++++++++++++++++++++ tool/tsh/common/mcp.go | 17 +++++++++++++---- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/lib/client/db/mcp/server.go b/lib/client/db/mcp/server.go index e839848a94b9e..72758d597ffe7 100644 --- a/lib/client/db/mcp/server.go +++ b/lib/client/db/mcp/server.go @@ -76,7 +76,7 @@ func (s *RootServer) ListDatabases(ctx context.Context, request mcp.CallToolRequ s.logger.ErrorContext(ctx, "error while list databases", "error", err) return mcp.NewToolResultError(FormatErrorMessage(err).Error()), nil } - res = append(res, mcp.EmbeddedResource{Type: "resource", Resource: contents}) + res = append(res, mcp.NewTextContent(contents)) } return &mcp.CallToolResult{ @@ -99,7 +99,11 @@ func (s *RootServer) GetDatabaseResource(ctx context.Context, request mcp.ReadRe return nil, trace.Wrap(err) } - return []mcp.ResourceContents{encodedDb}, nil + return []mcp.ResourceContents{mcp.TextResourceContents{ + URI: request.Params.URI, + MIMEType: databaseResourceMIMEType, + Text: encodedDb, + }}, nil } // RegisterDatabase register a database on the root server. This make it @@ -130,18 +134,14 @@ func buildDatabaseResource(db *Database) DatabaseResource { } } -func encodeDatabaseResource(db *Database) (mcp.ResourceContents, error) { +func encodeDatabaseResource(db *Database) (string, error) { resource := buildDatabaseResource(db) out, err := yaml.Marshal(resource) if err != nil { - return nil, trace.Wrap(err) + return "", trace.Wrap(err) } - return mcp.TextResourceContents{ - URI: resource.URI, - MIMEType: databaseResourceMIMEType, - Text: string(out), - }, nil + return string(out), nil } const ( diff --git a/lib/client/db/mcp/server_test.go b/lib/client/db/mcp/server_test.go index 0a207bf24037f..997c90c7d9e8c 100644 --- a/lib/client/db/mcp/server_test.go +++ b/lib/client/db/mcp/server_test.go @@ -80,8 +80,7 @@ func TestRegisterDatabase(t *testing.T) { require.Len(t, res.Content, len(databases)) for _, c := range res.Content { - require.IsType(t, mcp.EmbeddedResource{}, c) - require.IsType(t, mcp.TextResourceContents{}, c.(mcp.EmbeddedResource).Resource) + require.IsType(t, mcp.TextContent{}, c) } // Although we're not sorting by the URI directly, the only field that @@ -89,14 +88,16 @@ func TestRegisterDatabase(t *testing.T) { // cause them to have the same order). So here we sort by the YAML // contents to avoid having to decode. slices.SortFunc(res.Content, func(a, b mcp.Content) int { - resourceA := a.(mcp.EmbeddedResource).Resource.(mcp.TextResourceContents) - resourceB := b.(mcp.EmbeddedResource).Resource.(mcp.TextResourceContents) - return strings.Compare(resourceA.Text, resourceB.Text) + resourceA := a.(mcp.TextContent).Text + resourceB := b.(mcp.TextContent).Text + return strings.Compare(resourceA, resourceB) }) for i, c := range res.Content { - content := c.(mcp.EmbeddedResource) - assertDatabaseResource(t, databases[i], content.Resource) + var database DatabaseResource + content := c.(mcp.TextContent) + require.NoError(t, yaml.Unmarshal([]byte(content.Text), &database)) + require.Empty(t, cmp.Diff(buildDatabaseResource(databases[i]), database, cmpopts.IgnoreFields(types.Metadata{}, "Namespace"))) } }) } diff --git a/lib/client/mcp/claude/config.go b/lib/client/mcp/claude/config.go index e462621d3b2d1..6932cdfc0a322 100644 --- a/lib/client/mcp/claude/config.go +++ b/lib/client/mcp/claude/config.go @@ -57,6 +57,18 @@ func DefaultConfigPath() (string, error) { } } +// GlobalCursorPath returns the default path for Cursor global MCP configuration. +// +// https://docs.cursor.com/context/mcp#configuration-locations +func GlobalCursorPath() (string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", trace.Wrap(err) + } + + return filepath.Join(homeDir, ".cursor", "mcp.json"), nil +} + // MCPServer contains details to launch an MCP server. // // https://modelcontextprotocol.io/quickstart/user @@ -239,6 +251,17 @@ func LoadConfigFromDefaultPath() (*FileConfig, error) { return config, trace.Wrap(err) } +// LoadConfigFromGlobalCursor loads the Cursor global MCP server configuration. +func LoadConfigFromGlobalCursor() (*FileConfig, error) { + configPath, err := GlobalCursorPath() + if err != nil { + return nil, trace.Wrap(err) + } + + config, err := LoadConfigFromFile(configPath) + return config, trace.Wrap(err) +} + // Exists returns true if config file exists. func (c *FileConfig) Exists() bool { return c.configExists diff --git a/tool/tsh/common/mcp.go b/tool/tsh/common/mcp.go index a24c0b2cb2bc0..0ef4773f9f628 100644 --- a/tool/tsh/common/mcp.go +++ b/tool/tsh/common/mcp.go @@ -58,14 +58,16 @@ type mcpClientConfigFlags struct { const ( mcpClientConfigClaude = "claude" + mcpClientConfigCursor = "cursor" ) func (m *mcpClientConfigFlags) addToCmd(cmd *kingpin.CmdClause) { cmd.Flag( "client-config", fmt.Sprintf( - "If specified, update the specified client config. %q for default Claude Desktop config, or specify a JSON file path. Can also be set with environment variable %s.", + "If specified, update the specified client config. %q for default Claude Desktop config, %q for global Cursor MCP servers config, or specify a JSON file path. Can also be set with environment variable %s.", mcpClientConfigClaude, + mcpClientConfigCursor, mcpClientConfigEnvVar, )). Envar(mcpClientConfigEnvVar). @@ -92,6 +94,8 @@ func (m *mcpClientConfigFlags) loadConfig() (*claude.FileConfig, error) { switch m.clientConfig { case mcpClientConfigClaude: return claude.LoadConfigFromDefaultPath() + case mcpClientConfigCursor: + return claude.LoadConfigFromGlobalCursor() default: return claude.LoadConfigFromFile(m.clientConfig) } @@ -107,9 +111,7 @@ func (m *mcpClientConfigFlags) jsonFormatOptions() []string { } func (m *mcpClientConfigFlags) printHint(w io.Writer) error { - _, err := fmt.Fprintln(w, `Tip: use --client-config=claude to update your Claude Desktop configuration. -You can also specify a custom config path with --client-config= to update -a config file compatible with the "mcpServer" mapping.`) + _, err := fmt.Fprintln(w, mcpConfigHint) return trace.Wrap(err) } @@ -149,3 +151,10 @@ func getLoggingOptsForMCPServer(cf *CLIConf) loggingOpts { debug: true, }) } + +// mcpConfigHint is the hint message displayed when the configuration is shown +// to users. +const mcpConfigHint = `Tip: You can use this command to update your MCP servers configuration file automatically. +- For Claude Desktop, use --client-config=claude to update the default configuration, or specify the file path using --client-config=. +- For Cursor, use --client-config=cursor to update the global MCP servers configuration, or update a project using --client-config=/.cursor/mcp.json +In addition, --client-config= can be used to update any config file compatible with the "mcpServers" mapping.` From 3bdc2ca75625f80764c76d12befa161ca3f00a1a Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Mon, 7 Jul 2025 12:00:03 -0300 Subject: [PATCH 2/2] refactor: code review suggestions --- lib/client/db/mcp/server.go | 4 ++++ tool/tsh/common/help.go | 6 ++++++ tool/tsh/common/mcp.go | 7 ++++--- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/client/db/mcp/server.go b/lib/client/db/mcp/server.go index 72758d597ffe7..a28e71960a052 100644 --- a/lib/client/db/mcp/server.go +++ b/lib/client/db/mcp/server.go @@ -61,6 +61,10 @@ func NewRootServer(logger *slog.Logger) *RootServer { } // ListDatabases tool function used to list all available/served databases. +// +// Note: Given some MCP clients not fully support resources of any kind (including +// embedded and references), we must return the databases as plain text result so +// the tool keeps working on those clients. func (s *RootServer) ListDatabases(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { s.mu.RLock() defer s.mu.RUnlock() diff --git a/tool/tsh/common/help.go b/tool/tsh/common/help.go index 092b78279f238..c7551e3570c81 100644 --- a/tool/tsh/common/help.go +++ b/tool/tsh/common/help.go @@ -85,6 +85,9 @@ Examples: Add all MCP servers to Claude Desktop $ tsh mcp config --all --client-config=claude + Add all MCP servers to Cursor + $ tsh mcp config --all --client-config=cursor + Search MCP servers with labels and add to the specified JSON file $ tsh mcp config --labels env=dev --client-config=my-config.json` @@ -96,6 +99,9 @@ Examples: Add the database configuration to Claude Desktop $ tsh mcp db config --db-user=mydbuser --db-name=mydbname --client-config=claude my-db-resource + Add the database configuration to Cursor + $ tsh mcp db config --db-user=mydbuser --db-name=mydbname --client-config=cursor my-db-resource + Add the database configuration to the specified JSON file $ tsh mcp db config --db-user=mydbuser --db-name=mydbname --client-config=my-config.json my-db-resource ` diff --git a/tool/tsh/common/mcp.go b/tool/tsh/common/mcp.go index 0ef4773f9f628..54b7cb99aa619 100644 --- a/tool/tsh/common/mcp.go +++ b/tool/tsh/common/mcp.go @@ -155,6 +155,7 @@ func getLoggingOptsForMCPServer(cf *CLIConf) loggingOpts { // mcpConfigHint is the hint message displayed when the configuration is shown // to users. const mcpConfigHint = `Tip: You can use this command to update your MCP servers configuration file automatically. -- For Claude Desktop, use --client-config=claude to update the default configuration, or specify the file path using --client-config=. -- For Cursor, use --client-config=cursor to update the global MCP servers configuration, or update a project using --client-config=/.cursor/mcp.json -In addition, --client-config= can be used to update any config file compatible with the "mcpServers" mapping.` +- For Claude Desktop, use --client-config=claude to update the default configuration. +- For Cursor, use --client-config=cursor to update the global MCP servers configuration. +In addition, you can use --client-config= to specify a config file location that is compatible with the "mcpServers" mapping. +For example, you can update a Cursor project using --client-config=/.cursor/mcp.json`