@lunora/mcp 1.0.0-alpha.135 → 1.0.0-alpha.137
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 +74 -2
- package/dist/bin.mjs +4 -4
- package/dist/index.d.mts +33 -27
- package/dist/index.d.ts +33 -27
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/LOCAL_SERVER_NAME-BbopFImI.mjs +1 -0
- package/dist/packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-mO4JuHgV.mjs +1 -0
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-DKcVAOe2.mjs +1 -0
- package/dist/packem_shared/connectStdio-CQI9JGJC.mjs +1 -0
- package/dist/packem_shared/{createAuthedMcpFetchHandler-DwOzAueC.mjs → createAuthedMcpFetchHandler-Mfw8jlOV.mjs} +1 -1
- package/dist/packem_shared/{createMcpFetchHandler-DSK2X9Hd.mjs → createMcpFetchHandler-C5doZK5c.mjs} +1 -1
- package/dist/packem_shared/createPaidMcpServer-Ba1gMM4Y.mjs +1 -0
- package/dist/packem_shared/observability-tools-D4Z8dGS-.mjs +1 -0
- package/dist/packem_shared/promise-memo-OqVPWoUX.mjs +1 -0
- package/dist/packem_shared/tools-D-JWERWj.mjs +2 -0
- package/package.json +4 -4
- package/dist/packem_shared/LOCAL_SERVER_NAME-DPay9JzK.mjs +0 -1
- package/dist/packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-Byqgb9wh.mjs +0 -1
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-_aG40yWx.mjs +0 -1
- package/dist/packem_shared/connectStdio-BBtfW4UB.mjs +0 -1
- package/dist/packem_shared/createPaidMcpServer-BIBKMtxs.mjs +0 -1
- package/dist/packem_shared/observability-tools-B-g9Y9IT.mjs +0 -1
package/README.md
CHANGED
|
@@ -51,8 +51,8 @@ Part of the [Lunora](https://github.com/anolilab/lunora) framework — a type-sa
|
|
|
51
51
|
| `lunora_list_tables` | List the deployment's `.global()` tables with their row counts. |
|
|
52
52
|
| `lunora_get_function_schema` | Return a function's argument descriptors and kind by path, so a caller can construct a valid arguments object. |
|
|
53
53
|
| `lunora_run_query` | Run a query and return its result. Read-only. |
|
|
54
|
-
| `lunora_run_mutation` | Run a mutation
|
|
55
|
-
| `lunora_run_action` | Run an action
|
|
54
|
+
| `lunora_run_mutation` | Run a mutation. Writes data. Two-step: propose, then confirm with the returned `actionDigest`. |
|
|
55
|
+
| `lunora_run_action` | Run an action. May call external services. Two-step: propose, then confirm with the returned `actionDigest`. |
|
|
56
56
|
| `lunora_get_logs` | Read the deployment's recent log entries (newest first). Requires an admin token. |
|
|
57
57
|
| `lunora_get_issues` | List errors grouped into Issues by fingerprint, with counts and triage status. Requires an admin token. |
|
|
58
58
|
| `lunora_get_advisories` | List the deployment's schema/query advisories. Requires an admin token. |
|
|
@@ -68,8 +68,80 @@ Part of the [Lunora](https://github.com/anolilab/lunora) framework — a type-sa
|
|
|
68
68
|
2. lunora_get_function_schema → retrieve the argument descriptors for a specific path
|
|
69
69
|
3. lunora_run_query / lunora_run_mutation / lunora_run_action
|
|
70
70
|
→ call the function with a well-formed arguments object
|
|
71
|
+
(the two write tools take a second, confirming call)
|
|
71
72
|
```
|
|
72
73
|
|
|
74
|
+
### Write confirmation (the two-step handshake)
|
|
75
|
+
|
|
76
|
+
`LUNORA_MCP_ALLOW_WRITES` decides whether this server may write at **all**. It
|
|
77
|
+
never said anything about whether a _particular_ write was reviewed, so past that
|
|
78
|
+
gate `lunora_run_mutation` and `lunora_run_action` each take two calls.
|
|
79
|
+
|
|
80
|
+
The first call **executes nothing**. It returns the proposed action and a digest:
|
|
81
|
+
|
|
82
|
+
```jsonc
|
|
83
|
+
{
|
|
84
|
+
"status": "action_required",
|
|
85
|
+
"actionDigest": "1789129912052.0ZR2…",
|
|
86
|
+
"expiresAt": "2026-09-11T14:41:52.052Z",
|
|
87
|
+
"proposedAction": {
|
|
88
|
+
"tool": "lunora_run_mutation",
|
|
89
|
+
"kind": "mutation",
|
|
90
|
+
"functionPath": "messages:send",
|
|
91
|
+
"args": { "roomId": "r1", "text": "hi" },
|
|
92
|
+
},
|
|
93
|
+
"nextStep": "Show proposedAction to a human. To execute, call …",
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Render `proposedAction` for a human, then call the same tool again — before
|
|
98
|
+
`expiresAt` — with the identical `functionPath` / `args` / `shardKey` /
|
|
99
|
+
`idempotencyKey`, plus `confirmed: true` and that `actionDigest`. Only then does
|
|
100
|
+
the write happen.
|
|
101
|
+
|
|
102
|
+
The digest is `<expiresAt>.<signature>`, where the signature is an HMAC over a
|
|
103
|
+
canonical (sorted-key) encoding of the tool name, function path, arguments, shard
|
|
104
|
+
key, idempotency key **and that deadline**, keyed by the deployment's own
|
|
105
|
+
identity. So:
|
|
106
|
+
|
|
107
|
+
- **Argument key order is irrelevant** — re-serializing `args` does not invalidate a confirmation.
|
|
108
|
+
- **Any real edit invalidates it.** A different target, argument, or shard key produces a different digest, and the confirmation is refused with nothing written. That is the guarantee: what executes is exactly what was reviewed.
|
|
109
|
+
- **It expires after 10 minutes.** The deadline travels in the clear (the verifying instance has to read it) but is signed alongside the proposal, so moving it breaks the signature. An expired digest is refused, not silently re-proposed — call again without `confirmed` for a fresh one.
|
|
110
|
+
- **No server state is involved.** The HTTP handler serves statelessly (a fresh server per request), so a confirmation is revalidated by recomputation on whichever instance receives it, not looked up in a store.
|
|
111
|
+
|
|
112
|
+
`idempotencyKey` is optional and is folded into the digest. A client that timed
|
|
113
|
+
out can resubmit the confirmation it already holds, for as long as that digest is
|
|
114
|
+
inside its window, without asking for a second review; and a deliberately-repeated
|
|
115
|
+
identical write under a **new** key gets its own review instead of riding the
|
|
116
|
+
first one. It does **not** deduplicate the write: this server keeps no state
|
|
117
|
+
between requests and never forwards the key to your function, so a resubmitted
|
|
118
|
+
confirmed call executes again. Make the function itself idempotent if the write
|
|
119
|
+
must happen at most once.
|
|
120
|
+
|
|
121
|
+
#### What the handshake does not do
|
|
122
|
+
|
|
123
|
+
It binds **intent, not human presence**, and the difference matters when you
|
|
124
|
+
decide whether to enable writes at all.
|
|
125
|
+
|
|
126
|
+
A verified digest proves the call about to run is exactly the call that was
|
|
127
|
+
proposed, on this deployment, inside its window. It does **not** prove a human
|
|
128
|
+
saw it, and no server-side check can: an MCP server has no channel to a person —
|
|
129
|
+
no session, no end-user identity, no UI — and MCP deliberately puts the
|
|
130
|
+
human-in-the-loop at the **host**. The client is what renders a tool call for
|
|
131
|
+
approval. A client that asks nobody can take the digest it was just handed, send
|
|
132
|
+
it straight back with `confirmed: true`, and the write runs.
|
|
133
|
+
|
|
134
|
+
That is why writes are off by default and refused at dispatch as well as omitted
|
|
135
|
+
from `ListTools`: enabling `LUNORA_MCP_ALLOW_WRITES` is **your** statement that
|
|
136
|
+
the client on the other end does the asking. Treat the handshake as a client-UI
|
|
137
|
+
affordance and an audit record of what was proposed, not as a gate against the
|
|
138
|
+
model.
|
|
139
|
+
|
|
140
|
+
Two more scope limits, stated rather than implied:
|
|
141
|
+
|
|
142
|
+
- **The digest is deployment-wide, not principal-bound.** Its key is the domain separator, the deployment URL and the admin bearer — nothing identifying a user. On an OAuth-fronted server (`createAuthedMcpFetchHandler`) every principal shares that bearer, so within the 10-minute window any principal holding write scope can confirm another's identical proposal. Binding it to a person would mean folding the verified `sub` claim into the signing key, which this package does not do today.
|
|
143
|
+
- **It survives an admin-bearer rotation only as long as the bearer does.** Rotating `LUNORA_ADMIN_TOKEN`, or moving the deployment URL, invalidates every outstanding digest — which is the intended behaviour, not a bug to work around.
|
|
144
|
+
|
|
73
145
|
### Observability tools (privileged)
|
|
74
146
|
|
|
75
147
|
The five `lunora_get_*` observability tools are read-only, but they surface the
|
package/dist/bin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{parseAgentsEnv as N}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{connectStdio as
|
|
3
|
-
`),new
|
|
4
|
-
`),new
|
|
5
|
-
`),new
|
|
2
|
+
import{parseAgentsEnv as N}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{connectStdio as l}from"./packem_shared/connectStdio-CQI9JGJC.mjs";const E=new Set(["1","on","true","yes"]),o=r=>r!==void 0&&E.has(r.trim().toLowerCase());class s extends Error{code;constructor(e,a){super(e),this.name="BinError",this.code=a}}const O=async(r,e={})=>{const a=e.connect??l,i=e.writeError??(t=>{process.stderr.write(t)}),n=r.LUNORA_URL;if(n===void 0||n.length===0)throw i(`lunora-mcp: LUNORA_URL environment variable is required
|
|
3
|
+
`),new s("LUNORA_URL environment variable is required",1);const c=r.LUNORA_ADMIN_TOKEN;if(c===void 0||c.length===0)throw i(`lunora-mcp: LUNORA_ADMIN_TOKEN environment variable is required (every tool reads admin-gated routes)
|
|
4
|
+
`),new s("LUNORA_ADMIN_TOKEN environment variable is required",1);const A=Number(r.LUNORA_MCP_AGENT_TIMEOUT_MS),_=Number.isFinite(A)&&A>0?A:void 0;try{await a({agents:N(r.LUNORA_MCP_AGENTS),allowAgents:o(r.LUNORA_MCP_ALLOW_AGENTS),allowDataReads:o(r.LUNORA_MCP_ALLOW_DATA_READS),allowObservability:o(r.LUNORA_MCP_ALLOW_OBSERVABILITY),allowWrites:o(r.LUNORA_MCP_ALLOW_WRITES),token:c,url:n,..._===void 0?{}:{agentMaxWaitMs:_}})}catch(t){const L=t instanceof Error?t.message:String(t);throw i(`lunora-mcp: failed to start — ${L}
|
|
5
|
+
`),new s(`failed to start — ${L}`,1)}};try{await O(process.env)}catch(r){process.exit(r instanceof s?r.code:1)}
|
package/dist/index.d.mts
CHANGED
|
@@ -15,31 +15,23 @@ declare const READ_ONLY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
|
|
|
15
15
|
/** The write tool surface (mutations + actions). Exposed ONLY when writes are enabled. */
|
|
16
16
|
declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
|
|
17
17
|
/**
|
|
18
|
-
* The tools this server advertises, in
|
|
19
|
-
*
|
|
20
|
-
* - the
|
|
21
|
-
*
|
|
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;
|
|
25
|
-
* - the write surface, exposed only when `allowWrites` is set.
|
|
26
|
-
*
|
|
27
|
-
* Both gates OMIT rather than refuse: an AI agent can't invoke what it can't
|
|
28
|
-
* see. Dispatch re-checks both in {@link callTool}, so the guarantee does not
|
|
29
|
-
* depend on a client honouring the advertised list.
|
|
18
|
+
* The tools this server advertises: every family whose gate is open, in table
|
|
19
|
+
* order. A gated family is OMITTED rather than refused — an AI agent can't
|
|
20
|
+
* invoke what it can't see — and {@link callTool} re-checks the same gate at
|
|
21
|
+
* dispatch, so the guarantee does not depend on a client honouring this list.
|
|
30
22
|
*/
|
|
31
|
-
declare const toolDefinitions: (allowWrites: boolean, allowObservability?: boolean) => ReadonlyArray<ToolDefinition>;
|
|
23
|
+
declare const toolDefinitions: (allowWrites: boolean, allowObservability?: boolean, allowDataReads?: boolean) => ReadonlyArray<ToolDefinition>;
|
|
32
24
|
/**
|
|
33
|
-
* Dispatch a tool call against `client
|
|
34
|
-
*
|
|
35
|
-
*
|
|
25
|
+
* Dispatch a tool call against `client`: find the family that owns `name`, check
|
|
26
|
+
* its gate, hand off. Unknown tools and thrown errors are returned as `isError`
|
|
27
|
+
* results (rather than rejections) so the calling model sees the failure as tool
|
|
28
|
+
* output, per the MCP convention.
|
|
36
29
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* dispatch, not just in the advertised tool list.
|
|
30
|
+
* Each gate is enforced HERE as well as in {@link toolDefinitions}, so a call to
|
|
31
|
+
* a gated tool is refused even if the client somehow names one it was never
|
|
32
|
+
* shown.
|
|
41
33
|
*/
|
|
42
|
-
declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, allowObservability?: boolean) => Promise<ToolResult>;
|
|
34
|
+
declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, allowObservability?: boolean, allowDataReads?: boolean) => Promise<ToolResult>;
|
|
43
35
|
/**
|
|
44
36
|
* Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
|
|
45
37
|
* MCP tool an external agent can call. The capability boundary is the MCP-server
|
|
@@ -127,6 +119,7 @@ interface LunoraMcpServerOptions {
|
|
|
127
119
|
* without it the tools are omitted from the advertised list AND refused at
|
|
128
120
|
* dispatch. Only takes effect when a `token` resolved.
|
|
129
121
|
*/
|
|
122
|
+
allowDataReads?: boolean;
|
|
130
123
|
allowObservability?: boolean;
|
|
131
124
|
/**
|
|
132
125
|
* Expose the write tools (`lunora_run_mutation` / `lunora_run_action`).
|
|
@@ -332,8 +325,9 @@ declare const LOCAL_SERVER_NAME = "lunora";
|
|
|
332
325
|
declare const NO_DEPLOYMENT_MESSAGE = "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).";
|
|
333
326
|
/**
|
|
334
327
|
* Assemble the tool list, in the order it is advertised: docs first (the
|
|
335
|
-
* surface that always works), then the caller's extras, then the
|
|
336
|
-
*
|
|
328
|
+
* surface that always works), then the caller's extras, then the error catalog
|
|
329
|
+
* (compiled in, so it needs no deployment either), then the deployment tools.
|
|
330
|
+
* Order also decides precedence — `createToolServer` keeps the first
|
|
337
331
|
* registration of a duplicated name.
|
|
338
332
|
*
|
|
339
333
|
* `clientFor` is the shared client cache built once by
|
|
@@ -469,7 +463,9 @@ export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type AuthedMcpFetchHand
|
|
|
469
463
|
* The deployment server: It registers tools for introspecting a deployment
|
|
470
464
|
* (`lunora_list_functions`, `lunora_list_tables`) and invoking its functions
|
|
471
465
|
* (`lunora_run_query`, plus `lunora_run_mutation` and `lunora_run_action` when
|
|
472
|
-
* writes are enabled), each backed by `LunoraClient` over HTTP RPC
|
|
466
|
+
* writes are enabled), each backed by `LunoraClient` over HTTP RPC, plus
|
|
467
|
+
* `lunora_explain_error` — a credential-free read of the static error catalog
|
|
468
|
+
* (status, title, hint, matched solution) that needs no deployment at all. It also
|
|
473
469
|
* exposes the deployment's observability reads (`lunora_get_logs`,
|
|
474
470
|
* `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
475
471
|
* `lunora_get_migration_status`) when `allowObservability` (or the
|
|
@@ -477,7 +473,11 @@ export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type AuthedMcpFetchHand
|
|
|
477
473
|
* production user data, so they are omitted entirely without it. The server is
|
|
478
474
|
* read-only by default — the write tools are exposed only when `allowWrites`
|
|
479
475
|
* (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
|
|
480
|
-
* allowlisted against the deployment's discovered public functions.
|
|
476
|
+
* allowlisted against the deployment's discovered public functions. Past that
|
|
477
|
+
* gate the two write tools run a two-step confirmation handshake: the first
|
|
478
|
+
* call writes nothing and returns the proposed action plus a self-verifying
|
|
479
|
+
* `actionDigest`, and only a second call carrying `confirmed: true` and that
|
|
480
|
+
* digest executes (see `./write-confirmation`). It can also
|
|
481
481
|
* front durable `@lunora/agent` runs as `agent_<name>` tools when `allowAgents`
|
|
482
482
|
* (or `LUNORA_MCP_ALLOW_AGENTS` + `LUNORA_MCP_AGENTS`) is set. Run the
|
|
483
483
|
* `lunora-mcp` binary (configured via the `LUNORA_URL`, `LUNORA_ADMIN_TOKEN`,
|
|
@@ -501,7 +501,9 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
|
|
|
501
501
|
* The deployment server: It registers tools for introspecting a deployment
|
|
502
502
|
* (`lunora_list_functions`, `lunora_list_tables`) and invoking its functions
|
|
503
503
|
* (`lunora_run_query`, plus `lunora_run_mutation` and `lunora_run_action` when
|
|
504
|
-
* writes are enabled), each backed by `LunoraClient` over HTTP RPC
|
|
504
|
+
* writes are enabled), each backed by `LunoraClient` over HTTP RPC, plus
|
|
505
|
+
* `lunora_explain_error` — a credential-free read of the static error catalog
|
|
506
|
+
* (status, title, hint, matched solution) that needs no deployment at all. It also
|
|
505
507
|
* exposes the deployment's observability reads (`lunora_get_logs`,
|
|
506
508
|
* `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
507
509
|
* `lunora_get_migration_status`) when `allowObservability` (or the
|
|
@@ -509,7 +511,11 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
|
|
|
509
511
|
* production user data, so they are omitted entirely without it. The server is
|
|
510
512
|
* read-only by default — the write tools are exposed only when `allowWrites`
|
|
511
513
|
* (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
|
|
512
|
-
* allowlisted against the deployment's discovered public functions.
|
|
514
|
+
* allowlisted against the deployment's discovered public functions. Past that
|
|
515
|
+
* gate the two write tools run a two-step confirmation handshake: the first
|
|
516
|
+
* call writes nothing and returns the proposed action plus a self-verifying
|
|
517
|
+
* `actionDigest`, and only a second call carrying `confirmed: true` and that
|
|
518
|
+
* digest executes (see `./write-confirmation`). It can also
|
|
513
519
|
* front durable `@lunora/agent` runs as `agent_<name>` tools when `allowAgents`
|
|
514
520
|
* (or `LUNORA_MCP_ALLOW_AGENTS` + `LUNORA_MCP_AGENTS`) is set. Run the
|
|
515
521
|
* `lunora-mcp` binary (configured via the `LUNORA_URL`, `LUNORA_ADMIN_TOKEN`,
|
package/dist/index.d.ts
CHANGED
|
@@ -15,31 +15,23 @@ declare const READ_ONLY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
|
|
|
15
15
|
/** The write tool surface (mutations + actions). Exposed ONLY when writes are enabled. */
|
|
16
16
|
declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
|
|
17
17
|
/**
|
|
18
|
-
* The tools this server advertises, in
|
|
19
|
-
*
|
|
20
|
-
* - the
|
|
21
|
-
*
|
|
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;
|
|
25
|
-
* - the write surface, exposed only when `allowWrites` is set.
|
|
26
|
-
*
|
|
27
|
-
* Both gates OMIT rather than refuse: an AI agent can't invoke what it can't
|
|
28
|
-
* see. Dispatch re-checks both in {@link callTool}, so the guarantee does not
|
|
29
|
-
* depend on a client honouring the advertised list.
|
|
18
|
+
* The tools this server advertises: every family whose gate is open, in table
|
|
19
|
+
* order. A gated family is OMITTED rather than refused — an AI agent can't
|
|
20
|
+
* invoke what it can't see — and {@link callTool} re-checks the same gate at
|
|
21
|
+
* dispatch, so the guarantee does not depend on a client honouring this list.
|
|
30
22
|
*/
|
|
31
|
-
declare const toolDefinitions: (allowWrites: boolean, allowObservability?: boolean) => ReadonlyArray<ToolDefinition>;
|
|
23
|
+
declare const toolDefinitions: (allowWrites: boolean, allowObservability?: boolean, allowDataReads?: boolean) => ReadonlyArray<ToolDefinition>;
|
|
32
24
|
/**
|
|
33
|
-
* Dispatch a tool call against `client
|
|
34
|
-
*
|
|
35
|
-
*
|
|
25
|
+
* Dispatch a tool call against `client`: find the family that owns `name`, check
|
|
26
|
+
* its gate, hand off. Unknown tools and thrown errors are returned as `isError`
|
|
27
|
+
* results (rather than rejections) so the calling model sees the failure as tool
|
|
28
|
+
* output, per the MCP convention.
|
|
36
29
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* dispatch, not just in the advertised tool list.
|
|
30
|
+
* Each gate is enforced HERE as well as in {@link toolDefinitions}, so a call to
|
|
31
|
+
* a gated tool is refused even if the client somehow names one it was never
|
|
32
|
+
* shown.
|
|
41
33
|
*/
|
|
42
|
-
declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, allowObservability?: boolean) => Promise<ToolResult>;
|
|
34
|
+
declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, allowObservability?: boolean, allowDataReads?: boolean) => Promise<ToolResult>;
|
|
43
35
|
/**
|
|
44
36
|
* Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
|
|
45
37
|
* MCP tool an external agent can call. The capability boundary is the MCP-server
|
|
@@ -127,6 +119,7 @@ interface LunoraMcpServerOptions {
|
|
|
127
119
|
* without it the tools are omitted from the advertised list AND refused at
|
|
128
120
|
* dispatch. Only takes effect when a `token` resolved.
|
|
129
121
|
*/
|
|
122
|
+
allowDataReads?: boolean;
|
|
130
123
|
allowObservability?: boolean;
|
|
131
124
|
/**
|
|
132
125
|
* Expose the write tools (`lunora_run_mutation` / `lunora_run_action`).
|
|
@@ -332,8 +325,9 @@ declare const LOCAL_SERVER_NAME = "lunora";
|
|
|
332
325
|
declare const NO_DEPLOYMENT_MESSAGE = "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).";
|
|
333
326
|
/**
|
|
334
327
|
* Assemble the tool list, in the order it is advertised: docs first (the
|
|
335
|
-
* surface that always works), then the caller's extras, then the
|
|
336
|
-
*
|
|
328
|
+
* surface that always works), then the caller's extras, then the error catalog
|
|
329
|
+
* (compiled in, so it needs no deployment either), then the deployment tools.
|
|
330
|
+
* Order also decides precedence — `createToolServer` keeps the first
|
|
337
331
|
* registration of a duplicated name.
|
|
338
332
|
*
|
|
339
333
|
* `clientFor` is the shared client cache built once by
|
|
@@ -469,7 +463,9 @@ export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type AuthedMcpFetchHand
|
|
|
469
463
|
* The deployment server: It registers tools for introspecting a deployment
|
|
470
464
|
* (`lunora_list_functions`, `lunora_list_tables`) and invoking its functions
|
|
471
465
|
* (`lunora_run_query`, plus `lunora_run_mutation` and `lunora_run_action` when
|
|
472
|
-
* writes are enabled), each backed by `LunoraClient` over HTTP RPC
|
|
466
|
+
* writes are enabled), each backed by `LunoraClient` over HTTP RPC, plus
|
|
467
|
+
* `lunora_explain_error` — a credential-free read of the static error catalog
|
|
468
|
+
* (status, title, hint, matched solution) that needs no deployment at all. It also
|
|
473
469
|
* exposes the deployment's observability reads (`lunora_get_logs`,
|
|
474
470
|
* `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
475
471
|
* `lunora_get_migration_status`) when `allowObservability` (or the
|
|
@@ -477,7 +473,11 @@ export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type AuthedMcpFetchHand
|
|
|
477
473
|
* production user data, so they are omitted entirely without it. The server is
|
|
478
474
|
* read-only by default — the write tools are exposed only when `allowWrites`
|
|
479
475
|
* (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
|
|
480
|
-
* allowlisted against the deployment's discovered public functions.
|
|
476
|
+
* allowlisted against the deployment's discovered public functions. Past that
|
|
477
|
+
* gate the two write tools run a two-step confirmation handshake: the first
|
|
478
|
+
* call writes nothing and returns the proposed action plus a self-verifying
|
|
479
|
+
* `actionDigest`, and only a second call carrying `confirmed: true` and that
|
|
480
|
+
* digest executes (see `./write-confirmation`). It can also
|
|
481
481
|
* front durable `@lunora/agent` runs as `agent_<name>` tools when `allowAgents`
|
|
482
482
|
* (or `LUNORA_MCP_ALLOW_AGENTS` + `LUNORA_MCP_AGENTS`) is set. Run the
|
|
483
483
|
* `lunora-mcp` binary (configured via the `LUNORA_URL`, `LUNORA_ADMIN_TOKEN`,
|
|
@@ -501,7 +501,9 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
|
|
|
501
501
|
* The deployment server: It registers tools for introspecting a deployment
|
|
502
502
|
* (`lunora_list_functions`, `lunora_list_tables`) and invoking its functions
|
|
503
503
|
* (`lunora_run_query`, plus `lunora_run_mutation` and `lunora_run_action` when
|
|
504
|
-
* writes are enabled), each backed by `LunoraClient` over HTTP RPC
|
|
504
|
+
* writes are enabled), each backed by `LunoraClient` over HTTP RPC, plus
|
|
505
|
+
* `lunora_explain_error` — a credential-free read of the static error catalog
|
|
506
|
+
* (status, title, hint, matched solution) that needs no deployment at all. It also
|
|
505
507
|
* exposes the deployment's observability reads (`lunora_get_logs`,
|
|
506
508
|
* `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
507
509
|
* `lunora_get_migration_status`) when `allowObservability` (or the
|
|
@@ -509,7 +511,11 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
|
|
|
509
511
|
* production user data, so they are omitted entirely without it. The server is
|
|
510
512
|
* read-only by default — the write tools are exposed only when `allowWrites`
|
|
511
513
|
* (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
|
|
512
|
-
* allowlisted against the deployment's discovered public functions.
|
|
514
|
+
* allowlisted against the deployment's discovered public functions. Past that
|
|
515
|
+
* gate the two write tools run a two-step confirmation handshake: the first
|
|
516
|
+
* call writes nothing and returns the proposed action plus a self-verifying
|
|
517
|
+
* `actionDigest`, and only a second call carrying `confirmed: true` and that
|
|
518
|
+
* digest executes (see `./write-confirmation`). It can also
|
|
513
519
|
* front durable `@lunora/agent` runs as `agent_<name>` tools when `allowAgents`
|
|
514
520
|
* (or `LUNORA_MCP_ALLOW_AGENTS` + `LUNORA_MCP_AGENTS`) is set. Run the
|
|
515
521
|
* `lunora-mcp` binary (configured via the `LUNORA_URL`, `LUNORA_ADMIN_TOKEN`,
|
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 a}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{createAuthedMcpFetchHandler as S,mcpTokenScopes as _}from"./packem_shared/createAuthedMcpFetchHandler-Mfw8jlOV.mjs";import{createToolServer as l}from"./packem_shared/createToolServer-BtGuPyMU.mjs";import{createMcpFetchHandler as n}from"./packem_shared/createMcpFetchHandler-C5doZK5c.mjs";import{LOCAL_SERVER_NAME as N,NO_DEPLOYMENT_MESSAGE as s,connectLocalStdio as I,createLocalMcpServer as L,localTools as f}from"./packem_shared/LOCAL_SERVER_NAME-BbopFImI.mjs";import{createPaidMcpServer as M}from"./packem_shared/createPaidMcpServer-Ba1gMM4Y.mjs";import{connectStdio as i,createLunoraMcpServer as D}from"./packem_shared/connectStdio-CQI9JGJC.mjs";import{R as d,W as v,c as F,t as U}from"./packem_shared/tools-D-JWERWj.mjs";import{DEFAULT_MAX_REQUEST_BYTES as g,serveStateless as h}from"./packem_shared/DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{O as G}from"./packem_shared/observability-tools-D4Z8dGS-.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,s 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,I as connectLocalStdio,i as connectStdio,S as createAuthedMcpFetchHandler,L as createLocalMcpServer,D as createLunoraMcpServer,n as createMcpFetchHandler,M as createPaidMcpServer,l as createToolServer,f as localTools,_ as mcpTokenScopes,a as parseAgentsEnv,h as serveStateless,U as toolDefinitions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraClient as f}from"@lunora/client";import{StdioServerTransport as m}from"@modelcontextprotocol/sdk/server/stdio.js";import{e as p}from"./promise-memo-OqVPWoUX.mjs";import{createToolServer as v}from"./createToolServer-BtGuPyMU.mjs";import{createRemoteDocsIndex as h}from"./DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{docsResources as E}from"./DOCS_URI_SCHEME-Buo752CV.mjs";import{docsTools as y}from"./DEFAULT_SEARCH_LIMIT-BqSYN5vr.mjs";import{E as R,a as O,t as T,b as _,c as S}from"./tools-D-JWERWj.mjs";const l=e=>e.docs===!1?void 0:h({...e.docs?.baseUrl===void 0?{}:{baseUrl:e.docs.baseUrl},...e.fetch===void 0?{}:{fetch:e.fetch}}),P="lunora",C="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).",g=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),p(n,g),n.set(r,o),o}},i=e=>e?.token!==void 0&&e.token.length>0,A=(e,n,t)=>{const r=typeof e=="function"?e:()=>e;return T(n,i(r())).filter(c=>!_.has(c.name)).map(c=>({definition:c,handle:async o=>{const s=r();return s===void 0?{content:[{text:C,type:"text"}],isError:!0}:S(t(s),c.name,o,n,i(s))}}))},I=()=>R.map(e=>({definition:e,handle:n=>Promise.resolve(O(e.name,n))})),N="lunora-spec:openrpc",k="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:N},{description:"The deployment's generated OpenAPI 3.1 document.",fetch:async e=>e.fetchOpenApi(),name:"OpenAPI specification",uri:k}],w=(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)}}}},x=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}}}),L=(e,n)=>{const t=[],r=l(e);return r!==void 0&&t.push(...y(r)),t.push(...e.extraTools??[],...I()),e.deployment!==void 0&&t.push(...A(e.deployment,e.allowWrites??!1,n??d(e.fetch))),t},U=(e={})=>{const n=l(e),t=[];n!==void 0&&t.push(E(n));const r=d(e.fetch);return e.deployment!==void 0&&t.push(w(e.deployment,r)),v({name:P,version:e.version??"0.0.0"},L(e,r),t.length===0?void 0:x(t))},H=async(e={})=>{const n=U(e);return await n.connect(new m),n};export{P as LOCAL_SERVER_NAME,C as NO_DEPLOYMENT_MESSAGE,k as OPENAPI_RESOURCE_URI,N as OPENRPC_RESOURCE_URI,H as connectLocalStdio,U as createLocalMcpServer,L as localTools};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{D as T,M as a,O as L,b as s,c as _}from"./observability-tools-D4Z8dGS-.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{R as T,d as _,W as o,c as N,t as E}from"./tools-D-JWERWj.mjs";import{O as s}from"./observability-tools-D4Z8dGS-.mjs";export{s as OBSERVABILITY_TOOL_DEFINITIONS,T as READ_ONLY_TOOL_DEFINITIONS,_ as ROW_READ_TOOL_DEFINITIONS,o as WRITE_TOOL_DEFINITIONS,N as callTool,E as toolDefinitions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFileSync as d}from"node:fs";import{dirname as m,join as w}from"node:path";import{fileURLToPath as h}from"node:url";import{LunoraClient as T}from"@lunora/client";import{LunoraError as f}from"@lunora/errors";import{Server as y}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as A}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as M,CallToolRequestSchema as S}from"@modelcontextprotocol/sdk/types.js";import{agentToolDefinitions as k,isAgentToolName as p,callAgentTool as R}from"./AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{t as N,c as L}from"./tools-D-JWERWj.mjs";const E=()=>{try{let e=m(h(import.meta.url));for(let r=0;r<8;r+=1){try{const n=d(w(e,"package.json"),"utf8"),t=JSON.parse(n);if(t.name==="@lunora/mcp"&&typeof t.version=="string"&&t.version.length>0)return t.version}catch{}const a=m(e);if(a===e)break;e=a}}catch{}return"0.0.0"},I={name:"lunora",version:E()},W=e=>{if(e.client!==void 0)return e.client;if(e.url===void 0)throw new f("INTERNAL","createLunoraMcpServer requires either a `client` or a `url`");if(e.token===void 0||e.token.length===0)throw new f("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 T({fetch:e.fetch,url:e.url});return r.setAuthToken(e.token),r},b=e=>{const r=W(e),a=e.allowWrites??!1,n=e.allowAgents??!1,t=e.agents??[],s=typeof e.token=="string"&&e.token.length>0,i=e.allowObservability===!0&&s,c=e.allowDataReads===!0&&s,o=new y(I,{capabilities:{tools:{}}});return o.setRequestHandler(M,()=>({tools:[...N(a,i,c),...k(t,n)]})),o.setRequestHandler(S,async v=>{const{arguments:g,name:l}=v.params,u=g??{};return p(l,t)?await R(r,l,u,{allowAgents:n,exposures:t,...e.agentMaxWaitMs===void 0?{}:{maxWaitMs:e.agentMaxWaitMs},...e.agentPollIntervalMs===void 0?{}:{pollIntervalMs:e.agentPollIntervalMs}}):await L(r,l,u,a,i,c)}),o},V=async e=>{const r=b(e);return await r.connect(new A),r};export{V as connectStdio,b as createLunoraMcpServer,W as resolveClient};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{serveStateless as n}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{resolveClient as a,createLunoraMcpServer as o}from"./connectStdio-
|
|
1
|
+
import{serveStateless as n}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{resolveClient as a,createLunoraMcpServer as o}from"./connectStdio-CQI9JGJC.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};
|
package/dist/packem_shared/{createMcpFetchHandler-DSK2X9Hd.mjs → createMcpFetchHandler-C5doZK5c.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{serveStateless as c}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{DEFAULT_MAX_REQUEST_BYTES as u}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{resolveClient as s,createLunoraMcpServer as a}from"./connectStdio-
|
|
1
|
+
import{serveStateless as c}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{DEFAULT_MAX_REQUEST_BYTES as u}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{resolveClient as s,createLunoraMcpServer as a}from"./connectStdio-CQI9JGJC.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 E}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as T,CallToolRequestSchema as g}from"@modelcontextprotocol/sdk/types.js";import{m as x}from"./promise-memo-OqVPWoUX.mjs";import{readScreenedBody as B,serveStateless as M}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";const 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",f=t=>{if(typeof t!="object"||t===null)return;const{method:s,params:a}=t;if(s!==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}),j=t=>{const s=new Map,a=new Map,c=new Map,h=t.serverInfo??A,u=(e,r,n)=>{if(s.has(e.name))throw new y("BAD_REQUEST",`MCP tool "${e.name}" is already registered.`);const o={description:e.description,inputSchema:e.inputSchema,name:e.name};e.annotations!==void 0&&(o.annotations=e.annotations),s.set(e.name,{definition:o,handler:r}),n!==void 0&&a.set(e.name,n)},v=()=>{const e=new E(h,{capabilities:{tools:{}}});return e.setRequestHandler(T,()=>({tools:[...s.values()].map(r=>r.definition)})),e.setRequestHandler(g,async r=>{const n=s.get(r.params.name);if(n===void 0)return{content:[{text:`unknown tool: ${r.params.name}`,type:"text"}],isError:!0};try{return await n.handler(r.params.arguments??{})}catch(o){return{content:[{text:o instanceof Error?o.message:String(o),type:"text"}],isError:!0}}}),e},w=(e,r)=>x(c,e,async()=>(await C())({...t.charge,price:r},{resource:e}));return{fetchHandler:async(e,r,n)=>{const o=await B(e.clone(),t.maxRequestBytes);if("response"in o)return o.response;const{parsedBody:i}=o,d=()=>M(v(),e,i===void 0?{maxRequestBytes:t.maxRequestBytes}:{maxRequestBytes:t.maxRequestBytes,parsedBody:i});if(Array.isArray(i))return i.some(m=>a.has(f(m)??""))?q():d();const l=f(i),p=l===void 0?void 0:a.get(l);if(l===void 0||p===void 0)return d();const R=await w(l,p),S=typeof n?.waitUntil=="function"?{waitUntil:m=>{n.waitUntil?.(m)}}:void 0;return R.handle(e,d,S)},paidTool:(e,r)=>{u(e,r,e.price)},tool:(e,r)=>{u(e,r)}}};export{j as createPaidMcpServer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as N}from"@lunora/errors";import{ADMIN_FUNCTIONS as l}from"@lunora/shard-engine";const A=e=>{let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)},R=e=>{const t=atob(e),r=new Uint8Array(t.length);for(let s=0;s<t.length;s+=1)r[s]=t.codePointAt(s)??0;return r},a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",F=e=>{let t="",r=0;const s=e.length-2;for(;r<s;r+=3){const n=e[r]<<16|e[r+1]<<8|e[r+2];t+=a.charAt(n>>18&63)+a.charAt(n>>12&63)+a.charAt(n>>6&63)+a.charAt(n&63)}const o=e.length-r;if(o===1){const n=e[r]<<16;t+=a.charAt(n>>18&63)+a.charAt(n>>12&63)}else if(o===2){const n=e[r]<<16|e[r+1]<<8;t+=a.charAt(n>>18&63)+a.charAt(n>>12&63)+a.charAt(n>>6&63)}return t},$=e=>{const t=e.replaceAll("-","+").replaceAll("_","/"),r=t+"=".repeat((4-t.length%4)%4);return R(r)},b=(e,t)=>{if(typeof t=="bigint")return t.toString();if(t instanceof ArrayBuffer)return A(new Uint8Array(t));if(ArrayBuffer.isView(t)){const r=t;return A(new Uint8Array(r.buffer,r.byteOffset,r.byteLength))}return t},U=e=>JSON.parse(JSON.stringify(e,b)),P=e=>({content:[{text:e===void 0?"null":JSON.stringify(e,b,2),type:"text"}]}),p=e=>({...P(e),structuredContent:U(e)}),J=e=>({content:[{text:e,type:"text"}],isError:!0}),u={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},I=50,w=500,E=["1m","5m","15m","1h"],O=["open","resolved","ignored"],v=["trace","debug","log","info","warn","error","fatal"],g={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"},L=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,T=e=>typeof e=="string"&&e.length>0?e:void 0,M={properties:{level:{description:`Keep only entries at this severity. One of: ${v.join(", ")}.`,type:"string"},limit:y,shardKey:g},type:"object"},k={properties:{functionPathPrefix:{description:'Keep only Issues whose function path starts with this, e.g. "messages:".',type:"string"},limit:y,shardKey:g,status:{description:`Triage status to keep. One of: ${O.join(", ")}. Default: all.`,type:"string"}},type:"object"},H={properties:{limit:y,shardKey:g},type:"object"},x={properties:{limit:y,range:{description:`Time window to report over. One of: ${E.join(", ")}. Default: 15m.`,type:"string"},shardKey:g},type:"object"},C={properties:{shardKey:g},type:"object"},j={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"},q={properties:{issues:{description:"Grouped error Issues, newest first: { hash, title, count, status, functionPath, lastSeen, … }.",type:"array"}},required:["issues"],type:"object"},B={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"},D={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"},G={properties:{migrations:{description:"Every declared migration with its applied/pending state.",type:"array"}},required:["migrations"],type:"object"},K=[{annotations:{...u,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:M,name:"lunora_get_logs",outputSchema:j},{annotations:{...u,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:k,name:"lunora_get_issues",outputSchema:q},{annotations:{...u,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:H,name:"lunora_get_advisories",outputSchema:B},{annotations:{...u,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:x,name:"lunora_get_query_insights",outputSchema:D},{annotations:{...u,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:C,name:"lunora_get_migration_status",outputSchema:G}],z=new Set(K.map(e=>e.name)),h=async(e,t,r,s)=>{const o={__lunoraRef:t};return e.query(o,r,{...s===void 0?{}:{shardKey:s}})},m=(e,t)=>{const r=e?.[t];return Array.isArray(r)?r:[]},W=async(e,t,r)=>{const s=T(r.shardKey),o=L(r.limit);switch(t){case"lunora_get_advisories":{const n=await h(e,l.getAdvisories,{},s),i=m(n,"advisories");return p({advisories:i.slice(0,o),total:i.length})}case"lunora_get_issues":{const n=S(r.status,O),i=T(r.functionPathPrefix),c=await h(e,l.getIssues,{limit:o,...n===void 0?{}:{status:n},...i===void 0?{}:{functionPathPrefix:i}},s);return p({issues:m(c,"issues")})}case"lunora_get_logs":{const n=S(r.level,v),i=await h(e,l.getLogs,{},s),c=m(i,"entries").filter(f=>n===void 0||f.level===n),{dropped:d}=i??{};return p({dropped:typeof d=="number"?d:0,entries:c.slice(0,o),total:c.length})}case"lunora_get_migration_status":{const n=await h(e,l.migrationStatus,{},s);return p({migrations:m(n,"migrations")})}case"lunora_get_query_insights":{const n=S(r.range,E),i=await h(e,l.getQueryInsights,{...n===void 0?{}:{range:n}},s),c=m(i,"entries"),{buckets:d,capped:f,trackedStatements:_}=i??{};return p({buckets:Array.isArray(d)?d:[],capped:f===!0,entries:c.slice(0,o),total:c.length,trackedStatements:typeof _=="number"?_:c.length})}default:throw new N("INTERNAL",`unknown observability tool: ${t}`)}};export{I as D,w as M,K as O,P as a,z as b,W as c,J as e,$ as f,p as o,F as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const d=(e,t)=>{if(e.size<t)return;const o=e.keys().next().value;o!==void 0&&e.delete(o)},r=(e,t,o,s)=>{const n=e.get(t);if(n!==void 0)return n;s!==void 0&&d(e,s);const i=o().catch(c=>{throw e.get(t)===i&&e.delete(t),c});return e.set(t,i),i};export{d as e,r as m};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{getCatalogEntry as j,resolveHint as q,findIssueSolution as B,isInternalCode as N,LunoraError as c}from"@lunora/errors";import{e as l,o as Q,a as u,t as Y,f as J,b as G,O as V,c as X}from"./observability-tools-D4Z8dGS-.mjs";import{ADMIN_FUNCTIONS as z}from"@lunora/shard-engine";import{m as Z}from"./promise-memo-OqVPWoUX.mjs";const ee={destructiveHint:!1,idempotentHint:!0,openWorldHint:!1,readOnlyHint:!0},w="https://lunora.sh/docs/errors",te={properties:{code:{description:'The error code to explain, e.g. "CONFLICT" or "RLS_REQUIRED". Case-sensitive; codes are SCREAMING_SNAKE_CASE.',type:"string"},message:{description:"The raw error message, when there is no code (codegen and Cloudflare platform errors arrive as plain text). Matched against the solution tables.",type:"string"}},type:"object"},ne={properties:{code:{description:"The code that was looked up, when one was given.",type:"string"},docsUrl:{description:"Link to this code's section of the published error reference.",type:"string"},found:{description:"False when neither the code nor the message matched anything — the other fields are then absent.",type:"boolean"},hint:{description:"Actionable Markdown remediation, when the catalog or a message rule has one.",type:"string"},internal:{description:"True when the code is redacted on the wire: its real message is logged server-side and never reaches a client, so it is absent from the published reference.",type:"boolean"},solution:{description:"A message-matched solution: { id, header, body }, from Lunora's rules or the Cloudflare platform table.",type:"object"},status:{description:"HTTP/RPC status this code maps to on the wire.",type:"number"},title:{description:"Short human-readable summary of the code.",type:"string"}},required:["found"],type:"object"},S=[{annotations:{...ee,title:"Explain a Lunora error"},description:"Explain a Lunora error before guessing at a fix: pass its code (CONFLICT, RLS_REQUIRED, …) or the raw error message, and get the catalog's transport status, title and actionable hint, plus any matched codegen/Cloudflare solution and a link to the reference. Static catalog data — no deployment and no credentials needed.",inputSchema:te,name:"lunora_explain_error",outputSchema:ne}],oe=new Set(S.map(e=>e.name)),_=e=>typeof e=="string"&&e.length>0?e:void 0,ie=e=>typeof e=="string"?e:e.join(`
|
|
2
|
+
`),re=(e,t)=>t?.docsUrl!==void 0?t.docsUrl:e===void 0||t===void 0||N(e)?w:`${w}#${e.toLowerCase()}`,se=(e,t)=>{const n=e===void 0?void 0:j(e),o=e===void 0?void 0:q({code:e}),i=t===void 0?void 0:B(t),r={docsUrl:re(e,n),found:n!==void 0||i!==void 0};return e!==void 0&&(r.code=e,N(e)&&(r.internal=!0)),n!==void 0&&(r.status=n.status,r.title=n.title),o!==void 0&&(r.hint=ie(o)),i!==void 0&&(r.solution={body:i.body,header:i.header,id:i.id}),r},ae=(e,t)=>{if(e!=="lunora_explain_error")return l(`unknown error tool: ${e}`);const n=_(t.code),o=_(t.message);return n===void 0&&o===void 0?l('lunora_explain_error needs a "code" or a "message" (or both); received neither.'):Q(se(n,o))},ce={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},de={properties:{cursor:{description:"Opaque cursor from a previous call's continueCursor, to fetch the next page.",type:"string"},depth:{description:"How many hops to follow. 1 (default) is the direct neighbourhood; maximum 4.",type:"number"},direction:{description:'Which way to follow foreign keys: "out" follows the ids this row holds (ticket → customer), "in" the rows that point at it (customer → tickets), "both" (default) does both.',type:"string"},edges:{description:'Restrict the walk to these edge names, each "<table>.<column>" (e.g. "tickets.customerId"). Omit to follow every declared foreign key.',items:{type:"string"},type:"array"},id:{description:"Document id of the row to start from.",type:"string"},limit:{description:"Maximum related rows to return (default 50, maximum 200).",type:"number"},shardKey:{description:"Shard to read from on a .shardBy()-partitioned deployment. Omit for the default (unsharded) shard.",type:"string"},table:{description:'Table the start row lives in, e.g. "customers".',type:"string"}},required:["table","id"],type:"object"},v=[{annotations:{...ce,title:"Find rows related to a row"},description:"Follow the schema's foreign keys out of one row and return the rows it is connected to, each with its hop distance, the edge names walked to reach it, and a depth-decaying score. Use this to answer 'what is connected to this' — a customer's tickets, those tickets' messages — which keyword and semantic search cannot, because the connection lives in a foreign key rather than in the text. Read-only, but it reads through the deployment's ADMIN writer: RLS policies and column masks do not apply to what it returns.",inputSchema:de,name:"lunora_find_related"}],le=new Set(v.map(e=>e.name)),ue=e=>{const{cursor:t,depth:n,direction:o,edges:i,limit:r}=e;if(t!=null&&typeof t!="string")throw new c("BAD_REQUEST","findRelated: `cursor` must be a string");if(n!==void 0&&typeof n!="number")throw new c("BAD_REQUEST","findRelated: `depth` must be a number");if(o!==void 0&&o!=="both"&&o!=="in"&&o!=="out")throw new c("BAD_REQUEST",'findRelated: `direction` must be one of "in", "out" or "both"');if(r!==void 0&&typeof r!="number")throw new c("BAD_REQUEST","findRelated: `limit` must be a number");if(i!==void 0&&(!Array.isArray(i)||!i.every(s=>typeof s=="string")))throw new c("BAD_REQUEST","findRelated: `edges` must be an array of edge-name strings")},he=e=>{const{cursor:t,depth:n,direction:o,edges:i,id:r,limit:s,shardKey:a,table:d}=e;if(typeof d!="string"||d.trim()==="")throw new c("BAD_REQUEST","findRelated: `table` is required");if(typeof r!="string"||r.trim()==="")throw new c("BAD_REQUEST","findRelated: `id` is required");if(ue(e),a!==void 0&&(typeof a!="string"||a.trim()===""))throw new c("BAD_REQUEST","findRelated: `shardKey` must be a non-empty string when given");return{args:{id:r,table:d,...typeof t=="string"?{cursor:t}:{},...n===void 0?{}:{depth:n},...o===void 0?{}:{direction:o},...i===void 0?{}:{edges:i},...s===void 0?{}:{limit:s}},shardKey:a}},fe=async(e,t,n)=>{if(t!=="lunora_find_related")throw new c("INTERNAL",`unknown row-read tool: ${t}`);const{args:o,shardKey:i}=he(n),r={__lunoraRef:z.findRelated};return u(await e.query(r,o,{...i===void 0?{}:{shardKey:i}}))},g=new TextEncoder,pe=Array.from({length:32},(e,t)=>t);new RegExp(`[${pe.map(e=>String.fromCodePoint(e)).join("")}]`,"u");const me=64,ye=new Map,I=async e=>Z(ye,e,async()=>crypto.subtle.importKey("raw",g.encode(e),{hash:"SHA-256",name:"HMAC"},!1,["sign","verify"]),me),ge=async(e,t)=>{const n=await I(e),o=await crypto.subtle.sign("HMAC",n,g.encode(t));return Y(new Uint8Array(o))},we=async(e,t,n)=>{const o=await I(e);return crypto.subtle.verify("HMAC",o,n,g.encode(t))},_e=/["\\\u0000-\u001F\uD800-\uDFFF]/,b=e=>_e.test(e)?JSON.stringify(e):`"${e}"`,p=e=>{if(e===void 0)return"null";if(typeof e=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(typeof e=="number"){if(Number.isNaN(e))return"nan";if(e===1/0)return"inf";if(e===-1/0)return"-inf";if(Object.is(e,-0))return"-0"}if(typeof e=="string")return b(e);if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e)){let s="[";for(let a=0;a<e.length;a++)a>0&&(s+=","),s+=p(e[a]);return s+"]"}const t=Object.getPrototypeOf(e);if(t!==null&&t!==Object.prototype){const s=e.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${s} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const n=e,o=Object.keys(n).sort();let i="{",r=!0;for(const s of o){const a=n[s];a!==void 0&&(r?r=!1:i+=",",i+=b(s),i+=":",i+=p(a))}return i+"}"},be="lunora-mcp-write-confirmation-v1",D=600*1e3,C=".",L=e=>{const t=e.getAuthToken();if(t===null||t==="")throw new c("UNAUTHORIZED","write confirmation needs the deployment's admin token: it is the secret half of the digest key, and without it a digest would be forgeable by anyone.");return`${be}\0${e.url}\0${t}`},k=(e,t)=>`${p(e)}\0${String(t)}`,Ee=async(e,t)=>{const n=Date.now()+D,o=await ge(L(e),k(t,n));return{actionDigest:`${String(n)}${C}${o}`,expiresAt:n}},Ae=async(e,t,n)=>{const o=n.indexOf(C);if(o<=0)return"mismatch";const i=Number(n.slice(0,o));if(!Number.isSafeInteger(i)||i<=0)return"mismatch";if(Date.now()>=i)return"expired";let r;try{r=J(n.slice(o+1))}catch{return"mismatch"}return await we(L(e),k(t,i),r)?"valid":"mismatch"},Oe="Optional caller-chosen token folded into the digest. GUARANTEE: a retry after a client timeout can resubmit the confirmation you already hold, for as long as that digest is inside its window, instead of asking for a second review — and a deliberately-repeated identical write sent under a NEW key gets its own digest, so it cannot ride the first review. NOT GUARANTEED: this does not deduplicate the write. The server keeps no state between requests and never forwards the key to the function, so a resubmitted confirmed call executes again. Make the function itself idempotent if the write must happen at most once.",Te={actionDigest:{description:"The digest returned by the preceding action_required result. Required together with confirmed, and only valid until the expiresAt that result reported — past that, propose again.",type:"string"},confirmed:{description:"Set to true ONLY on the second call, after a human has reviewed the proposed action, and only together with the actionDigest that proposal returned. Omit it on the first call. The server cannot tell whether a human actually reviewed it — that is this client's responsibility, and the operator enabled writes on the understanding that this client asks.",type:"boolean"},idempotencyKey:{description:Oe,type:"string"}},Re=e=>{const t=typeof e.actionDigest=="string"&&e.actionDigest.length>0?e.actionDigest:void 0,n=typeof e.idempotencyKey=="string"&&e.idempotencyKey.length>0?e.idempotencyKey:void 0;return{actionDigest:t,confirmed:e.confirmed===!0,idempotencyKey:n}},Ne=(e,t)=>u({actionDigest:t.actionDigest,expiresAt:new Date(t.expiresAt).toISOString(),nextStep:`Show proposedAction to a human. To execute, call ${e.tool} again with the IDENTICAL functionPath, args, shardKey and idempotencyKey, plus confirmed: true and this actionDigest, before expiresAt. Nothing has been written or called yet.`,proposedAction:e,status:"action_required"}),Se=async(e,t,n)=>{if(!n.confirmed||n.actionDigest===void 0)return Ne(t,await Ee(e,t));const o=await Ae(e,t,n.actionDigest);if(o!=="valid")return o==="expired"?l(`confirmation rejected: this actionDigest has expired. A confirmation is good for ${String(D/6e4)} minutes from the proposal that issued it, so an approval cannot be replayed in a later session. Nothing was written. Call ${t.tool} again WITHOUT confirmed to get a fresh actionDigest, have it reviewed, then resubmit those same arguments with it.`):l(`confirmation rejected: the actionDigest does not match this call. A digest is bound to the exact tool, function path, arguments, shard key and idempotency key it was issued for, so any edit to the proposal invalidates it — and a digest from another deployment never matches. Nothing was written. Call ${t.tool} again WITHOUT confirmed to get a fresh actionDigest for the current arguments, have it reviewed, then resubmit those same arguments with it.`)},U={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"},E={properties:{},type:"object"},ve={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},h={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},H=[{annotations:{...h,title:"List deployment functions"},description:"List the deployment's public functions (queries, mutations, actions) with their kinds.",inputSchema:E,name:"lunora_list_functions"},{annotations:{...h,title:"List global tables"},description:"List the deployment's .global() tables with their row counts. Names and row counts only — no column shapes.",inputSchema:E,name:"lunora_list_tables"},{annotations:{...h,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:ve,name:"lunora_get_function_schema"},{annotations:{...h,title:"Run a query"},description:"Run a query and return its result. Read-only.",inputSchema:U,name:"lunora_run_query"}],Ie=new Set(H.map(e=>e.name)),A={properties:{...U.properties,...Te},required:["functionPath"],type:"object"},O='TWO-STEP: the first call does NOT execute. It returns status "action_required" with the proposed action, an actionDigest and the expiresAt it is good until; show that to a human, then call again before expiresAt with the IDENTICAL functionPath/args/shardKey plus confirmed: true and that actionDigest. Any change to the target or the arguments produces a different digest and needs a fresh review, and an expired digest is refused rather than re-proposed.',P=[{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run a mutation (writes data)"},description:`Run a mutation. Writes data. ${O}`,inputSchema:A,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. May call external services — send mail, charge a card, hit a third-party API — so the confirmation is the only thing between a proposal and a real-world side effect. ${O}`,inputSchema:A,name:"lunora_run_action"}],De=new Set(P.map(e=>e.name)),x=e=>{const{functionPath:t}=e;if(typeof t!="string"||t.length===0)throw new c("BAD_REQUEST",'"functionPath" is required and must be a non-empty string');return t},T=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),R=e=>Array.isArray(e)?"an array":`a ${typeof e}`,Ce=e=>{if(e==null)return{};if(typeof e=="string"){let t;try{t=JSON.parse(e)}catch{throw new c("BAD_REQUEST",'"args" must be a JSON object; received a string that is not valid JSON')}if(!T(t))throw new c("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${R(t)}`);return t}if(!T(e))throw new c("BAD_REQUEST",`"args" must be a JSON object, got ${R(e)}`);return e},M=e=>{const t=x(e),n=Ce(e.args),o=typeof e.shardKey=="string"&&e.shardKey.length>0?e.shardKey:void 0;return{args:n,functionPath:t,shardKey:o}},m=e=>({__lunoraRef:e}),Le=3e4,f=new WeakMap,y=e=>{const t=Date.now(),n=f.get(e);if(n!==void 0&&n.expiresAt>t)return n.promise;const o=e.listFunctions().catch(i=>{throw f.get(e)?.promise===o&&f.delete(e),i});return f.set(e,{expiresAt:t+Le,promise:o}),o},$=async(e,t,n)=>{const i=(await y(e)).find(r=>r.path===t);if(i===void 0)throw new c("NOT_FOUND",`function not found or not public: ${t}`);if(i.kind!==n)throw new c("BAD_REQUEST",`function ${t} is a ${i.kind}, not a ${n}`)},ke=async(e,t,n,o,i)=>{const r=Re(i);return Se(e,{...o,idempotencyKey:r.idempotencyKey,kind:n,tool:t},r)},Ue=async(e,t,n)=>{switch(t){case"lunora_get_function_schema":{const o=x(n),r=(await y(e)).find(s=>s.path===o);return r===void 0?l(`function not found: ${o}`):u({args:r.args??[],kind:r.kind,path:r.path})}case"lunora_list_functions":return u(await y(e));case"lunora_list_tables":return u(await e.listGlobalTables());case"lunora_run_query":{const{args:o,functionPath:i,shardKey:r}=M(n);return await $(e,i,"query"),u(await e.query(m(i),o,{shardKey:r}))}default:throw new c("INTERNAL",`unknown read-only tool: ${t}`)}},He=async(e,t,n)=>{if(t!=="lunora_run_action"&&t!=="lunora_run_mutation")throw new c("INTERNAL",`unknown write tool: ${t}`);const o=t==="lunora_run_action"?"action":"mutation",{args:i,functionPath:r,shardKey:s}=M(n);await $(e,r,o);const a=await ke(e,t,o,{args:i,functionPath:r,shardKey:s},n);return a!==void 0?a:u(o==="action"?await e.action(m(r),i,{shardKey:s}):await e.mutation(m(r),i,{shardKey:s}))},Pe=e=>e===!0,K=[{call:Ue,definitions:H,names:Ie},{call:(e,t,n)=>ae(t,n),definitions:S,names:oe},{call:fe,definitions:v,gate:{flag:"allowDataReads",refuse:e=>`tool "${e}" is disabled: it returns raw table rows read through the deployment's ADMIN writer, so RLS policies and column masks do not apply. Enable it with the LUNORA_MCP_ALLOW_DATA_READS env var.`},names:le},{call:X,definitions:V,gate:{flag:"allowObservability",refuse:e=>`tool "${e}" 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.`},names:G},{call:He,definitions:P,gate:{flag:"allowWrites",refuse:e=>`tool "${e}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`},names:De}],F=(e,t)=>e.gate!==void 0&&!Pe(t[e.gate.flag])?e.gate:void 0,Fe=(e,t=!1,n=!1)=>{const o={allowDataReads:n,allowObservability:t,allowWrites:e};return K.filter(i=>F(i,o)===void 0).flatMap(i=>i.definitions)},We=async(e,t,n,o=!1,i=!1,r=!1)=>{const s=K.find(d=>d.names.has(t));if(s===void 0)return l(`unknown tool: ${t}`);const a=F(s,{allowDataReads:r,allowObservability:i,allowWrites:o});if(a!==void 0)return l(a.refuse(t));try{return await s.call(e,t,n)}catch(d){const W=d instanceof Error?d.message:String(d);return l(W)}};export{S as E,H as R,P as W,ae as a,oe as b,We as c,v as d,Fe as t};
|
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.137",
|
|
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.102",
|
|
57
|
+
"@lunora/errors": "1.0.0-alpha.36",
|
|
58
|
+
"@lunora/shard-engine": "1.0.0-alpha.64",
|
|
59
59
|
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
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-Buo752CV.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 +0,0 @@
|
|
|
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};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as c}from"@lunora/errors";import{O as T,e as d,a as N,c as E,o as u}from"./observability-tools-B-g9Y9IT.mjs";const m={properties:{args:{description:"Arguments object passed to the function",type:"object"},functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"},shardKey:{description:"Optional shard key when the function is .shardBy()-partitioned",type:"string"}},required:["functionPath"],type:"object"},g={properties:{},type:"object"},R={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},l={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},I=[{annotations:{...l,title:"List deployment functions"},description:"List the deployment's public functions (queries, mutations, actions) with their kinds.",inputSchema:g,name:"lunora_list_functions"},{annotations:{...l,title:"List global tables"},description:"List the deployment's .global() tables with their row counts. Names and row counts only — no column shapes.",inputSchema:g,name:"lunora_list_tables"},{annotations:{...l,title:"Describe a function's arguments"},description:"Return a function's argument descriptors (name, validator kind, whether it is optional) and its kind, so a caller can construct a valid arguments object. Call lunora_list_functions first to discover available function paths.",inputSchema:R,name:"lunora_get_function_schema"},{annotations:{...l,title:"Run a query"},description:"Run a query and return its result. Read-only.",inputSchema:m,name:"lunora_run_query"}],b=[{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run a mutation (writes data)"},description:"Run a mutation and return its result. Writes data — use with care.",inputSchema:m,name:"lunora_run_mutation"},{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run an action (may call external services)"},description:"Run an action and return its result. May call external services.",inputSchema:m,name:"lunora_run_action"}],L=new Set(b.map(t=>t.name)),U=(t,n=!1)=>[...I,...n===!0?T:[],...t===!0?b:[]],A=t=>{const{functionPath:n}=t;if(typeof n!="string"||n.length===0)throw new c("BAD_REQUEST",'"functionPath" is required and must be a non-empty string');return n},O=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),w=t=>Array.isArray(t)?"an array":`a ${typeof t}`,v=t=>{if(t==null)return{};if(typeof t=="string"){let n;try{n=JSON.parse(t)}catch{throw new c("BAD_REQUEST",'"args" must be a JSON object; received a string that is not valid JSON')}if(!O(n))throw new c("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${w(n)}`);return n}if(!O(t))throw new c("BAD_REQUEST",`"args" must be a JSON object, got ${w(t)}`);return t},f=t=>{const n=A(t),e=v(t.args),o=typeof t.shardKey=="string"&&t.shardKey.length>0?t.shardKey:void 0;return{args:e,functionPath:n,shardKey:o}},p=t=>({__lunoraRef:t}),P=3e4,h=new WeakMap,y=t=>{const n=Date.now(),e=h.get(t);if(e!==void 0&&e.expiresAt>n)return e.promise;const o=t.listFunctions().catch(i=>{throw h.get(t)?.promise===o&&h.delete(t),i});return h.set(t,{expiresAt:n+P,promise:o}),o},_=async(t,n,e)=>{const i=(await y(t)).find(r=>r.path===n);if(i===void 0)throw new c("NOT_FOUND",`function not found or not public: ${n}`);if(i.kind!==e)throw new c("BAD_REQUEST",`function ${n} is a ${i.kind}, not a ${e}`)},B=async(t,n,e,o=!1,i=!1)=>{try{if(o!==!0&&L.has(n))return d(`tool "${n}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`);if(N.has(n))return i!==!0?d(`tool "${n}" is disabled: it reads the deployment's logs, request metadata and grouped errors — user data that would land at the model provider. Enable it with the LUNORA_MCP_ALLOW_OBSERVABILITY env var.`):await E(t,n,e);switch(n){case"lunora_get_function_schema":{const r=A(e),s=(await y(t)).find(S=>S.path===r);return s===void 0?d(`function not found: ${r}`):u({args:s.args??[],kind:s.kind,path:s.path})}case"lunora_list_functions":return u(await y(t));case"lunora_list_tables":return u(await t.listGlobalTables());case"lunora_run_action":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"action"),u(await t.action(p(a),r,{shardKey:s}))}case"lunora_run_mutation":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"mutation"),u(await t.mutation(p(a),r,{shardKey:s}))}case"lunora_run_query":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"query"),u(await t.query(p(a),r,{shardKey:s}))}default:return d(`unknown tool: ${n}`)}}catch(r){const a=r instanceof Error?r.message:String(r);return d(a)}};export{T as OBSERVABILITY_TOOL_DEFINITIONS,I as READ_ONLY_TOOL_DEFINITIONS,b as WRITE_TOOL_DEFINITIONS,B as callTool,U as toolDefinitions};
|
|
@@ -1 +0,0 @@
|
|
|
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};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as y}from"@lunora/errors";import{Server as g}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as E,CallToolRequestSchema as x}from"@modelcontextprotocol/sdk/types.js";import{readScreenedBody as T,serveStateless as B}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";const M=(t,o,a,c)=>{const l=t.get(o);if(l!==void 0)return l;const i=a().catch(u=>{throw t.get(o)===i&&t.delete(o),u});return t.set(o,i),i},C=async()=>{try{return(await import("@lunora/x402/charge")).createChargeMiddleware}catch(t){throw new y("INTERNAL",`paid MCP tools need the optional peer "@lunora/x402" — install it alongside @lunora/mcp to charge for tools (${t instanceof Error?t.message:String(t)})`)}},A={name:"lunora-paid-mcp",version:"0.0.0"},L="tools/call",v=t=>{if(typeof t!="object"||t===null)return;const{method:o,params:a}=t;if(o!==L||typeof a!="object"||a===null)return;const{name:c}=a;return typeof c=="string"?c:void 0},q=()=>Response.json({error:"A JSON-RPC batch may not reference a paid MCP tool; send paid tools/call requests individually."},{status:400}),I=t=>{const o=new Map,a=new Map,c=new Map,l=t.serverInfo??A,i=(e,r,s)=>{if(o.has(e.name))throw new y("BAD_REQUEST",`MCP tool "${e.name}" is already registered.`);const n={description:e.description,inputSchema:e.inputSchema,name:e.name};e.annotations!==void 0&&(n.annotations=e.annotations),o.set(e.name,{definition:n,handler:r}),s!==void 0&&a.set(e.name,s)},u=()=>{const e=new g(l,{capabilities:{tools:{}}});return e.setRequestHandler(E,()=>({tools:[...o.values()].map(r=>r.definition)})),e.setRequestHandler(x,async r=>{const s=o.get(r.params.name);if(s===void 0)return{content:[{text:`unknown tool: ${r.params.name}`,type:"text"}],isError:!0};try{return await s.handler(r.params.arguments??{})}catch(n){return{content:[{text:n instanceof Error?n.message:String(n),type:"text"}],isError:!0}}}),e},w=(e,r)=>M(c,e,async()=>(await C())({...t.charge,price:r},{resource:e}));return{fetchHandler:async(e,r,s)=>{const n=await T(e.clone(),t.maxRequestBytes);if("response"in n)return n.response;const{parsedBody:d}=n,p=()=>B(u(),e,d===void 0?{maxRequestBytes:t.maxRequestBytes}:{maxRequestBytes:t.maxRequestBytes,parsedBody:d});if(Array.isArray(d))return d.some(f=>a.has(v(f)??""))?q():p();const m=v(d),h=m===void 0?void 0:a.get(m);if(m===void 0||h===void 0)return p();const R=await w(m,h),S=typeof s?.waitUntil=="function"?{waitUntil:f=>{s.waitUntil?.(f)}}:void 0;return R.handle(e,p,S)},paidTool:(e,r)=>{i(e,r,e.price)},tool:(e,r)=>{i(e,r)}}};export{I as createPaidMcpServer};
|
|
@@ -1 +0,0 @@
|
|
|
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};
|