Skip to content

Commit 8fca2dc

Browse files
aprimakinaclaude
andcommitted
refactor(mcp): address review feedback for read-only tool registration
- Pass readOnly as a parameter through registerTools/registerServiceTools/ registerDatabaseTools instead of storing it on the Server struct, so the stale-once-set value can't leak to tool handlers. - Remove the deny-tiger-cli.sh hook and .claude/settings.json; this dev-only tooling shouldn't be checked in repo-wide. - Update README read_only/TIGER_READ_ONLY docs to match spec_mcp.md: write MCP tools are not registered (absent from tools/list), not erroring. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9be1c39 commit 8fca2dc

7 files changed

Lines changed: 28 additions & 65 deletions

File tree

.claude/hooks/deny-tiger-cli.sh

Lines changed: 0 additions & 21 deletions
This file was deleted.

.claude/settings.json

Lines changed: 0 additions & 15 deletions
This file was deleted.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ All configuration options can be set via `tiger config set <key> <value>`:
248248
- `mcp_max_rows` - Maximum number of rows the `db_execute_query` MCP tool returns per result set before truncating, to limit how much data lands in an AI agent's context. Only applies to the MCP tool, not CLI commands. Default: `100`
249249
- `output` - Output format: `json`, `yaml`, or `table` (default: `table`)
250250
- `password_storage` - Password storage method: `keyring`, `pgpass`, or `none` (default: `keyring`)
251-
- `read_only` - When `true`, mutating operations are refused: the `tiger service create`/`fork`/`start`/`stop`/`resize`/`update-password`/`delete` CLI commands and their MCP equivalents return an error, and `tiger db connect`, `tiger db connection-string`, and the `db_execute_query` MCP tool open the database session in Tiger Cloud's immutable read-only mode (writes and DDL are rejected by the server). Read commands/tools are unaffected — `tiger db schema` and the `db_schema` MCP tool always open a read-only session regardless of this setting. Default: `false`.
251+
- `read_only` - When `true`, mutating operations are refused: the `tiger service create`/`fork`/`start`/`stop`/`resize`/`update-password`/`delete` CLI commands return an error, and their MCP equivalents are not registered, so they don't appear in `tools/list` and can't be called. `tiger db connect`, `tiger db connection-string`, and the `db_execute_query` MCP tool open the database session in Tiger Cloud's immutable read-only mode (writes and DDL are rejected by the server). Read commands/tools are unaffected — `tiger db schema` and the `db_schema` MCP tool always open a read-only session regardless of this setting. Default: `false`.
252252
- `service_id` - Default service ID
253253
- `version_check` - When `true`, the CLI checks for a newer version on each invocation (in an interactive terminal) and prints a notice if one is available. Set to `false` to disable. Default: `true`.
254254

@@ -263,7 +263,7 @@ Environment variables override configuration file values. All variables use the
263263
- `TIGER_DOCS_MCP` - Enable/disable docs MCP proxy
264264
- `TIGER_OUTPUT` - Output format: `json`, `yaml`, or `table`
265265
- `TIGER_PASSWORD_STORAGE` - Password storage method: `keyring`, `pgpass`, or `none`
266-
- `TIGER_READ_ONLY` - When `true`, write/destructive CLI commands and Tiger MCP tools refuse to run, and `db_execute_query` runs against a read-only database connection
266+
- `TIGER_READ_ONLY` - When `true`, write/destructive CLI commands return an error, the corresponding Tiger MCP write tools are not registered, and `db_execute_query` runs against a read-only database connection
267267
- `TIGER_PUBLIC_KEY` - Public key to use for authentication (takes priority over stored credentials)
268268
- `TIGER_SECRET_KEY` - Secret key to use for authentication (takes priority over stored credentials)
269269
- `TIGER_SERVICE_ID` - Default service ID

internal/tiger/mcp/db_tools.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ func (DBExecuteQueryOutput) Schema() *jsonschema.Schema {
147147
}
148148

149149
// registerDatabaseTools registers database operation tools with comprehensive schemas and descriptions
150-
func (s *Server) registerDatabaseTools() {
151-
addTool(s, &mcp.Tool{
150+
func (s *Server) registerDatabaseTools(readOnly bool) {
151+
addTool(s, readOnly, &mcp.Tool{
152152
Name: toolDBExecuteQuery,
153153
Title: "Execute SQL Query",
154154
Description: `Execute SQL queries against a service database.

internal/tiger/mcp/server.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,14 @@ const (
2727
type Server struct {
2828
mcpServer *mcp.Server
2929
docsProxyClient *ProxyClient
30-
31-
// readOnly is captured from config at construction time. Handlers still call
32-
// common.CheckReadOnly in case read_only is toggled on mid-session.
33-
readOnly bool
3430
}
3531

3632
// addTool registers an MCP tool, skipping readOnlyGatedTools in read-only mode.
37-
func addTool[In, Out any](s *Server, t *mcp.Tool, h mcp.ToolHandlerFor[In, Out]) {
38-
if s.readOnly && slices.Contains(readOnlyGatedTools, t.Name) {
33+
// readOnly is passed in (rather than stored on Server) so that this stale-once-set
34+
// value never leaks to tool handlers; handlers re-check the live config via
35+
// common.CheckReadOnly in case read_only is toggled on mid-session.
36+
func addTool[In, Out any](s *Server, readOnly bool, t *mcp.Tool, h mcp.ToolHandlerFor[In, Out]) {
37+
if readOnly && slices.Contains(readOnlyGatedTools, t.Name) {
3938
logging.Debug("Skipping write tool in read-only mode", zap.String("tool", t.Name))
4039
return
4140
}
@@ -69,11 +68,12 @@ func NewServer(ctx context.Context, cfg *config.Config) (*Server, error) {
6968

7069
server := &Server{
7170
mcpServer: mcpServer,
72-
readOnly: cfg != nil && cfg.ReadOnly,
7371
}
7472

75-
// Register all tools (including proxied docs tools)
76-
server.registerTools(ctx)
73+
// Register all tools (including proxied docs tools). readOnly is captured
74+
// here and threaded through registration only.
75+
readOnly := cfg != nil && cfg.ReadOnly
76+
server.registerTools(ctx, readOnly)
7777

7878
// Add analytics tracking middleware
7979
server.mcpServer.AddReceivingMiddleware(server.analyticsMiddleware)
@@ -96,12 +96,12 @@ func (s *Server) HTTPHandler() http.Handler {
9696
}
9797

9898
// registerTools registers all available MCP tools
99-
func (s *Server) registerTools(ctx context.Context) {
99+
func (s *Server) registerTools(ctx context.Context, readOnly bool) {
100100
// Service management tools
101-
s.registerServiceTools()
101+
s.registerServiceTools(readOnly)
102102

103103
// Database operation tools
104-
s.registerDatabaseTools()
104+
s.registerDatabaseTools(readOnly)
105105

106106
// TODO: Register more tool groups
107107

internal/tiger/mcp/server_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@ func registeredToolNames(t *testing.T, readOnly bool) []string {
3333
Title: serverTitle,
3434
Version: config.Version,
3535
}, nil),
36-
readOnly: readOnly,
3736
}
38-
s.registerServiceTools()
39-
s.registerDatabaseTools()
37+
s.registerServiceTools(readOnly)
38+
s.registerDatabaseTools(readOnly)
4039

4140
clientTransport, serverTransport := mcp.NewInMemoryTransports()
4241

internal/tiger/mcp/service_tools.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -418,9 +418,9 @@ func (ServiceLogsOutput) Schema() *jsonschema.Schema {
418418
}
419419

420420
// registerServiceTools registers service management tools with comprehensive schemas and descriptions
421-
func (s *Server) registerServiceTools() {
421+
func (s *Server) registerServiceTools(readOnly bool) {
422422
// service_list
423-
addTool(s, &mcp.Tool{
423+
addTool(s, readOnly, &mcp.Tool{
424424
Name: toolServiceList,
425425
Title: "List Database Services",
426426
Description: "List all database services in your Tiger Cloud project. " +
@@ -435,7 +435,7 @@ func (s *Server) registerServiceTools() {
435435
}, s.handleServiceList)
436436

437437
// service_get
438-
addTool(s, &mcp.Tool{
438+
addTool(s, readOnly, &mcp.Tool{
439439
Name: toolServiceGet,
440440
Title: "Get Service Details",
441441
Description: "Get detailed information for a specific database service. " +
@@ -450,7 +450,7 @@ func (s *Server) registerServiceTools() {
450450
}, s.handleServiceGet)
451451

452452
// service_create
453-
addTool(s, &mcp.Tool{
453+
addTool(s, readOnly, &mcp.Tool{
454454
Name: toolServiceCreate,
455455
Title: "Create Database Service",
456456
Description: `Create a new database service in Tiger Cloud with specified type, compute resources, region, and HA options.
@@ -472,7 +472,7 @@ WARNING: Creates billable resources.`,
472472
}, s.handleServiceCreate)
473473

474474
// service_fork
475-
addTool(s, &mcp.Tool{
475+
addTool(s, readOnly, &mcp.Tool{
476476
Name: toolServiceFork,
477477
Title: "Fork Database Service",
478478
Description: `Fork an existing database service to create a new independent copy.
@@ -500,7 +500,7 @@ WARNING: Creates billable resources.`,
500500
}, s.handleServiceFork)
501501

502502
// service_update_password
503-
addTool(s, &mcp.Tool{
503+
addTool(s, readOnly, &mcp.Tool{
504504
Name: toolServiceUpdatePassword,
505505
Title: "Update Service Password",
506506
Description: "Update master password for 'tsdbadmin' user of a database service. " +
@@ -517,7 +517,7 @@ WARNING: Creates billable resources.`,
517517
}, s.handleServiceUpdatePassword)
518518

519519
// service_start
520-
addTool(s, &mcp.Tool{
520+
addTool(s, readOnly, &mcp.Tool{
521521
Name: toolServiceStart,
522522
Title: "Start Database Service",
523523
Description: `Start a stopped database service.
@@ -535,7 +535,7 @@ This operation starts a service that is currently in a stopped/paused state. The
535535
}, s.handleServiceStart)
536536

537537
// service_stop
538-
addTool(s, &mcp.Tool{
538+
addTool(s, readOnly, &mcp.Tool{
539539
Name: toolServiceStop,
540540
Title: "Stop Database Service",
541541
Description: `Stop a running database service.
@@ -553,7 +553,7 @@ This operation stops a service that is currently running. The service will trans
553553
}, s.handleServiceStop)
554554

555555
// service_resize
556-
addTool(s, &mcp.Tool{
556+
addTool(s, readOnly, &mcp.Tool{
557557
Name: toolServiceResize,
558558
Title: "Resize Database Service",
559559
Description: `Resize a database service by changing its CPU and memory allocation.
@@ -574,7 +574,7 @@ WARNING: Creates billable resource changes. Increasing resources will increase c
574574
}, s.handleServiceResize)
575575

576576
// service_logs
577-
addTool(s, &mcp.Tool{
577+
addTool(s, readOnly, &mcp.Tool{
578578
Name: toolServiceLogs,
579579
Title: "Get Service Logs",
580580
Description: `View logs for a database service.

0 commit comments

Comments
 (0)