@truealter/sdk 0.5.1 → 0.5.3
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 +115 -50
- package/dist/bin/alter-identity.js +383 -48
- package/dist/bin/mcp-bridge.js +40 -4
- package/dist/index.cjs +517 -64
- package/dist/index.d.cts +480 -128
- package/dist/index.d.ts +480 -128
- package/dist/index.js +496 -65
- package/package.json +3 -3
- package/dist/mcp-bridge.js +0 -166
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truealter/sdk",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "ALTER Identity SDK
|
|
3
|
+
"version": "0.5.3",
|
|
4
|
+
"description": "ALTER Identity SDK: query the continuous identity field from any JavaScript/TypeScript environment",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.cjs",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"test:watch": "vitest",
|
|
34
34
|
"lint": "eslint src/",
|
|
35
35
|
"typecheck": "tsc --noEmit",
|
|
36
|
-
"prepublishOnly": "npm run build"
|
|
36
|
+
"prepublishOnly": "npm run build && bash scripts/prepublish.sh"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@noble/curves": "1.9.7",
|
package/dist/mcp-bridge.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as os from 'os';
|
|
5
|
-
import * as https from 'https';
|
|
6
|
-
import * as http from 'http';
|
|
7
|
-
import * as crypto from 'crypto';
|
|
8
|
-
import * as readline from 'readline';
|
|
9
|
-
|
|
10
|
-
var DEFAULT_ENDPOINT = "https://mcp.truealter.com/api/v1/mcp";
|
|
11
|
-
var BRIDGE_VERSION = "0.4.0";
|
|
12
|
-
var xdgConfig = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
|
|
13
|
-
var SESSION_FILE = process.env.ALTER_SESSION_FILE ?? path.join(xdgConfig, "alter", "session.json");
|
|
14
|
-
var CF_ENV_FILE = process.env.ALTER_CF_ACCESS_ENV ?? path.join(xdgConfig, "alter", "cf-access.env");
|
|
15
|
-
var ENDPOINT = process.env.ALTER_PUBLIC_MCP_ENDPOINT ?? DEFAULT_ENDPOINT;
|
|
16
|
-
function readSession() {
|
|
17
|
-
try {
|
|
18
|
-
const raw = fs.readFileSync(SESSION_FILE, "utf8");
|
|
19
|
-
const clean = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
20
|
-
return JSON.parse(clean);
|
|
21
|
-
} catch {
|
|
22
|
-
return {};
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function readCfAccess() {
|
|
26
|
-
try {
|
|
27
|
-
const raw = fs.readFileSync(CF_ENV_FILE, "utf8");
|
|
28
|
-
const idMatch = raw.match(/CF_ACCESS_(?:MCP_ORG_ALTER_)?CLIENT_ID=['"](.*?)['"]/);
|
|
29
|
-
const secretMatch = raw.match(/CF_ACCESS_(?:MCP_ORG_ALTER_)?CLIENT_SECRET=['"](.*?)['"]/);
|
|
30
|
-
return {
|
|
31
|
-
id: idMatch?.[1] ?? "",
|
|
32
|
-
secret: secretMatch?.[1] ?? ""
|
|
33
|
-
};
|
|
34
|
-
} catch {
|
|
35
|
-
return { id: "", secret: "" };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
function versionHash() {
|
|
39
|
-
const digest = crypto.createHash("sha256").update(`@truealter/sdk-bridge@${BRIDGE_VERSION}`).digest("hex").slice(0, 32);
|
|
40
|
-
return `sha256:${digest}`;
|
|
41
|
-
}
|
|
42
|
-
function b64url(buf) {
|
|
43
|
-
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
44
|
-
}
|
|
45
|
-
function canonicalJson(v) {
|
|
46
|
-
if (v === null || typeof v !== "object") return JSON.stringify(v);
|
|
47
|
-
if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
|
|
48
|
-
const obj = v;
|
|
49
|
-
const keys = Object.keys(obj).sort();
|
|
50
|
-
return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalJson(obj[k])).join(",") + "}";
|
|
51
|
-
}
|
|
52
|
-
function loadSigningKey(session) {
|
|
53
|
-
const kid = session.signing_kid;
|
|
54
|
-
if (!kid) return null;
|
|
55
|
-
const candidates = [];
|
|
56
|
-
if (process.env.ALTER_SIGNING_KEY_FILE) candidates.push(process.env.ALTER_SIGNING_KEY_FILE);
|
|
57
|
-
candidates.push(path.join(xdgConfig, "alter", "signing-keys", `${kid}.pem`));
|
|
58
|
-
candidates.push(path.join(xdgConfig, "alter", "signing-key.pem"));
|
|
59
|
-
for (const p of candidates) {
|
|
60
|
-
try {
|
|
61
|
-
const pem = fs.readFileSync(p, "utf8");
|
|
62
|
-
return { kid, key: crypto.createPrivateKey(pem) };
|
|
63
|
-
} catch {
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
function signInvocation(signer, toolName, toolArgs, handle) {
|
|
69
|
-
const header = b64url(Buffer.from(JSON.stringify({ alg: "ES256", kid: signer.kid })));
|
|
70
|
-
const claims = {
|
|
71
|
-
tool: toolName,
|
|
72
|
-
args_sha256: crypto.createHash("sha256").update(canonicalJson(toolArgs ?? {}), "utf8").digest("hex"),
|
|
73
|
-
nonce: b64url(crypto.randomBytes(24)),
|
|
74
|
-
iat: Math.floor(Date.now() / 1e3),
|
|
75
|
-
iss: handle
|
|
76
|
-
};
|
|
77
|
-
const payload = b64url(Buffer.from(JSON.stringify(claims)));
|
|
78
|
-
const signingInput = `${header}.${payload}`;
|
|
79
|
-
const sig = crypto.createSign("SHA256").update(signingInput).sign({ key: signer.key, dsaEncoding: "ieee-p1363" });
|
|
80
|
-
return `${signingInput}.${b64url(sig)}`;
|
|
81
|
-
}
|
|
82
|
-
function proxyRequest(body, msg) {
|
|
83
|
-
return new Promise((resolve, reject) => {
|
|
84
|
-
const session = readSession();
|
|
85
|
-
const cfAccess = readCfAccess();
|
|
86
|
-
const apiKey = process.env.ALTER_API_KEY ?? session.member_api_key ?? "";
|
|
87
|
-
const url = new URL(ENDPOINT);
|
|
88
|
-
const isHttps = url.protocol === "https:";
|
|
89
|
-
const transport = isHttps ? https : http;
|
|
90
|
-
const headers = {
|
|
91
|
-
"Content-Type": "application/json",
|
|
92
|
-
"Content-Length": String(Buffer.byteLength(body)),
|
|
93
|
-
"X-Agent-Version-Hash": versionHash()
|
|
94
|
-
};
|
|
95
|
-
if (apiKey) headers["X-ALTER-API-Key"] = apiKey;
|
|
96
|
-
if (cfAccess.id) headers["CF-Access-Client-Id"] = cfAccess.id;
|
|
97
|
-
if (cfAccess.secret) headers["CF-Access-Client-Secret"] = cfAccess.secret;
|
|
98
|
-
if (apiKey && msg.method === "tools/call" && msg.params?.name) {
|
|
99
|
-
const signer = loadSigningKey(session);
|
|
100
|
-
if (signer) {
|
|
101
|
-
try {
|
|
102
|
-
headers["Mcp-Invocation-Signature"] = signInvocation(
|
|
103
|
-
signer,
|
|
104
|
-
msg.params.name,
|
|
105
|
-
msg.params.arguments ?? {},
|
|
106
|
-
session.handle ?? ""
|
|
107
|
-
);
|
|
108
|
-
} catch (e) {
|
|
109
|
-
process.stderr.write(`bridge sign error: ${e.message}
|
|
110
|
-
`);
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
113
|
-
process.stderr.write(
|
|
114
|
-
`bridge: no signing key for kid ${session.signing_kid ?? "(unset)"} \u2014 run 'alter login' to provision one
|
|
115
|
-
`
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
const req = transport.request(
|
|
120
|
-
{
|
|
121
|
-
hostname: url.hostname,
|
|
122
|
-
port: url.port || (isHttps ? 443 : 80),
|
|
123
|
-
path: url.pathname,
|
|
124
|
-
method: "POST",
|
|
125
|
-
headers
|
|
126
|
-
},
|
|
127
|
-
(res) => {
|
|
128
|
-
let data = "";
|
|
129
|
-
res.on("data", (chunk) => data += chunk.toString());
|
|
130
|
-
res.on("end", () => resolve(data));
|
|
131
|
-
}
|
|
132
|
-
);
|
|
133
|
-
req.on("error", (err) => reject(err));
|
|
134
|
-
req.write(body);
|
|
135
|
-
req.end();
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
async function main() {
|
|
139
|
-
const rl = readline.createInterface({ input: process.stdin });
|
|
140
|
-
for await (const line of rl) {
|
|
141
|
-
const trimmed = line.trim();
|
|
142
|
-
if (!trimmed) continue;
|
|
143
|
-
let msg;
|
|
144
|
-
try {
|
|
145
|
-
msg = JSON.parse(trimmed);
|
|
146
|
-
} catch {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
try {
|
|
150
|
-
const response = await proxyRequest(trimmed, msg);
|
|
151
|
-
process.stdout.write(response + "\n");
|
|
152
|
-
} catch (err) {
|
|
153
|
-
const errResponse = JSON.stringify({
|
|
154
|
-
jsonrpc: "2.0",
|
|
155
|
-
id: msg.id ?? null,
|
|
156
|
-
error: { code: -32e3, message: err.message }
|
|
157
|
-
});
|
|
158
|
-
process.stdout.write(errResponse + "\n");
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
main().catch((err) => {
|
|
163
|
-
process.stderr.write(`bridge fatal: ${err.message}
|
|
164
|
-
`);
|
|
165
|
-
process.exit(1);
|
|
166
|
-
});
|