happy-mcp-server 0.3.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,9 +18,48 @@ happy-mcp auth
18
18
 
19
19
  Scan the QR code with the Happy mobile app to pair your account.
20
20
 
21
- ### 3. Configure Your MCP Client
21
+ ### 3. Choose Your Transport
22
22
 
23
- Add `happy-mcp` to your MCP client configuration. See [MCP Configuration](#mcp-configuration) below.
23
+ **Option A: Stdio Transport (Default)**
24
+
25
+ Configure `happy-mcp` in your MCP client. See [MCP Configuration](#mcp-configuration) below.
26
+
27
+ **Option B: HTTP Transport**
28
+
29
+ Start the server with HTTP transport:
30
+
31
+ ```bash
32
+ happy-mcp serve
33
+ # Or specify a port:
34
+ happy-mcp serve --port 3000
35
+ ```
36
+
37
+ The server will start at `http://127.0.0.1:<port>/mcp` (port auto-assigned if not specified).
38
+
39
+ ## Commands
40
+
41
+ `happy-mcp` provides several commands for different modes and authentication:
42
+
43
+ | Command | Description |
44
+ |---------|-------------|
45
+ | `happy-mcp` | Start the MCP server using stdio transport (default). Use this mode when configuring the server in MCP clients like Claude Desktop, Claude Code, Cursor, etc. |
46
+ | `happy-mcp serve` | Start the MCP server using HTTP transport on an auto-assigned port. The server binds to `127.0.0.1` and reports the listening port and endpoint URL on startup. |
47
+ | `happy-mcp serve --port <port>` | Start the HTTP server on a specific port (1-65535). Useful when you need a predictable port number for client configuration or firewall rules. |
48
+ | `happy-mcp auth` | Check authentication status. If not authenticated, prompts you to scan a QR code with the Happy mobile app to pair your account. |
49
+ | `happy-mcp auth login` | Force a new pairing flow, even if already authenticated. Use this to switch accounts or re-authenticate. |
50
+ | `happy-mcp auth logout` | Remove saved credentials from `~/.happy-mcp/credentials.json`. |
51
+ | `happy-mcp help` | Display help message with available commands. |
52
+
53
+ ### Transport Comparison
54
+
55
+ | Feature | Stdio Transport | HTTP Transport |
56
+ |---------|----------------|----------------|
57
+ | **Use case** | MCP client integration (Claude Desktop, Cursor, etc.) | Custom clients, testing, or programmatic access |
58
+ | **Command** | `happy-mcp` | `happy-mcp serve [--port <port>]` |
59
+ | **Communication** | Standard input/output streams | HTTP POST/GET/DELETE to `/mcp` endpoint |
60
+ | **Port** | N/A (uses stdio) | Auto-assigned or specified with `--port` |
61
+ | **Accessibility** | Only via client process | HTTP endpoint on localhost (`127.0.0.1`) |
62
+ | **Authentication** | Lazy (authenticates on first tool call if needed) | Required before startup (fails if credentials missing) |
24
63
 
25
64
  ## Configuration
26
65
 
@@ -34,6 +73,55 @@ Environment variables customize server behavior:
34
73
  | `HAPPY_MCP_LOG_LEVEL` | `warn` | Log level: `debug`, `info`, `warn`, or `error`. Logs are written to stderr. |
35
74
  | `HAPPY_MCP_ENABLE_START` | `true` | Set to `false` to disable the `start_session` tool. |
36
75
 
76
+ ## HTTP Transport
77
+
78
+ When running `happy-mcp serve`, the server exposes MCP over HTTP instead of stdio. This mode is useful for custom clients, testing, or programmatic access.
79
+
80
+ ### Starting the HTTP Server
81
+
82
+ ```bash
83
+ # Auto-assign port (reports actual port on startup)
84
+ happy-mcp serve
85
+
86
+ # Specify a port
87
+ happy-mcp serve --port 3000
88
+
89
+ # With environment variables
90
+ HAPPY_MCP_LOG_LEVEL=debug happy-mcp serve --port 8080
91
+ ```
92
+
93
+ ### Server Endpoint
94
+
95
+ The server exposes the MCP protocol at:
96
+
97
+ ```
98
+ http://127.0.0.1:<port>/mcp
99
+ ```
100
+
101
+ **Security:** The server binds to `127.0.0.1` (localhost only) and is not accessible from other machines. This prevents unauthorized network access to your Happy Coder sessions.
102
+
103
+ ### HTTP Methods
104
+
105
+ | Method | Endpoint | Purpose |
106
+ |--------|----------|---------|
107
+ | POST | `/mcp` | Initialize new MCP session or send requests |
108
+ | GET | `/mcp` | Server-Sent Events (SSE) stream for notifications |
109
+ | DELETE | `/mcp` | Terminate an MCP session |
110
+
111
+ ### Requirements
112
+
113
+ - **Authentication required**: You must run `happy-mcp auth` before starting the HTTP server. The server will exit with an error if credentials are not found.
114
+ - **Credential file permissions**: The credentials file must have `0600` permissions (readable only by owner).
115
+
116
+ ### Example: Testing with curl
117
+
118
+ ```bash
119
+ # Initialize a new MCP session
120
+ curl -X POST http://127.0.0.1:3000/mcp \
121
+ -H "Content-Type: application/json" \
122
+ -d '{"jsonrpc": "2.0", "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0.0"}}, "id": 1}'
123
+ ```
124
+
37
125
  ## MCP Configuration
38
126
 
39
127
  <details>
@@ -149,6 +149,10 @@ export class RelayClient extends EventEmitter {
149
149
  : null;
150
150
  this.sessionManager.updateAgentState(sessionId, decrypted, agentState.version);
151
151
  }
152
+ // Handle active flag
153
+ if (body.active !== undefined) {
154
+ this.sessionManager.updateSessionActivity(sessionId, body.active);
155
+ }
152
156
  this.emit('session_update', sessionId);
153
157
  }
154
158
  handleUpdateMachine(body) {
@@ -202,6 +206,12 @@ export class RelayClient extends EventEmitter {
202
206
  }
203
207
  }
204
208
  break;
209
+ case 'activity':
210
+ if (event.id && this.sessionManager.has(event.id)) {
211
+ this.sessionManager.updateSessionActivity(event.id, event.active ?? false, event.activeAt, event.thinking);
212
+ this.emit('session_update', event.id);
213
+ }
214
+ break;
205
215
  default:
206
216
  logger.debug('Unhandled ephemeral type:', event.type);
207
217
  }
@@ -13,6 +13,7 @@ export declare class SessionManager {
13
13
  remove(id: string): void;
14
14
  updateMetadata(id: string, metadata: SessionMetadata | null, version: number): void;
15
15
  updateAgentState(id: string, agentState: AgentState | null, version: number): void;
16
+ updateSessionActivity(id: string, active: boolean, activeAt?: number, thinking?: boolean): void;
16
17
  applyMessage(sessionId: string, messageId: string, seq: number, content: unknown, createdAt: number): void;
17
18
  getMessages(sessionId: string, limit?: number): DecryptedMessage[];
18
19
  getSessionStatus(sessionId: string): SessionStatus;
@@ -39,6 +39,9 @@ export class SessionManager {
39
39
  lastSeq: 0,
40
40
  lastActivity: Date.now(),
41
41
  active,
42
+ activeAt: Date.now(),
43
+ thinking: false,
44
+ thinkingAt: 0,
42
45
  createdAt,
43
46
  updatedAt,
44
47
  });
@@ -78,6 +81,20 @@ export class SessionManager {
78
81
  logger.debug(`Session ${id} agentState updated to v${version}`);
79
82
  }
80
83
  }
84
+ updateSessionActivity(id, active, activeAt, thinking) {
85
+ const session = this.sessions.get(id);
86
+ if (!session)
87
+ return;
88
+ session.active = active;
89
+ if (activeAt !== undefined)
90
+ session.activeAt = activeAt;
91
+ if (thinking !== undefined) {
92
+ session.thinking = thinking;
93
+ session.thinkingAt = Date.now();
94
+ }
95
+ session.lastActivity = Date.now();
96
+ logger.debug(`Session ${id} activity updated: active=${active}, thinking=${thinking ?? session.thinking}`);
97
+ }
81
98
  // --- Message Handling ---
82
99
  applyMessage(sessionId, messageId, seq, content, createdAt) {
83
100
  const session = this.sessions.get(sessionId);
@@ -104,13 +121,16 @@ export class SessionManager {
104
121
  // --- Status Derivation ---
105
122
  getSessionStatus(sessionId) {
106
123
  const session = this.sessions.get(sessionId);
107
- if (!session?.agentState)
124
+ if (!session)
108
125
  return 'idle';
109
- const state = session.agentState;
110
- const hasRequests = state.requests && Object.keys(state.requests).length > 0;
111
- if (hasRequests)
112
- return 'waiting_permission';
113
- if (state.controlledByUser)
126
+ // Permission requests take highest priority
127
+ if (session.agentState) {
128
+ const hasRequests = session.agentState.requests && Object.keys(session.agentState.requests).length > 0;
129
+ if (hasRequests)
130
+ return 'waiting_permission';
131
+ }
132
+ // Session is active if the relay says it's active or it's thinking
133
+ if (session.active || session.thinking)
114
134
  return 'active';
115
135
  return 'idle';
116
136
  }
@@ -52,6 +52,9 @@ export interface CachedSession {
52
52
  lastSeq: number;
53
53
  lastActivity: number;
54
54
  active: boolean;
55
+ activeAt: number;
56
+ thinking: boolean;
57
+ thinkingAt: number;
55
58
  createdAt: number;
56
59
  updatedAt: number;
57
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-mcp-server",
3
- "version": "0.3.2",
3
+ "version": "1.0.1",
4
4
  "description": "MCP server for observing and controlling Happy Coder sessions",
5
5
  "author": {
6
6
  "name": "Jared Spencer",