tools: replace instructions with usage tool

Amolith created

Change summary

internal/server/server.go | 32 +++++++++++++++++++++++++++-----
internal/server/tools.go  |  3 +++
2 files changed, 30 insertions(+), 5 deletions(-)

Detailed changes

internal/server/server.go 🔗

@@ -37,14 +37,13 @@ func New(cfg *config.Config) *mcp.Server {
 	server := mcp.NewServer(&mcp.Implementation{
 		Name:    "sb-mcp",
 		Version: Version,
-	}, &mcp.ServerOptions{
-		Instructions: Instructions,
-	})
+	}, &mcp.ServerOptions{})
 
 	// Register tools with the SB client closed over
 	mcp.AddTool(server, &mcp.Tool{
-		Name:        "execute_lua",
-		Description: "Execute a Space Lua script on the SilverBullet instance. Use 'return' to send results back. 'print()' output is not captured.",
+		Name: "execute_lua",
+		Description: "Execute a Space Lua script on the SilverBullet instance. " +
+			"Always call `usage` first to load the Space Lua reference and API guide.",
 		Annotations: &mcp.ToolAnnotations{
 			Title:           "Execute Lua",
 			ReadOnlyHint:    false,
@@ -76,6 +75,18 @@ func New(cfg *config.Config) *mcp.Server {
 		},
 	}, makeConsoleLogsHandler(sbClient))
 
+	mcp.AddTool(server, &mcp.Tool{
+		Name: "usage",
+		Description: "Load the full SilverBullet usage guide: Space Lua syntax, available APIs (space.*, editor.*, net.*, query), gotchas, and best practices. " +
+			"Always call this before `execute_lua`.",
+		Annotations: &mcp.ToolAnnotations{
+			Title:          "Usage Guide",
+			ReadOnlyHint:   true,
+			IdempotentHint: true,
+			OpenWorldHint:  ptrBool(false),
+		},
+	}, makeUsageHandler())
+
 	return server
 }
 
@@ -185,6 +196,17 @@ func makeConsoleLogsHandler(client *silverbullet.Client) func(context.Context, *
 	}
 }
 
+// makeUsageHandler returns a tool handler that returns the embedded SilverBullet usage guide.
+func makeUsageHandler() func(context.Context, *mcp.CallToolRequest, UsageParams) (*mcp.CallToolResult, any, error) {
+	return func(_ context.Context, _ *mcp.CallToolRequest, _ UsageParams) (*mcp.CallToolResult, any, error) {
+		return &mcp.CallToolResult{
+			Content: []mcp.Content{
+				&mcp.TextContent{Text: Instructions},
+			},
+		}, nil, nil
+	}
+}
+
 // formatLuaResult formats a JSON result from Lua execution for display.
 // JSON strings are unescaped to plain text (so markdown content reads cleanly).
 // All other values (numbers, bools, null, objects, arrays) pass through as raw JSON.

internal/server/tools.go 🔗

@@ -19,3 +19,6 @@ type ConsoleLogsParams struct {
 	Limit int   `json:"limit,omitempty"  jsonschema:"Maximum number of log entries to return (1-1000,default=100)"`
 	Since int64 `json:"since,omitempty"  jsonschema:"Only return entries newer than this unix millisecond timestamp (optional)"`
 }
+
+// UsageParams is empty because there are none :D .
+type UsageParams struct{}