@corenel/sidecar 0.8.0 → 0.8.2
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/dist/cli.js +139 -17
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -25280,9 +25280,10 @@ function oauthConfigFromEnv(env = process.env) {
|
|
|
25280
25280
|
return { issuer, clientId };
|
|
25281
25281
|
}
|
|
25282
25282
|
function openBrowser(url) {
|
|
25283
|
-
const cmd = process.platform === "win32" ? "
|
|
25284
|
-
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
25283
|
+
const [cmd, args] = process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
|
|
25285
25284
|
const child = spawn6(cmd, args, { detached: true, stdio: "ignore" });
|
|
25285
|
+
child.on("error", () => {
|
|
25286
|
+
});
|
|
25286
25287
|
child.unref();
|
|
25287
25288
|
}
|
|
25288
25289
|
async function exchangeCode2(p2) {
|
|
@@ -46192,7 +46193,7 @@ var FLAG_TO_KEY = {
|
|
|
46192
46193
|
"--default-model": "defaultModel",
|
|
46193
46194
|
"--name": "name"
|
|
46194
46195
|
};
|
|
46195
|
-
var DEFAULT_GUEST_IMAGE2 = "prompd/corenel-sandbox:0.
|
|
46196
|
+
var DEFAULT_GUEST_IMAGE2 = "prompd/corenel-sandbox:0.8.2";
|
|
46196
46197
|
function parseArgsWithProvenance(argv) {
|
|
46197
46198
|
const explicit = /* @__PURE__ */ new Set();
|
|
46198
46199
|
for (const a2 of argv) {
|
|
@@ -46880,6 +46881,49 @@ async function readMcpCredential(stateDir, server) {
|
|
|
46880
46881
|
const cred = section[server];
|
|
46881
46882
|
return isUsableCredential(cred) ? cred : null;
|
|
46882
46883
|
}
|
|
46884
|
+
function isUsableClient(v2) {
|
|
46885
|
+
if (typeof v2 !== "object" || v2 === null) return false;
|
|
46886
|
+
const r2 = v2;
|
|
46887
|
+
return typeof r2.clientId === "string" && r2.clientId.length > 0 && typeof r2.authServer === "string" && r2.authServer.length > 0 && (r2.clientSecret === void 0 || typeof r2.clientSecret === "string") && (r2.redirectPort === void 0 || typeof r2.redirectPort === "number" && Number.isInteger(r2.redirectPort) && r2.redirectPort > 0 && r2.redirectPort < 65536);
|
|
46888
|
+
}
|
|
46889
|
+
async function readMcpClient(stateDir, server) {
|
|
46890
|
+
const doc = await readAuthJson(stateDir);
|
|
46891
|
+
const section = doc.mcpClients;
|
|
46892
|
+
if (typeof section !== "object" || section === null || Array.isArray(section)) return null;
|
|
46893
|
+
const rec5 = section[server];
|
|
46894
|
+
return isUsableClient(rec5) ? rec5 : null;
|
|
46895
|
+
}
|
|
46896
|
+
async function writeMcpClient(stateDir, server, client) {
|
|
46897
|
+
const doc = await readAuthJson(stateDir);
|
|
46898
|
+
const prev = doc.mcpClients;
|
|
46899
|
+
const section = typeof prev === "object" && prev !== null && !Array.isArray(prev) ? { ...prev } : {};
|
|
46900
|
+
section[server] = client;
|
|
46901
|
+
const next = { ...doc, mcpClients: section };
|
|
46902
|
+
const path = credentialFilePath(stateDir);
|
|
46903
|
+
await mkdir4(credentialDir(stateDir), { recursive: true });
|
|
46904
|
+
await writeFile2(path, `${JSON.stringify(next, null, 2)}
|
|
46905
|
+
`, { mode: 384 });
|
|
46906
|
+
try {
|
|
46907
|
+
await chmod(path, 384);
|
|
46908
|
+
} catch {
|
|
46909
|
+
}
|
|
46910
|
+
}
|
|
46911
|
+
async function clearMcpClient(stateDir, server) {
|
|
46912
|
+
const doc = await readAuthJson(stateDir);
|
|
46913
|
+
const prev = doc.mcpClients;
|
|
46914
|
+
if (typeof prev !== "object" || prev === null || Array.isArray(prev)) return;
|
|
46915
|
+
const section = { ...prev };
|
|
46916
|
+
if (!(server in section)) return;
|
|
46917
|
+
delete section[server];
|
|
46918
|
+
const path = credentialFilePath(stateDir);
|
|
46919
|
+
await mkdir4(credentialDir(stateDir), { recursive: true });
|
|
46920
|
+
await writeFile2(path, `${JSON.stringify({ ...doc, mcpClients: section }, null, 2)}
|
|
46921
|
+
`, { mode: 384 });
|
|
46922
|
+
try {
|
|
46923
|
+
await chmod(path, 384);
|
|
46924
|
+
} catch {
|
|
46925
|
+
}
|
|
46926
|
+
}
|
|
46883
46927
|
async function writeMcpCredential(stateDir, server, cred) {
|
|
46884
46928
|
const doc = await readAuthJson(stateDir);
|
|
46885
46929
|
const section = isMcpAuthSection(doc.mcp) ? { ...doc.mcp } : {};
|
|
@@ -46921,6 +46965,15 @@ function firstAuthServer(doc) {
|
|
|
46921
46965
|
const first = list[0];
|
|
46922
46966
|
return typeof first === "string" && first.length > 0 ? first : null;
|
|
46923
46967
|
}
|
|
46968
|
+
function scopesFrom(doc) {
|
|
46969
|
+
const list = doc.scopes_supported;
|
|
46970
|
+
if (!Array.isArray(list) || list.length === 0) return void 0;
|
|
46971
|
+
return list.every((s2) => typeof s2 === "string") ? list : void 0;
|
|
46972
|
+
}
|
|
46973
|
+
function resourceResult(doc, authServer, mcpUrl) {
|
|
46974
|
+
const scopes = scopesFrom(doc);
|
|
46975
|
+
return { authServer, resource: resourceFor(doc, mcpUrl), ...scopes ? { scopes } : {} };
|
|
46976
|
+
}
|
|
46924
46977
|
function resourceMetadataUrlFromHeader(headerValue) {
|
|
46925
46978
|
if (!headerValue) return null;
|
|
46926
46979
|
const match = /resource_metadata="([^"]+)"/.exec(headerValue);
|
|
@@ -46934,9 +46987,22 @@ async function discoverAuthServer(mcpUrl, fetchImpl = fetch) {
|
|
|
46934
46987
|
const wellKnownUrl = new URL(PROTECTED_RESOURCE_PATH, origin).toString();
|
|
46935
46988
|
const direct = await tryFetchJson(wellKnownUrl, fetchImpl);
|
|
46936
46989
|
if (direct) {
|
|
46937
|
-
const
|
|
46938
|
-
if (
|
|
46990
|
+
const authServer = firstAuthServer(direct);
|
|
46991
|
+
if (authServer) return resourceResult(direct, authServer, mcpUrl);
|
|
46939
46992
|
}
|
|
46993
|
+
const viaHint = await hintedResource(mcpUrl, fetchImpl);
|
|
46994
|
+
if (viaHint) {
|
|
46995
|
+
const authServer = firstAuthServer(viaHint);
|
|
46996
|
+
if (authServer) return resourceResult(viaHint, authServer, mcpUrl);
|
|
46997
|
+
}
|
|
46998
|
+
const own = await tryFetchJson(authServerMetadataUrl(origin), fetchImpl);
|
|
46999
|
+
if (own && typeof own.authorization_endpoint === "string" && own.authorization_endpoint.length > 0) {
|
|
47000
|
+
const issuer = typeof own.issuer === "string" && own.issuer.length > 0 ? own.issuer : origin;
|
|
47001
|
+
return resourceResult(own, issuer, mcpUrl);
|
|
47002
|
+
}
|
|
47003
|
+
return null;
|
|
47004
|
+
}
|
|
47005
|
+
async function hintedResource(mcpUrl, fetchImpl) {
|
|
46940
47006
|
let probe;
|
|
46941
47007
|
try {
|
|
46942
47008
|
probe = await fetchImpl(mcpUrl);
|
|
@@ -46946,10 +47012,7 @@ async function discoverAuthServer(mcpUrl, fetchImpl = fetch) {
|
|
|
46946
47012
|
if (probe.status !== 401) return null;
|
|
46947
47013
|
const hinted = resourceMetadataUrlFromHeader(probe.headers.get("www-authenticate"));
|
|
46948
47014
|
if (!hinted) return null;
|
|
46949
|
-
|
|
46950
|
-
if (!viaHint) return null;
|
|
46951
|
-
const authServer = firstAuthServer(viaHint);
|
|
46952
|
-
return authServer ? { authServer, resource: resourceFor(viaHint, mcpUrl) } : null;
|
|
47015
|
+
return tryFetchJson(hinted, fetchImpl);
|
|
46953
47016
|
}
|
|
46954
47017
|
function authServerMetadataUrl(authServer) {
|
|
46955
47018
|
const u2 = new URL(authServer);
|
|
@@ -47208,23 +47271,32 @@ async function loginToMcpServer(stateDir, server, deps = {}) {
|
|
|
47208
47271
|
message: `"${server.name}" does not advertise an OAuth authorization server. If it takes a static token instead, store that as a header.`
|
|
47209
47272
|
};
|
|
47210
47273
|
}
|
|
47211
|
-
const
|
|
47274
|
+
const asMeta = await discoverAuthMetadata(
|
|
47212
47275
|
discovered.authServer,
|
|
47213
47276
|
fetchImpl
|
|
47214
47277
|
);
|
|
47215
|
-
const
|
|
47278
|
+
const meta = discovered.scopes ? { ...asMeta, scopesSupported: discovered.scopes } : asMeta;
|
|
47279
|
+
const cached = await readMcpClient(stateDir, server.name);
|
|
47280
|
+
const port = cached?.redirectPort ?? await getFreePort();
|
|
47216
47281
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
47217
|
-
const registered2 = await registerClient(
|
|
47282
|
+
const registered2 = cached && cached.authServer === discovered.authServer ? { clientId: cached.clientId, ...cached.clientSecret ? { clientSecret: cached.clientSecret } : {} } : await registerClient(
|
|
47218
47283
|
meta,
|
|
47219
47284
|
redirectUri,
|
|
47220
47285
|
fetchImpl
|
|
47221
47286
|
);
|
|
47287
|
+
if (registered2 && !cached) {
|
|
47288
|
+
await writeMcpClient(stateDir, server.name, {
|
|
47289
|
+
clientId: registered2.clientId,
|
|
47290
|
+
...registered2.clientSecret ? { clientSecret: registered2.clientSecret } : {},
|
|
47291
|
+
authServer: discovered.authServer
|
|
47292
|
+
});
|
|
47293
|
+
}
|
|
47222
47294
|
if (!registered2) {
|
|
47223
47295
|
return {
|
|
47224
47296
|
ok: false,
|
|
47225
47297
|
reason: "no-dcr",
|
|
47226
47298
|
authServer: discovered.authServer,
|
|
47227
|
-
message: `"${server.name}"'s authorization server (${discovered.authServer})
|
|
47299
|
+
message: `"${server.name}"'s authorization server (${discovered.authServer}) supports OAuth but not dynamic client registration, so no client can be created on demand. Store a static token as a header instead -- a server in this position normally issues one from its own settings.`
|
|
47228
47300
|
};
|
|
47229
47301
|
}
|
|
47230
47302
|
const pkce = makePkce();
|
|
@@ -47259,12 +47331,17 @@ async function loginToMcpServer(stateDir, server, deps = {}) {
|
|
|
47259
47331
|
await writeMcpCredential(stateDir, server.name, cred);
|
|
47260
47332
|
return { ok: true };
|
|
47261
47333
|
} catch (e2) {
|
|
47262
|
-
|
|
47334
|
+
const message = e2 instanceof Error ? e2.message : String(e2);
|
|
47335
|
+
if (/invalid_client|unauthorized_client|client.{0,20}not.{0,10}found/i.test(message)) {
|
|
47336
|
+
await clearMcpClient(stateDir, server.name).catch(() => {
|
|
47337
|
+
});
|
|
47338
|
+
}
|
|
47339
|
+
return { ok: false, reason: "failed", message };
|
|
47263
47340
|
}
|
|
47264
47341
|
}
|
|
47265
47342
|
|
|
47266
47343
|
// src/config/mcpCmd.ts
|
|
47267
|
-
var USAGE2 = "Use: corenel mcp add <name> <url> [--hosted] | remove <name> | list | set-header <name> <header> <value> | login <name> | refresh <name>";
|
|
47344
|
+
var USAGE2 = "Use: corenel mcp add <name> <url> [--hosted] | remove <name> | list | set-header <name> <header> <value> | set-client <name> <client-id> [<client-secret>] --auth-server <url> [--redirect-port <port>] | login <name> | refresh <name>";
|
|
47268
47345
|
function isHttpUrl(url) {
|
|
47269
47346
|
try {
|
|
47270
47347
|
const parsed = new URL(url);
|
|
@@ -47277,7 +47354,7 @@ async function runMcp(raw, io, deps = {}) {
|
|
|
47277
47354
|
const argv = stripStateDir(raw);
|
|
47278
47355
|
const sub = argv[0];
|
|
47279
47356
|
const { corrupt } = await readDaemonConfig(io.stateDir);
|
|
47280
|
-
const isWrite = sub === "add" || sub === "remove" || sub === "set-header" || sub === "login";
|
|
47357
|
+
const isWrite = sub === "add" || sub === "remove" || sub === "set-header" || sub === "set-client" || sub === "login";
|
|
47281
47358
|
if (corrupt && isWrite) {
|
|
47282
47359
|
io.err(`daemon.json in ${io.stateDir} is not valid JSON; fix or delete it before writing.`);
|
|
47283
47360
|
return 2;
|
|
@@ -47332,6 +47409,51 @@ async function runMcp(raw, io, deps = {}) {
|
|
|
47332
47409
|
if (corrupt) io.err(`daemon.json in ${io.stateDir} is not valid JSON; these are the defaults, not what the file says.`);
|
|
47333
47410
|
return 0;
|
|
47334
47411
|
}
|
|
47412
|
+
if (sub === "set-client") {
|
|
47413
|
+
const flagAt = argv.indexOf("--auth-server");
|
|
47414
|
+
const authServer = flagAt === -1 ? void 0 : argv[flagAt + 1];
|
|
47415
|
+
const portAt = argv.indexOf("--redirect-port");
|
|
47416
|
+
const portRaw = portAt === -1 ? void 0 : argv[portAt + 1];
|
|
47417
|
+
const consumed = /* @__PURE__ */ new Set([0, flagAt, flagAt + 1, portAt, portAt + 1]);
|
|
47418
|
+
const positional = argv.filter((a2, i2) => !consumed.has(i2));
|
|
47419
|
+
const name = positional[0];
|
|
47420
|
+
const clientId = positional[1];
|
|
47421
|
+
const clientSecret = positional[2];
|
|
47422
|
+
if (!name || !clientId) {
|
|
47423
|
+
io.err(`corenel mcp set-client needs a name and a client id. ${USAGE2}`);
|
|
47424
|
+
return 2;
|
|
47425
|
+
}
|
|
47426
|
+
if (!authServer || !isHttpUrl(authServer)) {
|
|
47427
|
+
io.err(`corenel mcp set-client needs --auth-server <url> -- the authorization server the client was registered with, copied from the service's own app settings. ${USAGE2}`);
|
|
47428
|
+
return 2;
|
|
47429
|
+
}
|
|
47430
|
+
let redirectPort;
|
|
47431
|
+
if (portRaw !== void 0) {
|
|
47432
|
+
const parsed = Number(portRaw);
|
|
47433
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed >= 65536) {
|
|
47434
|
+
io.err(`"${portRaw}" is not a usable port for --redirect-port. ${USAGE2}`);
|
|
47435
|
+
return 2;
|
|
47436
|
+
}
|
|
47437
|
+
redirectPort = parsed;
|
|
47438
|
+
}
|
|
47439
|
+
const servers = await readMcpServers(io.stateDir);
|
|
47440
|
+
if (!servers.some((s2) => s2.name === name)) {
|
|
47441
|
+
io.err(`"${name}" is not a configured MCP server. Add it first: corenel mcp add ${name} <url>`);
|
|
47442
|
+
return 2;
|
|
47443
|
+
}
|
|
47444
|
+
await writeMcpClient(io.stateDir, name, {
|
|
47445
|
+
clientId,
|
|
47446
|
+
...clientSecret ? { clientSecret } : {},
|
|
47447
|
+
authServer,
|
|
47448
|
+
...redirectPort ? { redirectPort } : {}
|
|
47449
|
+
});
|
|
47450
|
+
io.out(`Stored an OAuth client for "${name}" (client id ${clientId}${clientSecret ? ", with a secret" : ""}).`);
|
|
47451
|
+
if (redirectPort !== void 0) {
|
|
47452
|
+
io.out(`Register this redirect URL with the service: http://localhost:${redirectPort}/callback`);
|
|
47453
|
+
}
|
|
47454
|
+
io.out(`Run: corenel mcp login ${name}`);
|
|
47455
|
+
return 0;
|
|
47456
|
+
}
|
|
47335
47457
|
if (sub === "set-header") {
|
|
47336
47458
|
const name = argv[1];
|
|
47337
47459
|
const header = argv[2];
|
|
@@ -47385,7 +47507,7 @@ async function runMcp(raw, io, deps = {}) {
|
|
|
47385
47507
|
return 2;
|
|
47386
47508
|
}
|
|
47387
47509
|
if (outcome.reason === "no-dcr") {
|
|
47388
|
-
io.err(`"${name}"'s authorization server (${outcome.authServer})
|
|
47510
|
+
io.err(`"${name}"'s authorization server (${outcome.authServer}) supports OAuth but not dynamic client registration, so no client can be created on demand. Use: corenel mcp set-header ${name} <header> <value> with a token from the service's own settings.`);
|
|
47389
47511
|
return 2;
|
|
47390
47512
|
}
|
|
47391
47513
|
io.err(`Sign-in for "${name}" failed: ${outcome.message}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@corenel/sidecar",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"description": "Prompd sidecar — a local capability server. Run it next to Prompd (prompd.app) so the browser app can reach your machine: local LLMs (Ollama/LM Studio/vLLM) without CORS, your filesystem, and (opt-in) shell.",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"typescript": "^5.9.3",
|
|
39
39
|
"@corenel/harness": "0.7.0",
|
|
40
40
|
"@corenel/protocol": "0.7.0",
|
|
41
|
-
"@corenel/
|
|
42
|
-
"@corenel/
|
|
41
|
+
"@corenel/crew": "0.0.0",
|
|
42
|
+
"@corenel/tools-node": "0.3.2"
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|