@0xmaxma/claude-gateway 1.2.12 → 1.2.15

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.
@@ -0,0 +1,87 @@
1
+ import type { ToolModule, McpToolDefinition, McpToolResult, ToolVisibility } from '../../types';
2
+
3
+ export class ApiModule implements ToolModule {
4
+ id = 'api';
5
+ toolVisibility: ToolVisibility = 'current-channel';
6
+
7
+ isEnabled(): boolean {
8
+ return process.env.GATEWAY_ORIGIN_CHANNEL === 'api';
9
+ }
10
+
11
+ getTools(): McpToolDefinition[] {
12
+ return [
13
+ {
14
+ name: 'api_reply',
15
+ description:
16
+ 'Attach files to the current API session response. ' +
17
+ 'Use to include screenshots or images in the reply returned to the API caller. ' +
18
+ 'Files must be absolute paths already saved to the session media directory.',
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ files: {
23
+ type: 'array',
24
+ items: { type: 'string' },
25
+ description: 'Absolute file paths to attach as images.',
26
+ },
27
+ },
28
+ required: ['files'],
29
+ },
30
+ },
31
+ ];
32
+ }
33
+
34
+ async handleTool(name: string, args: Record<string, unknown>): Promise<McpToolResult> {
35
+ if (name === 'api_reply') {
36
+ return this.handleApiReply(args);
37
+ }
38
+ return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
39
+ }
40
+
41
+ private async handleApiReply(args: Record<string, unknown>): Promise<McpToolResult> {
42
+ const files = (args.files as string[] | undefined) ?? [];
43
+ if (!files.length) {
44
+ return { content: [{ type: 'text', text: 'No files provided.' }] };
45
+ }
46
+
47
+ const apiUrl = process.env.GATEWAY_API_URL;
48
+ const agentId = process.env.GATEWAY_AGENT_ID;
49
+ const sessionId = process.env.GATEWAY_SESSION_ID;
50
+ const apiKey = process.env.GATEWAY_API_KEY;
51
+
52
+ if (!apiUrl || !agentId || !sessionId) {
53
+ return {
54
+ content: [{ type: 'text', text: 'api_reply: missing GATEWAY_API_URL, GATEWAY_AGENT_ID, or GATEWAY_SESSION_ID' }],
55
+ isError: true,
56
+ };
57
+ }
58
+
59
+ try {
60
+ const res = await fetch(
61
+ `${apiUrl}/v1/agents/${encodeURIComponent(agentId)}/sessions/${encodeURIComponent(sessionId)}/attachments`,
62
+ {
63
+ method: 'POST',
64
+ headers: {
65
+ 'Content-Type': 'application/json',
66
+ ...(apiKey ? { 'X-Api-Key': apiKey } : {}),
67
+ },
68
+ body: JSON.stringify({ files }),
69
+ signal: AbortSignal.timeout(5000),
70
+ },
71
+ );
72
+ if (!res.ok) {
73
+ const text = await res.text().catch(() => '');
74
+ return {
75
+ content: [{ type: 'text', text: `api_reply: gateway returned ${res.status}: ${text}` }],
76
+ isError: true,
77
+ };
78
+ }
79
+ return { content: [{ type: 'text', text: `Attached ${files.length} file(s).` }] };
80
+ } catch (err) {
81
+ return {
82
+ content: [{ type: 'text', text: `api_reply: ${(err as Error).message}` }],
83
+ isError: true,
84
+ };
85
+ }
86
+ }
87
+ }
@@ -24,16 +24,37 @@ export class BrowserModule implements ToolModule {
24
24
 
25
25
  const result = await callGetpodBrowser(name, args);
26
26
 
27
+ if (name === 'browser_create_session' && !result.isError) {
28
+ const textBlock = result.content[0] as { type: string; text: string } | undefined;
29
+ if (textBlock?.type === 'text') {
30
+ try {
31
+ const parsed = JSON.parse(textBlock.text) as Record<string, unknown>;
32
+ delete parsed['stream_url'];
33
+ return { content: [{ type: 'text', text: JSON.stringify(parsed) }] };
34
+ } catch {
35
+ // return as-is if parsing fails
36
+ }
37
+ }
38
+ }
39
+
27
40
  if (name === 'browser_screenshot' && !result.isError) {
28
41
  // getpod-browser returns {type:"image", data: base64, mimeType:"image/jpeg"}.
29
- // Decode and save to /tmp so callers can attach the file path directly.
42
+ // Decode and save so callers can attach the file path directly.
43
+ // For API sessions, save to the session media dir so files are HTTP-accessible.
30
44
  const block = result.content[0] as Record<string, string> | undefined;
31
45
  const b64 = block?.['data'] ?? '';
32
46
  const mime = block?.['mimeType'] ?? 'image/jpeg';
33
47
  if (b64) {
34
48
  const ext = mime.includes('png') ? 'png' : 'jpg';
35
49
  const sid = (args.session_id as string | undefined) ?? 'default';
36
- const filePath = path.join('/tmp', `browser_shot_${sid}.${ext}`);
50
+ const mediaDir = process.env.GATEWAY_SESSION_MEDIA_DIR;
51
+ let filePath: string;
52
+ if (mediaDir) {
53
+ fs.mkdirSync(mediaDir, { recursive: true });
54
+ filePath = path.join(mediaDir, `browser_shot_${sid}_${Date.now()}.${ext}`);
55
+ } else {
56
+ filePath = path.join('/tmp', `browser_shot_${sid}.${ext}`);
57
+ }
37
58
  fs.writeFileSync(filePath, Buffer.from(b64, 'base64'));
38
59
  return { content: [{ type: 'text', text: filePath }] };
39
60
  }
@@ -136,9 +157,7 @@ async function callGetpodBrowser(
136
157
  const browserToolDefs: McpToolDefinition[] = [
137
158
  {
138
159
  name: 'browser_create_session',
139
- description:
140
- 'Create or resume a browser session. Returns stream_url and status. ' +
141
- 'IMPORTANT: After creating a session, always share the stream_url with the user so they can open the browser in their client.',
160
+ description: 'Create or resume a browser session. Returns session status.',
142
161
  inputSchema: {
143
162
  type: 'object',
144
163
  properties: {
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: open-browser
3
- description: "Open a URL in the agent's persistent browser session via MCP browser tools"
3
+ description: "ALWAYS invoke this skill when user says 'browser [site]', 'open [site]', or asks to navigate to a website. Never call MCP browser tools directly."
4
4
  user-invocable: true
5
5
  ---
6
6
 
7
7
  # open-browser
8
8
 
9
- When user says "เปิด X", "navigate to X", "open X in browser", "switch to tab X", etc.
9
+ When user says "open X in browser", "navigate to X", "open chrome", "browser to X", "switch to tab X", etc.
10
10
 
11
11
  ## Rules
12
12
 
@@ -21,13 +21,9 @@ When user says "เปิด X", "navigate to X", "open X in browser", "switch t
21
21
 
22
22
  Call `mcp__gateway__browser_create_session` with NO arguments (session_id is auto-injected).
23
23
 
24
- Result contains `stream_url` use it to build the browser URL for the user.
24
+ Result contains session status only.
25
25
 
26
- ### Step 2 — Send browser URL to user IMMEDIATELY (no waiting)
27
-
28
- Reply on Telegram right away with the stream_url so the user can open the browser.
29
-
30
- ### Step 3 — Check current tabs
26
+ ### Step 2 — Check current tabs
31
27
 
32
28
  Call `mcp__gateway__browser_tabs` — returns list of `{tab_id, url, title}`.
33
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmaxma/claude-gateway",
3
- "version": "1.2.12",
3
+ "version": "1.2.15",
4
4
  "description": "Multi-agent gateway for Claude",
5
5
  "repository": {
6
6
  "type": "git",