@browserlessapi/inferlo 0.0.0-stage → 0.3.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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -2
  3. package/dist/ai/index.d.ts +96 -0
  4. package/dist/ai/index.js +327 -0
  5. package/dist/ai/index.js.map +1 -0
  6. package/dist/ai/providers.d.ts +97 -0
  7. package/dist/ai/providers.js +204 -0
  8. package/dist/ai/providers.js.map +1 -0
  9. package/dist/app.d.ts +94 -0
  10. package/dist/app.js +365 -0
  11. package/dist/app.js.map +1 -0
  12. package/dist/auth.d.ts +29 -0
  13. package/dist/auth.js +61 -0
  14. package/dist/auth.js.map +1 -0
  15. package/dist/cli/main.d.ts +2 -0
  16. package/dist/cli/main.js +467 -0
  17. package/dist/cli/main.js.map +1 -0
  18. package/dist/db/memory.d.ts +2 -0
  19. package/dist/db/memory.js +128 -0
  20. package/dist/db/memory.js.map +1 -0
  21. package/dist/db/migrate.d.ts +84 -0
  22. package/dist/db/migrate.js +355 -0
  23. package/dist/db/migrate.js.map +1 -0
  24. package/dist/db/postgres.d.ts +37 -0
  25. package/dist/db/postgres.js +338 -0
  26. package/dist/db/postgres.js.map +1 -0
  27. package/dist/db/table.d.ts +72 -0
  28. package/dist/db/table.js +177 -0
  29. package/dist/db/table.js.map +1 -0
  30. package/dist/db/types.d.ts +36 -0
  31. package/dist/db/types.js +2 -0
  32. package/dist/db/types.js.map +1 -0
  33. package/dist/describe.d.ts +56 -0
  34. package/dist/describe.js +64 -0
  35. package/dist/describe.js.map +1 -0
  36. package/dist/env.d.ts +19 -0
  37. package/dist/env.js +72 -0
  38. package/dist/env.js.map +1 -0
  39. package/dist/errors.d.ts +40 -0
  40. package/dist/errors.js +99 -0
  41. package/dist/errors.js.map +1 -0
  42. package/dist/index.d.ts +26 -0
  43. package/dist/index.js +18 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/mcp.d.ts +18 -0
  46. package/dist/mcp.js +166 -0
  47. package/dist/mcp.js.map +1 -0
  48. package/dist/model.d.ts +36 -0
  49. package/dist/model.js +76 -0
  50. package/dist/model.js.map +1 -0
  51. package/dist/openapi.d.ts +6 -0
  52. package/dist/openapi.js +71 -0
  53. package/dist/openapi.js.map +1 -0
  54. package/dist/route.d.ts +125 -0
  55. package/dist/route.js +28 -0
  56. package/dist/route.js.map +1 -0
  57. package/dist/schema.d.ts +156 -0
  58. package/dist/schema.js +430 -0
  59. package/dist/schema.js.map +1 -0
  60. package/dist/serve.d.ts +11 -0
  61. package/dist/serve.js +83 -0
  62. package/dist/serve.js.map +1 -0
  63. package/dist/testing.d.ts +16 -0
  64. package/dist/testing.js +30 -0
  65. package/dist/testing.js.map +1 -0
  66. package/llms.txt +27 -0
  67. package/package.json +82 -4
  68. package/templates/app/AGENTS.md +53 -0
  69. package/templates/app/CLAUDE.md +1 -0
  70. package/templates/app/README.md +14 -0
  71. package/templates/app/_env.example +5 -0
  72. package/templates/app/_gitignore +3 -0
  73. package/templates/app/package.json.tpl +31 -0
  74. package/templates/app/src/app.ts +38 -0
  75. package/templates/app/src/models.ts +31 -0
  76. package/templates/app/src/routes/health.ts +3 -0
  77. package/templates/app/src/routes/notes.ts +56 -0
  78. package/templates/app/src/server.ts +16 -0
  79. package/templates/app/test/notes.test.ts +50 -0
  80. package/templates/app/tsconfig.json +17 -0
@@ -0,0 +1,36 @@
1
+ import type { AnyModel } from "../model.ts";
2
+ export type Op = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "notIn" | "like" | "ilike" | "isNull";
3
+ export interface Condition {
4
+ field: string;
5
+ op: Op;
6
+ value: unknown;
7
+ }
8
+ export interface Query {
9
+ where: Condition[];
10
+ orderBy: [field: string, direction: "asc" | "desc"][];
11
+ limit?: number;
12
+ offset?: number;
13
+ }
14
+ export type DbRecord = Record<string, unknown>;
15
+ /**
16
+ * Storage backend. Inferlo ships memory() and postgres(). Adapters receive validated,
17
+ * already-normalised data with camelCase field names.
18
+ */
19
+ export interface Adapter {
20
+ readonly dialect: string;
21
+ connect?(models: AnyModel[]): Promise<void> | void;
22
+ insert(model: AnyModel, row: DbRecord): Promise<DbRecord>;
23
+ select(model: AnyModel, query: Query): Promise<DbRecord[]>;
24
+ update(model: AnyModel, where: Condition[], patch: DbRecord): Promise<DbRecord[]>;
25
+ delete(model: AnyModel, where: Condition[]): Promise<number>;
26
+ count(model: AnyModel, where: Condition[]): Promise<number>;
27
+ /** Create missing tables, columns and indexes. Returns the statements it ran (or would run). */
28
+ sync(models: AnyModel[], options?: {
29
+ dryRun?: boolean;
30
+ }): Promise<string[]>;
31
+ /** Run fn inside a transaction, passing an adapter bound to it. */
32
+ transaction?<T>(fn: (tx: Adapter) => Promise<T>): Promise<T>;
33
+ /** Raw parameterised SQL (Postgres only). */
34
+ sql?(text: string, params?: unknown[]): Promise<DbRecord[]>;
35
+ close?(): Promise<void>;
36
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/db/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * describe() returns a machine-readable map of the whole app. AI coding agents read it
3
+ * (inferlo describe --json) instead of searching the codebase.
4
+ */
5
+ import type { App } from "./app.ts";
6
+ import type { JSONSchema } from "./schema.ts";
7
+ export interface FieldDescription {
8
+ type: string;
9
+ required: boolean;
10
+ nullable: boolean;
11
+ default?: unknown;
12
+ unique?: boolean;
13
+ index?: boolean;
14
+ references?: string;
15
+ values?: readonly unknown[];
16
+ column: string;
17
+ description?: string;
18
+ }
19
+ export interface AppDescription {
20
+ inferlo: string;
21
+ name: string;
22
+ version: string;
23
+ description?: string;
24
+ database: string;
25
+ auth: boolean;
26
+ endpoints: {
27
+ openapi: string | false;
28
+ mcp: string | false;
29
+ };
30
+ models: {
31
+ name: string;
32
+ fields: Record<string, FieldDescription>;
33
+ }[];
34
+ routes: {
35
+ name?: string;
36
+ method: string;
37
+ path: string;
38
+ auth: string;
39
+ summary?: string;
40
+ tool?: string;
41
+ params?: JSONSchema;
42
+ query?: JSONSchema;
43
+ body?: JSONSchema;
44
+ }[];
45
+ ai: {
46
+ configured: boolean;
47
+ aliases: Record<string, string[]>;
48
+ };
49
+ }
50
+ export declare const INFERLO_VERSION = "0.3.0";
51
+ export declare function describeApp(app: App<any>, options: {
52
+ auth: boolean;
53
+ openapi: string | false;
54
+ mcp: string | false;
55
+ description?: string;
56
+ }): AppDescription;
@@ -0,0 +1,64 @@
1
+ import { toolName } from "./mcp.js";
2
+ import { columnName } from "./model.js";
3
+ export const INFERLO_VERSION = "0.3.0";
4
+ function field(name, schema) {
5
+ const d = schema.def;
6
+ const out = {
7
+ type: d.kind,
8
+ required: !d.optional && !d.hasDefault,
9
+ nullable: d.nullable,
10
+ column: columnName(name),
11
+ };
12
+ if (d.hasDefault)
13
+ out.default = typeof d.defaultValue === "function" ? "(function)" : d.defaultValue;
14
+ if (d.unique)
15
+ out.unique = true;
16
+ if (d.index)
17
+ out.index = true;
18
+ if (d.refTable)
19
+ out.references = d.refTable;
20
+ if (d.values && d.kind === "enum")
21
+ out.values = d.values;
22
+ if (d.description)
23
+ out.description = d.description;
24
+ return out;
25
+ }
26
+ export function describeApp(app, options) {
27
+ return {
28
+ inferlo: INFERLO_VERSION,
29
+ name: app.name,
30
+ version: app.version,
31
+ description: options.description,
32
+ database: app.db.$adapter.dialect,
33
+ auth: options.auth,
34
+ endpoints: { openapi: options.openapi, mcp: options.mcp },
35
+ models: app.models.map((m) => ({
36
+ name: m.name,
37
+ fields: Object.fromEntries([
38
+ ["id", { type: "uuid", required: false, nullable: false, column: "id", description: "Set automatically" }],
39
+ ...Object.entries(m.fields).map(([k, s]) => [k, field(k, s)]),
40
+ ["createdAt", { type: "date", required: false, nullable: false, column: "created_at", description: "Set automatically" }],
41
+ ["updatedAt", { type: "date", required: false, nullable: false, column: "updated_at", description: "Set automatically" }],
42
+ ]),
43
+ })),
44
+ routes: app.routes.map((r) => {
45
+ const o = r.options;
46
+ const out = { method: r.method, path: r.path, auth: o.auth ?? "user" };
47
+ if (r.name)
48
+ out.name = r.name;
49
+ if (o.summary)
50
+ out.summary = o.summary;
51
+ if (o.tool)
52
+ out.tool = toolName(r);
53
+ if (o.params)
54
+ out.params = o.params.toJSONSchema();
55
+ if (o.query)
56
+ out.query = o.query.toJSONSchema();
57
+ if (o.body)
58
+ out.body = o.body.toJSONSchema();
59
+ return out;
60
+ }),
61
+ ai: { configured: app.ai.configured, aliases: app.ai.aliases },
62
+ };
63
+ }
64
+ //# sourceMappingURL=describe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe.js","sourceRoot":"","sources":["../src/describe.ts"],"names":[],"mappings":"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"}
package/dist/env.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ /** Use a fixed set of variables (tests, Cloudflare Workers bindings). */
2
+ export declare function setEnv(values: Record<string, string | undefined> | undefined): void;
3
+ interface EnvFn {
4
+ (name: string): string;
5
+ (name: string, options: {
6
+ default: string;
7
+ }): string;
8
+ (name: string, options: {
9
+ optional: true;
10
+ }): string | undefined;
11
+ int(name: string, options?: {
12
+ default?: number;
13
+ }): number;
14
+ bool(name: string, options?: {
15
+ default?: boolean;
16
+ }): boolean;
17
+ }
18
+ export declare const env: EnvFn;
19
+ export {};
package/dist/env.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Read environment variables on Node, Bun, Deno and Workers, with clear errors.
3
+ *
4
+ * env("DATABASE_URL") // required string
5
+ * env("PORT", { default: "3000" }) // optional with default
6
+ * env.int("PORT", { default: 3000 })
7
+ * env.bool("DEBUG", { default: false })
8
+ */
9
+ import { InferloError } from "./errors.js";
10
+ let overrides;
11
+ /** Use a fixed set of variables (tests, Cloudflare Workers bindings). */
12
+ export function setEnv(values) {
13
+ overrides = values;
14
+ }
15
+ function read(name) {
16
+ if (overrides && name in overrides)
17
+ return overrides[name];
18
+ const g = globalThis;
19
+ if (g.process?.env)
20
+ return g.process.env[name];
21
+ if (g.Deno) {
22
+ try {
23
+ return g.Deno.env.get(name);
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ return undefined;
30
+ }
31
+ function missing(name) {
32
+ return new InferloError("IL7001", `Environment variable ${name} is not set.`, {
33
+ fix: `Set ${name} in your environment or .env file, or pass a default: env("${name}", { default: "..." }).`,
34
+ });
35
+ }
36
+ export const env = Object.assign((name, options = {}) => {
37
+ const value = read(name);
38
+ if (value !== undefined && value !== "")
39
+ return value;
40
+ if (options.default !== undefined)
41
+ return options.default;
42
+ if (options.optional)
43
+ return undefined;
44
+ throw missing(name);
45
+ }, {
46
+ int(name, options = {}) {
47
+ const value = read(name);
48
+ if (value === undefined || value === "") {
49
+ if (options.default !== undefined)
50
+ return options.default;
51
+ throw missing(name);
52
+ }
53
+ const n = Number(value);
54
+ if (!Number.isInteger(n))
55
+ throw new InferloError("IL7002", `Environment variable ${name} must be a whole number, got "${value}".`);
56
+ return n;
57
+ },
58
+ bool(name, options = {}) {
59
+ const value = read(name)?.toLowerCase();
60
+ if (value === undefined || value === "") {
61
+ if (options.default !== undefined)
62
+ return options.default;
63
+ throw missing(name);
64
+ }
65
+ if (["1", "true", "yes", "on"].includes(value))
66
+ return true;
67
+ if (["0", "false", "no", "off"].includes(value))
68
+ return false;
69
+ throw new InferloError("IL7002", `Environment variable ${name} must be true or false, got "${value}".`);
70
+ },
71
+ });
72
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Every error Inferlo raises has a stable code, a plain cause, a suggested fix and a docs link.
3
+ * Error codes never change meaning once released, so AI models and humans can rely on them.
4
+ */
5
+ export declare const DOCS_BASE = "https://github.com/BrowserlessAPI/Inferlo/blob/main/docs/errors.md";
6
+ export declare function docsUrl(code: string): string;
7
+ export interface InferloErrorOptions {
8
+ status?: number;
9
+ fix?: string;
10
+ details?: unknown;
11
+ cause?: unknown;
12
+ /** Set to false to hide the message from API clients (it is still logged). */
13
+ expose?: boolean;
14
+ }
15
+ export declare class InferloError extends Error {
16
+ readonly code: string;
17
+ readonly status: number;
18
+ readonly fix: string | undefined;
19
+ readonly details: unknown;
20
+ readonly expose: boolean;
21
+ constructor(code: string, message: string, options?: InferloErrorOptions);
22
+ get docs(): string;
23
+ toJSON(): {
24
+ error: Record<string, unknown>;
25
+ };
26
+ /** Multi-line text for terminals and logs. */
27
+ format(): string;
28
+ }
29
+ /** Throw from a handler to return an HTTP error with your own code and message. */
30
+ export declare function httpError(status: number, message: string, options?: {
31
+ code?: string;
32
+ fix?: string;
33
+ details?: unknown;
34
+ }): InferloError;
35
+ export declare function notFound(message?: string, details?: unknown): InferloError;
36
+ export declare function forbidden(message?: string): InferloError;
37
+ export declare function badRequest(message: string, details?: unknown): InferloError;
38
+ /** Closest candidate by edit distance, used for "did you mean" hints. */
39
+ export declare function suggest(input: string, candidates: Iterable<string>): string | undefined;
40
+ export declare function didYouMean(input: string, candidates: Iterable<string>): string;
package/dist/errors.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Every error Inferlo raises has a stable code, a plain cause, a suggested fix and a docs link.
3
+ * Error codes never change meaning once released, so AI models and humans can rely on them.
4
+ */
5
+ export const DOCS_BASE = "https://github.com/BrowserlessAPI/Inferlo/blob/main/docs/errors.md";
6
+ export function docsUrl(code) {
7
+ return `${DOCS_BASE}#${code.toLowerCase()}`;
8
+ }
9
+ export class InferloError extends Error {
10
+ code;
11
+ status;
12
+ fix;
13
+ details;
14
+ expose;
15
+ constructor(code, message, options = {}) {
16
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
17
+ this.name = `InferloError[${code}]`;
18
+ this.code = code;
19
+ this.status = options.status ?? 500;
20
+ this.fix = options.fix;
21
+ this.details = options.details;
22
+ this.expose = options.expose ?? this.status < 500;
23
+ }
24
+ get docs() {
25
+ return docsUrl(this.code);
26
+ }
27
+ toJSON() {
28
+ const error = { code: this.code, message: this.expose ? this.message : "Internal server error" };
29
+ if (this.expose && this.fix)
30
+ error.fix = this.fix;
31
+ if (this.expose && this.details !== undefined)
32
+ error.details = this.details;
33
+ error.docs = this.docs;
34
+ return { error };
35
+ }
36
+ /** Multi-line text for terminals and logs. */
37
+ format() {
38
+ const lines = [`${this.code} ${this.message}`];
39
+ if (this.fix)
40
+ lines.push(`Fix: ${this.fix}`);
41
+ lines.push(`Docs: ${this.docs}`);
42
+ return lines.join("\n");
43
+ }
44
+ }
45
+ /** Throw from a handler to return an HTTP error with your own code and message. */
46
+ export function httpError(status, message, options = {}) {
47
+ return new InferloError(options.code ?? `HTTP${status}`, message, { status, fix: options.fix, details: options.details, expose: true });
48
+ }
49
+ export function notFound(message = "Not found", details) {
50
+ return new InferloError("IL4001", message, { status: 404, details });
51
+ }
52
+ export function forbidden(message = "You do not have permission to do this.") {
53
+ return new InferloError("IL3002", message, { status: 403 });
54
+ }
55
+ export function badRequest(message, details) {
56
+ return new InferloError("HTTP400", message, { status: 400, details });
57
+ }
58
+ /** Closest candidate by edit distance, used for "did you mean" hints. */
59
+ export function suggest(input, candidates) {
60
+ let best;
61
+ let bestScore = Infinity;
62
+ const lower = input.toLowerCase();
63
+ for (const candidate of candidates) {
64
+ const c = candidate.toLowerCase();
65
+ let score = distance(lower, c);
66
+ if (c.startsWith(lower) || lower.startsWith(c))
67
+ score = Math.min(score, 1);
68
+ if (score < bestScore) {
69
+ bestScore = score;
70
+ best = candidate;
71
+ }
72
+ }
73
+ if (best === undefined)
74
+ return undefined;
75
+ const limit = Math.max(2, Math.floor(input.length / 3));
76
+ return bestScore <= limit ? best : undefined;
77
+ }
78
+ function distance(a, b) {
79
+ if (a === b)
80
+ return 0;
81
+ const prev = new Array(b.length + 1);
82
+ for (let j = 0; j <= b.length; j++)
83
+ prev[j] = j;
84
+ for (let i = 1; i <= a.length; i++) {
85
+ let diag = prev[0];
86
+ prev[0] = i;
87
+ for (let j = 1; j <= b.length; j++) {
88
+ const above = prev[j];
89
+ prev[j] = Math.min(above + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
90
+ diag = above;
91
+ }
92
+ }
93
+ return prev[b.length];
94
+ }
95
+ export function didYouMean(input, candidates) {
96
+ const match = suggest(input, candidates);
97
+ return match ? ` Did you mean "${match}"?` : "";
98
+ }
99
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Inferlo: the TypeScript backend framework with built-in AI model support.
3
+ * https://github.com/BrowserlessAPI/Inferlo
4
+ */
5
+ export { createApp } from "./app.ts";
6
+ export type { App, AppConfig, AuthConfig, CorsConfig, Middleware, MiddlewareContext, RequestOptions, RouteSource, Logger } from "./app.ts";
7
+ export { route, isRoute } from "./route.ts";
8
+ export type { Register, Context, RouteDef, RouteOptions, Auth, Method, ToolOptions, AppUser, AppDb } from "./route.ts";
9
+ export { t, Schema, ObjectSchema, ArraySchema, ValidationError, toJSONSchema } from "./schema.ts";
10
+ export type { Infer, InferInput, Issue, JSONSchema, ParseResult, Shape, AnySchema } from "./schema.ts";
11
+ export { model, Model, columnName } from "./model.ts";
12
+ export type { Row, CreateInput, UpdateInput, AnyModel } from "./model.ts";
13
+ export { memory } from "./db/memory.ts";
14
+ export { createDb, Table } from "./db/table.ts";
15
+ export type { Db, Where, OrderBy, FindManyOptions } from "./db/table.ts";
16
+ export type { Adapter, Condition, Query, DbRecord } from "./db/types.ts";
17
+ export { planMigration, applyMigrations } from "./db/migrate.ts";
18
+ export type { ApplyMigrationsOptions, Migration, MigrationPlan, MigrationSnapshot, MigrationSnapshotColumn, MigrationSnapshotV1, PlanMigrationOptions, } from "./db/migrate.ts";
19
+ export { createAI, cosineSimilarity, extractJson, openai, openaiCompatible, anthropic, gemini, ollama, mockProvider, ProviderError } from "./ai/index.ts";
20
+ export type { AI, AIConfig, GenerateOptions, GenerateResult, StreamResult, Usage, Pricing, Provider, ProviderRequest, Message } from "./ai/index.ts";
21
+ export { bearer, hashToken, createToken, hashPassword, verifyPassword, safeEqual } from "./auth.ts";
22
+ export { env, setEnv } from "./env.ts";
23
+ export { InferloError, httpError, notFound, forbidden, badRequest, docsUrl } from "./errors.ts";
24
+ export { serve } from "./serve.ts";
25
+ export { INFERLO_VERSION } from "./describe.ts";
26
+ export type { AppDescription } from "./describe.ts";
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Inferlo: the TypeScript backend framework with built-in AI model support.
3
+ * https://github.com/BrowserlessAPI/Inferlo
4
+ */
5
+ export { createApp } from "./app.js";
6
+ export { route, isRoute } from "./route.js";
7
+ export { t, Schema, ObjectSchema, ArraySchema, ValidationError, toJSONSchema } from "./schema.js";
8
+ export { model, Model, columnName } from "./model.js";
9
+ export { memory } from "./db/memory.js";
10
+ export { createDb, Table } from "./db/table.js";
11
+ export { planMigration, applyMigrations } from "./db/migrate.js";
12
+ export { createAI, cosineSimilarity, extractJson, openai, openaiCompatible, anthropic, gemini, ollama, mockProvider, ProviderError } from "./ai/index.js";
13
+ export { bearer, hashToken, createToken, hashPassword, verifyPassword, safeEqual } from "./auth.js";
14
+ export { env, setEnv } from "./env.js";
15
+ export { InferloError, httpError, notFound, forbidden, badRequest, docsUrl } from "./errors.js";
16
+ export { serve } from "./serve.js";
17
+ export { INFERLO_VERSION } from "./describe.js";
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGrC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGlG,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGtD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAWjE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG1J,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACpG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Built-in MCP server (Model Context Protocol, Streamable HTTP transport, JSON responses).
3
+ * Routes with `tool: true` become tools. Tool calls run through the normal router, so auth,
4
+ * roles and validation apply exactly as they do for HTTP clients: the caller's Authorization
5
+ * header is forwarded.
6
+ */
7
+ import type { App } from "./app.ts";
8
+ import type { RouteDef } from "./route.ts";
9
+ import type { JSONSchema } from "./schema.ts";
10
+ export interface ToolDefinition {
11
+ name: string;
12
+ description: string;
13
+ inputSchema: JSONSchema;
14
+ route: RouteDef;
15
+ }
16
+ export declare function toolName(route: RouteDef): string;
17
+ export declare function toolDefinitions(app: App<any>): ToolDefinition[];
18
+ export declare function handleMcp(app: App<any>, req: Request, headers: Headers, testUsers: WeakMap<Request, unknown>): Promise<Response>;
package/dist/mcp.js ADDED
@@ -0,0 +1,166 @@
1
+ import { InferloError } from "./errors.js";
2
+ const PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
3
+ function pathKeys(path) {
4
+ return path.split("/").filter((p) => p.startsWith(":")).map((p) => p.slice(1));
5
+ }
6
+ export function toolName(route) {
7
+ const opt = typeof route.options.tool === "object" ? route.options.tool.name : undefined;
8
+ if (opt)
9
+ return opt;
10
+ if (route.name)
11
+ return route.name;
12
+ const slug = route.path.replace(/[:/]+/g, "_").replace(/^_|_$/g, "") || "root";
13
+ return `${route.method.toLowerCase()}_${slug}`;
14
+ }
15
+ export function toolDefinitions(app) {
16
+ return app.routes
17
+ .filter((r) => r.options.tool)
18
+ .map((route) => {
19
+ const properties = {};
20
+ const required = new Set();
21
+ const add = (schema) => {
22
+ if (!schema)
23
+ return;
24
+ for (const [k, v] of Object.entries(schema.properties ?? {}))
25
+ properties[k] = v;
26
+ for (const k of schema.required ?? [])
27
+ required.add(k);
28
+ };
29
+ const o = route.options;
30
+ if (o.params)
31
+ add(o.params.toJSONSchema());
32
+ else
33
+ for (const key of pathKeys(route.path))
34
+ (properties[key] = { type: "string" }), required.add(key);
35
+ if (o.query)
36
+ add(o.query.toJSONSchema());
37
+ if (o.body) {
38
+ const body = o.body.toJSONSchema();
39
+ if (body.type === "object")
40
+ add(body);
41
+ else {
42
+ properties.body = body;
43
+ required.add("body");
44
+ }
45
+ }
46
+ const toolOpt = typeof o.tool === "object" ? o.tool : {};
47
+ return {
48
+ name: toolName(route),
49
+ description: toolOpt.description ?? o.description ?? o.summary ?? `${route.method} ${route.path}`,
50
+ inputSchema: { type: "object", properties, ...(required.size ? { required: [...required] } : {}) },
51
+ route,
52
+ };
53
+ });
54
+ }
55
+ function rpcError(id, code, message) {
56
+ return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
57
+ }
58
+ export async function handleMcp(app, req, headers, testUsers) {
59
+ headers.set("content-type", "application/json; charset=utf-8");
60
+ if (req.method !== "POST") {
61
+ headers.set("allow", "POST");
62
+ return new Response(JSON.stringify(rpcError(null, -32000, "Use POST for MCP requests.")), { status: 405, headers });
63
+ }
64
+ let payload;
65
+ try {
66
+ payload = await req.json();
67
+ }
68
+ catch {
69
+ return new Response(JSON.stringify(rpcError(null, -32700, "Parse error")), { status: 400, headers });
70
+ }
71
+ const tools = toolDefinitions(app);
72
+ const handleOne = async (msg) => {
73
+ const isNotification = msg?.id === undefined || String(msg?.method).startsWith("notifications/");
74
+ if (!msg || msg.jsonrpc !== "2.0" || typeof msg.method !== "string")
75
+ return rpcError(msg?.id, -32600, "Invalid request");
76
+ if (String(msg.method).startsWith("notifications/"))
77
+ return undefined;
78
+ switch (msg.method) {
79
+ case "initialize": {
80
+ const requested = String(msg.params?.protocolVersion ?? "");
81
+ return {
82
+ jsonrpc: "2.0",
83
+ id: msg.id,
84
+ result: {
85
+ protocolVersion: PROTOCOL_VERSIONS.includes(requested) ? requested : PROTOCOL_VERSIONS[0],
86
+ capabilities: { tools: { listChanged: false } },
87
+ serverInfo: { name: app.name, version: app.version },
88
+ instructions: `Tools for ${app.name}. Each tool is an HTTP route of this app and follows its permissions.`,
89
+ },
90
+ };
91
+ }
92
+ case "ping":
93
+ return { jsonrpc: "2.0", id: msg.id, result: {} };
94
+ case "tools/list":
95
+ return { jsonrpc: "2.0", id: msg.id, result: { tools: tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })) } };
96
+ case "tools/call": {
97
+ const name = String(msg.params?.name ?? "");
98
+ const tool = tools.find((t) => t.name === name);
99
+ if (!tool)
100
+ return rpcError(msg.id, -32602, `Unknown tool: ${name}`);
101
+ const args = (msg.params?.arguments ?? {});
102
+ const response = await callTool(app, tool, args, req, testUsers);
103
+ const text = await response.text();
104
+ return {
105
+ jsonrpc: "2.0",
106
+ id: msg.id,
107
+ result: { content: [{ type: "text", text: text || `${response.status}` }], isError: !response.ok },
108
+ };
109
+ }
110
+ default:
111
+ if (isNotification)
112
+ return undefined;
113
+ return rpcError(msg.id, -32601, `Method not found: ${msg.method}`);
114
+ }
115
+ };
116
+ if (Array.isArray(payload)) {
117
+ const results = (await Promise.all(payload.map(handleOne))).filter(Boolean);
118
+ return results.length ? new Response(JSON.stringify(results), { status: 200, headers }) : new Response(null, { status: 202, headers });
119
+ }
120
+ const result = await handleOne(payload);
121
+ return result ? new Response(JSON.stringify(result), { status: 200, headers }) : new Response(null, { status: 202, headers });
122
+ }
123
+ async function callTool(app, tool, args, original, testUsers) {
124
+ const route = tool.route;
125
+ const o = route.options;
126
+ const keys = pathKeys(route.path);
127
+ const queryKeys = new Set(Object.keys(o.query?.toJSONSchema().properties ?? {}));
128
+ let path = route.path;
129
+ const query = new URLSearchParams();
130
+ const body = {};
131
+ for (const [k, v] of Object.entries(args)) {
132
+ if (keys.includes(k))
133
+ path = path.replace(`:${k}`, encodeURIComponent(String(v)));
134
+ else if (queryKeys.has(k)) {
135
+ if (Array.isArray(v))
136
+ for (const item of v)
137
+ query.append(k, String(item));
138
+ else if (v !== undefined && v !== null)
139
+ query.set(k, String(v));
140
+ }
141
+ else
142
+ body[k] = v;
143
+ }
144
+ if (keys.some((k) => path.includes(`:${k}`))) {
145
+ return new Response(JSON.stringify(new InferloError("IL1001", `Missing path parameter for ${route.path}.`, { status: 422 }).toJSON()), { status: 422 });
146
+ }
147
+ const url = new URL(path, original.url);
148
+ url.search = query.toString();
149
+ const headers = new Headers();
150
+ for (const h of ["authorization", "cookie", "x-api-key"]) {
151
+ const v = original.headers.get(h);
152
+ if (v)
153
+ headers.set(h, v);
154
+ }
155
+ let payload;
156
+ if (o.body) {
157
+ headers.set("content-type", "application/json");
158
+ const bodySchema = o.body.toJSONSchema();
159
+ payload = JSON.stringify(bodySchema.type === "object" ? body : body.body);
160
+ }
161
+ const inner = new Request(url, { method: route.method, headers, body: payload });
162
+ if (testUsers.has(original))
163
+ testUsers.set(inner, testUsers.get(original));
164
+ return app.fetch(inner);
165
+ }
166
+ //# sourceMappingURL=mcp.js.map