@agentrq/acp-gateway 0.1.0
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/README.md +180 -0
- package/dist/__tests__/acpClient.test.js +258 -0
- package/dist/__tests__/acpClient.test.js.map +1 -0
- package/dist/__tests__/config.test.js +80 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/index.test.js +10 -0
- package/dist/__tests__/index.test.js.map +1 -0
- package/dist/__tests__/mcpClient.test.js +234 -0
- package/dist/__tests__/mcpClient.test.js.map +1 -0
- package/dist/acpClient.js +118 -0
- package/dist/acpClient.js.map +1 -0
- package/dist/config.js +58 -0
- package/dist/config.js.map +1 -0
- package/dist/index.js +141 -0
- package/dist/index.js.map +1 -0
- package/dist/mcpClient.js +137 -0
- package/dist/mcpClient.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# @agentrq/acp-gateway
|
|
2
|
+
|
|
3
|
+
ACP+MCP Bridge brings Experimentation Claude Notification Channels feature to all agents that supports Agent Client Protocol.
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> **Pre-Alpha**: This project is in early development. APIs, configurations, and behaviors are subject to change without notice.
|
|
7
|
+
>
|
|
8
|
+
> **Note**: `claude/notifications` is an experimental feature of Claude Code. `@agentrq/acp-gateway` extends this same capability to any `--acp` compatible agent (e.g., Gemini CLI).
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
`@agentrq/acp-gateway` bridges the [Agent Client Protocol (ACP)](https://agentclientprotocol.com) with the [Model Context Protocol (MCP)](https://modelcontextprotocol.io) to connect ACP-compatible AI agents (e.g., Gemini) to an agentrq MCP server.
|
|
13
|
+
|
|
14
|
+
It automates task execution by:
|
|
15
|
+
|
|
16
|
+
1. Loading your workspace's `.mcp.json` configuration.
|
|
17
|
+
2. Connecting to the agentrq MCP server.
|
|
18
|
+
3. Spawning an ACP-compatible agent subprocess.
|
|
19
|
+
4. Bridging MCP notifications (tasks, permission requests) to ACP prompts and vice versa.
|
|
20
|
+
5. Providing file read/write capabilities through the ACP protocol.
|
|
21
|
+
6. Auto-reconnecting the MCP transport on disconnection.
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
- **Node.js** >= 24
|
|
26
|
+
- **npm** or **yarn**
|
|
27
|
+
- An [ACP-compatible agent](https://agentclientprotocol.com) (e.g., Gemini CLI)
|
|
28
|
+
- An [agentrq](https://github.com/agentrq/agentrq) workspace with an HTTP MCP server
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd acp-gateway
|
|
34
|
+
npm install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
To use globally:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install -g @agentrq/acp-gateway
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Quick Start
|
|
46
|
+
|
|
47
|
+
Run `acp-gateway` from your agentrq workspace root (the directory containing `.mcp.json`):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# From workspace root
|
|
51
|
+
acp-gateway -- gemini --acp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or with a custom command:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
acp-gateway -- your-acp-agent --flag1 --flag2
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Configuration
|
|
61
|
+
|
|
62
|
+
`acp-gateway` searches for `.mcp.json` starting in the current working directory and up to 3 parent directories.
|
|
63
|
+
|
|
64
|
+
Example `.mcp.json`:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"agentrq": {
|
|
70
|
+
"type": "http",
|
|
71
|
+
"url": "http://localhost:3000/mcp"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`acp-gateway` prefers servers with `agentrq` in the name; it falls back to the first HTTP server with a `url`.
|
|
78
|
+
|
|
79
|
+
## How It Works
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
┌─────────────┐ ACP (JSON-RPC) ┌──────────────────┐
|
|
83
|
+
│ ACP Agent │ ◄─────────────────────────► │ │
|
|
84
|
+
│ (Gemini) │ │ acp-gateway │
|
|
85
|
+
└─────────────┘ │ │
|
|
86
|
+
│ MCP Bridge │
|
|
87
|
+
│ │
|
|
88
|
+
┌─────────────────────────────────────────────┤ │
|
|
89
|
+
│ │ │
|
|
90
|
+
│ │ │
|
|
91
|
+
│ ▼ │
|
|
92
|
+
│ ┌──────────────────────────┐ │
|
|
93
|
+
│ │ agentrq MCP Server │ │
|
|
94
|
+
│ │ (HTTP / StreamableHTTP) │ │
|
|
95
|
+
│ └──────────────────────────┘ │
|
|
96
|
+
└────────────────────────────────────────────────────────────────┘
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Flow
|
|
100
|
+
|
|
101
|
+
1. **Config Loading** — Reads `.mcp.json` to find the agentrq MCP server.
|
|
102
|
+
2. **MCP Connection** — Establishes a `StreamableHTTPClientTransport` to the MCP server with automatic retry and reconnection.
|
|
103
|
+
3. **Agent Spawning** — Launches the specified ACP agent as a subprocess with stdio piping.
|
|
104
|
+
4. **ACP Handshake** — Initializes the ACP connection and creates a new session with the MCP server reference.
|
|
105
|
+
5. **Task Bridge** — The MCP server sends `notifications/claude/channel` for new tasks; `acp-gateway` forwards them to the ACP agent.
|
|
106
|
+
6. **Permission Bridge** — Permission requests from the ACP agent are forwarded to the MCP server; verdicts are sent back.
|
|
107
|
+
7. **Recursive Execution** — After each task completes, `acp-gateway` checks for the next pending task automatically.
|
|
108
|
+
|
|
109
|
+
### Key Components
|
|
110
|
+
|
|
111
|
+
| File | Description |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `src/index.ts` | Entry point; orchestrates config loading, MCP connection, agent spawning, and ACP session lifecycle. |
|
|
114
|
+
| `src/acpClient.ts` | Implements the ACP `Client` interface — routes permission requests, handles session updates, and provides file operations. |
|
|
115
|
+
| `src/mcpClient.ts` | `EventEmitter`-based MCP client with auto-reconnection, notification handling, and tool call dispatch. |
|
|
116
|
+
| `src/config.ts` | Parses `.mcp.json` from the current directory tree up to 3 levels deep. |
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
### Scripts
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Run in development mode
|
|
124
|
+
npm run dev
|
|
125
|
+
|
|
126
|
+
# Type-check
|
|
127
|
+
npm run typecheck
|
|
128
|
+
|
|
129
|
+
# Run tests
|
|
130
|
+
npm test
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Project Structure
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
acp-gateway/
|
|
137
|
+
├── src/
|
|
138
|
+
│ ├── acpClient.ts # ACP Client implementation
|
|
139
|
+
│ ├── config.ts # .mcp.json loader
|
|
140
|
+
│ ├── index.ts # Entry point & orchestrator
|
|
141
|
+
│ ├── mcpClient.ts # MCP Bridge with auto-reconnect
|
|
142
|
+
│ └── __tests__/ # Unit tests
|
|
143
|
+
├── package.json
|
|
144
|
+
└── tsconfig.json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Architecture Notes
|
|
148
|
+
|
|
149
|
+
- **Auto-reconnection**: The MCP transport auto-reconnects on disconnection with exponential backoff (1s → 30s max).
|
|
150
|
+
- **Notification-driven tasks**: The MCP server pushes task content via `notifications/claude/channel`; `acp-gateway` reacts immediately.
|
|
151
|
+
- **Permission flow**: ACP agent requests permission → `acp-gateway` forwards to MCP server → waits for verdict → resolves the ACP permission.
|
|
152
|
+
- **File I/O**: `readTextFile` / `writeTextFile` are proxied directly to the filesystem; paths are resolved relative to `process.cwd()`.
|
|
153
|
+
|
|
154
|
+
## Contributing
|
|
155
|
+
|
|
156
|
+
Contributions are welcome! Please feel free to submit pull requests or open issues.
|
|
157
|
+
|
|
158
|
+
1. Fork the repository
|
|
159
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
160
|
+
3. Commit your changes (`git commit -m 'Add: amazing feature'`)
|
|
161
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
162
|
+
5. Open a Pull Request
|
|
163
|
+
|
|
164
|
+
## Contributing License
|
|
165
|
+
|
|
166
|
+
By contributing to this project, you agree that your contributions will be licensed under the project's Apache License 2.0.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
Apache License 2.0
|
|
173
|
+
|
|
174
|
+
Copyright (c) 2026 Contextual, Inc.
|
|
175
|
+
|
|
176
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
177
|
+
|
|
178
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
179
|
+
|
|
180
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { AgentRQACPClient } from "../acpClient.js";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
// Mock dependencies
|
|
6
|
+
vi.mock("node:fs/promises");
|
|
7
|
+
vi.mock("node:path");
|
|
8
|
+
describe("AgentRQACPClient", () => {
|
|
9
|
+
let mcpBridge;
|
|
10
|
+
let client;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vi.clearAllMocks();
|
|
13
|
+
mcpBridge = {
|
|
14
|
+
getSessionId: vi.fn().mockReturnValue("test-session"),
|
|
15
|
+
sendNotification: vi.fn().mockResolvedValue(undefined),
|
|
16
|
+
on: vi.fn(),
|
|
17
|
+
off: vi.fn(),
|
|
18
|
+
};
|
|
19
|
+
client = new AgentRQACPClient(mcpBridge);
|
|
20
|
+
});
|
|
21
|
+
describe("requestPermission", () => {
|
|
22
|
+
it("should send a notification and wait for a verdict", async () => {
|
|
23
|
+
const params = {
|
|
24
|
+
toolCall: {
|
|
25
|
+
toolCallId: "req-123",
|
|
26
|
+
title: "Test Tool",
|
|
27
|
+
rawInput: { foo: "bar" },
|
|
28
|
+
},
|
|
29
|
+
options: [
|
|
30
|
+
{ optionId: "opt-1", kind: "allow", name: "Allow Once" },
|
|
31
|
+
{ optionId: "opt-2", kind: "deny", name: "Deny" },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
mcpBridge.on.mockImplementation((event, handler) => {
|
|
35
|
+
if (event === "verdict") {
|
|
36
|
+
setTimeout(() => handler({ requestId: "req-123", behavior: "allow" }), 10);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const response = await client.requestPermission(params);
|
|
40
|
+
expect(response.outcome.optionId).toBe("opt-1");
|
|
41
|
+
});
|
|
42
|
+
it("should auto-allow tools matching agentrq-<11 chars> pattern in title", async () => {
|
|
43
|
+
const params = {
|
|
44
|
+
toolCall: {
|
|
45
|
+
toolCallId: "req-123",
|
|
46
|
+
title: "updateTaskStatus (agentrq-0aleR6CbZBp MCP Server)",
|
|
47
|
+
},
|
|
48
|
+
options: [
|
|
49
|
+
{ optionId: "opt-1", kind: "allow", name: "Allow" },
|
|
50
|
+
{ optionId: "opt-2", kind: "deny", name: "Deny" },
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
const response = await client.requestPermission(params);
|
|
54
|
+
expect(mcpBridge.sendNotification).not.toHaveBeenCalled();
|
|
55
|
+
expect(response.outcome.outcome).toBe("selected");
|
|
56
|
+
expect(response.outcome.optionId).toBe("opt-1");
|
|
57
|
+
});
|
|
58
|
+
it("should handle missing tool title", async () => {
|
|
59
|
+
const consoleSpy = vi
|
|
60
|
+
.spyOn(console, "error")
|
|
61
|
+
.mockImplementation(() => { });
|
|
62
|
+
const params = {
|
|
63
|
+
toolCall: { toolCallId: "req-123" },
|
|
64
|
+
options: [{ optionId: "opt-1", kind: "allow", name: "Allow" }],
|
|
65
|
+
};
|
|
66
|
+
mcpBridge.on.mockImplementation((event, handler) => {
|
|
67
|
+
if (event === "verdict") {
|
|
68
|
+
setTimeout(() => handler({ requestId: "req-123", behavior: "allow" }), 10);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const response = await client.requestPermission(params);
|
|
72
|
+
// Permission matching still works based on behavior, independent of title presence
|
|
73
|
+
expect(response.outcome.optionId).toBe("opt-1");
|
|
74
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Unknown Tool"));
|
|
75
|
+
consoleSpy.mockRestore();
|
|
76
|
+
});
|
|
77
|
+
it("should handle missing rawInput and missing session ID", async () => {
|
|
78
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
79
|
+
mcpBridge.getSessionId.mockReturnValue(undefined);
|
|
80
|
+
const params = {
|
|
81
|
+
toolCall: { toolCallId: "req-123", title: "Test Tool" },
|
|
82
|
+
options: [{ optionId: "opt-1", kind: "allow", name: "Allow" }],
|
|
83
|
+
};
|
|
84
|
+
mcpBridge.on.mockImplementation((event, handler) => {
|
|
85
|
+
if (event === "verdict") {
|
|
86
|
+
setTimeout(() => handler({ requestId: "req-123", behavior: "allow" }), 10);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const response = await client.requestPermission(params);
|
|
90
|
+
expect(response.outcome.optionId).toBe("opt-1");
|
|
91
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Bridge Session ID: unknown"));
|
|
92
|
+
// Verify payload had empty object for rawInput
|
|
93
|
+
expect(mcpBridge.sendNotification).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({ input_preview: "{}" }));
|
|
94
|
+
consoleSpy.mockRestore();
|
|
95
|
+
});
|
|
96
|
+
it("should match options with 'yes' or 'approve'", async () => {
|
|
97
|
+
const params = {
|
|
98
|
+
toolCall: { toolCallId: "req-123" },
|
|
99
|
+
options: [
|
|
100
|
+
{ optionId: "opt-1", kind: "other", name: "Yes, proceed" },
|
|
101
|
+
{ optionId: "opt-2", kind: "other", name: "No" },
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
mcpBridge.on.mockImplementation((event, handler) => {
|
|
105
|
+
if (event === "verdict") {
|
|
106
|
+
setTimeout(() => handler({ requestId: "req-123", behavior: "allow" }), 10);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
const response = await client.requestPermission(params);
|
|
110
|
+
expect(response.outcome.optionId).toBe("opt-1");
|
|
111
|
+
});
|
|
112
|
+
it("should match options with 'deny' in the name", async () => {
|
|
113
|
+
const params = {
|
|
114
|
+
toolCall: { toolCallId: "req-123" },
|
|
115
|
+
options: [
|
|
116
|
+
{ optionId: "opt-1", kind: "allow", name: "Allow" },
|
|
117
|
+
{ optionId: "opt-2", kind: "other", name: "Deny this" },
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
mcpBridge.on.mockImplementation((event, handler) => {
|
|
121
|
+
if (event === "verdict") {
|
|
122
|
+
setTimeout(() => handler({ requestId: "req-123", behavior: "deny" }), 10);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const response = await client.requestPermission(params);
|
|
126
|
+
expect(response.outcome.optionId).toBe("opt-2");
|
|
127
|
+
});
|
|
128
|
+
it("should fall back to first option if 'deny' verdict has no specific match", async () => {
|
|
129
|
+
const params = {
|
|
130
|
+
toolCall: { toolCallId: "req-123" },
|
|
131
|
+
options: [
|
|
132
|
+
{ optionId: "opt-1", kind: "other", name: "Other 1" },
|
|
133
|
+
{ optionId: "opt-2", kind: "other", name: "Other 2" },
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
mcpBridge.on.mockImplementation((event, handler) => {
|
|
137
|
+
if (event === "verdict") {
|
|
138
|
+
setTimeout(() => handler({ requestId: "req-123", behavior: "deny" }), 10);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
const response = await client.requestPermission(params);
|
|
142
|
+
expect(response.outcome.optionId).toBe("opt-1");
|
|
143
|
+
});
|
|
144
|
+
it("should fall back to first option if no match", async () => {
|
|
145
|
+
const params = {
|
|
146
|
+
toolCall: { toolCallId: "req-123" },
|
|
147
|
+
options: [{ optionId: "opt-default", kind: "other", name: "Maybe" }],
|
|
148
|
+
};
|
|
149
|
+
mcpBridge.on.mockImplementation((event, handler) => {
|
|
150
|
+
if (event === "verdict") {
|
|
151
|
+
setTimeout(() => handler({ requestId: "req-123", behavior: "allow" }), 10);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
const response = await client.requestPermission(params);
|
|
155
|
+
expect(response.outcome.optionId).toBe("opt-default");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe("sessionUpdate", () => {
|
|
159
|
+
it("should write text chunks to stdout", async () => {
|
|
160
|
+
const writeSpy = vi
|
|
161
|
+
.spyOn(process.stdout, "write")
|
|
162
|
+
.mockImplementation(() => true);
|
|
163
|
+
const params = {
|
|
164
|
+
update: {
|
|
165
|
+
sessionUpdate: "agent_message_chunk",
|
|
166
|
+
content: { type: "text", text: "hello" },
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
await client.sessionUpdate(params);
|
|
170
|
+
expect(writeSpy).toHaveBeenCalledWith("hello");
|
|
171
|
+
writeSpy.mockRestore();
|
|
172
|
+
});
|
|
173
|
+
it("should ignore non-text chunks", async () => {
|
|
174
|
+
const writeSpy = vi
|
|
175
|
+
.spyOn(process.stdout, "write")
|
|
176
|
+
.mockImplementation(() => true);
|
|
177
|
+
const params = {
|
|
178
|
+
update: {
|
|
179
|
+
sessionUpdate: "agent_message_chunk",
|
|
180
|
+
content: { type: "other", text: "ignored" },
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
await client.sessionUpdate(params);
|
|
184
|
+
expect(writeSpy).not.toHaveBeenCalled();
|
|
185
|
+
writeSpy.mockRestore();
|
|
186
|
+
});
|
|
187
|
+
it("should log tool calls", async () => {
|
|
188
|
+
const consoleSpy = vi
|
|
189
|
+
.spyOn(console, "error")
|
|
190
|
+
.mockImplementation(() => { });
|
|
191
|
+
const params = {
|
|
192
|
+
update: {
|
|
193
|
+
sessionUpdate: "tool_call",
|
|
194
|
+
title: "test-tool",
|
|
195
|
+
status: "pending",
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
await client.sessionUpdate(params);
|
|
199
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Tool call: test-tool (pending)"));
|
|
200
|
+
consoleSpy.mockRestore();
|
|
201
|
+
});
|
|
202
|
+
it("should skip logging for auto-allowed tool calls", async () => {
|
|
203
|
+
const consoleSpy = vi
|
|
204
|
+
.spyOn(console, "error")
|
|
205
|
+
.mockImplementation(() => { });
|
|
206
|
+
const params = {
|
|
207
|
+
update: {
|
|
208
|
+
sessionUpdate: "tool_call",
|
|
209
|
+
title: "updateTaskStatus (agentrq-0aleR6CbZBp MCP Server)",
|
|
210
|
+
status: "pending",
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
await client.sessionUpdate(params);
|
|
214
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
215
|
+
consoleSpy.mockRestore();
|
|
216
|
+
});
|
|
217
|
+
it("should handle unknown update types", async () => {
|
|
218
|
+
const params = { update: { sessionUpdate: "unknown" } };
|
|
219
|
+
await expect(client.sessionUpdate(params)).resolves.toBeUndefined();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe("file operations", () => {
|
|
223
|
+
it("should read text files", async () => {
|
|
224
|
+
vi.mocked(path.resolve).mockReturnValue("/mock/path/file.txt");
|
|
225
|
+
vi.mocked(fs.readFile).mockResolvedValue("file content");
|
|
226
|
+
const response = await client.readTextFile({
|
|
227
|
+
path: "file.txt",
|
|
228
|
+
sessionId: "test-session",
|
|
229
|
+
});
|
|
230
|
+
expect(response.content).toBe("file content");
|
|
231
|
+
});
|
|
232
|
+
it("should throw error when reading file fails", async () => {
|
|
233
|
+
vi.mocked(fs.readFile).mockRejectedValue(new Error("read failed"));
|
|
234
|
+
await expect(client.readTextFile({ path: "fail.txt", sessionId: "test-session" })).rejects.toThrow("read failed");
|
|
235
|
+
});
|
|
236
|
+
it("should write text files", async () => {
|
|
237
|
+
vi.mocked(path.resolve).mockReturnValue("/mock/path/file.txt");
|
|
238
|
+
vi.mocked(path.dirname).mockReturnValue("/mock/path");
|
|
239
|
+
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
240
|
+
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
241
|
+
await client.writeTextFile({
|
|
242
|
+
path: "file.txt",
|
|
243
|
+
content: "new content",
|
|
244
|
+
sessionId: "test-session",
|
|
245
|
+
});
|
|
246
|
+
expect(fs.writeFile).toHaveBeenCalledWith("/mock/path/file.txt", "new content", "utf8");
|
|
247
|
+
});
|
|
248
|
+
it("should throw error when writing file fails", async () => {
|
|
249
|
+
vi.mocked(fs.writeFile).mockRejectedValue(new Error("write failed"));
|
|
250
|
+
await expect(client.writeTextFile({
|
|
251
|
+
path: "fail.txt",
|
|
252
|
+
content: "content",
|
|
253
|
+
sessionId: "test-session",
|
|
254
|
+
})).rejects.toThrow("write failed");
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
//# sourceMappingURL=acpClient.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acpClient.test.js","sourceRoot":"","sources":["../../src/__tests__/acpClient.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { loadMcpConfig, pickAgentrqServer } from "../config.js";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
vi.mock("node:fs");
|
|
6
|
+
vi.mock("node:path");
|
|
7
|
+
describe("config", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.clearAllMocks();
|
|
10
|
+
});
|
|
11
|
+
describe("loadMcpConfig", () => {
|
|
12
|
+
it("should parse .mcp.json and return server configs", () => {
|
|
13
|
+
vi.mocked(resolve).mockImplementation((...args) => args.join("/"));
|
|
14
|
+
vi.mocked(readFileSync).mockReturnValue(JSON.stringify({
|
|
15
|
+
mcpServers: {
|
|
16
|
+
"agentrq": {
|
|
17
|
+
type: "http",
|
|
18
|
+
url: "http://localhost:8080"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}));
|
|
22
|
+
const configs = loadMcpConfig("/dummy");
|
|
23
|
+
expect(configs).toHaveLength(1);
|
|
24
|
+
expect(configs[0]).toEqual({
|
|
25
|
+
name: "agentrq",
|
|
26
|
+
type: "http",
|
|
27
|
+
url: "http://localhost:8080",
|
|
28
|
+
args: undefined,
|
|
29
|
+
command: undefined,
|
|
30
|
+
env: undefined,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
it("should handle missing mcpServers in JSON", () => {
|
|
34
|
+
vi.mocked(resolve).mockImplementation((...args) => args.join("/"));
|
|
35
|
+
vi.mocked(readFileSync).mockReturnValue(JSON.stringify({}));
|
|
36
|
+
expect(() => loadMcpConfig("/dummy")).toThrow("Could not find .mcp.json");
|
|
37
|
+
});
|
|
38
|
+
it("should infer type based on url presence", () => {
|
|
39
|
+
vi.mocked(resolve).mockImplementation((...args) => args.join("/"));
|
|
40
|
+
vi.mocked(readFileSync).mockReturnValue(JSON.stringify({
|
|
41
|
+
mcpServers: {
|
|
42
|
+
"http-server": { url: "http://example.com" },
|
|
43
|
+
"stdio-server": { command: "node", args: ["server.js"] }
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
const configs = loadMcpConfig("/dummy");
|
|
47
|
+
expect(configs).toHaveLength(2);
|
|
48
|
+
expect(configs[0].type).toBe("http");
|
|
49
|
+
expect(configs[1].type).toBe("stdio");
|
|
50
|
+
});
|
|
51
|
+
it("should throw error if no .mcp.json is found", () => {
|
|
52
|
+
vi.mocked(readFileSync).mockImplementation(() => { throw new Error("not found"); });
|
|
53
|
+
expect(() => loadMcpConfig("/dummy")).toThrow("Could not find .mcp.json");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("pickAgentrqServer", () => {
|
|
57
|
+
it("should prefer server with agentrq in its name", () => {
|
|
58
|
+
const servers = [
|
|
59
|
+
{ name: "other", type: "http", url: "http://other" },
|
|
60
|
+
{ name: "my-agentrq-server", type: "http", url: "http://agentrq" }
|
|
61
|
+
];
|
|
62
|
+
const picked = pickAgentrqServer(servers);
|
|
63
|
+
expect(picked.name).toBe("my-agentrq-server");
|
|
64
|
+
});
|
|
65
|
+
it("should fall back to first HTTP server", () => {
|
|
66
|
+
const servers = [
|
|
67
|
+
{ name: "other", type: "http", url: "http://other" }
|
|
68
|
+
];
|
|
69
|
+
const picked = pickAgentrqServer(servers);
|
|
70
|
+
expect(picked.name).toBe("other");
|
|
71
|
+
});
|
|
72
|
+
it("should throw error if no HTTP server found", () => {
|
|
73
|
+
const servers = [
|
|
74
|
+
{ name: "stdio-server", type: "stdio", command: "ls" }
|
|
75
|
+
];
|
|
76
|
+
expect(() => pickAgentrqServer(servers)).toThrow("No HTTP MCP server found");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AACnB,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AAErB,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YAC1D,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,GAAG,IAAc,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7E,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC;gBACrD,UAAU,EAAE;oBACV,SAAS,EAAE;wBACT,IAAI,EAAE,MAAM;wBACZ,GAAG,EAAE,uBAAuB;qBAC7B;iBACF;aACF,CAAC,CAAC,CAAC;YAEJ,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACzB,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,MAAM;gBACZ,GAAG,EAAE,uBAAuB;gBAC5B,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,SAAS;gBAClB,GAAG,EAAE,SAAS;aACf,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,GAAG,IAAc,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7E,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;YAC5D,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,GAAG,IAAc,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7E,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC;gBACrD,UAAU,EAAE;oBACV,aAAa,EAAE,EAAE,GAAG,EAAE,oBAAoB,EAAE;oBAC5C,cAAc,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,WAAW,CAAC,EAAE;iBACzD;aACF,CAAC,CAAC,CAAC;YAEJ,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACpF,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,MAAM,OAAO,GAAG;gBACd,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,EAAE;gBACpD,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE;aAC5D,CAAC;YAET,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;YAC1C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,OAAO,GAAG;gBACd,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,EAAE;aAC9C,CAAC;YAET,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;YAC1C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACpD,MAAM,OAAO,GAAG;gBACd,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE;aAChD,CAAC;YAET,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;QAC/E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
// This is a minimal test for logic found in index.ts, if any can be exported.
|
|
3
|
+
// Since index.ts is mostly procedural, we'll just check if it's there for now.
|
|
4
|
+
describe("index", () => {
|
|
5
|
+
it("should have bridge logic", () => {
|
|
6
|
+
// This is more of a placeholder as index.ts is the main entry point
|
|
7
|
+
expect(true).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAkB,MAAM,QAAQ,CAAC;AAG9D,8EAA8E;AAC9E,+EAA+E;AAE/E,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;IACrB,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,oEAAoE;QACpE,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|