@ractive-ch/tab-mcp 0.6.0 → 0.6.1
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 +21 -2
- package/index.js +30 -3
- package/index.js.map +3 -3
- package/main.js +30 -3
- package/main.js.map +3 -3
- package/package.json +1 -1
- package/relay.d.ts +6 -0
- package/testing.d.ts +33 -2
- package/testing.js +5830 -1
- package/testing.js.map +28 -4
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ and gets an MCP address. The user adds that address to an AI app and pairs it by
|
|
|
10
10
|
a code from the page on the relay's sign-in page. The relay then forwards the AI app's
|
|
11
11
|
requests to the page. It knows nothing about the tools themselves.
|
|
12
12
|
|
|
13
|
-
**Status: 0.6.
|
|
13
|
+
**Status: 0.6.1.** Until 1.0, a new minor version can still change settings and the API.
|
|
14
14
|
The relay speaks MCP revision 2026-07-28 only and accepts AI apps that register through
|
|
15
15
|
OAuth Client ID Metadata Documents; see [compatibility](https://github.com/ractive/tab-mcp/blob/main/docs/compatibility.md)
|
|
16
16
|
for which apps work.
|
|
@@ -32,7 +32,7 @@ Or with Docker (linux/amd64 and linux/arm64):
|
|
|
32
32
|
docker run -d -p 127.0.0.1:8080:8080 -v tab-mcp-data:/data \
|
|
33
33
|
-e TAB_MCP_PUBLIC_URL=https://relay.example.com \
|
|
34
34
|
-e TAB_MCP_ALLOWED_ORIGINS=https://app.example.com \
|
|
35
|
-
ractive/tab-mcp:0.6.
|
|
35
|
+
ractive/tab-mcp:0.6.1
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
The image is also on `ghcr.io/ractive/tab-mcp`. Put the relay behind a reverse proxy
|
|
@@ -112,6 +112,25 @@ tokenStoreConformance("MyTokenStore", () => new MyTokenStore());
|
|
|
112
112
|
[Token store](https://github.com/ractive/tab-mcp/blob/main/docs/token-store.md) explains
|
|
113
113
|
the interface, its guarantees and a Redis example.
|
|
114
114
|
|
|
115
|
+
### Testing a page's tools
|
|
116
|
+
|
|
117
|
+
For end-to-end tests under `bun test`, `startTestRelay()` runs a relay in the test
|
|
118
|
+
process (127.0.0.1, a free port, development mode, every page origin allowed, a memory
|
|
119
|
+
store, no log output unless `logger` or `logLevel` is given; options override any of that), and `tokenFor()` writes a token for
|
|
120
|
+
a page's MCP address into its store, as if an AI app had paired:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { startTestRelay, tokenFor } from "@ractive-ch/tab-mcp/testing";
|
|
124
|
+
|
|
125
|
+
const relay = startTestRelay();
|
|
126
|
+
// The page connects with `relay: relay.url`; its address is `session.address`.
|
|
127
|
+
const token = await tokenFor(relay.store, address);
|
|
128
|
+
// Any MCP client, with the header `Authorization: Bearer ${token}`.
|
|
129
|
+
await relay.stop();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
It is for tests only: it accepts every origin and skips pairing.
|
|
133
|
+
|
|
115
134
|
## Security
|
|
116
135
|
|
|
117
136
|
Connecting an AI app to a page means trusting that page, as with any MCP server. The
|
package/index.js
CHANGED
|
@@ -1218,7 +1218,7 @@ function parseTabMessage(text) {
|
|
|
1218
1218
|
}
|
|
1219
1219
|
}
|
|
1220
1220
|
// package.json
|
|
1221
|
-
var version = "0.6.
|
|
1221
|
+
var version = "0.6.1";
|
|
1222
1222
|
|
|
1223
1223
|
// src/exchange.ts
|
|
1224
1224
|
var RelayErrorCode = {
|
|
@@ -1848,6 +1848,26 @@ function newId(prefix) {
|
|
|
1848
1848
|
function hashToken(token) {
|
|
1849
1849
|
return new Bun.CryptoHasher("sha256").update(token).digest().toBase64({ alphabet: "base64url", omitPadding: true });
|
|
1850
1850
|
}
|
|
1851
|
+
async function issueAccessToken(store, grant, options) {
|
|
1852
|
+
const now = options.now ?? Date.now();
|
|
1853
|
+
const token = newToken("access");
|
|
1854
|
+
const record = {
|
|
1855
|
+
hash: hashToken(token),
|
|
1856
|
+
kind: "access",
|
|
1857
|
+
grantId: grant.grantId ?? newId("g"),
|
|
1858
|
+
addressId: grant.addressId,
|
|
1859
|
+
clientId: grant.clientId,
|
|
1860
|
+
clientName: grant.clientName,
|
|
1861
|
+
scopes: grant.scopes ?? ["tools"],
|
|
1862
|
+
grantCreatedAt: grant.grantCreatedAt ?? now,
|
|
1863
|
+
issuedAt: now,
|
|
1864
|
+
expiresAt: now + options.ttlMs
|
|
1865
|
+
};
|
|
1866
|
+
if (grant.subject !== undefined)
|
|
1867
|
+
record.subject = grant.subject;
|
|
1868
|
+
await store.put(record);
|
|
1869
|
+
return { token, record };
|
|
1870
|
+
}
|
|
1851
1871
|
async function issueTokenPair(store, grant, options) {
|
|
1852
1872
|
const now = options.now ?? Date.now();
|
|
1853
1873
|
const accessToken = newToken("access");
|
|
@@ -4845,6 +4865,13 @@ var FORWARDED_HEADERS = new Set([
|
|
|
4845
4865
|
"traceparent",
|
|
4846
4866
|
"tracestate"
|
|
4847
4867
|
]);
|
|
4868
|
+
function developmentUrl(host, port) {
|
|
4869
|
+
if (host === "127.0.0.1")
|
|
4870
|
+
return `http://127.0.0.1:${port}`;
|
|
4871
|
+
if (host === "::1" || host === "[::1]")
|
|
4872
|
+
return `http://[::1]:${port}`;
|
|
4873
|
+
return `http://localhost:${port}`;
|
|
4874
|
+
}
|
|
4848
4875
|
var HEALTH_STORE_TIMEOUT_MS = 1000;
|
|
4849
4876
|
var HEALTH_REUSE_MS = 1000;
|
|
4850
4877
|
var HEALTH_HUNG_RETRY_MS = 30000;
|
|
@@ -5711,7 +5738,7 @@ function startRelay(options, hooks) {
|
|
|
5711
5738
|
let closing = false;
|
|
5712
5739
|
const port = server.port ?? config.port;
|
|
5713
5740
|
if (publicUrl === "")
|
|
5714
|
-
publicUrl =
|
|
5741
|
+
publicUrl = developmentUrl(config.host, port);
|
|
5715
5742
|
const sweep = setInterval(() => {
|
|
5716
5743
|
auth.sweep();
|
|
5717
5744
|
store.deleteExpired(Date.now()).then((n) => {
|
|
@@ -5784,5 +5811,5 @@ export {
|
|
|
5784
5811
|
resolveConfig
|
|
5785
5812
|
};
|
|
5786
5813
|
|
|
5787
|
-
//# debugId=
|
|
5814
|
+
//# debugId=F0E5B49004089DE664756E2164756E21
|
|
5788
5815
|
//# sourceMappingURL=index.js.map
|