@krak-stack/registry 0.1.36 → 0.1.38

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.
@@ -3,7 +3,6 @@ import type { Json } from "effect/Schema";
3
3
  import { HttpApi, OpenApi } from "effect/http-api";
4
4
  export declare const JsonObjectSchema: Schema.$Record<Schema.String, Schema.Codec<Json, Json, never, never>>;
5
5
  export type JsonObject = typeof JsonObjectSchema.Type;
6
- type HttpBoundaryError = ErrorOptions["cause"];
7
6
  export type HttpApiDocument = ReturnType<typeof OpenApi.fromApi>;
8
7
  export type HttpApiMethod = "get" | "post" | "put" | "patch" | "delete";
9
8
  export declare const HttpApiMethods: ReadonlyArray<HttpApiMethod>;
@@ -40,58 +39,37 @@ export type HttpApiOperationInput = {
40
39
  readonly params: HttpApiOperationInputValues;
41
40
  readonly query: HttpApiOperationInputValues;
42
41
  };
42
+ export type HttpApiOperationDefinition = HttpApiOperationEntry & {
43
+ readonly name: string;
44
+ readonly inputSchema: Schema.Codec<Partial<HttpApiOperationInput>, Json>;
45
+ readonly inputJsonSchema: JsonSchema.JsonSchema;
46
+ readonly readOnly: boolean;
47
+ readonly destructive: boolean;
48
+ readonly idempotent: boolean;
49
+ };
43
50
  export type HttpApiSpecConfig = {
44
51
  readonly api: HttpApi.Constraint;
45
52
  readonly methods?: ReadonlyArray<HttpApiMethod>;
46
53
  readonly include?: (operation: HttpApiOperationEntry) => boolean;
47
54
  };
48
- export declare const toHttpError: (message: string, error: HttpBoundaryError) => Error;
55
+ export declare const toHttpError: (message: string, error: ErrorOptions["cause"]) => Error;
49
56
  export declare const sanitizeHttpName: (name: string) => string;
50
57
  export declare const httpApiToolName: (method: string, path: string, operation: HttpApiOperation) => string;
51
58
  export declare const httpApiOperations: ({ spec, methods, }: {
52
59
  readonly spec: HttpApiDocument;
53
60
  readonly methods?: ReadonlyArray<HttpApiMethod>;
54
61
  }) => ReadonlyArray<HttpApiOperationEntry>;
55
- export declare const httpApiToolEntries: (operations: readonly HttpApiOperationEntry[]) => Effect.Effect<{
56
- method: HttpApiMethod;
57
- path: string;
58
- operation: HttpApiOperation;
59
- name: string;
60
- }[], Error, never>;
61
- export declare const httpApiOperationInputSchema: (operation: HttpApiOperation) => JsonSchema.JsonSchema;
62
- export declare const decodeHttpApiOperationInput: (input: Json, operation: HttpApiOperation) => Effect.Effect<{
63
- body?: Json | undefined;
64
- headers: any;
65
- params: any;
66
- query: any;
67
- }, Error, never>;
68
62
  export declare const parseJsonObject: (value: string | undefined, label: string) => Effect.Effect<{
69
63
  readonly [x: string]: Json;
70
64
  }, Error, never>;
71
65
  export declare const parseJsonValue: (value: string | undefined, label: string) => Effect.Effect<Json | undefined, Error, never>;
72
66
  declare const HttpApiSpec_base: Context.ServiceClass<HttpApiSpec, "HttpApiSpec", {
73
67
  info: OpenApi.OpenAPISpecInfo;
74
- operations: HttpApiOperationEntry[];
75
- decodeOperationInput: (input: Json, operation: HttpApiOperation) => Effect.Effect<{
76
- body?: Json | undefined;
77
- headers: any;
78
- params: any;
79
- query: any;
80
- }, Error, never>;
81
- operationJsonSchema: (operation: HttpApiOperation) => JsonSchema.JsonSchema;
82
- operationSchema: (operation: HttpApiOperation) => Schema.Codec<unknown, unknown, never, never>;
68
+ operations: readonly HttpApiOperationDefinition[];
83
69
  }> & {
84
70
  readonly make: (config: HttpApiSpecConfig) => Effect.Effect<{
85
71
  info: OpenApi.OpenAPISpecInfo;
86
- operations: HttpApiOperationEntry[];
87
- decodeOperationInput: (input: Json, operation: HttpApiOperation) => Effect.Effect<{
88
- body?: Json | undefined;
89
- headers: any;
90
- params: any;
91
- query: any;
92
- }, Error, never>;
93
- operationJsonSchema: (operation: HttpApiOperation) => JsonSchema.JsonSchema;
94
- operationSchema: (operation: HttpApiOperation) => Schema.Codec<unknown, unknown, never, never>;
72
+ operations: readonly HttpApiOperationDefinition[];
95
73
  }, Error, never>;
96
74
  };
97
75
  export declare class HttpApiSpec extends HttpApiSpec_base {
@@ -1,13 +1,5 @@
1
1
  // ../../src/lib/httpapi-helpers.tsx
2
- import {
3
- Context,
4
- Effect,
5
- JsonSchema,
6
- Layer,
7
- Option,
8
- Schema,
9
- SchemaRepresentation
10
- } from "effect";
2
+ import { Context, Effect, Layer, Option, Schema } from "effect";
11
3
  import { HttpApi, OpenApi } from "effect/http-api";
12
4
  var JsonObjectSchema = Schema.Record(Schema.String, Schema.Json).annotate({
13
5
  identifier: "HttpJsonObject",
@@ -17,23 +9,6 @@ var JsonObjectSchema = Schema.Record(Schema.String, Schema.Json).annotate({
17
9
  });
18
10
  var JsonObjectFromString = Schema.fromJsonString(JsonObjectSchema);
19
11
  var JsonValueFromString = Schema.fromJsonString(Schema.Json);
20
- var JsonSchemaAnnotations = Schema.Struct({
21
- title: Schema.optional(Schema.String),
22
- description: Schema.optional(Schema.String),
23
- examples: Schema.optional(Schema.Array(Schema.Json))
24
- }).annotate({
25
- identifier: "HttpJsonSchemaAnnotations",
26
- title: "HTTP JSON Schema annotations",
27
- description: "JSON Schema annotations surfaced on generated tool inputs."
28
- });
29
- var HttpApiToolInputSchema = Schema.Struct({
30
- body: Schema.optional(Schema.Json)
31
- }).annotate({
32
- identifier: "HttpApiToolInput",
33
- title: "HTTP API tool input",
34
- description: "Input accepted by an HTTP API-backed tool.",
35
- examples: [{ body: { id: "example-id" } }]
36
- });
37
12
  var HttpApiMethods = [
38
13
  "get",
39
14
  "post",
@@ -57,130 +32,12 @@ var httpApiOperations = ({
57
32
  for (const [path, pathItem] of Object.entries(spec.paths)) {
58
33
  for (const method of methods) {
59
34
  const operation = decodeHttpApiOperation(pathItem[method]);
60
- if (Option.isSome(operation)) {
35
+ if (Option.isSome(operation))
61
36
  operations.push({ method, path, operation: operation.value });
62
- }
63
37
  }
64
38
  }
65
39
  return operations;
66
40
  };
67
- var httpApiToolEntries = Effect.fn("Http.toolEntries")(function* (operations) {
68
- return yield* Effect.try({
69
- try: () => {
70
- const names = new Set;
71
- return operations.map((entry) => {
72
- const name = httpApiToolName(entry.method, entry.path, entry.operation);
73
- if (!name || names.has(name)) {
74
- throw new Error(`Duplicate or empty HTTP API tool name: ${name}`);
75
- }
76
- names.add(name);
77
- return { ...entry, name };
78
- });
79
- },
80
- catch: (error) => error instanceof Error ? error : new Error(String(error))
81
- });
82
- });
83
- var decodeAnnotations = Schema.decodeUnknownOption(JsonSchemaAnnotations);
84
- var schemaWithVisibleAnnotations = (schema) => {
85
- const directAnnotations = decodeAnnotations(schema).pipe(Option.getOrElse(() => ({})));
86
- const allOf = Array.isArray(schema.allOf) ? schema.allOf : [];
87
- const annotations = allOf.reduce((acc, item) => ({
88
- ...acc,
89
- ...decodeAnnotations(item).pipe(Option.getOrElse(() => ({})))
90
- }), directAnnotations);
91
- const visible = { ...schema };
92
- if ("title" in annotations && !("title" in schema)) {
93
- visible.title = annotations.title;
94
- }
95
- if ("description" in annotations && !("description" in schema)) {
96
- visible.description = annotations.description;
97
- }
98
- if ("examples" in annotations && !("examples" in schema)) {
99
- visible.examples = annotations.examples;
100
- }
101
- return visible;
102
- };
103
- var referencedDefinitions = (schema, definitions) => {
104
- const names = new Set;
105
- const pending = [Schema.decodeUnknownSync(Schema.Json)(schema)];
106
- while (pending.length > 0) {
107
- const current = pending.pop();
108
- if (Array.isArray(current)) {
109
- pending.push(...current);
110
- continue;
111
- }
112
- const record = Schema.decodeUnknownOption(Schema.Record(Schema.String, Schema.Json))(current);
113
- if (Option.isNone(record))
114
- continue;
115
- for (const [key, value] of Object.entries(record.value)) {
116
- if (key === "$ref" && Schema.is(Schema.String)(value)) {
117
- const name = value.match(/^#\/(?:\$defs|components\/schemas)\/(.+)$/)?.[1];
118
- if (name && !names.has(name) && definitions[name]) {
119
- names.add(name);
120
- pending.push(Schema.decodeUnknownSync(Schema.Json)(definitions[name]));
121
- }
122
- } else if (key !== "$defs") {
123
- pending.push(value);
124
- }
125
- }
126
- }
127
- return Object.fromEntries([...names].map((name) => [name, definitions[name]]));
128
- };
129
- var operationParameters = (parameters, location) => parameters.filter((parameter) => parameter.in === location);
130
- var httpApiOperationInputSchema = (operation) => {
131
- const parameters = operation.parameters ?? [];
132
- const pathParameters = operationParameters(parameters, "path");
133
- const queryParameters = operationParameters(parameters, "query");
134
- const headerParameters = operationParameters(parameters, "header");
135
- const properties = {};
136
- const required = [];
137
- const body = operation.requestBody?.content?.["application/json"]?.schema;
138
- for (const parameter of [
139
- ...pathParameters,
140
- ...queryParameters,
141
- ...headerParameters
142
- ]) {
143
- const property = {
144
- ...parameter.schema ? schemaWithVisibleAnnotations(parameter.schema) : { type: "string" }
145
- };
146
- if (parameter.description)
147
- property.description = parameter.description;
148
- properties[parameter.name] = property;
149
- if (parameter.required)
150
- required.push(parameter.name);
151
- }
152
- if (body) {
153
- properties.body = body;
154
- if (operation.requestBody?.required)
155
- required.push("body");
156
- }
157
- return {
158
- type: "object",
159
- properties,
160
- required,
161
- additionalProperties: false
162
- };
163
- };
164
- var decodeJsonObject = Schema.decodeUnknownOption(JsonObjectSchema);
165
- var decodeHttpApiOperationInput = Effect.fn("Http.decodeApiOperationInput")(function* (input, operation) {
166
- const payload = yield* Schema.decodeUnknownEffect(JsonObjectSchema)(input ?? {}).pipe(Effect.mapError(() => new Error("Tool input must be a JSON object")));
167
- const decoded = yield* Schema.decodeUnknownEffect(HttpApiToolInputSchema)(payload).pipe(Effect.mapError(() => new Error("Tool input must be a JSON object")));
168
- const parameters = operation.parameters ?? [];
169
- const pickParameters = (location) => {
170
- const nestedKey = location === "path" ? "params" : location;
171
- const nested = decodeJsonObject(payload[nestedKey]).pipe(Option.getOrElse(() => ({})));
172
- return Object.fromEntries(operationParameters(parameters, location).map((parameter) => [
173
- parameter.name,
174
- nested[parameter.name] ?? payload[parameter.name]
175
- ]).filter(([, value]) => value !== undefined));
176
- };
177
- return {
178
- ...decoded,
179
- headers: pickParameters("header"),
180
- params: pickParameters("path"),
181
- query: pickParameters("query")
182
- };
183
- });
184
41
  var reflectedSchema = (schema) => Schema.make(schema.ast);
185
42
  var reflectedOperationInputSchemas = (api) => {
186
43
  const schemas = new Map;
@@ -192,23 +49,18 @@ var reflectedOperationInputSchemas = (api) => {
192
49
  const operationId = Context.getOrElse(endpoint.annotations, OpenApi.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
193
50
  const fields = {};
194
51
  if (endpoint.params)
195
- fields.params = reflectedSchema(endpoint.params);
196
- if (endpoint.query) {
197
- fields.query = Schema.optionalKey(reflectedSchema(endpoint.query));
198
- }
199
- if (endpoint.headers) {
200
- fields.headers = Schema.optionalKey(reflectedSchema(endpoint.headers));
201
- }
202
- const payloadSchemas = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema));
203
- if (payloadSchemas.length === 1 && payloadSchemas[0]) {
204
- fields.body = payloadSchemas[0];
205
- } else if (payloadSchemas.length > 1) {
206
- fields.body = Schema.Union(payloadSchemas);
207
- }
208
- const inputSchema = Object.keys(fields).length === 0 ? Schema.Record(Schema.String, Schema.Never) : Schema.Struct(fields);
209
- schemas.set(operationId, inputSchema.annotate({
210
- identifier: `${sanitizeHttpName(operationId)}ToolInput`
211
- }));
52
+ fields.params = Schema.toType(reflectedSchema(endpoint.params));
53
+ if (endpoint.query)
54
+ fields.query = Schema.optionalKey(Schema.toType(reflectedSchema(endpoint.query)));
55
+ if (endpoint.headers)
56
+ fields.headers = Schema.optionalKey(Schema.toType(reflectedSchema(endpoint.headers)));
57
+ const payloads = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema));
58
+ if (payloads.length === 1 && payloads[0])
59
+ fields.body = payloads[0];
60
+ else if (payloads.length > 1)
61
+ fields.body = Schema.Union(payloads);
62
+ const input = Object.keys(fields).length === 0 ? Schema.Record(Schema.String, Schema.Never) : Schema.Struct(fields);
63
+ schemas.set(operationId, Schema.make(Schema.toCodecJson(input).ast).annotate({ identifier: `${sanitizeHttpName(operationId)}ToolInput` }));
212
64
  }
213
65
  });
214
66
  return schemas;
@@ -228,42 +80,37 @@ class HttpApiSpec extends Context.Service()("HttpApiSpec", {
228
80
  make: (config) => Effect.try({
229
81
  try: () => {
230
82
  const api = config.api;
231
- if (!HttpApi.isHttpApi(api)) {
83
+ if (!HttpApi.isHttpApi(api))
232
84
  throw new Error("HttpApiSpec requires a valid HttpApi");
233
- }
234
85
  const spec = OpenApi.fromApi(api);
235
- const reflectedSchemas = reflectedOperationInputSchemas(api);
86
+ const schemas = reflectedOperationInputSchemas(api);
87
+ const names = new Set;
236
88
  const operations = httpApiOperations({
237
89
  spec,
238
90
  methods: config.methods
239
- }).filter((operation) => config.include?.(operation) ?? true);
240
- const operationJsonSchema = (operation) => {
241
- const schema = httpApiOperationInputSchema(operation);
242
- const document = JsonSchema.fromSchemaOpenApi3_1({
243
- ...schema,
244
- $defs: spec.components?.schemas
245
- });
246
- const definitions = document.definitions ?? {};
247
- const usedDefinitions = referencedDefinitions(document.schema, definitions);
248
- const result = { ...document.schema };
249
- if (Object.keys(usedDefinitions).length > 0) {
250
- result.$defs = usedDefinitions;
251
- }
252
- return result;
253
- };
254
- return {
255
- info: spec.info,
256
- operations,
257
- decodeOperationInput: decodeHttpApiOperationInput,
258
- operationJsonSchema,
259
- operationSchema: (operation) => {
260
- const reflected = operation.operationId ? reflectedSchemas.get(operation.operationId) : undefined;
261
- if (reflected)
262
- return reflected;
263
- const document = JsonSchema.fromSchemaOpenApi3_1(operationJsonSchema(operation));
264
- return reflectedSchema(SchemaRepresentation.fromJsonSchemaDocument(document));
265
- }
266
- };
91
+ }).filter((entry) => config.include?.(entry) ?? true).map((entry) => {
92
+ const name = httpApiToolName(entry.method, entry.path, entry.operation);
93
+ if (!name || names.has(name))
94
+ throw new Error(`Duplicate or empty HTTP API tool name: ${name}`);
95
+ names.add(name);
96
+ const inputSchema = entry.operation.operationId ? schemas.get(entry.operation.operationId) : undefined;
97
+ if (!inputSchema)
98
+ throw new Error(`Missing endpoint schema for ${entry.operation.operationId}`);
99
+ const document = Schema.toJsonSchemaDocument(inputSchema.annotate({ identifier: undefined }));
100
+ return {
101
+ ...entry,
102
+ name,
103
+ inputSchema,
104
+ inputJsonSchema: {
105
+ ...document.schema,
106
+ $defs: document.definitions
107
+ },
108
+ readOnly: entry.method === "get",
109
+ destructive: entry.method === "delete",
110
+ idempotent: entry.method === "get" || entry.method === "put" || entry.method === "delete"
111
+ };
112
+ });
113
+ return { info: spec.info, operations };
267
114
  },
268
115
  catch: (error) => toHttpError("Failed to build HTTP API spec", error)
269
116
  })
@@ -274,10 +121,7 @@ export {
274
121
  HttpApiMethods,
275
122
  HttpApiSpec,
276
123
  JsonObjectSchema,
277
- decodeHttpApiOperationInput,
278
- httpApiOperationInputSchema,
279
124
  httpApiOperations,
280
- httpApiToolEntries,
281
125
  httpApiToolName,
282
126
  parseJsonObject,
283
127
  parseJsonValue,
@@ -1,20 +1,25 @@
1
1
  import { Context, Effect, Layer } from "effect";
2
- import { McpSchema, McpServer } from "effect/ai";
3
- import { ApiClient } from "./httpapi-client.js";
4
- import { HttpApiSpec } from "./httpapi-helpers.js";
2
+ import { McpServer } from "effect/ai";
3
+ import type { HttpApiGroup } from "effect/http-api";
4
+ import { ApiClient, type ApiClientConfig } from "./httpapi-client.js";
5
+ import { HttpApiSpec, type HttpApiSpecConfig } from "./httpapi-helpers.js";
5
6
  export type HttpApiMcpConfig = {
6
7
  readonly toolMetaKey?: string;
7
8
  };
9
+ export type HttpApiMcpHttpConfig<Id extends string, Groups extends HttpApiGroup.Constraint> = ApiClientConfig<Id, Groups> & Pick<HttpApiSpecConfig, "methods" | "include"> & HttpApiMcpConfig & {
10
+ readonly path?: Parameters<typeof McpServer.layerHttp>[0]["path"];
11
+ };
8
12
  declare const HttpApiMcp_base: Context.ServiceClass<HttpApiMcp, "HttpApiMcp", {
9
- registerTools: Effect.Effect<void, Error, McpServer.McpServer>;
13
+ registerTools: Effect.Effect<void, never, ApiClient | McpServer.McpServer>;
10
14
  }> & {
11
15
  readonly make: (config: HttpApiMcpConfig) => Effect.Effect<{
12
- registerTools: Effect.Effect<void, Error, McpServer.McpServer>;
13
- }, never, ApiClient | HttpApiSpec>;
16
+ registerTools: Effect.Effect<void, never, ApiClient | McpServer.McpServer>;
17
+ }, never, HttpApiSpec>;
14
18
  };
15
19
  export declare class HttpApiMcp extends HttpApiMcp_base {
16
- static readonly layer: (config: HttpApiMcpConfig) => Layer.Layer<HttpApiMcp, never, ApiClient | HttpApiSpec>;
20
+ static readonly layer: (config: HttpApiMcpConfig) => Layer.Layer<HttpApiMcp, never, HttpApiSpec>;
21
+ static readonly layerHttp: <Id extends string, Groups extends HttpApiGroup.Constraint>(config: HttpApiMcpHttpConfig<Id, Groups>) => Layer.Layer<McpServer.McpServer | import("effect/ai/McpSchema").McpServerClient, Error | import("effect/Cause").IllegalArgumentError, import("effect/http/HttpClient").HttpClient | import("effect/http/HttpRouter").HttpRouter | Exclude<import("effect/http-api/HttpApiMiddleware").MiddlewareClient<import("effect/http-api/HttpApiEndpoint").Middleware<HttpApiGroup.Endpoints<Groups>>>, import("effect/Scope").Scope>>;
17
22
  }
18
- export declare const httpApiMcpToolsLayer: Layer.Layer<never, Error, HttpApiMcp | McpServer.McpServer>;
19
- export declare const httpApiMcpServerLayer: (path: Parameters<typeof McpServer.layerHttp>[0]["path"]) => Layer.Layer<McpServer.McpServer | McpSchema.McpServerClient, import("effect/Cause").IllegalArgumentError, HttpApiSpec | import("effect/http/HttpRouter").HttpRouter>;
23
+ export declare const httpApiMcpToolsLayer: Layer.Layer<never, never, ApiClient | HttpApiMcp | McpServer.McpServer>;
24
+ export declare const httpApiMcpServerLayer: (path: Parameters<typeof McpServer.layerHttp>[0]["path"]) => Layer.Layer<McpServer.McpServer | import("effect/ai/McpSchema").McpServerClient, import("effect/Cause").IllegalArgumentError, HttpApiSpec | import("effect/http/HttpRouter").HttpRouter>;
20
25
  export {};