@testdriverai/agent 7.11.174-canary → 7.11.175-test
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/docs/_data/examples-manifest.json +40 -40
- package/docs/examples/assert.mdx +1 -1
- package/docs/examples/element-not-found.mdx +1 -1
- package/docs/examples/findall-coffee-icons.mdx +1 -1
- package/docs/examples/hover-image.mdx +1 -1
- package/docs/examples/hover-text-with-description.mdx +1 -1
- package/docs/examples/hover-text.mdx +1 -1
- package/docs/examples/installer.mdx +1 -1
- package/docs/examples/launch-vscode-linux.mdx +1 -1
- package/docs/examples/parse.mdx +1 -1
- package/docs/examples/press-keys.mdx +1 -1
- package/docs/examples/scroll-keyboard.mdx +1 -1
- package/docs/examples/scroll.mdx +1 -1
- package/docs/examples/type.mdx +1 -1
- package/mcp-server/dist/server.mjs +28 -35
- package/mcp-server/src/server.ts +33 -11
- package/package.json +1 -1
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
"$schema": "./examples-manifest.schema.json",
|
|
3
3
|
"examples": {
|
|
4
4
|
"assert.test.mjs": {
|
|
5
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
6
|
-
"lastUpdated": "2026-09-
|
|
5
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d71565c44978ce8964d",
|
|
6
|
+
"lastUpdated": "2026-09-23T00:29:37.098Z"
|
|
7
7
|
},
|
|
8
8
|
"drag-and-drop.test.mjs": {
|
|
9
9
|
"url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b42fc0ac3cc632a918b",
|
|
10
10
|
"lastUpdated": "2026-03-03T00:32:25.275Z"
|
|
11
11
|
},
|
|
12
12
|
"exec-pwsh.test.mjs": {
|
|
13
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
14
|
-
"lastUpdated": "2026-09-
|
|
13
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d5c825adccbbd9ca378",
|
|
14
|
+
"lastUpdated": "2026-09-23T00:29:58.695Z"
|
|
15
15
|
},
|
|
16
16
|
"match-image.test.mjs": {
|
|
17
17
|
"url": "https://console-test.testdriver.ai/runs/69c8738614b73310c7839412/69c8738c14b73310c783941d",
|
|
@@ -22,78 +22,78 @@
|
|
|
22
22
|
"lastUpdated": "2026-03-03T00:32:25.282Z"
|
|
23
23
|
},
|
|
24
24
|
"hover-text-with-description.test.mjs": {
|
|
25
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
26
|
-
"lastUpdated": "2026-09-
|
|
25
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d61825adccbbd9ca379",
|
|
26
|
+
"lastUpdated": "2026-09-23T00:30:02.507Z"
|
|
27
27
|
},
|
|
28
28
|
"windows-installer.test.mjs": {
|
|
29
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
30
|
-
"lastUpdated": "2026-09-
|
|
29
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d63825adccbbd9ca37a",
|
|
30
|
+
"lastUpdated": "2026-09-23T00:34:46.111Z"
|
|
31
31
|
},
|
|
32
32
|
"exec-output.test.mjs": {
|
|
33
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
34
|
-
"lastUpdated": "2026-09-
|
|
33
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d65825adccbbd9ca37b",
|
|
34
|
+
"lastUpdated": "2026-09-23T00:30:45.866Z"
|
|
35
35
|
},
|
|
36
36
|
"chrome-extension.test.mjs": {
|
|
37
37
|
"url": "https://console-test.testdriver.ai/runs/6a87988fb7cddebeab9bfbea/6a8798913062df0fb9b85592",
|
|
38
38
|
"lastUpdated": "2026-08-21T00:15:47.131Z"
|
|
39
39
|
},
|
|
40
40
|
"launch-vscode-linux.test.mjs": {
|
|
41
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
42
|
-
"lastUpdated": "2026-09-
|
|
41
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d5f5b31bd91beec444f",
|
|
42
|
+
"lastUpdated": "2026-09-23T00:30:39.661Z"
|
|
43
43
|
},
|
|
44
44
|
"hover-image.test.mjs": {
|
|
45
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
46
|
-
"lastUpdated": "2026-09-
|
|
45
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d675b31bd91beec4450",
|
|
46
|
+
"lastUpdated": "2026-09-23T00:29:27.303Z"
|
|
47
47
|
},
|
|
48
48
|
"installer.test.mjs": {
|
|
49
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
50
|
-
"lastUpdated": "2026-09-
|
|
49
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d6a825adccbbd9ca37c",
|
|
50
|
+
"lastUpdated": "2026-09-23T00:29:29.990Z"
|
|
51
51
|
},
|
|
52
52
|
"type.test.mjs": {
|
|
53
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
54
|
-
"lastUpdated": "2026-09-
|
|
53
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d6c825adccbbd9ca37e",
|
|
54
|
+
"lastUpdated": "2026-09-23T00:30:52.193Z"
|
|
55
55
|
},
|
|
56
56
|
"press-keys.test.mjs": {
|
|
57
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
58
|
-
"replayUrl": "https://console-test.testdriver.ai/replay/
|
|
59
|
-
"embedUrl": "https://console-test.testdriver.ai/replay/
|
|
60
|
-
"lastUpdated": "2026-09-
|
|
57
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d6f825adccbbd9ca37f",
|
|
58
|
+
"replayUrl": "https://console-test.testdriver.ai/replay/6ab31fe0825adccbbd9ca3b4?share=LuZwQKvHo7PpGpUp6Z9ovQ",
|
|
59
|
+
"embedUrl": "https://console-test.testdriver.ai/replay/6ab31fe0825adccbbd9ca3b4?share=LuZwQKvHo7PpGpUp6Z9ovQ&embed=true",
|
|
60
|
+
"lastUpdated": "2026-09-23T00:40:05.729Z"
|
|
61
61
|
},
|
|
62
62
|
"scroll-keyboard.test.mjs": {
|
|
63
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
64
|
-
"lastUpdated": "2026-09-
|
|
63
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d75565c44978ce8964f",
|
|
64
|
+
"lastUpdated": "2026-09-23T00:31:00.707Z"
|
|
65
65
|
},
|
|
66
66
|
"scroll.test.mjs": {
|
|
67
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
68
|
-
"lastUpdated": "2026-09-
|
|
67
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d7d825adccbbd9ca381",
|
|
68
|
+
"lastUpdated": "2026-09-23T00:30:29.129Z"
|
|
69
69
|
},
|
|
70
70
|
"scroll-until-image.test.mjs": {
|
|
71
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
72
|
-
"lastUpdated": "2026-09-
|
|
71
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d77565c44978ce89650",
|
|
72
|
+
"lastUpdated": "2026-09-23T00:29:44.052Z"
|
|
73
73
|
},
|
|
74
74
|
"prompt.test.mjs": {
|
|
75
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
76
|
-
"lastUpdated": "2026-09-
|
|
75
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d79565c44978ce89651",
|
|
76
|
+
"lastUpdated": "2026-09-23T00:31:04.765Z"
|
|
77
77
|
},
|
|
78
78
|
"focus-window.test.mjs": {
|
|
79
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
80
|
-
"lastUpdated": "2026-09-
|
|
79
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d7b825adccbbd9ca380",
|
|
80
|
+
"lastUpdated": "2026-09-23T00:31:06.563Z"
|
|
81
81
|
},
|
|
82
82
|
"captcha-api.test.mjs": {
|
|
83
83
|
"url": "https://console.testdriver.ai/runs/698f7df69e27ce1528d7d087/698f7fb0d3b320ad547d9d44",
|
|
84
84
|
"lastUpdated": "2026-02-13T19:55:05.951Z"
|
|
85
85
|
},
|
|
86
86
|
"element-not-found.test.mjs": {
|
|
87
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
88
|
-
"lastUpdated": "2026-09-
|
|
87
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d7f825adccbbd9ca382",
|
|
88
|
+
"lastUpdated": "2026-09-23T00:30:31.309Z"
|
|
89
89
|
},
|
|
90
90
|
"formatted-logging.test.mjs": {
|
|
91
91
|
"url": "https://console-test.testdriver.ai/runs/69c8738614b73310c7839412/69c873a714b73310c7839450",
|
|
92
92
|
"lastUpdated": "2026-03-29T00:36:10.628Z"
|
|
93
93
|
},
|
|
94
94
|
"hover-text.test.mjs": {
|
|
95
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
96
|
-
"lastUpdated": "2026-09-
|
|
95
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d825b31bd91beec4451",
|
|
96
|
+
"lastUpdated": "2026-09-23T00:30:33.432Z"
|
|
97
97
|
},
|
|
98
98
|
"no-provision.test.mjs": {
|
|
99
99
|
"url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b7706a177a05bccd1cf",
|
|
@@ -138,12 +138,12 @@
|
|
|
138
138
|
"lastUpdated": "2026-02-13T19:55:05.953Z"
|
|
139
139
|
},
|
|
140
140
|
"findall-coffee-icons.test.mjs": {
|
|
141
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
142
|
-
"lastUpdated": "2026-09-
|
|
141
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d73565c44978ce8964e",
|
|
142
|
+
"lastUpdated": "2026-09-23T00:29:39.384Z"
|
|
143
143
|
},
|
|
144
144
|
"parse.test.mjs": {
|
|
145
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
146
|
-
"lastUpdated": "2026-09-
|
|
145
|
+
"url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d84825adccbbd9ca383",
|
|
146
|
+
"lastUpdated": "2026-09-23T00:30:35.532Z"
|
|
147
147
|
},
|
|
148
148
|
"flake-diffthreshold-001.test.mjs": {
|
|
149
149
|
"url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62bcafc0ac3cc632a91aa",
|
package/docs/examples/assert.mdx
CHANGED
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* assert.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d71565c44978ce8964d/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* element-not-found.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d7f825adccbbd9ca382/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -18,7 +18,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
18
18
|
|
|
19
19
|
{/* findall-coffee-icons.test.mjs output */}
|
|
20
20
|
<iframe
|
|
21
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
21
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d73565c44978ce8964e/replay"
|
|
22
22
|
width="100%"
|
|
23
23
|
height="600"
|
|
24
24
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* hover-image.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d675b31bd91beec4450/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -18,7 +18,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
18
18
|
|
|
19
19
|
{/* hover-text-with-description.test.mjs output */}
|
|
20
20
|
<iframe
|
|
21
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
21
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d61825adccbbd9ca379/replay"
|
|
22
22
|
width="100%"
|
|
23
23
|
height="600"
|
|
24
24
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* hover-text.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d825b31bd91beec4451/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* installer.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d6a825adccbbd9ca37c/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* launch-vscode-linux.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d5f5b31bd91beec444f/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
package/docs/examples/parse.mdx
CHANGED
|
@@ -18,7 +18,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
18
18
|
|
|
19
19
|
{/* parse.test.mjs output */}
|
|
20
20
|
<iframe
|
|
21
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
21
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d84825adccbbd9ca383/replay"
|
|
22
22
|
width="100%"
|
|
23
23
|
height="600"
|
|
24
24
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* press-keys.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://console-test.testdriver.ai/replay/
|
|
15
|
+
src="https://console-test.testdriver.ai/replay/6ab31fe0825adccbbd9ca3b4?share=LuZwQKvHo7PpGpUp6Z9ovQ&embed=true"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* scroll-keyboard.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d75565c44978ce8964f/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
package/docs/examples/scroll.mdx
CHANGED
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* scroll.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d7d825adccbbd9ca381/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
package/docs/examples/type.mdx
CHANGED
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* type.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d6c825adccbbd9ca37e/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -1663,32 +1663,6 @@ function readJsonBody(req) {
|
|
|
1663
1663
|
req.on("error", () => resolve(undefined));
|
|
1664
1664
|
});
|
|
1665
1665
|
}
|
|
1666
|
-
/**
|
|
1667
|
-
* Serve the MCP server over Streamable HTTP with no authentication.
|
|
1668
|
-
*
|
|
1669
|
-
* This is what the eve agent (and any other Streamable-HTTP MCP client) connects
|
|
1670
|
-
* to. Each MCP session gets its OWN transport + server + isolated action context,
|
|
1671
|
-
* keyed by the `mcp-session-id` header:
|
|
1672
|
-
*
|
|
1673
|
-
* - An `initialize` request (no session id yet) mints a fresh connection: a new
|
|
1674
|
-
* isolated {@link core.CoreContext}, a server built over it, and a transport
|
|
1675
|
-
* whose generated session id we store. Every later request carrying that id
|
|
1676
|
-
* routes back to the same connection and runs inside its context via
|
|
1677
|
-
* {@link core.runInContext} — so one client's tool call can only ever touch
|
|
1678
|
-
* its own sandbox / element refs / image store. This is the fix for the
|
|
1679
|
-
* cross-session bleed: state is per-connection, not process-global.
|
|
1680
|
-
* - `DELETE` (or transport close) tears the connection down and drops it from
|
|
1681
|
-
* the map, so a disconnecting client frees its slot and its context is GC'd.
|
|
1682
|
-
*
|
|
1683
|
-
* No auth is applied here by design (the connection is expected to be local-only
|
|
1684
|
-
* or otherwise protected outside the MCP layer). Do not expose this on a public
|
|
1685
|
-
* network without putting a real authenticating proxy in front of it.
|
|
1686
|
-
*/
|
|
1687
|
-
/**
|
|
1688
|
-
* Exchange a verified Auth0 access token for the caller's native TestDriver team
|
|
1689
|
-
* API key via the backend, so each hosted session provisions under the right
|
|
1690
|
-
* team and quota. Returns null if the exchange fails (caller falls back / errors).
|
|
1691
|
-
*/
|
|
1692
1666
|
async function exchangeOAuthTokenForApiKey(token) {
|
|
1693
1667
|
const apiRoot = process.env.TD_API_ROOT || "https://api.testdriver.ai";
|
|
1694
1668
|
try {
|
|
@@ -1698,18 +1672,36 @@ async function exchangeOAuthTokenForApiKey(token) {
|
|
|
1698
1672
|
"content-type": "application/json",
|
|
1699
1673
|
authorization: `Bearer ${token}`,
|
|
1700
1674
|
},
|
|
1701
|
-
body
|
|
1675
|
+
// The backend reads `token` as a required body input (Sails machine
|
|
1676
|
+
// input), not from the Authorization header — omitting it 400s and was
|
|
1677
|
+
// being surfaced as a misleading `no_team`.
|
|
1678
|
+
body: JSON.stringify({ token }),
|
|
1702
1679
|
});
|
|
1703
1680
|
if (!resp.ok) {
|
|
1704
|
-
|
|
1705
|
-
|
|
1681
|
+
let error = "exchange_failed";
|
|
1682
|
+
let description = `Token exchange failed (HTTP ${resp.status})`;
|
|
1683
|
+
try {
|
|
1684
|
+
const body = (await resp.json());
|
|
1685
|
+
if (body.error)
|
|
1686
|
+
error = body.error;
|
|
1687
|
+
if (body.message || body.error_description)
|
|
1688
|
+
description = body.message || body.error_description || description;
|
|
1689
|
+
}
|
|
1690
|
+
catch {
|
|
1691
|
+
// non-JSON error body; keep the generic description
|
|
1692
|
+
}
|
|
1693
|
+
logger.warn("http: exchange-oauth-token failed", { status: resp.status, error, description });
|
|
1694
|
+
return { ok: false, status: resp.status, error, description };
|
|
1706
1695
|
}
|
|
1707
1696
|
const data = (await resp.json());
|
|
1708
|
-
|
|
1697
|
+
if (!data.apiKey) {
|
|
1698
|
+
return { ok: false, status: 403, error: "no_team", description: "No TestDriver team is linked to this account" };
|
|
1699
|
+
}
|
|
1700
|
+
return { ok: true, apiKey: data.apiKey };
|
|
1709
1701
|
}
|
|
1710
1702
|
catch (error) {
|
|
1711
1703
|
logger.error("http: exchange-oauth-token error", { error: String(error) });
|
|
1712
|
-
return
|
|
1704
|
+
return { ok: false, status: 502, error: "exchange_unreachable", description: "Could not reach the TestDriver API to exchange the token" };
|
|
1713
1705
|
}
|
|
1714
1706
|
}
|
|
1715
1707
|
// Fly.io machine that owns this process. When set (hosted, multi-machine), we
|
|
@@ -1793,12 +1785,13 @@ async function startHttpServer() {
|
|
|
1793
1785
|
return;
|
|
1794
1786
|
}
|
|
1795
1787
|
resolvedToken = auth.token;
|
|
1796
|
-
|
|
1797
|
-
if (!
|
|
1798
|
-
res.writeHead(
|
|
1799
|
-
res.end(JSON.stringify({ error:
|
|
1788
|
+
const exchange = await exchangeOAuthTokenForApiKey(auth.token);
|
|
1789
|
+
if (!exchange.ok) {
|
|
1790
|
+
res.writeHead(exchange.status, { "content-type": "application/json" });
|
|
1791
|
+
res.end(JSON.stringify({ error: exchange.error, error_description: exchange.description }));
|
|
1800
1792
|
return;
|
|
1801
1793
|
}
|
|
1794
|
+
resolvedApiKey = exchange.apiKey;
|
|
1802
1795
|
}
|
|
1803
1796
|
const sessionId = req.headers["mcp-session-id"];
|
|
1804
1797
|
const sid = Array.isArray(sessionId) ? sessionId[0] : sessionId;
|
package/mcp-server/src/server.ts
CHANGED
|
@@ -2059,9 +2059,15 @@ function readJsonBody(req: http.IncomingMessage): Promise<unknown> {
|
|
|
2059
2059
|
/**
|
|
2060
2060
|
* Exchange a verified Auth0 access token for the caller's native TestDriver team
|
|
2061
2061
|
* API key via the backend, so each hosted session provisions under the right
|
|
2062
|
-
* team and quota.
|
|
2062
|
+
* team and quota. On failure, returns the backend's error so the caller can
|
|
2063
|
+
* surface the real reason (invalid token vs genuinely no team) instead of a
|
|
2064
|
+
* catch-all `no_team`.
|
|
2063
2065
|
*/
|
|
2064
|
-
|
|
2066
|
+
type ExchangeResult =
|
|
2067
|
+
| { ok: true; apiKey: string }
|
|
2068
|
+
| { ok: false; status: number; error: string; description: string };
|
|
2069
|
+
|
|
2070
|
+
async function exchangeOAuthTokenForApiKey(token: string): Promise<ExchangeResult> {
|
|
2065
2071
|
const apiRoot = process.env.TD_API_ROOT || "https://api.testdriver.ai";
|
|
2066
2072
|
try {
|
|
2067
2073
|
const resp = await fetch(`${apiRoot.replace(/\/$/, "")}/auth/exchange-oauth-token`, {
|
|
@@ -2070,17 +2076,32 @@ async function exchangeOAuthTokenForApiKey(token: string): Promise<string | null
|
|
|
2070
2076
|
"content-type": "application/json",
|
|
2071
2077
|
authorization: `Bearer ${token}`,
|
|
2072
2078
|
},
|
|
2073
|
-
body
|
|
2079
|
+
// The backend reads `token` as a required body input (Sails machine
|
|
2080
|
+
// input), not from the Authorization header — omitting it 400s and was
|
|
2081
|
+
// being surfaced as a misleading `no_team`.
|
|
2082
|
+
body: JSON.stringify({ token }),
|
|
2074
2083
|
});
|
|
2075
2084
|
if (!resp.ok) {
|
|
2076
|
-
|
|
2077
|
-
|
|
2085
|
+
let error = "exchange_failed";
|
|
2086
|
+
let description = `Token exchange failed (HTTP ${resp.status})`;
|
|
2087
|
+
try {
|
|
2088
|
+
const body = (await resp.json()) as { error?: string; message?: string; error_description?: string };
|
|
2089
|
+
if (body.error) error = body.error;
|
|
2090
|
+
if (body.message || body.error_description) description = body.message || body.error_description || description;
|
|
2091
|
+
} catch {
|
|
2092
|
+
// non-JSON error body; keep the generic description
|
|
2093
|
+
}
|
|
2094
|
+
logger.warn("http: exchange-oauth-token failed", { status: resp.status, error, description });
|
|
2095
|
+
return { ok: false, status: resp.status, error, description };
|
|
2078
2096
|
}
|
|
2079
2097
|
const data = (await resp.json()) as { apiKey?: string };
|
|
2080
|
-
|
|
2098
|
+
if (!data.apiKey) {
|
|
2099
|
+
return { ok: false, status: 403, error: "no_team", description: "No TestDriver team is linked to this account" };
|
|
2100
|
+
}
|
|
2101
|
+
return { ok: true, apiKey: data.apiKey };
|
|
2081
2102
|
} catch (error) {
|
|
2082
2103
|
logger.error("http: exchange-oauth-token error", { error: String(error) });
|
|
2083
|
-
return
|
|
2104
|
+
return { ok: false, status: 502, error: "exchange_unreachable", description: "Could not reach the TestDriver API to exchange the token" };
|
|
2084
2105
|
}
|
|
2085
2106
|
}
|
|
2086
2107
|
|
|
@@ -2179,12 +2200,13 @@ async function startHttpServer() {
|
|
|
2179
2200
|
return;
|
|
2180
2201
|
}
|
|
2181
2202
|
resolvedToken = auth.token;
|
|
2182
|
-
|
|
2183
|
-
if (!
|
|
2184
|
-
res.writeHead(
|
|
2185
|
-
res.end(JSON.stringify({ error:
|
|
2203
|
+
const exchange = await exchangeOAuthTokenForApiKey(auth.token);
|
|
2204
|
+
if (!exchange.ok) {
|
|
2205
|
+
res.writeHead(exchange.status, { "content-type": "application/json" });
|
|
2206
|
+
res.end(JSON.stringify({ error: exchange.error, error_description: exchange.description }));
|
|
2186
2207
|
return;
|
|
2187
2208
|
}
|
|
2209
|
+
resolvedApiKey = exchange.apiKey;
|
|
2188
2210
|
}
|
|
2189
2211
|
|
|
2190
2212
|
const sessionId = req.headers["mcp-session-id"];
|