@hasna/computer 0.1.4 → 0.1.6

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/LICENSE CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  Apache License
2
3
  Version 2.0, January 2004
3
4
  http://www.apache.org/licenses/
@@ -34,7 +35,8 @@
34
35
 
35
36
  "Work" shall mean the work of authorship, whether in Source or
36
37
  Object form, made available under the License, as indicated by a
37
- copyright notice that is included in or attached to the work.
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
38
40
 
39
41
  "Derivative Works" shall mean any work, whether in Source or Object
40
42
  form, that is based on (or derived from) the Work and for which the
@@ -174,7 +176,7 @@
174
176
 
175
177
  END OF TERMS AND CONDITIONS
176
178
 
177
- Copyright 2026 Andrei Hasna
179
+ Copyright 2026 Hasna, Inc.
178
180
 
179
181
  Licensed under the Apache License, Version 2.0 (the "License");
180
182
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -67,6 +67,19 @@ Add to your Claude Code config:
67
67
  }
68
68
  ```
69
69
 
70
+ ## HTTP mode
71
+
72
+ Shared Streamable HTTP transport for multi-agent sessions (stdio remains the default):
73
+
74
+ ```bash
75
+ computer-mcp --http # http://127.0.0.1:8806/mcp
76
+ MCP_HTTP=1 computer-mcp # same via env
77
+ computer-mcp --http --port 9000 # override port
78
+ ```
79
+
80
+ - Health: `GET http://127.0.0.1:8806/health` → `{"status":"ok","name":"computer"}`
81
+ - MCP endpoint is also mounted on `computer-serve` at `/mcp`.
82
+
70
83
  **Available tools:**
71
84
  - `computer_run_task` — Run a full computer use task
72
85
  - `computer_screenshot` — Capture the screen
@@ -0,0 +1,15 @@
1
+ export declare const MCP_HTTP_PORT = 8806;
2
+ export declare const MCP_NAME = "computer";
3
+ export declare function isHttpMode(argv: string[]): boolean;
4
+ export declare function resolveHttpPort(argv: string[]): number;
5
+ export declare function healthPayload(): {
6
+ status: string;
7
+ name: string;
8
+ };
9
+ /** Handle /health and /mcp; return null if the request is unrelated. */
10
+ export declare function handleMcpHttpRequest(req: Request): Promise<Response | null>;
11
+ export declare function startMcpHttpServer(port: number): Promise<{
12
+ port: number;
13
+ stop: () => void;
14
+ }>;
15
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/mcp/http.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa,OAAO,CAAC;AAClC,eAAO,MAAM,QAAQ,aAAa,CAAC;AAEnC,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAElD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAiBtD;AAED,wBAAgB,aAAa,IAAI;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEhE;AAYD,wEAAwE;AACxE,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CASjF;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC,CAW7C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=http.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.test.d.ts","sourceRoot":"","sources":["../../src/mcp/http.test.ts"],"names":[],"mappings":""}
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env bun
2
- export {};
2
+ export { buildServer } from "./server.js";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";AA2BA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
package/dist/mcp/index.js CHANGED
@@ -17,9 +17,11 @@ var __export = (target, all) => {
17
17
  var __require = import.meta.require;
18
18
 
19
19
  // src/mcp/index.ts
20
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
21
20
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
21
 
22
+ // src/mcp/server.ts
23
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24
+
23
25
  // node_modules/zod/v3/external.js
24
26
  var exports_external = {};
25
27
  __export(exports_external, {
@@ -23888,240 +23890,322 @@ function getAccessibilityHelperPath() {
23888
23890
  throw new Error("Accessibility helper not found. Run `swiftc helpers/accessibility.swift -o helpers/accessibility -framework AppKit` from the project root.");
23889
23891
  }
23890
23892
 
23891
- // src/mcp/index.ts
23892
- var server = new McpServer({
23893
- name: "computer",
23894
- version: "0.1.0"
23895
- });
23896
- server.tool("computer_run_task", "Run a computer use task \u2014 the AI sees your screen and controls mouse/keyboard to complete it", {
23897
- task: exports_external.string().describe("Natural language description of what to do"),
23898
- provider: exports_external.enum(["anthropic", "openai"]).default("anthropic").describe("AI provider"),
23899
- model: exports_external.string().optional().describe("Specific model to use"),
23900
- max_steps: exports_external.number().default(50).describe("Maximum steps before stopping"),
23901
- save_screenshots: exports_external.boolean().default(false).describe("Save screenshots to disk"),
23902
- dry_run: exports_external.boolean().default(false).describe("Plan actions without executing them")
23903
- }, async (params) => {
23904
- const session = await runTask({
23905
- task: params.task,
23906
- provider: params.provider,
23907
- model: params.model,
23908
- maxSteps: params.max_steps,
23909
- saveScreenshots: params.save_screenshots,
23910
- dryRun: params.dry_run
23893
+ // src/mcp/server.ts
23894
+ function buildServer() {
23895
+ const server = new McpServer({
23896
+ name: "computer",
23897
+ version: "0.1.0"
23911
23898
  });
23912
- return {
23913
- content: [
23914
- {
23915
- type: "text",
23916
- text: JSON.stringify(session, null, 2)
23917
- }
23918
- ]
23919
- };
23920
- });
23921
- server.tool("computer_screenshot", "Capture a screenshot of the current screen", {
23922
- save_to: exports_external.string().optional().describe("Optional file path to save the screenshot")
23923
- }, async (params) => {
23924
- const ss = await captureScreenshot();
23925
- if (params.save_to) {
23926
- const dir = params.save_to.substring(0, params.save_to.lastIndexOf("/"));
23927
- const file = params.save_to.substring(params.save_to.lastIndexOf("/") + 1);
23928
- await saveScreenshotToFile(ss, dir, file);
23929
- }
23930
- return {
23931
- content: [
23932
- {
23933
- type: "image",
23934
- data: ss.base64,
23935
- mimeType: "image/png"
23936
- },
23937
- {
23938
- type: "text",
23939
- text: `Screen: ${ss.size.width}x${ss.size.height}`
23940
- }
23941
- ]
23942
- };
23943
- });
23944
- server.tool("computer_click", "Click at a specific screen coordinate", {
23945
- x: exports_external.number().describe("X coordinate"),
23946
- y: exports_external.number().describe("Y coordinate"),
23947
- button: exports_external.enum(["left", "right", "middle"]).default("left").describe("Mouse button"),
23948
- count: exports_external.number().default(1).describe("Click count (1=single, 2=double, 3=triple)")
23949
- }, async (params) => {
23950
- const result = await executeAction({
23951
- type: "click",
23952
- point: { x: params.x, y: params.y },
23953
- button: params.button,
23954
- count: params.count
23899
+ server.tool("computer_run_task", "Run a computer use task \u2014 the AI sees your screen and controls mouse/keyboard to complete it", {
23900
+ task: exports_external.string().describe("Natural language description of what to do"),
23901
+ provider: exports_external.enum(["anthropic", "openai"]).default("anthropic").describe("AI provider"),
23902
+ model: exports_external.string().optional().describe("Specific model to use"),
23903
+ max_steps: exports_external.number().default(50).describe("Maximum steps before stopping"),
23904
+ save_screenshots: exports_external.boolean().default(false).describe("Save screenshots to disk"),
23905
+ dry_run: exports_external.boolean().default(false).describe("Plan actions without executing them")
23906
+ }, async (params) => {
23907
+ const session = await runTask({
23908
+ task: params.task,
23909
+ provider: params.provider,
23910
+ model: params.model,
23911
+ maxSteps: params.max_steps,
23912
+ saveScreenshots: params.save_screenshots,
23913
+ dryRun: params.dry_run
23914
+ });
23915
+ return {
23916
+ content: [
23917
+ {
23918
+ type: "text",
23919
+ text: JSON.stringify(session, null, 2)
23920
+ }
23921
+ ]
23922
+ };
23923
+ });
23924
+ server.tool("computer_screenshot", "Capture a screenshot of the current screen", {
23925
+ save_to: exports_external.string().optional().describe("Optional file path to save the screenshot")
23926
+ }, async (params) => {
23927
+ const ss = await captureScreenshot();
23928
+ if (params.save_to) {
23929
+ const dir = params.save_to.substring(0, params.save_to.lastIndexOf("/"));
23930
+ const file = params.save_to.substring(params.save_to.lastIndexOf("/") + 1);
23931
+ await saveScreenshotToFile(ss, dir, file);
23932
+ }
23933
+ return {
23934
+ content: [
23935
+ {
23936
+ type: "image",
23937
+ data: ss.base64,
23938
+ mimeType: "image/png"
23939
+ },
23940
+ {
23941
+ type: "text",
23942
+ text: `Screen: ${ss.size.width}x${ss.size.height}`
23943
+ }
23944
+ ]
23945
+ };
23946
+ });
23947
+ server.tool("computer_click", "Click at a specific screen coordinate", {
23948
+ x: exports_external.number().describe("X coordinate"),
23949
+ y: exports_external.number().describe("Y coordinate"),
23950
+ button: exports_external.enum(["left", "right", "middle"]).default("left").describe("Mouse button"),
23951
+ count: exports_external.number().default(1).describe("Click count (1=single, 2=double, 3=triple)")
23952
+ }, async (params) => {
23953
+ const result = await executeAction({
23954
+ type: "click",
23955
+ point: { x: params.x, y: params.y },
23956
+ button: params.button,
23957
+ count: params.count
23958
+ });
23959
+ const content = [{ type: "text", text: result.success ? "Click executed" : `Click failed: ${result.error}` }];
23960
+ if (result.screenshot) {
23961
+ content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
23962
+ }
23963
+ return { content };
23964
+ });
23965
+ server.tool("computer_type", "Type text using the keyboard", {
23966
+ text: exports_external.string().describe("Text to type")
23967
+ }, async (params) => {
23968
+ const result = await executeAction({ type: "type", text: params.text });
23969
+ const content = [{ type: "text", text: result.success ? "Text typed" : `Type failed: ${result.error}` }];
23970
+ if (result.screenshot) {
23971
+ content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
23972
+ }
23973
+ return { content };
23974
+ });
23975
+ server.tool("computer_key", "Press a key or key combination (e.g. 'enter', 'cmd+c', 'ctrl+shift+a')", {
23976
+ keys: exports_external.string().describe("Key or combination to press")
23977
+ }, async (params) => {
23978
+ const result = await executeAction({ type: "key", keys: params.keys });
23979
+ const content = [{ type: "text", text: result.success ? "Key pressed" : `Key failed: ${result.error}` }];
23980
+ if (result.screenshot) {
23981
+ content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
23982
+ }
23983
+ return { content };
23984
+ });
23985
+ server.tool("computer_scroll", "Scroll at a specific position", {
23986
+ x: exports_external.number().describe("X coordinate"),
23987
+ y: exports_external.number().describe("Y coordinate"),
23988
+ direction: exports_external.enum(["up", "down"]).describe("Scroll direction"),
23989
+ amount: exports_external.number().default(3).describe("Scroll amount")
23990
+ }, async (params) => {
23991
+ const dy = params.direction === "down" ? params.amount : -params.amount;
23992
+ const result = await executeAction({
23993
+ type: "scroll",
23994
+ point: { x: params.x, y: params.y },
23995
+ deltaX: 0,
23996
+ deltaY: dy
23997
+ });
23998
+ const content = [{ type: "text", text: result.success ? "Scrolled" : `Scroll failed: ${result.error}` }];
23999
+ if (result.screenshot) {
24000
+ content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
24001
+ }
24002
+ return { content };
24003
+ });
24004
+ server.tool("computer_mouse_move", "Move the mouse to a position", {
24005
+ x: exports_external.number().describe("X coordinate"),
24006
+ y: exports_external.number().describe("Y coordinate")
24007
+ }, async (params) => {
24008
+ const result = await executeAction({ type: "mouse_move", point: { x: params.x, y: params.y } });
24009
+ return { content: [{ type: "text", text: result.success ? "Mouse moved" : `Move failed: ${result.error}` }] };
24010
+ });
24011
+ server.tool("computer_open_url", "Open a URL in the default browser", {
24012
+ url: exports_external.string().describe("URL to open")
24013
+ }, async (params) => {
24014
+ const result = await executeAction({ type: "open_url", url: params.url });
24015
+ const content = [{ type: "text", text: result.success ? "URL opened" : `Open failed: ${result.error}` }];
24016
+ if (result.screenshot) {
24017
+ content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
24018
+ }
24019
+ return { content };
24020
+ });
24021
+ server.tool("computer_open_app", "Open a macOS application by name", {
24022
+ name: exports_external.string().describe("Application name (e.g. 'Safari', 'Terminal', 'Slack')")
24023
+ }, async (params) => {
24024
+ const result = await executeAction({ type: "open_app", name: params.name });
24025
+ const content = [{ type: "text", text: result.success ? `Opened ${params.name}` : `Open failed: ${result.error}` }];
24026
+ if (result.screenshot) {
24027
+ content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
24028
+ }
24029
+ return { content };
24030
+ });
24031
+ server.tool("computer_screen_size", "Get the current screen resolution", {}, async () => {
24032
+ const size = await getScreenSize();
24033
+ return { content: [{ type: "text", text: `${size.width}x${size.height}` }] };
24034
+ });
24035
+ server.tool("computer_list_sessions", "List past computer use sessions", {
24036
+ limit: exports_external.number().default(20).describe("Max sessions to return"),
24037
+ status: exports_external.enum(["running", "paused", "completed", "failed", "cancelled"]).optional().describe("Filter by status")
24038
+ }, async (params) => {
24039
+ const sessions = listSessions({ limit: params.limit, status: params.status });
24040
+ return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
24041
+ });
24042
+ server.tool("computer_get_session", "Get details of a specific session including action log", {
24043
+ id: exports_external.string().describe("Session ID")
24044
+ }, async (params) => {
24045
+ const session = getSession(params.id);
24046
+ if (!session)
24047
+ return { content: [{ type: "text", text: "Session not found" }] };
24048
+ const logs = getActionLogs(params.id);
24049
+ return {
24050
+ content: [
24051
+ { type: "text", text: JSON.stringify({ session, action_logs: logs }, null, 2) }
24052
+ ]
24053
+ };
24054
+ });
24055
+ server.tool("computer_delete_session", "Delete a session and its action logs", {
24056
+ id: exports_external.string().describe("Session ID")
24057
+ }, async (params) => {
24058
+ const deleted = deleteSession(params.id);
24059
+ return { content: [{ type: "text", text: deleted ? "Session deleted" : "Session not found" }] };
24060
+ });
24061
+ server.tool("computer_search", "Full-text search across sessions (by task) and action logs (by reasoning)", {
24062
+ query: exports_external.string().describe("Search query"),
24063
+ scope: exports_external.enum(["sessions", "actions", "both"]).default("both").describe("Where to search"),
24064
+ limit: exports_external.number().default(20).describe("Max results")
24065
+ }, async (params) => {
24066
+ const results = {};
24067
+ if (params.scope === "sessions" || params.scope === "both") {
24068
+ results.sessions = searchSessions(params.query, params.limit);
24069
+ }
24070
+ if (params.scope === "actions" || params.scope === "both") {
24071
+ results.action_logs = searchActionLogs(params.query, params.limit);
24072
+ }
24073
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
24074
+ });
24075
+ server.tool("computer_stats", "Get usage statistics for computer use", {}, async () => {
24076
+ const stats = getStats();
24077
+ return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
24078
+ });
24079
+ server.tool("computer_accessibility", "Query the macOS accessibility tree \u2014 get structured UI elements (buttons, fields, labels) with positions. Much more precise than pixel-guessing from screenshots.", {
24080
+ app: exports_external.string().optional().describe("App name to query (default: frontmost)"),
24081
+ focused_only: exports_external.boolean().default(false).describe("Only get focused element's subtree"),
24082
+ depth: exports_external.number().default(3).describe("Max tree traversal depth"),
24083
+ format: exports_external.enum(["json", "summary"]).default("summary").describe("Output format")
24084
+ }, async (params) => {
24085
+ try {
24086
+ const elements = await queryAccessibilityTree({
24087
+ app: params.app,
24088
+ focusedOnly: params.focused_only,
24089
+ depth: params.depth
24090
+ });
24091
+ const text = params.format === "json" ? JSON.stringify(elements, null, 2) : summarizeAccessibilityTree(elements);
24092
+ return { content: [{ type: "text", text }] };
24093
+ } catch (err) {
24094
+ return { content: [{ type: "text", text: `Accessibility query failed: ${err instanceof Error ? err.message : err}` }] };
24095
+ }
24096
+ });
24097
+ server.tool("computer_register_agent", "Register an agent for multi-agent coordination", {
24098
+ name: exports_external.string().describe("Agent name"),
24099
+ description: exports_external.string().optional().describe("Agent description"),
24100
+ capabilities: exports_external.array(exports_external.string()).optional().describe("Agent capabilities")
24101
+ }, async (params) => {
24102
+ const agent = registerAgent(params);
24103
+ return { content: [{ type: "text", text: JSON.stringify(agent, null, 2) }] };
24104
+ });
24105
+ server.tool("computer_heartbeat", "Send a heartbeat to mark an agent as active", {
24106
+ agent_id: exports_external.string().describe("Agent ID")
24107
+ }, async (params) => {
24108
+ const ok = heartbeat(params.agent_id);
24109
+ return { content: [{ type: "text", text: ok ? "Heartbeat received" : "Agent not found" }] };
24110
+ });
24111
+ server.tool("computer_set_focus", "Set what an agent is currently focused on", {
24112
+ agent_id: exports_external.string().describe("Agent ID"),
24113
+ focus: exports_external.string().describe("Current focus description")
24114
+ }, async (params) => {
24115
+ const ok = setFocus(params.agent_id, params.focus);
24116
+ return { content: [{ type: "text", text: ok ? "Focus updated" : "Agent not found" }] };
24117
+ });
24118
+ server.tool("computer_list_agents", "List all registered agents", {}, async () => {
24119
+ const agents = listAgents();
24120
+ return { content: [{ type: "text", text: JSON.stringify(agents, null, 2) }] };
23955
24121
  });
23956
- const content = [{ type: "text", text: result.success ? "Click executed" : `Click failed: ${result.error}` }];
23957
- if (result.screenshot) {
23958
- content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
24122
+ registerCloudTools(server, "computer");
24123
+ return server;
24124
+ }
24125
+
24126
+ // src/mcp/http.ts
24127
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
24128
+ var MCP_HTTP_PORT = 8806;
24129
+ var MCP_NAME = "computer";
24130
+ function isHttpMode(argv) {
24131
+ return argv.includes("--http") || process.env.MCP_HTTP === "1";
24132
+ }
24133
+ function resolveHttpPort(argv) {
24134
+ const eqArg = argv.find((a) => a.startsWith("--port="));
24135
+ if (eqArg) {
24136
+ const parsed = Number.parseInt(eqArg.slice("--port=".length), 10);
24137
+ if (!Number.isNaN(parsed))
24138
+ return parsed;
23959
24139
  }
23960
- return { content };
23961
- });
23962
- server.tool("computer_type", "Type text using the keyboard", {
23963
- text: exports_external.string().describe("Text to type")
23964
- }, async (params) => {
23965
- const result = await executeAction({ type: "type", text: params.text });
23966
- const content = [{ type: "text", text: result.success ? "Text typed" : `Type failed: ${result.error}` }];
23967
- if (result.screenshot) {
23968
- content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
23969
- }
23970
- return { content };
23971
- });
23972
- server.tool("computer_key", "Press a key or key combination (e.g. 'enter', 'cmd+c', 'ctrl+shift+a')", {
23973
- keys: exports_external.string().describe("Key or combination to press")
23974
- }, async (params) => {
23975
- const result = await executeAction({ type: "key", keys: params.keys });
23976
- const content = [{ type: "text", text: result.success ? "Key pressed" : `Key failed: ${result.error}` }];
23977
- if (result.screenshot) {
23978
- content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
23979
- }
23980
- return { content };
23981
- });
23982
- server.tool("computer_scroll", "Scroll at a specific position", {
23983
- x: exports_external.number().describe("X coordinate"),
23984
- y: exports_external.number().describe("Y coordinate"),
23985
- direction: exports_external.enum(["up", "down"]).describe("Scroll direction"),
23986
- amount: exports_external.number().default(3).describe("Scroll amount")
23987
- }, async (params) => {
23988
- const dy = params.direction === "down" ? params.amount : -params.amount;
23989
- const result = await executeAction({
23990
- type: "scroll",
23991
- point: { x: params.x, y: params.y },
23992
- deltaX: 0,
23993
- deltaY: dy
24140
+ const idx = argv.indexOf("--port");
24141
+ if (idx >= 0) {
24142
+ const parsed = Number.parseInt(argv[idx + 1] ?? "", 10);
24143
+ if (!Number.isNaN(parsed))
24144
+ return parsed;
24145
+ }
24146
+ const envPort = process.env.MCP_HTTP_PORT;
24147
+ if (envPort) {
24148
+ const parsed = Number.parseInt(envPort, 10);
24149
+ if (!Number.isNaN(parsed))
24150
+ return parsed;
24151
+ }
24152
+ return MCP_HTTP_PORT;
24153
+ }
24154
+ function healthPayload() {
24155
+ return { status: "ok", name: MCP_NAME };
24156
+ }
24157
+ async function handleMcpRequest(req) {
24158
+ const server = buildServer();
24159
+ const transport = new WebStandardStreamableHTTPServerTransport({
24160
+ sessionIdGenerator: undefined,
24161
+ enableJsonResponse: true
23994
24162
  });
23995
- const content = [{ type: "text", text: result.success ? "Scrolled" : `Scroll failed: ${result.error}` }];
23996
- if (result.screenshot) {
23997
- content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
24163
+ await server.connect(transport);
24164
+ return transport.handleRequest(req);
24165
+ }
24166
+ async function handleMcpHttpRequest(req) {
24167
+ const url = new URL(req.url);
24168
+ if (url.pathname === "/health" && req.method === "GET") {
24169
+ return Response.json(healthPayload());
23998
24170
  }
23999
- return { content };
24000
- });
24001
- server.tool("computer_mouse_move", "Move the mouse to a position", {
24002
- x: exports_external.number().describe("X coordinate"),
24003
- y: exports_external.number().describe("Y coordinate")
24004
- }, async (params) => {
24005
- const result = await executeAction({ type: "mouse_move", point: { x: params.x, y: params.y } });
24006
- return { content: [{ type: "text", text: result.success ? "Mouse moved" : `Move failed: ${result.error}` }] };
24007
- });
24008
- server.tool("computer_open_url", "Open a URL in the default browser", {
24009
- url: exports_external.string().describe("URL to open")
24010
- }, async (params) => {
24011
- const result = await executeAction({ type: "open_url", url: params.url });
24012
- const content = [{ type: "text", text: result.success ? "URL opened" : `Open failed: ${result.error}` }];
24013
- if (result.screenshot) {
24014
- content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
24015
- }
24016
- return { content };
24017
- });
24018
- server.tool("computer_open_app", "Open a macOS application by name", {
24019
- name: exports_external.string().describe("Application name (e.g. 'Safari', 'Terminal', 'Slack')")
24020
- }, async (params) => {
24021
- const result = await executeAction({ type: "open_app", name: params.name });
24022
- const content = [{ type: "text", text: result.success ? `Opened ${params.name}` : `Open failed: ${result.error}` }];
24023
- if (result.screenshot) {
24024
- content.push({ type: "image", data: result.screenshot.base64, mimeType: "image/png" });
24025
- }
24026
- return { content };
24027
- });
24028
- server.tool("computer_screen_size", "Get the current screen resolution", {}, async () => {
24029
- const size = await getScreenSize();
24030
- return { content: [{ type: "text", text: `${size.width}x${size.height}` }] };
24031
- });
24032
- server.tool("computer_list_sessions", "List past computer use sessions", {
24033
- limit: exports_external.number().default(20).describe("Max sessions to return"),
24034
- status: exports_external.enum(["running", "paused", "completed", "failed", "cancelled"]).optional().describe("Filter by status")
24035
- }, async (params) => {
24036
- const sessions = listSessions({ limit: params.limit, status: params.status });
24037
- return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
24038
- });
24039
- server.tool("computer_get_session", "Get details of a specific session including action log", {
24040
- id: exports_external.string().describe("Session ID")
24041
- }, async (params) => {
24042
- const session = getSession(params.id);
24043
- if (!session)
24044
- return { content: [{ type: "text", text: "Session not found" }] };
24045
- const logs = getActionLogs(params.id);
24046
- return {
24047
- content: [
24048
- { type: "text", text: JSON.stringify({ session, action_logs: logs }, null, 2) }
24049
- ]
24050
- };
24051
- });
24052
- server.tool("computer_delete_session", "Delete a session and its action logs", {
24053
- id: exports_external.string().describe("Session ID")
24054
- }, async (params) => {
24055
- const deleted = deleteSession(params.id);
24056
- return { content: [{ type: "text", text: deleted ? "Session deleted" : "Session not found" }] };
24057
- });
24058
- server.tool("computer_search", "Full-text search across sessions (by task) and action logs (by reasoning)", {
24059
- query: exports_external.string().describe("Search query"),
24060
- scope: exports_external.enum(["sessions", "actions", "both"]).default("both").describe("Where to search"),
24061
- limit: exports_external.number().default(20).describe("Max results")
24062
- }, async (params) => {
24063
- const results = {};
24064
- if (params.scope === "sessions" || params.scope === "both") {
24065
- results.sessions = searchSessions(params.query, params.limit);
24066
- }
24067
- if (params.scope === "actions" || params.scope === "both") {
24068
- results.action_logs = searchActionLogs(params.query, params.limit);
24069
- }
24070
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
24071
- });
24072
- server.tool("computer_stats", "Get usage statistics for computer use", {}, async () => {
24073
- const stats = getStats();
24074
- return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
24075
- });
24076
- server.tool("computer_accessibility", "Query the macOS accessibility tree \u2014 get structured UI elements (buttons, fields, labels) with positions. Much more precise than pixel-guessing from screenshots.", {
24077
- app: exports_external.string().optional().describe("App name to query (default: frontmost)"),
24078
- focused_only: exports_external.boolean().default(false).describe("Only get focused element's subtree"),
24079
- depth: exports_external.number().default(3).describe("Max tree traversal depth"),
24080
- format: exports_external.enum(["json", "summary"]).default("summary").describe("Output format")
24081
- }, async (params) => {
24082
- try {
24083
- const elements = await queryAccessibilityTree({
24084
- app: params.app,
24085
- focusedOnly: params.focused_only,
24086
- depth: params.depth
24087
- });
24088
- const text = params.format === "json" ? JSON.stringify(elements, null, 2) : summarizeAccessibilityTree(elements);
24089
- return { content: [{ type: "text", text }] };
24090
- } catch (err) {
24091
- return { content: [{ type: "text", text: `Accessibility query failed: ${err instanceof Error ? err.message : err}` }] };
24171
+ if (url.pathname === "/mcp") {
24172
+ return handleMcpRequest(req);
24092
24173
  }
24093
- });
24094
- server.tool("computer_register_agent", "Register an agent for multi-agent coordination", {
24095
- name: exports_external.string().describe("Agent name"),
24096
- description: exports_external.string().optional().describe("Agent description"),
24097
- capabilities: exports_external.array(exports_external.string()).optional().describe("Agent capabilities")
24098
- }, async (params) => {
24099
- const agent = registerAgent(params);
24100
- return { content: [{ type: "text", text: JSON.stringify(agent, null, 2) }] };
24101
- });
24102
- server.tool("computer_heartbeat", "Send a heartbeat to mark an agent as active", {
24103
- agent_id: exports_external.string().describe("Agent ID")
24104
- }, async (params) => {
24105
- const ok = heartbeat(params.agent_id);
24106
- return { content: [{ type: "text", text: ok ? "Heartbeat received" : "Agent not found" }] };
24107
- });
24108
- server.tool("computer_set_focus", "Set what an agent is currently focused on", {
24109
- agent_id: exports_external.string().describe("Agent ID"),
24110
- focus: exports_external.string().describe("Current focus description")
24111
- }, async (params) => {
24112
- const ok = setFocus(params.agent_id, params.focus);
24113
- return { content: [{ type: "text", text: ok ? "Focus updated" : "Agent not found" }] };
24114
- });
24115
- server.tool("computer_list_agents", "List all registered agents", {}, async () => {
24116
- const agents = listAgents();
24117
- return { content: [{ type: "text", text: JSON.stringify(agents, null, 2) }] };
24118
- });
24119
- registerCloudTools(server, "computer");
24174
+ return null;
24175
+ }
24176
+ async function startMcpHttpServer(port) {
24177
+ const httpServer = Bun.serve({
24178
+ hostname: "127.0.0.1",
24179
+ port,
24180
+ async fetch(req) {
24181
+ const handled = await handleMcpHttpRequest(req);
24182
+ if (handled)
24183
+ return handled;
24184
+ return Response.json({ error: "Not found" }, { status: 404 });
24185
+ }
24186
+ });
24187
+ return { port: httpServer.port, stop: () => httpServer.stop() };
24188
+ }
24189
+
24190
+ // src/mcp/index.ts
24120
24191
  async function main() {
24192
+ const argv = process.argv.slice(2);
24193
+ if (isHttpMode(argv)) {
24194
+ const port = resolveHttpPort(argv);
24195
+ const { port: boundPort } = await startMcpHttpServer(port);
24196
+ console.error(`computer-mcp HTTP listening on http://127.0.0.1:${boundPort}/mcp`);
24197
+ return;
24198
+ }
24199
+ const server = buildServer();
24121
24200
  const transport = new StdioServerTransport;
24122
24201
  await server.connect(transport);
24123
24202
  }
24124
- main().catch((err) => {
24125
- console.error("MCP server error:", err);
24126
- process.exit(1);
24127
- });
24203
+ if (import.meta.main) {
24204
+ main().catch((err) => {
24205
+ console.error("MCP server error:", err);
24206
+ process.exit(1);
24207
+ });
24208
+ }
24209
+ export {
24210
+ buildServer
24211
+ };