happy-mcp-server 0.3.1 → 1.0.0
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 +90 -2
- package/dist/relay/client.d.ts +5 -0
- package/dist/relay/client.js +13 -0
- package/dist/server.js +1 -1
- package/dist/tools/send_message.d.ts +2 -2
- package/dist/tools/send_message.js +14 -7
- package/package.json +1 -1
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.
|
|
21
|
+
### 3. Choose Your Transport
|
|
22
22
|
|
|
23
|
-
|
|
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>
|
package/dist/relay/client.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export declare class RelayClient extends EventEmitter {
|
|
|
27
27
|
* Response is encrypted with session key.
|
|
28
28
|
*/
|
|
29
29
|
sessionRpc<R>(sessionId: string, method: string, params: unknown): Promise<R>;
|
|
30
|
+
/**
|
|
31
|
+
* Send a message to a session via Socket.io emit (fire-and-forget).
|
|
32
|
+
* Used by send_message tool.
|
|
33
|
+
*/
|
|
34
|
+
sendMessage(sessionId: string, encryptedContent: string): void;
|
|
30
35
|
/**
|
|
31
36
|
* Encrypted RPC call to a machine.
|
|
32
37
|
* Used for start_session (spawn-happy-session).
|
package/dist/relay/client.js
CHANGED
|
@@ -233,6 +233,19 @@ export class RelayClient extends EventEmitter {
|
|
|
233
233
|
}
|
|
234
234
|
return undefined;
|
|
235
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Send a message to a session via Socket.io emit (fire-and-forget).
|
|
238
|
+
* Used by send_message tool.
|
|
239
|
+
*/
|
|
240
|
+
sendMessage(sessionId, encryptedContent) {
|
|
241
|
+
if (!this.connected || !this.socket) {
|
|
242
|
+
throw new RelayError('Relay not connected');
|
|
243
|
+
}
|
|
244
|
+
this.socket.emit('message', {
|
|
245
|
+
sid: sessionId,
|
|
246
|
+
message: encryptedContent,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
236
249
|
/**
|
|
237
250
|
* Encrypted RPC call to a machine.
|
|
238
251
|
* Used for start_session (spawn-happy-session).
|
package/dist/server.js
CHANGED
|
@@ -42,7 +42,7 @@ export function registerAllTools(server, config, api, relay, sessionManager) {
|
|
|
42
42
|
registerListSessions(server, sessionManager, config);
|
|
43
43
|
registerGetSession(server, api, sessionManager);
|
|
44
44
|
registerWatchSession(server, relay, sessionManager);
|
|
45
|
-
registerSendMessage(server,
|
|
45
|
+
registerSendMessage(server, relay, sessionManager);
|
|
46
46
|
registerApprovePermission(server, relay, sessionManager);
|
|
47
47
|
registerDenyPermission(server, relay, sessionManager);
|
|
48
48
|
registerInterruptSession(server, relay, sessionManager);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RelayClient } from '../relay/client.js';
|
|
3
3
|
import type { SessionManager } from '../session/manager.js';
|
|
4
|
-
export declare function registerSendMessage(server: McpServer,
|
|
4
|
+
export declare function registerSendMessage(server: McpServer, relay: RelayClient, sessionManager: SessionManager): RegisteredTool;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { randomUUID } from 'crypto';
|
|
3
2
|
import { encryptToBase64 } from '../auth/crypto.js';
|
|
4
3
|
const PERMISSION_MODES = ['default', 'acceptEdits', 'bypassPermissions', 'plan', 'read-only', 'safe-yolo', 'yolo'];
|
|
5
|
-
export function registerSendMessage(server,
|
|
4
|
+
export function registerSendMessage(server, relay, sessionManager) {
|
|
6
5
|
return server.tool('send_message', 'Send a message to a Happy Coder session. The message will appear as a user message in the session. If the session is actively generating, the message will be queued and processed when the session is ready. Can optionally change the permission mode.', {
|
|
7
6
|
sessionId: z.string().describe('The session ID to send the message to'),
|
|
8
7
|
message: z.string().describe('The message text to send'),
|
|
@@ -13,6 +12,16 @@ export function registerSendMessage(server, api, sessionManager) {
|
|
|
13
12
|
}).optional(),
|
|
14
13
|
}, async ({ sessionId, message, meta }) => {
|
|
15
14
|
try {
|
|
15
|
+
// Check relay connectivity
|
|
16
|
+
if (!relay.connected) {
|
|
17
|
+
const msg = relay.state === 'connecting'
|
|
18
|
+
? 'Relay is still connecting. Please try again in a few seconds.'
|
|
19
|
+
: 'Relay is disconnected.';
|
|
20
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify({
|
|
21
|
+
error: relay.state === 'connecting' ? 'RelayConnecting' : 'RelayDisconnected',
|
|
22
|
+
message: msg,
|
|
23
|
+
}) }] };
|
|
24
|
+
}
|
|
16
25
|
const session = sessionManager.get(sessionId);
|
|
17
26
|
if (!session) {
|
|
18
27
|
return { isError: true, content: [{ type: 'text', text: JSON.stringify({ error: 'SessionNotFound', message: `Session ${sessionId} not found` }) }] };
|
|
@@ -28,17 +37,15 @@ export function registerSendMessage(server, api, sessionManager) {
|
|
|
28
37
|
...(meta?.disallowedTools ? { disallowedTools: meta.disallowedTools } : {}),
|
|
29
38
|
},
|
|
30
39
|
};
|
|
31
|
-
// Encrypt and send via
|
|
40
|
+
// Encrypt and send via Socket.io emit (fire-and-forget)
|
|
32
41
|
const encrypted = encryptToBase64(session.encryption, content);
|
|
33
|
-
|
|
34
|
-
const result = await api.sendMessages(sessionId, [{ content: encrypted, localId }]);
|
|
42
|
+
relay.sendMessage(sessionId, encrypted);
|
|
35
43
|
return {
|
|
36
44
|
content: [{
|
|
37
45
|
type: 'text',
|
|
38
46
|
text: JSON.stringify({
|
|
39
47
|
success: true,
|
|
40
|
-
|
|
41
|
-
seq: result.messages?.[0]?.seq,
|
|
48
|
+
dispatched: true,
|
|
42
49
|
}, null, 2),
|
|
43
50
|
}],
|
|
44
51
|
};
|