Skip to content

Commit 38fcc9a

Browse files
committed
fix: make hook mode default, require --mcp for MCP server mode
Remove TTY auto-detection from mode selection. The binary now runs in hook mode by default and requires an explicit --mcp flag for MCP server mode, ensuring reliable behavior when invoked as a Claude Code hook regardless of stdin type. Update install commands for Claude Desktop and Claude Code MCP to pass --mcp, and drop the PipeAutoDetect test that covered the removed behavior.
1 parent 51ab43c commit 38fcc9a

4 files changed

Lines changed: 10 additions & 32 deletions

File tree

cmd/datetime-mcp/main.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"strings"
99
_ "time/tzdata"
1010

11-
"github.com/tmsdnl/datetime-mcp/internal/detect"
1211
"github.com/tmsdnl/datetime-mcp/internal/formats"
1312
"github.com/tmsdnl/datetime-mcp/internal/hook"
1413
"github.com/tmsdnl/datetime-mcp/internal/install"
@@ -31,7 +30,7 @@ func main() {
3130
}
3231

3332
var (
34-
forceMCP = flag.Bool("mcp", false, "Force MCP server mode (override TTY auto-detection)")
33+
mcpMode = flag.Bool("mcp", false, "Run as MCP server (stdio JSON-RPC)")
3534
tz = flag.String("tz", "", "Override timezone (IANA tz database identifier)")
3635
format = flag.String("format", "", "Output format")
3736
formatsDir = flag.String("formats-dir", "", "Format files directory (default: {XDG_CONFIG_HOME}/datetime-mcp/formats/)")
@@ -58,8 +57,8 @@ func main() {
5857

5958
resolvedDir := resolveFormatsDir(*formatsDir)
6059

61-
// Mode selection: --mcp flag or non-TTY stdin → MCP server mode.
62-
if *forceMCP || !detect.IsTerminal(os.Stdin) {
60+
// Mode selection: --mcp flag → MCP server mode, otherwise hook mode.
61+
if *mcpMode {
6362
if err := mcp.Run(mcp.Config{
6463
Timezone: *tz,
6564
FormatsDir: resolvedDir,
@@ -137,8 +136,7 @@ func printHelp(formatsDir string) {
137136
}
138137

139138
fmt.Print(`A date/time provider for Claude Desktop, Claude Code, and Codex.
140-
Prints the current date/time when run from a terminal, or starts an MCP
141-
server when stdin is a pipe.
139+
Prints the current date/time by default, or starts an MCP server with --mcp.
142140
143141
Usage:
144142
datetime-mcp [flags]
@@ -148,7 +146,7 @@ Subcommands:
148146
install Register with supported AI tool integrations
149147
150148
Flags:
151-
--mcp Force MCP server mode (override TTY auto-detection)
149+
--mcp Run as MCP server (stdio JSON-RPC)
152150
--tz string Override timezone (IANA tz database identifier).
153151
Falls back to TZ env var, then system local timezone.
154152
Examples: America/Los_Angeles, Europe/Vilnius, UTC

cmd/datetime-mcp/main_test.go

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -191,26 +191,6 @@ func TestMCPMode_ToolCall(t *testing.T) {
191191
}
192192
}
193193

194-
func TestMCPMode_PipeAutoDetect(t *testing.T) {
195-
// Without --mcp, piping stdin should auto-detect pipe mode and enter MCP mode.
196-
msg := `{"jsonrpc":"2.0","id":1,"method":"ping"}` + "\n"
197-
stdout, _, code := run(t, msg) // no --mcp flag, but stdin is a pipe via cmd.Stdin
198-
if code != 0 {
199-
t.Fatalf("exited with %d", code)
200-
}
201-
// Should have ping response.
202-
scanner := bufio.NewScanner(strings.NewReader(stdout))
203-
if !scanner.Scan() {
204-
t.Fatal("no response for ping")
205-
}
206-
var resp map[string]any
207-
if err := json.Unmarshal(scanner.Bytes(), &resp); err != nil {
208-
t.Fatalf("invalid JSON: %v", err)
209-
}
210-
if resp["result"] == nil {
211-
t.Errorf("ping response missing result: %v", resp)
212-
}
213-
}
214194

215195
func TestMCPMode_SIGTERMShutdown(t *testing.T) {
216196
if runtime.GOOS == "windows" {

internal/install/claude_code_mcp.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ func installClaudeCodeMCP(exePath string, dryRun bool) Result {
3030

3131
if dryRun {
3232
return Result{Target: "Claude Code MCP", Status: StatusAdded, Path: path, DryRun: true,
33-
Note: "claude mcp add --scope user datetime " + exePath}
33+
Note: "claude mcp add --scope user datetime " + exePath + " --mcp"}
3434
}
3535

3636
// Delegate to claude CLI.
3737
if _, err := exec.LookPath("claude"); err != nil {
3838
return Result{Target: "Claude Code MCP", Status: StatusError, Path: path,
39-
Err: fmt.Errorf("claude CLI not found in PATH — run: claude mcp add --scope user datetime %s", exePath)}
39+
Err: fmt.Errorf("claude CLI not found in PATH — run: claude mcp add --scope user datetime %s --mcp", exePath)}
4040
}
41-
cmd := exec.Command("claude", "mcp", "add", "--scope", "user", "datetime", exePath)
41+
cmd := exec.Command("claude", "mcp", "add", "--scope", "user", "datetime", exePath, "--mcp")
4242
if out, err := cmd.CombinedOutput(); err != nil {
4343
return Result{Target: "Claude Code MCP", Status: StatusError, Path: path,
4444
Err: fmt.Errorf("claude mcp add: %w: %s", err, out)}

internal/install/claude_desktop.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ func installClaudeDesktop(exePath string, dryRun bool) Result {
5555
}
5656

5757
if dryRun {
58-
b, _ := json.MarshalIndent(map[string]map[string]string{"datetime": {"command": exePath}}, "", " ")
58+
b, _ := json.MarshalIndent(map[string]any{"datetime": map[string]any{"command": exePath, "args": []string{"--mcp"}}}, "", " ")
5959
return Result{Target: "Claude Desktop", Status: StatusAdded, Path: path, DryRun: true, Snippet: string(b)}
6060
}
6161

6262
// Add the entry.
63-
entry, _ := json.Marshal(map[string]string{"command": exePath})
63+
entry, _ := json.Marshal(map[string]any{"command": exePath, "args": []string{"--mcp"}})
6464
mcpServers["datetime"] = json.RawMessage(entry)
6565

6666
serialized, err := json.Marshal(mcpServers)

0 commit comments

Comments
 (0)