@commonlyai/mcp 0.3.11 → 0.3.12
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/package.json +1 -1
- package/src/client.js +50 -24
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -29,22 +29,29 @@ export class HttpError extends Error {
|
|
|
29
29
|
/**
|
|
30
30
|
* Read the runtime credential.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
* of the environment"
|
|
32
|
+
* THREE CHANNELS, resolved in this order by what the parent declares (TASK-078,
|
|
33
|
+
* ruled 2026-09-19 — "take the token out of the environment" — extended the
|
|
34
|
+
* same day to a file, so claude and codex can use it too):
|
|
34
35
|
*
|
|
35
36
|
* 1. `COMMONLY_TOKEN_FD` names an inherited pipe holding the credential. This
|
|
36
|
-
* is the channel the
|
|
37
|
+
* is the channel the pi adapter uses: the token is never in this process's
|
|
37
38
|
* environment, so a same-user reader cannot get it from
|
|
38
39
|
* `/proc/<pid>/environ` or `ps eww <pid>` — which is exactly how the old
|
|
39
40
|
* channel leaked it.
|
|
40
|
-
* 2. `
|
|
41
|
+
* 2. `COMMONLY_TOKEN_FILE` names a file holding the credential, written 0600
|
|
42
|
+
* for one spawn. This is the channel claude and codex use, because neither
|
|
43
|
+
* of them can be handed a pipe we opened: claude expands `${VAR}` from its
|
|
44
|
+
* own environment, and codex forwards declared variables from its own. A
|
|
45
|
+
* path is not a secret, so the token stays out of both of their
|
|
46
|
+
* environments even though it reaches the grandchild.
|
|
47
|
+
* 3. `COMMONLY_AGENT_TOKEN` in the environment. Kept because the channel is a
|
|
41
48
|
* property of the PARENT, not of this package: an older daemon, another
|
|
42
49
|
* driver, or a hand-run server still passes it this way, and refusing the
|
|
43
50
|
* old channel would turn a security fix into an outage.
|
|
44
51
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
52
|
+
* A declared source is authoritative. A read that fails does NOT fall through
|
|
53
|
+
* to the next one: silently using the weaker channel after being told to use
|
|
54
|
+
* the stronger one is the class of bug this exists to remove.
|
|
48
55
|
*
|
|
49
56
|
* The fd is read to EOF and deliberately never closed — on macOS a `closeSync`
|
|
50
57
|
* after a failed read aborts the process (`kqueue.c`), and the descriptor goes
|
|
@@ -52,24 +59,43 @@ export class HttpError extends Error {
|
|
|
52
59
|
*/
|
|
53
60
|
export const readToken = (env = process.env, { readImpl = readFileSync } = {}) => {
|
|
54
61
|
const declaredFd = env.COMMONLY_TOKEN_FD;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
const declaredFile = env.COMMONLY_TOKEN_FILE;
|
|
63
|
+
const blank = (value) => value === undefined || String(value).trim() === '';
|
|
64
|
+
|
|
65
|
+
if (!blank(declaredFd)) {
|
|
66
|
+
const fd = Number.parseInt(String(declaredFd).trim(), 10);
|
|
67
|
+
if (!Number.isInteger(fd) || fd < 0 || String(fd) !== String(declaredFd).trim()) {
|
|
68
|
+
throw new Error(`COMMONLY_TOKEN_FD must be a file descriptor number, got '${declaredFd}'`);
|
|
69
|
+
}
|
|
70
|
+
let raw;
|
|
71
|
+
try {
|
|
72
|
+
raw = readImpl(fd, 'utf8');
|
|
73
|
+
} catch (err) {
|
|
74
|
+
throw new Error(`COMMONLY_TOKEN_FD=${fd} was declared but the credential could not be read from it: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
const token = String(raw ?? '').trim();
|
|
77
|
+
if (!token) {
|
|
78
|
+
throw new Error(`COMMONLY_TOKEN_FD=${fd} carried an empty credential`);
|
|
79
|
+
}
|
|
80
|
+
return token;
|
|
67
81
|
}
|
|
68
|
-
|
|
69
|
-
if (!
|
|
70
|
-
|
|
82
|
+
|
|
83
|
+
if (!blank(declaredFile)) {
|
|
84
|
+
const tokenPath = String(declaredFile).trim();
|
|
85
|
+
let raw;
|
|
86
|
+
try {
|
|
87
|
+
raw = readImpl(tokenPath, 'utf8');
|
|
88
|
+
} catch (err) {
|
|
89
|
+
throw new Error(`COMMONLY_TOKEN_FILE=${tokenPath} was declared but the credential could not be read from it: ${err.message}`);
|
|
90
|
+
}
|
|
91
|
+
const token = String(raw ?? '').trim();
|
|
92
|
+
if (!token) {
|
|
93
|
+
throw new Error(`COMMONLY_TOKEN_FILE=${tokenPath} carried an empty credential`);
|
|
94
|
+
}
|
|
95
|
+
return token;
|
|
71
96
|
}
|
|
72
|
-
|
|
97
|
+
|
|
98
|
+
return env.COMMONLY_AGENT_TOKEN;
|
|
73
99
|
};
|
|
74
100
|
|
|
75
101
|
/**
|
|
@@ -87,7 +113,7 @@ export const loadConfig = (env = process.env, opts = {}) => {
|
|
|
87
113
|
throw new Error('COMMONLY_API_URL is required (e.g. https://api.commonly.me)');
|
|
88
114
|
}
|
|
89
115
|
if (!token) {
|
|
90
|
-
throw new Error('No runtime token:
|
|
116
|
+
throw new Error('No runtime token: COMMONLY_TOKEN_FD must name a pipe, COMMONLY_TOKEN_FILE must name a file, or COMMONLY_AGENT_TOKEN must be set (cm_agent_* runtime token)');
|
|
91
117
|
}
|
|
92
118
|
if (!token.startsWith('cm_agent_')) {
|
|
93
119
|
throw new Error('The runtime token must be a cm_agent_* token, whichever channel delivered it');
|