Skip to content

Commit b62aebc

Browse files
Merge pull request #1 from DeliciousBuding/release/v0.2.0
Release v0.2.0
2 parents 94bdc5f + c99e714 commit b62aebc

9 files changed

Lines changed: 140 additions & 14 deletions

File tree

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# Binaries
22
*.exe
33
bin/
4-
bridge
5-
codex-browser-bridge
4+
/bridge
5+
/codex-browser-bridge
66

77
# Test artifacts
88
test_*.txt
9+
cover.out
10+
*.coverprofile
911

1012
# IDE
1113
.idea/

CHANGELOG.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,30 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## [Unreleased]
5+
## [0.2.0] - 2026-05-16
66

77
### Added
8-
- Unit tests for `protocol`, `client`, `discovery`, and `mcp` packages
9-
- `NewMCPServerWithIO` for testable I/O injection
8+
- `codex_navigate_back` and `codex_navigate_forward` MCP tools (history navigation was already in the client; now exposed)
9+
- `codex_wait_for_load` MCP tool — polls `document.readyState` until `complete` or timeout
10+
- `codex_screenshot` now returns MCP `image` content so agents can view the screenshot directly (previously only base64 text)
11+
- `MCPServer.SetVersion` so the build version flows into the MCP `initialize` handshake (`serverInfo.version`)
12+
- Unit tests across `protocol`, `client`, `discovery`, and `mcp` packages
13+
- In-memory `net.Pipe` fake server for end-to-end RPC tests without a real Codex pipe
14+
- Concurrent `SendRequest` stress test under `-race`
15+
- Wire-format invariants for `executeCdp`, `claimUserTab`, history navigation, JS escaping, CUA event sequencing, DOM box-model math
16+
- MCP handler integration tests that exercise the full client → MCP path
17+
- `NewMCPServerWithIO` constructor for testable I/O injection
18+
- CI now runs `go test -race -cover`
1019

1120
### Fixed
1221
- `discovery.extractUUID` no longer truncates UUIDs containing hyphens
13-
- Better error messages on pipe-not-found and dial failures
22+
- Clearer error messages on pipe-not-found and dial failures
23+
- `Makefile install-local` now copies the `.exe` binary on Windows
24+
- Duplicate option numbering in README install sections
25+
26+
### Internal
27+
- `client.NewFromConn` for wrapping an existing `net.Conn` (used by tests)
28+
- `cover.out` and `*.coverprofile` added to `.gitignore`
1429

1530
## [0.1.0] - 2026-05-16
1631

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,13 @@ quit
235235

236236
### Navigation
237237

238-
| Tool | Description |
239-
| --------------------- | --------------------------------------------------- |
240-
| `codex_navigate` | Navigate a tab to a URL |
241-
| `codex_reload` | Reload a tab |
242-
| `codex_wait_for_load` | Poll `document.readyState` until `complete` |
238+
| Tool | Description |
239+
| -------------------------- | --------------------------------------------------- |
240+
| `codex_navigate` | Navigate a tab to a URL |
241+
| `codex_navigate_back` | Navigate a tab back one entry in its history |
242+
| `codex_navigate_forward` | Navigate a tab forward one entry in its history |
243+
| `codex_reload` | Reload a tab |
244+
| `codex_wait_for_load` | Poll `document.readyState` until `complete` |
243245

244246
### Page inspection
245247

README.zh-CN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ info ping try <method> [json] quit
218218
| 工具 | 说明 |
219219
|------|------|
220220
| `codex_navigate` | 导航到指定 URL |
221+
| `codex_navigate_back` | 历史记录后退一项 |
222+
| `codex_navigate_forward` | 历史记录前进一项 |
221223
| `codex_reload` | 重新加载标签页 |
222224
| `codex_wait_for_load` | 轮询 `document.readyState` 直到 `complete` |
223225

cmd/bridge/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func runMCP(pipeName string, logger *log.Logger) {
6565
logger.Println("Connected to Codex browser pipe")
6666

6767
srv := mcp.NewMCPServer(c)
68+
srv.SetVersion(version)
6869
if err := srv.Run(); err != nil {
6970
fmt.Fprintf(os.Stderr, "MCP server error: %v\n", err)
7071
os.Exit(1)

internal/mcp/handlers_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,72 @@ func TestHandleNavigate(t *testing.T) {
190190
}
191191
}
192192

193+
func TestHandleNavigateBack(t *testing.T) {
194+
srv, pipe, cleanup := newServerWithPipe(t, func(req protocol.Request) (interface{}, *protocol.ErrorObject) {
195+
if req.Method != "executeCdp" {
196+
return map[string]bool{"ok": true}, nil
197+
}
198+
params, _ := req.Params.(map[string]interface{})
199+
if params["method"] == "Page.getNavigationHistory" {
200+
return map[string]interface{}{
201+
"currentIndex": 1,
202+
"entries": []map[string]interface{}{
203+
{"id": 100, "url": "https://a.test"},
204+
{"id": 101, "url": "https://b.test"},
205+
},
206+
}, nil
207+
}
208+
return map[string]bool{"ok": true}, nil
209+
})
210+
defer cleanup()
211+
212+
out, ok := callTool(t, srv, "codex_navigate_back", map[string]interface{}{"tab_id": "5"})
213+
if !ok {
214+
t.Fatalf("handler errored: %s", out)
215+
}
216+
if !strings.Contains(out, "back") {
217+
t.Errorf("output should mention back navigation: %s", out)
218+
}
219+
220+
hasNavigate := false
221+
for _, m := range pipe.recordedMethods() {
222+
if m == "executeCdp" {
223+
hasNavigate = true
224+
}
225+
}
226+
if !hasNavigate {
227+
t.Errorf("expected executeCdp calls, got %v", pipe.recordedMethods())
228+
}
229+
}
230+
231+
func TestHandleNavigateForward(t *testing.T) {
232+
srv, _, cleanup := newServerWithPipe(t, func(req protocol.Request) (interface{}, *protocol.ErrorObject) {
233+
if req.Method != "executeCdp" {
234+
return map[string]bool{"ok": true}, nil
235+
}
236+
params, _ := req.Params.(map[string]interface{})
237+
if params["method"] == "Page.getNavigationHistory" {
238+
return map[string]interface{}{
239+
"currentIndex": 0,
240+
"entries": []map[string]interface{}{
241+
{"id": 100, "url": "https://a.test"},
242+
{"id": 101, "url": "https://b.test"},
243+
},
244+
}, nil
245+
}
246+
return map[string]bool{"ok": true}, nil
247+
})
248+
defer cleanup()
249+
250+
out, ok := callTool(t, srv, "codex_navigate_forward", map[string]interface{}{"tab_id": "5"})
251+
if !ok {
252+
t.Fatalf("handler errored: %s", out)
253+
}
254+
if !strings.Contains(out, "forward") {
255+
t.Errorf("output should mention forward navigation: %s", out)
256+
}
257+
}
258+
193259
func TestHandleScreenshot(t *testing.T) {
194260
srv, _, cleanup := newServerWithPipe(t, func(req protocol.Request) (interface{}, *protocol.ErrorObject) {
195261
if req.Method == "executeCdp" {

internal/mcp/server.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type MCPServer struct {
1717
toolMap map[string]Tool
1818
in io.Reader
1919
out io.Writer
20+
version string
2021
}
2122

2223
type Tool struct {
@@ -52,11 +53,20 @@ func NewMCPServerWithIO(c *client.Client, in io.Reader, out io.Writer) *MCPServe
5253
toolMap: make(map[string]Tool),
5354
in: in,
5455
out: out,
56+
version: "dev",
5557
}
5658
s.registerTools()
5759
return s
5860
}
5961

62+
// SetVersion overrides the version reported in the MCP initialize handshake.
63+
// main.go calls this with the ldflags-injected build version.
64+
func (s *MCPServer) SetVersion(v string) {
65+
if v != "" {
66+
s.version = v
67+
}
68+
}
69+
6070
func (s *MCPServer) registerTools() {
6171
s.tools = []Tool{
6272
// Tab management
@@ -80,6 +90,12 @@ func (s *MCPServer) registerTools() {
8090
{Name: "codex_reload", Description: "Reload a tab",
8191
InputSchema: schema(`{"type":"object","properties":{"tab_id":{"type":"string"}},"required":["tab_id"]}`),
8292
Handler: s.handleReload},
93+
{Name: "codex_navigate_back", Description: "Navigate a tab back one entry in its history",
94+
InputSchema: schema(`{"type":"object","properties":{"tab_id":{"type":"string"}},"required":["tab_id"]}`),
95+
Handler: s.handleNavigateBack},
96+
{Name: "codex_navigate_forward", Description: "Navigate a tab forward one entry in its history",
97+
InputSchema: schema(`{"type":"object","properties":{"tab_id":{"type":"string"}},"required":["tab_id"]}`),
98+
Handler: s.handleNavigateForward},
8399
{Name: "codex_wait_for_load", Description: "Poll document.readyState until it equals \"complete\" or timeout (ms) elapses. Useful after navigation on slow pages.",
84100
InputSchema: schema(`{"type":"object","properties":{"tab_id":{"type":"string"},"timeout_ms":{"type":"number","description":"Timeout in milliseconds. Defaults to 10000."}},"required":["tab_id"]}`),
85101
Handler: s.handleWaitForLoad},
@@ -182,7 +198,7 @@ func (s *MCPServer) handleMessage(req struct {
182198
s.writeResult(req.ID, map[string]interface{}{
183199
"protocolVersion": "2024-11-05",
184200
"capabilities": map[string]interface{}{"tools": map[string]interface{}{}},
185-
"serverInfo": map[string]interface{}{"name": "codex-browser-bridge", "version": "0.1.0"},
201+
"serverInfo": map[string]interface{}{"name": "codex-browser-bridge", "version": s.version},
186202
})
187203
case "tools/list":
188204
tools := make([]map[string]interface{}, len(s.tools))
@@ -324,6 +340,28 @@ func (s *MCPServer) handleReload(args json.RawMessage) ([]Content, error) {
324340
return textContent(fmt.Sprintf("Reloaded tab %s", p.TabID)), nil
325341
}
326342

343+
func (s *MCPServer) handleNavigateBack(args json.RawMessage) ([]Content, error) {
344+
var p struct {
345+
TabID string `json:"tab_id"`
346+
}
347+
json.Unmarshal(args, &p)
348+
if err := s.client.NavigateBack(p.TabID); err != nil {
349+
return nil, err
350+
}
351+
return textContent(fmt.Sprintf("Navigated tab %s back", p.TabID)), nil
352+
}
353+
354+
func (s *MCPServer) handleNavigateForward(args json.RawMessage) ([]Content, error) {
355+
var p struct {
356+
TabID string `json:"tab_id"`
357+
}
358+
json.Unmarshal(args, &p)
359+
if err := s.client.NavigateForward(p.TabID); err != nil {
360+
return nil, err
361+
}
362+
return textContent(fmt.Sprintf("Navigated tab %s forward", p.TabID)), nil
363+
}
364+
327365
func (s *MCPServer) handleWaitForLoad(args json.RawMessage) ([]Content, error) {
328366
var p struct {
329367
TabID string `json:"tab_id"`

internal/mcp/server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func decodeResponses(t *testing.T, out *bytes.Buffer) []map[string]interface{} {
3131

3232
func TestRegisteredToolCount(t *testing.T) {
3333
s, _ := newTestServer("")
34-
const want = 22
34+
const want = 24
3535
if len(s.tools) != want {
3636
t.Errorf("tool count = %d, want %d", len(s.tools), want)
3737
}

npm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@delicious233/codex-browser-bridge",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"private": false,
55
"description": "MCP server that exposes Codex Desktop's Chrome browser bridge for Claude Code and other agents.",
66
"bin": {

0 commit comments

Comments
 (0)