caplets 0.7.0 → 0.8.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
@@ -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.8.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;
@@ -18680,6 +18680,7 @@ function matter(file, options) {
18680
18680
  //#region src/config/validation.ts
18681
18681
  const SERVER_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
18682
18682
  const HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
18683
+ const HTTP_BASE_URL_PATTERN = /^(?![a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/?#]*@)[^?#]*$/;
18683
18684
  const FORBIDDEN_HEADERS = new Set([
18684
18685
  "accept",
18685
18686
  "authorization",
@@ -18697,6 +18698,16 @@ const FORBIDDEN_HEADERS = new Set([
18697
18698
  "transfer-encoding",
18698
18699
  "upgrade"
18699
18700
  ]);
18701
+ function validateHttpActionHeaders(headers, ctx, path) {
18702
+ for (const headerName of Object.keys(headers)) {
18703
+ const normalized = headerName.toLowerCase();
18704
+ if (!HEADER_NAME_PATTERN.test(headerName) || FORBIDDEN_HEADERS.has(normalized)) ctx.addIssue({
18705
+ code: "custom",
18706
+ path: [...path, headerName],
18707
+ message: `header ${headerName} is not allowed`
18708
+ });
18709
+ }
18710
+ }
18700
18711
  function isAllowedRemoteUrl(value) {
18701
18712
  let url;
18702
18713
  try {
@@ -18712,6 +18723,23 @@ function isAllowedRemoteUrl(value) {
18712
18723
  "::1"
18713
18724
  ].includes(url.hostname);
18714
18725
  }
18726
+ function isAllowedHttpBaseUrl(value) {
18727
+ let url;
18728
+ try {
18729
+ url = new URL(value);
18730
+ } catch {
18731
+ return false;
18732
+ }
18733
+ return isAllowedRemoteUrl(value) && !url.username && !url.password && !url.search && !url.hash;
18734
+ }
18735
+ function isUrl(value) {
18736
+ try {
18737
+ new URL(value);
18738
+ return true;
18739
+ } catch {
18740
+ return false;
18741
+ }
18742
+ }
18715
18743
  //#endregion
18716
18744
  //#region src/caplet-files.ts
18717
18745
  const MAX_CAPLET_FILE_BYTES = 128 * 1024;
@@ -18917,6 +18945,52 @@ const capletGraphQlEndpointSchema = object$1({
18917
18945
  });
18918
18946
  validateEndpointAuthHeaders$1(endpoint.auth, ctx);
18919
18947
  });
18948
+ const httpScalarMappingSchema$1 = record(string(), union([
18949
+ string(),
18950
+ number$1(),
18951
+ boolean()
18952
+ ]));
18953
+ const capletHttpActionSchema = object$1({
18954
+ method: _enum([
18955
+ "GET",
18956
+ "POST",
18957
+ "PUT",
18958
+ "PATCH",
18959
+ "DELETE"
18960
+ ]).describe("HTTP method used for this action."),
18961
+ 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"),
18962
+ description: string().min(1).optional().describe("Action capability description."),
18963
+ inputSchema: record(string(), unknown()).optional().describe("JSON Schema for call_tool arguments."),
18964
+ query: httpScalarMappingSchema$1.optional().describe("Query parameter mapping."),
18965
+ headers: httpScalarMappingSchema$1.optional().describe("Request header mapping."),
18966
+ jsonBody: unknown().optional().describe("JSON request body mapping.")
18967
+ }).strict().superRefine((action, ctx) => {
18968
+ if (action.method === "GET" && action.jsonBody !== void 0) ctx.addIssue({
18969
+ code: "custom",
18970
+ path: ["jsonBody"],
18971
+ message: "HTTP GET actions must not define jsonBody"
18972
+ });
18973
+ });
18974
+ const capletHttpApiSchema = object$1({
18975
+ 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."),
18976
+ auth: capletEndpointAuthSchema.describe("Explicit HTTP API request auth config. Use {\"type\":\"none\"} for public APIs."),
18977
+ 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."),
18978
+ requestTimeoutMs: number$1().int().positive().optional().describe("Timeout in milliseconds for HTTP action requests."),
18979
+ maxResponseBytes: number$1().int().positive().optional().describe("Maximum HTTP action response body bytes to read."),
18980
+ disabled: boolean().optional().describe("When true, omit this Caplet from discovery.")
18981
+ }).strict().superRefine((api, ctx) => {
18982
+ if (api.baseUrl && !hasEnvReference$1(api.baseUrl) && !isAllowedHttpBaseUrl(api.baseUrl)) ctx.addIssue({
18983
+ code: "custom",
18984
+ path: ["baseUrl"],
18985
+ message: "HTTP API baseUrl must use https except loopback development urls and must not include credentials, query, or fragment"
18986
+ });
18987
+ validateEndpointAuthHeaders$1(api.auth, ctx);
18988
+ for (const [actionName, action] of Object.entries(api.actions)) if (action.headers) validateHttpActionHeaders(action.headers, ctx, [
18989
+ "actions",
18990
+ actionName,
18991
+ "headers"
18992
+ ]);
18993
+ });
18920
18994
  const capletFileSchema = object$1({
18921
18995
  $schema: string().url().optional().describe("Optional JSON Schema URL for editor validation."),
18922
18996
  name: string().trim().min(1).max(80).describe("Human-readable Caplet display name."),
@@ -18924,11 +18998,12 @@ const capletFileSchema = object$1({
18924
18998
  tags: array(string().trim().min(1).max(80)).optional().describe("Optional tags for grouping or searching Caplets."),
18925
18999
  mcpServer: capletMcpServerSchema.describe("MCP server backend configuration for this Caplet.").optional(),
18926
19000
  openapiEndpoint: capletOpenApiEndpointSchema.describe("OpenAPI endpoint backend configuration for this Caplet.").optional(),
18927
- graphqlEndpoint: capletGraphQlEndpointSchema.describe("GraphQL endpoint backend configuration for this Caplet.").optional()
19001
+ graphqlEndpoint: capletGraphQlEndpointSchema.describe("GraphQL endpoint backend configuration for this Caplet.").optional(),
19002
+ httpApi: capletHttpApiSchema.describe("HTTP API backend configuration for this Caplet.").optional()
18928
19003
  }).strict().superRefine((frontmatter, ctx) => {
18929
- if (Number(Boolean(frontmatter.mcpServer)) + Number(Boolean(frontmatter.openapiEndpoint)) + Number(Boolean(frontmatter.graphqlEndpoint)) !== 1) ctx.addIssue({
19004
+ if (Number(Boolean(frontmatter.mcpServer)) + Number(Boolean(frontmatter.openapiEndpoint)) + Number(Boolean(frontmatter.graphqlEndpoint)) + Number(Boolean(frontmatter.httpApi)) !== 1) ctx.addIssue({
18930
19005
  code: "custom",
18931
- message: "Caplet file must define exactly one backend: mcpServer, openapiEndpoint, or graphqlEndpoint"
19006
+ message: "Caplet file must define exactly one backend: mcpServer, openapiEndpoint, graphqlEndpoint, or httpApi"
18932
19007
  });
18933
19008
  });
18934
19009
  function loadCapletFiles(root) {
@@ -18936,24 +19011,30 @@ function loadCapletFiles(root) {
18936
19011
  const servers = {};
18937
19012
  const openapiEndpoints = {};
18938
19013
  const graphqlEndpoints = {};
19014
+ const httpApis = {};
18939
19015
  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}`);
19016
+ 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
19017
  const config = readCapletFile(candidate.path);
18942
- if (isPlainObject$3(config) && config.backend === "openapi") {
19018
+ if (isPlainObject$4(config) && config.backend === "openapi") {
18943
19019
  const { backend: _backend, ...endpoint } = config;
18944
19020
  openapiEndpoints[candidate.id] = endpoint;
18945
- } else if (isPlainObject$3(config) && config.backend === "graphql") {
19021
+ } else if (isPlainObject$4(config) && config.backend === "graphql") {
18946
19022
  const { backend: _backend, ...endpoint } = config;
18947
19023
  graphqlEndpoints[candidate.id] = endpoint;
19024
+ } else if (isPlainObject$4(config) && config.backend === "http") {
19025
+ const { backend: _backend, ...endpoint } = config;
19026
+ httpApis[candidate.id] = endpoint;
18948
19027
  } else servers[candidate.id] = config;
18949
19028
  }
18950
19029
  const hasServers = Object.keys(servers).length > 0;
18951
19030
  const hasOpenApi = Object.keys(openapiEndpoints).length > 0;
18952
19031
  const hasGraphQl = Object.keys(graphqlEndpoints).length > 0;
18953
- return hasServers || hasOpenApi || hasGraphQl ? {
19032
+ const hasHttpApis = Object.keys(httpApis).length > 0;
19033
+ return hasServers || hasOpenApi || hasGraphQl || hasHttpApis ? {
18954
19034
  ...hasServers ? { mcpServers: servers } : {},
18955
19035
  ...hasOpenApi ? { openapiEndpoints } : {},
18956
- ...hasGraphQl ? { graphqlEndpoints } : {}
19036
+ ...hasGraphQl ? { graphqlEndpoints } : {},
19037
+ ...hasHttpApis ? { httpApis } : {}
18957
19038
  } : void 0;
18958
19039
  }
18959
19040
  function discoverCapletFiles(root) {
@@ -19011,6 +19092,14 @@ function capletToServerConfig(frontmatter, body, baseDir) {
19011
19092
  ...frontmatter.tags ? { tags: frontmatter.tags } : {},
19012
19093
  body
19013
19094
  };
19095
+ if (frontmatter.httpApi) return {
19096
+ ...frontmatter.httpApi,
19097
+ backend: "http",
19098
+ name: frontmatter.name,
19099
+ description: frontmatter.description,
19100
+ ...frontmatter.tags ? { tags: frontmatter.tags } : {},
19101
+ body
19102
+ };
19014
19103
  return {
19015
19104
  ...frontmatter.mcpServer,
19016
19105
  name: frontmatter.name,
@@ -19053,7 +19142,7 @@ function parseFrontmatter(text, path) {
19053
19142
  value: text
19054
19143
  });
19055
19144
  matter(file, { strip: true });
19056
- if (!isPlainObject$3(file.data.matter) || Object.keys(file.data.matter).length === 0) throw new Error("empty frontmatter");
19145
+ if (!isPlainObject$4(file.data.matter) || Object.keys(file.data.matter).length === 0) throw new Error("empty frontmatter");
19057
19146
  return {
19058
19147
  frontmatter: file.data.matter,
19059
19148
  body: String(file)
@@ -19062,7 +19151,7 @@ function parseFrontmatter(text, path) {
19062
19151
  throw new CapletsError("CONFIG_INVALID", `Caplet file at ${path} has invalid YAML frontmatter`, redactSecrets(error));
19063
19152
  }
19064
19153
  }
19065
- function isPlainObject$3(value) {
19154
+ function isPlainObject$4(value) {
19066
19155
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19067
19156
  }
19068
19157
  function validateCapletId(id, path) {
@@ -19071,14 +19160,6 @@ function validateCapletId(id, path) {
19071
19160
  function hasEnvReference$1(value) {
19072
19161
  return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]*/.test(value);
19073
19162
  }
19074
- function isUrl(value) {
19075
- try {
19076
- new URL(value);
19077
- return true;
19078
- } catch {
19079
- return false;
19080
- }
19081
- }
19082
19163
  //#endregion
19083
19164
  //#region src/config/paths.ts
19084
19165
  const DEFAULT_CONFIG_PATH = join(homedir(), ".caplets", "config.json");
@@ -19248,7 +19329,45 @@ const publicGraphQlEndpointSchema = object$1({
19248
19329
  });
19249
19330
  });
19250
19331
  const normalizedGraphQlEndpointSchema = publicGraphQlEndpointSchema.extend({ body: string().optional() });
19251
- function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlEndpointValueSchema) {
19332
+ const httpScalarMappingSchema = record(string(), union([
19333
+ string(),
19334
+ number$1(),
19335
+ boolean()
19336
+ ]));
19337
+ const httpActionSchema = object$1({
19338
+ method: _enum([
19339
+ "GET",
19340
+ "POST",
19341
+ "PUT",
19342
+ "PATCH",
19343
+ "DELETE"
19344
+ ]).describe("HTTP method used for this action."),
19345
+ 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"),
19346
+ description: string().min(1).optional().describe("Action capability description."),
19347
+ inputSchema: record(string(), unknown()).optional().describe("JSON Schema for call_tool arguments."),
19348
+ query: httpScalarMappingSchema.optional().describe("Query parameter mapping."),
19349
+ headers: httpScalarMappingSchema.optional().describe("Request header mapping."),
19350
+ jsonBody: unknown().optional().describe("JSON request body mapping.")
19351
+ }).strict().superRefine((action, ctx) => {
19352
+ if (action.method === "GET" && action.jsonBody !== void 0) ctx.addIssue({
19353
+ code: "custom",
19354
+ path: ["jsonBody"],
19355
+ message: "HTTP GET actions must not define jsonBody"
19356
+ });
19357
+ });
19358
+ const publicHttpApiSchema = object$1({
19359
+ name: string().trim().min(1).max(80).describe("Human-readable HTTP API display name."),
19360
+ 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"),
19361
+ 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."),
19362
+ auth: openApiAuthSchema.describe("Explicit HTTP API request auth config. Use {\"type\":\"none\"} for public APIs."),
19363
+ 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."),
19364
+ tags: array(string().trim().min(1).max(80)).optional(),
19365
+ requestTimeoutMs: number$1().int().positive().default(6e4).describe("Timeout in milliseconds for HTTP action requests."),
19366
+ maxResponseBytes: number$1().int().positive().default(1e6).describe("Maximum HTTP action response body bytes to read."),
19367
+ disabled: boolean().default(false).describe("When true, omit this HTTP API Caplet.")
19368
+ }).strict();
19369
+ const normalizedHttpApiSchema = publicHttpApiSchema.extend({ body: string().optional() });
19370
+ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlEndpointValueSchema, httpApiValueSchema) {
19252
19371
  return object$1({
19253
19372
  $schema: string().url().optional().describe("Optional JSON Schema URL for editor validation."),
19254
19373
  version: literal(1).default(1).describe("Caplets config schema version."),
@@ -19256,7 +19375,8 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
19256
19375
  maxSearchLimit: number$1().int().positive().max(50).default(50).describe("Maximum accepted search_tools limit."),
19257
19376
  mcpServers: record(string().regex(SERVER_ID_PATTERN), serverValueSchema).default({}).describe("Downstream MCP servers keyed by stable server ID."),
19258
19377
  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.")
19378
+ graphqlEndpoints: record(string().regex(SERVER_ID_PATTERN), graphQlEndpointValueSchema).default({}).describe("GraphQL endpoints keyed by stable Caplet ID."),
19379
+ httpApis: record(string().regex(SERVER_ID_PATTERN), httpApiValueSchema).default({}).describe("HTTP APIs keyed by stable Caplet ID.")
19260
19380
  }).strict().superRefine((config, ctx) => {
19261
19381
  if (config.defaultSearchLimit > config.maxSearchLimit) ctx.addIssue({
19262
19382
  code: "custom",
@@ -19410,10 +19530,45 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
19410
19530
  "auth"
19411
19531
  ]);
19412
19532
  }
19533
+ for (const [endpoint, rawValue] of Object.entries(config.httpApis)) {
19534
+ const raw = rawValue;
19535
+ const duplicateBackend = config.mcpServers[endpoint] ? "mcpServers" : config.openapiEndpoints[endpoint] ? "openapiEndpoints" : config.graphqlEndpoints[endpoint] ? "graphqlEndpoints" : void 0;
19536
+ if (duplicateBackend) ctx.addIssue({
19537
+ code: "custom",
19538
+ path: ["httpApis", endpoint],
19539
+ message: `Caplet ID ${endpoint} is already used by ${duplicateBackend}`
19540
+ });
19541
+ if (!SERVER_ID_PATTERN.test(endpoint)) ctx.addIssue({
19542
+ code: "custom",
19543
+ path: ["httpApis", endpoint],
19544
+ message: "HTTP API ID must match ^[a-zA-Z0-9_-]{1,64}$"
19545
+ });
19546
+ if (raw.baseUrl && !isAllowedHttpBaseUrl(raw.baseUrl)) ctx.addIssue({
19547
+ code: "custom",
19548
+ path: [
19549
+ "httpApis",
19550
+ endpoint,
19551
+ "baseUrl"
19552
+ ],
19553
+ message: "HTTP API baseUrl must use https except loopback development urls and must not include credentials, query, or fragment"
19554
+ });
19555
+ validateEndpointAuthHeaders(raw.auth, ctx, [
19556
+ "httpApis",
19557
+ endpoint,
19558
+ "auth"
19559
+ ]);
19560
+ for (const [actionName, action] of Object.entries(raw.actions)) if (action.headers) validateHttpActionHeaders(action.headers, ctx, [
19561
+ "httpApis",
19562
+ endpoint,
19563
+ "actions",
19564
+ actionName,
19565
+ "headers"
19566
+ ]);
19567
+ }
19413
19568
  });
19414
19569
  }
19415
- const configFileSchema = configSchemaFor(publicServerSchema, publicOpenApiEndpointSchema, publicGraphQlEndpointSchema);
19416
- const normalizedConfigFileSchema = configSchemaFor(normalizedServerSchema, normalizedOpenApiEndpointSchema, normalizedGraphQlEndpointSchema);
19570
+ const configFileSchema = configSchemaFor(publicServerSchema, publicOpenApiEndpointSchema, publicGraphQlEndpointSchema, publicHttpApiSchema);
19571
+ const normalizedConfigFileSchema = configSchemaFor(normalizedServerSchema, normalizedOpenApiEndpointSchema, normalizedGraphQlEndpointSchema, normalizedHttpApiSchema);
19417
19572
  function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConfigPath()) {
19418
19573
  const hasUserConfig = existsSync(path);
19419
19574
  const hasProjectConfig = existsSync(projectPath);
@@ -19424,7 +19579,7 @@ function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConf
19424
19579
  if (!hasUserConfig && !hasProjectConfig && !userCaplets && !projectCaplets) throw new CapletsError("CONFIG_NOT_FOUND", `Caplets config not found at ${path} or ${projectPath}`);
19425
19580
  try {
19426
19581
  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");
19582
+ 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
19583
  return config;
19429
19584
  } catch (error) {
19430
19585
  if (error instanceof CapletsError) throw error;
@@ -19454,7 +19609,7 @@ function normalizeLocalPaths(input, baseDir) {
19454
19609
  }
19455
19610
  function normalizeEndpointPaths(endpoints, baseDir, normalize) {
19456
19611
  if (!endpoints) return;
19457
- return Object.fromEntries(Object.entries(endpoints).map(([id, endpoint]) => [id, isPlainObject$2(endpoint) ? normalize(endpoint, baseDir) : endpoint]));
19612
+ return Object.fromEntries(Object.entries(endpoints).map(([id, endpoint]) => [id, isPlainObject$3(endpoint) ? normalize(endpoint, baseDir) : endpoint]));
19458
19613
  }
19459
19614
  function normalizeOpenApiPath(endpoint, baseDir) {
19460
19615
  return {
@@ -19463,7 +19618,7 @@ function normalizeOpenApiPath(endpoint, baseDir) {
19463
19618
  };
19464
19619
  }
19465
19620
  function normalizeGraphQlPath(endpoint, baseDir) {
19466
- const operations = isPlainObject$2(endpoint.operations) ? Object.fromEntries(Object.entries(endpoint.operations).map(([name, operation]) => [name, isPlainObject$2(operation) ? {
19621
+ const operations = isPlainObject$3(endpoint.operations) ? Object.fromEntries(Object.entries(endpoint.operations).map(([name, operation]) => [name, isPlainObject$3(operation) ? {
19467
19622
  ...operation,
19468
19623
  documentPath: normalizeLocalPath(operation.documentPath, baseDir)
19469
19624
  } : operation])) : endpoint.operations;
@@ -19480,6 +19635,7 @@ function normalizeLocalPath(value, baseDir) {
19480
19635
  function rejectUntrustedProjectExecutableBackends(input, path) {
19481
19636
  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
19637
  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`);
19638
+ 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
19639
  return input;
19484
19640
  }
19485
19641
  function mergeConfigInputs(...inputs) {
@@ -19500,6 +19656,10 @@ function mergeConfigInputs(...inputs) {
19500
19656
  graphqlEndpoints: {
19501
19657
  ...merged?.graphqlEndpoints,
19502
19658
  ...input.graphqlEndpoints
19659
+ },
19660
+ httpApis: {
19661
+ ...merged?.httpApis,
19662
+ ...input.httpApis
19503
19663
  }
19504
19664
  };
19505
19665
  }
@@ -19530,6 +19690,12 @@ function parseConfig(input) {
19530
19690
  server,
19531
19691
  backend: "graphql"
19532
19692
  });
19693
+ const httpApis = {};
19694
+ for (const [server, raw] of Object.entries(parsed.data.httpApis)) httpApis[server] = stripUndefined({
19695
+ ...raw,
19696
+ server,
19697
+ backend: "http"
19698
+ });
19533
19699
  return {
19534
19700
  version: parsed.data.version,
19535
19701
  options: {
@@ -19538,7 +19704,8 @@ function parseConfig(input) {
19538
19704
  },
19539
19705
  mcpServers: servers,
19540
19706
  openapiEndpoints,
19541
- graphqlEndpoints
19707
+ graphqlEndpoints,
19708
+ httpApis
19542
19709
  };
19543
19710
  }
19544
19711
  function validateEndpointAuthHeaders(auth, ctx, path) {
@@ -19567,10 +19734,10 @@ function interpolateConfig(value, path = []) {
19567
19734
  return value;
19568
19735
  }
19569
19736
  function isPublicMetadataPath(path) {
19570
- if (path.length < 3 || path[0] !== "mcpServers" && path[0] !== "openapiEndpoints" && path[0] !== "graphqlEndpoints") return false;
19737
+ if (path.length < 3 || path[0] !== "mcpServers" && path[0] !== "openapiEndpoints" && path[0] !== "graphqlEndpoints" && path[0] !== "httpApis") return false;
19571
19738
  return NON_INTERPOLATED_SERVER_FIELDS.has(path[2] ?? "");
19572
19739
  }
19573
- function isPlainObject$2(value) {
19740
+ function isPlainObject$3(value) {
19574
19741
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19575
19742
  }
19576
19743
  function hasEnvReference(value) {
@@ -19629,7 +19796,8 @@ function authTargets(config) {
19629
19796
  return [
19630
19797
  ...Object.values(config.mcpServers).filter((server) => server.transport !== "stdio" && (server.auth?.type === "oauth2" || server.auth?.type === "oidc")),
19631
19798
  ...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)
19799
+ ...Object.values(config.graphqlEndpoints).filter((endpoint) => endpoint.auth?.type === "oauth2" || endpoint.auth?.type === "oidc").map(graphQlAuthTarget),
19800
+ ...Object.values(config.httpApis).filter((api) => api.auth?.type === "oauth2" || api.auth?.type === "oidc").map(httpAuthTarget)
19633
19801
  ];
19634
19802
  }
19635
19803
  function graphQlAuthTarget(endpoint) {
@@ -19638,6 +19806,9 @@ function graphQlAuthTarget(endpoint) {
19638
19806
  url: endpoint.endpointUrl
19639
19807
  };
19640
19808
  }
19809
+ function httpAuthTarget(api) {
19810
+ return { ...api };
19811
+ }
19641
19812
  function assertLoginTarget(target, serverId) {
19642
19813
  if (!target) throw new CapletsError("SERVER_NOT_FOUND", `Server ${serverId} is not configured for OAuth`);
19643
19814
  if ("disabled" in target && target.disabled) throw new CapletsError("SERVER_UNAVAILABLE", `Server ${serverId} is disabled`);
@@ -25649,7 +25820,7 @@ var Protocol = class {
25649
25820
  };
25650
25821
  }
25651
25822
  };
25652
- function isPlainObject$1(value) {
25823
+ function isPlainObject$2(value) {
25653
25824
  return value !== null && typeof value === "object" && !Array.isArray(value);
25654
25825
  }
25655
25826
  function mergeCapabilities(base, additional) {
@@ -25659,7 +25830,7 @@ function mergeCapabilities(base, additional) {
25659
25830
  const addValue = additional[k];
25660
25831
  if (addValue === void 0) continue;
25661
25832
  const baseValue = result[k];
25662
- if (isPlainObject$1(baseValue) && isPlainObject$1(addValue)) result[k] = {
25833
+ if (isPlainObject$2(baseValue) && isPlainObject$2(addValue)) result[k] = {
25663
25834
  ...baseValue,
25664
25835
  ...addValue
25665
25836
  };
@@ -50843,6 +51014,279 @@ function graphQlCacheKey(endpoint) {
50843
51014
  });
50844
51015
  }
50845
51016
  //#endregion
51017
+ //#region src/http-actions.ts
51018
+ const DEFAULT_INPUT_SCHEMA = {
51019
+ type: "object",
51020
+ additionalProperties: true
51021
+ };
51022
+ var HttpActionManager = class {
51023
+ registry;
51024
+ options;
51025
+ constructor(registry, options = {}) {
51026
+ this.registry = registry;
51027
+ this.options = options;
51028
+ }
51029
+ updateRegistry(registry) {
51030
+ this.registry = registry;
51031
+ }
51032
+ invalidate(_serverId) {}
51033
+ async checkApi(api) {
51034
+ const startedAt = Date.now();
51035
+ try {
51036
+ const operations = operationsFor(api);
51037
+ validateBaseUrl(api);
51038
+ authHeaders$1(api, this.options.authDir);
51039
+ for (const operation of operations) validateAction(api, operation);
51040
+ this.registry.setStatus(api.server, "available");
51041
+ return {
51042
+ server: api.server,
51043
+ status: "available",
51044
+ toolCount: operations.length,
51045
+ elapsedMs: Date.now() - startedAt
51046
+ };
51047
+ } catch (error) {
51048
+ const safe = toSafeError(error, "SERVER_UNAVAILABLE");
51049
+ this.registry.setStatus(api.server, "unavailable", safe);
51050
+ return {
51051
+ server: api.server,
51052
+ status: "unavailable",
51053
+ elapsedMs: Date.now() - startedAt,
51054
+ error: safe
51055
+ };
51056
+ }
51057
+ }
51058
+ async listTools(api) {
51059
+ return operationsFor(api).map((operation) => this.toTool(operation));
51060
+ }
51061
+ async getTool(api, toolName) {
51062
+ return this.toTool(getOperation(api, toolName));
51063
+ }
51064
+ async callTool(api, toolName, args) {
51065
+ const operation = getOperation(api, toolName);
51066
+ const startedAt = Date.now();
51067
+ const request = buildRequest$1(api, operation, args, this.options.authDir);
51068
+ const controller = new AbortController();
51069
+ const timeout = setTimeout(() => controller.abort(), api.requestTimeoutMs);
51070
+ try {
51071
+ const response = await fetch(request.url, {
51072
+ method: operation.method,
51073
+ headers: request.headers,
51074
+ redirect: "manual",
51075
+ signal: controller.signal,
51076
+ ...request.body === void 0 ? {} : { body: request.body }
51077
+ });
51078
+ if (response.status >= 300 && response.status < 400) throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "HTTP action request returned a redirect", {
51079
+ server: api.server,
51080
+ status: response.status,
51081
+ location: response.headers.get("location") ? "[REDACTED]" : void 0
51082
+ });
51083
+ const parsed = await readResponse$1(response, api, Date.now() - startedAt);
51084
+ return {
51085
+ content: [{
51086
+ type: "text",
51087
+ text: JSON.stringify(parsed, null, 2)
51088
+ }],
51089
+ structuredContent: parsed,
51090
+ isError: !response.ok
51091
+ };
51092
+ } catch (error) {
51093
+ if (isAbortError(error)) throw new CapletsError("TOOL_CALL_TIMEOUT", `HTTP action request timed out for ${api.server}/${toolName}`);
51094
+ if (error instanceof CapletsError) throw error;
51095
+ throw new CapletsError("DOWNSTREAM_TOOL_ERROR", `HTTP action request failed for ${api.server}/${toolName}`, toSafeError(error));
51096
+ } finally {
51097
+ clearTimeout(timeout);
51098
+ }
51099
+ }
51100
+ compact(api, tool) {
51101
+ return {
51102
+ server: api.server,
51103
+ tool: tool.name,
51104
+ ...tool.description ? { description: tool.description } : {},
51105
+ ...tool.annotations ? { annotations: tool.annotations } : {},
51106
+ hasInputSchema: Boolean(tool.inputSchema)
51107
+ };
51108
+ }
51109
+ search(api, tools, query, limit) {
51110
+ const needle = query.toLocaleLowerCase();
51111
+ 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));
51112
+ }
51113
+ toTool(operation) {
51114
+ return {
51115
+ name: operation.name,
51116
+ ...operation.description ? { description: operation.description } : {},
51117
+ inputSchema: operation.inputSchema ?? DEFAULT_INPUT_SCHEMA,
51118
+ annotations: {
51119
+ readOnlyHint: operation.method === "GET",
51120
+ destructiveHint: operation.method === "DELETE"
51121
+ }
51122
+ };
51123
+ }
51124
+ };
51125
+ function operationsFor(api) {
51126
+ return Object.entries(api.actions).map(([name, action]) => ({
51127
+ name,
51128
+ ...action
51129
+ })).sort((left, right) => left.name.localeCompare(right.name));
51130
+ }
51131
+ function getOperation(api, toolName) {
51132
+ const operations = operationsFor(api);
51133
+ const operation = operations.find((candidate) => candidate.name === toolName);
51134
+ if (!operation) throw new CapletsError("TOOL_NOT_FOUND", `Tool ${toolName} was not found on ${api.server}`, {
51135
+ server: api.server,
51136
+ tool: toolName,
51137
+ suggestions: operations.map((candidate) => candidate.name).filter((name) => name.toLocaleLowerCase().includes(toolName.toLocaleLowerCase()[0] ?? "")).slice(0, 5)
51138
+ });
51139
+ return operation;
51140
+ }
51141
+ function buildRequest$1(api, operation, args, authDir) {
51142
+ validateBaseUrl(api);
51143
+ validateAction(api, operation);
51144
+ const url = buildActionUrl(api.baseUrl, substitutePath$1(operation.path, args, operation), { allowEncodedSlash: true });
51145
+ const query = resolveMappingToRecord(operation.query, args, "query");
51146
+ for (const [key, value] of Object.entries(query)) if (value !== void 0 && value !== null) url.searchParams.append(key, serializeHttpValue$1("query", key, value));
51147
+ const headers = new Headers();
51148
+ applyAuth$1(headers, api, authDir);
51149
+ const resolvedHeaders = resolveMappingToRecord(operation.headers, args, "headers");
51150
+ for (const [key, value] of Object.entries(resolvedHeaders)) if (value !== void 0 && value !== null) {
51151
+ validateResolvedHeader(api, operation, key);
51152
+ headers.set(key, serializeHttpValue$1("header", key, value));
51153
+ }
51154
+ const bodyValue = resolveMapping(operation.jsonBody, args);
51155
+ if (operation.jsonBody !== void 0) {
51156
+ if (bodyValue === void 0) throw new CapletsError("REQUEST_INVALID", "HTTP action jsonBody must not resolve to undefined");
51157
+ headers.set("content-type", "application/json");
51158
+ return {
51159
+ url,
51160
+ headers,
51161
+ body: JSON.stringify(bodyValue)
51162
+ };
51163
+ }
51164
+ return {
51165
+ url,
51166
+ headers
51167
+ };
51168
+ }
51169
+ function substitutePath$1(path, args, operation) {
51170
+ return path.replace(/\{([^}]+)\}/g, (_match, name) => {
51171
+ const value = args[name];
51172
+ if (value === void 0 || value === null || value === "") throw new CapletsError("REQUEST_INVALID", `Missing required path parameter ${name}`, { tool: operation.name });
51173
+ return encodeURIComponent(serializeHttpValue$1("path", name, value));
51174
+ });
51175
+ }
51176
+ function resolveMapping(mapping, input) {
51177
+ if (typeof mapping === "string") {
51178
+ if (mapping === "$input") return input;
51179
+ if (mapping.startsWith("$input.")) return valueAtPath(input, mapping.slice(7));
51180
+ return mapping;
51181
+ }
51182
+ if (Array.isArray(mapping)) return mapping.map((item) => resolveMapping(item, input));
51183
+ if (mapping && typeof mapping === "object") return Object.fromEntries(Object.entries(mapping).map(([key, value]) => [key, resolveMapping(value, input)]));
51184
+ return mapping;
51185
+ }
51186
+ function resolveMappingToRecord(mapping, input, name) {
51187
+ if (mapping === void 0) return {};
51188
+ const resolved = resolveMapping(mapping, input);
51189
+ if (!isPlainObject$1(resolved)) throw new CapletsError("REQUEST_INVALID", `HTTP action ${name} mapping must resolve to an object`);
51190
+ return resolved;
51191
+ }
51192
+ function valueAtPath(input, path) {
51193
+ let current = input;
51194
+ for (const segment of path.split(".")) {
51195
+ if (!current || typeof current !== "object" || Array.isArray(current)) return;
51196
+ current = current[segment];
51197
+ }
51198
+ return current;
51199
+ }
51200
+ function validateAction(api, operation) {
51201
+ buildActionUrl(api.baseUrl, operation.path);
51202
+ validateConfiguredHeaders(api, operation);
51203
+ }
51204
+ function validateConfiguredHeaders(api, operation) {
51205
+ const configured = asRecord$1(operation.headers);
51206
+ for (const key of Object.keys(configured)) validateResolvedHeader(api, operation, key);
51207
+ }
51208
+ function validateResolvedHeader(api, operation, key) {
51209
+ const authHeaderNames = api.auth.type === "headers" ? new Set(Object.keys(api.auth.headers).map((header) => header.toLowerCase())) : /* @__PURE__ */ new Set();
51210
+ const normalized = key.toLowerCase();
51211
+ if (FORBIDDEN_HEADERS.has(normalized) || authHeaderNames.has(normalized)) throw new CapletsError("CONFIG_INVALID", `HTTP action header ${key} is not allowed`, {
51212
+ server: api.server,
51213
+ tool: operation.name
51214
+ });
51215
+ }
51216
+ function serializeHttpValue$1(location, name, value) {
51217
+ switch (typeof value) {
51218
+ case "string":
51219
+ case "number":
51220
+ case "boolean": return String(value);
51221
+ default: throw new CapletsError("REQUEST_INVALID", `HTTP action ${location} parameter ${name} must be a string, number, or boolean`);
51222
+ }
51223
+ }
51224
+ function applyAuth$1(headers, api, authDir) {
51225
+ for (const [key, value] of Object.entries(authHeaders$1(api, authDir))) headers.set(key, value);
51226
+ }
51227
+ function authHeaders$1(api, authDir) {
51228
+ switch (api.auth.type) {
51229
+ case "none": return {};
51230
+ case "bearer": return { authorization: `Bearer ${api.auth.token}` };
51231
+ case "headers": return api.auth.headers;
51232
+ case "oauth2":
51233
+ case "oidc": return genericOAuthHeaders({
51234
+ server: api.server,
51235
+ backend: "http",
51236
+ baseUrl: api.baseUrl,
51237
+ auth: api.auth,
51238
+ requestTimeoutMs: api.requestTimeoutMs
51239
+ }, authDir);
51240
+ }
51241
+ }
51242
+ async function readResponse$1(response, api, elapsedMs) {
51243
+ const contentType = response.headers.get("content-type") ?? "";
51244
+ const body = parseHttpBody(contentType, await readLimitedText(response, {
51245
+ maxBytes: maxResponseBytes(api),
51246
+ errorMessage: "HTTP action response exceeded byte limit"
51247
+ }));
51248
+ return {
51249
+ status: response.status,
51250
+ statusText: response.statusText,
51251
+ headers: { "content-type": contentType },
51252
+ ...body === void 0 ? {} : { body },
51253
+ elapsedMs
51254
+ };
51255
+ }
51256
+ function maxResponseBytes(api) {
51257
+ return api.maxResponseBytes;
51258
+ }
51259
+ function validateBaseUrl(api) {
51260
+ if (!isAllowedRemoteUrl(api.baseUrl)) throw new CapletsError("CONFIG_INVALID", `${api.server} HTTP API baseUrl is not allowed`);
51261
+ const url = new URL(api.baseUrl);
51262
+ 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`);
51263
+ }
51264
+ function buildActionUrl(base, actionPath, options = {}) {
51265
+ if (/^[a-z][a-z0-9+.-]*:/i.test(actionPath) || actionPath.startsWith("//")) throw new CapletsError("CONFIG_INVALID", "HTTP action path cannot change origin");
51266
+ for (const rawSegment of actionPath.split("/")) {
51267
+ let segment;
51268
+ try {
51269
+ segment = decodeURIComponent(rawSegment);
51270
+ } catch {
51271
+ throw new CapletsError("CONFIG_INVALID", "HTTP action path contains invalid encoding");
51272
+ }
51273
+ if (segment === "." || segment === ".." || !options.allowEncodedSlash && segment.includes("/")) throw new CapletsError("CONFIG_INVALID", "HTTP action path cannot contain dot segments");
51274
+ }
51275
+ const baseUrl = new URL(base);
51276
+ const originalOrigin = baseUrl.origin;
51277
+ const basePath = baseUrl.pathname.replace(/\/+$/, "");
51278
+ baseUrl.pathname = [basePath, actionPath.replace(/^\/+/, "")].filter(Boolean).join("/");
51279
+ if (baseUrl.origin !== originalOrigin) throw new CapletsError("CONFIG_INVALID", "HTTP action path cannot change origin");
51280
+ if (basePath && baseUrl.pathname !== basePath && !baseUrl.pathname.startsWith(`${basePath}/`)) throw new CapletsError("CONFIG_INVALID", "HTTP action path cannot escape baseUrl path");
51281
+ return baseUrl;
51282
+ }
51283
+ function asRecord$1(value) {
51284
+ return isPlainObject$1(value) ? value : {};
51285
+ }
51286
+ function isPlainObject$1(value) {
51287
+ return value !== null && typeof value === "object" && !Array.isArray(value);
51288
+ }
51289
+ //#endregion
50846
51290
  //#region node_modules/.pnpm/@apidevtools+swagger-parser@12.1.0_openapi-types@12.1.3/node_modules/@apidevtools/swagger-parser/lib/util.js
50847
51291
  var require_util = /* @__PURE__ */ __commonJSMin(((exports) => {
50848
51292
  const util = __require("util");
@@ -60731,7 +61175,7 @@ var ServerRegistry = class {
60731
61175
  return this.allCaplets().filter((server) => !server.disabled);
60732
61176
  }
60733
61177
  get(serverId) {
60734
- const server = this.config.mcpServers[serverId] ?? this.config.openapiEndpoints[serverId] ?? this.config.graphqlEndpoints[serverId];
61178
+ const server = this.config.mcpServers[serverId] ?? this.config.openapiEndpoints[serverId] ?? this.config.graphqlEndpoints[serverId] ?? this.config.httpApis[serverId];
60735
61179
  return server?.disabled ? void 0 : server;
60736
61180
  }
60737
61181
  require(serverId) {
@@ -60781,12 +61225,13 @@ var ServerRegistry = class {
60781
61225
  return [
60782
61226
  ...Object.values(this.config.mcpServers),
60783
61227
  ...Object.values(this.config.openapiEndpoints),
60784
- ...Object.values(this.config.graphqlEndpoints)
61228
+ ...Object.values(this.config.graphqlEndpoints),
61229
+ ...Object.values(this.config.httpApis)
60785
61230
  ];
60786
61231
  }
60787
61232
  };
60788
61233
  function capabilityDescription(server) {
60789
- const backendName = server.backend === "mcp" ? "MCP server" : server.backend === "openapi" ? "OpenAPI endpoint" : "GraphQL endpoint";
61234
+ const backendName = server.backend === "mcp" ? "MCP server" : server.backend === "openapi" ? "OpenAPI endpoint" : server.backend === "graphql" ? "GraphQL endpoint" : "HTTP API";
60790
61235
  const checkOperation = server.backend === "mcp" ? "check_mcp_server" : "check_backend";
60791
61236
  const hint = [
60792
61237
  `Use this Caplet to inspect and call tools from its ${backendName} backend.`,
@@ -60818,6 +61263,12 @@ function backendDetail(server) {
60818
61263
  source: graphQlSource(server),
60819
61264
  configuredOperations: Boolean(server.operations && Object.keys(server.operations).length > 0)
60820
61265
  };
61266
+ if (server.backend === "http") return {
61267
+ type: "http",
61268
+ disabled: server.disabled,
61269
+ requestTimeoutMs: server.requestTimeoutMs,
61270
+ configuredActions: Object.keys(server.actions).length
61271
+ };
60821
61272
  return {
60822
61273
  type: "mcp",
60823
61274
  transport: server.transport,
@@ -60854,16 +61305,16 @@ const generatedToolInputSchema = object$1({
60854
61305
  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
61306
  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
61307
  }).strict();
60857
- async function handleServerTool(server, request, registry, downstream, openapi, graphql) {
61308
+ async function handleServerTool(server, request, registry, downstream, openapi, graphql, http) {
60858
61309
  const parsed = validateOperationRequest(request, registry.config.options.maxSearchLimit);
60859
61310
  switch (parsed.operation) {
60860
61311
  case "get_caplet": return jsonResult(registry.detail(server));
60861
- case "check_backend": return jsonResult(await backendFor(server, downstream, openapi, graphql).check(server));
61312
+ case "check_backend": return jsonResult(await backendFor(server, downstream, openapi, graphql, http).check(server));
60862
61313
  case "check_mcp_server":
60863
61314
  if (server.backend !== "mcp") throw new CapletsError("REQUEST_INVALID", "check_mcp_server is only valid for MCP-backed Caplets; use check_backend");
60864
61315
  return jsonResult(await downstream.checkServer(server));
60865
61316
  case "list_tools": {
60866
- const backend = backendFor(server, downstream, openapi, graphql);
61317
+ const backend = backendFor(server, downstream, openapi, graphql, http);
60867
61318
  const tools = await backend.listTools(server);
60868
61319
  return jsonResult({
60869
61320
  server: server.server,
@@ -60871,7 +61322,7 @@ async function handleServerTool(server, request, registry, downstream, openapi,
60871
61322
  });
60872
61323
  }
60873
61324
  case "search_tools": {
60874
- const backend = backendFor(server, downstream, openapi, graphql);
61325
+ const backend = backendFor(server, downstream, openapi, graphql, http);
60875
61326
  const tools = await backend.listTools(server);
60876
61327
  const limit = parsed.limit ?? registry.config.options.defaultSearchLimit;
60877
61328
  return jsonResult({
@@ -60881,13 +61332,13 @@ async function handleServerTool(server, request, registry, downstream, openapi,
60881
61332
  });
60882
61333
  }
60883
61334
  case "get_tool": {
60884
- const tool = await backendFor(server, downstream, openapi, graphql).getTool(server, parsed.tool);
61335
+ const tool = await backendFor(server, downstream, openapi, graphql, http).getTool(server, parsed.tool);
60885
61336
  return jsonResult({
60886
61337
  server: server.server,
60887
61338
  tool
60888
61339
  });
60889
61340
  }
60890
- case "call_tool": return backendFor(server, downstream, openapi, graphql).callTool(server, parsed.tool, parsed.arguments);
61341
+ case "call_tool": return backendFor(server, downstream, openapi, graphql, http).callTool(server, parsed.tool, parsed.arguments);
60891
61342
  }
60892
61343
  }
60893
61344
  function validateOperationRequest(request, maxSearchLimit) {
@@ -60950,7 +61401,7 @@ function jsonResult(value) {
60950
61401
  function isPlainObject(value) {
60951
61402
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
60952
61403
  }
60953
- function backendFor(server, downstream, openapi, graphql) {
61404
+ function backendFor(server, downstream, openapi, graphql, http) {
60954
61405
  if (server.backend === "mcp") return {
60955
61406
  check: (...args) => downstream.checkServer(...args),
60956
61407
  listTools: (...args) => downstream.listTools(...args),
@@ -60970,6 +61421,17 @@ function backendFor(server, downstream, openapi, graphql) {
60970
61421
  search: (...args) => graphql.search(...args)
60971
61422
  };
60972
61423
  }
61424
+ if (server.backend === "http") {
61425
+ if (!http) throw new CapletsError("INTERNAL_ERROR", "HTTP action manager is not configured");
61426
+ return {
61427
+ check: (...args) => http.checkApi(...args),
61428
+ listTools: (...args) => http.listTools(...args),
61429
+ getTool: (...args) => http.getTool(...args),
61430
+ callTool: (...args) => http.callTool(...args),
61431
+ compact: (...args) => http.compact(...args),
61432
+ search: (...args) => http.search(...args)
61433
+ };
61434
+ }
60973
61435
  if (!openapi) throw new CapletsError("INTERNAL_ERROR", "OpenAPI manager is not configured");
60974
61436
  return {
60975
61437
  check: (...args) => openapi.checkEndpoint(...args),
@@ -60988,6 +61450,7 @@ var CapletsRuntime = class {
60988
61450
  downstream;
60989
61451
  openapi;
60990
61452
  graphql;
61453
+ http;
60991
61454
  tools = /* @__PURE__ */ new Map();
60992
61455
  paths;
60993
61456
  watchDebounceMs;
@@ -61008,6 +61471,7 @@ var CapletsRuntime = class {
61008
61471
  this.downstream = new DownstreamManager(this.registry, selectAuthOptions(options.authDir));
61009
61472
  this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir));
61010
61473
  this.graphql = new GraphQLManager(this.registry, selectAuthOptions(options.authDir));
61474
+ this.http = new HttpActionManager(this.registry, selectAuthOptions(options.authDir));
61011
61475
  this.server = options.server ?? new McpServer({
61012
61476
  name: "caplets",
61013
61477
  version
@@ -61083,6 +61547,7 @@ var CapletsRuntime = class {
61083
61547
  this.downstream.updateRegistry(nextRegistry);
61084
61548
  this.openapi.updateRegistry(nextRegistry);
61085
61549
  this.graphql.updateRegistry(nextRegistry);
61550
+ this.http.updateRegistry(nextRegistry);
61086
61551
  let invalidated = true;
61087
61552
  try {
61088
61553
  await this.invalidateChangedBackends(previousConfig, nextConfig);
@@ -61141,7 +61606,7 @@ var CapletsRuntime = class {
61141
61606
  }
61142
61607
  async handleTool(serverId, request) {
61143
61608
  try {
61144
- return await handleServerTool(this.registry.require(serverId), request, this.registry, this.downstream, this.openapi, this.graphql);
61609
+ return await handleServerTool(this.registry.require(serverId), request, this.registry, this.downstream, this.openapi, this.graphql, this.http);
61145
61610
  } catch (error) {
61146
61611
  return errorResult(error);
61147
61612
  }
@@ -61157,6 +61622,7 @@ var CapletsRuntime = class {
61157
61622
  if (before?.backend === "mcp") await this.downstream.closeServer(serverId);
61158
61623
  if (before?.backend === "openapi" || after?.backend === "openapi" || !after) this.openapi.invalidate(serverId);
61159
61624
  if (before?.backend === "graphql" || after?.backend === "graphql" || !after) this.graphql.invalidate(serverId);
61625
+ if (before?.backend === "http" || after?.backend === "http" || !after) this.http.invalidate(serverId);
61160
61626
  }
61161
61627
  }
61162
61628
  resetWatchers() {
@@ -61248,14 +61714,15 @@ function allCaplets(config) {
61248
61714
  return [
61249
61715
  ...Object.values(config.mcpServers),
61250
61716
  ...Object.values(config.openapiEndpoints),
61251
- ...Object.values(config.graphqlEndpoints)
61717
+ ...Object.values(config.graphqlEndpoints),
61718
+ ...Object.values(config.httpApis)
61252
61719
  ];
61253
61720
  }
61254
61721
  function nextEnabledServers(config) {
61255
61722
  return allCaplets(config).filter((server) => !server.disabled);
61256
61723
  }
61257
61724
  function capletById(config, serverId) {
61258
- return config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? config.graphqlEndpoints[serverId];
61725
+ return config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? config.graphqlEndpoints[serverId] ?? config.httpApis[serverId];
61259
61726
  }
61260
61727
  function serializeCaplet(caplet) {
61261
61728
  return JSON.stringify(caplet ?? null);