@lunora/mcp 1.0.0-alpha.106 → 1.0.0-alpha.108
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.
- package/README.md +8 -1
- package/dist/bin.mjs +1 -1
- package/dist/docs/index.d.mts +8 -3
- package/dist/docs/index.d.ts +8 -3
- package/dist/docs/index.mjs +1 -1
- package/dist/index.d.mts +63 -5
- package/dist/index.d.ts +63 -5
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs +1 -0
- package/dist/packem_shared/DOCS_SERVER_NAME-GSrAnODG.mjs +1 -0
- package/dist/packem_shared/{LOCAL_SERVER_NAME-BPPFBI2g.mjs → LOCAL_SERVER_NAME-Do7MU6Kd.mjs} +1 -1
- package/dist/packem_shared/{OBSERVABILITY_TOOL_DEFINITIONS-KrCukPbu.mjs → OBSERVABILITY_TOOL_DEFINITIONS-Byqgb9wh.mjs} +1 -1
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-_aG40yWx.mjs +1 -0
- package/dist/packem_shared/{connectStdio-D2XPJMyB.mjs → connectStdio-BBtfW4UB.mjs} +1 -1
- package/dist/packem_shared/createAuthedMcpFetchHandler-DwOzAueC.mjs +1 -0
- package/dist/packem_shared/createMcpFetchHandler-DSK2X9Hd.mjs +1 -0
- package/dist/packem_shared/createPaidMcpServer-BIBKMtxs.mjs +1 -0
- package/dist/packem_shared/observability-tools-B-g9Y9IT.mjs +1 -0
- package/dist/packem_shared/{serve-stateless.d-Qu1g6Pf4.d.mts → serve-stateless.d-CKsbI0fP.d.mts} +16 -3
- package/dist/packem_shared/{serve-stateless.d-Qu1g6Pf4.d.ts → serve-stateless.d-CKsbI0fP.d.ts} +16 -3
- package/package.json +3 -3
- package/dist/packem_shared/DOCS_SERVER_NAME-hHomMT1b.mjs +0 -1
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-CMhsP7pi.mjs +0 -1
- package/dist/packem_shared/createAuthedMcpFetchHandler-D9RPx2pl.mjs +0 -1
- package/dist/packem_shared/createMcpFetchHandler-oQqcp6i5.mjs +0 -1
- package/dist/packem_shared/createPaidMcpServer-XUo-pELu.mjs +0 -1
- package/dist/packem_shared/observability-tools-Ce0YK58T.mjs +0 -1
- 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
|
-
|
|
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-
|
|
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)}
|
package/dist/docs/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-
|
|
1
|
+
import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-CKsbI0fP.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-
|
|
52
|
+
d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-CKsbI0fP.mjs";
|
|
53
53
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
54
54
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
55
55
|
/**
|
|
@@ -135,7 +135,12 @@ declare const DOCS_SERVER_NAME = "lunora-docs";
|
|
|
135
135
|
interface DocsMcpServerOptions {
|
|
136
136
|
/** The documentation source the tools read. */
|
|
137
137
|
index: DocsIndex;
|
|
138
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
140
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
141
|
+
* (re-exported at the foot of this module), which a value that is not a
|
|
142
|
+
* non-negative safe integer also falls back to.
|
|
143
|
+
*/
|
|
139
144
|
maxRequestBytes?: number;
|
|
140
145
|
/**
|
|
141
146
|
* Version reported in the handshake. Defaults to `"0.0.0"` — a docs site
|
package/dist/docs/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-
|
|
1
|
+
import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-CKsbI0fP.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-
|
|
52
|
+
d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-CKsbI0fP.js";
|
|
53
53
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
54
54
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
55
55
|
/**
|
|
@@ -135,7 +135,12 @@ declare const DOCS_SERVER_NAME = "lunora-docs";
|
|
|
135
135
|
interface DocsMcpServerOptions {
|
|
136
136
|
/** The documentation source the tools read. */
|
|
137
137
|
index: DocsIndex;
|
|
138
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
140
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
141
|
+
* (re-exported at the foot of this module), which a value that is not a
|
|
142
|
+
* non-negative safe integer also falls back to.
|
|
143
|
+
*/
|
|
139
144
|
maxRequestBytes?: number;
|
|
140
145
|
/**
|
|
141
146
|
* Version reported in the handshake. Defaults to `"0.0.0"` — a docs site
|
package/dist/docs/index.mjs
CHANGED
|
@@ -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-
|
|
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-GSrAnODG.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-
|
|
3
|
-
export { type d as McpServerInfo, type S as ServeStatelessOptions, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-
|
|
2
|
+
import { T as ToolDefinition, f as ToolResult, e as ToolInputSchema, a as McpFetchHandler, b as McpTool } from "./packem_shared/serve-stateless.d-CKsbI0fP.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-CKsbI0fP.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,13 @@ 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
|
+
/**
|
|
219
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
220
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
221
|
+
* (128 KiB), which a value that is not a non-negative safe integer also
|
|
222
|
+
* falls back to.
|
|
223
|
+
*/
|
|
224
|
+
maxRequestBytes?: number;
|
|
218
225
|
/**
|
|
219
226
|
* The OAuth gate to mount the MCP server behind. Pass
|
|
220
227
|
* `(handler) => requireMcpAuth(auth, handler, opts)` from
|
|
@@ -247,14 +254,37 @@ declare const mcpTokenScopes: (claims: McpAccessTokenClaims) => ReadonlySet<stri
|
|
|
247
254
|
* `server` (resolved against the verified claims) and serves it through
|
|
248
255
|
* {@link serveStateless}, exactly as the unprotected `createMcpFetchHandler`
|
|
249
256
|
* does — the transport behaviour is identical, only the gate is new.
|
|
257
|
+
*
|
|
258
|
+
* A fixed `server` object names one deployment, so its `LunoraClient` is
|
|
259
|
+
* resolved once and shared: the public-function registry memo in `./tools` is
|
|
260
|
+
* keyed by client identity and never hits when each request builds its own. The
|
|
261
|
+
* `(claims) => …` form is per-request by construction — the claims decide which
|
|
262
|
+
* deployment and token to use — so it keeps a client per request.
|
|
250
263
|
*/
|
|
251
264
|
declare const createAuthedMcpFetchHandler: (options: AuthedMcpFetchHandlerOptions) => McpFetchHandler;
|
|
265
|
+
/** {@link createMcpFetchHandler} options: the server's, plus this transport's body limit. */
|
|
266
|
+
interface McpFetchHandlerOptions extends LunoraMcpServerOptions {
|
|
267
|
+
/**
|
|
268
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
269
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
270
|
+
* (128 KiB), which a value that is not a non-negative safe integer also
|
|
271
|
+
* falls back to.
|
|
272
|
+
*/
|
|
273
|
+
maxRequestBytes?: number;
|
|
274
|
+
}
|
|
252
275
|
/**
|
|
253
276
|
* Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
|
|
254
277
|
* invocation constructs a fresh proxy server and serves the request through
|
|
255
278
|
* {@link serveStateless}.
|
|
279
|
+
*
|
|
280
|
+
* The `LunoraClient` is resolved ONCE, here, and shared by every request: it is
|
|
281
|
+
* the same deployment on each one, and the public-function registry memo in
|
|
282
|
+
* `./tools` is keyed by client identity, so a per-request client would re-fetch
|
|
283
|
+
* that registry on every tool call. A misconfiguration (`url` without `token`)
|
|
284
|
+
* therefore throws when the handler is built rather than on first request,
|
|
285
|
+
* which is where `createLunoraMcpServer` already documents reporting it.
|
|
256
286
|
*/
|
|
257
|
-
declare const createMcpFetchHandler: (options:
|
|
287
|
+
declare const createMcpFetchHandler: (options: McpFetchHandlerOptions) => McpFetchHandler;
|
|
258
288
|
/** A Lunora deployment the tools dispatch against. */
|
|
259
289
|
interface LocalDeployment {
|
|
260
290
|
token?: string;
|
|
@@ -375,16 +405,44 @@ type PaidMcpChargeConfig = X402ChargeSettings;
|
|
|
375
405
|
interface PaidMcpServerConfig {
|
|
376
406
|
/** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
|
|
377
407
|
charge: PaidMcpChargeConfig;
|
|
408
|
+
/**
|
|
409
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
410
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
411
|
+
* (128 KiB), which a value that is not a non-negative safe integer also
|
|
412
|
+
* falls back to.
|
|
413
|
+
*/
|
|
414
|
+
maxRequestBytes?: number;
|
|
378
415
|
/** Name/version advertised in the MCP `initialize` handshake. Defaults to `lunora-paid-mcp`. */
|
|
379
416
|
serverInfo?: {
|
|
380
417
|
name: string;
|
|
381
418
|
version: string;
|
|
382
419
|
};
|
|
383
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* The Worker execution context, as this module reads it: only `waitUntil`, and
|
|
423
|
+
* structurally, so the package takes no `@cloudflare/workers-types` dependency.
|
|
424
|
+
*/
|
|
425
|
+
interface PaidMcpExecutionContext {
|
|
426
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* The paid server's fetch handler.
|
|
430
|
+
*
|
|
431
|
+
* Unlike the free `McpFetchHandler` it takes the Worker's full
|
|
432
|
+
* `(request, env, ctx)` triple — the shape `export default { fetch }` is called
|
|
433
|
+
* with — because the x402 receipt sink NEEDS `ctx.waitUntil`: work that is
|
|
434
|
+
* neither awaited into the response nor registered with it is cancelled when the
|
|
435
|
+
* request ends, so an async `onReceipt` (inserting the settled payment into a
|
|
436
|
+
* durable table) frequently never runs while the money has already moved
|
|
437
|
+
* on-chain. `env` is accepted and ignored so the handler drops straight into the
|
|
438
|
+
* default export; both are optional, so a non-Workers caller may still invoke it
|
|
439
|
+
* with a bare `Request` (the middleware then simply sees no `waitUntil`).
|
|
440
|
+
*/
|
|
441
|
+
type PaidMcpFetchHandler = (request: Request, env?: unknown, context?: PaidMcpExecutionContext) => Promise<Response>;
|
|
384
442
|
/** A paid MCP server: register free/paid tools, then serve over Streamable HTTP. */
|
|
385
443
|
interface PaidMcpServer {
|
|
386
444
|
/** The Streamable-HTTP fetch handler; gates each paid `tools/call` behind x402. */
|
|
387
|
-
readonly fetchHandler:
|
|
445
|
+
readonly fetchHandler: PaidMcpFetchHandler;
|
|
388
446
|
/** Register a **paid** tool: its dispatch runs the x402 charge middleware first. */
|
|
389
447
|
paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
|
|
390
448
|
/** Register a **free** tool (coexists with paid tools on the same server). */
|
|
@@ -466,4 +524,4 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
|
|
|
466
524
|
* better-auth's MCP OAuth gate (`requireMcpAuth` from `@lunora/auth/plugins`)
|
|
467
525
|
* and can scope tool exposure to the access token's own scopes.
|
|
468
526
|
*/
|
|
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 };
|
|
527
|
+
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-
|
|
3
|
-
export { type d as McpServerInfo, type S as ServeStatelessOptions, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-
|
|
2
|
+
import { T as ToolDefinition, f as ToolResult, e as ToolInputSchema, a as McpFetchHandler, b as McpTool } from "./packem_shared/serve-stateless.d-CKsbI0fP.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-CKsbI0fP.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,13 @@ 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
|
+
/**
|
|
219
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
220
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
221
|
+
* (128 KiB), which a value that is not a non-negative safe integer also
|
|
222
|
+
* falls back to.
|
|
223
|
+
*/
|
|
224
|
+
maxRequestBytes?: number;
|
|
218
225
|
/**
|
|
219
226
|
* The OAuth gate to mount the MCP server behind. Pass
|
|
220
227
|
* `(handler) => requireMcpAuth(auth, handler, opts)` from
|
|
@@ -247,14 +254,37 @@ declare const mcpTokenScopes: (claims: McpAccessTokenClaims) => ReadonlySet<stri
|
|
|
247
254
|
* `server` (resolved against the verified claims) and serves it through
|
|
248
255
|
* {@link serveStateless}, exactly as the unprotected `createMcpFetchHandler`
|
|
249
256
|
* does — the transport behaviour is identical, only the gate is new.
|
|
257
|
+
*
|
|
258
|
+
* A fixed `server` object names one deployment, so its `LunoraClient` is
|
|
259
|
+
* resolved once and shared: the public-function registry memo in `./tools` is
|
|
260
|
+
* keyed by client identity and never hits when each request builds its own. The
|
|
261
|
+
* `(claims) => …` form is per-request by construction — the claims decide which
|
|
262
|
+
* deployment and token to use — so it keeps a client per request.
|
|
250
263
|
*/
|
|
251
264
|
declare const createAuthedMcpFetchHandler: (options: AuthedMcpFetchHandlerOptions) => McpFetchHandler;
|
|
265
|
+
/** {@link createMcpFetchHandler} options: the server's, plus this transport's body limit. */
|
|
266
|
+
interface McpFetchHandlerOptions extends LunoraMcpServerOptions {
|
|
267
|
+
/**
|
|
268
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
269
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
270
|
+
* (128 KiB), which a value that is not a non-negative safe integer also
|
|
271
|
+
* falls back to.
|
|
272
|
+
*/
|
|
273
|
+
maxRequestBytes?: number;
|
|
274
|
+
}
|
|
252
275
|
/**
|
|
253
276
|
* Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
|
|
254
277
|
* invocation constructs a fresh proxy server and serves the request through
|
|
255
278
|
* {@link serveStateless}.
|
|
279
|
+
*
|
|
280
|
+
* The `LunoraClient` is resolved ONCE, here, and shared by every request: it is
|
|
281
|
+
* the same deployment on each one, and the public-function registry memo in
|
|
282
|
+
* `./tools` is keyed by client identity, so a per-request client would re-fetch
|
|
283
|
+
* that registry on every tool call. A misconfiguration (`url` without `token`)
|
|
284
|
+
* therefore throws when the handler is built rather than on first request,
|
|
285
|
+
* which is where `createLunoraMcpServer` already documents reporting it.
|
|
256
286
|
*/
|
|
257
|
-
declare const createMcpFetchHandler: (options:
|
|
287
|
+
declare const createMcpFetchHandler: (options: McpFetchHandlerOptions) => McpFetchHandler;
|
|
258
288
|
/** A Lunora deployment the tools dispatch against. */
|
|
259
289
|
interface LocalDeployment {
|
|
260
290
|
token?: string;
|
|
@@ -375,16 +405,44 @@ type PaidMcpChargeConfig = X402ChargeSettings;
|
|
|
375
405
|
interface PaidMcpServerConfig {
|
|
376
406
|
/** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
|
|
377
407
|
charge: PaidMcpChargeConfig;
|
|
408
|
+
/**
|
|
409
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
410
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
411
|
+
* (128 KiB), which a value that is not a non-negative safe integer also
|
|
412
|
+
* falls back to.
|
|
413
|
+
*/
|
|
414
|
+
maxRequestBytes?: number;
|
|
378
415
|
/** Name/version advertised in the MCP `initialize` handshake. Defaults to `lunora-paid-mcp`. */
|
|
379
416
|
serverInfo?: {
|
|
380
417
|
name: string;
|
|
381
418
|
version: string;
|
|
382
419
|
};
|
|
383
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* The Worker execution context, as this module reads it: only `waitUntil`, and
|
|
423
|
+
* structurally, so the package takes no `@cloudflare/workers-types` dependency.
|
|
424
|
+
*/
|
|
425
|
+
interface PaidMcpExecutionContext {
|
|
426
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* The paid server's fetch handler.
|
|
430
|
+
*
|
|
431
|
+
* Unlike the free `McpFetchHandler` it takes the Worker's full
|
|
432
|
+
* `(request, env, ctx)` triple — the shape `export default { fetch }` is called
|
|
433
|
+
* with — because the x402 receipt sink NEEDS `ctx.waitUntil`: work that is
|
|
434
|
+
* neither awaited into the response nor registered with it is cancelled when the
|
|
435
|
+
* request ends, so an async `onReceipt` (inserting the settled payment into a
|
|
436
|
+
* durable table) frequently never runs while the money has already moved
|
|
437
|
+
* on-chain. `env` is accepted and ignored so the handler drops straight into the
|
|
438
|
+
* default export; both are optional, so a non-Workers caller may still invoke it
|
|
439
|
+
* with a bare `Request` (the middleware then simply sees no `waitUntil`).
|
|
440
|
+
*/
|
|
441
|
+
type PaidMcpFetchHandler = (request: Request, env?: unknown, context?: PaidMcpExecutionContext) => Promise<Response>;
|
|
384
442
|
/** A paid MCP server: register free/paid tools, then serve over Streamable HTTP. */
|
|
385
443
|
interface PaidMcpServer {
|
|
386
444
|
/** The Streamable-HTTP fetch handler; gates each paid `tools/call` behind x402. */
|
|
387
|
-
readonly fetchHandler:
|
|
445
|
+
readonly fetchHandler: PaidMcpFetchHandler;
|
|
388
446
|
/** Register a **paid** tool: its dispatch runs the x402 charge middleware first. */
|
|
389
447
|
paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
|
|
390
448
|
/** Register a **free** tool (coexists with paid tools on the same server). */
|
|
@@ -466,4 +524,4 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
|
|
|
466
524
|
* better-auth's MCP OAuth gate (`requireMcpAuth` from `@lunora/auth/plugins`)
|
|
467
525
|
* and can scope tool exposure to the access token's own scopes.
|
|
468
526
|
*/
|
|
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 };
|
|
527
|
+
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
|
|
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-DwOzAueC.mjs";import{createToolServer as l}from"./packem_shared/createToolServer-BtGuPyMU.mjs";import{createMcpFetchHandler as n}from"./packem_shared/createMcpFetchHandler-DSK2X9Hd.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-BIBKMtxs.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-CbbpkHRK.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 y}from"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";const u=128*1024,p=e=>e!==void 0&&Number.isSafeInteger(e)&&e>=0?e:u,l=async(e,t)=>{if(!e.body)return"";const r=e.body.getReader(),s=new TextDecoder;let n=0,o="";for(;;){const{done:i,value:a}=await r.read();if(i)break;if(a){if(n+=a.byteLength,n>t){r.cancel().catch(()=>{});return}o+=s.decode(a,{stream:!0})}}return o+s.decode()},d=(e,t,r)=>Response.json({error:{code:t,message:r},id:null,jsonrpc:"2.0"},{status:e}),c=()=>d(400,-32600,"batched requests are not supported: send one JSON-RPC message per request"),f=async(e,t)=>{if(e.method!=="POST")return{parsedBody:void 0};const r=p(t),s={response:d(413,-32600,`request body exceeds ${String(r)} bytes`)},n=Number(e.headers.get("content-length"));if(Number.isFinite(n)&&n>r)return s;const o=await l(e,r);if(o===void 0)return s;try{return{parsedBody:JSON.parse(o)}}catch{return{response:d(400,-32700,"parse error: body is not valid JSON")}}},b=async(e,t)=>{if(e.method!=="POST")return{parsedBody:t?.parsedBody};if(t?.parsedBody!==void 0)return Array.isArray(t.parsedBody)?{response:c()}:{parsedBody:t.parsedBody};const r=await f(e,t?.maxRequestBytes);return"response"in r?r:Array.isArray(r.parsedBody)?{response:c()}:r},B=async(e,t,r)=>{const s=await b(t,r);if("response"in s)return s.response;const n=new y({enableJsonResponse:!0,sessionIdGenerator:void 0});try{return await e.connect(n),await n.handleRequest(t,{authInfo:r?.authInfo,parsedBody:s.parsedBody})}finally{n.close().catch(()=>{}),e.close().catch(()=>{})}};export{u as DEFAULT_MAX_REQUEST_BYTES,f as readScreenedBody,B as serveStateless};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createToolServer as o}from"./createToolServer-BtGuPyMU.mjs";import{serveStateless as s}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{DEFAULT_MAX_REQUEST_BYTES as i}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.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};
|
package/dist/packem_shared/{LOCAL_SERVER_NAME-BPPFBI2g.mjs → LOCAL_SERVER_NAME-Do7MU6Kd.mjs}
RENAMED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-CbbpkHRK.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-CbbpkHRK.mjs";import{DEFAULT_MAX_REQUEST_BYTES as u}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.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-CbbpkHRK.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};
|
package/dist/packem_shared/{serve-stateless.d-Qu1g6Pf4.d.mts → serve-stateless.d-CKsbI0fP.d.mts}
RENAMED
|
@@ -111,9 +111,20 @@ 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
|
+
/**
|
|
123
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
124
|
+
* in, not after it is buffered. Defaults to
|
|
125
|
+
* {@link DEFAULT_MAX_REQUEST_BYTES}, which a value that is not a
|
|
126
|
+
* non-negative safe integer also falls back to (see `resolveLimit`).
|
|
127
|
+
*/
|
|
117
128
|
maxRequestBytes?: number;
|
|
118
129
|
}
|
|
119
130
|
/**
|
|
@@ -131,11 +142,13 @@ interface ServeStatelessOptions extends HandleRequestOptions {
|
|
|
131
142
|
*
|
|
132
143
|
* `options.parsedBody` lets a caller hand over a body it already read (e.g. the
|
|
133
144
|
* paid-tool gate, which peeks the JSON-RPC message to price the call) so the
|
|
134
|
-
* transport doesn't re-read a consumed stream.
|
|
145
|
+
* transport doesn't re-read a consumed stream. It is trusted as already
|
|
146
|
+
* screened, so read it with {@link readScreenedBody} — anything else hands this
|
|
147
|
+
* surface an unbounded body.
|
|
135
148
|
*
|
|
136
149
|
* Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
|
|
137
150
|
* would otherwise skip it and leak a server + transport per failed request,
|
|
138
151
|
* which on a public endpoint is exactly the request an attacker can repeat.
|
|
139
152
|
*/
|
|
140
153
|
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 };
|
|
154
|
+
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/dist/packem_shared/{serve-stateless.d-Qu1g6Pf4.d.ts → serve-stateless.d-CKsbI0fP.d.ts}
RENAMED
|
@@ -111,9 +111,20 @@ 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
|
+
/**
|
|
123
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
124
|
+
* in, not after it is buffered. Defaults to
|
|
125
|
+
* {@link DEFAULT_MAX_REQUEST_BYTES}, which a value that is not a
|
|
126
|
+
* non-negative safe integer also falls back to (see `resolveLimit`).
|
|
127
|
+
*/
|
|
117
128
|
maxRequestBytes?: number;
|
|
118
129
|
}
|
|
119
130
|
/**
|
|
@@ -131,11 +142,13 @@ interface ServeStatelessOptions extends HandleRequestOptions {
|
|
|
131
142
|
*
|
|
132
143
|
* `options.parsedBody` lets a caller hand over a body it already read (e.g. the
|
|
133
144
|
* paid-tool gate, which peeks the JSON-RPC message to price the call) so the
|
|
134
|
-
* transport doesn't re-read a consumed stream.
|
|
145
|
+
* transport doesn't re-read a consumed stream. It is trusted as already
|
|
146
|
+
* screened, so read it with {@link readScreenedBody} — anything else hands this
|
|
147
|
+
* surface an unbounded body.
|
|
135
148
|
*
|
|
136
149
|
* Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
|
|
137
150
|
* would otherwise skip it and leak a server + transport per failed request,
|
|
138
151
|
* which on a public endpoint is exactly the request an attacker can repeat.
|
|
139
152
|
*/
|
|
140
153
|
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 };
|
|
154
|
+
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.
|
|
3
|
+
"version": "1.0.0-alpha.108",
|
|
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.
|
|
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.
|
|
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};
|