Skip to content

Commit f64456b

Browse files
author
James Karanja
committed
docs: add CONTRIBUTING.md, SECURITY.md, AGENTS.md
1 parent d5695d0 commit f64456b

3 files changed

Lines changed: 354 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# Using computer-use-mcp with AI Agents
2+
3+
This guide covers how to integrate `computer-use-mcp` into AI agent frameworks and agentic workflows.
4+
5+
## Quick setup for any agent
6+
7+
The server speaks standard MCP over stdio. Start it with:
8+
9+
```bash
10+
npx @zavora-ai/computer-use-mcp
11+
```
12+
13+
Any agent framework with MCP support can connect to it immediately.
14+
15+
## Claude (Anthropic)
16+
17+
### Claude Desktop
18+
19+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
20+
21+
```json
22+
{
23+
"mcpServers": {
24+
"computer-use": {
25+
"command": "npx",
26+
"args": ["-y", "@zavora-ai/computer-use-mcp"]
27+
}
28+
}
29+
}
30+
```
31+
32+
Restart Claude Desktop. Claude will automatically use the tools when asked to interact with your Mac.
33+
34+
**Example prompts:**
35+
- *"Take a screenshot and tell me what's on my screen"*
36+
- *"Open Safari, go to github.com, and find the trending repositories"*
37+
- *"Open TextEdit, write a short poem, and save it to the desktop"*
38+
39+
### Claude API (programmatic)
40+
41+
```typescript
42+
import Anthropic from '@anthropic-ai/sdk'
43+
import { createComputerUseServer } from '@zavora-ai/computer-use-mcp'
44+
import { connectInProcess } from '@zavora-ai/computer-use-mcp/client'
45+
46+
// Start the MCP server in-process
47+
const server = createComputerUseServer()
48+
const mcpClient = await connectInProcess(server)
49+
50+
// List available tools to pass to Claude
51+
const tools = await mcpClient.listTools()
52+
53+
const anthropic = new Anthropic()
54+
55+
// Agent loop
56+
async function runAgent(task: string) {
57+
const messages: any[] = [{ role: 'user', content: task }]
58+
59+
while (true) {
60+
const response = await anthropic.messages.create({
61+
model: 'claude-opus-4-5',
62+
max_tokens: 4096,
63+
tools: tools.map(t => ({
64+
name: t.name,
65+
description: t.description,
66+
input_schema: { type: 'object', properties: {} }
67+
})),
68+
messages,
69+
})
70+
71+
if (response.stop_reason === 'end_turn') break
72+
73+
// Execute tool calls
74+
const toolResults = []
75+
for (const block of response.content) {
76+
if (block.type === 'tool_use') {
77+
const result = await mcpClient.callTool(block.name, block.input as any)
78+
toolResults.push({
79+
type: 'tool_result',
80+
tool_use_id: block.id,
81+
content: result.content,
82+
})
83+
}
84+
}
85+
86+
messages.push({ role: 'assistant', content: response.content })
87+
if (toolResults.length) {
88+
messages.push({ role: 'user', content: toolResults })
89+
}
90+
}
91+
92+
await mcpClient.close()
93+
}
94+
95+
await runAgent('Open Calculator and compute 123 * 456')
96+
```
97+
98+
## OpenAI Agents SDK
99+
100+
```typescript
101+
import OpenAI from 'openai'
102+
import { createComputerUseServer } from '@zavora-ai/computer-use-mcp'
103+
import { connectInProcess } from '@zavora-ai/computer-use-mcp/client'
104+
105+
const server = createComputerUseServer()
106+
const mcpClient = await connectInProcess(server)
107+
const openai = new OpenAI()
108+
109+
// Wrap MCP tools as OpenAI function tools
110+
const tools = (await mcpClient.listTools()).map(t => ({
111+
type: 'function' as const,
112+
function: {
113+
name: t.name,
114+
description: t.description ?? '',
115+
parameters: { type: 'object', properties: {}, additionalProperties: true },
116+
},
117+
}))
118+
119+
async function runAgent(task: string) {
120+
const messages: any[] = [{ role: 'user', content: task }]
121+
122+
while (true) {
123+
const response = await openai.chat.completions.create({
124+
model: 'gpt-4o',
125+
messages,
126+
tools,
127+
tool_choice: 'auto',
128+
})
129+
130+
const msg = response.choices[0].message
131+
messages.push(msg)
132+
133+
if (!msg.tool_calls?.length) break
134+
135+
for (const call of msg.tool_calls) {
136+
const args = JSON.parse(call.function.arguments)
137+
const result = await mcpClient.callTool(call.function.name, args)
138+
messages.push({
139+
role: 'tool',
140+
tool_call_id: call.id,
141+
content: JSON.stringify(result.content),
142+
})
143+
}
144+
}
145+
146+
await mcpClient.close()
147+
}
148+
149+
await runAgent('Take a screenshot and describe what you see')
150+
```
151+
152+
## LangChain / LangGraph
153+
154+
```typescript
155+
import { ChatAnthropic } from '@langchain/anthropic'
156+
import { createComputerUseServer } from '@zavora-ai/computer-use-mcp'
157+
import { connectInProcess } from '@zavora-ai/computer-use-mcp/client'
158+
159+
const server = createComputerUseServer()
160+
const mcpClient = await connectInProcess(server)
161+
162+
// Wrap as LangChain tools
163+
import { DynamicStructuredTool } from '@langchain/core/tools'
164+
import { z } from 'zod'
165+
166+
const tools = (await mcpClient.listTools()).map(t =>
167+
new DynamicStructuredTool({
168+
name: t.name,
169+
description: t.description ?? '',
170+
schema: z.object({}).passthrough(),
171+
func: async (args) => {
172+
const result = await mcpClient.callTool(t.name, args)
173+
return result.content.map(c => c.type === 'text' ? c.text : '[image]').join('\n')
174+
},
175+
})
176+
)
177+
178+
const model = new ChatAnthropic({ model: 'claude-opus-4-5' }).bindTools(tools)
179+
// Use with LangGraph agent executor as normal
180+
```
181+
182+
## Best practices for agents
183+
184+
### Always specify `target_app`
185+
Agents should explicitly target the app they want to control to avoid sending keystrokes to the wrong window:
186+
187+
```typescript
188+
await client.type('Hello', 'com.apple.TextEdit')
189+
await client.key('command+s', 'com.apple.TextEdit')
190+
```
191+
192+
### Screenshot before acting
193+
Take a screenshot first to understand the current state before clicking or typing:
194+
195+
```typescript
196+
const shot = await client.screenshot()
197+
// Pass shot to the model to understand what's on screen
198+
// Then decide where to click
199+
```
200+
201+
### Use clipboard for long text
202+
For typing long content, use clipboard paste instead of `type` — it's faster and more reliable:
203+
204+
```typescript
205+
await client.writeClipboard(longText)
206+
await client.key('command+v', targetApp)
207+
```
208+
209+
### Handle `activated: false`
210+
When opening an app, check if it actually launched:
211+
212+
```typescript
213+
const result = await client.openApp('com.apple.Safari')
214+
const text = result.content.find(c => c.type === 'text')?.text ?? ''
215+
if (text.includes('activated: false')) {
216+
await client.wait(2) // give it more time
217+
}
218+
```
219+
220+
### Coordinate system
221+
Coordinates are in logical pixels (not physical pixels on Retina displays). Use `get_display_size` to get the screen dimensions before calculating click positions:
222+
223+
```typescript
224+
const size = await client.getDisplaySize()
225+
// size contains width, height, pixelWidth, pixelHeight, scaleFactor
226+
```

CONTRIBUTING.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Contributing to computer-use-mcp
2+
3+
Thank you for your interest in contributing!
4+
5+
## Getting started
6+
7+
```bash
8+
git clone https://github.com/zavora-ai/computer-use-mcp
9+
cd computer-use-mcp
10+
npm install
11+
npm run build
12+
npm run demo # verify everything works
13+
```
14+
15+
## Project structure
16+
17+
```
18+
src/ TypeScript source (server, session, client, native loader)
19+
native/src/ Rust NAPI module (mouse, keyboard, apps, display, screenshot)
20+
dist/ Compiled TypeScript output (generated, not committed)
21+
```
22+
23+
## Making changes
24+
25+
### TypeScript changes
26+
Edit files in `src/`, then:
27+
```bash
28+
npm run build:ts
29+
npm run demo
30+
```
31+
32+
### Rust changes
33+
Edit files in `native/src/`, then:
34+
```bash
35+
npm run build:native
36+
npm run demo
37+
```
38+
39+
### Full rebuild
40+
```bash
41+
npm run build
42+
```
43+
44+
## Code standards
45+
46+
- **Rust**: run `cargo fmt` and `cargo clippy` before committing — both must be clean
47+
- **TypeScript**: `strict: true` is enforced — no `any` except where unavoidable
48+
- All tool inputs must be validated in `session.ts` before reaching native code
49+
- New tools must be registered in `server.ts`, dispatched in `session.ts`, and typed in `client.ts`
50+
51+
## Testing
52+
53+
There is no automated test suite. Verify changes manually:
54+
55+
```bash
56+
npm run demo # Calculator: open, compute 42+58, clipboard, close
57+
npx tsx src/browser-test.ts # Safari: navigate to example.com and github.com
58+
```
59+
60+
Both must pass cleanly before submitting a PR.
61+
62+
## Pull requests
63+
64+
- Keep PRs focused — one feature or fix per PR
65+
- Update the README if you add or change a tool
66+
- Bump the version in `package.json` following semver if you change public API
67+
68+
## Reporting bugs
69+
70+
Open a GitHub issue with:
71+
1. macOS version (`sw_vers`)
72+
2. Node.js version (`node --version`)
73+
3. The exact error message
74+
4. Steps to reproduce
75+
76+
## License
77+
78+
By contributing, you agree your contributions will be licensed under the MIT License.

SECURITY.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Security Policy
2+
3+
## Supported versions
4+
5+
| Version | Supported |
6+
|---------|-----------|
7+
| 2.x | ✅ Yes |
8+
| < 2.0 | ❌ No |
9+
10+
## Reporting a vulnerability
11+
12+
**Please do not report security vulnerabilities through public GitHub issues.**
13+
14+
Email: **security@zavora.ai**
15+
16+
Include:
17+
- Description of the vulnerability
18+
- Steps to reproduce
19+
- Potential impact
20+
- Any suggested fix (optional)
21+
22+
You will receive a response within **48 hours**. We aim to release a fix within **7 days** of confirmation.
23+
24+
## Scope
25+
26+
This package has **full control of your Mac** when Accessibility permission is granted. The following are in scope:
27+
28+
- Privilege escalation via tool inputs
29+
- Symlink attacks on temp files
30+
- Shell injection via clipboard or text inputs
31+
- Bypassing input validation to crash the server
32+
- Memory safety issues in the Rust native module
33+
34+
## Out of scope
35+
36+
- Issues requiring physical access to the machine
37+
- Social engineering attacks
38+
- Vulnerabilities in dependencies (report those upstream)
39+
40+
## Security model
41+
42+
- All tool inputs are validated with Zod schemas at the MCP boundary and again in the session layer
43+
- No shell string interpolation — all subprocess calls use argument arrays
44+
- Screenshot temp files use `O_EXCL` exclusive creation with a monotonic counter to prevent symlink attacks
45+
- The `wait` tool is capped at 300 seconds
46+
- The server has no network listener — it communicates only over stdio
47+
48+
## Disclosure policy
49+
50+
We follow coordinated disclosure. We will credit researchers in the release notes unless they prefer to remain anonymous.

0 commit comments

Comments
 (0)