Skip to content

Commit 3efd62d

Browse files
Copilotintel352
andauthored
feat: CLI self-update command, update check, and wfctl mcp subcommand (#203)
* Initial plan * feat: add CLI self-update command, update check, and wfctl mcp command Co-authored-by: intel352 <77607+intel352@users.noreply.github.com> * fix: address review comments on self-update and MCP commands Co-authored-by: intel352 <77607+intel352@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
1 parent 01e9c18 commit 3efd62d

6 files changed

Lines changed: 844 additions & 16 deletions

File tree

cmd/wfctl/main.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"fmt"
55
"os"
6+
"time"
67
)
78

89
var version = "dev"
@@ -29,6 +30,8 @@ var commands = map[string]func([]string) error{
2930
"generate": runGenerate,
3031
"git": runGit,
3132
"registry": runRegistry,
33+
"update": runUpdate,
34+
"mcp": runMCP,
3235
}
3336

3437
func usage() {
@@ -59,6 +62,8 @@ Commands:
5962
generate Code generation (github-actions: generate CI/CD workflows from config)
6063
git Git integration (connect: link to GitHub repo, push: commit and push)
6164
registry Registry management (list, add, remove plugin registry sources)
65+
update Update wfctl to the latest version (use --check to only check)
66+
mcp Start the MCP server over stdio for AI assistant integration
6267
6368
Run 'wfctl <command> -h' for command-specific help.
6469
`, version)
@@ -87,8 +92,25 @@ func main() {
8792
os.Exit(1)
8893
}
8994

95+
// Start the update check in the background before running the command so
96+
// that it runs concurrently. For long-running commands (mcp, run) we skip
97+
// it entirely. After the command finishes we wait briefly for the result.
98+
var updateNoticeDone <-chan struct{}
99+
if cmd != "mcp" && cmd != "run" {
100+
updateNoticeDone = checkForUpdateNotice()
101+
}
102+
90103
if err := fn(os.Args[2:]); err != nil {
91104
fmt.Fprintf(os.Stderr, "error: %v\n", err) //nolint:gosec // G705: CLI error output
92105
os.Exit(1)
93106
}
107+
108+
// Wait briefly for the update notice after the command completes.
109+
// A 1-second ceiling ensures we never meaningfully delay the shell prompt.
110+
if updateNoticeDone != nil {
111+
select {
112+
case <-updateNoticeDone:
113+
case <-time.After(time.Second):
114+
}
115+
}
94116
}

cmd/wfctl/mcp.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
7+
workflowmcp "github.com/GoCodeAlone/workflow/mcp"
8+
)
9+
10+
// runMCP starts the workflow MCP (Model Context Protocol) server over stdio.
11+
// This exposes workflow engine tools and resources to AI assistants.
12+
func runMCP(args []string) error {
13+
fs := flag.NewFlagSet("mcp", flag.ContinueOnError)
14+
pluginDir := fs.String("plugin-dir", "data/plugins", "Plugin data directory")
15+
fs.Usage = func() {
16+
fmt.Fprintf(fs.Output(), `Usage: wfctl mcp [options]
17+
18+
Start the workflow MCP (Model Context Protocol) server over stdio.
19+
This exposes workflow engine tools and resources to AI assistants such as
20+
Claude Desktop, VS Code with GitHub Copilot, and Cursor.
21+
22+
The server provides tools for listing module types, validating configs,
23+
generating schemas, and inspecting workflow YAML configurations.
24+
25+
Options:
26+
`)
27+
fs.PrintDefaults()
28+
fmt.Fprintf(fs.Output(), `
29+
Example Claude Desktop configuration (~/.config/claude/claude_desktop_config.json):
30+
31+
{
32+
"mcpServers": {
33+
"workflow": {
34+
"command": "wfctl",
35+
"args": ["mcp", "-plugin-dir", "/path/to/data/plugins"]
36+
}
37+
}
38+
}
39+
40+
See docs/mcp.md for full setup instructions.
41+
`)
42+
}
43+
if err := fs.Parse(args); err != nil {
44+
return err
45+
}
46+
47+
// Propagate the CLI version so the MCP handshake and version output
48+
// reflect the release version set at build time.
49+
workflowmcp.Version = version
50+
51+
srv := workflowmcp.NewServer(*pluginDir)
52+
return srv.ServeStdio()
53+
}

cmd/wfctl/mcp_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestRunMCP_Usage(t *testing.T) {
8+
// Passing -h should return an error from flag parsing (ExitOnError calls os.Exit,
9+
// but we use ContinueOnError in runMCP so it returns an error instead).
10+
err := runMCP([]string{"-h"})
11+
if err == nil {
12+
t.Fatal("expected error from -h flag")
13+
}
14+
}
15+
16+
func TestRunMCP_UnknownFlag(t *testing.T) {
17+
err := runMCP([]string{"--unknown-flag"})
18+
if err == nil {
19+
t.Fatal("expected error for unknown flag")
20+
}
21+
}

0 commit comments

Comments
 (0)