@lunora/mcp 1.0.0-alpha.24 → 1.0.0-alpha.241

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 (47) hide show
  1. package/LICENSE.md +120 -0
  2. package/README.md +200 -14
  3. package/dist/bin.mjs +4 -47
  4. package/dist/docs/index.d.mts +259 -0
  5. package/dist/docs/index.d.ts +259 -0
  6. package/dist/docs/index.mjs +1 -0
  7. package/dist/index.d.mts +84 -82
  8. package/dist/index.d.ts +84 -82
  9. package/dist/index.mjs +1 -5
  10. package/dist/node/index.d.mts +90 -0
  11. package/dist/node/index.d.ts +90 -0
  12. package/dist/node/index.mjs +1 -0
  13. package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs +1 -0
  14. package/dist/packem_shared/DEFAULT_DOCS_BASE_URL-CiHy_Dbz.mjs +4 -0
  15. package/dist/packem_shared/DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs +1 -0
  16. package/dist/packem_shared/DEFAULT_SEARCH_LIMIT-B8C5JM2y.mjs +3 -0
  17. package/dist/packem_shared/DOCS_SERVER_NAME-CUnLrk1a.mjs +1 -0
  18. package/dist/packem_shared/DOCS_URI_SCHEME-C35UQ5Fo.mjs +3 -0
  19. package/dist/packem_shared/LOCAL_SERVER_NAME-Bj3l_7WG.mjs +1 -0
  20. package/dist/packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-mO4JuHgV.mjs +1 -0
  21. package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-CihHZdB6.mjs +1 -0
  22. package/dist/packem_shared/compose.d-BVnP3uAy.d.mts +111 -0
  23. package/dist/packem_shared/compose.d-BVnP3uAy.d.ts +111 -0
  24. package/dist/packem_shared/connectStdio-DYI-3vnF.mjs +1 -0
  25. package/dist/packem_shared/createAuthedMcpFetchHandler-D_SQpDhH.mjs +1 -0
  26. package/dist/packem_shared/createLunoraMcpServer-CQJIwdik.mjs +1 -0
  27. package/dist/packem_shared/createMcpFetchHandler-TkLzJUNn.mjs +1 -0
  28. package/dist/packem_shared/createPaidMcpServer-BH_mly77.mjs +1 -0
  29. package/dist/packem_shared/createToolServer-BtGuPyMU.mjs +1 -0
  30. package/dist/packem_shared/observability-tools-D4Z8dGS-.mjs +1 -0
  31. package/dist/packem_shared/promise-memo-OqVPWoUX.mjs +1 -0
  32. package/dist/packem_shared/serve-stateless.d-DD5arqFx.d.mts +45 -0
  33. package/dist/packem_shared/serve-stateless.d-DD5arqFx.d.ts +45 -0
  34. package/dist/packem_shared/server.d-D3rejkVc.d.mts +152 -0
  35. package/dist/packem_shared/server.d-DQgZyRkG.d.ts +152 -0
  36. package/dist/packem_shared/toDocsSearchHits-CBLmtWXt.mjs +1 -0
  37. package/dist/packem_shared/tool-call-Bs-QeKp6.mjs +1 -0
  38. package/dist/packem_shared/tools-BOfWz78m.mjs +2 -0
  39. package/dist/worker/index.d.mts +258 -0
  40. package/dist/worker/index.d.ts +258 -0
  41. package/dist/worker/index.mjs +1 -0
  42. package/package.json +26 -5
  43. package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-DWobae3P.mjs +0 -168
  44. package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-CWb_wFd5.mjs +0 -204
  45. package/dist/packem_shared/connectStdio-bkJcaXYE.mjs +0 -76
  46. package/dist/packem_shared/createMcpFetchHandler-DfzBSLrm.mjs +0 -14
  47. package/dist/packem_shared/createPaidMcpServer-b-qp_aEF.mjs +0 -93
@@ -0,0 +1,258 @@
1
+ import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-DQgZyRkG.js";
2
+ export { A as AGENT_RUN_INPUT_SCHEMA, a as AGENT_STATUS_TOOL_NAME,
3
+ /** Worker-safe entry: fetch handlers and tool composition, with no stdio transport or `node:*` imports. */
4
+ type C as CallAgentToolOptions,
5
+ /** Worker-safe entry: fetch handlers and tool composition, with no stdio transport or `node:*` imports. */
6
+ type M as McpAgentExposure, b as agentToolDefinitions, c as callAgentTool, p as parseAgentsEnv } from "../packem_shared/server.d-DQgZyRkG.js";
7
+ import { M as McpFetchHandler } from "../packem_shared/serve-stateless.d-DD5arqFx.js";
8
+ export { D as DEFAULT_MAX_REQUEST_BYTES, type S as ServeStatelessOptions, s as serveStateless } from "../packem_shared/serve-stateless.d-DD5arqFx.js";
9
+ import { T as ToolDefinition, c as ToolResult, b as ToolInputSchema } from "../packem_shared/compose.d-BVnP3uAy.js";
10
+ export { type M as McpServerInfo, type a as McpTool, d as createToolServer } from "../packem_shared/compose.d-BVnP3uAy.js";
11
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
12
+ import { LunoraClient } from '@lunora/client';
13
+ import '@modelcontextprotocol/sdk/server/index.js';
14
+ import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
15
+ /**
16
+ * The observability tools. Descriptions say WHEN to call the tool, not just
17
+ * what it returns — an agent picking between five similar reads needs the
18
+ * trigger, and it pays for these strings on every turn.
19
+ */
20
+ declare const OBSERVABILITY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
21
+ /** The read-only tool surface: introspection + query. Always exposed. */
22
+ declare const READ_ONLY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
23
+ /** The write tool surface (mutations + actions). Exposed ONLY when writes are enabled. */
24
+ declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
25
+ /**
26
+ * The tools this server advertises: every family whose gate is open, in table
27
+ * order. A gated family is OMITTED rather than refused — an AI agent can't
28
+ * invoke what it can't see — and {@link callTool} re-checks the same gate at
29
+ * dispatch, so the guarantee does not depend on a client honouring this list.
30
+ */
31
+ declare const toolDefinitions: (allowWrites: boolean, allowObservability?: boolean, allowDataReads?: boolean) => ReadonlyArray<ToolDefinition>;
32
+ /**
33
+ * Dispatch a tool call against `client`: find the family that owns `name`, check
34
+ * its gate, hand off. Unknown tools and thrown errors are returned as `isError`
35
+ * results (rather than rejections) so the calling model sees the failure as tool
36
+ * output, per the MCP convention.
37
+ *
38
+ * Each gate is enforced HERE as well as in {@link toolDefinitions}, so a call to
39
+ * a gated tool is refused even if the client somehow names one it was never
40
+ * shown.
41
+ */
42
+ declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, allowObservability?: boolean, allowDataReads?: boolean) => Promise<ToolResult>;
43
+ /**
44
+ * The verified access-token payload better-auth hands a protected handler.
45
+ *
46
+ * A JWT payload is an open bag of claims, so this is deliberately an index
47
+ * signature with the two entries this module reads named. It is structurally
48
+ * satisfied by `jose`'s `JWTPayload`, which is what better-auth passes.
49
+ */
50
+ interface McpAccessTokenClaims {
51
+ readonly [claim: string]: unknown;
52
+ /** Space-delimited granted scopes (RFC 6749 §3.3). */
53
+ readonly scope?: unknown;
54
+ /** Subject — the user the token was issued for. */
55
+ readonly sub?: string;
56
+ }
57
+ /**
58
+ * The MCP auth gate: wraps a claims-aware handler into a plain fetch handler.
59
+ *
60
+ * Declared structurally here rather than imported from `@better-auth/mcp`,
61
+ * following the same rule `./paid` follows for `@lunora/x402`: a type import
62
+ * from a package this one does not depend on puts that package's `.d.ts` into
63
+ * the build graph, and a consumer that never installs it never builds it
64
+ * either — so the dts bundler looks for a `dist/` that does not exist and fails
65
+ * the build. Structural typing costs nothing here because this module never
66
+ * inspects the gate; it only applies it.
67
+ *
68
+ * Both better-auth entry points partially apply to this shape:
69
+ * `(handler) => requireMcpAuth(auth, handler, opts)` and
70
+ * `(handler) => createMcpProtectedRequestHandler(options, handler)`.
71
+ */
72
+ type McpAuthProtect = (handler: (request: Request, claims: McpAccessTokenClaims) => Promise<Response>) => McpFetchHandler;
73
+ /** Server options, or a function deriving them from the request's verified claims. */
74
+ type AuthedMcpServerOptions = ((claims: McpAccessTokenClaims) => LunoraMcpServerOptions | Promise<LunoraMcpServerOptions>) | LunoraMcpServerOptions;
75
+ interface AuthedMcpFetchHandlerOptions {
76
+ /**
77
+ * Largest accepted request body, in bytes — enforced while the body streams
78
+ * in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
79
+ * (128 KiB), which a value that is not a non-negative safe integer also
80
+ * falls back to.
81
+ */
82
+ maxRequestBytes?: number;
83
+ /**
84
+ * The OAuth gate to mount the MCP server behind. Pass
85
+ * `(handler) => requireMcpAuth(auth, handler, opts)` from
86
+ * `@lunora/auth/plugins`.
87
+ */
88
+ protect: McpAuthProtect;
89
+ /**
90
+ * The Lunora MCP server to serve once a request is authorized — either a
91
+ * fixed options object, or a function of the verified token claims so tool
92
+ * exposure can follow the scopes the token actually carries.
93
+ */
94
+ server: AuthedMcpServerOptions;
95
+ /**
96
+ * Step-up for the side-effecting tools: the write tools
97
+ * (`lunora_run_mutation` / `lunora_run_action`) and the agent tools (each
98
+ * `agent_<name>` and `lunora_agent_status`, since starting a durable run is a
99
+ * side effect). A token without `scope` still sees them in `tools/list`, and
100
+ * a `tools/call` to one throws `challenge([scope])` instead of running, so the
101
+ * client can re-authorize for the scope and retry. Unset, tools follow
102
+ * `allowWrites` / `allowAgents` alone. The raw-row and observability families
103
+ * need their own token scopes with or without `stepUp`; without it a call
104
+ * without the scope is refused as a tool error, not challenged.
105
+ */
106
+ stepUp?: {
107
+ /**
108
+ * Build the error that makes the gate answer 403 `insufficient_scope`
109
+ * naming `scopes`. Pass `createInsufficientScopeError` from
110
+ * `@lunora/auth/plugins`: better-auth only turns an error that factory
111
+ * created into the challenge.
112
+ */
113
+ challenge: (scopes: string[]) => unknown;
114
+ /** The scope a call to a side-effecting tool needs, e.g. `"lunora:write"`. */
115
+ scope: string;
116
+ };
117
+ }
118
+ /**
119
+ * Parse an access token's `scope` claim into a set.
120
+ *
121
+ * RFC 6749 §3.3 makes `scope` a space-delimited string, and better-auth issues
122
+ * it that way; anything else (absent, or a non-string an extension wrote)
123
+ * yields an empty set rather than throwing, so a scope check on a malformed
124
+ * token denies instead of crashing the tool call.
125
+ */
126
+ declare const mcpTokenScopes: (claims: McpAccessTokenClaims) => ReadonlySet<string>;
127
+ declare const createAuthedMcpFetchHandler: (options: AuthedMcpFetchHandlerOptions) => McpFetchHandler;
128
+ /** {@link createMcpFetchHandler} options: the server's, plus this transport's body limit. */
129
+ interface McpFetchHandlerOptions extends LunoraMcpServerOptions {
130
+ /**
131
+ * Largest accepted request body, in bytes — enforced while the body streams
132
+ * in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
133
+ * (128 KiB), which a value that is not a non-negative safe integer also
134
+ * falls back to.
135
+ */
136
+ maxRequestBytes?: number;
137
+ }
138
+ /**
139
+ * Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
140
+ * invocation constructs a fresh proxy server and serves the request through
141
+ * {@link serveStateless}.
142
+ *
143
+ * The `LunoraClient` is resolved ONCE, here, and shared by every request: it is
144
+ * the same deployment on each one, and the public-function registry memo in
145
+ * `./tools` is keyed by client identity, so a per-request client would re-fetch
146
+ * that registry on every tool call. A misconfiguration (`url` without `token`)
147
+ * therefore throws when the handler is built rather than on first request,
148
+ * which is where `createLunoraMcpServer` already documents reporting it.
149
+ */
150
+ declare const createMcpFetchHandler: (options: McpFetchHandlerOptions) => McpFetchHandler;
151
+ /** A tool handler: receives the call's `arguments` bag, returns an MCP tool result. */
152
+ /**
153
+ * The x402 vocabulary this module needs, declared here rather than imported.
154
+ *
155
+ * The x402 package is an optional peer, and a type import from its `charge`
156
+ * entry puts its `.d.ts` back into this package's build graph:
157
+ * a consumer that never installs x402 never builds it either, so the dts bundler
158
+ * looks for a `dist/` that does not exist and fails. That is not hypothetical —
159
+ * it broke the docs site build, which runs a filtered build over the docs app and its dependency closure
160
+ * and therefore never builds x402.
161
+ *
162
+ * Declaring them locally is safe because this module never *inspects* a charge
163
+ * config; it forwards it whole to `createChargeMiddleware`. The index signature
164
+ * keeps a real charge config assignable as x402 grows fields.
165
+ */
166
+ /** Mirrors x402's `X402Price` — a decimal string like `"$0.05"`, or a number. */
167
+ type X402Price = number | string;
168
+ /** Mirrors x402's charge config with the per-tool price omitted. */
169
+ interface X402ChargeSettings {
170
+ /** Network this resource settles on. */
171
+ readonly network: string;
172
+ /** Everything else x402 accepts, forwarded untouched. */
173
+ readonly [key: string]: unknown;
174
+ /** Payout wallet(s), per network family. */
175
+ readonly recipient: {
176
+ readonly evm?: string;
177
+ readonly svm?: string;
178
+ };
179
+ }
180
+ type ToolHandler = (arguments_: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
181
+ /** Registration shape for a free tool. */
182
+ interface RegisterToolOptions {
183
+ /** Optional MCP tool annotations (`readOnlyHint`, `title`, …). */
184
+ annotations?: Tool["annotations"];
185
+ /** Human/model-facing description of what the tool does. */
186
+ description: string;
187
+ /** JSON-Schema object describing the tool's arguments. */
188
+ inputSchema: ToolInputSchema;
189
+ /** Unique tool name (the MCP `tools/call` `name`). */
190
+ name: string;
191
+ }
192
+ /** Registration shape for a paid tool: a {@link RegisterToolOptions} plus its USD price. */
193
+ interface RegisterPaidToolOptions extends RegisterToolOptions {
194
+ /** USD price per call (e.g. `"$0.05"`), charged via x402 before dispatch. */
195
+ price: X402Price;
196
+ }
197
+ /** x402 settlement vocabulary shared by every paid tool (network, recipient, facilitator); price is per-tool. */
198
+ type PaidMcpChargeConfig = X402ChargeSettings;
199
+ /** Config for `createPaidMcpServer`. */
200
+ interface PaidMcpServerConfig {
201
+ /** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
202
+ charge: PaidMcpChargeConfig;
203
+ /**
204
+ * Largest accepted request body, in bytes — enforced while the body streams
205
+ * in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
206
+ * (128 KiB), which a value that is not a non-negative safe integer also
207
+ * falls back to.
208
+ */
209
+ maxRequestBytes?: number;
210
+ /** Name/version advertised in the MCP `initialize` handshake. Defaults to `lunora-paid-mcp`. */
211
+ serverInfo?: {
212
+ name: string;
213
+ version: string;
214
+ };
215
+ }
216
+ /**
217
+ * The Worker execution context, as this module reads it: only `waitUntil`, and
218
+ * structurally, so the package takes no `@cloudflare/workers-types` dependency.
219
+ */
220
+ interface PaidMcpExecutionContext {
221
+ waitUntil?: (promise: Promise<unknown>) => void;
222
+ }
223
+ /**
224
+ * The paid server's fetch handler.
225
+ *
226
+ * Unlike the free `McpFetchHandler` it takes the Worker's full
227
+ * `(request, env, ctx)` triple — the shape `export default { fetch }` is called
228
+ * with — because the x402 receipt sink NEEDS `ctx.waitUntil`: work that is
229
+ * neither awaited into the response nor registered with it is cancelled when the
230
+ * request ends, so an async `onReceipt` (inserting the settled payment into a
231
+ * durable table) frequently never runs while the money has already moved
232
+ * on-chain. `env` is accepted and ignored so the handler drops straight into the
233
+ * default export; both are optional, so a non-Workers caller may still invoke it
234
+ * with a bare `Request` (the middleware then simply sees no `waitUntil`).
235
+ */
236
+ type PaidMcpFetchHandler = (request: Request, env?: unknown, context?: PaidMcpExecutionContext) => Promise<Response>;
237
+ /** A paid MCP server: register free/paid tools, then serve over Streamable HTTP. */
238
+ interface PaidMcpServer {
239
+ /** The Streamable-HTTP fetch handler; gates each paid `tools/call` behind x402. */
240
+ readonly fetchHandler: PaidMcpFetchHandler;
241
+ /** Register a **paid** tool: its dispatch runs the x402 charge middleware first. */
242
+ paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
243
+ /** Register a **free** tool (coexists with paid tools on the same server). */
244
+ tool: (options: RegisterToolOptions, handler: ToolHandler) => void;
245
+ }
246
+ /**
247
+ * Create a paid MCP server. Register free tools with `tool()` and priced tools
248
+ * with `paidTool()` (they coexist), then serve `fetchHandler` over HTTP.
249
+ *
250
+ * The server is **stateless**: `fetchHandler` builds a fresh `Server` per
251
+ * request (reading the live tool registry), so tools registered before the
252
+ * first request are all visible. Each priced tool memoises one initialised
253
+ * `ChargeMiddleware` (keyed by tool name, baking that tool's price and naming
254
+ * the tool as the challenge `resource`); a failed init is not cached, so a
255
+ * transient facilitator outage retries on the next call.
256
+ */
257
+ declare const createPaidMcpServer: (config: PaidMcpServerConfig) => PaidMcpServer;
258
+ export { type AuthedMcpFetchHandlerOptions, type AuthedMcpServerOptions, type McpAccessTokenClaims, type McpAuthProtect, type McpFetchHandler, type McpFetchHandlerOptions, OBSERVABILITY_TOOL_DEFINITIONS, type PaidMcpChargeConfig, type PaidMcpExecutionContext, type PaidMcpFetchHandler, type PaidMcpServer, type PaidMcpServerConfig, READ_ONLY_TOOL_DEFINITIONS, type RegisterPaidToolOptions, type RegisterToolOptions, type ToolDefinition, type ToolHandler, type ToolInputSchema, type ToolResult, WRITE_TOOL_DEFINITIONS, callTool, createAuthedMcpFetchHandler, createMcpFetchHandler, createPaidMcpServer, mcpTokenScopes, toolDefinitions };
@@ -0,0 +1 @@
1
+ import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as T,callAgentTool as a,parseAgentsEnv as _}from"../packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{createAuthedMcpFetchHandler as c,mcpTokenScopes as p}from"../packem_shared/createAuthedMcpFetchHandler-D_SQpDhH.mjs";import{createToolServer as s}from"../packem_shared/createToolServer-BtGuPyMU.mjs";import{createMcpFetchHandler as S}from"../packem_shared/createMcpFetchHandler-TkLzJUNn.mjs";import{createPaidMcpServer as A}from"../packem_shared/createPaidMcpServer-BH_mly77.mjs";import{R as n,W as f,c as m,t as x}from"../packem_shared/tools-BOfWz78m.mjs";import{DEFAULT_MAX_REQUEST_BYTES as D,serveStateless as L}from"../packem_shared/DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{O as M}from"../packem_shared/observability-tools-D4Z8dGS-.mjs";export{r as AGENT_RUN_INPUT_SCHEMA,t as AGENT_STATUS_TOOL_NAME,D as DEFAULT_MAX_REQUEST_BYTES,M as OBSERVABILITY_TOOL_DEFINITIONS,n as READ_ONLY_TOOL_DEFINITIONS,f as WRITE_TOOL_DEFINITIONS,T as agentToolDefinitions,a as callAgentTool,m as callTool,c as createAuthedMcpFetchHandler,S as createMcpFetchHandler,A as createPaidMcpServer,s as createToolServer,p as mcpTokenScopes,_ as parseAgentsEnv,L as serveStateless,x as toolDefinitions};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/mcp",
3
- "version": "1.0.0-alpha.24",
3
+ "version": "1.0.0-alpha.241",
4
4
  "description": "Model Context Protocol server exposing a Lunora deployment to AI agents",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -43,16 +43,37 @@
43
43
  "types": "./dist/index.d.ts",
44
44
  "import": "./dist/index.mjs"
45
45
  },
46
+ "./docs": {
47
+ "types": "./dist/docs/index.d.ts",
48
+ "import": "./dist/docs/index.mjs"
49
+ },
50
+ "./node": {
51
+ "types": "./dist/node/index.d.ts",
52
+ "import": "./dist/node/index.mjs"
53
+ },
54
+ "./worker": {
55
+ "types": "./dist/worker/index.d.ts",
56
+ "import": "./dist/worker/index.mjs"
57
+ },
46
58
  "./package.json": "./package.json"
47
59
  },
48
60
  "publishConfig": {
49
61
  "access": "public"
50
62
  },
51
63
  "dependencies": {
52
- "@lunora/client": "1.0.0-alpha.22",
53
- "@lunora/errors": "1.0.0-alpha.4",
54
- "@lunora/x402": "1.0.0-alpha.2",
55
- "@modelcontextprotocol/sdk": "^1.29.0"
64
+ "@cfworker/json-schema": "^4.1.1",
65
+ "@lunora/client": "1.0.0-alpha.207",
66
+ "@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
67
+ "@lunora/shard-engine": "1.0.0-alpha.128",
68
+ "@modelcontextprotocol/sdk": "^1.32.0"
69
+ },
70
+ "peerDependencies": {
71
+ "@lunora/x402": ">=1.0.0-alpha.21 <2.0.0-0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "@lunora/x402": {
75
+ "optional": true
76
+ }
56
77
  },
57
78
  "engines": {
58
79
  "node": "^22.15.0 || >=24.11.0"
@@ -1,168 +0,0 @@
1
- const AGENT_RUN_PATH = "agents:agentRun";
2
- const AGENT_THREAD_PATH = "agents:agentThread";
3
- const AGENT_MESSAGES_PATH = "agents:agentMessages";
4
- const AGENT_STATUS_TOOL_NAME = "lunora_agent_status";
5
- const TERMINAL_STATUSES = /* @__PURE__ */ new Set(["cancelled", "error", "idle"]);
6
- const DEFAULT_MAX_WAIT_MS = 6e4;
7
- const DEFAULT_POLL_INTERVAL_MS = 600;
8
- const AGENT_RUN_INPUT_SCHEMA = {
9
- properties: {
10
- prompt: { description: "The task or message for the agent.", type: "string" },
11
- threadKey: { description: "Reuse to continue a conversation; omit to start a new thread.", type: "string" },
12
- title: { description: "Optional thread title (first run only).", type: "string" }
13
- },
14
- required: ["prompt"],
15
- type: "object"
16
- };
17
- const AGENT_STATUS_INPUT_SCHEMA = {
18
- properties: {
19
- threadKey: { description: "The thread key returned by an agent tool call.", type: "string" }
20
- },
21
- required: ["threadKey"],
22
- type: "object"
23
- };
24
- const agentToolName = (exposure) => exposure.toolName ?? `agent_${exposure.name}`;
25
- const parseAgentsEnv = (raw) => {
26
- if (raw === void 0) {
27
- return [];
28
- }
29
- const exposures = [];
30
- for (const entry of raw.split(";")) {
31
- const trimmed = entry.trim();
32
- if (trimmed.length === 0) {
33
- continue;
34
- }
35
- const separator = trimmed.indexOf(":");
36
- if (separator <= 0) {
37
- continue;
38
- }
39
- const name = trimmed.slice(0, separator).trim();
40
- const description = trimmed.slice(separator + 1).trim();
41
- if (name.length === 0 || description.length === 0) {
42
- continue;
43
- }
44
- exposures.push({ description, name });
45
- }
46
- return exposures;
47
- };
48
- const agentToolDefinitions = (exposures, allowAgents) => {
49
- if (allowAgents !== true || exposures.length === 0) {
50
- return [];
51
- }
52
- const perAgent = exposures.map((exposure) => {
53
- return {
54
- description: `${exposure.description} Starts a durable agent run and returns its final answer.`,
55
- inputSchema: AGENT_RUN_INPUT_SCHEMA,
56
- name: agentToolName(exposure)
57
- };
58
- });
59
- return [
60
- ...perAgent,
61
- {
62
- description: "Check the status of a durable agent run (and its answer if finished) by its threadKey.",
63
- inputSchema: AGENT_STATUS_INPUT_SCHEMA,
64
- name: AGENT_STATUS_TOOL_NAME
65
- }
66
- ];
67
- };
68
- const isAgentToolName = (name, exposures) => name === AGENT_STATUS_TOOL_NAME || exposures.some((exposure) => agentToolName(exposure) === name);
69
- const freshThreadKey = () => `mcp-${crypto.randomUUID()}`;
70
- const reference = (path) => {
71
- return { __lunoraRef: path };
72
- };
73
- const defaultWait = async (ms) => {
74
- await new Promise((resolve) => {
75
- setTimeout(resolve, ms);
76
- });
77
- };
78
- const ok = (value) => {
79
- return { content: [{ text: JSON.stringify(value, void 0, 2), type: "text" }] };
80
- };
81
- const fail = (text) => {
82
- return { content: [{ text, type: "text" }], isError: true };
83
- };
84
- const readString = (input, key) => {
85
- const value = input[key];
86
- return typeof value === "string" && value.length > 0 ? value : void 0;
87
- };
88
- const finalAnswer = (messages) => {
89
- for (let index = messages.length - 1; index >= 0; index -= 1) {
90
- const row = messages[index];
91
- const toolCalls = row?.["toolCalls"];
92
- const pending = Array.isArray(toolCalls) && toolCalls.length > 0;
93
- if (row?.["role"] === "assistant" && !pending) {
94
- return typeof row["content"] === "string" ? row["content"] : "";
95
- }
96
- }
97
- return "";
98
- };
99
- const readThreadStatus = (thread) => {
100
- if (thread !== null && typeof thread === "object" && typeof thread.status === "string") {
101
- return thread.status;
102
- }
103
- return "unknown";
104
- };
105
- const readTerminal = async (client, threadKey, status, thread) => {
106
- const messages = await client.query(reference(AGENT_MESSAGES_PATH), { key: threadKey });
107
- if (status === "error") {
108
- const error = thread !== null && typeof thread === "object" ? thread.error : void 0;
109
- return ok({ error: typeof error === "string" ? error : "the agent run failed", status, threadKey });
110
- }
111
- return ok({ status, text: finalAnswer(messages), threadKey });
112
- };
113
- const callAgentStatus = async (client, input) => {
114
- const threadKey = readString(input, "threadKey");
115
- if (threadKey === void 0) {
116
- return fail('"threadKey" is required and must be a non-empty string');
117
- }
118
- const thread = await client.query(reference(AGENT_THREAD_PATH), { key: threadKey });
119
- const status = readThreadStatus(thread);
120
- if (TERMINAL_STATUSES.has(status)) {
121
- return readTerminal(client, threadKey, status, thread);
122
- }
123
- return ok({ status: status === "unknown" ? "running" : status, threadKey });
124
- };
125
- const callAgentTool = async (client, name, input, options) => {
126
- try {
127
- if (options.allowAgents !== true) {
128
- return fail(`tool "${name}" is disabled: agent tools are off. Enable them with the LUNORA_MCP_ALLOW_AGENTS env var.`);
129
- }
130
- if (name === AGENT_STATUS_TOOL_NAME) {
131
- return await callAgentStatus(client, input);
132
- }
133
- const exposure = options.exposures.find((candidate) => agentToolName(candidate) === name);
134
- if (exposure === void 0) {
135
- return fail(`agent tool "${name}" is not exposed by this MCP server`);
136
- }
137
- const prompt = readString(input, "prompt");
138
- if (prompt === void 0) {
139
- return fail('"prompt" is required and must be a non-empty string');
140
- }
141
- const threadKey = readString(input, "threadKey") ?? freshThreadKey();
142
- const title = readString(input, "title");
143
- const { id } = await client.mutation(reference(AGENT_RUN_PATH), {
144
- agent: exposure.name,
145
- input: prompt,
146
- threadKey,
147
- ...title === void 0 ? {} : { title }
148
- });
149
- const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
150
- const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
151
- const wait = options.wait ?? defaultWait;
152
- const maxPolls = Math.max(1, Math.ceil(maxWaitMs / pollIntervalMs));
153
- for (let attempt = 0; attempt < maxPolls; attempt += 1) {
154
- const thread = await client.query(reference(AGENT_THREAD_PATH), { key: threadKey });
155
- const status = readThreadStatus(thread);
156
- if (TERMINAL_STATUSES.has(status)) {
157
- return await readTerminal(client, threadKey, status, thread);
158
- }
159
- await wait(pollIntervalMs);
160
- }
161
- return ok({ hint: "call lunora_agent_status with this threadKey to poll for the answer", runId: id, status: "running", threadKey });
162
- } catch (error) {
163
- const message = error instanceof Error ? error.message : String(error);
164
- return fail(message);
165
- }
166
- };
167
-
168
- export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, agentToolDefinitions, callAgentTool, finalAnswer, isAgentToolName, parseAgentsEnv };