@hirey/hi-mcp-server 0.1.23 → 0.1.26
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 +20 -0
- package/dist/apiKey.d.ts +9 -0
- package/dist/apiKey.d.ts.map +1 -0
- package/dist/apiKey.js +51 -0
- package/dist/apiKeyExchange.d.ts +9 -0
- package/dist/apiKeyExchange.d.ts.map +1 -0
- package/dist/apiKeyExchange.js +65 -0
- package/dist/defaultReplyRoute.d.ts +4 -2
- package/dist/defaultReplyRoute.d.ts.map +1 -1
- package/dist/defaultReplyRoute.js +3 -0
- package/dist/installDefaults.d.ts +2 -1
- package/dist/installDefaults.d.ts.map +1 -1
- package/dist/installDefaults.js +14 -3
- package/dist/oauthRequestAuth.d.ts +67 -0
- package/dist/oauthRequestAuth.d.ts.map +1 -0
- package/dist/oauthRequestAuth.js +131 -0
- package/dist/receiver-command.d.ts +2 -1
- package/dist/receiver-command.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +970 -72
- package/dist/state.d.ts +2 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +77 -2
- package/package.json +1 -1
package/dist/state.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export type HiAgentPersistedState = {
|
|
|
38
38
|
};
|
|
39
39
|
export declare const DEFAULT_HI_MCP_PROFILE = "default";
|
|
40
40
|
export declare function normalizeStateProfile(raw: unknown): string;
|
|
41
|
+
export declare function deriveSubjectProfileSuffix(subjectId: unknown): string;
|
|
42
|
+
export declare function composeSubjectScopedProfile(baseProfile: unknown, subjectId: unknown): string;
|
|
41
43
|
export declare function resolveDefaultStateDir(profileRaw: unknown): string;
|
|
42
44
|
export declare function resolveCanonicalOpenClawStateDir(profileRaw?: unknown): string;
|
|
43
45
|
export type OpenClawStateDirValidation = {
|
package/dist/state.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
|
package/dist/state.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -17,6 +18,30 @@ export const DEFAULT_HI_MCP_PROFILE = 'default';
|
|
|
17
18
|
export function normalizeStateProfile(raw) {
|
|
18
19
|
return String(raw || '').trim() || DEFAULT_HI_MCP_PROFILE;
|
|
19
20
|
}
|
|
21
|
+
// In OAuth multi-tenant HTTP mode every /mcp request carries a verified bearer
|
|
22
|
+
// whose `sub` claim is a stable per-installation Hi subject (e.g. `sub_aB12…`).
|
|
23
|
+
// We derive a deterministic profile suffix from that subject so each OAuth
|
|
24
|
+
// caller gets its own state file under the same stateDir — without leaking
|
|
25
|
+
// the raw `sub` (which is a secret-ish identifier) into the filesystem.
|
|
26
|
+
//
|
|
27
|
+
// Background: before 2026-05-20 every OAuth subject hitting the same hi-mcp
|
|
28
|
+
// pod read and wrote `<stateDir>/<profile>.json`, so subject A's
|
|
29
|
+
// `hi_agent_install` would overwrite the disk identity, then subject B's
|
|
30
|
+
// `hi_agent_status` would return A's `client_id`/`client_secret`. That cross-
|
|
31
|
+
// tenant leak is fixed by routing per-subject state through this helper.
|
|
32
|
+
export function deriveSubjectProfileSuffix(subjectId) {
|
|
33
|
+
const raw = String(subjectId || '').trim();
|
|
34
|
+
if (!raw)
|
|
35
|
+
return '';
|
|
36
|
+
return crypto.createHash('sha256').update(raw).digest('hex').slice(0, 16);
|
|
37
|
+
}
|
|
38
|
+
export function composeSubjectScopedProfile(baseProfile, subjectId) {
|
|
39
|
+
const profile = normalizeStateProfile(baseProfile);
|
|
40
|
+
const suffix = deriveSubjectProfileSuffix(subjectId);
|
|
41
|
+
if (!suffix)
|
|
42
|
+
return profile;
|
|
43
|
+
return `${profile}__s_${suffix}`;
|
|
44
|
+
}
|
|
20
45
|
function normalizeStateDirPath(raw) {
|
|
21
46
|
const text = typeof raw === 'string' ? String(raw).trim() : '';
|
|
22
47
|
return text ? path.resolve(text) : '';
|
|
@@ -179,13 +204,63 @@ export async function readState(args) {
|
|
|
179
204
|
catch (error) {
|
|
180
205
|
if (error?.code === 'ENOENT')
|
|
181
206
|
return buildDefaultState(args.profile);
|
|
207
|
+
if (error instanceof SyntaxError) {
|
|
208
|
+
// File exists but isn't valid JSON — a legacy torn/truncated write (from
|
|
209
|
+
// before atomicWriteFile) or external corruption. Don't hard-fail every
|
|
210
|
+
// tool call; preserve the bad file for forensics and fall back to a fresh
|
|
211
|
+
// default so the agent can re-provision instead of bricking.
|
|
212
|
+
const corruptPath = `${filePath}.corrupt-${process.pid}-${crypto.randomBytes(4).toString('hex')}`;
|
|
213
|
+
try {
|
|
214
|
+
await fs.rename(filePath, corruptPath);
|
|
215
|
+
}
|
|
216
|
+
catch { }
|
|
217
|
+
try {
|
|
218
|
+
console.error(`[hi-mcp] state file was corrupt; quarantined to ${corruptPath} and reset to default`);
|
|
219
|
+
}
|
|
220
|
+
catch { }
|
|
221
|
+
return buildDefaultState(args.profile);
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Crash-safe atomic file write. A plain fs.writeFile truncates the target up
|
|
227
|
+
// front and streams bytes in — a SIGKILL / OOM / power loss mid-write leaves a
|
|
228
|
+
// 0-byte or half-written file. For the credential/identity file that means the
|
|
229
|
+
// agent's client_id/client_secret silently vanish and the user is forced to
|
|
230
|
+
// re-login (the "codex creds mysteriously disappeared" class of bug). Instead:
|
|
231
|
+
// write to a unique temp file, fsync its contents to disk, then atomically
|
|
232
|
+
// rename it over the target. A concurrent reader always sees either the old
|
|
233
|
+
// complete file or the new complete file — never a torn one. mode 0o600 because
|
|
234
|
+
// the payload carries client_secret.
|
|
235
|
+
async function atomicWriteFile(filePath, data) {
|
|
236
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
237
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${crypto.randomBytes(6).toString('hex')}`;
|
|
238
|
+
let fh;
|
|
239
|
+
try {
|
|
240
|
+
fh = await fs.open(tmpPath, 'w', 0o600);
|
|
241
|
+
await fh.writeFile(data, 'utf8');
|
|
242
|
+
await fh.sync();
|
|
243
|
+
await fh.close();
|
|
244
|
+
fh = undefined;
|
|
245
|
+
await fs.rename(tmpPath, filePath);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (fh) {
|
|
249
|
+
try {
|
|
250
|
+
await fh.close();
|
|
251
|
+
}
|
|
252
|
+
catch { }
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
await fs.unlink(tmpPath);
|
|
256
|
+
}
|
|
257
|
+
catch { }
|
|
182
258
|
throw error;
|
|
183
259
|
}
|
|
184
260
|
}
|
|
185
261
|
export async function writeState(args) {
|
|
186
262
|
const filePath = resolveStateFile(args);
|
|
187
|
-
await
|
|
188
|
-
await fs.writeFile(filePath, `${JSON.stringify(args.state, null, 2)}\n`, 'utf8');
|
|
263
|
+
await atomicWriteFile(filePath, `${JSON.stringify(args.state, null, 2)}\n`);
|
|
189
264
|
}
|
|
190
265
|
export async function updateState(args) {
|
|
191
266
|
const current = await readState(args);
|