Skip to content

Commit e2fe698

Browse files
committed
feat(cli): trigger self-install rune-mcp on first 'rune mcp-server' invocation
1 parent 17fd632 commit e2fe698

1 file changed

Lines changed: 56 additions & 0 deletions

File tree

cmd/rune/mcpserver.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"os"
8+
"time"
79

810
"github.com/CryptoLabInc/rune-cli/internal/bootstrap"
911
)
@@ -15,5 +17,59 @@ func runMCPServer(ctx context.Context, args []string, stderr io.Writer) int {
1517
return 1
1618
}
1719

20+
// Fresh `claude plugin install rune` try to spawn MCP server via
21+
// "${CLAUDE_PLUGIN_ROOT}/bin/rune mcp-server" which does not exist yet.
22+
// Self-install rune-mcp itself in this case.
23+
if _, statErr := os.Stat(paths.RuneMCPBinary); statErr != nil {
24+
fmt.Fprintln(stderr, "rune: rune-mcp not installed yet; fetching before launch...")
25+
manifest := manifestURL
26+
if env := os.Getenv("RUNE_MANIFEST"); env != "" {
27+
manifest = env
28+
}
29+
30+
// Concurrency-safe install
31+
_, instErr := bootstrap.Install(ctx, bootstrap.InstallOptions{
32+
ManifestURL: manifest,
33+
Target: []string{bootstrap.StepRuneMCP},
34+
Log: func(format string, a ...any) {
35+
fmt.Fprintf(stderr, format+"\n", a...)
36+
},
37+
})
38+
if instErr != nil {
39+
// Sessions which do not invoke install wait here
40+
if !waitForFile(ctx, paths.RuneMCPBinary, bootstrap.InstallLockTimeout) {
41+
fmt.Fprintf(stderr, "rune: failed to install rune-mcp: %v\n", instErr)
42+
return 1
43+
}
44+
45+
fmt.Fprintln(stderr, "rune: rune-mcp installed by a concurrent session")
46+
}
47+
}
48+
1849
return execInstalledBinary(ctx, paths.RuneBin, "rune-mcp", args, nil, stderr)
1950
}
51+
52+
func waitForFile(ctx context.Context, path string, timeout time.Duration) bool {
53+
if _, err := os.Stat(path); err == nil {
54+
return true
55+
}
56+
57+
deadline := time.NewTimer(timeout)
58+
defer deadline.Stop()
59+
60+
tick := time.NewTicker(300 * time.Millisecond)
61+
defer tick.Stop()
62+
63+
for {
64+
select {
65+
case <-ctx.Done():
66+
return false
67+
case <-deadline.C:
68+
return false
69+
case <-tick.C:
70+
if _, err := os.Stat(path); err == nil {
71+
return true
72+
}
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)