@lunora/mcp 1.0.0-alpha.106 → 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 (28) hide show
  1. package/README.md +8 -1
  2. package/dist/bin.mjs +1 -1
  3. package/dist/docs/index.d.mts +2 -2
  4. package/dist/docs/index.d.ts +2 -2
  5. package/dist/docs/index.mjs +1 -1
  6. package/dist/index.d.mts +48 -5
  7. package/dist/index.d.ts +48 -5
  8. package/dist/index.mjs +1 -1
  9. package/dist/packem_shared/DEFAULT_MAX_REQUEST_BYTES-B8WntTxM.mjs +1 -0
  10. package/dist/packem_shared/DOCS_SERVER_NAME-DmmgZtad.mjs +1 -0
  11. package/dist/packem_shared/{LOCAL_SERVER_NAME-BPPFBI2g.mjs → LOCAL_SERVER_NAME-Do7MU6Kd.mjs} +1 -1
  12. package/dist/packem_shared/{OBSERVABILITY_TOOL_DEFINITIONS-KrCukPbu.mjs → OBSERVABILITY_TOOL_DEFINITIONS-Byqgb9wh.mjs} +1 -1
  13. package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-_aG40yWx.mjs +1 -0
  14. package/dist/packem_shared/{connectStdio-D2XPJMyB.mjs → connectStdio-BBtfW4UB.mjs} +1 -1
  15. package/dist/packem_shared/createAuthedMcpFetchHandler-CmVUae-x.mjs +1 -0
  16. package/dist/packem_shared/createMcpFetchHandler-BJKPn555.mjs +1 -0
  17. package/dist/packem_shared/createPaidMcpServer-CQrzMUhI.mjs +1 -0
  18. package/dist/packem_shared/observability-tools-B-g9Y9IT.mjs +1 -0
  19. package/dist/packem_shared/{serve-stateless.d-Qu1g6Pf4.d.mts → serve-stateless.d-C_y-E_zk.d.mts} +10 -2
  20. package/dist/packem_shared/{serve-stateless.d-Qu1g6Pf4.d.ts → serve-stateless.d-C_y-E_zk.d.ts} +10 -2
  21. package/package.json +3 -3
  22. package/dist/packem_shared/DOCS_SERVER_NAME-hHomMT1b.mjs +0 -1
  23. package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-CMhsP7pi.mjs +0 -1
  24. package/dist/packem_shared/createAuthedMcpFetchHandler-D9RPx2pl.mjs +0 -1
  25. package/dist/packem_shared/createMcpFetchHandler-oQqcp6i5.mjs +0 -1
  26. package/dist/packem_shared/createPaidMcpServer-XUo-pELu.mjs +0 -1
  27. package/dist/packem_shared/observability-tools-Ce0YK58T.mjs +0 -1
  28. package/dist/packem_shared/serveStateless-B3nFSJ-z.mjs +0 -1
package/README.md CHANGED
@@ -144,7 +144,14 @@ await server.connect(myTransport);
144
144
 
145
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.
146
146
 
147
- 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):
148
155
 
149
156
  - `LUNORA_MCP_ALLOW_AGENTS` — set to `1`/`true`/`yes`/`on` to expose the agent tools. Default: agents disabled.
150
157
  - `LUNORA_MCP_AGENTS` — a `;`-separated list of `name:description` pairs selecting which agents to expose, e.g. `"support:Support questions;billing:Billing help"`.
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{parseAgentsEnv as N}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{connectStdio as E}from"./packem_shared/connectStdio-D2XPJMyB.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
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
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
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
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-Qu1g6Pf4.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-Qu1g6Pf4.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
  /**
@@ -1,4 +1,4 @@
1
- import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-Qu1g6Pf4.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-Qu1g6Pf4.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
  /**
@@ -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-hHomMT1b.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};
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-Qu1g6Pf4.mjs";
3
- export { type d as McpServerInfo, type S as ServeStatelessOptions, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-Qu1g6Pf4.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';
@@ -215,6 +215,8 @@ type McpAuthProtect = (handler: (request: Request, claims: McpAccessTokenClaims)
215
215
  /** Server options, or a function deriving them from the request's verified claims. */
216
216
  type AuthedMcpServerOptions = ((claims: McpAccessTokenClaims) => LunoraMcpServerOptions | Promise<LunoraMcpServerOptions>) | LunoraMcpServerOptions;
217
217
  interface AuthedMcpFetchHandlerOptions {
218
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES` (128 KiB). */
219
+ maxRequestBytes?: number;
218
220
  /**
219
221
  * The OAuth gate to mount the MCP server behind. Pass
220
222
  * `(handler) => requireMcpAuth(auth, handler, opts)` from
@@ -247,14 +249,32 @@ declare const mcpTokenScopes: (claims: McpAccessTokenClaims) => ReadonlySet<stri
247
249
  * `server` (resolved against the verified claims) and serves it through
248
250
  * {@link serveStateless}, exactly as the unprotected `createMcpFetchHandler`
249
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.
250
258
  */
251
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
+ }
252
265
  /**
253
266
  * Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
254
267
  * invocation constructs a fresh proxy server and serves the request through
255
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.
256
276
  */
257
- declare const createMcpFetchHandler: (options: LunoraMcpServerOptions) => McpFetchHandler;
277
+ declare const createMcpFetchHandler: (options: McpFetchHandlerOptions) => McpFetchHandler;
258
278
  /** A Lunora deployment the tools dispatch against. */
259
279
  interface LocalDeployment {
260
280
  token?: string;
@@ -375,16 +395,39 @@ type PaidMcpChargeConfig = X402ChargeSettings;
375
395
  interface PaidMcpServerConfig {
376
396
  /** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
377
397
  charge: PaidMcpChargeConfig;
398
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES` (128 KiB). */
399
+ maxRequestBytes?: number;
378
400
  /** Name/version advertised in the MCP `initialize` handshake. Defaults to `lunora-paid-mcp`. */
379
401
  serverInfo?: {
380
402
  name: string;
381
403
  version: string;
382
404
  };
383
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>;
384
427
  /** A paid MCP server: register free/paid tools, then serve over Streamable HTTP. */
385
428
  interface PaidMcpServer {
386
429
  /** The Streamable-HTTP fetch handler; gates each paid `tools/call` behind x402. */
387
- readonly fetchHandler: McpFetchHandler;
430
+ readonly fetchHandler: PaidMcpFetchHandler;
388
431
  /** Register a **paid** tool: its dispatch runs the x402 charge middleware first. */
389
432
  paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
390
433
  /** Register a **free** tool (coexists with paid tools on the same server). */
@@ -466,4 +509,4 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
466
509
  * better-auth's MCP OAuth gate (`requireMcpAuth` from `@lunora/auth/plugins`)
467
510
  * and can scope tool exposure to the access token's own scopes.
468
511
  */
469
- 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-Qu1g6Pf4.js";
3
- export { type d as McpServerInfo, type S as ServeStatelessOptions, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-Qu1g6Pf4.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';
@@ -215,6 +215,8 @@ type McpAuthProtect = (handler: (request: Request, claims: McpAccessTokenClaims)
215
215
  /** Server options, or a function deriving them from the request's verified claims. */
216
216
  type AuthedMcpServerOptions = ((claims: McpAccessTokenClaims) => LunoraMcpServerOptions | Promise<LunoraMcpServerOptions>) | LunoraMcpServerOptions;
217
217
  interface AuthedMcpFetchHandlerOptions {
218
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES` (128 KiB). */
219
+ maxRequestBytes?: number;
218
220
  /**
219
221
  * The OAuth gate to mount the MCP server behind. Pass
220
222
  * `(handler) => requireMcpAuth(auth, handler, opts)` from
@@ -247,14 +249,32 @@ declare const mcpTokenScopes: (claims: McpAccessTokenClaims) => ReadonlySet<stri
247
249
  * `server` (resolved against the verified claims) and serves it through
248
250
  * {@link serveStateless}, exactly as the unprotected `createMcpFetchHandler`
249
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.
250
258
  */
251
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
+ }
252
265
  /**
253
266
  * Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
254
267
  * invocation constructs a fresh proxy server and serves the request through
255
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.
256
276
  */
257
- declare const createMcpFetchHandler: (options: LunoraMcpServerOptions) => McpFetchHandler;
277
+ declare const createMcpFetchHandler: (options: McpFetchHandlerOptions) => McpFetchHandler;
258
278
  /** A Lunora deployment the tools dispatch against. */
259
279
  interface LocalDeployment {
260
280
  token?: string;
@@ -375,16 +395,39 @@ type PaidMcpChargeConfig = X402ChargeSettings;
375
395
  interface PaidMcpServerConfig {
376
396
  /** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
377
397
  charge: PaidMcpChargeConfig;
398
+ /** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES` (128 KiB). */
399
+ maxRequestBytes?: number;
378
400
  /** Name/version advertised in the MCP `initialize` handshake. Defaults to `lunora-paid-mcp`. */
379
401
  serverInfo?: {
380
402
  name: string;
381
403
  version: string;
382
404
  };
383
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>;
384
427
  /** A paid MCP server: register free/paid tools, then serve over Streamable HTTP. */
385
428
  interface PaidMcpServer {
386
429
  /** The Streamable-HTTP fetch handler; gates each paid `tools/call` behind x402. */
387
- readonly fetchHandler: McpFetchHandler;
430
+ readonly fetchHandler: PaidMcpFetchHandler;
388
431
  /** Register a **paid** tool: its dispatch runs the x402 charge middleware first. */
389
432
  paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
390
433
  /** Register a **free** tool (coexists with paid tools on the same server). */
@@ -466,4 +509,4 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
466
509
  * better-auth's MCP OAuth gate (`requireMcpAuth` from `@lunora/auth/plugins`)
467
510
  * and can scope tool exposure to the access token's own scopes.
468
511
  */
469
- 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-hKbpa3Dg.mjs";import{createAuthedMcpFetchHandler as E,mcpTokenScopes as S}from"./packem_shared/createAuthedMcpFetchHandler-D9RPx2pl.mjs";import{createToolServer as O}from"./packem_shared/createToolServer-BtGuPyMU.mjs";import{createMcpFetchHandler as n}from"./packem_shared/createMcpFetchHandler-oQqcp6i5.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-BPPFBI2g.mjs";import{createPaidMcpServer as x}from"./packem_shared/createPaidMcpServer-XUo-pELu.mjs";import{connectStdio as i,createLunoraMcpServer as D}from"./packem_shared/connectStdio-D2XPJMyB.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-CMhsP7pi.mjs";import{O as G}from"./packem_shared/observability-tools-Ce0YK58T.mjs";import{serveStateless as P}from"./packem_shared/serveStateless-B3nFSJ-z.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
+ 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 @@
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-BqSYN5vr.mjs";import{toolDefinitions as E,callTool as R}from"./READ_ONLY_TOOL_DEFINITIONS-CMhsP7pi.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};
@@ -1 +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-CMhsP7pi.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};
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,12 @@ 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;
114
120
  /** What {@link serveStateless} accepts on top of the transport's own options. */
115
121
  interface ServeStatelessOptions extends HandleRequestOptions {
116
122
  /** Largest accepted request body, in bytes. Defaults to {@link DEFAULT_MAX_REQUEST_BYTES}. */
@@ -131,11 +137,13 @@ interface ServeStatelessOptions extends HandleRequestOptions {
131
137
  *
132
138
  * `options.parsedBody` lets a caller hand over a body it already read (e.g. the
133
139
  * paid-tool gate, which peeks the JSON-RPC message to price the call) so the
134
- * 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.
135
143
  *
136
144
  * Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
137
145
  * would otherwise skip it and leak a server + transport per failed request,
138
146
  * which on a public endpoint is exactly the request an attacker can repeat.
139
147
  */
140
148
  declare const serveStateless: (server: Server, request: Request, options?: ServeStatelessOptions) => Promise<Response>;
141
- export { 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 };
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,12 @@ 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;
114
120
  /** What {@link serveStateless} accepts on top of the transport's own options. */
115
121
  interface ServeStatelessOptions extends HandleRequestOptions {
116
122
  /** Largest accepted request body, in bytes. Defaults to {@link DEFAULT_MAX_REQUEST_BYTES}. */
@@ -131,11 +137,13 @@ interface ServeStatelessOptions extends HandleRequestOptions {
131
137
  *
132
138
  * `options.parsedBody` lets a caller hand over a body it already read (e.g. the
133
139
  * paid-tool gate, which peeks the JSON-RPC message to price the call) so the
134
- * 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.
135
143
  *
136
144
  * Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
137
145
  * would otherwise skip it and leak a server + transport per failed request,
138
146
  * which on a public endpoint is exactly the request an attacker can repeat.
139
147
  */
140
148
  declare const serveStateless: (server: Server, request: Request, options?: ServeStatelessOptions) => Promise<Response>;
141
- export { 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 };
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.106",
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.72",
56
+ "@lunora/client": "1.0.0-alpha.73",
57
57
  "@lunora/errors": "1.0.0-alpha.30",
58
- "@lunora/shard-engine": "1.0.0-alpha.53",
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
- import{createToolServer as o}from"./createToolServer-BtGuPyMU.mjs";import{serveStateless as s}from"./serveStateless-B3nFSJ-z.mjs";import{DEFAULT_MAX_REQUEST_BYTES as i}from"./serveStateless-B3nFSJ-z.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 +0,0 @@
1
- import{LunoraError as c}from"@lunora/errors";import{O as T,e as d,a as S,c as E,o as u}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"}],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)),D=(t,n=!1)=>[...I,...n===!0?T:[],...t===!0?b:[]],N=t=>{const{functionPath:n}=t;if(typeof n!="string"||n.length===0)throw new c("INTERNAL",'"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=N(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}`)},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 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=N(e),s=(await y(t)).find(A=>A.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,U as callTool,D as toolDefinitions};
@@ -1 +0,0 @@
1
- import{serveStateless as n}from"./serveStateless-B3nFSJ-z.mjs";import{createLunoraMcpServer as o}from"./connectStdio-D2XPJMyB.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-B3nFSJ-z.mjs";import{createLunoraMcpServer as o}from"./connectStdio-D2XPJMyB.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-B3nFSJ-z.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 y}from"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";const p=128*1024,i=e=>new TextEncoder().encode(e).length,a=(e,r,s)=>Response.json({error:{code:r,message:s},id:null,jsonrpc:"2.0"},{status:e}),c=()=>a(400,-32600,"batched requests are not supported: send one JSON-RPC message per request"),u=async(e,r)=>{if(e.method!=="POST")return{parsedBody:r?.parsedBody};if(r?.parsedBody!==void 0)return Array.isArray(r.parsedBody)?{response:c()}:{parsedBody:r.parsedBody};const s=r?.maxRequestBytes??p,t={response:a(413,-32600,`request body exceeds ${String(s)} bytes`)},n=Number(e.headers.get("content-length"));if(Number.isFinite(n)&&n>s)return t;const d=await e.text();if(i(d)>s)return t;let o;try{o=JSON.parse(d)}catch{return{response:a(400,-32700,"parse error: body is not valid JSON")}}return Array.isArray(o)?{response:c()}:{parsedBody:o}},h=async(e,r,s)=>{const t=await u(r,s);if("response"in t)return t.response;const n=new y({enableJsonResponse:!0,sessionIdGenerator:void 0});try{return await e.connect(n),await n.handleRequest(r,{authInfo:s?.authInfo,parsedBody:t.parsedBody})}finally{n.close().catch(()=>{}),e.close().catch(()=>{})}};export{p as DEFAULT_MAX_REQUEST_BYTES,h as serveStateless};