@lunora/mcp 1.0.0-alpha.105 → 1.0.0-alpha.107

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 (33) hide show
  1. package/README.md +17 -6
  2. package/dist/bin.mjs +4 -4
  3. package/dist/docs/index.d.mts +3 -3
  4. package/dist/docs/index.d.ts +3 -3
  5. package/dist/docs/index.mjs +1 -1
  6. package/dist/index.d.mts +76 -18
  7. package/dist/index.d.ts +76 -18
  8. package/dist/index.mjs +1 -1
  9. package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs +1 -0
  10. package/dist/packem_shared/DEFAULT_MAX_REQUEST_BYTES-B8WntTxM.mjs +1 -0
  11. package/dist/packem_shared/DEFAULT_SEARCH_LIMIT-BqSYN5vr.mjs +3 -0
  12. package/dist/packem_shared/DOCS_SERVER_NAME-DmmgZtad.mjs +1 -0
  13. package/dist/packem_shared/{LOCAL_SERVER_NAME-BwlBnhX-.mjs → LOCAL_SERVER_NAME-Do7MU6Kd.mjs} +1 -1
  14. package/dist/packem_shared/{OBSERVABILITY_TOOL_DEFINITIONS-KrCukPbu.mjs → OBSERVABILITY_TOOL_DEFINITIONS-Byqgb9wh.mjs} +1 -1
  15. package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-_aG40yWx.mjs +1 -0
  16. package/dist/packem_shared/connectStdio-BBtfW4UB.mjs +1 -0
  17. package/dist/packem_shared/createAuthedMcpFetchHandler-CmVUae-x.mjs +1 -0
  18. package/dist/packem_shared/createMcpFetchHandler-BJKPn555.mjs +1 -0
  19. package/dist/packem_shared/createPaidMcpServer-CQrzMUhI.mjs +1 -0
  20. package/dist/packem_shared/observability-tools-B-g9Y9IT.mjs +1 -0
  21. package/dist/packem_shared/{serve-stateless.d-B_q8q39X.d.mts → serve-stateless.d-C_y-E_zk.d.mts} +19 -3
  22. package/dist/packem_shared/{serve-stateless.d-B_q8q39X.d.ts → serve-stateless.d-C_y-E_zk.d.ts} +19 -3
  23. package/package.json +4 -4
  24. package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-DGpNvk5K.mjs +0 -1
  25. package/dist/packem_shared/DEFAULT_SEARCH_LIMIT-V7pCZ1wt.mjs +0 -3
  26. package/dist/packem_shared/DOCS_SERVER_NAME-BNYVF715.mjs +0 -1
  27. package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-aD-b7Hw2.mjs +0 -1
  28. package/dist/packem_shared/connectStdio-CDsCt1IT.mjs +0 -1
  29. package/dist/packem_shared/createAuthedMcpFetchHandler-LLzMY9SW.mjs +0 -1
  30. package/dist/packem_shared/createMcpFetchHandler-B9koGwkW.mjs +0 -1
  31. package/dist/packem_shared/createPaidMcpServer-DyNwdCq2.mjs +0 -1
  32. package/dist/packem_shared/observability-tools-Ce0YK58T.mjs +0 -1
  33. package/dist/packem_shared/serveStateless-Db7J3pAO.mjs +0 -1
package/README.md CHANGED
@@ -75,11 +75,13 @@ Part of the [Lunora](https://github.com/anolilab/lunora) framework — a type-sa
75
75
  The five `lunora_get_*` observability tools are read-only, but they surface the
76
76
  deployment's **operational data** — log lines, request metadata, and grouped
77
77
  error messages, all of which may contain user data, and all of which land in the
78
- model's context (and therefore at its provider). They are exposed **only when an
79
- admin token resolved**: without one they are omitted from `ListTools` entirely
80
- and refused at dispatch, the same omit-don't-refuse rule the write tools use.
81
- They are independent of `--allow-writes`, which is about changing data, not
82
- reading operational data.
78
+ model's context (and therefore at its provider). They are therefore **off by
79
+ default**: set `LUNORA_MCP_ALLOW_OBSERVABILITY=1` (or pass
80
+ `allowObservability: true`) to expose them. Without it they are omitted from
81
+ `ListTools` entirely and refused at dispatch, the same omit-don't-refuse rule the
82
+ write tools use. They are independent of `--allow-writes`, which is about
83
+ changing data, not reading operational data — and independent of the admin
84
+ bearer, which every tool already needs, so holding it is not the opt-in.
83
85
 
84
86
  They return `structuredContent` alongside the usual text block, described by each
85
87
  tool's `outputSchema` (MCP revision `2025-06-18` and later; older clients keep
@@ -142,7 +144,14 @@ await server.connect(myTransport);
142
144
 
143
145
  A deployment's durable [`@lunora/agent`](https://www.npmjs.com/package/@lunora/agent) agents can be fronted as MCP tools. This is **opt-in and fail-closed**, mirroring `allowWrites`: starting an agent run is a side effect, so the agent tools are omitted from the advertised list _and_ refused at dispatch unless you explicitly enable them. `@lunora/mcp` takes no dependency on `@lunora/agent` — it reaches the agent's public `agents:agentRun` mutation over RPC like any other function.
144
146
 
145
- Enable it with two env vars (or the matching `createLunoraMcpServer` options):
147
+ Two opt-ins are needed, on **both** sides:
148
+
149
+ 1. **On the agent**, `defineAgent({ publicRun: true })`. `agents:agentRun` refuses
150
+ any agent without it (`FORBIDDEN`), because starting a durable run from
151
+ outside the deployment is a side effect the agent's author has to allow. The
152
+ env vars below do not grant it — an agent exposed here but not marked
153
+ `publicRun` is advertised and fails on its first call.
154
+ 2. **On this server**, the env vars (or the matching `createLunoraMcpServer` options):
146
155
 
147
156
  - `LUNORA_MCP_ALLOW_AGENTS` — set to `1`/`true`/`yes`/`on` to expose the agent tools. Default: agents disabled.
148
157
  - `LUNORA_MCP_AGENTS` — a `;`-separated list of `name:description` pairs selecting which agents to expose, e.g. `"support:Support questions;billing:Billing help"`.
@@ -240,6 +249,8 @@ await connectLocalStdio({
240
249
 
241
250
  The deployment tools are advertised even when the resolver currently returns nothing — MCP clients cache the tool list, so a surface that appeared only when the dev server happened to be up would stay invisible for the rest of the session. Calling one with nothing running returns an actionable error instead.
242
251
 
252
+ The observability tools are the exception: their gate is snapshotted when the tool list is built, so a session started before `lunora dev` never advertises them (and the cached list keeps them absent afterwards). Restart the MCP server once the dev server is up.
253
+
243
254
  > This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs)**.
244
255
 
245
256
  ## Related
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{parseAgentsEnv as E}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-DGpNvk5K.mjs";import{connectStdio as L}from"./packem_shared/connectStdio-CDsCt1IT.mjs";const d=new Set(["1","on","true","yes"]),A=r=>r!==void 0&&d.has(r.trim().toLowerCase());class o extends Error{code;constructor(t,s){super(t),this.name="BinError",this.code=s}}const l=async(r,t={})=>{const s=t.connect??L,n=t.writeError??(e=>{process.stderr.write(e)}),i=r.LUNORA_URL;if(i===void 0||i.length===0)throw n(`lunora-mcp: LUNORA_URL environment variable is required
3
- `),new o("LUNORA_URL environment variable is required",1);const a=r.LUNORA_ADMIN_TOKEN;if(a===void 0||a.length===0)throw n(`lunora-mcp: LUNORA_ADMIN_TOKEN environment variable is required (every tool reads admin-gated routes)
4
- `),new o("LUNORA_ADMIN_TOKEN environment variable is required",1);const c=Number(r.LUNORA_MCP_AGENT_TIMEOUT_MS),N=Number.isFinite(c)&&c>0?c:void 0;try{await s({agents:E(r.LUNORA_MCP_AGENTS),allowAgents:A(r.LUNORA_MCP_ALLOW_AGENTS),allowWrites:A(r.LUNORA_MCP_ALLOW_WRITES),token:a,url:i,...N===void 0?{}:{agentMaxWaitMs:N}})}catch(e){const _=e instanceof Error?e.message:String(e);throw n(`lunora-mcp: failed to start — ${_}
5
- `),new o(`failed to start — ${_}`,1)}};try{await l(process.env)}catch(r){process.exit(r instanceof o?r.code:1)}
2
+ import{parseAgentsEnv as N}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{connectStdio as E}from"./packem_shared/connectStdio-BBtfW4UB.mjs";const l=new Set(["1","on","true","yes"]),_=r=>r!==void 0&&l.has(r.trim().toLowerCase());class o extends Error{code;constructor(t,s){super(t),this.name="BinError",this.code=s}}const O=async(r,t={})=>{const s=t.connect??E,i=t.writeError??(e=>{process.stderr.write(e)}),n=r.LUNORA_URL;if(n===void 0||n.length===0)throw i(`lunora-mcp: LUNORA_URL environment variable is required
3
+ `),new o("LUNORA_URL environment variable is required",1);const a=r.LUNORA_ADMIN_TOKEN;if(a===void 0||a.length===0)throw i(`lunora-mcp: LUNORA_ADMIN_TOKEN environment variable is required (every tool reads admin-gated routes)
4
+ `),new o("LUNORA_ADMIN_TOKEN environment variable is required",1);const c=Number(r.LUNORA_MCP_AGENT_TIMEOUT_MS),A=Number.isFinite(c)&&c>0?c:void 0;try{await s({agents:N(r.LUNORA_MCP_AGENTS),allowAgents:_(r.LUNORA_MCP_ALLOW_AGENTS),allowObservability:_(r.LUNORA_MCP_ALLOW_OBSERVABILITY),allowWrites:_(r.LUNORA_MCP_ALLOW_WRITES),token:a,url:n,...A===void 0?{}:{agentMaxWaitMs:A}})}catch(e){const L=e instanceof Error?e.message:String(e);throw i(`lunora-mcp: failed to start — ${L}
5
+ `),new o(`failed to start — ${L}`,1)}};try{await O(process.env)}catch(r){process.exit(r instanceof o?r.code:1)}
@@ -1,4 +1,4 @@
1
- import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-B_q8q39X.mjs";
1
+ import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-C_y-E_zk.mjs";
2
2
  export type {
3
3
  /**
4
4
  * `@lunora/mcp/docs` — the documentation tool surface: `lunora_search_docs`,
@@ -49,7 +49,7 @@ c as McpResourceSummary,
49
49
  * (`createToolServer`) is exported from the package root, so a consumer using
50
50
  * both entries gets one implementation rather than two copies.
51
51
  */
52
- d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-B_q8q39X.mjs";
52
+ d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-C_y-E_zk.mjs";
53
53
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
54
54
  import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
55
55
  /**
@@ -135,7 +135,7 @@ declare const DOCS_SERVER_NAME = "lunora-docs";
135
135
  interface DocsMcpServerOptions {
136
136
  /** The documentation source the tools read. */
137
137
  index: DocsIndex;
138
- /** Largest accepted request body. Defaults to {@link DEFAULT_MAX_REQUEST_BYTES}. */
138
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES`, re-exported at the foot of this module. */
139
139
  maxRequestBytes?: number;
140
140
  /**
141
141
  * Version reported in the handshake. Defaults to `"0.0.0"` — a docs site
@@ -1,4 +1,4 @@
1
- import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-B_q8q39X.js";
1
+ import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-C_y-E_zk.js";
2
2
  export type {
3
3
  /**
4
4
  * `@lunora/mcp/docs` — the documentation tool surface: `lunora_search_docs`,
@@ -49,7 +49,7 @@ c as McpResourceSummary,
49
49
  * (`createToolServer`) is exported from the package root, so a consumer using
50
50
  * both entries gets one implementation rather than two copies.
51
51
  */
52
- d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-B_q8q39X.js";
52
+ d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-C_y-E_zk.js";
53
53
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
54
54
  import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
55
55
  /**
@@ -135,7 +135,7 @@ declare const DOCS_SERVER_NAME = "lunora-docs";
135
135
  interface DocsMcpServerOptions {
136
136
  /** The documentation source the tools read. */
137
137
  index: DocsIndex;
138
- /** Largest accepted request body. Defaults to {@link DEFAULT_MAX_REQUEST_BYTES}. */
138
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES`, re-exported at the foot of this module. */
139
139
  maxRequestBytes?: number;
140
140
  /**
141
141
  * Version reported in the handshake. Defaults to `"0.0.0"` — a docs site
@@ -1 +1 @@
1
- import{toDocsSearchHits as e}from"../packem_shared/toDocsSearchHits-CBLmtWXt.mjs";import{DEFAULT_DOCS_BASE_URL as D,createRemoteDocsIndex as t}from"../packem_shared/DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{DOCS_URI_SCHEME as s,docsResources as S,fromDocsUri as E,toDocsUri as I}from"../packem_shared/DOCS_URI_SCHEME-CFANc3vr.mjs";import{DOCS_SERVER_NAME as R,createDocsMcpFetchHandler as p,createDocsMcpServer as A}from"../packem_shared/DOCS_SERVER_NAME-BNYVF715.mjs";import{DEFAULT_SEARCH_LIMIT as M,DOCS_TOOL_DEFINITIONS as O,MAX_SEARCH_LIMIT as T,docsTools as U,normalizeDocUrl as a}from"../packem_shared/DEFAULT_SEARCH_LIMIT-V7pCZ1wt.mjs";export{D as DEFAULT_DOCS_BASE_URL,M as DEFAULT_SEARCH_LIMIT,R as DOCS_SERVER_NAME,O as DOCS_TOOL_DEFINITIONS,s as DOCS_URI_SCHEME,T as MAX_SEARCH_LIMIT,p as createDocsMcpFetchHandler,A as createDocsMcpServer,t as createRemoteDocsIndex,S as docsResources,U as docsTools,E as fromDocsUri,a as normalizeDocUrl,e as toDocsSearchHits,I as toDocsUri};
1
+ import{toDocsSearchHits as e}from"../packem_shared/toDocsSearchHits-CBLmtWXt.mjs";import{DEFAULT_DOCS_BASE_URL as D,createRemoteDocsIndex as t}from"../packem_shared/DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{DOCS_URI_SCHEME as s,docsResources as S,fromDocsUri as E,toDocsUri as I}from"../packem_shared/DOCS_URI_SCHEME-CFANc3vr.mjs";import{DOCS_SERVER_NAME as R,createDocsMcpFetchHandler as p,createDocsMcpServer as A}from"../packem_shared/DOCS_SERVER_NAME-DmmgZtad.mjs";import{DEFAULT_SEARCH_LIMIT as M,DOCS_TOOL_DEFINITIONS as O,MAX_SEARCH_LIMIT as T,docsTools as U,normalizeDocUrl as a}from"../packem_shared/DEFAULT_SEARCH_LIMIT-BqSYN5vr.mjs";export{D as DEFAULT_DOCS_BASE_URL,M as DEFAULT_SEARCH_LIMIT,R as DOCS_SERVER_NAME,O as DOCS_TOOL_DEFINITIONS,s as DOCS_URI_SCHEME,T as MAX_SEARCH_LIMIT,p as createDocsMcpFetchHandler,A as createDocsMcpServer,t as createRemoteDocsIndex,S as docsResources,U as docsTools,E as fromDocsUri,a as normalizeDocUrl,e as toDocsSearchHits,I as toDocsUri};
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { LunoraClient } from '@lunora/client';
2
- import { T as ToolDefinition, f as ToolResult, e as ToolInputSchema, a as McpFetchHandler, b as McpTool } from "./packem_shared/serve-stateless.d-B_q8q39X.mjs";
3
- export { type d as McpServerInfo, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-B_q8q39X.mjs";
2
+ import { T as ToolDefinition, f as ToolResult, e as ToolInputSchema, a as McpFetchHandler, b as McpTool } from "./packem_shared/serve-stateless.d-C_y-E_zk.mjs";
3
+ export { D as DEFAULT_MAX_REQUEST_BYTES, type d as McpServerInfo, type S as ServeStatelessOptions, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-C_y-E_zk.mjs";
4
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
5
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
6
6
  import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
@@ -18,27 +18,28 @@ declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
18
18
  * The tools this server advertises, in three tiers:
19
19
  *
20
20
  * - the read-only surface, always exposed;
21
- * - the observability surface, exposed only when an admin token resolved —
22
- * read-only, but it surfaces production logs and grouped errors, so an
23
- * unauthenticated server must not even advertise that it exists;
21
+ * - the observability surface, exposed only when `allowObservability` is set —
22
+ * read-only, but every row it returns (log lines, request metadata, grouped
23
+ * error messages) is user data that lands in the model's context and therefore
24
+ * at its provider, so it is opt-in rather than implied by holding a token;
24
25
  * - the write surface, exposed only when `allowWrites` is set.
25
26
  *
26
27
  * Both gates OMIT rather than refuse: an AI agent can't invoke what it can't
27
28
  * see. Dispatch re-checks both in {@link callTool}, so the guarantee does not
28
29
  * depend on a client honouring the advertised list.
29
30
  */
30
- declare const toolDefinitions: (allowWrites: boolean, hasAdminToken?: boolean) => ReadonlyArray<ToolDefinition>;
31
+ declare const toolDefinitions: (allowWrites: boolean, allowObservability?: boolean) => ReadonlyArray<ToolDefinition>;
31
32
  /**
32
33
  * Dispatch a tool call against `client`. Unknown tools and thrown errors are
33
34
  * returned as `isError` results (rather than rejections) so the calling model
34
35
  * sees the failure as tool output, per the MCP convention.
35
36
  *
36
- * `allowWrites` gates the mutation/action tools and `hasAdminToken` gates the
37
- * observability tools: when either is false a call to the gated tool is refused
38
- * even if the client somehow names it, so both guarantees hold at dispatch, not
39
- * just in the advertised tool list.
37
+ * `allowWrites` gates the mutation/action tools and `allowObservability` gates
38
+ * the observability tools: when either is false a call to the gated tool is
39
+ * refused even if the client somehow names it, so both guarantees hold at
40
+ * dispatch, not just in the advertised tool list.
40
41
  */
41
- declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, hasAdminToken?: boolean) => Promise<ToolResult>;
42
+ declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, allowObservability?: boolean) => Promise<ToolResult>;
42
43
  /**
43
44
  * Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
44
45
  * MCP tool an external agent can call. The capability boundary is the MCP-server
@@ -115,6 +116,18 @@ interface LunoraMcpServerOptions {
115
116
  * opted in. Only takes effect together with a non-empty `agents` list.
116
117
  */
117
118
  allowAgents?: boolean;
119
+ /**
120
+ * Expose the observability tools (`lunora_get_logs`, `lunora_get_issues`,
121
+ * `lunora_get_advisories`, `lunora_get_query_insights`,
122
+ * `lunora_get_migration_status`). Defaults to `false`, mirroring
123
+ * `allowWrites`: they are read-only, but every row they return — log lines,
124
+ * request metadata, grouped error messages — is production user data that
125
+ * lands in the model's context and therefore at its provider. Holding the
126
+ * admin bearer is not consent to ship that, so it is a separate opt-in;
127
+ * without it the tools are omitted from the advertised list AND refused at
128
+ * dispatch. Only takes effect when a `token` resolved.
129
+ */
130
+ allowObservability?: boolean;
118
131
  /**
119
132
  * Expose the write tools (`lunora_run_mutation` / `lunora_run_action`).
120
133
  * Defaults to `false`: the server is READ-ONLY unless explicitly opted in,
@@ -202,6 +215,8 @@ type McpAuthProtect = (handler: (request: Request, claims: McpAccessTokenClaims)
202
215
  /** Server options, or a function deriving them from the request's verified claims. */
203
216
  type AuthedMcpServerOptions = ((claims: McpAccessTokenClaims) => LunoraMcpServerOptions | Promise<LunoraMcpServerOptions>) | LunoraMcpServerOptions;
204
217
  interface AuthedMcpFetchHandlerOptions {
218
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES` (128 KiB). */
219
+ maxRequestBytes?: number;
205
220
  /**
206
221
  * The OAuth gate to mount the MCP server behind. Pass
207
222
  * `(handler) => requireMcpAuth(auth, handler, opts)` from
@@ -234,14 +249,32 @@ declare const mcpTokenScopes: (claims: McpAccessTokenClaims) => ReadonlySet<stri
234
249
  * `server` (resolved against the verified claims) and serves it through
235
250
  * {@link serveStateless}, exactly as the unprotected `createMcpFetchHandler`
236
251
  * does — the transport behaviour is identical, only the gate is new.
252
+ *
253
+ * A fixed `server` object names one deployment, so its `LunoraClient` is
254
+ * resolved once and shared: the public-function registry memo in `./tools` is
255
+ * keyed by client identity and never hits when each request builds its own. The
256
+ * `(claims) => …` form is per-request by construction — the claims decide which
257
+ * deployment and token to use — so it keeps a client per request.
237
258
  */
238
259
  declare const createAuthedMcpFetchHandler: (options: AuthedMcpFetchHandlerOptions) => McpFetchHandler;
260
+ /** {@link createMcpFetchHandler} options: the server's, plus this transport's body limit. */
261
+ interface McpFetchHandlerOptions extends LunoraMcpServerOptions {
262
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES` (128 KiB). */
263
+ maxRequestBytes?: number;
264
+ }
239
265
  /**
240
266
  * Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
241
267
  * invocation constructs a fresh proxy server and serves the request through
242
268
  * {@link serveStateless}.
269
+ *
270
+ * The `LunoraClient` is resolved ONCE, here, and shared by every request: it is
271
+ * the same deployment on each one, and the public-function registry memo in
272
+ * `./tools` is keyed by client identity, so a per-request client would re-fetch
273
+ * that registry on every tool call. A misconfiguration (`url` without `token`)
274
+ * therefore throws when the handler is built rather than on first request,
275
+ * which is where `createLunoraMcpServer` already documents reporting it.
243
276
  */
244
- declare const createMcpFetchHandler: (options: LunoraMcpServerOptions) => McpFetchHandler;
277
+ declare const createMcpFetchHandler: (options: McpFetchHandlerOptions) => McpFetchHandler;
245
278
  /** A Lunora deployment the tools dispatch against. */
246
279
  interface LocalDeployment {
247
280
  token?: string;
@@ -362,16 +395,39 @@ type PaidMcpChargeConfig = X402ChargeSettings;
362
395
  interface PaidMcpServerConfig {
363
396
  /** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
364
397
  charge: PaidMcpChargeConfig;
398
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES` (128 KiB). */
399
+ maxRequestBytes?: number;
365
400
  /** Name/version advertised in the MCP `initialize` handshake. Defaults to `lunora-paid-mcp`. */
366
401
  serverInfo?: {
367
402
  name: string;
368
403
  version: string;
369
404
  };
370
405
  }
406
+ /**
407
+ * The Worker execution context, as this module reads it: only `waitUntil`, and
408
+ * structurally, so the package takes no `@cloudflare/workers-types` dependency.
409
+ */
410
+ interface PaidMcpExecutionContext {
411
+ waitUntil?: (promise: Promise<unknown>) => void;
412
+ }
413
+ /**
414
+ * The paid server's fetch handler.
415
+ *
416
+ * Unlike the free `McpFetchHandler` it takes the Worker's full
417
+ * `(request, env, ctx)` triple — the shape `export default { fetch }` is called
418
+ * with — because the x402 receipt sink NEEDS `ctx.waitUntil`: work that is
419
+ * neither awaited into the response nor registered with it is cancelled when the
420
+ * request ends, so an async `onReceipt` (inserting the settled payment into a
421
+ * durable table) frequently never runs while the money has already moved
422
+ * on-chain. `env` is accepted and ignored so the handler drops straight into the
423
+ * default export; both are optional, so a non-Workers caller may still invoke it
424
+ * with a bare `Request` (the middleware then simply sees no `waitUntil`).
425
+ */
426
+ type PaidMcpFetchHandler = (request: Request, env?: unknown, context?: PaidMcpExecutionContext) => Promise<Response>;
371
427
  /** A paid MCP server: register free/paid tools, then serve over Streamable HTTP. */
372
428
  interface PaidMcpServer {
373
429
  /** The Streamable-HTTP fetch handler; gates each paid `tools/call` behind x402. */
374
- readonly fetchHandler: McpFetchHandler;
430
+ readonly fetchHandler: PaidMcpFetchHandler;
375
431
  /** Register a **paid** tool: its dispatch runs the x402 charge middleware first. */
376
432
  paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
377
433
  /** Register a **free** tool (coexists with paid tools on the same server). */
@@ -401,8 +457,9 @@ export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type AuthedMcpFetchHand
401
457
  * writes are enabled), each backed by `LunoraClient` over HTTP RPC. It also
402
458
  * exposes the deployment's observability reads (`lunora_get_logs`,
403
459
  * `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
404
- * `lunora_get_migration_status`) whenever an admin token resolved — read-only,
405
- * but privileged, so they are omitted entirely without one. The server is
460
+ * `lunora_get_migration_status`) when `allowObservability` (or the
461
+ * LUNORA_MCP_ALLOW_OBSERVABILITY env) is set — read-only, but they return
462
+ * production user data, so they are omitted entirely without it. The server is
406
463
  * read-only by default — the write tools are exposed only when `allowWrites`
407
464
  * (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
408
465
  * allowlisted against the deployment's discovered public functions. It can also
@@ -432,8 +489,9 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
432
489
  * writes are enabled), each backed by `LunoraClient` over HTTP RPC. It also
433
490
  * exposes the deployment's observability reads (`lunora_get_logs`,
434
491
  * `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
435
- * `lunora_get_migration_status`) whenever an admin token resolved — read-only,
436
- * but privileged, so they are omitted entirely without one. The server is
492
+ * `lunora_get_migration_status`) when `allowObservability` (or the
493
+ * LUNORA_MCP_ALLOW_OBSERVABILITY env) is set — read-only, but they return
494
+ * production user data, so they are omitted entirely without it. The server is
437
495
  * read-only by default — the write tools are exposed only when `allowWrites`
438
496
  * (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
439
497
  * allowlisted against the deployment's discovered public functions. It can also
@@ -451,4 +509,4 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
451
509
  * better-auth's MCP OAuth gate (`requireMcpAuth` from `@lunora/auth/plugins`)
452
510
  * and can scope tool exposure to the access token's own scopes.
453
511
  */
454
- type McpAgentExposure, type McpAuthProtect, type McpFetchHandler, type McpTool, NO_DEPLOYMENT_MESSAGE, OBSERVABILITY_TOOL_DEFINITIONS, type PaidMcpChargeConfig, type PaidMcpServer, type PaidMcpServerConfig, READ_ONLY_TOOL_DEFINITIONS, type RegisterPaidToolOptions, type RegisterToolOptions, type ToolDefinition, type ToolHandler, type ToolInputSchema, type ToolResult, WRITE_TOOL_DEFINITIONS, agentToolDefinitions, callAgentTool, callTool, connectLocalStdio, connectStdio, createAuthedMcpFetchHandler, createLocalMcpServer, createLunoraMcpServer, createMcpFetchHandler, createPaidMcpServer, localTools, mcpTokenScopes, parseAgentsEnv, toolDefinitions };
512
+ type McpAgentExposure, type McpAuthProtect, type McpFetchHandler, type McpFetchHandlerOptions, type McpTool, NO_DEPLOYMENT_MESSAGE, 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, agentToolDefinitions, callAgentTool, callTool, connectLocalStdio, connectStdio, createAuthedMcpFetchHandler, createLocalMcpServer, createLunoraMcpServer, createMcpFetchHandler, createPaidMcpServer, localTools, mcpTokenScopes, parseAgentsEnv, toolDefinitions };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { LunoraClient } from '@lunora/client';
2
- import { T as ToolDefinition, f as ToolResult, e as ToolInputSchema, a as McpFetchHandler, b as McpTool } from "./packem_shared/serve-stateless.d-B_q8q39X.js";
3
- export { type d as McpServerInfo, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-B_q8q39X.js";
2
+ import { T as ToolDefinition, f as ToolResult, e as ToolInputSchema, a as McpFetchHandler, b as McpTool } from "./packem_shared/serve-stateless.d-C_y-E_zk.js";
3
+ export { D as DEFAULT_MAX_REQUEST_BYTES, type d as McpServerInfo, type S as ServeStatelessOptions, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-C_y-E_zk.js";
4
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
5
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
6
6
  import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
@@ -18,27 +18,28 @@ declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
18
18
  * The tools this server advertises, in three tiers:
19
19
  *
20
20
  * - the read-only surface, always exposed;
21
- * - the observability surface, exposed only when an admin token resolved —
22
- * read-only, but it surfaces production logs and grouped errors, so an
23
- * unauthenticated server must not even advertise that it exists;
21
+ * - the observability surface, exposed only when `allowObservability` is set —
22
+ * read-only, but every row it returns (log lines, request metadata, grouped
23
+ * error messages) is user data that lands in the model's context and therefore
24
+ * at its provider, so it is opt-in rather than implied by holding a token;
24
25
  * - the write surface, exposed only when `allowWrites` is set.
25
26
  *
26
27
  * Both gates OMIT rather than refuse: an AI agent can't invoke what it can't
27
28
  * see. Dispatch re-checks both in {@link callTool}, so the guarantee does not
28
29
  * depend on a client honouring the advertised list.
29
30
  */
30
- declare const toolDefinitions: (allowWrites: boolean, hasAdminToken?: boolean) => ReadonlyArray<ToolDefinition>;
31
+ declare const toolDefinitions: (allowWrites: boolean, allowObservability?: boolean) => ReadonlyArray<ToolDefinition>;
31
32
  /**
32
33
  * Dispatch a tool call against `client`. Unknown tools and thrown errors are
33
34
  * returned as `isError` results (rather than rejections) so the calling model
34
35
  * sees the failure as tool output, per the MCP convention.
35
36
  *
36
- * `allowWrites` gates the mutation/action tools and `hasAdminToken` gates the
37
- * observability tools: when either is false a call to the gated tool is refused
38
- * even if the client somehow names it, so both guarantees hold at dispatch, not
39
- * just in the advertised tool list.
37
+ * `allowWrites` gates the mutation/action tools and `allowObservability` gates
38
+ * the observability tools: when either is false a call to the gated tool is
39
+ * refused even if the client somehow names it, so both guarantees hold at
40
+ * dispatch, not just in the advertised tool list.
40
41
  */
41
- declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, hasAdminToken?: boolean) => Promise<ToolResult>;
42
+ declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, allowObservability?: boolean) => Promise<ToolResult>;
42
43
  /**
43
44
  * Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
44
45
  * MCP tool an external agent can call. The capability boundary is the MCP-server
@@ -115,6 +116,18 @@ interface LunoraMcpServerOptions {
115
116
  * opted in. Only takes effect together with a non-empty `agents` list.
116
117
  */
117
118
  allowAgents?: boolean;
119
+ /**
120
+ * Expose the observability tools (`lunora_get_logs`, `lunora_get_issues`,
121
+ * `lunora_get_advisories`, `lunora_get_query_insights`,
122
+ * `lunora_get_migration_status`). Defaults to `false`, mirroring
123
+ * `allowWrites`: they are read-only, but every row they return — log lines,
124
+ * request metadata, grouped error messages — is production user data that
125
+ * lands in the model's context and therefore at its provider. Holding the
126
+ * admin bearer is not consent to ship that, so it is a separate opt-in;
127
+ * without it the tools are omitted from the advertised list AND refused at
128
+ * dispatch. Only takes effect when a `token` resolved.
129
+ */
130
+ allowObservability?: boolean;
118
131
  /**
119
132
  * Expose the write tools (`lunora_run_mutation` / `lunora_run_action`).
120
133
  * Defaults to `false`: the server is READ-ONLY unless explicitly opted in,
@@ -202,6 +215,8 @@ type McpAuthProtect = (handler: (request: Request, claims: McpAccessTokenClaims)
202
215
  /** Server options, or a function deriving them from the request's verified claims. */
203
216
  type AuthedMcpServerOptions = ((claims: McpAccessTokenClaims) => LunoraMcpServerOptions | Promise<LunoraMcpServerOptions>) | LunoraMcpServerOptions;
204
217
  interface AuthedMcpFetchHandlerOptions {
218
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES` (128 KiB). */
219
+ maxRequestBytes?: number;
205
220
  /**
206
221
  * The OAuth gate to mount the MCP server behind. Pass
207
222
  * `(handler) => requireMcpAuth(auth, handler, opts)` from
@@ -234,14 +249,32 @@ declare const mcpTokenScopes: (claims: McpAccessTokenClaims) => ReadonlySet<stri
234
249
  * `server` (resolved against the verified claims) and serves it through
235
250
  * {@link serveStateless}, exactly as the unprotected `createMcpFetchHandler`
236
251
  * does — the transport behaviour is identical, only the gate is new.
252
+ *
253
+ * A fixed `server` object names one deployment, so its `LunoraClient` is
254
+ * resolved once and shared: the public-function registry memo in `./tools` is
255
+ * keyed by client identity and never hits when each request builds its own. The
256
+ * `(claims) => …` form is per-request by construction — the claims decide which
257
+ * deployment and token to use — so it keeps a client per request.
237
258
  */
238
259
  declare const createAuthedMcpFetchHandler: (options: AuthedMcpFetchHandlerOptions) => McpFetchHandler;
260
+ /** {@link createMcpFetchHandler} options: the server's, plus this transport's body limit. */
261
+ interface McpFetchHandlerOptions extends LunoraMcpServerOptions {
262
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES` (128 KiB). */
263
+ maxRequestBytes?: number;
264
+ }
239
265
  /**
240
266
  * Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
241
267
  * invocation constructs a fresh proxy server and serves the request through
242
268
  * {@link serveStateless}.
269
+ *
270
+ * The `LunoraClient` is resolved ONCE, here, and shared by every request: it is
271
+ * the same deployment on each one, and the public-function registry memo in
272
+ * `./tools` is keyed by client identity, so a per-request client would re-fetch
273
+ * that registry on every tool call. A misconfiguration (`url` without `token`)
274
+ * therefore throws when the handler is built rather than on first request,
275
+ * which is where `createLunoraMcpServer` already documents reporting it.
243
276
  */
244
- declare const createMcpFetchHandler: (options: LunoraMcpServerOptions) => McpFetchHandler;
277
+ declare const createMcpFetchHandler: (options: McpFetchHandlerOptions) => McpFetchHandler;
245
278
  /** A Lunora deployment the tools dispatch against. */
246
279
  interface LocalDeployment {
247
280
  token?: string;
@@ -362,16 +395,39 @@ type PaidMcpChargeConfig = X402ChargeSettings;
362
395
  interface PaidMcpServerConfig {
363
396
  /** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
364
397
  charge: PaidMcpChargeConfig;
398
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES` (128 KiB). */
399
+ maxRequestBytes?: number;
365
400
  /** Name/version advertised in the MCP `initialize` handshake. Defaults to `lunora-paid-mcp`. */
366
401
  serverInfo?: {
367
402
  name: string;
368
403
  version: string;
369
404
  };
370
405
  }
406
+ /**
407
+ * The Worker execution context, as this module reads it: only `waitUntil`, and
408
+ * structurally, so the package takes no `@cloudflare/workers-types` dependency.
409
+ */
410
+ interface PaidMcpExecutionContext {
411
+ waitUntil?: (promise: Promise<unknown>) => void;
412
+ }
413
+ /**
414
+ * The paid server's fetch handler.
415
+ *
416
+ * Unlike the free `McpFetchHandler` it takes the Worker's full
417
+ * `(request, env, ctx)` triple — the shape `export default { fetch }` is called
418
+ * with — because the x402 receipt sink NEEDS `ctx.waitUntil`: work that is
419
+ * neither awaited into the response nor registered with it is cancelled when the
420
+ * request ends, so an async `onReceipt` (inserting the settled payment into a
421
+ * durable table) frequently never runs while the money has already moved
422
+ * on-chain. `env` is accepted and ignored so the handler drops straight into the
423
+ * default export; both are optional, so a non-Workers caller may still invoke it
424
+ * with a bare `Request` (the middleware then simply sees no `waitUntil`).
425
+ */
426
+ type PaidMcpFetchHandler = (request: Request, env?: unknown, context?: PaidMcpExecutionContext) => Promise<Response>;
371
427
  /** A paid MCP server: register free/paid tools, then serve over Streamable HTTP. */
372
428
  interface PaidMcpServer {
373
429
  /** The Streamable-HTTP fetch handler; gates each paid `tools/call` behind x402. */
374
- readonly fetchHandler: McpFetchHandler;
430
+ readonly fetchHandler: PaidMcpFetchHandler;
375
431
  /** Register a **paid** tool: its dispatch runs the x402 charge middleware first. */
376
432
  paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
377
433
  /** Register a **free** tool (coexists with paid tools on the same server). */
@@ -401,8 +457,9 @@ export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type AuthedMcpFetchHand
401
457
  * writes are enabled), each backed by `LunoraClient` over HTTP RPC. It also
402
458
  * exposes the deployment's observability reads (`lunora_get_logs`,
403
459
  * `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
404
- * `lunora_get_migration_status`) whenever an admin token resolved — read-only,
405
- * but privileged, so they are omitted entirely without one. The server is
460
+ * `lunora_get_migration_status`) when `allowObservability` (or the
461
+ * LUNORA_MCP_ALLOW_OBSERVABILITY env) is set — read-only, but they return
462
+ * production user data, so they are omitted entirely without it. The server is
406
463
  * read-only by default — the write tools are exposed only when `allowWrites`
407
464
  * (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
408
465
  * allowlisted against the deployment's discovered public functions. It can also
@@ -432,8 +489,9 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
432
489
  * writes are enabled), each backed by `LunoraClient` over HTTP RPC. It also
433
490
  * exposes the deployment's observability reads (`lunora_get_logs`,
434
491
  * `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
435
- * `lunora_get_migration_status`) whenever an admin token resolved — read-only,
436
- * but privileged, so they are omitted entirely without one. The server is
492
+ * `lunora_get_migration_status`) when `allowObservability` (or the
493
+ * LUNORA_MCP_ALLOW_OBSERVABILITY env) is set — read-only, but they return
494
+ * production user data, so they are omitted entirely without it. The server is
437
495
  * read-only by default — the write tools are exposed only when `allowWrites`
438
496
  * (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
439
497
  * allowlisted against the deployment's discovered public functions. It can also
@@ -451,4 +509,4 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
451
509
  * better-auth's MCP OAuth gate (`requireMcpAuth` from `@lunora/auth/plugins`)
452
510
  * and can scope tool exposure to the access token's own scopes.
453
511
  */
454
- type McpAgentExposure, type McpAuthProtect, type McpFetchHandler, type McpTool, NO_DEPLOYMENT_MESSAGE, OBSERVABILITY_TOOL_DEFINITIONS, type PaidMcpChargeConfig, type PaidMcpServer, type PaidMcpServerConfig, READ_ONLY_TOOL_DEFINITIONS, type RegisterPaidToolOptions, type RegisterToolOptions, type ToolDefinition, type ToolHandler, type ToolInputSchema, type ToolResult, WRITE_TOOL_DEFINITIONS, agentToolDefinitions, callAgentTool, callTool, connectLocalStdio, connectStdio, createAuthedMcpFetchHandler, createLocalMcpServer, createLunoraMcpServer, createMcpFetchHandler, createPaidMcpServer, localTools, mcpTokenScopes, parseAgentsEnv, toolDefinitions };
512
+ type McpAgentExposure, type McpAuthProtect, type McpFetchHandler, type McpFetchHandlerOptions, type McpTool, NO_DEPLOYMENT_MESSAGE, 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, agentToolDefinitions, callAgentTool, callTool, connectLocalStdio, connectStdio, createAuthedMcpFetchHandler, createLocalMcpServer, createLunoraMcpServer, createMcpFetchHandler, createPaidMcpServer, localTools, mcpTokenScopes, parseAgentsEnv, toolDefinitions };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as c,callAgentTool as T,parseAgentsEnv as a}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-DGpNvk5K.mjs";import{createAuthedMcpFetchHandler as E,mcpTokenScopes as S}from"./packem_shared/createAuthedMcpFetchHandler-LLzMY9SW.mjs";import{createToolServer as O}from"./packem_shared/createToolServer-BtGuPyMU.mjs";import{createMcpFetchHandler as n}from"./packem_shared/createMcpFetchHandler-B9koGwkW.mjs";import{LOCAL_SERVER_NAME as A,NO_DEPLOYMENT_MESSAGE as I,connectLocalStdio as f,createLocalMcpServer as L,localTools as m}from"./packem_shared/LOCAL_SERVER_NAME-BwlBnhX-.mjs";import{createPaidMcpServer as x}from"./packem_shared/createPaidMcpServer-DyNwdCq2.mjs";import{connectStdio as i,createLunoraMcpServer as D}from"./packem_shared/connectStdio-CDsCt1IT.mjs";import{READ_ONLY_TOOL_DEFINITIONS as v,WRITE_TOOL_DEFINITIONS as R,callTool as F,toolDefinitions as g}from"./packem_shared/READ_ONLY_TOOL_DEFINITIONS-aD-b7Hw2.mjs";import{O as G}from"./packem_shared/observability-tools-Ce0YK58T.mjs";import{serveStateless as P}from"./packem_shared/serveStateless-Db7J3pAO.mjs";export{r as AGENT_RUN_INPUT_SCHEMA,t as AGENT_STATUS_TOOL_NAME,A as LOCAL_SERVER_NAME,I as NO_DEPLOYMENT_MESSAGE,G as OBSERVABILITY_TOOL_DEFINITIONS,v as READ_ONLY_TOOL_DEFINITIONS,R as WRITE_TOOL_DEFINITIONS,c as agentToolDefinitions,T as callAgentTool,F as callTool,f as connectLocalStdio,i as connectStdio,E as createAuthedMcpFetchHandler,L as createLocalMcpServer,D as createLunoraMcpServer,n as createMcpFetchHandler,x as createPaidMcpServer,O as createToolServer,m as localTools,S as mcpTokenScopes,a as parseAgentsEnv,P as serveStateless,g as toolDefinitions};
1
+ import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as c,callAgentTool as T,parseAgentsEnv as E}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{createAuthedMcpFetchHandler as _,mcpTokenScopes as a}from"./packem_shared/createAuthedMcpFetchHandler-CmVUae-x.mjs";import{createToolServer as l}from"./packem_shared/createToolServer-BtGuPyMU.mjs";import{createMcpFetchHandler as n}from"./packem_shared/createMcpFetchHandler-BJKPn555.mjs";import{LOCAL_SERVER_NAME as N,NO_DEPLOYMENT_MESSAGE as I,connectLocalStdio as L,createLocalMcpServer as f,localTools as m}from"./packem_shared/LOCAL_SERVER_NAME-Do7MU6Kd.mjs";import{createPaidMcpServer as s}from"./packem_shared/createPaidMcpServer-CQrzMUhI.mjs";import{connectStdio as i,createLunoraMcpServer as D}from"./packem_shared/connectStdio-BBtfW4UB.mjs";import{READ_ONLY_TOOL_DEFINITIONS as d,WRITE_TOOL_DEFINITIONS as v,callTool as F,toolDefinitions as U}from"./packem_shared/READ_ONLY_TOOL_DEFINITIONS-_aG40yWx.mjs";import{DEFAULT_MAX_REQUEST_BYTES as g,serveStateless as h}from"./packem_shared/DEFAULT_MAX_REQUEST_BYTES-B8WntTxM.mjs";import{O as G}from"./packem_shared/observability-tools-B-g9Y9IT.mjs";export{r as AGENT_RUN_INPUT_SCHEMA,t as AGENT_STATUS_TOOL_NAME,g as DEFAULT_MAX_REQUEST_BYTES,N as LOCAL_SERVER_NAME,I as NO_DEPLOYMENT_MESSAGE,G as OBSERVABILITY_TOOL_DEFINITIONS,d as READ_ONLY_TOOL_DEFINITIONS,v as WRITE_TOOL_DEFINITIONS,c as agentToolDefinitions,T as callAgentTool,F as callTool,L as connectLocalStdio,i as connectStdio,_ as createAuthedMcpFetchHandler,f as createLocalMcpServer,D as createLunoraMcpServer,n as createMcpFetchHandler,s as createPaidMcpServer,l as createToolServer,m as localTools,a as mcpTokenScopes,E as parseAgentsEnv,h as serveStateless,U as toolDefinitions};
@@ -0,0 +1 @@
1
+ const b="agents:agentRun",T="agents:agentThread",H="agents:agentMessages",p="lunora_agent_status",_=new Set(["awaiting_input","cancelled","error","idle"]),O=6e4,x=600,P={properties:{prompt:{description:"The task or message for the agent.",type:"string"},threadKey:{description:"Reuse to continue a conversation; omit to start a new thread.",type:"string"},title:{description:"Optional thread title (first run only).",type:"string"}},required:["prompt"],type:"object"},U={properties:{threadKey:{description:"The thread key returned by an agent tool call.",type:"string"}},required:["threadKey"],type:"object"},g=t=>t.toolName??`agent_${t.name}`,C=t=>{if(t===void 0)return[];const n=[];for(const e of t.split(";")){const r=e.trim();if(r.length===0)continue;const o=r.indexOf(":");if(o<=0)continue;const a=r.slice(0,o).trim(),s=r.slice(o+1).trim();a.length===0||s.length===0||n.push({description:s,name:a})}return n},L={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},K=(t,n)=>n!==!0||t.length===0?[]:[...t.map(r=>({annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:`Run the ${r.name} agent (starts a durable run)`},description:`${r.description} Starts a durable agent run and returns its final answer.`,inputSchema:P,name:g(r)})),{annotations:{...L,title:"Check a durable agent run"},description:"Check the status of a durable agent run (and its answer if finished) by its threadKey.",inputSchema:U,name:p}],G=(t,n)=>t===p||n.some(e=>g(e)===t),R=()=>`mcp-${crypto.randomUUID()}`,d=t=>({__lunoraRef:t}),k=async t=>{await new Promise(n=>{setTimeout(n,t)})},c=t=>({content:[{text:JSON.stringify(t,void 0,2),type:"text"}]}),i=t=>({content:[{text:t,type:"text"}],isError:!0}),l=(t,n)=>{const e=t[n];return typeof e=="string"&&e.length>0?e:void 0},A=t=>{for(let n=t.length-1;n>=0;n-=1){const e=t[n],r=e?.toolCalls,o=Array.isArray(r)&&r.length>0;if(e?.role==="assistant"&&!o)return typeof e.content=="string"?e.content:""}return""},w=t=>t!==null&&typeof t=="object"&&typeof t.status=="string"?t.status:"unknown",S=async(t,n,e,r)=>{const o=await t.query(d(H),{key:n});if(e==="error"){const a=r!==null&&typeof r=="object"?r.error:void 0;return c({error:typeof a=="string"?a:"the agent run failed",status:e,threadKey:n})}return c(e==="awaiting_input"?{hint:"This run is paused on a human-in-the-loop tool approval. Approve or reject it in the app that owns the agent; MCP cannot supply the input. Poll lunora_agent_status with this threadKey afterwards.",status:e,text:A(o),threadKey:n}:{status:e,text:A(o),threadKey:n})},I=async(t,n)=>{const e=l(n,"threadKey");if(e===void 0)return i('"threadKey" is required and must be a non-empty string');const r=await t.query(d(T),{key:e}),o=w(r);return _.has(o)?S(t,e,o,r):c({status:o==="unknown"?"running":o,threadKey:e})},q=async(t,n,e,r)=>{try{if(r.allowAgents!==!0)return i(`tool "${n}" is disabled: agent tools are off. Enable them with the LUNORA_MCP_ALLOW_AGENTS env var.`);if(n===p)return await I(t,e);const o=r.exposures.find(u=>g(u)===n);if(o===void 0)return i(`agent tool "${n}" is not exposed by this MCP server`);const a=l(e,"prompt");if(a===void 0)return i('"prompt" is required and must be a non-empty string');const s=l(e,"threadKey")??R(),h=l(e,"title"),{id:E}=await t.mutation(d(b),{agent:o.name,input:a,threadKey:s,...h===void 0?{}:{title:h}}),f=r.pollIntervalMs??x,N=r.maxWaitMs??O,v=r.wait??k,M=Math.max(1,Math.ceil(N/f));for(let u=0;u<M;u+=1){const y=await t.query(d(T),{key:s}),m=w(y);if(_.has(m))return await S(t,s,m,y);await v(f)}return c({hint:"call lunora_agent_status with this threadKey to poll for the answer",runId:E,status:"running",threadKey:s})}catch(o){const a=o instanceof Error?o.message:String(o);return i(a)}};export{P as AGENT_RUN_INPUT_SCHEMA,p as AGENT_STATUS_TOOL_NAME,K as agentToolDefinitions,q as callAgentTool,A as finalAnswer,G as isAgentToolName,C as parseAgentsEnv};
@@ -0,0 +1 @@
1
+ import{WebStandardStreamableHTTPServerTransport as c}from"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";const y=128*1024,i=e=>new TextEncoder().encode(e).length,o=(e,s,r)=>Response.json({error:{code:s,message:r},id:null,jsonrpc:"2.0"},{status:e}),d=()=>o(400,-32600,"batched requests are not supported: send one JSON-RPC message per request"),p=async(e,s)=>{if(e.method!=="POST")return{parsedBody:void 0};const r=s??y,t={response:o(413,-32600,`request body exceeds ${String(r)} bytes`)},n=Number(e.headers.get("content-length"));if(Number.isFinite(n)&&n>r)return t;const a=await e.text();if(i(a)>r)return t;try{return{parsedBody:JSON.parse(a)}}catch{return{response:o(400,-32700,"parse error: body is not valid JSON")}}},u=async(e,s)=>{if(e.method!=="POST")return{parsedBody:s?.parsedBody};if(s?.parsedBody!==void 0)return Array.isArray(s.parsedBody)?{response:d()}:{parsedBody:s.parsedBody};const r=await p(e,s?.maxRequestBytes);return"response"in r?r:Array.isArray(r.parsedBody)?{response:d()}:r},l=async(e,s,r)=>{const t=await u(s,r);if("response"in t)return t.response;const n=new c({enableJsonResponse:!0,sessionIdGenerator:void 0});try{return await e.connect(n),await n.handleRequest(s,{authInfo:r?.authInfo,parsedBody:t.parsedBody})}finally{n.close().catch(()=>{}),e.close().catch(()=>{})}};export{y as DEFAULT_MAX_REQUEST_BYTES,p as readScreenedBody,l as serveStateless};
@@ -0,0 +1,3 @@
1
+ const T=10,A=50,h="docs",L=500,_={properties:{limit:{description:`Maximum hits to return (default ${String(10)}, max ${String(50)})`,type:"number"},query:{description:'Search terms, e.g. "shardBy" or "optimistic updates"',type:"string"}},required:["query"],type:"object"},m={properties:{url:{description:'Site-relative page URL from a search hit, e.g. "/docs/sharding"',type:"string"}},required:["url"],type:"object"},g={properties:{},type:"object"},s={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},u={annotations:{...s,title:"Search the Lunora documentation"},description:"Search the Lunora documentation and return matching pages and sections. Use this before writing Lunora code (schema, queries, mutations, actions, sharding, .global(), client hooks) so the answer reflects the framework's current API rather than a guess. Follow a hit with lunora_get_doc to read the full page.",inputSchema:_,name:"lunora_search_docs"},l={annotations:{...s,title:"Read a Lunora documentation page"},description:"Return one Lunora documentation page in full, as Markdown. Takes the `url` of a lunora_search_docs hit or a lunora_list_docs entry.",inputSchema:m,name:"lunora_get_doc"},d={annotations:{...s,title:"List the Lunora documentation pages"},description:"List every Lunora documentation page with its title and description. Prefer lunora_search_docs when you know what you're looking for.",inputSchema:g,name:"lunora_list_docs"},y=[u,l,d],r=n=>({content:[{text:JSON.stringify(n,void 0,2),type:"text"}]}),p=n=>({content:[{text:n,type:"text"}],isError:!0}),a=512,c=(n,t)=>{const e=n[t];if(typeof e!="string"||e.trim().length===0)throw new TypeError(`"${t}" is required and must be a non-empty string`);if(e.length>a)throw new RangeError(`"${t}" must be at most ${String(a)} characters`);return e.trim()},f=n=>{const t=typeof n=="string"?Number(n):n;return typeof t!="number"||!Number.isFinite(t)?10:Math.min(50,Math.max(1,Math.floor(t)))},S=(n,t)=>{let e;try{e=decodeURIComponent(n)}catch{throw new RangeError(`"url" contains a malformed percent-escape: ${t}`)}if(e.includes("%")||e.includes("\\"))throw new RangeError(`"url" must not contain ".." or encoded segments: ${t}`);for(const o of e.split("/"))if(o===".."||o===".")throw new RangeError(`"url" must not contain ".." or encoded segments: ${t}`)},E=n=>{let t=n.trim().replaceAll("\\","/");if(t.startsWith("http://")||t.startsWith("https://")){const e=t.slice(t.indexOf("//")+2),o=e.indexOf("/");t=o===-1?"/":e.slice(o)}for(const e of["?","#"]){const o=t.indexOf(e);o!==-1&&(t=t.slice(0,o))}for(;t.endsWith("/")&&t.length>1;)t=t.slice(0,-1);return t.startsWith("/")||(t=t.startsWith("docs/")?`/${t}`:`/${h}/${t}`),S(t,n),t},O=n=>[{definition:u,handle:async t=>{const e=c(t,"query"),o=f(t.limit),i=(await n.search(e)).slice(0,o);return i.length===0?r({hits:[],note:`no documentation matched "${e}" — try fewer or more general terms, or lunora_list_docs to browse`}):r({hits:i})}},{definition:l,handle:async t=>{const e=E(c(t,"url")),o=await n.getPage(e);return o===void 0?p(`documentation page not found: ${e}. Use lunora_search_docs or lunora_list_docs to find a valid url.`):{content:[{text:`# ${o.title} (${o.url})
2
+
3
+ ${o.content}`,type:"text"}]}}},{definition:d,handle:async()=>{const t=await n.listPages();return t.length>500?r({note:`showing the first ${String(500)} of ${String(t.length)} pages — use lunora_search_docs to find the rest`,pages:t.slice(0,500)}):r(t)}}];export{T as DEFAULT_SEARCH_LIMIT,y as DOCS_TOOL_DEFINITIONS,a as MAX_ARGUMENT_LENGTH,L as MAX_LISTED_PAGES,A as MAX_SEARCH_LIMIT,O as docsTools,E as normalizeDocUrl};
@@ -0,0 +1 @@
1
+ import{createToolServer as o}from"./createToolServer-BtGuPyMU.mjs";import{serveStateless as s}from"./DEFAULT_MAX_REQUEST_BYTES-B8WntTxM.mjs";import{DEFAULT_MAX_REQUEST_BYTES as i}from"./DEFAULT_MAX_REQUEST_BYTES-B8WntTxM.mjs";import{docsResources as t}from"./DOCS_URI_SCHEME-CFANc3vr.mjs";import{docsTools as c}from"./DEFAULT_SEARCH_LIMIT-BqSYN5vr.mjs";const m="lunora-docs",n=e=>({name:m,version:e??"0.0.0"}),a=e=>o(n(e.version),c(e.index),t(e.index)),v=e=>r=>s(a(e),r,{maxRequestBytes:e.maxRequestBytes});export{i as DEFAULT_MAX_REQUEST_BYTES,m as DOCS_SERVER_NAME,v as createDocsMcpFetchHandler,a as createDocsMcpServer};
@@ -1 +1 @@
1
- import{LunoraClient as f}from"@lunora/client";import{StdioServerTransport as p}from"@modelcontextprotocol/sdk/server/stdio.js";import{createToolServer as m}from"./createToolServer-BtGuPyMU.mjs";import{createRemoteDocsIndex as v}from"./DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{docsResources as h}from"./DOCS_URI_SCHEME-CFANc3vr.mjs";import{docsTools as y}from"./DEFAULT_SEARCH_LIMIT-V7pCZ1wt.mjs";import{toolDefinitions as E,callTool as R}from"./READ_ONLY_TOOL_DEFINITIONS-aD-b7Hw2.mjs";const O=(e,n)=>{if(e.size<n)return;const t=e.keys().next().value;t!==void 0&&e.delete(t)},l=e=>e.docs===!1?void 0:v({...e.docs?.baseUrl===void 0?{}:{baseUrl:e.docs.baseUrl},...e.fetch===void 0?{}:{fetch:e.fetch}}),_="lunora",S="no Lunora dev server is running for this project — start one with `lunora dev`, then call this tool again (call lunora_dev_status to check).",T=8,d=e=>{const n=new Map;return t=>{const r=JSON.stringify([t.url,t.token??""]),c=n.get(r);if(c!==void 0)return c;const o=new f({fetch:e,url:t.url});return t.token!==void 0&&t.token.length>0&&o.setAuthToken(t.token),O(n,T),n.set(r,o),o}},i=e=>e?.token!==void 0&&e.token.length>0,C=(e,n,t)=>{const r=typeof e=="function"?e:()=>e;return E(n,i(r())).map(c=>({definition:c,handle:async o=>{const s=r();return s===void 0?{content:[{text:S,type:"text"}],isError:!0}:R(t(s),c.name,o,n,i(s))}}))},P="lunora-spec:openrpc",g="lunora-spec:openapi",a=[{description:"The deployment's generated OpenRPC 1.x document — every RPC function's path, kind, and argument schema in one read, instead of list_functions plus one get_function_schema call per function.",fetch:async e=>e.fetchOpenRpc(),name:"OpenRPC specification",uri:P},{description:"The deployment's generated OpenAPI 3.1 document.",fetch:async e=>e.fetchOpenApi(),name:"OpenAPI specification",uri:g}],k=(e,n)=>{const t=typeof e=="function"?e:()=>e,r=async c=>{const o=t();if(o!==void 0)try{return await c.fetch(n(o))}catch{return}};return{list:async()=>(await Promise.all(a.map(async o=>await r(o)===void 0?void 0:{description:o.description,mimeType:"application/json",name:o.name,uri:o.uri}))).filter(o=>o!==void 0),read:async c=>{const o=a.find(u=>u.uri===c);if(o===void 0)return;const s=await r(o);return s===void 0?void 0:{mimeType:"application/json",text:JSON.stringify(s,void 0,2)}}}},w=e=>({list:async()=>(await Promise.all(e.map(async t=>t.list()))).flat(),read:async n=>{for(const t of e){const r=await t.read(n);if(r!==void 0)return r}}}),x=(e,n)=>{const t=[],r=l(e);return r!==void 0&&t.push(...y(r)),t.push(...e.extraTools??[]),e.deployment!==void 0&&t.push(...C(e.deployment,e.allowWrites??!1,n??d(e.fetch))),t},A=(e={})=>{const n=l(e),t=[];n!==void 0&&t.push(h(n));const r=d(e.fetch);return e.deployment!==void 0&&t.push(k(e.deployment,r)),m({name:_,version:e.version??"0.0.0"},x(e,r),t.length===0?void 0:w(t))},j=async(e={})=>{const n=A(e);return await n.connect(new p),n};export{_ as LOCAL_SERVER_NAME,S as NO_DEPLOYMENT_MESSAGE,g as OPENAPI_RESOURCE_URI,P as OPENRPC_RESOURCE_URI,j as connectLocalStdio,A as createLocalMcpServer,x as localTools};
1
+ import{LunoraClient as f}from"@lunora/client";import{StdioServerTransport as p}from"@modelcontextprotocol/sdk/server/stdio.js";import{createToolServer as m}from"./createToolServer-BtGuPyMU.mjs";import{createRemoteDocsIndex as v}from"./DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{docsResources as h}from"./DOCS_URI_SCHEME-CFANc3vr.mjs";import{docsTools as y}from"./DEFAULT_SEARCH_LIMIT-BqSYN5vr.mjs";import{toolDefinitions as E,callTool as R}from"./READ_ONLY_TOOL_DEFINITIONS-_aG40yWx.mjs";const O=(e,n)=>{if(e.size<n)return;const t=e.keys().next().value;t!==void 0&&e.delete(t)},l=e=>e.docs===!1?void 0:v({...e.docs?.baseUrl===void 0?{}:{baseUrl:e.docs.baseUrl},...e.fetch===void 0?{}:{fetch:e.fetch}}),_="lunora",S="no Lunora dev server is running for this project — start one with `lunora dev`, then call this tool again (call lunora_dev_status to check).",T=8,d=e=>{const n=new Map;return t=>{const r=JSON.stringify([t.url,t.token??""]),c=n.get(r);if(c!==void 0)return c;const o=new f({fetch:e,url:t.url});return t.token!==void 0&&t.token.length>0&&o.setAuthToken(t.token),O(n,T),n.set(r,o),o}},i=e=>e?.token!==void 0&&e.token.length>0,C=(e,n,t)=>{const r=typeof e=="function"?e:()=>e;return E(n,i(r())).map(c=>({definition:c,handle:async o=>{const s=r();return s===void 0?{content:[{text:S,type:"text"}],isError:!0}:R(t(s),c.name,o,n,i(s))}}))},P="lunora-spec:openrpc",g="lunora-spec:openapi",a=[{description:"The deployment's generated OpenRPC 1.x document — every RPC function's path, kind, and argument schema in one read, instead of list_functions plus one get_function_schema call per function.",fetch:async e=>e.fetchOpenRpc(),name:"OpenRPC specification",uri:P},{description:"The deployment's generated OpenAPI 3.1 document.",fetch:async e=>e.fetchOpenApi(),name:"OpenAPI specification",uri:g}],k=(e,n)=>{const t=typeof e=="function"?e:()=>e,r=async c=>{const o=t();if(o!==void 0)try{return await c.fetch(n(o))}catch{return}};return{list:async()=>(await Promise.all(a.map(async o=>await r(o)===void 0?void 0:{description:o.description,mimeType:"application/json",name:o.name,uri:o.uri}))).filter(o=>o!==void 0),read:async c=>{const o=a.find(u=>u.uri===c);if(o===void 0)return;const s=await r(o);return s===void 0?void 0:{mimeType:"application/json",text:JSON.stringify(s,void 0,2)}}}},w=e=>({list:async()=>(await Promise.all(e.map(async t=>t.list()))).flat(),read:async n=>{for(const t of e){const r=await t.read(n);if(r!==void 0)return r}}}),x=(e,n)=>{const t=[],r=l(e);return r!==void 0&&t.push(...y(r)),t.push(...e.extraTools??[]),e.deployment!==void 0&&t.push(...C(e.deployment,e.allowWrites??!1,n??d(e.fetch))),t},A=(e={})=>{const n=l(e),t=[];n!==void 0&&t.push(h(n));const r=d(e.fetch);return e.deployment!==void 0&&t.push(k(e.deployment,r)),m({name:_,version:e.version??"0.0.0"},x(e,r),t.length===0?void 0:w(t))},j=async(e={})=>{const n=A(e);return await n.connect(new p),n};export{_ as LOCAL_SERVER_NAME,S as NO_DEPLOYMENT_MESSAGE,g as OPENAPI_RESOURCE_URI,P as OPENRPC_RESOURCE_URI,j as connectLocalStdio,A as createLocalMcpServer,x as localTools};
@@ -1 +1 @@
1
- import{D as T,M as a,O as L,a as s,c as _}from"./observability-tools-Ce0YK58T.mjs";export{T as DEFAULT_LIMIT,a as MAX_LIMIT,L as OBSERVABILITY_TOOL_DEFINITIONS,s as OBSERVABILITY_TOOL_NAMES,_ as callObservabilityTool};
1
+ import{D as T,M as a,O as L,a as s,c as _}from"./observability-tools-B-g9Y9IT.mjs";export{T as DEFAULT_LIMIT,a as MAX_LIMIT,L as OBSERVABILITY_TOOL_DEFINITIONS,s as OBSERVABILITY_TOOL_NAMES,_ as callObservabilityTool};
@@ -0,0 +1 @@
1
+ import{LunoraError as c}from"@lunora/errors";import{O as T,e as d,a as N,c as E,o as u}from"./observability-tools-B-g9Y9IT.mjs";const m={properties:{args:{description:"Arguments object passed to the function",type:"object"},functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"},shardKey:{description:"Optional shard key when the function is .shardBy()-partitioned",type:"string"}},required:["functionPath"],type:"object"},g={properties:{},type:"object"},R={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},l={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},I=[{annotations:{...l,title:"List deployment functions"},description:"List the deployment's public functions (queries, mutations, actions) with their kinds.",inputSchema:g,name:"lunora_list_functions"},{annotations:{...l,title:"List global tables"},description:"List the deployment's .global() tables with their row counts. Names and row counts only — no column shapes.",inputSchema:g,name:"lunora_list_tables"},{annotations:{...l,title:"Describe a function's arguments"},description:"Return a function's argument descriptors (name, validator kind, whether it is optional) and its kind, so a caller can construct a valid arguments object. Call lunora_list_functions first to discover available function paths.",inputSchema:R,name:"lunora_get_function_schema"},{annotations:{...l,title:"Run a query"},description:"Run a query and return its result. Read-only.",inputSchema:m,name:"lunora_run_query"}],b=[{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run a mutation (writes data)"},description:"Run a mutation and return its result. Writes data — use with care.",inputSchema:m,name:"lunora_run_mutation"},{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run an action (may call external services)"},description:"Run an action and return its result. May call external services.",inputSchema:m,name:"lunora_run_action"}],L=new Set(b.map(t=>t.name)),U=(t,n=!1)=>[...I,...n===!0?T:[],...t===!0?b:[]],A=t=>{const{functionPath:n}=t;if(typeof n!="string"||n.length===0)throw new c("BAD_REQUEST",'"functionPath" is required and must be a non-empty string');return n},O=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),w=t=>Array.isArray(t)?"an array":`a ${typeof t}`,v=t=>{if(t==null)return{};if(typeof t=="string"){let n;try{n=JSON.parse(t)}catch{throw new c("BAD_REQUEST",'"args" must be a JSON object; received a string that is not valid JSON')}if(!O(n))throw new c("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${w(n)}`);return n}if(!O(t))throw new c("BAD_REQUEST",`"args" must be a JSON object, got ${w(t)}`);return t},f=t=>{const n=A(t),e=v(t.args),o=typeof t.shardKey=="string"&&t.shardKey.length>0?t.shardKey:void 0;return{args:e,functionPath:n,shardKey:o}},p=t=>({__lunoraRef:t}),P=3e4,h=new WeakMap,y=t=>{const n=Date.now(),e=h.get(t);if(e!==void 0&&e.expiresAt>n)return e.promise;const o=t.listFunctions().catch(i=>{throw h.get(t)?.promise===o&&h.delete(t),i});return h.set(t,{expiresAt:n+P,promise:o}),o},_=async(t,n,e)=>{const i=(await y(t)).find(r=>r.path===n);if(i===void 0)throw new c("NOT_FOUND",`function not found or not public: ${n}`);if(i.kind!==e)throw new c("BAD_REQUEST",`function ${n} is a ${i.kind}, not a ${e}`)},B=async(t,n,e,o=!1,i=!1)=>{try{if(o!==!0&&L.has(n))return d(`tool "${n}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`);if(N.has(n))return i!==!0?d(`tool "${n}" is disabled: it reads the deployment's logs, request metadata and grouped errors — user data that would land at the model provider. Enable it with the LUNORA_MCP_ALLOW_OBSERVABILITY env var.`):await E(t,n,e);switch(n){case"lunora_get_function_schema":{const r=A(e),s=(await y(t)).find(S=>S.path===r);return s===void 0?d(`function not found: ${r}`):u({args:s.args??[],kind:s.kind,path:s.path})}case"lunora_list_functions":return u(await y(t));case"lunora_list_tables":return u(await t.listGlobalTables());case"lunora_run_action":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"action"),u(await t.action(p(a),r,{shardKey:s}))}case"lunora_run_mutation":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"mutation"),u(await t.mutation(p(a),r,{shardKey:s}))}case"lunora_run_query":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"query"),u(await t.query(p(a),r,{shardKey:s}))}default:return d(`unknown tool: ${n}`)}}catch(r){const a=r instanceof Error?r.message:String(r);return d(a)}};export{T as OBSERVABILITY_TOOL_DEFINITIONS,I as READ_ONLY_TOOL_DEFINITIONS,b as WRITE_TOOL_DEFINITIONS,B as callTool,U as toolDefinitions};
@@ -0,0 +1 @@
1
+ import{readFileSync as g}from"node:fs";import{dirname as c,join as d}from"node:path";import{fileURLToPath as w}from"node:url";import{LunoraClient as h}from"@lunora/client";import{LunoraError as u}from"@lunora/errors";import{Server as T}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as y}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as p,CallToolRequestSchema as A}from"@modelcontextprotocol/sdk/types.js";import{agentToolDefinitions as M,isAgentToolName as S,callAgentTool as k}from"./AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{toolDefinitions as N,callTool as L}from"./READ_ONLY_TOOL_DEFINITIONS-_aG40yWx.mjs";const R=()=>{try{let e=c(w(import.meta.url));for(let r=0;r<8;r+=1){try{const a=g(d(e,"package.json"),"utf8"),t=JSON.parse(a);if(t.name==="@lunora/mcp"&&typeof t.version=="string"&&t.version.length>0)return t.version}catch{}const n=c(e);if(n===e)break;e=n}}catch{}return"0.0.0"},E={name:"lunora",version:R()},I=e=>{if(e.client!==void 0)return e.client;if(e.url===void 0)throw new u("INTERNAL","createLunoraMcpServer requires either a `client` or a `url`");if(e.token===void 0||e.token.length===0)throw new u("UNAUTHENTICATED","createLunoraMcpServer requires a `token` (LUNORA_ADMIN_TOKEN) alongside `url`: every tool reaches admin-gated /_lunora/admin/* routes, so an unauthenticated server can only 403. Writes stay off unless `allowWrites` is set.");const r=new h({fetch:e.fetch,url:e.url});return r.setAuthToken(e.token),r},W=e=>{const r=I(e),n=e.allowWrites??!1,a=e.allowAgents??!1,t=e.agents??[],m=typeof e.token=="string"&&e.token.length>0,s=e.allowObservability===!0&&m,o=new T(E,{capabilities:{tools:{}}});return o.setRequestHandler(p,()=>({tools:[...N(n,s),...M(t,a)]})),o.setRequestHandler(A,async f=>{const{arguments:v,name:l}=f.params,i=v??{};return S(l,t)?await k(r,l,i,{allowAgents:a,exposures:t,...e.agentMaxWaitMs===void 0?{}:{maxWaitMs:e.agentMaxWaitMs},...e.agentPollIntervalMs===void 0?{}:{pollIntervalMs:e.agentPollIntervalMs}}):await L(r,l,i,n,s)}),o},F=async e=>{const r=W(e);return await r.connect(new y),r};export{F as connectStdio,W as createLunoraMcpServer,I as resolveClient};
@@ -0,0 +1 @@
1
+ import{serveStateless as n}from"./DEFAULT_MAX_REQUEST_BYTES-B8WntTxM.mjs";import{resolveClient as a,createLunoraMcpServer as o}from"./connectStdio-BBtfW4UB.mjs";const f=e=>typeof e.scope!="string"?new Set:new Set(e.scope.split(" ").filter(r=>r!=="")),l=e=>{const r=typeof e.server=="function"?void 0:a(e.server);return e.protect(async(t,s)=>{const c=typeof e.server=="function"?await e.server(s):{...e.server,client:r};return await n(o(c),t,{maxRequestBytes:e.maxRequestBytes})})};export{l as createAuthedMcpFetchHandler,f as mcpTokenScopes};
@@ -0,0 +1 @@
1
+ import{serveStateless as c}from"./DEFAULT_MAX_REQUEST_BYTES-B8WntTxM.mjs";import{DEFAULT_MAX_REQUEST_BYTES as u}from"./DEFAULT_MAX_REQUEST_BYTES-B8WntTxM.mjs";import{resolveClient as s,createLunoraMcpServer as a}from"./connectStdio-BBtfW4UB.mjs";const l=e=>{const r=s(e);return t=>c(a({...e,client:r}),t,{maxRequestBytes:e.maxRequestBytes})};export{u as DEFAULT_MAX_REQUEST_BYTES,l as createMcpFetchHandler,c as serveStateless};
@@ -0,0 +1 @@
1
+ import{LunoraError as y}from"@lunora/errors";import{Server as g}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as E,CallToolRequestSchema as x}from"@modelcontextprotocol/sdk/types.js";import{readScreenedBody as T,serveStateless as B}from"./DEFAULT_MAX_REQUEST_BYTES-B8WntTxM.mjs";const M=(t,o,a,c)=>{const l=t.get(o);if(l!==void 0)return l;const i=a().catch(u=>{throw t.get(o)===i&&t.delete(o),u});return t.set(o,i),i},C=async()=>{try{return(await import("@lunora/x402/charge")).createChargeMiddleware}catch(t){throw new y("INTERNAL",`paid MCP tools need the optional peer "@lunora/x402" — install it alongside @lunora/mcp to charge for tools (${t instanceof Error?t.message:String(t)})`)}},A={name:"lunora-paid-mcp",version:"0.0.0"},L="tools/call",v=t=>{if(typeof t!="object"||t===null)return;const{method:o,params:a}=t;if(o!==L||typeof a!="object"||a===null)return;const{name:c}=a;return typeof c=="string"?c:void 0},q=()=>Response.json({error:"A JSON-RPC batch may not reference a paid MCP tool; send paid tools/call requests individually."},{status:400}),I=t=>{const o=new Map,a=new Map,c=new Map,l=t.serverInfo??A,i=(e,r,s)=>{if(o.has(e.name))throw new y("BAD_REQUEST",`MCP tool "${e.name}" is already registered.`);const n={description:e.description,inputSchema:e.inputSchema,name:e.name};e.annotations!==void 0&&(n.annotations=e.annotations),o.set(e.name,{definition:n,handler:r}),s!==void 0&&a.set(e.name,s)},u=()=>{const e=new g(l,{capabilities:{tools:{}}});return e.setRequestHandler(E,()=>({tools:[...o.values()].map(r=>r.definition)})),e.setRequestHandler(x,async r=>{const s=o.get(r.params.name);if(s===void 0)return{content:[{text:`unknown tool: ${r.params.name}`,type:"text"}],isError:!0};try{return await s.handler(r.params.arguments??{})}catch(n){return{content:[{text:n instanceof Error?n.message:String(n),type:"text"}],isError:!0}}}),e},w=(e,r)=>M(c,e,async()=>(await C())({...t.charge,price:r},{resource:e}));return{fetchHandler:async(e,r,s)=>{const n=await T(e.clone(),t.maxRequestBytes);if("response"in n)return n.response;const{parsedBody:d}=n,p=()=>B(u(),e,d===void 0?{maxRequestBytes:t.maxRequestBytes}:{maxRequestBytes:t.maxRequestBytes,parsedBody:d});if(Array.isArray(d))return d.some(f=>a.has(v(f)??""))?q():p();const m=v(d),h=m===void 0?void 0:a.get(m);if(m===void 0||h===void 0)return p();const R=await w(m,h),S=typeof s?.waitUntil=="function"?{waitUntil:f=>{s.waitUntil?.(f)}}:void 0;return R.handle(e,p,S)},paidTool:(e,r)=>{i(e,r,e.price)},tool:(e,r)=>{i(e,r)}}};export{I as createPaidMcpServer};
@@ -0,0 +1 @@
1
+ import{LunoraError as v}from"@lunora/errors";import{ADMIN_FUNCTIONS as d}from"@lunora/shard-engine";const _=e=>{let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)},T=(e,t)=>{if(typeof t=="bigint")return t.toString();if(t instanceof ArrayBuffer)return _(new Uint8Array(t));if(ArrayBuffer.isView(t)){const r=t;return _(new Uint8Array(r.buffer,r.byteOffset,r.byteLength))}return t},N=e=>JSON.parse(JSON.stringify(e,T)),R=e=>({content:[{text:e===void 0?"null":JSON.stringify(e,T,2),type:"text"}]}),u=e=>({...R(e),structuredContent:N(e)}),V=e=>({content:[{text:e,type:"text"}],isError:!0}),p={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},I=50,w=500,A=["1m","5m","15m","1h"],E=["open","resolved","ignored"],O=["trace","debug","log","info","warn","error","fatal"],m={description:"Shard to read from on a .shardBy()-partitioned deployment. Omit for the default (unsharded) shard — these reads are PER-SHARD, not deployment-wide.",type:"string"},y={description:`Maximum rows to return (default ${I.toString()}, clamped to ${w.toString()}).`,type:"number"},U=e=>{const t=typeof e=="number"?e:Number.NaN;return Number.isFinite(t)?Math.max(1,Math.min(Math.floor(t),w)):I},S=(e,t)=>t.includes(e)?e:void 0,b=e=>typeof e=="string"&&e.length>0?e:void 0,P={properties:{level:{description:`Keep only entries at this severity. One of: ${O.join(", ")}.`,type:"string"},limit:y,shardKey:m},type:"object"},L={properties:{functionPathPrefix:{description:'Keep only Issues whose function path starts with this, e.g. "messages:".',type:"string"},limit:y,shardKey:m,status:{description:`Triage status to keep. One of: ${E.join(", ")}. Default: all.`,type:"string"}},type:"object"},M={properties:{limit:y,shardKey:m},type:"object"},k={properties:{limit:y,range:{description:`Time window to report over. One of: ${A.join(", ")}. Default: 15m.`,type:"string"},shardKey:m},type:"object"},H={properties:{shardKey:m},type:"object"},C={properties:{dropped:{description:"Entries the shard's in-memory ring EVICTED before this read — they are gone and cannot be fetched. Non-zero means `entries` + `total` describe only the newest slice of what the deployment logged.",type:"number"},entries:{description:"Recent log entries, NEWEST FIRST: { level, message, timestamp, functionPath?, fields? }.",type:"array"},total:{description:"Entries still in the ring matching `level`, before `limit` narrowed them. NOT the number of lines logged — see `dropped`.",type:"number"}},required:["dropped","entries","total"],type:"object"},x={properties:{issues:{description:"Grouped error Issues, newest first: { hash, title, count, status, functionPath, lastSeen, … }.",type:"array"}},required:["issues"],type:"object"},j={properties:{advisories:{description:"Schema/query advisories: { id, level, title, detail, … }.",type:"array"},total:{description:"Advisories available before `limit` narrowed them.",type:"number"}},required:["advisories","total"],type:"object"},q={properties:{buckets:{description:"Combined throughput/latency series across the range.",type:"array"},capped:{description:"True when the deployment's tracked-statement cap was reached, so coverage is partial.",type:"boolean"},entries:{description:"Per-statement activity in the range, hottest first.",type:"array"},total:{description:"Statements available before `limit` narrowed them.",type:"number"},trackedStatements:{description:"Distinct statements the deployment is tracking.",type:"number"}},required:["entries","buckets"],type:"object"},D={properties:{migrations:{description:"Every declared migration with its applied/pending state.",type:"array"}},required:["migrations"],type:"object"},G=[{annotations:{...p,title:"Read recent logs"},description:"Read the deployment's recent log entries (newest first) after running a function, to see what it printed and where it failed. In-memory and per-shard: resets when the shard hibernates.",inputSchema:P,name:"lunora_get_logs",outputSchema:C},{annotations:{...p,title:"List grouped error Issues"},description:"List errors grouped into Issues by fingerprint, with occurrence counts and triage status — the first call when asking what is currently broken, rather than reading raw logs.",inputSchema:L,name:"lunora_get_issues",outputSchema:x},{annotations:{...p,title:"List schema and query advisories"},description:"List the deployment's schema/query advisories (missing indexes, unsafe policies, and similar lints) before or after changing the schema.",inputSchema:M,name:"lunora_get_advisories",outputSchema:j},{annotations:{...p,title:"Read query insights"},description:"Read per-statement execution counts and latency over a recent time window, to find which query is slow or hot before optimizing one.",inputSchema:k,name:"lunora_get_query_insights",outputSchema:q},{annotations:{...p,title:"Read migration status"},description:"Read which migrations have been applied and which are pending, to check whether a schema change has actually landed on the deployment.",inputSchema:H,name:"lunora_get_migration_status",outputSchema:D}],Y=new Set(G.map(e=>e.name)),l=async(e,t,r,s)=>{const a={__lunoraRef:t};return e.query(a,r,{...s===void 0?{}:{shardKey:s}})},h=(e,t)=>{const r=e?.[t];return Array.isArray(r)?r:[]},$=async(e,t,r)=>{const s=b(r.shardKey),a=U(r.limit);switch(t){case"lunora_get_advisories":{const n=await l(e,d.getAdvisories,{},s),i=h(n,"advisories");return u({advisories:i.slice(0,a),total:i.length})}case"lunora_get_issues":{const n=S(r.status,E),i=b(r.functionPathPrefix),o=await l(e,d.getIssues,{limit:a,...n===void 0?{}:{status:n},...i===void 0?{}:{functionPathPrefix:i}},s);return u({issues:h(o,"issues")})}case"lunora_get_logs":{const n=S(r.level,O),i=await l(e,d.getLogs,{},s),o=h(i,"entries").filter(g=>n===void 0||g.level===n),{dropped:c}=i??{};return u({dropped:typeof c=="number"?c:0,entries:o.slice(0,a),total:o.length})}case"lunora_get_migration_status":{const n=await l(e,d.migrationStatus,{},s);return u({migrations:h(n,"migrations")})}case"lunora_get_query_insights":{const n=S(r.range,A),i=await l(e,d.getQueryInsights,{...n===void 0?{}:{range:n}},s),o=h(i,"entries"),{buckets:c,capped:g,trackedStatements:f}=i??{};return u({buckets:Array.isArray(c)?c:[],capped:g===!0,entries:o.slice(0,a),total:o.length,trackedStatements:typeof f=="number"?f:o.length})}default:throw new v("INTERNAL",`unknown observability tool: ${t}`)}};export{I as D,w as M,G as O,Y as a,$ as c,V as e,R as o};
@@ -111,6 +111,17 @@ interface McpResourceProvider {
111
111
  declare const createToolServer: (info: McpServerInfo, tools: ReadonlyArray<McpTool>, resources?: McpResourceProvider) => Server;
112
112
  /** A Web-Standard fetch handler: takes a `Request`, returns the MCP `Response`. */
113
113
  type McpFetchHandler = (request: Request) => Promise<Response>;
114
+ /**
115
+ * Largest request body served. Every MCP call is a short JSON-RPC message — a
116
+ * few hundred bytes — so this is orders of magnitude of headroom while still
117
+ * bounding what a caller can push through the parser.
118
+ */
119
+ declare const DEFAULT_MAX_REQUEST_BYTES: number;
120
+ /** What {@link serveStateless} accepts on top of the transport's own options. */
121
+ interface ServeStatelessOptions extends HandleRequestOptions {
122
+ /** Largest accepted request body, in bytes. Defaults to {@link DEFAULT_MAX_REQUEST_BYTES}. */
123
+ maxRequestBytes?: number;
124
+ }
114
125
  /**
115
126
  * Drive one request through a fresh **stateless** Streamable-HTTP transport bound
116
127
  * to `server`, then tear both down.
@@ -121,13 +132,18 @@ type McpFetchHandler = (request: Request) => Promise<Response>;
121
132
  * an in-flight body. Cleanup is fire-and-forget with swallowed rejections (a
122
133
  * `.catch()`-terminated chain) so it never delays or masks the resolved response.
123
134
  *
135
+ * The request is screened first ({@link screenRequest}); an oversized body or a
136
+ * JSON-RPC batch is refused here and no server is ever constructed for it.
137
+ *
124
138
  * `options.parsedBody` lets a caller hand over a body it already read (e.g. the
125
139
  * paid-tool gate, which peeks the JSON-RPC message to price the call) so the
126
- * transport doesn't re-read a consumed stream.
140
+ * transport doesn't re-read a consumed stream. It is trusted as already
141
+ * screened, so read it with {@link readScreenedBody} — anything else hands this
142
+ * surface an unbounded body.
127
143
  *
128
144
  * Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
129
145
  * would otherwise skip it and leak a server + transport per failed request,
130
146
  * which on a public endpoint is exactly the request an attacker can repeat.
131
147
  */
132
- declare const serveStateless: (server: Server, request: Request, options?: HandleRequestOptions) => Promise<Response>;
133
- export { McpResourceProvider as M, ToolDefinition as T, McpFetchHandler as a, McpTool as b, McpResourceSummary as c, McpServerInfo as d, ToolInputSchema as e, ToolResult as f, createToolServer as g, serveStateless as s };
148
+ declare const serveStateless: (server: Server, request: Request, options?: ServeStatelessOptions) => Promise<Response>;
149
+ export { DEFAULT_MAX_REQUEST_BYTES as D, McpResourceProvider as M, ServeStatelessOptions as S, ToolDefinition as T, McpFetchHandler as a, McpTool as b, McpResourceSummary as c, McpServerInfo as d, ToolInputSchema as e, ToolResult as f, createToolServer as g, serveStateless as s };
@@ -111,6 +111,17 @@ interface McpResourceProvider {
111
111
  declare const createToolServer: (info: McpServerInfo, tools: ReadonlyArray<McpTool>, resources?: McpResourceProvider) => Server;
112
112
  /** A Web-Standard fetch handler: takes a `Request`, returns the MCP `Response`. */
113
113
  type McpFetchHandler = (request: Request) => Promise<Response>;
114
+ /**
115
+ * Largest request body served. Every MCP call is a short JSON-RPC message — a
116
+ * few hundred bytes — so this is orders of magnitude of headroom while still
117
+ * bounding what a caller can push through the parser.
118
+ */
119
+ declare const DEFAULT_MAX_REQUEST_BYTES: number;
120
+ /** What {@link serveStateless} accepts on top of the transport's own options. */
121
+ interface ServeStatelessOptions extends HandleRequestOptions {
122
+ /** Largest accepted request body, in bytes. Defaults to {@link DEFAULT_MAX_REQUEST_BYTES}. */
123
+ maxRequestBytes?: number;
124
+ }
114
125
  /**
115
126
  * Drive one request through a fresh **stateless** Streamable-HTTP transport bound
116
127
  * to `server`, then tear both down.
@@ -121,13 +132,18 @@ type McpFetchHandler = (request: Request) => Promise<Response>;
121
132
  * an in-flight body. Cleanup is fire-and-forget with swallowed rejections (a
122
133
  * `.catch()`-terminated chain) so it never delays or masks the resolved response.
123
134
  *
135
+ * The request is screened first ({@link screenRequest}); an oversized body or a
136
+ * JSON-RPC batch is refused here and no server is ever constructed for it.
137
+ *
124
138
  * `options.parsedBody` lets a caller hand over a body it already read (e.g. the
125
139
  * paid-tool gate, which peeks the JSON-RPC message to price the call) so the
126
- * transport doesn't re-read a consumed stream.
140
+ * transport doesn't re-read a consumed stream. It is trusted as already
141
+ * screened, so read it with {@link readScreenedBody} — anything else hands this
142
+ * surface an unbounded body.
127
143
  *
128
144
  * Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
129
145
  * would otherwise skip it and leak a server + transport per failed request,
130
146
  * which on a public endpoint is exactly the request an attacker can repeat.
131
147
  */
132
- declare const serveStateless: (server: Server, request: Request, options?: HandleRequestOptions) => Promise<Response>;
133
- export { McpResourceProvider as M, ToolDefinition as T, McpFetchHandler as a, McpTool as b, McpResourceSummary as c, McpServerInfo as d, ToolInputSchema as e, ToolResult as f, createToolServer as g, serveStateless as s };
148
+ declare const serveStateless: (server: Server, request: Request, options?: ServeStatelessOptions) => Promise<Response>;
149
+ export { DEFAULT_MAX_REQUEST_BYTES as D, McpResourceProvider as M, ServeStatelessOptions as S, ToolDefinition as T, McpFetchHandler as a, McpTool as b, McpResourceSummary as c, McpServerInfo as d, ToolInputSchema as e, ToolResult as f, createToolServer as g, serveStateless as s };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/mcp",
3
- "version": "1.0.0-alpha.105",
3
+ "version": "1.0.0-alpha.107",
4
4
  "description": "Model Context Protocol server exposing a Lunora deployment to AI agents",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -53,9 +53,9 @@
53
53
  "access": "public"
54
54
  },
55
55
  "dependencies": {
56
- "@lunora/client": "1.0.0-alpha.71",
57
- "@lunora/errors": "1.0.0-alpha.29",
58
- "@lunora/shard-engine": "1.0.0-alpha.52",
56
+ "@lunora/client": "1.0.0-alpha.73",
57
+ "@lunora/errors": "1.0.0-alpha.30",
58
+ "@lunora/shard-engine": "1.0.0-alpha.54",
59
59
  "@modelcontextprotocol/sdk": "^1.30.0"
60
60
  },
61
61
  "peerDependencies": {
@@ -1 +0,0 @@
1
- const b="agents:agentRun",T="agents:agentThread",x="agents:agentMessages",d="lunora_agent_status",_=new Set(["awaiting_input","cancelled","error","idle"]),P=6e4,U=600,L={properties:{prompt:{description:"The task or message for the agent.",type:"string"},threadKey:{description:"Reuse to continue a conversation; omit to start a new thread.",type:"string"},title:{description:"Optional thread title (first run only).",type:"string"}},required:["prompt"],type:"object"},k={properties:{threadKey:{description:"The thread key returned by an agent tool call.",type:"string"}},required:["threadKey"],type:"object"},g=t=>t.toolName??`agent_${t.name}`,C=t=>{if(t===void 0)return[];const n=[];for(const e of t.split(";")){const r=e.trim();if(r.length===0)continue;const o=r.indexOf(":");if(o<=0)continue;const s=r.slice(0,o).trim(),a=r.slice(o+1).trim();s.length===0||a.length===0||n.push({description:a,name:s})}return n},G=(t,n)=>n!==!0||t.length===0?[]:[...t.map(r=>({description:`${r.description} Starts a durable agent run and returns its final answer.`,inputSchema:L,name:g(r)})),{description:"Check the status of a durable agent run (and its answer if finished) by its threadKey.",inputSchema:k,name:d}],O=(t,n)=>t===d||n.some(e=>g(e)===t),I=()=>`mcp-${crypto.randomUUID()}`,p=t=>({__lunoraRef:t}),K=async t=>{await new Promise(n=>{setTimeout(n,t)})},c=t=>({content:[{text:JSON.stringify(t,void 0,2),type:"text"}]}),i=t=>({content:[{text:t,type:"text"}],isError:!0}),l=(t,n)=>{const e=t[n];return typeof e=="string"&&e.length>0?e:void 0},A=t=>{for(let n=t.length-1;n>=0;n-=1){const e=t[n],r=e?.toolCalls,o=Array.isArray(r)&&r.length>0;if(e?.role==="assistant"&&!o)return typeof e.content=="string"?e.content:""}return""},w=t=>t!==null&&typeof t=="object"&&typeof t.status=="string"?t.status:"unknown",S=async(t,n,e,r)=>{const o=await t.query(p(x),{key:n});if(e==="error"){const s=r!==null&&typeof r=="object"?r.error:void 0;return c({error:typeof s=="string"?s:"the agent run failed",status:e,threadKey:n})}return c(e==="awaiting_input"?{hint:"This run is paused on a human-in-the-loop tool approval. Approve or reject it in the app that owns the agent; MCP cannot supply the input. Poll lunora_agent_status with this threadKey afterwards.",status:e,text:A(o),threadKey:n}:{status:e,text:A(o),threadKey:n})},R=async(t,n)=>{const e=l(n,"threadKey");if(e===void 0)return i('"threadKey" is required and must be a non-empty string');const r=await t.query(p(T),{key:e}),o=w(r);return _.has(o)?S(t,e,o,r):c({status:o==="unknown"?"running":o,threadKey:e})},q=async(t,n,e,r)=>{try{if(r.allowAgents!==!0)return i(`tool "${n}" is disabled: agent tools are off. Enable them with the LUNORA_MCP_ALLOW_AGENTS env var.`);if(n===d)return await R(t,e);const o=r.exposures.find(u=>g(u)===n);if(o===void 0)return i(`agent tool "${n}" is not exposed by this MCP server`);const s=l(e,"prompt");if(s===void 0)return i('"prompt" is required and must be a non-empty string');const a=l(e,"threadKey")??I(),h=l(e,"title"),{id:E}=await t.mutation(p(b),{agent:o.name,input:s,threadKey:a,...h===void 0?{}:{title:h}}),f=r.pollIntervalMs??U,N=r.maxWaitMs??P,v=r.wait??K,M=Math.max(1,Math.ceil(N/f));for(let u=0;u<M;u+=1){const y=await t.query(p(T),{key:a}),m=w(y);if(_.has(m))return await S(t,a,m,y);await v(f)}return c({hint:"call lunora_agent_status with this threadKey to poll for the answer",runId:E,status:"running",threadKey:a})}catch(o){const s=o instanceof Error?o.message:String(o);return i(s)}};export{L as AGENT_RUN_INPUT_SCHEMA,d as AGENT_STATUS_TOOL_NAME,G as agentToolDefinitions,q as callAgentTool,A as finalAnswer,O as isAgentToolName,C as parseAgentsEnv};
@@ -1,3 +0,0 @@
1
- const T=10,A=50,h="docs",L=500,_={properties:{limit:{description:`Maximum hits to return (default ${String(10)}, max ${String(50)})`,type:"number"},query:{description:'Search terms, e.g. "shardBy" or "optimistic updates"',type:"string"}},required:["query"],type:"object"},m={properties:{url:{description:'Site-relative page URL from a search hit, e.g. "/docs/sharding"',type:"string"}},required:["url"],type:"object"},p={properties:{},type:"object"},s={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},u={annotations:{...s,title:"Search the Lunora documentation"},description:"Search the Lunora documentation and return matching pages and sections. Use this before writing Lunora code (schema, queries, mutations, actions, sharding, .global(), client hooks) so the answer reflects the framework's current API rather than a guess. Follow a hit with lunora_get_doc to read the full page.",inputSchema:_,name:"lunora_search_docs"},l={annotations:{...s,title:"Read a Lunora documentation page"},description:"Return one Lunora documentation page in full, as Markdown. Takes the `url` of a lunora_search_docs hit or a lunora_list_docs entry.",inputSchema:m,name:"lunora_get_doc"},d={annotations:{...s,title:"List the Lunora documentation pages"},description:"List every Lunora documentation page with its title and description. Prefer lunora_search_docs when you know what you're looking for.",inputSchema:p,name:"lunora_list_docs"},y=[u,l,d],r=e=>({content:[{text:JSON.stringify(e,void 0,2),type:"text"}]}),g=e=>({content:[{text:e,type:"text"}],isError:!0}),a=512,c=(e,t)=>{const o=e[t];if(typeof o!="string"||o.trim().length===0)throw new TypeError(`"${t}" is required and must be a non-empty string`);if(o.length>a)throw new RangeError(`"${t}" must be at most ${String(a)} characters`);return o.trim()},f=e=>{const t=typeof e=="string"?Number(e):e;return typeof t!="number"||!Number.isFinite(t)?10:Math.min(50,Math.max(1,Math.floor(t)))},S=(e,t)=>{for(const o of e.split("/")){let n;try{n=decodeURIComponent(o)}catch{throw new RangeError(`"url" contains a malformed percent-escape: ${t}`)}if(n===".."||n==="."||n.includes("%")||n.includes("\\"))throw new RangeError(`"url" must not contain ".." or encoded segments: ${t}`)}},E=e=>{let t=e.trim().replaceAll("\\","/");if(t.startsWith("http://")||t.startsWith("https://")){const o=t.slice(t.indexOf("//")+2),n=o.indexOf("/");t=n===-1?"/":o.slice(n)}for(const o of["?","#"]){const n=t.indexOf(o);n!==-1&&(t=t.slice(0,n))}for(;t.endsWith("/")&&t.length>1;)t=t.slice(0,-1);return t.startsWith("/")||(t=t.startsWith("docs/")?`/${t}`:`/${h}/${t}`),S(t,e),t},O=e=>[{definition:u,handle:async t=>{const o=c(t,"query"),n=f(t.limit),i=(await e.search(o)).slice(0,n);return i.length===0?r({hits:[],note:`no documentation matched "${o}" — try fewer or more general terms, or lunora_list_docs to browse`}):r({hits:i})}},{definition:l,handle:async t=>{const o=E(c(t,"url")),n=await e.getPage(o);return n===void 0?g(`documentation page not found: ${o}. Use lunora_search_docs or lunora_list_docs to find a valid url.`):{content:[{text:`# ${n.title} (${n.url})
2
-
3
- ${n.content}`,type:"text"}]}}},{definition:d,handle:async()=>{const t=await e.listPages();return t.length>500?r({note:`showing the first ${String(500)} of ${String(t.length)} pages — use lunora_search_docs to find the rest`,pages:t.slice(0,500)}):r(t)}}];export{T as DEFAULT_SEARCH_LIMIT,y as DOCS_TOOL_DEFINITIONS,a as MAX_ARGUMENT_LENGTH,L as MAX_LISTED_PAGES,A as MAX_SEARCH_LIMIT,O as docsTools,E as normalizeDocUrl};
@@ -1 +0,0 @@
1
- import{createToolServer as d}from"./createToolServer-BtGuPyMU.mjs";import{serveStateless as a}from"./serveStateless-Db7J3pAO.mjs";import{docsResources as i}from"./DOCS_URI_SCHEME-CFANc3vr.mjs";import{docsTools as p}from"./DEFAULT_SEARCH_LIMIT-V7pCZ1wt.mjs";const u="lunora-docs",y=128*1024,m=e=>({name:u,version:e??"0.0.0"}),l=e=>d(m(e.version),p(e.index),i(e.index)),S=e=>new TextEncoder().encode(e).length,t=(e,r,o)=>Response.json({error:{code:r,message:o},id:null,jsonrpc:"2.0"},{status:e}),f=async(e,r)=>{if(e.method!=="POST")return{parsedBody:void 0};const o={response:t(413,-32600,`request body exceeds ${String(r)} bytes`)},s=Number(e.headers.get("content-length"));if(Number.isFinite(s)&&s>r)return o;const c=await e.text();if(S(c)>r)return o;let n;try{n=JSON.parse(c)}catch{return{response:t(400,-32700,"parse error: body is not valid JSON")}}return Array.isArray(n)?{response:t(400,-32600,"batched requests are not supported: send one JSON-RPC message per request")}:{parsedBody:n}},g=e=>{const r=e.maxRequestBytes??y;return async o=>{const s=await f(o,r);return"response"in s?s.response:a(l(e),o,s.parsedBody===void 0?void 0:{parsedBody:s.parsedBody})}};export{y as DEFAULT_MAX_REQUEST_BYTES,u as DOCS_SERVER_NAME,g as createDocsMcpFetchHandler,l as createDocsMcpServer};
@@ -1 +0,0 @@
1
- import{LunoraError as u}from"@lunora/errors";import{O as A,e as d,a as S,c as E,o as c}from"./observability-tools-Ce0YK58T.mjs";const m={properties:{args:{description:"Arguments object passed to the function",type:"object"},functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"},shardKey:{description:"Optional shard key when the function is .shardBy()-partitioned",type:"string"}},required:["functionPath"],type:"object"},g={properties:{},type:"object"},R={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},l={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},I=[{annotations:{...l,title:"List deployment functions"},description:"List the deployment's public functions (queries, mutations, actions) with their kinds.",inputSchema:g,name:"lunora_list_functions"},{annotations:{...l,title:"List global tables"},description:"List the deployment's .global() tables with their row counts. Names and row counts only — no column shapes.",inputSchema:g,name:"lunora_list_tables"},{annotations:{...l,title:"Describe a function's arguments"},description:"Return a function's argument descriptors (name, validator kind, whether it is optional) and its kind, so a caller can construct a valid arguments object. Call lunora_list_functions first to discover available function paths.",inputSchema:R,name:"lunora_get_function_schema"},{annotations:{...l,title:"Run a query"},description:"Run a query and return its result. Read-only.",inputSchema:m,name:"lunora_run_query"}],w=[{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run a mutation (writes data)"},description:"Run a mutation and return its result. Writes data — use with care.",inputSchema:m,name:"lunora_run_mutation"},{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run an action (may call external services)"},description:"Run an action and return its result. May call external services.",inputSchema:m,name:"lunora_run_action"}],L=new Set(w.map(t=>t.name)),k=(t,n=!1)=>[...I,...n===!0?A:[],...t===!0?w:[]],b=t=>{const{functionPath:n}=t;if(typeof n!="string"||n.length===0)throw new u("INTERNAL",'"functionPath" is required and must be a non-empty string');return n},O=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),N=t=>Array.isArray(t)?"an array":`a ${typeof t}`,v=t=>{if(t==null)return{};if(typeof t=="string"){let n;try{n=JSON.parse(t)}catch{throw new u("BAD_REQUEST",'"args" must be a JSON object; received a string that is not valid JSON')}if(!O(n))throw new u("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${N(n)}`);return n}if(!O(t))throw new u("BAD_REQUEST",`"args" must be a JSON object, got ${N(t)}`);return t},f=t=>{const n=b(t),e=v(t.args),o=typeof t.shardKey=="string"&&t.shardKey.length>0?t.shardKey:void 0;return{args:e,functionPath:n,shardKey:o}},p=t=>({__lunoraRef:t}),H=3e4,h=new WeakMap,y=t=>{const n=Date.now(),e=h.get(t);if(e!==void 0&&e.expiresAt>n)return e.promise;const o=t.listFunctions().catch(i=>{throw h.get(t)?.promise===o&&h.delete(t),i});return h.set(t,{expiresAt:n+H,promise:o}),o},_=async(t,n,e)=>{const i=(await y(t)).find(r=>r.path===n);if(i===void 0)throw new u("NOT_FOUND",`function not found or not public: ${n}`);if(i.kind!==e)throw new u("BAD_REQUEST",`function ${n} is a ${i.kind}, not a ${e}`)},U=async(t,n,e,o=!1,i=!1)=>{try{if(o!==!0&&L.has(n))return d(`tool "${n}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`);if(S.has(n))return i!==!0?d(`tool "${n}" is unavailable: it reads the deployment's logs and errors, which needs an admin token. Set LUNORA_ADMIN_TOKEN (or pass --token) and reconnect.`):await E(t,n,e);switch(n){case"lunora_get_function_schema":{const r=b(e),a=(await y(t)).find(T=>T.path===r);return a===void 0?d(`function not found: ${r}`):c({args:a.args??[],kind:a.kind,path:a.path})}case"lunora_list_functions":return c(await y(t));case"lunora_list_tables":return c(await t.listGlobalTables());case"lunora_run_action":{const{args:r,functionPath:s,shardKey:a}=f(e);return await _(t,s,"action"),c(await t.action(p(s),r,{shardKey:a}))}case"lunora_run_mutation":{const{args:r,functionPath:s,shardKey:a}=f(e);return await _(t,s,"mutation"),c(await t.mutation(p(s),r,{shardKey:a}))}case"lunora_run_query":{const{args:r,functionPath:s,shardKey:a}=f(e);return await _(t,s,"query"),c(await t.query(p(s),r,{shardKey:a}))}default:return d(`unknown tool: ${n}`)}}catch(r){const s=r instanceof Error?r.message:String(r);return d(s)}};export{A as OBSERVABILITY_TOOL_DEFINITIONS,I as READ_ONLY_TOOL_DEFINITIONS,w as WRITE_TOOL_DEFINITIONS,U as callTool,k as toolDefinitions};
@@ -1 +0,0 @@
1
- import{readFileSync as v}from"node:fs";import{dirname as c,join as g}from"node:path";import{fileURLToPath as d}from"node:url";import{LunoraClient as h}from"@lunora/client";import{LunoraError as u}from"@lunora/errors";import{Server as w}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as T}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as p,CallToolRequestSchema as A}from"@modelcontextprotocol/sdk/types.js";import{agentToolDefinitions as M,isAgentToolName as S,callAgentTool as k}from"./AGENT_RUN_INPUT_SCHEMA-DGpNvk5K.mjs";import{toolDefinitions as y,callTool as N}from"./READ_ONLY_TOOL_DEFINITIONS-aD-b7Hw2.mjs";const L=()=>{try{let e=c(d(import.meta.url));for(let r=0;r<8;r+=1){try{const o=v(g(e,"package.json"),"utf8"),t=JSON.parse(o);if(t.name==="@lunora/mcp"&&typeof t.version=="string"&&t.version.length>0)return t.version}catch{}const n=c(e);if(n===e)break;e=n}}catch{}return"0.0.0"},R={name:"lunora",version:L()},E=e=>{if(e.client!==void 0)return e.client;if(e.url===void 0)throw new u("INTERNAL","createLunoraMcpServer requires either a `client` or a `url`");if(e.token===void 0||e.token.length===0)throw new u("UNAUTHENTICATED","createLunoraMcpServer requires a `token` (LUNORA_ADMIN_TOKEN) alongside `url`: every tool reaches admin-gated /_lunora/admin/* routes, so an unauthenticated server can only 403. Writes stay off unless `allowWrites` is set.");const r=new h({fetch:e.fetch,url:e.url});return r.setAuthToken(e.token),r},I=e=>{const r=E(e),n=e.allowWrites??!1,o=e.allowAgents??!1,t=e.agents??[],s=typeof e.token=="string"&&e.token.length>0,a=new w(R,{capabilities:{tools:{}}});return a.setRequestHandler(p,()=>({tools:[...y(n,s),...M(t,o)]})),a.setRequestHandler(A,async m=>{const{arguments:f,name:l}=m.params,i=f??{};return S(l,t)?await k(r,l,i,{allowAgents:o,exposures:t,...e.agentMaxWaitMs===void 0?{}:{maxWaitMs:e.agentMaxWaitMs},...e.agentPollIntervalMs===void 0?{}:{pollIntervalMs:e.agentPollIntervalMs}}):await N(r,l,i,n,s)}),a},j=async e=>{const r=I(e);return await r.connect(new T),r};export{j as connectStdio,I as createLunoraMcpServer};
@@ -1 +0,0 @@
1
- import{serveStateless as n}from"./serveStateless-Db7J3pAO.mjs";import{createLunoraMcpServer as o}from"./connectStdio-CDsCt1IT.mjs";const a=e=>typeof e.scope!="string"?new Set:new Set(e.scope.split(" ").filter(r=>r!=="")),f=e=>e.protect(async(r,t)=>{const c=typeof e.server=="function"?await e.server(t):e.server;return await n(o(c),r)});export{f as createAuthedMcpFetchHandler,a as mcpTokenScopes};
@@ -1 +0,0 @@
1
- import{serveStateless as t}from"./serveStateless-Db7J3pAO.mjs";import{createLunoraMcpServer as o}from"./connectStdio-CDsCt1IT.mjs";const p=e=>r=>t(o(e),r);export{p as createMcpFetchHandler,t as serveStateless};
@@ -1 +0,0 @@
1
- import{LunoraError as p}from"@lunora/errors";import{Server as v}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as w,CallToolRequestSchema as y}from"@modelcontextprotocol/sdk/types.js";import{serveStateless as g}from"./serveStateless-Db7J3pAO.mjs";const S=(o,r,n,c)=>{const d=o.get(r);if(d!==void 0)return d;const i=n().catch(l=>{throw o.get(r)===i&&o.delete(r),l});return o.set(r,i),i},T=async()=>{try{return(await import("@lunora/x402/charge")).createChargeMiddleware}catch(o){throw new p("INTERNAL",`paid MCP tools need the optional peer "@lunora/x402" — install it alongside @lunora/mcp to charge for tools (${o instanceof Error?o.message:String(o)})`)}},E={name:"lunora-paid-mcp",version:"0.0.0"},M="tools/call",u=o=>{if(typeof o!="object"||o===null)return;const{method:r,params:n}=o;if(r!==M||typeof n!="object"||n===null)return;const{name:c}=n;return typeof c=="string"?c:void 0},R=()=>Response.json({error:"A JSON-RPC batch may not reference a paid MCP tool; send paid tools/call requests individually."},{status:400}),b=()=>Response.json({error:"The request body could not be parsed as JSON; a priced MCP tool is registered, so the call cannot be gated."},{status:400}),B=o=>{const r=new Map,n=new Map,c=new Map,d=o.serverInfo??E,i=(e,t,a)=>{if(r.has(e.name))throw new p("BAD_REQUEST",`MCP tool "${e.name}" is already registered.`);const s={description:e.description,inputSchema:e.inputSchema,name:e.name};e.annotations!==void 0&&(s.annotations=e.annotations),r.set(e.name,{definition:s,handler:t}),a!==void 0&&n.set(e.name,a)},l=()=>{const e=new v(d,{capabilities:{tools:{}}});return e.setRequestHandler(w,()=>({tools:[...r.values()].map(t=>t.definition)})),e.setRequestHandler(y,async t=>{const a=r.get(t.params.name);return a===void 0?{content:[{text:`unknown tool: ${t.params.name}`,type:"text"}],isError:!0}:await a.handler(t.params.arguments??{})}),e},h=(e,t)=>S(c,e,async()=>(await T())({...o.charge,price:t},{resource:e}));return{fetchHandler:async e=>{let t;try{t=await e.clone().json()}catch{t=void 0}const a=()=>g(l(),e,t===void 0?void 0:{parsedBody:t});if(t===void 0&&n.size>0&&e.method==="POST")return b();if(Array.isArray(t))return t.some(f=>n.has(u(f)??""))?R():a();const s=u(t),m=s===void 0?void 0:n.get(s);return s===void 0||m===void 0?a():(await h(s,m)).handle(e,a)},paidTool:(e,t)=>{i(e,t,e.price)},tool:(e,t)=>{i(e,t)}}};export{B as createPaidMcpServer};
@@ -1 +0,0 @@
1
- import{LunoraError as v}from"@lunora/errors";import{ADMIN_FUNCTIONS as c}from"@lunora/shard-engine";const f=e=>{let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)},b=(e,t)=>{if(typeof t=="bigint")return t.toString();if(t instanceof ArrayBuffer)return f(new Uint8Array(t));if(ArrayBuffer.isView(t)){const r=t;return f(new Uint8Array(r.buffer,r.byteOffset,r.byteLength))}return t},N=e=>JSON.parse(JSON.stringify(e,b)),R=e=>({content:[{text:e===void 0?"null":JSON.stringify(e,b,2),type:"text"}]}),u=e=>({...R(e),structuredContent:N(e)}),V=e=>({content:[{text:e,type:"text"}],isError:!0}),d={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},T=50,I=500,w=["1m","5m","15m","1h"],A=["open","resolved","ignored"],E=["trace","debug","log","info","warn","error","fatal"],h={description:"Shard to read from on a .shardBy()-partitioned deployment. Omit for the default (unsharded) shard — these reads are PER-SHARD, not deployment-wide.",type:"string"},y={description:`Maximum rows to return (default ${T.toString()}, clamped to ${I.toString()}).`,type:"number"},U=e=>{const t=typeof e=="number"?e:Number.NaN;return Number.isFinite(t)?Math.max(1,Math.min(Math.floor(t),I)):T},g=(e,t)=>t.includes(e)?e:void 0,_=e=>typeof e=="string"&&e.length>0?e:void 0,P={properties:{level:{description:`Keep only entries at this severity. One of: ${E.join(", ")}.`,type:"string"},limit:y,shardKey:h},type:"object"},L={properties:{functionPathPrefix:{description:'Keep only Issues whose function path starts with this, e.g. "messages:".',type:"string"},limit:y,shardKey:h,status:{description:`Triage status to keep. One of: ${A.join(", ")}. Default: all.`,type:"string"}},type:"object"},M={properties:{limit:y,shardKey:h},type:"object"},k={properties:{limit:y,range:{description:`Time window to report over. One of: ${w.join(", ")}. Default: 15m.`,type:"string"},shardKey:h},type:"object"},H={properties:{shardKey:h},type:"object"},x={properties:{entries:{description:"Recent log entries, NEWEST FIRST: { level, message, timestamp, functionPath?, fields? }.",type:"array"},total:{description:"Entries available before `limit`/`level` narrowed them.",type:"number"}},required:["entries","total"],type:"object"},C={properties:{issues:{description:"Grouped error Issues, newest first: { hash, title, count, status, functionPath, lastSeen, … }.",type:"array"}},required:["issues"],type:"object"},j={properties:{advisories:{description:"Schema/query advisories: { id, level, title, detail, … }.",type:"array"},total:{description:"Advisories available before `limit` narrowed them.",type:"number"}},required:["advisories","total"],type:"object"},q={properties:{buckets:{description:"Combined throughput/latency series across the range.",type:"array"},capped:{description:"True when the deployment's tracked-statement cap was reached, so coverage is partial.",type:"boolean"},entries:{description:"Per-statement activity in the range, hottest first.",type:"array"},total:{description:"Statements available before `limit` narrowed them.",type:"number"},trackedStatements:{description:"Distinct statements the deployment is tracking.",type:"number"}},required:["entries","buckets"],type:"object"},D={properties:{migrations:{description:"Every declared migration with its applied/pending state.",type:"array"}},required:["migrations"],type:"object"},G=[{annotations:{...d,title:"Read recent logs"},description:"Read the deployment's recent log entries (newest first) after running a function, to see what it printed and where it failed. In-memory and per-shard: resets when the shard hibernates.",inputSchema:P,name:"lunora_get_logs",outputSchema:x},{annotations:{...d,title:"List grouped error Issues"},description:"List errors grouped into Issues by fingerprint, with occurrence counts and triage status — the first call when asking what is currently broken, rather than reading raw logs.",inputSchema:L,name:"lunora_get_issues",outputSchema:C},{annotations:{...d,title:"List schema and query advisories"},description:"List the deployment's schema/query advisories (missing indexes, unsafe policies, and similar lints) before or after changing the schema.",inputSchema:M,name:"lunora_get_advisories",outputSchema:j},{annotations:{...d,title:"Read query insights"},description:"Read per-statement execution counts and latency over a recent time window, to find which query is slow or hot before optimizing one.",inputSchema:k,name:"lunora_get_query_insights",outputSchema:q},{annotations:{...d,title:"Read migration status"},description:"Read which migrations have been applied and which are pending, to check whether a schema change has actually landed on the deployment.",inputSchema:H,name:"lunora_get_migration_status",outputSchema:D}],Y=new Set(G.map(e=>e.name)),p=async(e,t,r,s)=>{const a={__lunoraRef:t};return e.query(a,r,{...s===void 0?{}:{shardKey:s}})},l=(e,t)=>{const r=e?.[t];return Array.isArray(r)?r:[]},$=async(e,t,r)=>{const s=_(r.shardKey),a=U(r.limit);switch(t){case"lunora_get_advisories":{const n=await p(e,c.getAdvisories,{},s),i=l(n,"advisories");return u({advisories:i.slice(0,a),total:i.length})}case"lunora_get_issues":{const n=g(r.status,A),i=_(r.functionPathPrefix),o=await p(e,c.getIssues,{limit:a,...n===void 0?{}:{status:n},...i===void 0?{}:{functionPathPrefix:i}},s);return u({issues:l(o,"issues")})}case"lunora_get_logs":{const n=g(r.level,E),i=await p(e,c.getLogs,{},s),o=l(i,"entries").filter(m=>n===void 0||m.level===n);return u({entries:o.slice(0,a),total:o.length})}case"lunora_get_migration_status":{const n=await p(e,c.migrationStatus,{},s);return u({migrations:l(n,"migrations")})}case"lunora_get_query_insights":{const n=g(r.range,w),i=await p(e,c.getQueryInsights,{...n===void 0?{}:{range:n}},s),o=l(i,"entries"),{buckets:m,capped:O,trackedStatements:S}=i??{};return u({buckets:Array.isArray(m)?m:[],capped:O===!0,entries:o.slice(0,a),total:o.length,trackedStatements:typeof S=="number"?S:o.length})}default:throw new v("INTERNAL",`unknown observability tool: ${t}`)}};export{T as D,I as M,G as O,Y as a,$ as c,V as e,R as o};
@@ -1 +0,0 @@
1
- import{WebStandardStreamableHTTPServerTransport as o}from"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";const s=async(t,a,n)=>{const e=new o({enableJsonResponse:!0,sessionIdGenerator:void 0});try{return await t.connect(e),await e.handleRequest(a,n)}finally{e.close().catch(()=>{}),t.close().catch(()=>{})}};export{s as serveStateless};