happy-mcp-server 0.3.0 → 0.3.2

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.
@@ -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).
@@ -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, api, sessionManager);
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 { ApiClient } from '../api/client.js';
2
+ import type { RelayClient } from '../relay/client.js';
3
3
  import type { SessionManager } from '../session/manager.js';
4
- export declare function registerSendMessage(server: McpServer, api: ApiClient, sessionManager: SessionManager): RegisteredTool;
4
+ export declare function registerSendMessage(server: McpServer, relay: RelayClient, sessionManager: SessionManager): RegisteredTool;
@@ -1,9 +1,8 @@
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, api, sessionManager) {
6
- return server.tool('send_message', 'Send a message to a Happy Coder session. The message will appear as a user message in the session. Can optionally change the permission mode.', {
4
+ export function registerSendMessage(server, relay, sessionManager) {
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'),
9
8
  meta: z.object({
@@ -13,14 +12,20 @@ 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` }) }] };
19
28
  }
20
- const status = sessionManager.getSessionStatus(sessionId);
21
- if (status === 'active') {
22
- return { isError: true, content: [{ type: 'text', text: JSON.stringify({ error: 'SessionNotIdle', message: 'Cannot send message while session is actively generating' }) }] };
23
- }
24
29
  // Build message in legacy format (verified against upstream)
25
30
  const content = {
26
31
  role: 'user',
@@ -32,17 +37,15 @@ export function registerSendMessage(server, api, sessionManager) {
32
37
  ...(meta?.disallowedTools ? { disallowedTools: meta.disallowedTools } : {}),
33
38
  },
34
39
  };
35
- // Encrypt and send via V3 REST API
40
+ // Encrypt and send via Socket.io emit (fire-and-forget)
36
41
  const encrypted = encryptToBase64(session.encryption, content);
37
- const localId = randomUUID();
38
- const result = await api.sendMessages(sessionId, [{ content: encrypted, localId }]);
42
+ relay.sendMessage(sessionId, encrypted);
39
43
  return {
40
44
  content: [{
41
45
  type: 'text',
42
46
  text: JSON.stringify({
43
47
  success: true,
44
- messageId: result.messages?.[0]?.id,
45
- seq: result.messages?.[0]?.seq,
48
+ dispatched: true,
46
49
  }, null, 2),
47
50
  }],
48
51
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "MCP server for observing and controlling Happy Coder sessions",
5
5
  "author": {
6
6
  "name": "Jared Spencer",