caplets 0.7.0 → 0.9.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/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire } from "node:module";
3
3
  import minproc, { stdin, stdout, default as process$1 } from "node:process";
4
4
  import { execFileSync } from "node:child_process";
5
- import minpath, { basename, dirname, extname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
5
+ import minpath, { basename, dirname, extname, isAbsolute, join, parse, posix, relative, resolve, win32 } from "node:path";
6
6
  import { accessSync, chmodSync, constants, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
7
7
  import { createInterface } from "node:readline/promises";
8
8
  import { createServer } from "node:http";
@@ -180,7 +180,7 @@ const allowsEval = /* @__PURE__ */ cached(() => {
180
180
  return false;
181
181
  }
182
182
  });
183
- function isPlainObject$4(o) {
183
+ function isPlainObject$5(o) {
184
184
  if (isObject(o) === false) return false;
185
185
  const ctor = o.constructor;
186
186
  if (ctor === void 0) return true;
@@ -191,7 +191,7 @@ function isPlainObject$4(o) {
191
191
  return true;
192
192
  }
193
193
  function shallowClone(o) {
194
- if (isPlainObject$4(o)) return { ...o };
194
+ if (isPlainObject$5(o)) return { ...o };
195
195
  if (Array.isArray(o)) return [...o];
196
196
  if (o instanceof Map) return new Map(o);
197
197
  if (o instanceof Set) return new Set(o);
@@ -274,7 +274,7 @@ function omit(schema, mask) {
274
274
  }));
275
275
  }
276
276
  function extend(schema, shape) {
277
- if (!isPlainObject$4(shape)) throw new Error("Invalid input to extend: expected a plain object");
277
+ if (!isPlainObject$5(shape)) throw new Error("Invalid input to extend: expected a plain object");
278
278
  const checks = schema._zod.def.checks;
279
279
  if (checks && checks.length > 0) {
280
280
  const existingShape = schema._zod.def.shape;
@@ -290,7 +290,7 @@ function extend(schema, shape) {
290
290
  } }));
291
291
  }
292
292
  function safeExtend(schema, shape) {
293
- if (!isPlainObject$4(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
293
+ if (!isPlainObject$5(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
294
294
  return clone(schema, mergeDefs(schema._zod.def, { get shape() {
295
295
  const _shape = {
296
296
  ...schema._zod.def.shape,
@@ -1904,7 +1904,7 @@ function mergeValues$1(a, b) {
1904
1904
  valid: true,
1905
1905
  data: a
1906
1906
  };
1907
- if (isPlainObject$4(a) && isPlainObject$4(b)) {
1907
+ if (isPlainObject$5(a) && isPlainObject$5(b)) {
1908
1908
  const bKeys = Object.keys(b);
1909
1909
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
1910
1910
  const newObj = {
@@ -1980,7 +1980,7 @@ const $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
1980
1980
  $ZodType.init(inst, def);
1981
1981
  inst._zod.parse = (payload, ctx) => {
1982
1982
  const input = payload.value;
1983
- if (!isPlainObject$4(input)) {
1983
+ if (!isPlainObject$5(input)) {
1984
1984
  payload.issues.push({
1985
1985
  expected: "record",
1986
1986
  code: "invalid_type",
@@ -9619,7 +9619,7 @@ const { program, createCommand, createArgument, createOption, CommanderError, In
9619
9619
  })))(), 1)).default;
9620
9620
  //#endregion
9621
9621
  //#region package.json
9622
- var version = "0.7.0";
9622
+ var version = "0.9.0";
9623
9623
  //#endregion
9624
9624
  //#region node_modules/.pnpm/pkce-challenge@5.0.1/node_modules/pkce-challenge/dist/index.node.js
9625
9625
  let crypto;
@@ -10658,6 +10658,41 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
10658
10658
  return OAuthClientInformationFullSchema.parse(await response.json());
10659
10659
  }
10660
10660
  //#endregion
10661
+ //#region src/config/paths.ts
10662
+ function defaultConfigBaseDir(env = process.env, home = homedir(), platform = process.platform) {
10663
+ if (platform === "win32") return env.APPDATA && win32.isAbsolute(env.APPDATA) ? env.APPDATA : win32.join(home, "AppData", "Roaming");
10664
+ return env.XDG_CONFIG_HOME && posix.isAbsolute(env.XDG_CONFIG_HOME) ? env.XDG_CONFIG_HOME : posix.join(home, ".config");
10665
+ }
10666
+ function defaultStateBaseDir(env = process.env, home = homedir(), platform = process.platform) {
10667
+ if (platform === "win32") return env.LOCALAPPDATA && win32.isAbsolute(env.LOCALAPPDATA) ? env.LOCALAPPDATA : win32.join(home, "AppData", "Local");
10668
+ return env.XDG_STATE_HOME && posix.isAbsolute(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : posix.join(home, ".local", "state");
10669
+ }
10670
+ function defaultConfigPath(env = process.env, home = homedir(), platform = process.platform) {
10671
+ return (platform === "win32" ? win32.join : posix.join)(defaultConfigBaseDir(env, home, platform), "caplets", "config.json");
10672
+ }
10673
+ function defaultAuthDir(env = process.env, home = homedir(), platform = process.platform) {
10674
+ return (platform === "win32" ? win32.join : posix.join)(defaultStateBaseDir(env, home, platform), "caplets", "auth");
10675
+ }
10676
+ const DEFAULT_CONFIG_PATH = defaultConfigPath();
10677
+ const DEFAULT_AUTH_DIR = defaultAuthDir();
10678
+ const PROJECT_CONFIG_FILE = join(".caplets", "config.json");
10679
+ const TRUST_PROJECT_CAPLETS_ENV = "CAPLETS_TRUST_PROJECT_CAPLETS";
10680
+ function resolveConfigPath(path) {
10681
+ return path ?? DEFAULT_CONFIG_PATH;
10682
+ }
10683
+ function resolveProjectConfigPath(cwd = process.cwd()) {
10684
+ return join(cwd, PROJECT_CONFIG_FILE);
10685
+ }
10686
+ function resolveCapletsRoot(configPath = resolveConfigPath()) {
10687
+ return dirname(configPath);
10688
+ }
10689
+ function resolveProjectCapletsRoot(cwd = process.cwd()) {
10690
+ return join(cwd, ".caplets");
10691
+ }
10692
+ function isTrustedEnvEnabled(value) {
10693
+ return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
10694
+ }
10695
+ //#endregion
10661
10696
  //#region src/errors.ts
10662
10697
  var CapletsError = class extends Error {
10663
10698
  code;
@@ -10708,11 +10743,12 @@ function errorResult(error, fallback) {
10708
10743
  }
10709
10744
  //#endregion
10710
10745
  //#region src/auth/store.ts
10711
- function authStorePath(server, authDir = join(homedir(), ".caplets", "auth")) {
10746
+ function authStorePath(server, authDir = DEFAULT_AUTH_DIR) {
10712
10747
  if (!server || server.includes("/") || server.includes("\\") || server.includes("..")) throw new CapletsError("REQUEST_INVALID", `Invalid auth store server name ${server}`);
10713
10748
  const authRoot = resolve(authDir);
10714
10749
  const candidate = resolve(authRoot, `${server}.json`);
10715
- if (candidate !== authRoot && candidate.startsWith(`${authRoot}${sep}`)) return candidate;
10750
+ const relativePath = relative(authRoot, candidate);
10751
+ if (relativePath && !relativePath.startsWith("..") && !isAbsolute(relativePath)) return candidate;
10716
10752
  throw new CapletsError("REQUEST_INVALID", `Invalid auth store server name ${server}`);
10717
10753
  }
10718
10754
  function readTokenBundle(server, authDir) {
@@ -10780,11 +10816,13 @@ var FileOAuthProvider = class {
10780
10816
  verifier = base64url(randomBytes(32));
10781
10817
  stateValue = base64url(randomBytes(24));
10782
10818
  clientInfo;
10819
+ clientMetadataUrl;
10783
10820
  constructor(server, redirectUrl, onRedirect, authDir) {
10784
10821
  this.server = server;
10785
10822
  this.redirectUrl = redirectUrl;
10786
10823
  this.onRedirect = onRedirect;
10787
10824
  this.authDir = authDir;
10825
+ if ((this.server.auth?.type === "oauth2" || this.server.auth?.type === "oidc") && this.server.auth.clientMetadataUrl) this.clientMetadataUrl = this.server.auth.clientMetadataUrl;
10788
10826
  }
10789
10827
  get clientMetadata() {
10790
10828
  return {
@@ -10884,10 +10922,19 @@ async function runOAuthFlow(server, options = {}) {
10884
10922
  authorizationCode: completion.code,
10885
10923
  ...scope ? { scope } : {}
10886
10924
  });
10925
+ } catch (error) {
10926
+ throw normalizeMcpOAuthError(server, error);
10887
10927
  } finally {
10888
10928
  await callback.close();
10889
10929
  }
10890
10930
  }
10931
+ function normalizeMcpOAuthError(server, error) {
10932
+ 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", {
10933
+ server: server.server,
10934
+ nextAction: "configure_bearer_auth_or_host_oauth_app"
10935
+ });
10936
+ return error;
10937
+ }
10891
10938
  async function runGenericOAuthFlow(target, options = {}) {
10892
10939
  if (target.auth?.type !== "oauth2" && target.auth?.type !== "oidc") throw new CapletsError("REQUEST_INVALID", `${target.server} is not configured for OAuth`);
10893
10940
  const authConfig = target.auth;
@@ -11057,6 +11104,11 @@ async function resolveGenericClient(target, authConfig, metadata, redirectUri, a
11057
11104
  ...authConfig.clientSecret ? { clientSecret: authConfig.clientSecret } : {},
11058
11105
  dynamic: false
11059
11106
  };
11107
+ if (authConfig.clientMetadataUrl) return {
11108
+ clientId: authConfig.clientMetadataUrl,
11109
+ ...authConfig.clientSecret ? { clientSecret: authConfig.clientSecret } : {},
11110
+ dynamic: false
11111
+ };
11060
11112
  if (!metadata.registration_endpoint) throw new CapletsError("AUTH_FAILED", "OAuth clientId is required without dynamic registration", { server: target.server });
11061
11113
  const response = await fetchJson(metadata.registration_endpoint, target.requestTimeoutMs, {
11062
11114
  method: "POST",
@@ -11125,8 +11177,9 @@ function assertAllowedAuthUrl(value, label, allowLoopbackHttp = false) {
11125
11177
  throw new CapletsError("AUTH_FAILED", `${label} must use https except loopback development URLs`);
11126
11178
  }
11127
11179
  function assertTokenBundleMatchesTarget(bundle, target, authConfig) {
11180
+ const configuredClientId = authConfig.clientId ?? authConfig.clientMetadataUrl;
11128
11181
  const expectedOrigin = protectedResourceOrigin(target, authConfig);
11129
- if (bundle.authType !== authConfig.type || expectedOrigin && bundle.protectedResourceOrigin !== expectedOrigin || authConfig.clientId && bundle.clientId !== authConfig.clientId || authConfig.issuer && bundle.issuer !== authConfig.issuer) throw new CapletsError("AUTH_REQUIRED", `OAuth credentials for ${target.server} do not match the configured backend`, {
11182
+ if (bundle.authType !== authConfig.type || expectedOrigin && bundle.protectedResourceOrigin !== expectedOrigin || configuredClientId && bundle.clientId !== configuredClientId || authConfig.issuer && bundle.issuer !== authConfig.issuer) throw new CapletsError("AUTH_REQUIRED", `OAuth credentials for ${target.server} do not match the configured backend`, {
11130
11183
  server: target.server,
11131
11184
  backend: target.backend,
11132
11185
  authType: authConfig.type,
@@ -18680,6 +18733,7 @@ function matter(file, options) {
18680
18733
  //#region src/config/validation.ts
18681
18734
  const SERVER_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
18682
18735
  const HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
18736
+ const HTTP_BASE_URL_PATTERN = /^(?![a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/?#]*@)[^?#]*$/;
18683
18737
  const FORBIDDEN_HEADERS = new Set([
18684
18738
  "accept",
18685
18739
  "authorization",
@@ -18697,6 +18751,16 @@ const FORBIDDEN_HEADERS = new Set([
18697
18751
  "transfer-encoding",
18698
18752
  "upgrade"
18699
18753
  ]);
18754
+ function validateHttpActionHeaders(headers, ctx, path) {
18755
+ for (const headerName of Object.keys(headers)) {
18756
+ const normalized = headerName.toLowerCase();
18757
+ if (!HEADER_NAME_PATTERN.test(headerName) || FORBIDDEN_HEADERS.has(normalized)) ctx.addIssue({
18758
+ code: "custom",
18759
+ path: [...path, headerName],
18760
+ message: `header ${headerName} is not allowed`
18761
+ });
18762
+ }
18763
+ }
18700
18764
  function isAllowedRemoteUrl(value) {
18701
18765
  let url;
18702
18766
  try {
@@ -18712,6 +18776,23 @@ function isAllowedRemoteUrl(value) {
18712
18776
  "::1"
18713
18777
  ].includes(url.hostname);
18714
18778
  }
18779
+ function isAllowedHttpBaseUrl(value) {
18780
+ let url;
18781
+ try {
18782
+ url = new URL(value);
18783
+ } catch {
18784
+ return false;
18785
+ }
18786
+ return isAllowedRemoteUrl(value) && !url.username && !url.password && !url.search && !url.hash;
18787
+ }
18788
+ function isUrl(value) {
18789
+ try {
18790
+ new URL(value);
18791
+ return true;
18792
+ } catch {
18793
+ return false;
18794
+ }
18795
+ }
18715
18796
  //#endregion
18716
18797
  //#region src/caplet-files.ts
18717
18798
  const MAX_CAPLET_FILE_BYTES = 128 * 1024;
@@ -18734,6 +18815,7 @@ const capletRemoteAuthSchema = discriminatedUnion("type", [
18734
18815
  resourceMetadataUrl: string().min(1).optional(),
18735
18816
  authorizationServerMetadataUrl: string().min(1).optional(),
18736
18817
  openidConfigurationUrl: string().min(1).optional(),
18818
+ clientMetadataUrl: string().min(1).optional(),
18737
18819
  clientId: string().min(1).optional(),
18738
18820
  clientSecret: string().min(1).optional(),
18739
18821
  scopes: array(string().min(1)).optional(),
@@ -18747,6 +18829,7 @@ const capletRemoteAuthSchema = discriminatedUnion("type", [
18747
18829
  resourceMetadataUrl: string().min(1).optional(),
18748
18830
  authorizationServerMetadataUrl: string().min(1).optional(),
18749
18831
  openidConfigurationUrl: string().min(1).optional(),
18832
+ clientMetadataUrl: string().min(1).optional(),
18750
18833
  clientId: string().min(1).optional(),
18751
18834
  clientSecret: string().min(1).optional(),
18752
18835
  scopes: array(string().min(1)).optional(),
@@ -18771,6 +18854,7 @@ const capletEndpointAuthSchema = discriminatedUnion("type", [
18771
18854
  resourceMetadataUrl: string().min(1).optional(),
18772
18855
  authorizationServerMetadataUrl: string().min(1).optional(),
18773
18856
  openidConfigurationUrl: string().min(1).optional(),
18857
+ clientMetadataUrl: string().min(1).optional(),
18774
18858
  clientId: string().min(1).optional(),
18775
18859
  clientSecret: string().min(1).optional(),
18776
18860
  scopes: array(string().min(1)).optional(),
@@ -18784,6 +18868,7 @@ const capletEndpointAuthSchema = discriminatedUnion("type", [
18784
18868
  resourceMetadataUrl: string().min(1).optional(),
18785
18869
  authorizationServerMetadataUrl: string().min(1).optional(),
18786
18870
  openidConfigurationUrl: string().min(1).optional(),
18871
+ clientMetadataUrl: string().min(1).optional(),
18787
18872
  clientId: string().min(1).optional(),
18788
18873
  clientSecret: string().min(1).optional(),
18789
18874
  scopes: array(string().min(1)).optional(),
@@ -18827,10 +18912,11 @@ const capletMcpServerSchema = object$1({
18827
18912
  path: ["url"],
18828
18913
  message: "remote url must use https except loopback development urls"
18829
18914
  });
18830
- if (server.auth?.type === "oauth2") for (const field of [
18915
+ if (server.auth?.type === "oauth2" || server.auth?.type === "oidc") for (const field of [
18831
18916
  "authorizationUrl",
18832
18917
  "tokenUrl",
18833
18918
  "issuer",
18919
+ "clientMetadataUrl",
18834
18920
  "redirectUri"
18835
18921
  ]) {
18836
18922
  const value = server.auth[field];
@@ -18917,6 +19003,52 @@ const capletGraphQlEndpointSchema = object$1({
18917
19003
  });
18918
19004
  validateEndpointAuthHeaders$1(endpoint.auth, ctx);
18919
19005
  });
19006
+ const httpScalarMappingSchema$1 = record(string(), union([
19007
+ string(),
19008
+ number$1(),
19009
+ boolean()
19010
+ ]));
19011
+ const capletHttpActionSchema = object$1({
19012
+ method: _enum([
19013
+ "GET",
19014
+ "POST",
19015
+ "PUT",
19016
+ "PATCH",
19017
+ "DELETE"
19018
+ ]).describe("HTTP method used for this action."),
19019
+ path: string().min(1).regex(/^\//, "HTTP action path must start with /").describe("URL path appended to the HTTP API baseUrl.").refine((value) => !value.startsWith("//"), "HTTP action path must not start with //").refine((value) => !isUrl(value), "HTTP action path must be a URL path, not a URL"),
19020
+ description: string().min(1).optional().describe("Action capability description."),
19021
+ inputSchema: record(string(), unknown()).optional().describe("JSON Schema for call_tool arguments."),
19022
+ query: httpScalarMappingSchema$1.optional().describe("Query parameter mapping."),
19023
+ headers: httpScalarMappingSchema$1.optional().describe("Request header mapping."),
19024
+ jsonBody: unknown().optional().describe("JSON request body mapping.")
19025
+ }).strict().superRefine((action, ctx) => {
19026
+ if (action.method === "GET" && action.jsonBody !== void 0) ctx.addIssue({
19027
+ code: "custom",
19028
+ path: ["jsonBody"],
19029
+ message: "HTTP GET actions must not define jsonBody"
19030
+ });
19031
+ });
19032
+ const capletHttpApiSchema = object$1({
19033
+ baseUrl: string().min(1).regex(HTTP_BASE_URL_PATTERN, "HTTP API baseUrl must not include credentials, query, or fragment").describe("Base URL for HTTP action requests."),
19034
+ auth: capletEndpointAuthSchema.describe("Explicit HTTP API request auth config. Use {\"type\":\"none\"} for public APIs."),
19035
+ actions: record(string().regex(SERVER_ID_PATTERN), capletHttpActionSchema).refine((actions) => Object.keys(actions).length > 0, "HTTP API must define at least one action").describe("Configured HTTP actions keyed by stable tool name."),
19036
+ requestTimeoutMs: number$1().int().positive().optional().describe("Timeout in milliseconds for HTTP action requests."),
19037
+ maxResponseBytes: number$1().int().positive().optional().describe("Maximum HTTP action response body bytes to read."),
19038
+ disabled: boolean().optional().describe("When true, omit this Caplet from discovery.")
19039
+ }).strict().superRefine((api, ctx) => {
19040
+ if (api.baseUrl && !hasEnvReference$1(api.baseUrl) && !isAllowedHttpBaseUrl(api.baseUrl)) ctx.addIssue({
19041
+ code: "custom",
19042
+ path: ["baseUrl"],
19043
+ message: "HTTP API baseUrl must use https except loopback development urls and must not include credentials, query, or fragment"
19044
+ });
19045
+ validateEndpointAuthHeaders$1(api.auth, ctx);
19046
+ for (const [actionName, action] of Object.entries(api.actions)) if (action.headers) validateHttpActionHeaders(action.headers, ctx, [
19047
+ "actions",
19048
+ actionName,
19049
+ "headers"
19050
+ ]);
19051
+ });
18920
19052
  const capletFileSchema = object$1({
18921
19053
  $schema: string().url().optional().describe("Optional JSON Schema URL for editor validation."),
18922
19054
  name: string().trim().min(1).max(80).describe("Human-readable Caplet display name."),
@@ -18924,11 +19056,12 @@ const capletFileSchema = object$1({
18924
19056
  tags: array(string().trim().min(1).max(80)).optional().describe("Optional tags for grouping or searching Caplets."),
18925
19057
  mcpServer: capletMcpServerSchema.describe("MCP server backend configuration for this Caplet.").optional(),
18926
19058
  openapiEndpoint: capletOpenApiEndpointSchema.describe("OpenAPI endpoint backend configuration for this Caplet.").optional(),
18927
- graphqlEndpoint: capletGraphQlEndpointSchema.describe("GraphQL endpoint backend configuration for this Caplet.").optional()
19059
+ graphqlEndpoint: capletGraphQlEndpointSchema.describe("GraphQL endpoint backend configuration for this Caplet.").optional(),
19060
+ httpApi: capletHttpApiSchema.describe("HTTP API backend configuration for this Caplet.").optional()
18928
19061
  }).strict().superRefine((frontmatter, ctx) => {
18929
- if (Number(Boolean(frontmatter.mcpServer)) + Number(Boolean(frontmatter.openapiEndpoint)) + Number(Boolean(frontmatter.graphqlEndpoint)) !== 1) ctx.addIssue({
19062
+ if (Number(Boolean(frontmatter.mcpServer)) + Number(Boolean(frontmatter.openapiEndpoint)) + Number(Boolean(frontmatter.graphqlEndpoint)) + Number(Boolean(frontmatter.httpApi)) !== 1) ctx.addIssue({
18930
19063
  code: "custom",
18931
- message: "Caplet file must define exactly one backend: mcpServer, openapiEndpoint, or graphqlEndpoint"
19064
+ message: "Caplet file must define exactly one backend: mcpServer, openapiEndpoint, graphqlEndpoint, or httpApi"
18932
19065
  });
18933
19066
  });
18934
19067
  function loadCapletFiles(root) {
@@ -18936,24 +19069,30 @@ function loadCapletFiles(root) {
18936
19069
  const servers = {};
18937
19070
  const openapiEndpoints = {};
18938
19071
  const graphqlEndpoints = {};
19072
+ const httpApis = {};
18939
19073
  for (const candidate of discoverCapletFiles(root)) {
18940
- if (servers[candidate.id] || openapiEndpoints[candidate.id] || graphqlEndpoints[candidate.id]) throw new CapletsError("CONFIG_INVALID", `Duplicate Caplet ID ${candidate.id} under ${root}`);
19074
+ if (servers[candidate.id] || openapiEndpoints[candidate.id] || graphqlEndpoints[candidate.id] || httpApis[candidate.id]) throw new CapletsError("CONFIG_INVALID", `Duplicate Caplet ID ${candidate.id} under ${root}`);
18941
19075
  const config = readCapletFile(candidate.path);
18942
- if (isPlainObject$3(config) && config.backend === "openapi") {
19076
+ if (isPlainObject$4(config) && config.backend === "openapi") {
18943
19077
  const { backend: _backend, ...endpoint } = config;
18944
19078
  openapiEndpoints[candidate.id] = endpoint;
18945
- } else if (isPlainObject$3(config) && config.backend === "graphql") {
19079
+ } else if (isPlainObject$4(config) && config.backend === "graphql") {
18946
19080
  const { backend: _backend, ...endpoint } = config;
18947
19081
  graphqlEndpoints[candidate.id] = endpoint;
19082
+ } else if (isPlainObject$4(config) && config.backend === "http") {
19083
+ const { backend: _backend, ...endpoint } = config;
19084
+ httpApis[candidate.id] = endpoint;
18948
19085
  } else servers[candidate.id] = config;
18949
19086
  }
18950
19087
  const hasServers = Object.keys(servers).length > 0;
18951
19088
  const hasOpenApi = Object.keys(openapiEndpoints).length > 0;
18952
19089
  const hasGraphQl = Object.keys(graphqlEndpoints).length > 0;
18953
- return hasServers || hasOpenApi || hasGraphQl ? {
19090
+ const hasHttpApis = Object.keys(httpApis).length > 0;
19091
+ return hasServers || hasOpenApi || hasGraphQl || hasHttpApis ? {
18954
19092
  ...hasServers ? { mcpServers: servers } : {},
18955
19093
  ...hasOpenApi ? { openapiEndpoints } : {},
18956
- ...hasGraphQl ? { graphqlEndpoints } : {}
19094
+ ...hasGraphQl ? { graphqlEndpoints } : {},
19095
+ ...hasHttpApis ? { httpApis } : {}
18957
19096
  } : void 0;
18958
19097
  }
18959
19098
  function discoverCapletFiles(root) {
@@ -19011,6 +19150,14 @@ function capletToServerConfig(frontmatter, body, baseDir) {
19011
19150
  ...frontmatter.tags ? { tags: frontmatter.tags } : {},
19012
19151
  body
19013
19152
  };
19153
+ if (frontmatter.httpApi) return {
19154
+ ...frontmatter.httpApi,
19155
+ backend: "http",
19156
+ name: frontmatter.name,
19157
+ description: frontmatter.description,
19158
+ ...frontmatter.tags ? { tags: frontmatter.tags } : {},
19159
+ body
19160
+ };
19014
19161
  return {
19015
19162
  ...frontmatter.mcpServer,
19016
19163
  name: frontmatter.name,
@@ -19053,7 +19200,7 @@ function parseFrontmatter(text, path) {
19053
19200
  value: text
19054
19201
  });
19055
19202
  matter(file, { strip: true });
19056
- if (!isPlainObject$3(file.data.matter) || Object.keys(file.data.matter).length === 0) throw new Error("empty frontmatter");
19203
+ if (!isPlainObject$4(file.data.matter) || Object.keys(file.data.matter).length === 0) throw new Error("empty frontmatter");
19057
19204
  return {
19058
19205
  frontmatter: file.data.matter,
19059
19206
  body: String(file)
@@ -19062,7 +19209,7 @@ function parseFrontmatter(text, path) {
19062
19209
  throw new CapletsError("CONFIG_INVALID", `Caplet file at ${path} has invalid YAML frontmatter`, redactSecrets(error));
19063
19210
  }
19064
19211
  }
19065
- function isPlainObject$3(value) {
19212
+ function isPlainObject$4(value) {
19066
19213
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19067
19214
  }
19068
19215
  function validateCapletId(id, path) {
@@ -19071,35 +19218,6 @@ function validateCapletId(id, path) {
19071
19218
  function hasEnvReference$1(value) {
19072
19219
  return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]*/.test(value);
19073
19220
  }
19074
- function isUrl(value) {
19075
- try {
19076
- new URL(value);
19077
- return true;
19078
- } catch {
19079
- return false;
19080
- }
19081
- }
19082
- //#endregion
19083
- //#region src/config/paths.ts
19084
- const DEFAULT_CONFIG_PATH = join(homedir(), ".caplets", "config.json");
19085
- const DEFAULT_AUTH_DIR = join(homedir(), ".caplets", "auth");
19086
- const PROJECT_CONFIG_FILE = join(".caplets", "config.json");
19087
- const TRUST_PROJECT_CAPLETS_ENV = "CAPLETS_TRUST_PROJECT_CAPLETS";
19088
- function resolveConfigPath(path) {
19089
- return path ?? DEFAULT_CONFIG_PATH;
19090
- }
19091
- function resolveProjectConfigPath(cwd = process.cwd()) {
19092
- return join(cwd, PROJECT_CONFIG_FILE);
19093
- }
19094
- function resolveCapletsRoot(configPath = resolveConfigPath()) {
19095
- return dirname(configPath);
19096
- }
19097
- function resolveProjectCapletsRoot(cwd = process.cwd()) {
19098
- return join(cwd, ".caplets");
19099
- }
19100
- function isTrustedEnvEnabled(value) {
19101
- return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
19102
- }
19103
19221
  //#endregion
19104
19222
  //#region src/config.ts
19105
19223
  const NON_INTERPOLATED_SERVER_FIELDS = new Set([
@@ -19126,6 +19244,7 @@ const remoteAuthSchema = discriminatedUnion("type", [
19126
19244
  resourceMetadataUrl: string().url().optional(),
19127
19245
  authorizationServerMetadataUrl: string().url().optional(),
19128
19246
  openidConfigurationUrl: string().url().optional(),
19247
+ clientMetadataUrl: string().url().optional(),
19129
19248
  clientId: string().min(1).optional(),
19130
19249
  clientSecret: string().min(1).optional(),
19131
19250
  scopes: array(string().min(1)).optional(),
@@ -19139,6 +19258,7 @@ const remoteAuthSchema = discriminatedUnion("type", [
19139
19258
  resourceMetadataUrl: string().url().optional(),
19140
19259
  authorizationServerMetadataUrl: string().url().optional(),
19141
19260
  openidConfigurationUrl: string().url().optional(),
19261
+ clientMetadataUrl: string().url().optional(),
19142
19262
  clientId: string().min(1).optional(),
19143
19263
  clientSecret: string().min(1).optional(),
19144
19264
  scopes: array(string().min(1)).optional(),
@@ -19153,6 +19273,7 @@ const oauthLikeAuthSchema = union([object$1({
19153
19273
  resourceMetadataUrl: string().url().optional(),
19154
19274
  authorizationServerMetadataUrl: string().url().optional(),
19155
19275
  openidConfigurationUrl: string().url().optional(),
19276
+ clientMetadataUrl: string().url().optional(),
19156
19277
  clientId: string().min(1).optional(),
19157
19278
  clientSecret: string().min(1).optional(),
19158
19279
  scopes: array(string().min(1)).optional(),
@@ -19165,6 +19286,7 @@ const oauthLikeAuthSchema = union([object$1({
19165
19286
  resourceMetadataUrl: string().url().optional(),
19166
19287
  authorizationServerMetadataUrl: string().url().optional(),
19167
19288
  openidConfigurationUrl: string().url().optional(),
19289
+ clientMetadataUrl: string().url().optional(),
19168
19290
  clientId: string().min(1).optional(),
19169
19291
  clientSecret: string().min(1).optional(),
19170
19292
  scopes: array(string().min(1)).optional(),
@@ -19248,7 +19370,45 @@ const publicGraphQlEndpointSchema = object$1({
19248
19370
  });
19249
19371
  });
19250
19372
  const normalizedGraphQlEndpointSchema = publicGraphQlEndpointSchema.extend({ body: string().optional() });
19251
- function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlEndpointValueSchema) {
19373
+ const httpScalarMappingSchema = record(string(), union([
19374
+ string(),
19375
+ number$1(),
19376
+ boolean()
19377
+ ]));
19378
+ const httpActionSchema = object$1({
19379
+ method: _enum([
19380
+ "GET",
19381
+ "POST",
19382
+ "PUT",
19383
+ "PATCH",
19384
+ "DELETE"
19385
+ ]).describe("HTTP method used for this action."),
19386
+ path: string().min(1).regex(/^\//, "HTTP action path must start with /").describe("URL path appended to the HTTP API baseUrl.").refine((value) => !value.startsWith("//"), "HTTP action path must not start with //").refine((value) => !isUrl(value), "HTTP action path must be a URL path, not a URL"),
19387
+ description: string().min(1).optional().describe("Action capability description."),
19388
+ inputSchema: record(string(), unknown()).optional().describe("JSON Schema for call_tool arguments."),
19389
+ query: httpScalarMappingSchema.optional().describe("Query parameter mapping."),
19390
+ headers: httpScalarMappingSchema.optional().describe("Request header mapping."),
19391
+ jsonBody: unknown().optional().describe("JSON request body mapping.")
19392
+ }).strict().superRefine((action, ctx) => {
19393
+ if (action.method === "GET" && action.jsonBody !== void 0) ctx.addIssue({
19394
+ code: "custom",
19395
+ path: ["jsonBody"],
19396
+ message: "HTTP GET actions must not define jsonBody"
19397
+ });
19398
+ });
19399
+ const publicHttpApiSchema = object$1({
19400
+ name: string().trim().min(1).max(80).describe("Human-readable HTTP API display name."),
19401
+ description: string().describe("Capability description shown to agents before HTTP actions are disclosed.").refine((value) => value.trim().length >= 10, "description must contain at least 10 non-whitespace characters").refine((value) => value.length <= 1500, "description must be at most 1500 characters"),
19402
+ baseUrl: string().url().regex(HTTP_BASE_URL_PATTERN, "HTTP API baseUrl must not include credentials, query, or fragment").describe("Base URL for HTTP action requests."),
19403
+ auth: openApiAuthSchema.describe("Explicit HTTP API request auth config. Use {\"type\":\"none\"} for public APIs."),
19404
+ actions: record(string().regex(SERVER_ID_PATTERN), httpActionSchema).refine((actions) => Object.keys(actions).length > 0, "HTTP API must define at least one action").describe("Configured HTTP actions keyed by stable tool name."),
19405
+ tags: array(string().trim().min(1).max(80)).optional(),
19406
+ requestTimeoutMs: number$1().int().positive().default(6e4).describe("Timeout in milliseconds for HTTP action requests."),
19407
+ maxResponseBytes: number$1().int().positive().default(1e6).describe("Maximum HTTP action response body bytes to read."),
19408
+ disabled: boolean().default(false).describe("When true, omit this HTTP API Caplet.")
19409
+ }).strict();
19410
+ const normalizedHttpApiSchema = publicHttpApiSchema.extend({ body: string().optional() });
19411
+ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlEndpointValueSchema, httpApiValueSchema) {
19252
19412
  return object$1({
19253
19413
  $schema: string().url().optional().describe("Optional JSON Schema URL for editor validation."),
19254
19414
  version: literal(1).default(1).describe("Caplets config schema version."),
@@ -19256,7 +19416,8 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
19256
19416
  maxSearchLimit: number$1().int().positive().max(50).default(50).describe("Maximum accepted search_tools limit."),
19257
19417
  mcpServers: record(string().regex(SERVER_ID_PATTERN), serverValueSchema).default({}).describe("Downstream MCP servers keyed by stable server ID."),
19258
19418
  openapiEndpoints: record(string().regex(SERVER_ID_PATTERN), openApiEndpointValueSchema).default({}).describe("OpenAPI endpoints keyed by stable Caplet ID."),
19259
- graphqlEndpoints: record(string().regex(SERVER_ID_PATTERN), graphQlEndpointValueSchema).default({}).describe("GraphQL endpoints keyed by stable Caplet ID.")
19419
+ graphqlEndpoints: record(string().regex(SERVER_ID_PATTERN), graphQlEndpointValueSchema).default({}).describe("GraphQL endpoints keyed by stable Caplet ID."),
19420
+ httpApis: record(string().regex(SERVER_ID_PATTERN), httpApiValueSchema).default({}).describe("HTTP APIs keyed by stable Caplet ID.")
19260
19421
  }).strict().superRefine((config, ctx) => {
19261
19422
  if (config.defaultSearchLimit > config.maxSearchLimit) ctx.addIssue({
19262
19423
  code: "custom",
@@ -19410,10 +19571,45 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
19410
19571
  "auth"
19411
19572
  ]);
19412
19573
  }
19574
+ for (const [endpoint, rawValue] of Object.entries(config.httpApis)) {
19575
+ const raw = rawValue;
19576
+ const duplicateBackend = config.mcpServers[endpoint] ? "mcpServers" : config.openapiEndpoints[endpoint] ? "openapiEndpoints" : config.graphqlEndpoints[endpoint] ? "graphqlEndpoints" : void 0;
19577
+ if (duplicateBackend) ctx.addIssue({
19578
+ code: "custom",
19579
+ path: ["httpApis", endpoint],
19580
+ message: `Caplet ID ${endpoint} is already used by ${duplicateBackend}`
19581
+ });
19582
+ if (!SERVER_ID_PATTERN.test(endpoint)) ctx.addIssue({
19583
+ code: "custom",
19584
+ path: ["httpApis", endpoint],
19585
+ message: "HTTP API ID must match ^[a-zA-Z0-9_-]{1,64}$"
19586
+ });
19587
+ if (raw.baseUrl && !isAllowedHttpBaseUrl(raw.baseUrl)) ctx.addIssue({
19588
+ code: "custom",
19589
+ path: [
19590
+ "httpApis",
19591
+ endpoint,
19592
+ "baseUrl"
19593
+ ],
19594
+ message: "HTTP API baseUrl must use https except loopback development urls and must not include credentials, query, or fragment"
19595
+ });
19596
+ validateEndpointAuthHeaders(raw.auth, ctx, [
19597
+ "httpApis",
19598
+ endpoint,
19599
+ "auth"
19600
+ ]);
19601
+ for (const [actionName, action] of Object.entries(raw.actions)) if (action.headers) validateHttpActionHeaders(action.headers, ctx, [
19602
+ "httpApis",
19603
+ endpoint,
19604
+ "actions",
19605
+ actionName,
19606
+ "headers"
19607
+ ]);
19608
+ }
19413
19609
  });
19414
19610
  }
19415
- const configFileSchema = configSchemaFor(publicServerSchema, publicOpenApiEndpointSchema, publicGraphQlEndpointSchema);
19416
- const normalizedConfigFileSchema = configSchemaFor(normalizedServerSchema, normalizedOpenApiEndpointSchema, normalizedGraphQlEndpointSchema);
19611
+ const configFileSchema = configSchemaFor(publicServerSchema, publicOpenApiEndpointSchema, publicGraphQlEndpointSchema, publicHttpApiSchema);
19612
+ const normalizedConfigFileSchema = configSchemaFor(normalizedServerSchema, normalizedOpenApiEndpointSchema, normalizedGraphQlEndpointSchema, normalizedHttpApiSchema);
19417
19613
  function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConfigPath()) {
19418
19614
  const hasUserConfig = existsSync(path);
19419
19615
  const hasProjectConfig = existsSync(projectPath);
@@ -19424,7 +19620,7 @@ function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConf
19424
19620
  if (!hasUserConfig && !hasProjectConfig && !userCaplets && !projectCaplets) throw new CapletsError("CONFIG_NOT_FOUND", `Caplets config not found at ${path} or ${projectPath}`);
19425
19621
  try {
19426
19622
  const config = parseConfig(mergeConfigInputs(userConfig, userCaplets, projectConfig, projectCaplets));
19427
- if (Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && Object.keys(config.graphqlEndpoints).length === 0) throw new CapletsError("CONFIG_INVALID", "Caplets config must define at least one MCP server, OpenAPI endpoint, or GraphQL endpoint");
19623
+ if (Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && Object.keys(config.graphqlEndpoints).length === 0 && Object.keys(config.httpApis).length === 0) throw new CapletsError("CONFIG_INVALID", "Caplets config must define at least one MCP server, OpenAPI endpoint, GraphQL endpoint, or HTTP API");
19428
19624
  return config;
19429
19625
  } catch (error) {
19430
19626
  if (error instanceof CapletsError) throw error;
@@ -19454,7 +19650,7 @@ function normalizeLocalPaths(input, baseDir) {
19454
19650
  }
19455
19651
  function normalizeEndpointPaths(endpoints, baseDir, normalize) {
19456
19652
  if (!endpoints) return;
19457
- return Object.fromEntries(Object.entries(endpoints).map(([id, endpoint]) => [id, isPlainObject$2(endpoint) ? normalize(endpoint, baseDir) : endpoint]));
19653
+ return Object.fromEntries(Object.entries(endpoints).map(([id, endpoint]) => [id, isPlainObject$3(endpoint) ? normalize(endpoint, baseDir) : endpoint]));
19458
19654
  }
19459
19655
  function normalizeOpenApiPath(endpoint, baseDir) {
19460
19656
  return {
@@ -19463,7 +19659,7 @@ function normalizeOpenApiPath(endpoint, baseDir) {
19463
19659
  };
19464
19660
  }
19465
19661
  function normalizeGraphQlPath(endpoint, baseDir) {
19466
- const operations = isPlainObject$2(endpoint.operations) ? Object.fromEntries(Object.entries(endpoint.operations).map(([name, operation]) => [name, isPlainObject$2(operation) ? {
19662
+ const operations = isPlainObject$3(endpoint.operations) ? Object.fromEntries(Object.entries(endpoint.operations).map(([name, operation]) => [name, isPlainObject$3(operation) ? {
19467
19663
  ...operation,
19468
19664
  documentPath: normalizeLocalPath(operation.documentPath, baseDir)
19469
19665
  } : operation])) : endpoint.operations;
@@ -19480,6 +19676,7 @@ function normalizeLocalPath(value, baseDir) {
19480
19676
  function rejectUntrustedProjectExecutableBackends(input, path) {
19481
19677
  if (input.openapiEndpoints && Object.keys(input.openapiEndpoints).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define openapiEndpoints; use trusted project Caplet files or user config`);
19482
19678
  if (input.graphqlEndpoints && Object.keys(input.graphqlEndpoints).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define graphqlEndpoints; use trusted project Caplet files or user config`);
19679
+ if (input.httpApis && Object.keys(input.httpApis).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define httpApis; use trusted project Caplet files or user config`);
19483
19680
  return input;
19484
19681
  }
19485
19682
  function mergeConfigInputs(...inputs) {
@@ -19500,6 +19697,10 @@ function mergeConfigInputs(...inputs) {
19500
19697
  graphqlEndpoints: {
19501
19698
  ...merged?.graphqlEndpoints,
19502
19699
  ...input.graphqlEndpoints
19700
+ },
19701
+ httpApis: {
19702
+ ...merged?.httpApis,
19703
+ ...input.httpApis
19503
19704
  }
19504
19705
  };
19505
19706
  }
@@ -19530,6 +19731,12 @@ function parseConfig(input) {
19530
19731
  server,
19531
19732
  backend: "graphql"
19532
19733
  });
19734
+ const httpApis = {};
19735
+ for (const [server, raw] of Object.entries(parsed.data.httpApis)) httpApis[server] = stripUndefined({
19736
+ ...raw,
19737
+ server,
19738
+ backend: "http"
19739
+ });
19533
19740
  return {
19534
19741
  version: parsed.data.version,
19535
19742
  options: {
@@ -19538,7 +19745,8 @@ function parseConfig(input) {
19538
19745
  },
19539
19746
  mcpServers: servers,
19540
19747
  openapiEndpoints,
19541
- graphqlEndpoints
19748
+ graphqlEndpoints,
19749
+ httpApis
19542
19750
  };
19543
19751
  }
19544
19752
  function validateEndpointAuthHeaders(auth, ctx, path) {
@@ -19567,10 +19775,10 @@ function interpolateConfig(value, path = []) {
19567
19775
  return value;
19568
19776
  }
19569
19777
  function isPublicMetadataPath(path) {
19570
- if (path.length < 3 || path[0] !== "mcpServers" && path[0] !== "openapiEndpoints" && path[0] !== "graphqlEndpoints") return false;
19778
+ if (path.length < 3 || path[0] !== "mcpServers" && path[0] !== "openapiEndpoints" && path[0] !== "graphqlEndpoints" && path[0] !== "httpApis") return false;
19571
19779
  return NON_INTERPOLATED_SERVER_FIELDS.has(path[2] ?? "");
19572
19780
  }
19573
- function isPlainObject$2(value) {
19781
+ function isPlainObject$3(value) {
19574
19782
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19575
19783
  }
19576
19784
  function hasEnvReference(value) {
@@ -19629,7 +19837,8 @@ function authTargets(config) {
19629
19837
  return [
19630
19838
  ...Object.values(config.mcpServers).filter((server) => server.transport !== "stdio" && (server.auth?.type === "oauth2" || server.auth?.type === "oidc")),
19631
19839
  ...Object.values(config.openapiEndpoints).filter((endpoint) => endpoint.auth?.type === "oauth2" || endpoint.auth?.type === "oidc"),
19632
- ...Object.values(config.graphqlEndpoints).filter((endpoint) => endpoint.auth?.type === "oauth2" || endpoint.auth?.type === "oidc").map(graphQlAuthTarget)
19840
+ ...Object.values(config.graphqlEndpoints).filter((endpoint) => endpoint.auth?.type === "oauth2" || endpoint.auth?.type === "oidc").map(graphQlAuthTarget),
19841
+ ...Object.values(config.httpApis).filter((api) => api.auth?.type === "oauth2" || api.auth?.type === "oidc").map(httpAuthTarget)
19633
19842
  ];
19634
19843
  }
19635
19844
  function graphQlAuthTarget(endpoint) {
@@ -19638,6 +19847,9 @@ function graphQlAuthTarget(endpoint) {
19638
19847
  url: endpoint.endpointUrl
19639
19848
  };
19640
19849
  }
19850
+ function httpAuthTarget(api) {
19851
+ return { ...api };
19852
+ }
19641
19853
  function assertLoginTarget(target, serverId) {
19642
19854
  if (!target) throw new CapletsError("SERVER_NOT_FOUND", `Server ${serverId} is not configured for OAuth`);
19643
19855
  if ("disabled" in target && target.disabled) throw new CapletsError("SERVER_UNAVAILABLE", `Server ${serverId} is disabled`);
@@ -19725,12 +19937,14 @@ function formatCapletList(rows) {
19725
19937
  }
19726
19938
  function resolveCliConfigPaths(envConfigPath, authDir) {
19727
19939
  const configPath = resolveConfigPath(envConfigPath);
19940
+ const effectiveAuthDir = authDir ?? DEFAULT_AUTH_DIR;
19728
19941
  return {
19729
19942
  userConfig: configPath,
19730
19943
  projectConfig: resolveProjectConfigPath(),
19731
19944
  userRoot: resolveCapletsRoot(configPath),
19945
+ stateRoot: dirname(effectiveAuthDir),
19732
19946
  projectRoot: resolveProjectCapletsRoot(),
19733
- authDir: authDir ?? DEFAULT_AUTH_DIR,
19947
+ authDir: effectiveAuthDir,
19734
19948
  envConfig: envConfigPath ?? null,
19735
19949
  projectCapletsTrusted: isTrustedProjectCapletsEnabled()
19736
19950
  };
@@ -19740,6 +19954,7 @@ function formatConfigPaths(paths) {
19740
19954
  `userConfig: ${paths.userConfig}`,
19741
19955
  `projectConfig: ${paths.projectConfig}`,
19742
19956
  `userRoot: ${paths.userRoot}`,
19957
+ `stateRoot: ${paths.stateRoot}`,
19743
19958
  `projectRoot: ${paths.projectRoot}`,
19744
19959
  `authDir: ${paths.authDir}`,
19745
19960
  `envConfig: ${paths.envConfig ?? "unset"}`,
@@ -25649,7 +25864,7 @@ var Protocol = class {
25649
25864
  };
25650
25865
  }
25651
25866
  };
25652
- function isPlainObject$1(value) {
25867
+ function isPlainObject$2(value) {
25653
25868
  return value !== null && typeof value === "object" && !Array.isArray(value);
25654
25869
  }
25655
25870
  function mergeCapabilities(base, additional) {
@@ -25659,7 +25874,7 @@ function mergeCapabilities(base, additional) {
25659
25874
  const addValue = additional[k];
25660
25875
  if (addValue === void 0) continue;
25661
25876
  const baseValue = result[k];
25662
- if (isPlainObject$1(baseValue) && isPlainObject$1(addValue)) result[k] = {
25877
+ if (isPlainObject$2(baseValue) && isPlainObject$2(addValue)) result[k] = {
25663
25878
  ...baseValue,
25664
25879
  ...addValue
25665
25880
  };
@@ -50843,6 +51058,279 @@ function graphQlCacheKey(endpoint) {
50843
51058
  });
50844
51059
  }
50845
51060
  //#endregion
51061
+ //#region src/http-actions.ts
51062
+ const DEFAULT_INPUT_SCHEMA = {
51063
+ type: "object",
51064
+ additionalProperties: true
51065
+ };
51066
+ var HttpActionManager = class {
51067
+ registry;
51068
+ options;
51069
+ constructor(registry, options = {}) {
51070
+ this.registry = registry;
51071
+ this.options = options;
51072
+ }
51073
+ updateRegistry(registry) {
51074
+ this.registry = registry;
51075
+ }
51076
+ invalidate(_serverId) {}
51077
+ async checkApi(api) {
51078
+ const startedAt = Date.now();
51079
+ try {
51080
+ const operations = operationsFor(api);
51081
+ validateBaseUrl(api);
51082
+ authHeaders$1(api, this.options.authDir);
51083
+ for (const operation of operations) validateAction(api, operation);
51084
+ this.registry.setStatus(api.server, "available");
51085
+ return {
51086
+ server: api.server,
51087
+ status: "available",
51088
+ toolCount: operations.length,
51089
+ elapsedMs: Date.now() - startedAt
51090
+ };
51091
+ } catch (error) {
51092
+ const safe = toSafeError(error, "SERVER_UNAVAILABLE");
51093
+ this.registry.setStatus(api.server, "unavailable", safe);
51094
+ return {
51095
+ server: api.server,
51096
+ status: "unavailable",
51097
+ elapsedMs: Date.now() - startedAt,
51098
+ error: safe
51099
+ };
51100
+ }
51101
+ }
51102
+ async listTools(api) {
51103
+ return operationsFor(api).map((operation) => this.toTool(operation));
51104
+ }
51105
+ async getTool(api, toolName) {
51106
+ return this.toTool(getOperation(api, toolName));
51107
+ }
51108
+ async callTool(api, toolName, args) {
51109
+ const operation = getOperation(api, toolName);
51110
+ const startedAt = Date.now();
51111
+ const request = buildRequest$1(api, operation, args, this.options.authDir);
51112
+ const controller = new AbortController();
51113
+ const timeout = setTimeout(() => controller.abort(), api.requestTimeoutMs);
51114
+ try {
51115
+ const response = await fetch(request.url, {
51116
+ method: operation.method,
51117
+ headers: request.headers,
51118
+ redirect: "manual",
51119
+ signal: controller.signal,
51120
+ ...request.body === void 0 ? {} : { body: request.body }
51121
+ });
51122
+ if (response.status >= 300 && response.status < 400) throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "HTTP action request returned a redirect", {
51123
+ server: api.server,
51124
+ status: response.status,
51125
+ location: response.headers.get("location") ? "[REDACTED]" : void 0
51126
+ });
51127
+ const parsed = await readResponse$1(response, api, Date.now() - startedAt);
51128
+ return {
51129
+ content: [{
51130
+ type: "text",
51131
+ text: JSON.stringify(parsed, null, 2)
51132
+ }],
51133
+ structuredContent: parsed,
51134
+ isError: !response.ok
51135
+ };
51136
+ } catch (error) {
51137
+ if (isAbortError(error)) throw new CapletsError("TOOL_CALL_TIMEOUT", `HTTP action request timed out for ${api.server}/${toolName}`);
51138
+ if (error instanceof CapletsError) throw error;
51139
+ throw new CapletsError("DOWNSTREAM_TOOL_ERROR", `HTTP action request failed for ${api.server}/${toolName}`, toSafeError(error));
51140
+ } finally {
51141
+ clearTimeout(timeout);
51142
+ }
51143
+ }
51144
+ compact(api, tool) {
51145
+ return {
51146
+ server: api.server,
51147
+ tool: tool.name,
51148
+ ...tool.description ? { description: tool.description } : {},
51149
+ ...tool.annotations ? { annotations: tool.annotations } : {},
51150
+ hasInputSchema: Boolean(tool.inputSchema)
51151
+ };
51152
+ }
51153
+ search(api, tools, query, limit) {
51154
+ const needle = query.toLocaleLowerCase();
51155
+ return tools.filter((tool) => `${tool.name}\n${tool.description ?? ""}`.toLocaleLowerCase().includes(needle)).sort((left, right) => left.name.localeCompare(right.name)).slice(0, limit).map((tool) => this.compact(api, tool));
51156
+ }
51157
+ toTool(operation) {
51158
+ return {
51159
+ name: operation.name,
51160
+ ...operation.description ? { description: operation.description } : {},
51161
+ inputSchema: operation.inputSchema ?? DEFAULT_INPUT_SCHEMA,
51162
+ annotations: {
51163
+ readOnlyHint: operation.method === "GET",
51164
+ destructiveHint: operation.method === "DELETE"
51165
+ }
51166
+ };
51167
+ }
51168
+ };
51169
+ function operationsFor(api) {
51170
+ return Object.entries(api.actions).map(([name, action]) => ({
51171
+ name,
51172
+ ...action
51173
+ })).sort((left, right) => left.name.localeCompare(right.name));
51174
+ }
51175
+ function getOperation(api, toolName) {
51176
+ const operations = operationsFor(api);
51177
+ const operation = operations.find((candidate) => candidate.name === toolName);
51178
+ if (!operation) throw new CapletsError("TOOL_NOT_FOUND", `Tool ${toolName} was not found on ${api.server}`, {
51179
+ server: api.server,
51180
+ tool: toolName,
51181
+ suggestions: operations.map((candidate) => candidate.name).filter((name) => name.toLocaleLowerCase().includes(toolName.toLocaleLowerCase()[0] ?? "")).slice(0, 5)
51182
+ });
51183
+ return operation;
51184
+ }
51185
+ function buildRequest$1(api, operation, args, authDir) {
51186
+ validateBaseUrl(api);
51187
+ validateAction(api, operation);
51188
+ const url = buildActionUrl(api.baseUrl, substitutePath$1(operation.path, args, operation), { allowEncodedSlash: true });
51189
+ const query = resolveMappingToRecord(operation.query, args, "query");
51190
+ for (const [key, value] of Object.entries(query)) if (value !== void 0 && value !== null) url.searchParams.append(key, serializeHttpValue$1("query", key, value));
51191
+ const headers = new Headers();
51192
+ applyAuth$1(headers, api, authDir);
51193
+ const resolvedHeaders = resolveMappingToRecord(operation.headers, args, "headers");
51194
+ for (const [key, value] of Object.entries(resolvedHeaders)) if (value !== void 0 && value !== null) {
51195
+ validateResolvedHeader(api, operation, key);
51196
+ headers.set(key, serializeHttpValue$1("header", key, value));
51197
+ }
51198
+ const bodyValue = resolveMapping(operation.jsonBody, args);
51199
+ if (operation.jsonBody !== void 0) {
51200
+ if (bodyValue === void 0) throw new CapletsError("REQUEST_INVALID", "HTTP action jsonBody must not resolve to undefined");
51201
+ headers.set("content-type", "application/json");
51202
+ return {
51203
+ url,
51204
+ headers,
51205
+ body: JSON.stringify(bodyValue)
51206
+ };
51207
+ }
51208
+ return {
51209
+ url,
51210
+ headers
51211
+ };
51212
+ }
51213
+ function substitutePath$1(path, args, operation) {
51214
+ return path.replace(/\{([^}]+)\}/g, (_match, name) => {
51215
+ const value = args[name];
51216
+ if (value === void 0 || value === null || value === "") throw new CapletsError("REQUEST_INVALID", `Missing required path parameter ${name}`, { tool: operation.name });
51217
+ return encodeURIComponent(serializeHttpValue$1("path", name, value));
51218
+ });
51219
+ }
51220
+ function resolveMapping(mapping, input) {
51221
+ if (typeof mapping === "string") {
51222
+ if (mapping === "$input") return input;
51223
+ if (mapping.startsWith("$input.")) return valueAtPath(input, mapping.slice(7));
51224
+ return mapping;
51225
+ }
51226
+ if (Array.isArray(mapping)) return mapping.map((item) => resolveMapping(item, input));
51227
+ if (mapping && typeof mapping === "object") return Object.fromEntries(Object.entries(mapping).map(([key, value]) => [key, resolveMapping(value, input)]));
51228
+ return mapping;
51229
+ }
51230
+ function resolveMappingToRecord(mapping, input, name) {
51231
+ if (mapping === void 0) return {};
51232
+ const resolved = resolveMapping(mapping, input);
51233
+ if (!isPlainObject$1(resolved)) throw new CapletsError("REQUEST_INVALID", `HTTP action ${name} mapping must resolve to an object`);
51234
+ return resolved;
51235
+ }
51236
+ function valueAtPath(input, path) {
51237
+ let current = input;
51238
+ for (const segment of path.split(".")) {
51239
+ if (!current || typeof current !== "object" || Array.isArray(current)) return;
51240
+ current = current[segment];
51241
+ }
51242
+ return current;
51243
+ }
51244
+ function validateAction(api, operation) {
51245
+ buildActionUrl(api.baseUrl, operation.path);
51246
+ validateConfiguredHeaders(api, operation);
51247
+ }
51248
+ function validateConfiguredHeaders(api, operation) {
51249
+ const configured = asRecord$1(operation.headers);
51250
+ for (const key of Object.keys(configured)) validateResolvedHeader(api, operation, key);
51251
+ }
51252
+ function validateResolvedHeader(api, operation, key) {
51253
+ const authHeaderNames = api.auth.type === "headers" ? new Set(Object.keys(api.auth.headers).map((header) => header.toLowerCase())) : /* @__PURE__ */ new Set();
51254
+ const normalized = key.toLowerCase();
51255
+ if (FORBIDDEN_HEADERS.has(normalized) || authHeaderNames.has(normalized)) throw new CapletsError("CONFIG_INVALID", `HTTP action header ${key} is not allowed`, {
51256
+ server: api.server,
51257
+ tool: operation.name
51258
+ });
51259
+ }
51260
+ function serializeHttpValue$1(location, name, value) {
51261
+ switch (typeof value) {
51262
+ case "string":
51263
+ case "number":
51264
+ case "boolean": return String(value);
51265
+ default: throw new CapletsError("REQUEST_INVALID", `HTTP action ${location} parameter ${name} must be a string, number, or boolean`);
51266
+ }
51267
+ }
51268
+ function applyAuth$1(headers, api, authDir) {
51269
+ for (const [key, value] of Object.entries(authHeaders$1(api, authDir))) headers.set(key, value);
51270
+ }
51271
+ function authHeaders$1(api, authDir) {
51272
+ switch (api.auth.type) {
51273
+ case "none": return {};
51274
+ case "bearer": return { authorization: `Bearer ${api.auth.token}` };
51275
+ case "headers": return api.auth.headers;
51276
+ case "oauth2":
51277
+ case "oidc": return genericOAuthHeaders({
51278
+ server: api.server,
51279
+ backend: "http",
51280
+ baseUrl: api.baseUrl,
51281
+ auth: api.auth,
51282
+ requestTimeoutMs: api.requestTimeoutMs
51283
+ }, authDir);
51284
+ }
51285
+ }
51286
+ async function readResponse$1(response, api, elapsedMs) {
51287
+ const contentType = response.headers.get("content-type") ?? "";
51288
+ const body = parseHttpBody(contentType, await readLimitedText(response, {
51289
+ maxBytes: maxResponseBytes(api),
51290
+ errorMessage: "HTTP action response exceeded byte limit"
51291
+ }));
51292
+ return {
51293
+ status: response.status,
51294
+ statusText: response.statusText,
51295
+ headers: { "content-type": contentType },
51296
+ ...body === void 0 ? {} : { body },
51297
+ elapsedMs
51298
+ };
51299
+ }
51300
+ function maxResponseBytes(api) {
51301
+ return api.maxResponseBytes;
51302
+ }
51303
+ function validateBaseUrl(api) {
51304
+ if (!isAllowedRemoteUrl(api.baseUrl)) throw new CapletsError("CONFIG_INVALID", `${api.server} HTTP API baseUrl is not allowed`);
51305
+ const url = new URL(api.baseUrl);
51306
+ if (url.username || url.password || url.search || url.hash) throw new CapletsError("CONFIG_INVALID", `${api.server} HTTP API baseUrl must not include credentials, query, or fragment`);
51307
+ }
51308
+ function buildActionUrl(base, actionPath, options = {}) {
51309
+ if (/^[a-z][a-z0-9+.-]*:/i.test(actionPath) || actionPath.startsWith("//")) throw new CapletsError("CONFIG_INVALID", "HTTP action path cannot change origin");
51310
+ for (const rawSegment of actionPath.split("/")) {
51311
+ let segment;
51312
+ try {
51313
+ segment = decodeURIComponent(rawSegment);
51314
+ } catch {
51315
+ throw new CapletsError("CONFIG_INVALID", "HTTP action path contains invalid encoding");
51316
+ }
51317
+ if (segment === "." || segment === ".." || !options.allowEncodedSlash && segment.includes("/")) throw new CapletsError("CONFIG_INVALID", "HTTP action path cannot contain dot segments");
51318
+ }
51319
+ const baseUrl = new URL(base);
51320
+ const originalOrigin = baseUrl.origin;
51321
+ const basePath = baseUrl.pathname.replace(/\/+$/, "");
51322
+ baseUrl.pathname = [basePath, actionPath.replace(/^\/+/, "")].filter(Boolean).join("/");
51323
+ if (baseUrl.origin !== originalOrigin) throw new CapletsError("CONFIG_INVALID", "HTTP action path cannot change origin");
51324
+ if (basePath && baseUrl.pathname !== basePath && !baseUrl.pathname.startsWith(`${basePath}/`)) throw new CapletsError("CONFIG_INVALID", "HTTP action path cannot escape baseUrl path");
51325
+ return baseUrl;
51326
+ }
51327
+ function asRecord$1(value) {
51328
+ return isPlainObject$1(value) ? value : {};
51329
+ }
51330
+ function isPlainObject$1(value) {
51331
+ return value !== null && typeof value === "object" && !Array.isArray(value);
51332
+ }
51333
+ //#endregion
50846
51334
  //#region node_modules/.pnpm/@apidevtools+swagger-parser@12.1.0_openapi-types@12.1.3/node_modules/@apidevtools/swagger-parser/lib/util.js
50847
51335
  var require_util = /* @__PURE__ */ __commonJSMin(((exports) => {
50848
51336
  const util = __require("util");
@@ -60731,7 +61219,7 @@ var ServerRegistry = class {
60731
61219
  return this.allCaplets().filter((server) => !server.disabled);
60732
61220
  }
60733
61221
  get(serverId) {
60734
- const server = this.config.mcpServers[serverId] ?? this.config.openapiEndpoints[serverId] ?? this.config.graphqlEndpoints[serverId];
61222
+ const server = this.config.mcpServers[serverId] ?? this.config.openapiEndpoints[serverId] ?? this.config.graphqlEndpoints[serverId] ?? this.config.httpApis[serverId];
60735
61223
  return server?.disabled ? void 0 : server;
60736
61224
  }
60737
61225
  require(serverId) {
@@ -60781,12 +61269,13 @@ var ServerRegistry = class {
60781
61269
  return [
60782
61270
  ...Object.values(this.config.mcpServers),
60783
61271
  ...Object.values(this.config.openapiEndpoints),
60784
- ...Object.values(this.config.graphqlEndpoints)
61272
+ ...Object.values(this.config.graphqlEndpoints),
61273
+ ...Object.values(this.config.httpApis)
60785
61274
  ];
60786
61275
  }
60787
61276
  };
60788
61277
  function capabilityDescription(server) {
60789
- const backendName = server.backend === "mcp" ? "MCP server" : server.backend === "openapi" ? "OpenAPI endpoint" : "GraphQL endpoint";
61278
+ const backendName = server.backend === "mcp" ? "MCP server" : server.backend === "openapi" ? "OpenAPI endpoint" : server.backend === "graphql" ? "GraphQL endpoint" : "HTTP API";
60790
61279
  const checkOperation = server.backend === "mcp" ? "check_mcp_server" : "check_backend";
60791
61280
  const hint = [
60792
61281
  `Use this Caplet to inspect and call tools from its ${backendName} backend.`,
@@ -60818,6 +61307,12 @@ function backendDetail(server) {
60818
61307
  source: graphQlSource(server),
60819
61308
  configuredOperations: Boolean(server.operations && Object.keys(server.operations).length > 0)
60820
61309
  };
61310
+ if (server.backend === "http") return {
61311
+ type: "http",
61312
+ disabled: server.disabled,
61313
+ requestTimeoutMs: server.requestTimeoutMs,
61314
+ configuredActions: Object.keys(server.actions).length
61315
+ };
60821
61316
  return {
60822
61317
  type: "mcp",
60823
61318
  transport: server.transport,
@@ -60854,16 +61349,16 @@ const generatedToolInputSchema = object$1({
60854
61349
  tool: string().optional().describe("Exact downstream tool name for get_tool or call_tool. Example: {\"operation\":\"get_tool\",\"tool\":\"web_search_exa\"} before calling it."),
60855
61350
  arguments: record(string(), unknown()).optional().describe("Required JSON object only for call_tool. Put every downstream tool input inside this object. Example: {\"operation\":\"call_tool\",\"tool\":\"web_search_exa\",\"arguments\":{\"query\":\"latest MCP docs\",\"numResults\":3}}. Do not send downstream inputs as top-level query, limit, url, path, or other fields.")
60856
61351
  }).strict();
60857
- async function handleServerTool(server, request, registry, downstream, openapi, graphql) {
61352
+ async function handleServerTool(server, request, registry, downstream, openapi, graphql, http) {
60858
61353
  const parsed = validateOperationRequest(request, registry.config.options.maxSearchLimit);
60859
61354
  switch (parsed.operation) {
60860
61355
  case "get_caplet": return jsonResult(registry.detail(server));
60861
- case "check_backend": return jsonResult(await backendFor(server, downstream, openapi, graphql).check(server));
61356
+ case "check_backend": return jsonResult(await backendFor(server, downstream, openapi, graphql, http).check(server));
60862
61357
  case "check_mcp_server":
60863
61358
  if (server.backend !== "mcp") throw new CapletsError("REQUEST_INVALID", "check_mcp_server is only valid for MCP-backed Caplets; use check_backend");
60864
61359
  return jsonResult(await downstream.checkServer(server));
60865
61360
  case "list_tools": {
60866
- const backend = backendFor(server, downstream, openapi, graphql);
61361
+ const backend = backendFor(server, downstream, openapi, graphql, http);
60867
61362
  const tools = await backend.listTools(server);
60868
61363
  return jsonResult({
60869
61364
  server: server.server,
@@ -60871,7 +61366,7 @@ async function handleServerTool(server, request, registry, downstream, openapi,
60871
61366
  });
60872
61367
  }
60873
61368
  case "search_tools": {
60874
- const backend = backendFor(server, downstream, openapi, graphql);
61369
+ const backend = backendFor(server, downstream, openapi, graphql, http);
60875
61370
  const tools = await backend.listTools(server);
60876
61371
  const limit = parsed.limit ?? registry.config.options.defaultSearchLimit;
60877
61372
  return jsonResult({
@@ -60881,13 +61376,13 @@ async function handleServerTool(server, request, registry, downstream, openapi,
60881
61376
  });
60882
61377
  }
60883
61378
  case "get_tool": {
60884
- const tool = await backendFor(server, downstream, openapi, graphql).getTool(server, parsed.tool);
61379
+ const tool = await backendFor(server, downstream, openapi, graphql, http).getTool(server, parsed.tool);
60885
61380
  return jsonResult({
60886
61381
  server: server.server,
60887
61382
  tool
60888
61383
  });
60889
61384
  }
60890
- case "call_tool": return backendFor(server, downstream, openapi, graphql).callTool(server, parsed.tool, parsed.arguments);
61385
+ case "call_tool": return backendFor(server, downstream, openapi, graphql, http).callTool(server, parsed.tool, parsed.arguments);
60891
61386
  }
60892
61387
  }
60893
61388
  function validateOperationRequest(request, maxSearchLimit) {
@@ -60950,7 +61445,7 @@ function jsonResult(value) {
60950
61445
  function isPlainObject(value) {
60951
61446
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
60952
61447
  }
60953
- function backendFor(server, downstream, openapi, graphql) {
61448
+ function backendFor(server, downstream, openapi, graphql, http) {
60954
61449
  if (server.backend === "mcp") return {
60955
61450
  check: (...args) => downstream.checkServer(...args),
60956
61451
  listTools: (...args) => downstream.listTools(...args),
@@ -60970,6 +61465,17 @@ function backendFor(server, downstream, openapi, graphql) {
60970
61465
  search: (...args) => graphql.search(...args)
60971
61466
  };
60972
61467
  }
61468
+ if (server.backend === "http") {
61469
+ if (!http) throw new CapletsError("INTERNAL_ERROR", "HTTP action manager is not configured");
61470
+ return {
61471
+ check: (...args) => http.checkApi(...args),
61472
+ listTools: (...args) => http.listTools(...args),
61473
+ getTool: (...args) => http.getTool(...args),
61474
+ callTool: (...args) => http.callTool(...args),
61475
+ compact: (...args) => http.compact(...args),
61476
+ search: (...args) => http.search(...args)
61477
+ };
61478
+ }
60973
61479
  if (!openapi) throw new CapletsError("INTERNAL_ERROR", "OpenAPI manager is not configured");
60974
61480
  return {
60975
61481
  check: (...args) => openapi.checkEndpoint(...args),
@@ -60988,6 +61494,7 @@ var CapletsRuntime = class {
60988
61494
  downstream;
60989
61495
  openapi;
60990
61496
  graphql;
61497
+ http;
60991
61498
  tools = /* @__PURE__ */ new Map();
60992
61499
  paths;
60993
61500
  watchDebounceMs;
@@ -61008,6 +61515,7 @@ var CapletsRuntime = class {
61008
61515
  this.downstream = new DownstreamManager(this.registry, selectAuthOptions(options.authDir));
61009
61516
  this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir));
61010
61517
  this.graphql = new GraphQLManager(this.registry, selectAuthOptions(options.authDir));
61518
+ this.http = new HttpActionManager(this.registry, selectAuthOptions(options.authDir));
61011
61519
  this.server = options.server ?? new McpServer({
61012
61520
  name: "caplets",
61013
61521
  version
@@ -61083,6 +61591,7 @@ var CapletsRuntime = class {
61083
61591
  this.downstream.updateRegistry(nextRegistry);
61084
61592
  this.openapi.updateRegistry(nextRegistry);
61085
61593
  this.graphql.updateRegistry(nextRegistry);
61594
+ this.http.updateRegistry(nextRegistry);
61086
61595
  let invalidated = true;
61087
61596
  try {
61088
61597
  await this.invalidateChangedBackends(previousConfig, nextConfig);
@@ -61141,7 +61650,7 @@ var CapletsRuntime = class {
61141
61650
  }
61142
61651
  async handleTool(serverId, request) {
61143
61652
  try {
61144
- return await handleServerTool(this.registry.require(serverId), request, this.registry, this.downstream, this.openapi, this.graphql);
61653
+ return await handleServerTool(this.registry.require(serverId), request, this.registry, this.downstream, this.openapi, this.graphql, this.http);
61145
61654
  } catch (error) {
61146
61655
  return errorResult(error);
61147
61656
  }
@@ -61157,6 +61666,7 @@ var CapletsRuntime = class {
61157
61666
  if (before?.backend === "mcp") await this.downstream.closeServer(serverId);
61158
61667
  if (before?.backend === "openapi" || after?.backend === "openapi" || !after) this.openapi.invalidate(serverId);
61159
61668
  if (before?.backend === "graphql" || after?.backend === "graphql" || !after) this.graphql.invalidate(serverId);
61669
+ if (before?.backend === "http" || after?.backend === "http" || !after) this.http.invalidate(serverId);
61160
61670
  }
61161
61671
  }
61162
61672
  resetWatchers() {
@@ -61248,14 +61758,15 @@ function allCaplets(config) {
61248
61758
  return [
61249
61759
  ...Object.values(config.mcpServers),
61250
61760
  ...Object.values(config.openapiEndpoints),
61251
- ...Object.values(config.graphqlEndpoints)
61761
+ ...Object.values(config.graphqlEndpoints),
61762
+ ...Object.values(config.httpApis)
61252
61763
  ];
61253
61764
  }
61254
61765
  function nextEnabledServers(config) {
61255
61766
  return allCaplets(config).filter((server) => !server.disabled);
61256
61767
  }
61257
61768
  function capletById(config, serverId) {
61258
- return config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? config.graphqlEndpoints[serverId];
61769
+ return config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? config.graphqlEndpoints[serverId] ?? config.httpApis[serverId];
61259
61770
  }
61260
61771
  function serializeCaplet(caplet) {
61261
61772
  return JSON.stringify(caplet ?? null);