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