@caplets/core 0.16.0 → 0.17.0
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/auth.d.ts +14 -0
- package/dist/cli/auth.d.ts +24 -0
- package/dist/cli.d.ts +2 -0
- package/dist/index.js +746 -77
- package/dist/native/options.d.ts +4 -5
- package/dist/native.js +15 -58
- package/dist/{engine-Brwid_mq.js → options-CJEOqS87.js} +238 -20
- package/dist/remote-control/auth-flow.d.ts +22 -0
- package/dist/remote-control/client.d.ts +11 -0
- package/dist/remote-control/dispatch.d.ts +9 -0
- package/dist/remote-control/types.d.ts +17 -0
- package/dist/serve/http.d.ts +11 -0
- package/dist/serve/options.d.ts +3 -1
- package/dist/server/options.d.ts +41 -0
- package/package.json +1 -1
package/dist/native/options.d.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { type CapletsMode, type CapletsServerEnv, type CapletsServerInput } from "../server/options";
|
|
2
|
+
export type NativeCapletsMode = CapletsMode;
|
|
2
3
|
export type NativeRemoteCapletsOptions = {
|
|
3
|
-
url?: string;
|
|
4
|
-
user?: string;
|
|
5
|
-
password?: string;
|
|
6
4
|
pollIntervalMs?: number;
|
|
7
5
|
fetch?: typeof fetch;
|
|
8
6
|
};
|
|
9
7
|
export type NativeCapletsServiceResolutionInput = {
|
|
10
8
|
mode?: NativeCapletsMode;
|
|
9
|
+
server?: CapletsServerInput;
|
|
11
10
|
remote?: NativeRemoteCapletsOptions;
|
|
12
11
|
};
|
|
13
|
-
export type NativeCapletsEnv =
|
|
12
|
+
export type NativeCapletsEnv = CapletsServerEnv;
|
|
14
13
|
export type NativeRemoteAuthOptions = {
|
|
15
14
|
enabled: false;
|
|
16
15
|
user: string;
|
package/dist/native.js
CHANGED
|
@@ -1,76 +1,33 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $ as ToolListChangedNotificationSchema, Nt as CapletsError, a as resolveCapletsServer, i as resolveCapletsMode, n as mcpUrlForBase, o as CapletsEngine, s as generatedToolInputSchema, u as capabilityDescription, v as StreamableHTTPClientTransport, x as Client } from "./options-CJEOqS87.js";
|
|
2
2
|
import { generatedToolInputJsonSchema } from "./generated-tool-input-schema.js";
|
|
3
|
-
import { Buffer } from "node:buffer";
|
|
4
3
|
//#region src/native/options.ts
|
|
5
|
-
const DEFAULT_REMOTE_USER = "caplets";
|
|
6
4
|
const DEFAULT_POLL_INTERVAL_MS = 3e4;
|
|
7
5
|
function resolveNativeCapletsServiceOptions(input = {}, env = process.env) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "Remote Caplets Basic Auth requires a password; set CAPLETS_REMOTE_PASSWORD or remote.password.");
|
|
18
|
-
const auth = password === void 0 ? {
|
|
19
|
-
enabled: false,
|
|
20
|
-
user
|
|
21
|
-
} : {
|
|
22
|
-
enabled: true,
|
|
23
|
-
user,
|
|
24
|
-
password
|
|
25
|
-
};
|
|
26
|
-
const requestInit = auth.enabled ? { headers: { Authorization: basicAuthHeader(auth.user, auth.password) } } : {};
|
|
6
|
+
if (resolveCapletsMode({
|
|
7
|
+
...input.mode ? { mode: input.mode } : {},
|
|
8
|
+
...input.server?.url ? { serverUrl: input.server.url } : {}
|
|
9
|
+
}, env).mode === "local") return { mode: "local" };
|
|
10
|
+
const serverFetch = input.remote?.fetch ?? input.server?.fetch;
|
|
11
|
+
const server = resolveCapletsServer({
|
|
12
|
+
...input.server,
|
|
13
|
+
...serverFetch ? { fetch: serverFetch } : {}
|
|
14
|
+
}, env);
|
|
27
15
|
return {
|
|
28
16
|
mode: "remote",
|
|
29
17
|
remote: {
|
|
30
|
-
url,
|
|
31
|
-
auth,
|
|
18
|
+
url: mcpUrlForBase(server.baseUrl),
|
|
19
|
+
auth: server.auth,
|
|
32
20
|
pollIntervalMs: parsePollInterval(input.remote?.pollIntervalMs),
|
|
33
|
-
requestInit,
|
|
34
|
-
...
|
|
21
|
+
requestInit: server.requestInit,
|
|
22
|
+
...server.fetch ? { fetch: server.fetch } : {}
|
|
35
23
|
}
|
|
36
24
|
};
|
|
37
25
|
}
|
|
38
|
-
function parseMode(value) {
|
|
39
|
-
if (value === "auto" || value === "local" || value === "remote") return value;
|
|
40
|
-
throw new CapletsError("REQUEST_INVALID", `Expected CAPLETS_NATIVE_MODE to be auto, local, or remote, got ${value}`);
|
|
41
|
-
}
|
|
42
|
-
function parseRemoteUrl(value) {
|
|
43
|
-
let url;
|
|
44
|
-
try {
|
|
45
|
-
url = new URL(value);
|
|
46
|
-
} catch {
|
|
47
|
-
throw new CapletsError("REQUEST_INVALID", "Invalid remote Caplets URL.");
|
|
48
|
-
}
|
|
49
|
-
if (url.username !== "" || url.password !== "") throw new CapletsError("REQUEST_INVALID", "Remote Caplets URL must not include username or password; use CAPLETS_REMOTE_USER/CAPLETS_REMOTE_PASSWORD or remote.user/remote.password instead.");
|
|
50
|
-
if (url.protocol === "https:") return url;
|
|
51
|
-
if (url.protocol === "http:" && isLoopbackHost(url.hostname)) return url;
|
|
52
|
-
throw new CapletsError("REQUEST_INVALID", "Remote Caplets URL must use https except loopback development URLs.");
|
|
53
|
-
}
|
|
54
|
-
function isLoopbackHost(host) {
|
|
55
|
-
return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
|
|
56
|
-
}
|
|
57
26
|
function parsePollInterval(value) {
|
|
58
27
|
if (value === void 0) return DEFAULT_POLL_INTERVAL_MS;
|
|
59
28
|
if (!Number.isInteger(value) || value < 1e3) throw new CapletsError("REQUEST_INVALID", "remote.pollIntervalMs must be an integer >= 1000.");
|
|
60
29
|
return value;
|
|
61
30
|
}
|
|
62
|
-
function basicAuthHeader(user, password) {
|
|
63
|
-
return `Basic ${Buffer.from(`${user}:${password}`).toString("base64")}`;
|
|
64
|
-
}
|
|
65
|
-
function nonEmpty(value, label) {
|
|
66
|
-
if (value === void 0) return;
|
|
67
|
-
const trimmed = value.trim();
|
|
68
|
-
if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
|
|
69
|
-
return trimmed;
|
|
70
|
-
}
|
|
71
|
-
function hasEnv(value) {
|
|
72
|
-
return value !== void 0 && value.trim() !== "";
|
|
73
|
-
}
|
|
74
31
|
//#endregion
|
|
75
32
|
//#region src/native/tools.ts
|
|
76
33
|
function nativeCapletToolName(capletId) {
|
|
@@ -281,7 +238,7 @@ function errorMessage(error) {
|
|
|
281
238
|
return error instanceof Error ? error.message : String(error);
|
|
282
239
|
}
|
|
283
240
|
function remoteAuthError() {
|
|
284
|
-
return new CapletsError("AUTH_FAILED", "Remote Caplets authentication failed; check
|
|
241
|
+
return new CapletsError("AUTH_FAILED", "Remote Caplets authentication failed; check CAPLETS_SERVER_USER and CAPLETS_SERVER_PASSWORD.");
|
|
285
242
|
}
|
|
286
243
|
function isSessionFailure(error) {
|
|
287
244
|
const message = errorMessage(error).toLowerCase();
|
|
@@ -9,6 +9,7 @@ import { homedir } from "node:os";
|
|
|
9
9
|
import { PassThrough } from "node:stream";
|
|
10
10
|
import { createServer } from "node:http";
|
|
11
11
|
import { createHash, randomBytes } from "node:crypto";
|
|
12
|
+
import { Buffer as Buffer$1 } from "node:buffer";
|
|
12
13
|
//#region \0rolldown/runtime.js
|
|
13
14
|
var __create = Object.create;
|
|
14
15
|
var __defProp = Object.defineProperty;
|
|
@@ -34,6 +35,25 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
35
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
35
36
|
//#endregion
|
|
36
37
|
//#region src/errors.ts
|
|
38
|
+
const CAPLETS_ERROR_CODES = [
|
|
39
|
+
"CONFIG_NOT_FOUND",
|
|
40
|
+
"CONFIG_EXISTS",
|
|
41
|
+
"CONFIG_INVALID",
|
|
42
|
+
"REQUEST_INVALID",
|
|
43
|
+
"SERVER_NOT_FOUND",
|
|
44
|
+
"SERVER_UNAVAILABLE",
|
|
45
|
+
"SERVER_START_TIMEOUT",
|
|
46
|
+
"UNKNOWN_OPERATION",
|
|
47
|
+
"TOOL_NOT_FOUND",
|
|
48
|
+
"TOOL_CALL_TIMEOUT",
|
|
49
|
+
"AUTH_REQUIRED",
|
|
50
|
+
"AUTH_FAILED",
|
|
51
|
+
"AUTH_REFRESH_FAILED",
|
|
52
|
+
"DOWNSTREAM_PROTOCOL_ERROR",
|
|
53
|
+
"DOWNSTREAM_TOOL_ERROR",
|
|
54
|
+
"UNSUPPORTED_TRANSPORT",
|
|
55
|
+
"INTERNAL_ERROR"
|
|
56
|
+
];
|
|
37
57
|
var CapletsError = class extends Error {
|
|
38
58
|
code;
|
|
39
59
|
details;
|
|
@@ -31189,8 +31209,45 @@ var FileOAuthProvider = class {
|
|
|
31189
31209
|
headers.set("content-type", "application/x-www-form-urlencoded");
|
|
31190
31210
|
};
|
|
31191
31211
|
};
|
|
31192
|
-
async function
|
|
31212
|
+
async function startOAuthFlow(server, options) {
|
|
31193
31213
|
if (server.transport === "stdio" || !server.url || server.auth?.type !== "oauth2" && server.auth?.type !== "oidc") throw new CapletsError("REQUEST_INVALID", `${server.server} is not a configured OAuth remote server`);
|
|
31214
|
+
let redirectUrl;
|
|
31215
|
+
const provider = new FileOAuthProvider(server, options.redirectUri, (url) => {
|
|
31216
|
+
redirectUrl = url;
|
|
31217
|
+
options.print?.(`Open this URL to authorize ${server.server}:\n${url.toString()}`);
|
|
31218
|
+
}, options.authDir);
|
|
31219
|
+
const scope = scopesFor(server.auth);
|
|
31220
|
+
try {
|
|
31221
|
+
if (await auth(provider, {
|
|
31222
|
+
serverUrl: server.url,
|
|
31223
|
+
...scope ? { scope } : {}
|
|
31224
|
+
}) === "AUTHORIZED") return {
|
|
31225
|
+
authorizationUrl: "",
|
|
31226
|
+
complete: async () => {}
|
|
31227
|
+
};
|
|
31228
|
+
} catch (error) {
|
|
31229
|
+
throw normalizeMcpOAuthError(server, error);
|
|
31230
|
+
}
|
|
31231
|
+
if (!redirectUrl) throw new CapletsError("AUTH_FAILED", "OAuth authorization URL was not provided");
|
|
31232
|
+
return {
|
|
31233
|
+
authorizationUrl: redirectUrl.toString(),
|
|
31234
|
+
complete: async (callbackUrl) => {
|
|
31235
|
+
assertNoOAuthCallbackError(server, callbackUrl);
|
|
31236
|
+
const completion = extractCompletion(callbackUrl);
|
|
31237
|
+
if (completion.state !== provider.state()) throw new CapletsError("AUTH_FAILED", "OAuth callback state did not match");
|
|
31238
|
+
try {
|
|
31239
|
+
await auth(provider, {
|
|
31240
|
+
serverUrl: server.url,
|
|
31241
|
+
authorizationCode: completion.code,
|
|
31242
|
+
...scope ? { scope } : {}
|
|
31243
|
+
});
|
|
31244
|
+
} catch (error) {
|
|
31245
|
+
throw normalizeMcpOAuthError(server, error);
|
|
31246
|
+
}
|
|
31247
|
+
}
|
|
31248
|
+
};
|
|
31249
|
+
}
|
|
31250
|
+
async function runOAuthFlow(server, options = {}) {
|
|
31194
31251
|
let callbackCode;
|
|
31195
31252
|
let callbackState;
|
|
31196
31253
|
const callback = await createLoopbackCallback((url) => {
|
|
@@ -31198,37 +31255,43 @@ async function runOAuthFlow(server, options = {}) {
|
|
|
31198
31255
|
callbackCode = url.searchParams.get("code") ?? void 0;
|
|
31199
31256
|
callbackState = url.searchParams.get("state") ?? void 0;
|
|
31200
31257
|
});
|
|
31201
|
-
let redirectUrl;
|
|
31202
|
-
const provider = new FileOAuthProvider(server, callback.redirectUri, (url) => {
|
|
31203
|
-
redirectUrl = url;
|
|
31204
|
-
options.print?.(`Open this URL to authorize ${server.server}:\n${url.toString()}`);
|
|
31205
|
-
}, options.authDir);
|
|
31206
31258
|
try {
|
|
31207
|
-
const
|
|
31208
|
-
|
|
31209
|
-
|
|
31210
|
-
...
|
|
31259
|
+
const started = await startOAuthFlow(server, {
|
|
31260
|
+
redirectUri: callback.redirectUri,
|
|
31261
|
+
...options.authDir ? { authDir: options.authDir } : {},
|
|
31262
|
+
...options.print ? { print: options.print } : {}
|
|
31211
31263
|
});
|
|
31212
|
-
if (
|
|
31213
|
-
if (!options.noOpen
|
|
31264
|
+
if (!started.authorizationUrl) return "AUTHORIZED";
|
|
31265
|
+
if (!options.noOpen) await (options.open ? options.open(started.authorizationUrl) : openBrowser(started.authorizationUrl));
|
|
31214
31266
|
const manualInput = options.manualInput ?? (options.noOpen ? await options.readManualInput?.() : void 0);
|
|
31215
31267
|
const completion = manualInput ? extractCompletion(manualInput) : await callback.waitForCode(() => callbackCode ? {
|
|
31216
31268
|
code: callbackCode,
|
|
31217
31269
|
...callbackState ? { state: callbackState } : {}
|
|
31218
31270
|
} : void 0);
|
|
31219
|
-
|
|
31220
|
-
|
|
31221
|
-
return await auth(provider, {
|
|
31222
|
-
serverUrl: server.url,
|
|
31223
|
-
authorizationCode: completion.code,
|
|
31224
|
-
...scope ? { scope } : {}
|
|
31225
|
-
});
|
|
31271
|
+
await started.complete(completion.state ? `${callback.redirectUri}?code=${encodeURIComponent(completion.code)}&state=${encodeURIComponent(completion.state)}` : `${callback.redirectUri}?code=${encodeURIComponent(completion.code)}`);
|
|
31272
|
+
return "AUTHORIZED";
|
|
31226
31273
|
} catch (error) {
|
|
31227
31274
|
throw normalizeMcpOAuthError(server, error);
|
|
31228
31275
|
} finally {
|
|
31229
31276
|
await callback.close();
|
|
31230
31277
|
}
|
|
31231
31278
|
}
|
|
31279
|
+
function assertNoOAuthCallbackError(target, callbackUrl) {
|
|
31280
|
+
let url;
|
|
31281
|
+
try {
|
|
31282
|
+
url = new URL(callbackUrl);
|
|
31283
|
+
} catch {
|
|
31284
|
+
return;
|
|
31285
|
+
}
|
|
31286
|
+
const error = url.searchParams.get("error");
|
|
31287
|
+
if (!error) return;
|
|
31288
|
+
const description = url.searchParams.get("error_description");
|
|
31289
|
+
throw new CapletsError("AUTH_FAILED", description ? `OAuth provider returned an error: ${description}` : "OAuth provider returned an error", redactSecrets({
|
|
31290
|
+
server: target.server,
|
|
31291
|
+
error,
|
|
31292
|
+
error_description: description ?? void 0
|
|
31293
|
+
}));
|
|
31294
|
+
}
|
|
31232
31295
|
function normalizeMcpOAuthError(server, error) {
|
|
31233
31296
|
if ((server.auth?.type === "oauth2" || server.auth?.type === "oidc") && !server.auth.clientId && !server.auth.clientMetadataUrl && error instanceof Error && error.message.includes("does not support dynamic client registration")) return new CapletsError("AUTH_FAILED", "OAuth is not available for this server without a host-specific OAuth app or PAT auth", {
|
|
31234
31297
|
server: server.server,
|
|
@@ -31236,6 +31299,75 @@ function normalizeMcpOAuthError(server, error) {
|
|
|
31236
31299
|
});
|
|
31237
31300
|
return error;
|
|
31238
31301
|
}
|
|
31302
|
+
async function startGenericOAuthFlow(target, options) {
|
|
31303
|
+
if (target.auth?.type !== "oauth2" && target.auth?.type !== "oidc") throw new CapletsError("REQUEST_INVALID", `${target.server} is not configured for OAuth`);
|
|
31304
|
+
const authConfig = target.auth;
|
|
31305
|
+
const redirectUri = authConfig.redirectUri ?? options.redirectUri;
|
|
31306
|
+
const verifier = base64url(randomBytes(32));
|
|
31307
|
+
const state = base64url(randomBytes(24));
|
|
31308
|
+
const allowLoopbackHttp = isLoopbackDevelopmentTarget(target, authConfig);
|
|
31309
|
+
const metadata = await discoverAuthorizationServer(target, authConfig, allowLoopbackHttp);
|
|
31310
|
+
const authorizationEndpoint = authConfig.authorizationUrl ?? metadata.authorization_endpoint;
|
|
31311
|
+
const tokenEndpoint = authConfig.tokenUrl ?? metadata.token_endpoint;
|
|
31312
|
+
if (!authorizationEndpoint || !tokenEndpoint) throw new CapletsError("AUTH_FAILED", "OAuth metadata is missing endpoints", { server: target.server });
|
|
31313
|
+
assertAllowedAuthUrl(authorizationEndpoint, "authorization endpoint", allowLoopbackHttp);
|
|
31314
|
+
assertAllowedAuthUrl(tokenEndpoint, "token endpoint", allowLoopbackHttp);
|
|
31315
|
+
const client = await resolveGenericClient(target, authConfig, metadata, redirectUri, allowLoopbackHttp);
|
|
31316
|
+
const scope = scopesFor(authConfig);
|
|
31317
|
+
const authorizationUrl = new URL(authorizationEndpoint);
|
|
31318
|
+
authorizationUrl.searchParams.set("response_type", "code");
|
|
31319
|
+
authorizationUrl.searchParams.set("client_id", client.clientId);
|
|
31320
|
+
authorizationUrl.searchParams.set("redirect_uri", redirectUri);
|
|
31321
|
+
authorizationUrl.searchParams.set("code_challenge", pkceChallenge(verifier));
|
|
31322
|
+
authorizationUrl.searchParams.set("code_challenge_method", "S256");
|
|
31323
|
+
authorizationUrl.searchParams.set("state", state);
|
|
31324
|
+
if (scope) authorizationUrl.searchParams.set("scope", scope);
|
|
31325
|
+
options.print?.(`Open this URL to authorize ${target.server}:\n${authorizationUrl.toString()}`);
|
|
31326
|
+
return {
|
|
31327
|
+
authorizationUrl: authorizationUrl.toString(),
|
|
31328
|
+
complete: async (callbackUrl) => {
|
|
31329
|
+
assertNoOAuthCallbackError(target, callbackUrl);
|
|
31330
|
+
const completion = extractCompletion(callbackUrl);
|
|
31331
|
+
if (completion.state !== state) throw new CapletsError("AUTH_FAILED", "OAuth callback state did not match");
|
|
31332
|
+
const params = new URLSearchParams({
|
|
31333
|
+
grant_type: "authorization_code",
|
|
31334
|
+
code: completion.code,
|
|
31335
|
+
redirect_uri: redirectUri,
|
|
31336
|
+
client_id: client.clientId,
|
|
31337
|
+
code_verifier: verifier
|
|
31338
|
+
});
|
|
31339
|
+
if (client.clientSecret) params.set("client_secret", client.clientSecret);
|
|
31340
|
+
const tokenResponse = await fetchJson(tokenEndpoint, target.requestTimeoutMs, {
|
|
31341
|
+
method: "POST",
|
|
31342
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
31343
|
+
body: params.toString()
|
|
31344
|
+
}, allowLoopbackHttp);
|
|
31345
|
+
const idToken = asString(tokenResponse.id_token);
|
|
31346
|
+
const idClaims = parseJwtPayload(idToken);
|
|
31347
|
+
validateOidcToken(authConfig, metadata, idToken, idClaims, client.clientId);
|
|
31348
|
+
writeTokenBundle(stripUndefined({
|
|
31349
|
+
server: target.server,
|
|
31350
|
+
authType: authConfig.type,
|
|
31351
|
+
accessToken: requireString(tokenResponse.access_token, "access_token"),
|
|
31352
|
+
refreshToken: asString(tokenResponse.refresh_token),
|
|
31353
|
+
tokenType: asString(tokenResponse.token_type),
|
|
31354
|
+
expiresAt: typeof tokenResponse.expires_in === "number" ? new Date(Date.now() + tokenResponse.expires_in * 1e3).toISOString() : void 0,
|
|
31355
|
+
scope: asString(tokenResponse.scope) ?? scope,
|
|
31356
|
+
idToken,
|
|
31357
|
+
issuer: asString(idClaims?.iss) ?? metadata.issuer ?? authConfig.issuer,
|
|
31358
|
+
subject: asString(idClaims?.sub),
|
|
31359
|
+
clientId: client.clientId,
|
|
31360
|
+
clientSecret: client.clientSecret,
|
|
31361
|
+
protectedResourceOrigin: protectedResourceOrigin(target, authConfig),
|
|
31362
|
+
metadata: redactSecrets({
|
|
31363
|
+
protectedResource: target.url ?? target.baseUrl ?? target.specUrl,
|
|
31364
|
+
authorizationServer: metadata,
|
|
31365
|
+
dynamicClient: client.dynamic ? { client_id: client.clientId } : void 0
|
|
31366
|
+
})
|
|
31367
|
+
}), options.authDir);
|
|
31368
|
+
}
|
|
31369
|
+
};
|
|
31370
|
+
}
|
|
31239
31371
|
async function runGenericOAuthFlow(target, options = {}) {
|
|
31240
31372
|
if (target.auth?.type !== "oauth2" && target.auth?.type !== "oidc") throw new CapletsError("REQUEST_INVALID", `${target.server} is not configured for OAuth`);
|
|
31241
31373
|
const authConfig = target.auth;
|
|
@@ -58156,4 +58288,90 @@ function isDirectory(path) {
|
|
|
58156
58288
|
}
|
|
58157
58289
|
}
|
|
58158
58290
|
//#endregion
|
|
58159
|
-
|
|
58291
|
+
//#region src/server/options.ts
|
|
58292
|
+
const DEFAULT_SERVER_USER = "caplets";
|
|
58293
|
+
function resolveCapletsMode(input = {}, env = process.env) {
|
|
58294
|
+
const mode = parseCapletsMode(input.mode ?? env.CAPLETS_MODE ?? "auto");
|
|
58295
|
+
if (mode === "local") return { mode: "local" };
|
|
58296
|
+
const rawUrl = nonEmpty(input.serverUrl, "serverUrl") ?? nonEmpty(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL");
|
|
58297
|
+
if (mode === "remote") {
|
|
58298
|
+
if (rawUrl === void 0) throw new CapletsError("REQUEST_INVALID", "CAPLETS_MODE=remote requires CAPLETS_SERVER_URL or serverUrl.");
|
|
58299
|
+
return { mode: "remote" };
|
|
58300
|
+
}
|
|
58301
|
+
return rawUrl === void 0 ? { mode: "local" } : { mode: "remote" };
|
|
58302
|
+
}
|
|
58303
|
+
function resolveCapletsServer(input = {}, env = process.env) {
|
|
58304
|
+
const rawUrl = nonEmpty(input.url, "url") ?? nonEmpty(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL");
|
|
58305
|
+
if (rawUrl === void 0) throw new CapletsError("REQUEST_INVALID", "CAPLETS_SERVER_URL or url is required.");
|
|
58306
|
+
const baseUrl = parseServerBaseUrl(rawUrl);
|
|
58307
|
+
const userWasExplicit = input.user !== void 0 || hasEnv(env.CAPLETS_SERVER_USER);
|
|
58308
|
+
const user = nonEmpty(input.user, "user") ?? nonEmpty(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? DEFAULT_SERVER_USER;
|
|
58309
|
+
const password = nonEmpty(input.password, "password") ?? nonEmpty(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
|
|
58310
|
+
if (userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "Caplets server Basic Auth requires a password; set CAPLETS_SERVER_PASSWORD or password.");
|
|
58311
|
+
const auth = password === void 0 ? {
|
|
58312
|
+
enabled: false,
|
|
58313
|
+
user
|
|
58314
|
+
} : {
|
|
58315
|
+
enabled: true,
|
|
58316
|
+
user,
|
|
58317
|
+
password
|
|
58318
|
+
};
|
|
58319
|
+
const requestInit = auth.enabled ? { headers: { Authorization: basicAuthHeader(auth.user, auth.password) } } : {};
|
|
58320
|
+
return {
|
|
58321
|
+
baseUrl,
|
|
58322
|
+
mcpUrl: mcpUrlForBase(baseUrl),
|
|
58323
|
+
controlUrl: controlUrlForBase(baseUrl),
|
|
58324
|
+
healthUrl: healthUrlForBase(baseUrl),
|
|
58325
|
+
auth,
|
|
58326
|
+
requestInit,
|
|
58327
|
+
...input.fetch ? { fetch: input.fetch } : {}
|
|
58328
|
+
};
|
|
58329
|
+
}
|
|
58330
|
+
function mcpUrlForBase(baseUrl) {
|
|
58331
|
+
return appendBasePath(baseUrl, "mcp");
|
|
58332
|
+
}
|
|
58333
|
+
function controlUrlForBase(baseUrl) {
|
|
58334
|
+
return appendBasePath(baseUrl, "control");
|
|
58335
|
+
}
|
|
58336
|
+
function healthUrlForBase(baseUrl) {
|
|
58337
|
+
return appendBasePath(baseUrl, "healthz");
|
|
58338
|
+
}
|
|
58339
|
+
function appendBasePath(baseUrl, path) {
|
|
58340
|
+
const url = new URL(baseUrl.href);
|
|
58341
|
+
url.pathname = `${url.pathname === "/" ? "" : url.pathname}/${path}`;
|
|
58342
|
+
return url;
|
|
58343
|
+
}
|
|
58344
|
+
function parseServerBaseUrl(value) {
|
|
58345
|
+
let url;
|
|
58346
|
+
try {
|
|
58347
|
+
url = new URL(value);
|
|
58348
|
+
} catch {
|
|
58349
|
+
throw new CapletsError("REQUEST_INVALID", "Invalid Caplets server URL.");
|
|
58350
|
+
}
|
|
58351
|
+
if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "") throw new CapletsError("REQUEST_INVALID", "Caplets server URL must not include username, password, query string, or fragment.");
|
|
58352
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost(url.hostname))) throw new CapletsError("REQUEST_INVALID", "Caplets server URL must use https except loopback development URLs.");
|
|
58353
|
+
url.pathname = url.pathname === "/" ? "/" : url.pathname.replace(/\/+$/u, "");
|
|
58354
|
+
return url;
|
|
58355
|
+
}
|
|
58356
|
+
function isLoopbackHost(host) {
|
|
58357
|
+
const normalized = host.toLocaleLowerCase();
|
|
58358
|
+
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
|
|
58359
|
+
}
|
|
58360
|
+
function parseCapletsMode(value) {
|
|
58361
|
+
if (value === "auto" || value === "local" || value === "remote") return value;
|
|
58362
|
+
throw new CapletsError("REQUEST_INVALID", `Expected CAPLETS_MODE to be auto, local, or remote, got ${value}`);
|
|
58363
|
+
}
|
|
58364
|
+
function basicAuthHeader(user, password) {
|
|
58365
|
+
return `Basic ${Buffer$1.from(`${user}:${password}`).toString("base64")}`;
|
|
58366
|
+
}
|
|
58367
|
+
function nonEmpty(value, label) {
|
|
58368
|
+
if (value === void 0) return;
|
|
58369
|
+
const trimmed = value.trim();
|
|
58370
|
+
if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
|
|
58371
|
+
return trimmed;
|
|
58372
|
+
}
|
|
58373
|
+
function hasEnv(value) {
|
|
58374
|
+
return value !== void 0 && value.trim() !== "";
|
|
58375
|
+
}
|
|
58376
|
+
//#endregion
|
|
58377
|
+
export { ToolListChangedNotificationSchema as $, CompleteRequestSchema as A, string as At, InitializedNotificationSchema as B, assertToolsCallTaskCapability as C, resolveProjectConfigPath as Ct, toJsonSchemaCompat as D, ZodOptional$1 as Dt, mergeCapabilities as E, SERVER_ID_PATTERN as Et, ElicitResultSchema as F, toSafeError as Ft, ListResourcesRequestSchema as G, LATEST_PROTOCOL_VERSION as H, EmptyResultSchema as I, __commonJSMin as It, LoggingLevelSchema as J, ListRootsResultSchema as K, ErrorCode as L, __require as Lt, CreateMessageResultWithToolsSchema as M, CAPLETS_ERROR_CODES as Mt, CreateTaskResultSchema as N, CapletsError as Nt, CallToolRequestSchema as O, literal as Ot, DEFAULT_NEGOTIATED_PROTOCOL_VERSION as P, redactSecrets as Pt, SetLevelRequestSchema as Q, GetPromptRequestSchema as R, __toESM as Rt, assertClientRequestTaskCapability as S, resolveProjectCapletsRoot as St, Protocol as T, validateCapletFile as Tt, ListPromptsRequestSchema as U, JSONRPCMessageSchema as V, ListResourceTemplatesRequestSchema as W, ReadResourceRequestSchema as X, McpError as Y, SUPPORTED_PROTOCOL_VERSIONS as Z, readTokenBundle as _, loadConfigWithSources as _t, resolveCapletsServer as a, isJSONRPCResultResponse as at, serializeMessage as b, resolveCapletsRoot as bt, handleServerTool as c, getParseErrorMessage as ct, runGenericOAuthFlow as d, isZ4Schema as dt, assertCompleteRequestPrompt as et, runOAuthFlow as f, normalizeObjectSchema as ft, isTokenBundleExpired as g, loadConfig as gt, deleteTokenBundle as h, safeParseAsync as ht, resolveCapletsMode as i, isJSONRPCRequest as it, CreateMessageResultSchema as j, url as jt, CallToolResultSchema as k, object$1 as kt, ServerRegistry as l, getSchemaDescription as lt, startOAuthFlow as m, safeParse as mt, mcpUrlForBase as n, isInitializeRequest as nt, CapletsEngine as o, getLiteralValue as ot, startGenericOAuthFlow as p, objectFromShape as pt, ListToolsRequestSchema as q, parseServerBaseUrl as r, isJSONRPCErrorResponse as rt, generatedToolInputSchema as s, getObjectShape as st, controlUrlForBase as t, assertCompleteRequestResourceTemplate as tt, capabilityDescription as u, isSchemaOptional as ut, StreamableHTTPClientTransport as v, parseConfig as vt, AjvJsonSchemaValidator as w, discoverCapletFiles as wt, Client as x, resolveConfigPath as xt, ReadBuffer as y, DEFAULT_AUTH_DIR as yt, InitializeRequestSchema as z };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type RemoteAuthFlow = {
|
|
2
|
+
id: string;
|
|
3
|
+
server: string;
|
|
4
|
+
authorizationUrl: string;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
complete(callbackUrl: string): Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
export type RemoteAuthFlowStoreOptions = {
|
|
9
|
+
ttlMs?: number;
|
|
10
|
+
now?: () => number;
|
|
11
|
+
};
|
|
12
|
+
export declare class RemoteAuthFlowStore {
|
|
13
|
+
private readonly options;
|
|
14
|
+
private readonly flows;
|
|
15
|
+
constructor(options?: RemoteAuthFlowStoreOptions);
|
|
16
|
+
create(flow: Omit<RemoteAuthFlow, "id" | "createdAt">, id?: string): RemoteAuthFlow;
|
|
17
|
+
get(id: string): RemoteAuthFlow | undefined;
|
|
18
|
+
delete(id: string): void;
|
|
19
|
+
private pruneExpired;
|
|
20
|
+
private isExpired;
|
|
21
|
+
private now;
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RemoteCliCommand, RemoteCliRequest } from "./types";
|
|
2
|
+
export type RemoteControlClientOptions = {
|
|
3
|
+
baseUrl: URL;
|
|
4
|
+
requestInit: RequestInit;
|
|
5
|
+
fetch?: typeof fetch;
|
|
6
|
+
};
|
|
7
|
+
export declare class RemoteControlClient {
|
|
8
|
+
#private;
|
|
9
|
+
constructor(options: RemoteControlClientOptions);
|
|
10
|
+
request(command: RemoteCliCommand, args: RemoteCliRequest["arguments"]): Promise<unknown>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type CapletsEngineOptions } from "../engine";
|
|
2
|
+
import type { RemoteAuthFlowStore } from "./auth-flow";
|
|
3
|
+
import type { RemoteCliRequest, RemoteCliResponse } from "./types";
|
|
4
|
+
export type RemoteControlDispatchContext = CapletsEngineOptions & {
|
|
5
|
+
projectCapletsRoot: string;
|
|
6
|
+
authFlowStore?: RemoteAuthFlowStore;
|
|
7
|
+
controlCallbackBaseUrl?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function dispatchRemoteCliRequest(request: RemoteCliRequest, context: RemoteControlDispatchContext): Promise<RemoteCliResponse>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CapletsErrorCode } from "../errors";
|
|
2
|
+
export type RemoteCliCommand = "list" | "get_caplet" | "check_backend" | "list_tools" | "search_tools" | "get_tool" | "call_tool" | "init" | "add" | "install" | "auth_login_start" | "auth_login_complete" | "auth_logout" | "auth_list";
|
|
3
|
+
export type RemoteCliRequest = {
|
|
4
|
+
command: RemoteCliCommand;
|
|
5
|
+
arguments: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
export type RemoteCliResponse = {
|
|
8
|
+
ok: true;
|
|
9
|
+
result: unknown;
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
error: {
|
|
13
|
+
code: CapletsErrorCode;
|
|
14
|
+
message: string;
|
|
15
|
+
nextAction?: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
package/dist/serve/http.d.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { CapletsEngine, type CapletsEngineOptions } from "../engine";
|
|
3
|
+
import { type RemoteControlDispatchContext } from "../remote-control/dispatch";
|
|
4
|
+
import { RemoteAuthFlowStore } from "../remote-control/auth-flow";
|
|
3
5
|
import type { HttpServeOptions } from "./options";
|
|
4
6
|
type HttpServeIo = {
|
|
5
7
|
writeErr?: (value: string) => void;
|
|
8
|
+
control?: Omit<RemoteControlDispatchContext, "writeErr">;
|
|
9
|
+
authFlowStore?: RemoteAuthFlowStore;
|
|
6
10
|
};
|
|
7
11
|
export type CapletsHttpApp = Hono & {
|
|
8
12
|
closeCapletsSessions: () => Promise<void>;
|
|
9
13
|
};
|
|
10
14
|
export declare function createHttpServeApp(options: HttpServeOptions, engine: CapletsEngine, io?: HttpServeIo): CapletsHttpApp;
|
|
11
15
|
export declare function serveHttp(options: HttpServeOptions, engineOptions?: CapletsEngineOptions, writeErr?: (value: string) => void): Promise<void>;
|
|
16
|
+
export declare function routePath(base: string, path: string): string;
|
|
17
|
+
export declare function servicePaths(base: string): {
|
|
18
|
+
base: string;
|
|
19
|
+
mcp: string;
|
|
20
|
+
control: string;
|
|
21
|
+
health: string;
|
|
22
|
+
};
|
|
12
23
|
export {};
|
package/dist/serve/options.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type RawServeOptions = {
|
|
|
7
7
|
user?: string;
|
|
8
8
|
password?: string;
|
|
9
9
|
allowUnauthenticatedHttp?: boolean;
|
|
10
|
+
trustProxy?: boolean;
|
|
10
11
|
};
|
|
11
12
|
export type StdioServeOptions = {
|
|
12
13
|
transport: "stdio";
|
|
@@ -19,6 +20,7 @@ export type HttpServeOptions = {
|
|
|
19
20
|
auth: HttpBasicAuthOptions;
|
|
20
21
|
warnUnauthenticatedNetwork: boolean;
|
|
21
22
|
loopback: boolean;
|
|
23
|
+
trustProxy: boolean;
|
|
22
24
|
};
|
|
23
25
|
export type HttpBasicAuthOptions = {
|
|
24
26
|
enabled: false;
|
|
@@ -29,6 +31,6 @@ export type HttpBasicAuthOptions = {
|
|
|
29
31
|
password: string;
|
|
30
32
|
};
|
|
31
33
|
export type ServeOptions = StdioServeOptions | HttpServeOptions;
|
|
32
|
-
export type ServeEnv = Partial<Record<"CAPLETS_SERVER_USER" | "CAPLETS_SERVER_PASSWORD", string>>;
|
|
34
|
+
export type ServeEnv = Partial<Record<"CAPLETS_SERVER_URL" | "CAPLETS_SERVER_USER" | "CAPLETS_SERVER_PASSWORD", string>>;
|
|
33
35
|
export declare function resolveServeOptions(raw: RawServeOptions, env?: ServeEnv): ServeOptions;
|
|
34
36
|
export declare function isLoopbackHost(host: string): boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type CapletsMode = "auto" | "local" | "remote";
|
|
2
|
+
export type CapletsServerEnv = Partial<Record<"CAPLETS_MODE" | "CAPLETS_SERVER_URL" | "CAPLETS_SERVER_USER" | "CAPLETS_SERVER_PASSWORD", string>>;
|
|
3
|
+
export type CapletsModeInput = {
|
|
4
|
+
mode?: string;
|
|
5
|
+
serverUrl?: string;
|
|
6
|
+
};
|
|
7
|
+
export type CapletsServerInput = {
|
|
8
|
+
url?: string;
|
|
9
|
+
user?: string;
|
|
10
|
+
password?: string;
|
|
11
|
+
fetch?: typeof fetch;
|
|
12
|
+
};
|
|
13
|
+
export type CapletsServerAuth = {
|
|
14
|
+
enabled: false;
|
|
15
|
+
user: string;
|
|
16
|
+
} | {
|
|
17
|
+
enabled: true;
|
|
18
|
+
user: string;
|
|
19
|
+
password: string;
|
|
20
|
+
};
|
|
21
|
+
export type ResolvedCapletsServer = {
|
|
22
|
+
baseUrl: URL;
|
|
23
|
+
mcpUrl: URL;
|
|
24
|
+
controlUrl: URL;
|
|
25
|
+
healthUrl: URL;
|
|
26
|
+
auth: CapletsServerAuth;
|
|
27
|
+
requestInit: RequestInit;
|
|
28
|
+
fetch?: typeof fetch;
|
|
29
|
+
};
|
|
30
|
+
export declare function resolveCapletsMode(input?: CapletsModeInput, env?: CapletsServerEnv): {
|
|
31
|
+
mode: "local";
|
|
32
|
+
} | {
|
|
33
|
+
mode: "remote";
|
|
34
|
+
};
|
|
35
|
+
export declare function resolveCapletsServer(input?: CapletsServerInput, env?: CapletsServerEnv): ResolvedCapletsServer;
|
|
36
|
+
export declare function mcpUrlForBase(baseUrl: URL): URL;
|
|
37
|
+
export declare function controlUrlForBase(baseUrl: URL): URL;
|
|
38
|
+
export declare function healthUrlForBase(baseUrl: URL): URL;
|
|
39
|
+
export declare function appendBasePath(baseUrl: URL, path: string): URL;
|
|
40
|
+
export declare function parseServerBaseUrl(value: string): URL;
|
|
41
|
+
export declare function isLoopbackHost(host: string): boolean;
|