@lunora/mcp 1.0.0-alpha.248 → 1.0.0-alpha.249
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 +2 -0
- package/dist/bin.mjs +1 -1
- 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 +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.mjs +1 -1
- package/dist/node/index.d.mts +3 -3
- package/dist/node/index.d.ts +3 -3
- package/dist/node/index.mjs +1 -1
- package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-fnFScxPw.mjs +1 -0
- package/dist/packem_shared/DEFAULT_SEARCH_LIMIT-CPjNPMx3.mjs +3 -0
- package/dist/packem_shared/{DOCS_SERVER_NAME-CUnLrk1a.mjs → DOCS_SERVER_NAME-Bk6vD8lk.mjs} +1 -1
- package/dist/packem_shared/{DOCS_URI_SCHEME-C35UQ5Fo.mjs → DOCS_URI_SCHEME-Dd_rZo2y.mjs} +1 -1
- package/dist/packem_shared/LOCAL_SERVER_NAME-DfC1rbyh.mjs +1 -0
- package/dist/packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-Dz7pHc8X.mjs +1 -0
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-gYI6MQSL.mjs +1 -0
- package/dist/packem_shared/{compose.d-BVnP3uAy.d.mts → compose.d-CNiBViBW.d.mts} +65 -23
- package/dist/packem_shared/{compose.d-BVnP3uAy.d.ts → compose.d-CNiBViBW.d.ts} +65 -23
- package/dist/packem_shared/{connectStdio-DAY8LUxm.mjs → connectStdio-DL2u3M7m.mjs} +1 -1
- package/dist/packem_shared/{createAuthedMcpFetchHandler-CKT7xe9W.mjs → createAuthedMcpFetchHandler-CWArTYX5.mjs} +1 -1
- package/dist/packem_shared/{createLunoraMcpServer-CNM2s_iq.mjs → createLunoraMcpServer-CS7Mmy8b.mjs} +1 -1
- package/dist/packem_shared/{createMcpFetchHandler-DRyWhMed.mjs → createMcpFetchHandler-D38o4DLo.mjs} +1 -1
- package/dist/packem_shared/createPaidMcpServer-BMuBkZCP.mjs +1 -0
- package/dist/packem_shared/createToolServer-DV1g-DGe.mjs +1 -0
- package/dist/packem_shared/{server.d-D3rejkVc.d.mts → server.d-C7ciln6z.d.mts} +1 -1
- package/dist/packem_shared/{server.d-DQgZyRkG.d.ts → server.d-CXO7cw_h.d.ts} +1 -1
- package/dist/packem_shared/tool-result-D3oW8Fxx.mjs +1 -0
- package/dist/packem_shared/tools-DiCVoZgA.mjs +2 -0
- package/dist/worker/index.d.mts +6 -7
- package/dist/worker/index.d.ts +6 -7
- package/dist/worker/index.mjs +1 -1
- package/package.json +3 -3
- package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs +0 -1
- package/dist/packem_shared/DEFAULT_SEARCH_LIMIT-B8C5JM2y.mjs +0 -3
- package/dist/packem_shared/LOCAL_SERVER_NAME-Ci-W2me-.mjs +0 -1
- package/dist/packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-mO4JuHgV.mjs +0 -1
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-DT0XSV-2.mjs +0 -1
- package/dist/packem_shared/createPaidMcpServer-BH_mly77.mjs +0 -1
- package/dist/packem_shared/createToolServer-BtGuPyMU.mjs +0 -1
- package/dist/packem_shared/observability-tools-D4Z8dGS-.mjs +0 -1
- package/dist/packem_shared/tools-DSqDlu_n.mjs +0 -2
package/README.md
CHANGED
|
@@ -247,6 +247,8 @@ Two opt-ins are needed, on **both** sides:
|
|
|
247
247
|
|
|
248
248
|
Each exposed agent gets an `agent_<name>` tool taking `prompt` (required), an optional `threadKey` (reuse to continue a conversation; omit to start a new thread), and an optional `title`. The tool starts a durable run and awaits it up to the timeout budget; if the run outlasts the budget it returns a pending result whose `threadKey` you feed to the generic `lunora_agent_status` tool to poll for the final answer.
|
|
249
249
|
|
|
250
|
+
**Limit:** a run paused for a tool approval or a secret request (`requestSecretTool`) cannot be resumed through MCP. MCP has no operation to approve, reject, supply or decline it, so the run waits for its owner to answer it in the app and otherwise expires. The status reports the run as `awaiting_input`, and that result's `hint` says the decision belongs in the app.
|
|
251
|
+
|
|
250
252
|
Runs are **owner-scoped** to the identity the configured token resolves to — which is the deployment's **admin** identity, because `LUNORA_ADMIN_TOKEN` is what every tool needs (see [Tokens](#tokens)). Every agent thread this server starts therefore belongs to that one identity; run a separate MCP server per deployment if you need them kept apart.
|
|
251
253
|
|
|
252
254
|
### Tokens
|
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-
|
|
2
|
+
import{parseAgentsEnv as N}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-fnFScxPw.mjs";import l from"./packem_shared/connectStdio-DL2u3M7m.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
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
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
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/docs/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as McpResourceProvider, T as ToolDefinition, a as McpTool } from "../packem_shared/compose.d-CNiBViBW.mjs";
|
|
2
2
|
export type {
|
|
3
3
|
/**
|
|
4
4
|
* `@lunora/mcp/docs` — the documentation tool surface: `lunora_search_docs`,
|
|
@@ -24,7 +24,7 @@ export type {
|
|
|
24
24
|
* (`createToolServer`) is exported from the package root, so a consumer using
|
|
25
25
|
* both entries gets one implementation rather than two copies.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
g as McpResourceSummary,
|
|
28
28
|
/**
|
|
29
29
|
* `@lunora/mcp/docs` — the documentation tool surface: `lunora_search_docs`,
|
|
30
30
|
* `lunora_get_doc`, and `lunora_list_docs`.
|
|
@@ -49,7 +49,7 @@ f 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
|
-
M as McpServerInfo, b as ToolInputSchema,
|
|
52
|
+
M as McpServerInfo, b as ToolInputSchema, d as ToolResult } from "../packem_shared/compose.d-CNiBViBW.mjs";
|
|
53
53
|
import { M as McpFetchHandler } from "../packem_shared/serve-stateless.d-DD5arqFx.mjs";
|
|
54
54
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
55
55
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
package/dist/docs/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as McpResourceProvider, T as ToolDefinition, a as McpTool } from "../packem_shared/compose.d-CNiBViBW.js";
|
|
2
2
|
export type {
|
|
3
3
|
/**
|
|
4
4
|
* `@lunora/mcp/docs` — the documentation tool surface: `lunora_search_docs`,
|
|
@@ -24,7 +24,7 @@ export type {
|
|
|
24
24
|
* (`createToolServer`) is exported from the package root, so a consumer using
|
|
25
25
|
* both entries gets one implementation rather than two copies.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
g as McpResourceSummary,
|
|
28
28
|
/**
|
|
29
29
|
* `@lunora/mcp/docs` — the documentation tool surface: `lunora_search_docs`,
|
|
30
30
|
* `lunora_get_doc`, and `lunora_list_docs`.
|
|
@@ -49,7 +49,7 @@ f 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
|
-
M as McpServerInfo, b as ToolInputSchema,
|
|
52
|
+
M as McpServerInfo, b as ToolInputSchema, d as ToolResult } from "../packem_shared/compose.d-CNiBViBW.js";
|
|
53
53
|
import { M as McpFetchHandler } from "../packem_shared/serve-stateless.d-DD5arqFx.js";
|
|
54
54
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
55
55
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
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-CiHy_Dbz.mjs";import{DOCS_URI_SCHEME as s,docsResources as S,fromDocsUri as E,toDocsUri as I}from"../packem_shared/DOCS_URI_SCHEME-
|
|
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-CiHy_Dbz.mjs";import{DOCS_URI_SCHEME as s,docsResources as S,fromDocsUri as E,toDocsUri as I}from"../packem_shared/DOCS_URI_SCHEME-Dd_rZo2y.mjs";import{DOCS_SERVER_NAME as R,createDocsMcpFetchHandler as p,createDocsMcpServer as A}from"../packem_shared/DOCS_SERVER_NAME-Bk6vD8lk.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-CPjNPMx3.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
|
@@ -74,12 +74,11 @@ type C as CallAgentToolOptions, type L as LunoraMcpServerOptions,
|
|
|
74
74
|
* better-auth's MCP OAuth gate (`requireMcpAuth` from `@lunora/auth/plugins`)
|
|
75
75
|
* and can scope tool exposure to the access token's own scopes.
|
|
76
76
|
*/
|
|
77
|
-
type M as McpAgentExposure, b as agentToolDefinitions, c as callAgentTool, d as createLunoraMcpServer, p as parseAgentsEnv } from "./packem_shared/server.d-
|
|
77
|
+
type M as McpAgentExposure, b as agentToolDefinitions, c as callAgentTool, d as createLunoraMcpServer, p as parseAgentsEnv } from "./packem_shared/server.d-C7ciln6z.mjs";
|
|
78
78
|
export { type AuthedMcpFetchHandlerOptions, type AuthedMcpServerOptions, type McpAccessTokenClaims, type McpAuthProtect, type McpFetchHandlerOptions, OBSERVABILITY_TOOL_DEFINITIONS, type PaidMcpChargeConfig, type PaidMcpExecutionContext, type PaidMcpFetchHandler, type PaidMcpServer, type PaidMcpServerConfig, READ_ONLY_TOOL_DEFINITIONS, type RegisterPaidToolOptions, type RegisterToolOptions, type ToolHandler, WRITE_TOOL_DEFINITIONS, callTool, createAuthedMcpFetchHandler, createMcpFetchHandler, createPaidMcpServer, mcpTokenScopes, toolDefinitions } from "./worker/index.mjs";
|
|
79
|
-
export { type M as McpServerInfo, type a as McpTool, type T as ToolDefinition, type b as ToolInputSchema, type c as
|
|
79
|
+
export { type M as McpServerInfo, type a as McpTool, type T as ToolDefinition, type b as ToolInputSchema, type c as ToolOutputSchema, type d as ToolResult, e as createToolServer } from "./packem_shared/compose.d-CNiBViBW.mjs";
|
|
80
80
|
export { LOCAL_SERVER_NAME, type LocalDeployment, type LocalDeploymentSource, type LocalMcpServerOptions, NO_DEPLOYMENT_MESSAGE, connectLocalStdio, connectStdio, createLocalMcpServer, localTools } from "./node/index.mjs";
|
|
81
81
|
export { D as DEFAULT_MAX_REQUEST_BYTES, type M as McpFetchHandler, type S as ServeStatelessOptions, s as serveStateless } from "./packem_shared/serve-stateless.d-DD5arqFx.mjs";
|
|
82
82
|
import '@lunora/client';
|
|
83
83
|
import '@modelcontextprotocol/sdk/server/index.js';
|
|
84
|
-
import '@modelcontextprotocol/sdk/types.js';
|
|
85
84
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -74,12 +74,11 @@ type C as CallAgentToolOptions, type L as LunoraMcpServerOptions,
|
|
|
74
74
|
* better-auth's MCP OAuth gate (`requireMcpAuth` from `@lunora/auth/plugins`)
|
|
75
75
|
* and can scope tool exposure to the access token's own scopes.
|
|
76
76
|
*/
|
|
77
|
-
type M as McpAgentExposure, b as agentToolDefinitions, c as callAgentTool, d as createLunoraMcpServer, p as parseAgentsEnv } from "./packem_shared/server.d-
|
|
77
|
+
type M as McpAgentExposure, b as agentToolDefinitions, c as callAgentTool, d as createLunoraMcpServer, p as parseAgentsEnv } from "./packem_shared/server.d-CXO7cw_h.js";
|
|
78
78
|
export { type AuthedMcpFetchHandlerOptions, type AuthedMcpServerOptions, type McpAccessTokenClaims, type McpAuthProtect, type McpFetchHandlerOptions, OBSERVABILITY_TOOL_DEFINITIONS, type PaidMcpChargeConfig, type PaidMcpExecutionContext, type PaidMcpFetchHandler, type PaidMcpServer, type PaidMcpServerConfig, READ_ONLY_TOOL_DEFINITIONS, type RegisterPaidToolOptions, type RegisterToolOptions, type ToolHandler, WRITE_TOOL_DEFINITIONS, callTool, createAuthedMcpFetchHandler, createMcpFetchHandler, createPaidMcpServer, mcpTokenScopes, toolDefinitions } from "./worker/index.js";
|
|
79
|
-
export { type M as McpServerInfo, type a as McpTool, type T as ToolDefinition, type b as ToolInputSchema, type c as
|
|
79
|
+
export { type M as McpServerInfo, type a as McpTool, type T as ToolDefinition, type b as ToolInputSchema, type c as ToolOutputSchema, type d as ToolResult, e as createToolServer } from "./packem_shared/compose.d-CNiBViBW.js";
|
|
80
80
|
export { LOCAL_SERVER_NAME, type LocalDeployment, type LocalDeploymentSource, type LocalMcpServerOptions, NO_DEPLOYMENT_MESSAGE, connectLocalStdio, connectStdio, createLocalMcpServer, localTools } from "./node/index.js";
|
|
81
81
|
export { D as DEFAULT_MAX_REQUEST_BYTES, type M as McpFetchHandler, type S as ServeStatelessOptions, s as serveStateless } from "./packem_shared/serve-stateless.d-DD5arqFx.js";
|
|
82
82
|
import '@lunora/client';
|
|
83
83
|
import '@modelcontextprotocol/sdk/server/index.js';
|
|
84
|
-
import '@modelcontextprotocol/sdk/types.js';
|
|
85
84
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as
|
|
1
|
+
import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as c,callAgentTool as a,parseAgentsEnv as T}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-fnFScxPw.mjs";import{createAuthedMcpFetchHandler as S,mcpTokenScopes as _}from"./packem_shared/createAuthedMcpFetchHandler-CWArTYX5.mjs";import{createToolServer as l}from"./packem_shared/createToolServer-DV1g-DGe.mjs";import{createMcpFetchHandler as O}from"./packem_shared/createMcpFetchHandler-D38o4DLo.mjs";import{LOCAL_SERVER_NAME as N,NO_DEPLOYMENT_MESSAGE as f,connectLocalStdio as s,createLocalMcpServer as I,localTools as L}from"./packem_shared/LOCAL_SERVER_NAME-DfC1rbyh.mjs";import{createPaidMcpServer as x}from"./packem_shared/createPaidMcpServer-BMuBkZCP.mjs";import{createLunoraMcpServer as i}from"./packem_shared/createLunoraMcpServer-CS7Mmy8b.mjs";import{default as R}from"./packem_shared/connectStdio-DL2u3M7m.mjs";import{R as v,W as F,c as U,t as Y}from"./packem_shared/tools-DiCVoZgA.mjs";import{DEFAULT_MAX_REQUEST_BYTES as h,serveStateless as u}from"./packem_shared/DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{OBSERVABILITY_TOOL_DEFINITIONS as G}from"./packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-Dz7pHc8X.mjs";export{r as AGENT_RUN_INPUT_SCHEMA,t as AGENT_STATUS_TOOL_NAME,h as DEFAULT_MAX_REQUEST_BYTES,N as LOCAL_SERVER_NAME,f as NO_DEPLOYMENT_MESSAGE,G as OBSERVABILITY_TOOL_DEFINITIONS,v as READ_ONLY_TOOL_DEFINITIONS,F as WRITE_TOOL_DEFINITIONS,c as agentToolDefinitions,a as callAgentTool,U as callTool,s as connectLocalStdio,R as connectStdio,S as createAuthedMcpFetchHandler,I as createLocalMcpServer,i as createLunoraMcpServer,O as createMcpFetchHandler,x as createPaidMcpServer,l as createToolServer,L as localTools,_ as mcpTokenScopes,T as parseAgentsEnv,u as serveStateless,Y as toolDefinitions};
|
package/dist/node/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LunoraClient } from '@lunora/client';
|
|
2
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
-
import { a as McpTool } from "../packem_shared/compose.d-
|
|
4
|
-
import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-
|
|
5
|
-
export { d as createLunoraMcpServer } from "../packem_shared/server.d-
|
|
3
|
+
import { a as McpTool } from "../packem_shared/compose.d-CNiBViBW.mjs";
|
|
4
|
+
import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-C7ciln6z.mjs";
|
|
5
|
+
export { d as createLunoraMcpServer } from "../packem_shared/server.d-C7ciln6z.mjs";
|
|
6
6
|
/** A Lunora deployment the tools dispatch against. */
|
|
7
7
|
interface LocalDeployment {
|
|
8
8
|
token?: string;
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LunoraClient } from '@lunora/client';
|
|
2
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
-
import { a as McpTool } from "../packem_shared/compose.d-
|
|
4
|
-
import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-
|
|
5
|
-
export { d as createLunoraMcpServer } from "../packem_shared/server.d-
|
|
3
|
+
import { a as McpTool } from "../packem_shared/compose.d-CNiBViBW.js";
|
|
4
|
+
import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-CXO7cw_h.js";
|
|
5
|
+
export { d as createLunoraMcpServer } from "../packem_shared/server.d-CXO7cw_h.js";
|
|
6
6
|
/** A Lunora deployment the tools dispatch against. */
|
|
7
7
|
interface LocalDeployment {
|
|
8
8
|
token?: string;
|
package/dist/node/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LOCAL_SERVER_NAME as r,NO_DEPLOYMENT_MESSAGE as c,connectLocalStdio as t,createLocalMcpServer as a,localTools as E}from"../packem_shared/LOCAL_SERVER_NAME-
|
|
1
|
+
import{LOCAL_SERVER_NAME as r,NO_DEPLOYMENT_MESSAGE as c,connectLocalStdio as t,createLocalMcpServer as a,localTools as E}from"../packem_shared/LOCAL_SERVER_NAME-DfC1rbyh.mjs";import{createLunoraMcpServer as l}from"../packem_shared/createLunoraMcpServer-CS7Mmy8b.mjs";import{default as n}from"../packem_shared/connectStdio-DL2u3M7m.mjs";export{r as LOCAL_SERVER_NAME,c as NO_DEPLOYMENT_MESSAGE,t as connectLocalStdio,n as connectStdio,a as createLocalMcpServer,l as createLunoraMcpServer,E as localTools};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{e as u,a as O}from"./tool-result-D3oW8Fxx.mjs";const b="agents:agentRun",A="agents:agentThread",R="agents:agentMessages",p="lunora_agent_status",_=new Set(["awaiting_input","cancelled","error","idle"]),U=6e4,H=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"},x={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}`,K=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},D={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},G=(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:{...D,title:"Check a durable agent run"},description:"Check the status of a durable agent run (and its answer if finished) by its threadKey.",inputSchema:x,name:p}],q=(t,n)=>t===p||n.some(e=>g(e)===t),I=()=>`mcp-${crypto.randomUUID()}`,d=t=>({__lunoraRef:t}),L=async t=>{await new Promise(n=>{setTimeout(n,t)})},i=t=>({content:[{text:JSON.stringify(t,void 0,2),type:"text"}]}),l=(t,n)=>{const e=t[n];return typeof e=="string"&&e.length>0?e:void 0},T=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",E=async(t,n,e,r)=>{const o=await t.query(d(R),{key:n});if(e==="error"){const a=r!==null&&typeof r=="object"?r.error:void 0;return i({error:typeof a=="string"?a:"the agent run failed",status:e,threadKey:n})}return i(e==="awaiting_input"?{hint:"This run is paused on a human-in-the-loop decision: a tool approval, or a secret the owner has to supply. Answer it in the app that owns the agent; MCP cannot answer it. Poll lunora_agent_status with this threadKey afterwards.",status:e,text:T(o),threadKey:n}:{status:e,text:T(o),threadKey:n})},k=async(t,n)=>{const e=l(n,"threadKey");if(e===void 0)return u("BAD_REQUEST",'"threadKey" is required and must be a non-empty string');const r=await t.query(d(A),{key:e}),o=w(r);return _.has(o)?E(t,e,o,r):i({status:o==="unknown"?"running":o,threadKey:e})},W=async(t,n,e,r)=>{try{if(r.allowAgents!==!0)return u("FORBIDDEN",`tool "${n}" is disabled: agent tools are off. Enable them with the LUNORA_MCP_ALLOW_AGENTS env var.`);if(n===p)return await k(t,e);const o=r.exposures.find(c=>g(c)===n);if(o===void 0)return u("NOT_FOUND",`agent tool "${n}" is not exposed by this MCP server`);const a=l(e,"prompt");if(a===void 0)return u("BAD_REQUEST",'"prompt" is required and must be a non-empty string');const s=l(e,"threadKey")??I(),h=l(e,"title"),{id:N}=await t.mutation(d(b),{agent:o.name,input:a,threadKey:s,...h===void 0?{}:{title:h}}),f=r.pollIntervalMs??H,S=r.maxWaitMs??U,v=r.wait??L,M=Math.max(1,Math.ceil(S/f));for(let c=0;c<M;c+=1){const y=await t.query(d(A),{key:s}),m=w(y);if(_.has(m))return await E(t,s,m,y);await v(f)}return i({hint:"call lunora_agent_status with this threadKey to poll for the answer",runId:N,status:"running",threadKey:s})}catch(o){return O(o)}};export{P as AGENT_RUN_INPUT_SCHEMA,p as AGENT_STATUS_TOOL_NAME,G as agentToolDefinitions,W as callAgentTool,T as finalAnswer,q as isAgentToolName,K as parseAgentsEnv};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{LunoraError as u}from"@lunora/errors";import{e as l}from"./tool-result-D3oW8Fxx.mjs";const m=10,g=50,S="docs",i=500,E={properties:{limit:{description:`Maximum hits to return (default ${String(m)}, max ${String(g)})`,type:"number"},query:{description:'Search terms, e.g. "shardBy" or "optimistic updates"',type:"string"}},required:["query"],type:"object"},O={properties:{url:{description:'Site-relative page URL from a search hit, e.g. "/docs/sharding"',type:"string"}},required:["url"],type:"object"},T={properties:{},type:"object"},a={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},_={annotations:{...a,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:E,name:"lunora_search_docs"},p={annotations:{...a,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:O,name:"lunora_get_doc"},f={annotations:{...a,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:T,name:"lunora_list_docs"},L=[_,p,f],s=n=>({content:[{text:JSON.stringify(n,void 0,2),type:"text"}]}),d=512,h=(n,t)=>{const e=n[t];if(typeof e!="string"||e.trim().length===0)throw new u("BAD_REQUEST",`"${t}" is required and must be a non-empty string`);if(e.length>d)throw new u("BAD_REQUEST",`"${t}" must be at most ${String(d)} characters`);return e.trim()},y=n=>{const t=typeof n=="string"?Number(n):n;return typeof t!="number"||!Number.isFinite(t)?m:Math.min(g,Math.max(1,Math.floor(t)))},N=(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}`)},I=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}`:`/${S}/${t}`),N(t,n),t},D=n=>[{definition:_,handle:async t=>{const e=h(t,"query"),o=y(t.limit),c=(await n.search(e)).slice(0,o);return c.length===0?s({hits:[],note:`no documentation matched "${e}" — try fewer or more general terms, or lunora_list_docs to browse`}):s({hits:c})}},{definition:p,handle:async t=>{let e;try{e=I(h(t,"url"))}catch(r){if(r instanceof RangeError)return l("BAD_REQUEST",r.message);throw r}const o=await n.getPage(e);return o===void 0?l("NOT_FOUND",`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:f,handle:async()=>{const t=await n.listPages();return t.length>i?s({note:`showing the first ${String(i)} of ${String(t.length)} pages — use lunora_search_docs to find the rest`,pages:t.slice(0,i)}):s(t)}}];export{m as DEFAULT_SEARCH_LIMIT,L as DOCS_TOOL_DEFINITIONS,g as MAX_SEARCH_LIMIT,D as docsTools,I as normalizeDocUrl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createToolServer as o}from"./createToolServer-
|
|
1
|
+
import{createToolServer as o}from"./createToolServer-DV1g-DGe.mjs";import{serveStateless as s}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{DEFAULT_MAX_REQUEST_BYTES as i}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{docsResources as t}from"./DOCS_URI_SCHEME-Dd_rZo2y.mjs";import{docsTools as c}from"./DEFAULT_SEARCH_LIMIT-CPjNPMx3.mjs";const m="lunora-docs",n=e=>({name:m,version:e??"0.0.0"}),a=e=>o(n(e.version),c(e.index),t(e.index)),v=e=>r=>s(a(e),r,{maxRequestBytes:e.maxRequestBytes});export{i as DEFAULT_MAX_REQUEST_BYTES,m as DOCS_SERVER_NAME,v as createDocsMcpFetchHandler,a as createDocsMcpServer};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{normalizeDocUrl as s}from"./DEFAULT_SEARCH_LIMIT-
|
|
1
|
+
import{normalizeDocUrl as s}from"./DEFAULT_SEARCH_LIMIT-CPjNPMx3.mjs";const e="lunora-docs:",n=t=>`${e}${t}`,c=t=>t.startsWith(e)?t.slice(e.length):void 0,d=t=>({list:async()=>(await t.listPages()).map(o=>({...o.description===void 0?{}:{description:o.description},mimeType:"text/markdown",name:o.title,uri:n(o.url)})),read:async i=>{const o=c(i);if(o===void 0)return;const r=await t.getPage(s(o));return r===void 0?void 0:{mimeType:"text/markdown",text:`# ${r.title} (${r.url})
|
|
2
2
|
|
|
3
3
|
${r.content}`}}});export{e as DOCS_URI_SCHEME,d as docsResources,c as fromDocsUri,n as toDocsUri};
|
|
@@ -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-DV1g-DGe.mjs";import{createRemoteDocsIndex as h}from"./DEFAULT_DOCS_BASE_URL-CiHy_Dbz.mjs";import{docsResources as E}from"./DOCS_URI_SCHEME-Dd_rZo2y.mjs";import{docsTools as R}from"./DEFAULT_SEARCH_LIMIT-CPjNPMx3.mjs";import{E as O,a as y,t as T,b as _,c as S}from"./tools-DiCVoZgA.mjs";import{e as P}from"./tool-result-D3oW8Fxx.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}}),C="lunora",N="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?P("NO_DEPLOYMENT",N):S(t(s),c.name,o,n,i(s))}}))},I=()=>O.map(e=>({definition:e,handle:n=>Promise.resolve(y(e.name,n))})),k="lunora-spec:openrpc",w="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:k},{description:"The deployment's generated OpenAPI 3.1 document.",fetch:async e=>e.fetchOpenApi(),name:"OpenAPI specification",uri:w}],L=(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}}}),M=(e,n)=>{const t=[],r=l(e);return r!==void 0&&t.push(...R(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(L(e.deployment,r)),v({name:C,version:e.version??"0.0.0"},M(e,r),t.length===0?void 0:x(t))},V=async(e={})=>{const n=U(e);return await n.connect(new m),n};export{C as LOCAL_SERVER_NAME,N as NO_DEPLOYMENT_MESSAGE,w as OPENAPI_RESOURCE_URI,k as OPENRPC_RESOURCE_URI,V as connectLocalStdio,U as createLocalMcpServer,M as localTools};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as A}from"@lunora/errors";import{ADMIN_FUNCTIONS as d}from"@lunora/shard-engine";import{w as p,o as l}from"./tool-result-D3oW8Fxx.mjs";const u={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},I=50,b=500,v=["1m","5m","15m","1h"],w=["open","resolved","ignored"],E=["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 ${b.toString()}).`,type:"number"},O=e=>{const r=typeof e=="number"?e:Number.NaN;return Number.isFinite(r)?Math.max(1,Math.min(Math.floor(r),b)):I},_=(e,r)=>r.includes(e)?e:void 0,T=e=>typeof e=="string"&&e.length>0?e:void 0,N={properties:{level:{description:`Keep only entries at this severity. One of: ${E.join(", ")}.`,type:"string"},limit:y,shardKey:g},type:"object"},R={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: ${w.join(", ")}. Default: all.`,type:"string"}},type:"object"},P={properties:{limit:y,shardKey:g},type:"object"},U={properties:{limit:y,range:{description:`Time window to report over. One of: ${v.join(", ")}. Default: 15m.`,type:"string"},shardKey:g},type:"object"},L={properties:{shardKey:g},type:"object"},M={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"},H={properties:{issues:{description:"Grouped error Issues, newest first: { hash, title, count, status, functionPath, lastSeen, … }.",type:"array"}},required:["issues"],type:"object"},k={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"},j={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"},q={properties:{migrations:{description:"Every declared migration with its applied/pending state.",type:"array"}},required:["migrations"],type:"object"},C=[{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:N,name:"lunora_get_logs",outputSchema:p(M)},{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:R,name:"lunora_get_issues",outputSchema:p(H)},{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:P,name:"lunora_get_advisories",outputSchema:p(k)},{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:U,name:"lunora_get_query_insights",outputSchema:p(j)},{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:L,name:"lunora_get_migration_status",outputSchema:p(q)}],K=new Set(C.map(e=>e.name)),h=async(e,r,s,n)=>{const a={__lunoraRef:r};return e.query(a,s,{...n===void 0?{}:{shardKey:n}})},m=(e,r)=>{const s=e?.[r];return Array.isArray(s)?s:[]},V=async(e,r,s)=>{const n=T(s.shardKey),a=O(s.limit);switch(r){case"lunora_get_advisories":{const t=await h(e,d.getAdvisories,{},n),i=m(t,"advisories");return l({advisories:i.slice(0,a),total:i.length})}case"lunora_get_issues":{const t=_(s.status,w),i=T(s.functionPathPrefix),o=await h(e,d.getIssues,{limit:a,...t===void 0?{}:{status:t},...i===void 0?{}:{functionPathPrefix:i}},n);return l({issues:m(o,"issues")})}case"lunora_get_logs":{const t=_(s.level,E),i=await h(e,d.getLogs,{},n),o=m(i,"entries").filter(S=>t===void 0||S.level===t),{dropped:c}=i??{};return l({dropped:typeof c=="number"?c:0,entries:o.slice(0,a),total:o.length})}case"lunora_get_migration_status":{const t=await h(e,d.migrationStatus,{},n);return l({migrations:m(t,"migrations")})}case"lunora_get_query_insights":{const t=_(s.range,v),i=await h(e,d.getQueryInsights,{...t===void 0?{}:{range:t}},n),o=m(i,"entries"),{buckets:c,capped:S,trackedStatements:f}=i??{};return l({buckets:Array.isArray(c)?c:[],capped:S===!0,entries:o.slice(0,a),total:o.length,trackedStatements:typeof f=="number"?f:o.length})}default:throw new A("INTERNAL",`unknown observability tool: ${r}`)}};export{I as DEFAULT_LIMIT,b as MAX_LIMIT,C as OBSERVABILITY_TOOL_DEFINITIONS,K as OBSERVABILITY_TOOL_NAMES,V as callObservabilityTool};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{D as E,O as _,R as o,e as s,S,W as a,d as D,c as N,s as A,t as R}from"./tools-DiCVoZgA.mjs";import{OBSERVABILITY_TOOL_DEFINITIONS as r}from"./OBSERVABILITY_TOOL_DEFINITIONS-Dz7pHc8X.mjs";import"./tool-result-D3oW8Fxx.mjs";export{E as DATA_READ_SCOPE,_ as OBSERVABILITY_SCOPE,r as OBSERVABILITY_TOOL_DEFINITIONS,o as READ_ONLY_TOOL_DEFINITIONS,s as ROW_READ_TOOL_DEFINITIONS,S as SCOPED_GATES,a as WRITE_TOOL_DEFINITIONS,D as WRITE_TOOL_NAMES,N as callTool,A as scopeForTool,R as toolDefinitions};
|
|
@@ -15,6 +15,49 @@ interface ToolInputSchema {
|
|
|
15
15
|
required?: ReadonlyArray<string>;
|
|
16
16
|
type: "object";
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A tool's `outputSchema`. Either a success shape, or the root `anyOf` a composed
|
|
20
|
+
* tool gets so an error result also validates. The root stays `type: "object"`,
|
|
21
|
+
* which the MCP SDK requires, so `properties` is absent on that root.
|
|
22
|
+
*/
|
|
23
|
+
interface ToolOutputSchema {
|
|
24
|
+
$defs?: Record<string, unknown>;
|
|
25
|
+
anyOf?: ReadonlyArray<object>;
|
|
26
|
+
definitions?: Record<string, unknown>;
|
|
27
|
+
properties?: Record<string, unknown>;
|
|
28
|
+
required?: ReadonlyArray<string>;
|
|
29
|
+
type: "object";
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Hints every tool carries, whether it reads or writes.
|
|
33
|
+
*/
|
|
34
|
+
interface ToolAnnotationsBase {
|
|
35
|
+
/** The tool reaches systems beyond this server (the network, external services). */
|
|
36
|
+
openWorldHint?: boolean;
|
|
37
|
+
/** Human-facing title, shown instead of the raw tool name. */
|
|
38
|
+
title?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A tool that does not modify anything. `destructiveHint` may only be `false`
|
|
42
|
+
* here, so a read-only tool cannot also claim it is destructive.
|
|
43
|
+
*/
|
|
44
|
+
interface ReadOnlyToolAnnotations extends ToolAnnotationsBase {
|
|
45
|
+
destructiveHint?: false;
|
|
46
|
+
idempotentHint?: boolean;
|
|
47
|
+
readOnlyHint: true;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A tool that may modify something. Both hints are REQUIRED here: a write that
|
|
51
|
+
* does not say whether it can destroy data, or whether a retry repeats its
|
|
52
|
+
* effect, is exactly the omission this type exists to rule out.
|
|
53
|
+
*/
|
|
54
|
+
interface WriteToolAnnotations extends ToolAnnotationsBase {
|
|
55
|
+
/** The tool may perform irreversible changes. */
|
|
56
|
+
destructiveHint: boolean;
|
|
57
|
+
/** Repeating the call with the same arguments has no additional effect. */
|
|
58
|
+
idempotentHint: boolean;
|
|
59
|
+
readOnlyHint: false;
|
|
60
|
+
}
|
|
18
61
|
/**
|
|
19
62
|
* MCP tool annotations — hints a client may use to decide how to present a tool
|
|
20
63
|
* (badge it read-only, confirm before a destructive call).
|
|
@@ -23,21 +66,14 @@ interface ToolInputSchema {
|
|
|
23
66
|
* dispatch, where a write tool is refused unless `allowWrites` is set. These
|
|
24
67
|
* exist so a client can *show* the user what the server already enforces,
|
|
25
68
|
* instead of every tool looking equally dangerous.
|
|
69
|
+
*
|
|
70
|
+
* `readOnlyHint` is required on every tool, so a new tool cannot ship without
|
|
71
|
+
* declaring its access. See `./tool-annotations.test` for the compile-time proof.
|
|
26
72
|
*/
|
|
27
|
-
|
|
28
|
-
/** The tool may perform irreversible changes. Only meaningful when `readOnlyHint` is false. */
|
|
29
|
-
destructiveHint?: boolean;
|
|
30
|
-
/** Repeating the call with the same arguments has no additional effect. */
|
|
31
|
-
idempotentHint?: boolean;
|
|
32
|
-
/** The tool reaches systems beyond this server (the network, external services). */
|
|
33
|
-
openWorldHint?: boolean;
|
|
34
|
-
/** The tool does not modify anything. */
|
|
35
|
-
readOnlyHint?: boolean;
|
|
36
|
-
/** Human-facing title, shown instead of the raw tool name. */
|
|
37
|
-
title?: string;
|
|
38
|
-
}
|
|
73
|
+
type ToolAnnotations = ReadOnlyToolAnnotations | WriteToolAnnotations;
|
|
39
74
|
interface ToolDefinition {
|
|
40
|
-
|
|
75
|
+
/** Required: every tool declares its access (see {@link ToolAnnotations}). */
|
|
76
|
+
annotations: ToolAnnotations;
|
|
41
77
|
description: string;
|
|
42
78
|
inputSchema: ToolInputSchema;
|
|
43
79
|
name: string;
|
|
@@ -46,24 +82,30 @@ interface ToolDefinition {
|
|
|
46
82
|
* emits only a text block declares none, and a client that negotiated an
|
|
47
83
|
* MCP revision older than `2025-06-18` ignores it either way.
|
|
48
84
|
*/
|
|
49
|
-
outputSchema?:
|
|
85
|
+
outputSchema?: ToolOutputSchema;
|
|
50
86
|
}
|
|
51
|
-
/**
|
|
52
|
-
|
|
87
|
+
/**
|
|
88
|
+
* The MCP `CallToolResult` shape this package's tools return.
|
|
89
|
+
*
|
|
90
|
+
* A type alias rather than an interface: the SDK's `CallToolResult` has an open
|
|
91
|
+
* index signature, which only an object-literal type satisfies implicitly.
|
|
92
|
+
*/
|
|
93
|
+
type ToolResult = {
|
|
53
94
|
content: {
|
|
54
95
|
text: string;
|
|
55
96
|
type: "text";
|
|
56
97
|
}[];
|
|
57
98
|
isError?: boolean;
|
|
58
99
|
/**
|
|
59
|
-
* The machine-readable result, mirroring the text block. Present
|
|
60
|
-
* tools that declare an `outputSchema
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* response rather than one
|
|
100
|
+
* The machine-readable result, mirroring the text block. Present on success
|
|
101
|
+
* for tools that declare an `outputSchema`, and on EVERY error result, where
|
|
102
|
+
* it is `{ code, message }`. Per the MCP spec it must be a JSON OBJECT (never
|
|
103
|
+
* a bare array) and must survive `JSON.stringify` — the transport serializes
|
|
104
|
+
* it, so an un-mapped `bigint` here fails the whole response rather than one
|
|
105
|
+
* field.
|
|
64
106
|
*/
|
|
65
107
|
structuredContent?: Record<string, unknown>;
|
|
66
|
-
}
|
|
108
|
+
};
|
|
67
109
|
/** A tool: what to advertise in `ListTools`, and what to run on `CallTool`. */
|
|
68
110
|
interface McpTool {
|
|
69
111
|
definition: ToolDefinition;
|
|
@@ -108,4 +150,4 @@ interface McpResourceProvider {
|
|
|
108
150
|
* not a protocol-level fault.
|
|
109
151
|
*/
|
|
110
152
|
declare const createToolServer: (info: McpServerInfo, tools: ReadonlyArray<McpTool>, resources?: McpResourceProvider) => Server;
|
|
111
|
-
export { McpServerInfo as M, ToolDefinition as T, McpTool as a, ToolInputSchema as b,
|
|
153
|
+
export { McpServerInfo as M, ToolDefinition as T, McpTool as a, ToolInputSchema as b, ToolOutputSchema as c, ToolResult as d, createToolServer as e, McpResourceProvider as f, McpResourceSummary as g, ToolAnnotations as h };
|
|
@@ -15,6 +15,49 @@ interface ToolInputSchema {
|
|
|
15
15
|
required?: ReadonlyArray<string>;
|
|
16
16
|
type: "object";
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A tool's `outputSchema`. Either a success shape, or the root `anyOf` a composed
|
|
20
|
+
* tool gets so an error result also validates. The root stays `type: "object"`,
|
|
21
|
+
* which the MCP SDK requires, so `properties` is absent on that root.
|
|
22
|
+
*/
|
|
23
|
+
interface ToolOutputSchema {
|
|
24
|
+
$defs?: Record<string, unknown>;
|
|
25
|
+
anyOf?: ReadonlyArray<object>;
|
|
26
|
+
definitions?: Record<string, unknown>;
|
|
27
|
+
properties?: Record<string, unknown>;
|
|
28
|
+
required?: ReadonlyArray<string>;
|
|
29
|
+
type: "object";
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Hints every tool carries, whether it reads or writes.
|
|
33
|
+
*/
|
|
34
|
+
interface ToolAnnotationsBase {
|
|
35
|
+
/** The tool reaches systems beyond this server (the network, external services). */
|
|
36
|
+
openWorldHint?: boolean;
|
|
37
|
+
/** Human-facing title, shown instead of the raw tool name. */
|
|
38
|
+
title?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A tool that does not modify anything. `destructiveHint` may only be `false`
|
|
42
|
+
* here, so a read-only tool cannot also claim it is destructive.
|
|
43
|
+
*/
|
|
44
|
+
interface ReadOnlyToolAnnotations extends ToolAnnotationsBase {
|
|
45
|
+
destructiveHint?: false;
|
|
46
|
+
idempotentHint?: boolean;
|
|
47
|
+
readOnlyHint: true;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A tool that may modify something. Both hints are REQUIRED here: a write that
|
|
51
|
+
* does not say whether it can destroy data, or whether a retry repeats its
|
|
52
|
+
* effect, is exactly the omission this type exists to rule out.
|
|
53
|
+
*/
|
|
54
|
+
interface WriteToolAnnotations extends ToolAnnotationsBase {
|
|
55
|
+
/** The tool may perform irreversible changes. */
|
|
56
|
+
destructiveHint: boolean;
|
|
57
|
+
/** Repeating the call with the same arguments has no additional effect. */
|
|
58
|
+
idempotentHint: boolean;
|
|
59
|
+
readOnlyHint: false;
|
|
60
|
+
}
|
|
18
61
|
/**
|
|
19
62
|
* MCP tool annotations — hints a client may use to decide how to present a tool
|
|
20
63
|
* (badge it read-only, confirm before a destructive call).
|
|
@@ -23,21 +66,14 @@ interface ToolInputSchema {
|
|
|
23
66
|
* dispatch, where a write tool is refused unless `allowWrites` is set. These
|
|
24
67
|
* exist so a client can *show* the user what the server already enforces,
|
|
25
68
|
* instead of every tool looking equally dangerous.
|
|
69
|
+
*
|
|
70
|
+
* `readOnlyHint` is required on every tool, so a new tool cannot ship without
|
|
71
|
+
* declaring its access. See `./tool-annotations.test` for the compile-time proof.
|
|
26
72
|
*/
|
|
27
|
-
|
|
28
|
-
/** The tool may perform irreversible changes. Only meaningful when `readOnlyHint` is false. */
|
|
29
|
-
destructiveHint?: boolean;
|
|
30
|
-
/** Repeating the call with the same arguments has no additional effect. */
|
|
31
|
-
idempotentHint?: boolean;
|
|
32
|
-
/** The tool reaches systems beyond this server (the network, external services). */
|
|
33
|
-
openWorldHint?: boolean;
|
|
34
|
-
/** The tool does not modify anything. */
|
|
35
|
-
readOnlyHint?: boolean;
|
|
36
|
-
/** Human-facing title, shown instead of the raw tool name. */
|
|
37
|
-
title?: string;
|
|
38
|
-
}
|
|
73
|
+
type ToolAnnotations = ReadOnlyToolAnnotations | WriteToolAnnotations;
|
|
39
74
|
interface ToolDefinition {
|
|
40
|
-
|
|
75
|
+
/** Required: every tool declares its access (see {@link ToolAnnotations}). */
|
|
76
|
+
annotations: ToolAnnotations;
|
|
41
77
|
description: string;
|
|
42
78
|
inputSchema: ToolInputSchema;
|
|
43
79
|
name: string;
|
|
@@ -46,24 +82,30 @@ interface ToolDefinition {
|
|
|
46
82
|
* emits only a text block declares none, and a client that negotiated an
|
|
47
83
|
* MCP revision older than `2025-06-18` ignores it either way.
|
|
48
84
|
*/
|
|
49
|
-
outputSchema?:
|
|
85
|
+
outputSchema?: ToolOutputSchema;
|
|
50
86
|
}
|
|
51
|
-
/**
|
|
52
|
-
|
|
87
|
+
/**
|
|
88
|
+
* The MCP `CallToolResult` shape this package's tools return.
|
|
89
|
+
*
|
|
90
|
+
* A type alias rather than an interface: the SDK's `CallToolResult` has an open
|
|
91
|
+
* index signature, which only an object-literal type satisfies implicitly.
|
|
92
|
+
*/
|
|
93
|
+
type ToolResult = {
|
|
53
94
|
content: {
|
|
54
95
|
text: string;
|
|
55
96
|
type: "text";
|
|
56
97
|
}[];
|
|
57
98
|
isError?: boolean;
|
|
58
99
|
/**
|
|
59
|
-
* The machine-readable result, mirroring the text block. Present
|
|
60
|
-
* tools that declare an `outputSchema
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* response rather than one
|
|
100
|
+
* The machine-readable result, mirroring the text block. Present on success
|
|
101
|
+
* for tools that declare an `outputSchema`, and on EVERY error result, where
|
|
102
|
+
* it is `{ code, message }`. Per the MCP spec it must be a JSON OBJECT (never
|
|
103
|
+
* a bare array) and must survive `JSON.stringify` — the transport serializes
|
|
104
|
+
* it, so an un-mapped `bigint` here fails the whole response rather than one
|
|
105
|
+
* field.
|
|
64
106
|
*/
|
|
65
107
|
structuredContent?: Record<string, unknown>;
|
|
66
|
-
}
|
|
108
|
+
};
|
|
67
109
|
/** A tool: what to advertise in `ListTools`, and what to run on `CallTool`. */
|
|
68
110
|
interface McpTool {
|
|
69
111
|
definition: ToolDefinition;
|
|
@@ -108,4 +150,4 @@ interface McpResourceProvider {
|
|
|
108
150
|
* not a protocol-level fault.
|
|
109
151
|
*/
|
|
110
152
|
declare const createToolServer: (info: McpServerInfo, tools: ReadonlyArray<McpTool>, resources?: McpResourceProvider) => Server;
|
|
111
|
-
export { McpServerInfo as M, ToolDefinition as T, McpTool as a, ToolInputSchema as b,
|
|
153
|
+
export { McpServerInfo as M, ToolDefinition as T, McpTool as a, ToolInputSchema as b, ToolOutputSchema as c, ToolResult as d, createToolServer as e, McpResourceProvider as f, McpResourceSummary as g, ToolAnnotations as h };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{StdioServerTransport as i}from"@modelcontextprotocol/sdk/server/stdio.js";import{readFileSync as a}from"node:fs";import{dirname as n,join as c}from"node:path";import{fileURLToPath as s}from"node:url";import{createLunoraMcpServer as m}from"./createLunoraMcpServer-
|
|
1
|
+
import{StdioServerTransport as i}from"@modelcontextprotocol/sdk/server/stdio.js";import{readFileSync as a}from"node:fs";import{dirname as n,join as c}from"node:path";import{fileURLToPath as s}from"node:url";import{createLunoraMcpServer as m}from"./createLunoraMcpServer-CS7Mmy8b.mjs";const p=()=>{let r=n(s(import.meta.url));for(let e=0;e<8;e+=1){try{const o=JSON.parse(a(c(r,"package.json"),"utf8"));if(o.name==="@lunora/mcp"&&typeof o.version=="string"&&o.version.length>0)return o.version}catch{}const t=n(r);if(t===r)break;r=t}},S=async r=>{const e=p(),t=m(e===void 0?r:{version:e,...r});return await t.connect(new i),t};export{S as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as y}from"@lunora/errors";import{isAgentToolName as S}from"./AGENT_RUN_INPUT_SCHEMA-
|
|
1
|
+
import{LunoraError as y}from"@lunora/errors";import{isAgentToolName as S}from"./AGENT_RUN_INPUT_SCHEMA-fnFScxPw.mjs";import{readScreenedBody as g,serveStateless as w}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{resolveClient as d,createLunoraMcpServer as T}from"./createLunoraMcpServer-CS7Mmy8b.mjs";import{c as M}from"./tool-call-Bs-QeKp6.mjs";import{d as E,s as _,S as k}from"./tools-DiCVoZgA.mjs";const A=t=>typeof t.scope!="string"?new Set:new Set(t.scope.split(" ").filter(c=>c!=="")),C=(t,c)=>{const o={...t};for(const{flag:s,scope:n}of k)o[s]=t[s]===!0&&c.has(n);return o},I=(t,c,o)=>{const s=new Set,n=c.agents??[];for(const l of t){const e=M(l);if(e===void 0)continue;(E.has(e)||S(e,n))&&s.add(o);const r=_(e,c);r!==void 0&&s.add(r)}return s},N=256,B=t=>{const{maxRequestBytes:c,stepUp:o}=t;if(o!==void 0&&(typeof o.scope!="string"||o.scope===""||typeof o.challenge!="function"))throw new y("MCP_STEP_UP_MISCONFIGURED","createAuthedMcpFetchHandler: `stepUp` needs a non-empty `scope` and a `challenge` function.");const s=typeof t.server=="function"?void 0:d(t.server),n=new Map,l=e=>{if(e.client!==void 0||e.url===void 0||e.token===void 0||e.token.length===0)return d(e);const r=JSON.stringify([e.url,e.token]),i=n.get(r);if(i!==void 0&&i.fetch===e.fetch)return n.delete(r),n.set(r,i),i.client;const f=d(e);return n.delete(r),n.set(r,{client:f,fetch:e.fetch}),n.size>N&&n.delete(n.keys().next().value),f};return t.protect(async(e,r)=>{const i=A(r),f=typeof t.server=="function"?await t.server(r):{...t.server,client:s};let a;if(o!==void 0){const p=await g(e,c);if("response"in p)return p.response;({parsedBody:a}=p);const m=Array.isArray(a)?a:[a],u=[...I(m,f,o.scope)].filter(v=>!i.has(v));if(u.length>0)throw o.challenge(u)}const h=C(typeof t.server=="function"?{...f,client:l(f)}:f,i);return await w(T(h),e,{maxRequestBytes:c,parsedBody:a})})};export{B as createAuthedMcpFetchHandler,A as mcpTokenScopes};
|
package/dist/packem_shared/{createLunoraMcpServer-CNM2s_iq.mjs → createLunoraMcpServer-CS7Mmy8b.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraClient as f}from"@lunora/client";import{LunoraError as m}from"@lunora/errors";import{Server as w}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as d,CallToolRequestSchema as h}from"@modelcontextprotocol/sdk/types.js";import{agentToolDefinitions as A,isAgentToolName as M,callAgentTool as T}from"./AGENT_RUN_INPUT_SCHEMA-
|
|
1
|
+
import{LunoraClient as f}from"@lunora/client";import{LunoraError as m}from"@lunora/errors";import{Server as w}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as d,CallToolRequestSchema as h}from"@modelcontextprotocol/sdk/types.js";import{agentToolDefinitions as A,isAgentToolName as M,callAgentTool as T}from"./AGENT_RUN_INPUT_SCHEMA-fnFScxPw.mjs";import{t as R,c as k}from"./tools-DiCVoZgA.mjs";var N="1.0.0-alpha.248";const E={version:N},L="lunora",y=e=>{if(e.client!==void 0)return e.client;if(e.url===void 0)throw new m("INTERNAL","createLunoraMcpServer requires either a `client` or a `url`");if(e.token===void 0||e.token.length===0)throw new m("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 f({fetch:e.fetch,url:e.url});return r.setAuthToken(e.token),r},C=e=>{const r=y(e),n=e.allowWrites??!1,s=e.allowAgents??!1,t=e.agents??[],o=typeof e.token=="string"&&e.token.length>0,i=e.allowObservability===!0&&o,c=e.allowDataReads===!0&&o,a=new w({name:L,version:e.version??E.version},{capabilities:{tools:{}}});return a.setRequestHandler(d,()=>({tools:[...R(n,i,c),...A(t,s)]})),a.setRequestHandler(h,async v=>{const{arguments:g,name:l}=v.params,u=g??{};return M(l,t)?await T(r,l,u,{allowAgents:s,exposures:t,...e.agentMaxWaitMs===void 0?{}:{maxWaitMs:e.agentMaxWaitMs},...e.agentPollIntervalMs===void 0?{}:{pollIntervalMs:e.agentPollIntervalMs}}):await k(r,l,u,n,i,c)}),a};export{C as createLunoraMcpServer,y as resolveClient};
|
package/dist/packem_shared/{createMcpFetchHandler-DRyWhMed.mjs → createMcpFetchHandler-D38o4DLo.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"./createLunoraMcpServer-
|
|
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"./createLunoraMcpServer-CS7Mmy8b.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 B}from"@lunora/errors";import{Server as E}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as M,ListToolsRequestSchema as P}from"@modelcontextprotocol/sdk/types.js";import{CfWorkerJsonSchemaValidator as _}from"@modelcontextprotocol/sdk/validation/cfworker";import{m as b}from"./promise-memo-OqVPWoUX.mjs";import{readScreenedBody as x,serveStateless as A}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{c as S}from"./tool-call-Bs-QeKp6.mjs";import{e as C,a as F}from"./tool-result-D3oW8Fxx.mjs";let T;const H=()=>(T??=new _,T),N=(t,o,s)=>{const d=t.validate(s);return d.valid?void 0:C("BAD_REQUEST",`invalid arguments for tool "${o}": ${d.errorMessage}`)},V=async()=>{try{return(await import("@lunora/x402/charge")).createChargeMiddleware}catch(t){throw new B("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)})`)}},$={name:"lunora-paid-mcp",version:"0.0.0"},q=()=>Response.json({error:"A JSON-RPC batch may not reference a paid MCP tool; send paid tools/call requests individually."},{status:400}),D=async(t,o,s,d,u)=>{const l={reached:!1},p=new E({name:"lunora-paid-mcp-preflight",version:"0.0.0"},{capabilities:{tools:{}}});p.setRequestHandler(M,v=>{const f=N(d,s,v.params.arguments??{});return l.reached=f===void 0&&v.params.name===s,f??{content:[]}});const h=await A(p,t,{maxRequestBytes:u,parsedBody:o});return l.reached?void 0:h},K=t=>{const o=new Map,s=new Map,d=new Map,u=t.serverInfo??$,l=(e,r,a)=>{if(o.has(e.name))throw new B("BAD_REQUEST",`MCP tool "${e.name}" is already registered.`);const n={annotations:e.annotations,description:e.description,inputSchema:e.inputSchema,name:e.name};o.set(e.name,{definition:n,handler:r,validate:H().getValidator(n.inputSchema)}),a!==void 0&&s.set(e.name,a)},p=()=>{const e=new E(u,{capabilities:{tools:{}}});return e.setRequestHandler(P,()=>({tools:[...o.values()].map(r=>r.definition)})),e.setRequestHandler(M,async r=>{const a=o.get(r.params.name);if(a===void 0)return C("NOT_FOUND",`unknown tool: ${r.params.name}`);const n=r.params.arguments??{},i=N(a,r.params.name,n);if(i!==void 0)return i;try{return await a.handler(n)}catch(m){return F(m)}}),e},h=(e,r)=>b(d,e,async()=>(await V())({...t.charge,price:r},{resource:e}));return{fetchHandler:async(e,r,a)=>{const n=await x(e.clone(),t.maxRequestBytes);if("response"in n)return n.response;const{parsedBody:i}=n,m=()=>A(p(),e,{maxRequestBytes:t.maxRequestBytes,parsedBody:i});if(Array.isArray(i))return i.some(w=>s.has(S(w)??""))?q():m();const c=S(i),y=c===void 0?void 0:s.get(c),R=c===void 0?void 0:o.get(c);if(c===void 0||y===void 0||R===void 0)return m();const g=await D(e,i,c,R,t.maxRequestBytes);if(g!==void 0)return g;const U=await h(c,y),j=typeof a?.waitUntil=="function"?{waitUntil:w=>{a.waitUntil?.(w)}}:void 0;return U.handle(e,m,j)},paidTool:(e,r)=>{l(e,r,e.price)},tool:(e,r)=>{l(e,r)}}};export{K as createPaidMcpServer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Server as l}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as p,CallToolRequestSchema as h,ListResourcesRequestSchema as d,ReadResourceRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{w as R,e as f,a as S}from"./tool-result-D3oW8Fxx.mjs";const y=(u,c,s)=>{const a=new Map;for(const e of c)a.has(e.definition.name)||a.set(e.definition.name,e);const i=[...a.values()].map(({definition:e})=>e.outputSchema===void 0?e:{...e,outputSchema:R(e.outputSchema)}),r=new l(u,{capabilities:s===void 0?{tools:{}}:{resources:{},tools:{}}});return r.setRequestHandler(p,()=>({tools:i})),r.setRequestHandler(h,async e=>{const{arguments:o,name:t}=e.params,n=a.get(t);if(n===void 0)return f("NOT_FOUND",`unknown tool: ${t}`);try{return await n.handle(o??{})}catch(m){return S(m)}}),s!==void 0&&(r.setRequestHandler(d,async()=>({resources:[...await s.list()]})),r.setRequestHandler(w,async e=>{const{uri:o}=e.params,t=await s.read(o);if(t===void 0)throw new Error(`unknown resource: ${o}`);return{contents:[{mimeType:t.mimeType??"text/plain",text:t.text,uri:o}]}})),r};export{y as createToolServer};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LunoraClient } from '@lunora/client';
|
|
2
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
-
import { b as ToolInputSchema, T as ToolDefinition,
|
|
3
|
+
import { b as ToolInputSchema, T as ToolDefinition, d as ToolResult } from "./compose.d-CNiBViBW.mjs";
|
|
4
4
|
/**
|
|
5
5
|
* Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
|
|
6
6
|
* MCP tool an external agent can call. The capability boundary is the MCP-server
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LunoraClient } from '@lunora/client';
|
|
2
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
-
import { b as ToolInputSchema, T as ToolDefinition,
|
|
3
|
+
import { b as ToolInputSchema, T as ToolDefinition, d as ToolResult } from "./compose.d-CNiBViBW.js";
|
|
4
4
|
/**
|
|
5
5
|
* Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
|
|
6
6
|
* MCP tool an external agent can call. The capability boundary is the MCP-server
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as A,isInternalCode as l,getCatalogEntry as c}from"@lunora/errors";const d=e=>{let t="";for(let n=0;n<e.length;n+=32768)t+=String.fromCharCode(...e.subarray(n,n+32768));return btoa(t)},E=e=>{const t=atob(e),r=new Uint8Array(t.length);for(let n=0;n<t.length;n+=1)r[n]=t.codePointAt(n)??0;return r},s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",O=e=>{let t="",r=0;const n=e.length-2;for(;r<n;r+=3){const o=e[r]<<16|e[r+1]<<8|e[r+2];t+=s.charAt(o>>18&63)+s.charAt(o>>12&63)+s.charAt(o>>6&63)+s.charAt(o&63)}const f=e.length-r;if(f===1){const o=e[r]<<16;t+=s.charAt(o>>18&63)+s.charAt(o>>12&63)}else if(f===2){const o=e[r]<<16|e[r+1]<<8;t+=s.charAt(o>>18&63)+s.charAt(o>>12&63)+s.charAt(o>>6&63)}return t},m=e=>{const t=e.replaceAll("-","+").replaceAll("_","/"),r=t+"=".repeat((4-t.length%4)%4);return E(r)},h=(e,t)=>{if(typeof t=="bigint")return t.toString();if(t instanceof ArrayBuffer)return d(new Uint8Array(t));if(ArrayBuffer.isView(t)){const r=t;return d(new Uint8Array(r.buffer,r.byteOffset,r.byteLength))}return t},g=e=>JSON.parse(JSON.stringify(e,h)),p=e=>({content:[{text:e===void 0?"null":JSON.stringify(e,h,2),type:"text"}]}),x=e=>({...p(e),structuredContent:g(e)}),i=(e,t)=>({content:[{text:`${e}: ${t}`,type:"text"}],isError:!0,structuredContent:{code:e,message:t}}),a="the tool call failed unexpectedly",_=e=>{if(e instanceof A){const{code:t}=e;return i(t,l(t)?c(t)?.title??a:e.message)}if(e instanceof Error){const{code:t}=e;if(typeof t=="string"&&c(t)!==void 0)return i(t,l(t)?c(t)?.title??a:e.message)}return i("INTERNAL",a)},R={description:"Set on an error result: the machine-readable error code.",type:"string"},y={description:"Set on an error result: a message that is safe to show.",type:"string"},u={properties:{code:R,message:y},required:["code","message"],type:"object"},w=e=>{if(e.anyOf?.includes(u)===!0)return e;const{$defs:t,definitions:r,...n}=e;return{...t===void 0?{}:{$defs:t},...r===void 0?{}:{definitions:r},anyOf:[n,u],type:"object"}};export{_ as a,p as b,i as e,m as f,x as o,O as t,w};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{getCatalogEntry as j,resolveHint as Q,findIssueSolution as Y,isInternalCode as I,LunoraError as s}from"@lunora/errors";import{w as G,e as u,o as J,b as l,t as V,f as X,a as z}from"./tool-result-D3oW8Fxx.mjs";import{OBSERVABILITY_TOOL_NAMES as Z,OBSERVABILITY_TOOL_DEFINITIONS as ee,callObservabilityTool as te}from"./OBSERVABILITY_TOOL_DEFINITIONS-Dz7pHc8X.mjs";import{ADMIN_FUNCTIONS as ne}from"@lunora/shard-engine";import{stableStringify as oe}from"@lunora/wire";import{m as ie}from"./promise-memo-OqVPWoUX.mjs";const re={destructiveHint:!1,idempotentHint:!0,openWorldHint:!1,readOnlyHint:!0},E="https://lunora.sh/docs/errors",se={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"},ae={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"},D=[{annotations:{...re,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:se,name:"lunora_explain_error",outputSchema:G(ae)}],ce=new Set(D.map(e=>e.name)),A=e=>typeof e=="string"&&e.length>0?e:void 0,de=e=>typeof e=="string"?e:e.join(`
|
|
2
|
+
`),le=(e,t)=>t?.docsUrl!==void 0?t.docsUrl:e===void 0||t===void 0||I(e)?E:`${E}#${e.toLowerCase()}`,ue=(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:Y(t),r={docsUrl:le(e,n),found:n!==void 0||i!==void 0};return e!==void 0&&(r.code=e,I(e)&&(r.internal=!0)),n!==void 0&&(r.status=n.status,r.title=n.title),o!==void 0&&(r.hint=de(o)),i!==void 0&&(r.solution={body:i.body,header:i.header,id:i.id}),r},he=(e,t)=>{if(e!=="lunora_explain_error")return u("NOT_FOUND",`unknown error tool: ${e}`);const n=A(t.code),o=A(t.message);return n===void 0&&o===void 0?u("BAD_REQUEST",'lunora_explain_error needs a "code" or a "message" (or both); received neither.'):J(ue(n,o))},pe={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},fe={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"},C=[{annotations:{...pe,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:fe,name:"lunora_find_related"}],me=new Set(C.map(e=>e.name)),ge=e=>{const{cursor:t,depth:n,direction:o,edges:i,limit:r}=e;if(t!=null&&typeof t!="string")throw new s("BAD_REQUEST","findRelated: `cursor` must be a string");if(n!==void 0&&typeof n!="number")throw new s("BAD_REQUEST","findRelated: `depth` must be a number");if(o!==void 0&&o!=="both"&&o!=="in"&&o!=="out")throw new s("BAD_REQUEST",'findRelated: `direction` must be one of "in", "out" or "both"');if(r!==void 0&&typeof r!="number")throw new s("BAD_REQUEST","findRelated: `limit` must be a number");if(i!==void 0&&(!Array.isArray(i)||!i.every(a=>typeof a=="string")))throw new s("BAD_REQUEST","findRelated: `edges` must be an array of edge-name strings")},ye=e=>{const{cursor:t,depth:n,direction:o,edges:i,id:r,limit:a,shardKey:d,table:c}=e;if(typeof c!="string"||c.trim()==="")throw new s("BAD_REQUEST","findRelated: `table` is required");if(typeof r!="string"||r.trim()==="")throw new s("BAD_REQUEST","findRelated: `id` is required");if(ge(e),d!==void 0&&(typeof d!="string"||d.trim()===""))throw new s("BAD_REQUEST","findRelated: `shardKey` must be a non-empty string when given");return{args:{id:r,table:c,...typeof t=="string"?{cursor:t}:{},...n===void 0?{}:{depth:n},...o===void 0?{}:{direction:o},...i===void 0?{}:{edges:i},...a===void 0?{}:{limit:a}},shardKey:d}},we=async(e,t,n)=>{if(t!=="lunora_find_related")throw new s("INTERNAL",`unknown row-read tool: ${t}`);const{args:o,shardKey:i}=ye(n),r={__lunoraRef:ne.findRelated};return l(await e.query(r,o,{...i===void 0?{}:{shardKey:i}}))},g=new TextEncoder,_e=Array.from({length:32},(e,t)=>t);new RegExp(`[${_e.map(e=>String.fromCodePoint(e)).join("")}]`,"u");const Ee=64,Ae=new Map,L=async e=>ie(Ae,e,async()=>crypto.subtle.importKey("raw",g.encode(e),{hash:"SHA-256",name:"HMAC"},!1,["sign","verify"]),Ee),Oe=async(e,t)=>{const n=await L(e),o=await crypto.subtle.sign("HMAC",n,g.encode(t));return V(new Uint8Array(o))},Te=async(e,t,n)=>{const o=await L(e);return crypto.subtle.verify("HMAC",o,n,g.encode(t))},be="lunora-mcp-write-confirmation-v1",U=600*1e3,k=".",H=e=>{const t=e.getAuthToken();if(t===null||t==="")throw new s("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}`},P=(e,t)=>`${oe(e)}\0${String(t)}`,Re=async(e,t)=>{const n=Date.now()+U,o=await Oe(H(e),P(t,n));return{actionDigest:`${String(n)}${k}${o}`,expiresAt:n}},ve=async(e,t,n)=>{const o=n.indexOf(k);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=X(n.slice(o+1))}catch{return"mismatch"}return await Te(H(e),P(t,i),r)?"valid":"mismatch"},Ne="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.",Se={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:Ne,type:"string"}},Ie=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}},M="«sensitive»",De=(e,t)=>{if(t.length===0)return e;const n=Object.fromEntries(Object.entries(e.args).map(([o,i])=>[o,t.includes(o)?M:i]));return{...e,args:n}},Ce=(e,t,n)=>l({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. Resend the REAL values of any argument shown as "${M}": that placeholder is display only and will not verify. Nothing has been written or called yet.`,proposedAction:De(e,n),status:"action_required"}),Le=async(e,t,n,o=[])=>{if(!n.confirmed||n.actionDigest===void 0)return Ce(t,await Re(e,t),o);const i=await ve(e,t,n.actionDigest);if(i!=="valid")return i==="expired"?u("CONFIRMATION_EXPIRED",`confirmation rejected: this actionDigest has expired. A confirmation is good for ${String(U/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.`):u("CONFIRMATION_MISMATCH",`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.`)},x={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"},O={properties:{},type:"object"},Ue={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},p={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},$=[{annotations:{...p,title:"List deployment functions"},description:"List the deployment's public functions (queries, mutations, actions) with their kinds.",inputSchema:O,name:"lunora_list_functions"},{annotations:{...p,title:"List global tables"},description:"List the deployment's .global() tables with their row counts. Names and row counts only — no column shapes.",inputSchema:O,name:"lunora_list_tables"},{annotations:{...p,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:Ue,name:"lunora_get_function_schema"},{annotations:{...p,title:"Run a query"},description:"Run a query and return its result. Read-only.",inputSchema:x,name:"lunora_run_query"}],ke=new Set($.map(e=>e.name)),T={properties:{...x.properties,...Se},required:["functionPath"],type:"object"},b='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.',F=[{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run a mutation (writes data)"},description:`Run a mutation. Writes data. ${b}`,inputSchema:T,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. ${b}`,inputSchema:T,name:"lunora_run_action"}],He=new Set(F.map(e=>e.name)),B=e=>{const{functionPath:t}=e;if(typeof t!="string"||t.length===0)throw new s("BAD_REQUEST",'"functionPath" is required and must be a non-empty string');return t},R=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),v=e=>Array.isArray(e)?"an array":`a ${typeof e}`,Pe=e=>{if(e==null)return{};if(typeof e=="string"){let t;try{t=JSON.parse(e)}catch{throw new s("BAD_REQUEST",'"args" must be a JSON object; received a string that is not valid JSON')}if(!R(t))throw new s("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${v(t)}`);return t}if(!R(e))throw new s("BAD_REQUEST",`"args" must be a JSON object, got ${v(e)}`);return e},K=e=>{const t=B(e),n=Pe(e.args),o=typeof e.shardKey=="string"&&e.shardKey.length>0?e.shardKey:void 0;return{args:n,functionPath:t,shardKey:o}},W=e=>({__lunoraRef:e}),Me=3e4,h=new WeakMap,m=e=>{const t=Date.now(),n=h.get(e);if(n!==void 0&&n.expiresAt>t)return n.promise;const o=e.listFunctions().catch(i=>{throw h.get(e)?.promise===o&&h.delete(e),i});return h.set(e,{expiresAt:t+Me,promise:o}),o},q=async(e,t,n)=>{const i=(await m(e)).find(r=>r.path===t);if(i===void 0)throw new s("NOT_FOUND",`function not found or not public: ${t}`);if(i.kind!==n)throw new s("BAD_REQUEST",`function ${t} is a ${i.kind}, not a ${n}`);return i},xe=async(e,t,n,o,i,r)=>{const a=Ie(i);return Le(e,{...o,idempotencyKey:a.idempotencyKey,kind:n,tool:t},a,r)},$e=e=>(e.args??[]).filter(t=>t.sensitive===!0).map(t=>t.name),Fe=async(e,t,n)=>{switch(t){case"lunora_get_function_schema":{const o=B(n),r=(await m(e)).find(a=>a.path===o);return r===void 0?u("NOT_FOUND",`function not found: ${o}`):l({args:r.args??[],kind:r.kind,path:r.path})}case"lunora_list_functions":return l(await m(e));case"lunora_list_tables":return l(await e.listGlobalTables());case"lunora_run_query":{const{args:o,functionPath:i,shardKey:r}=K(n);return await q(e,i,"query"),l(await e.query(W(i),o,{shardKey:r}))}default:throw new s("INTERNAL",`unknown read-only tool: ${t}`)}},Be=async(e,t,n)=>{if(t!=="lunora_run_action"&&t!=="lunora_run_mutation")throw new s("INTERNAL",`unknown write tool: ${t}`);const o=t==="lunora_run_action"?"action":"mutation",{args:i,functionPath:r,shardKey:a}=K(n);h.delete(e);const d=await q(e,r,o),c=$e(d),w=await xe(e,t,o,{args:i,functionPath:r,shardKey:a},n,c);if(w!==void 0)return w;const _=c.length===0?W(r):{__lunoraRef:r,sensitive:!0};return l(o==="action"?await e.action(_,i,{shardKey:a}):await e.mutation(_,i,{onlineOnly:!0,shardKey:a}))},N="lunora:data-read",S="lunora:observability",Ke=e=>e===!0,f=[{call:Fe,definitions:$,names:ke},{call:(e,t,n)=>he(t,n),definitions:D,names:ce},{call:we,definitions:C,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 or the allowDataReads option; on the authed path the token also needs the ${N} scope.`,scope:N},names:me},{call:te,definitions:ee,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 or the allowObservability option; on the authed path the token also needs the ${S} scope.`,scope:S},names:Z},{call:Be,definitions:F,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:He}],y=(e,t)=>e.gate!==void 0&&!Ke(t[e.gate.flag])?e.gate:void 0,Je=(e,t=!1,n=!1)=>{const o={allowDataReads:n,allowObservability:t,allowWrites:e};return f.filter(i=>y(i,o)===void 0).flatMap(i=>i.definitions)},Ve=async(e,t,n,o=!1,i=!1,r=!1)=>{const a=f.find(c=>c.names.has(t));if(a===void 0)return u("NOT_FOUND",`unknown tool: ${t}`);const d=y(a,{allowDataReads:r,allowObservability:i,allowWrites:o});if(d!==void 0)return u("FORBIDDEN",d.refuse(t));try{return await a.call(e,t,n)}catch(c){return z(c)}},Xe=(e,t)=>{const n=f.find(o=>o.names.has(e));if(!(n?.gate?.scope===void 0||y(n,t)!==void 0))return n.gate.scope},ze=f.flatMap(e=>e.gate?.scope===void 0?[]:[{flag:e.gate.flag,scope:e.gate.scope}]);export{N as D,D as E,S as O,$ as R,ze as S,F as W,he as a,ce as b,Ve as c,He as d,C as e,Xe as s,Je as t};
|
package/dist/worker/index.d.mts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-
|
|
1
|
+
import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-C7ciln6z.mjs";
|
|
2
2
|
export { A as AGENT_RUN_INPUT_SCHEMA, a as AGENT_STATUS_TOOL_NAME,
|
|
3
3
|
/** Worker-safe entry: fetch handlers and tool composition, with no stdio transport or `node:*` imports. */
|
|
4
4
|
type C as CallAgentToolOptions,
|
|
5
5
|
/** Worker-safe entry: fetch handlers and tool composition, with no stdio transport or `node:*` imports. */
|
|
6
|
-
type M as McpAgentExposure, b as agentToolDefinitions, c as callAgentTool, p as parseAgentsEnv } from "../packem_shared/server.d-
|
|
6
|
+
type M as McpAgentExposure, b as agentToolDefinitions, c as callAgentTool, p as parseAgentsEnv } from "../packem_shared/server.d-C7ciln6z.mjs";
|
|
7
7
|
import { M as McpFetchHandler } from "../packem_shared/serve-stateless.d-DD5arqFx.mjs";
|
|
8
8
|
export { D as DEFAULT_MAX_REQUEST_BYTES, type S as ServeStatelessOptions, s as serveStateless } from "../packem_shared/serve-stateless.d-DD5arqFx.mjs";
|
|
9
|
-
import { T as ToolDefinition,
|
|
10
|
-
export { type M as McpServerInfo, type a as McpTool,
|
|
11
|
-
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { T as ToolDefinition, d as ToolResult, h as ToolAnnotations, b as ToolInputSchema } from "../packem_shared/compose.d-CNiBViBW.mjs";
|
|
10
|
+
export { type M as McpServerInfo, type a as McpTool, e as createToolServer } from "../packem_shared/compose.d-CNiBViBW.mjs";
|
|
12
11
|
import { LunoraClient } from '@lunora/client';
|
|
13
12
|
import '@modelcontextprotocol/sdk/server/index.js';
|
|
14
13
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
@@ -180,8 +179,8 @@ interface X402ChargeSettings {
|
|
|
180
179
|
type ToolHandler = (arguments_: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
|
|
181
180
|
/** Registration shape for a free tool. */
|
|
182
181
|
interface RegisterToolOptions {
|
|
183
|
-
/**
|
|
184
|
-
annotations
|
|
182
|
+
/** MCP tool annotations. Required: a paid tool declares its access the same as every other tool. */
|
|
183
|
+
annotations: ToolAnnotations;
|
|
185
184
|
/** Human/model-facing description of what the tool does. */
|
|
186
185
|
description: string;
|
|
187
186
|
/** JSON-Schema object describing the tool's arguments. */
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-
|
|
1
|
+
import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-CXO7cw_h.js";
|
|
2
2
|
export { A as AGENT_RUN_INPUT_SCHEMA, a as AGENT_STATUS_TOOL_NAME,
|
|
3
3
|
/** Worker-safe entry: fetch handlers and tool composition, with no stdio transport or `node:*` imports. */
|
|
4
4
|
type C as CallAgentToolOptions,
|
|
5
5
|
/** Worker-safe entry: fetch handlers and tool composition, with no stdio transport or `node:*` imports. */
|
|
6
|
-
type M as McpAgentExposure, b as agentToolDefinitions, c as callAgentTool, p as parseAgentsEnv } from "../packem_shared/server.d-
|
|
6
|
+
type M as McpAgentExposure, b as agentToolDefinitions, c as callAgentTool, p as parseAgentsEnv } from "../packem_shared/server.d-CXO7cw_h.js";
|
|
7
7
|
import { M as McpFetchHandler } from "../packem_shared/serve-stateless.d-DD5arqFx.js";
|
|
8
8
|
export { D as DEFAULT_MAX_REQUEST_BYTES, type S as ServeStatelessOptions, s as serveStateless } from "../packem_shared/serve-stateless.d-DD5arqFx.js";
|
|
9
|
-
import { T as ToolDefinition,
|
|
10
|
-
export { type M as McpServerInfo, type a as McpTool,
|
|
11
|
-
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { T as ToolDefinition, d as ToolResult, h as ToolAnnotations, b as ToolInputSchema } from "../packem_shared/compose.d-CNiBViBW.js";
|
|
10
|
+
export { type M as McpServerInfo, type a as McpTool, e as createToolServer } from "../packem_shared/compose.d-CNiBViBW.js";
|
|
12
11
|
import { LunoraClient } from '@lunora/client';
|
|
13
12
|
import '@modelcontextprotocol/sdk/server/index.js';
|
|
14
13
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
@@ -180,8 +179,8 @@ interface X402ChargeSettings {
|
|
|
180
179
|
type ToolHandler = (arguments_: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
|
|
181
180
|
/** Registration shape for a free tool. */
|
|
182
181
|
interface RegisterToolOptions {
|
|
183
|
-
/**
|
|
184
|
-
annotations
|
|
182
|
+
/** MCP tool annotations. Required: a paid tool declares its access the same as every other tool. */
|
|
183
|
+
annotations: ToolAnnotations;
|
|
185
184
|
/** Human/model-facing description of what the tool does. */
|
|
186
185
|
description: string;
|
|
187
186
|
/** JSON-Schema object describing the tool's arguments. */
|
package/dist/worker/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as T,callAgentTool as a,parseAgentsEnv as _}from"../packem_shared/AGENT_RUN_INPUT_SCHEMA-
|
|
1
|
+
import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as T,callAgentTool as a,parseAgentsEnv as _}from"../packem_shared/AGENT_RUN_INPUT_SCHEMA-fnFScxPw.mjs";import{createAuthedMcpFetchHandler as c,mcpTokenScopes as p}from"../packem_shared/createAuthedMcpFetchHandler-CWArTYX5.mjs";import{createToolServer as O}from"../packem_shared/createToolServer-DV1g-DGe.mjs";import{createMcpFetchHandler as l}from"../packem_shared/createMcpFetchHandler-D38o4DLo.mjs";import{createPaidMcpServer as A}from"../packem_shared/createPaidMcpServer-BMuBkZCP.mjs";import{R as n,W as f,c as m,t as x}from"../packem_shared/tools-DiCVoZgA.mjs";import{DEFAULT_MAX_REQUEST_BYTES as D,serveStateless as L}from"../packem_shared/DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{OBSERVABILITY_TOOL_DEFINITIONS as M}from"../packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-Dz7pHc8X.mjs";export{r as AGENT_RUN_INPUT_SCHEMA,t as AGENT_STATUS_TOOL_NAME,D as DEFAULT_MAX_REQUEST_BYTES,M as OBSERVABILITY_TOOL_DEFINITIONS,n as READ_ONLY_TOOL_DEFINITIONS,f as WRITE_TOOL_DEFINITIONS,T as agentToolDefinitions,a as callAgentTool,m as callTool,c as createAuthedMcpFetchHandler,l as createMcpFetchHandler,A as createPaidMcpServer,O as createToolServer,p as mcpTokenScopes,_ as parseAgentsEnv,L as serveStateless,x as toolDefinitions};
|
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.249",
|
|
4
4
|
"description": "Model Context Protocol server exposing a Lunora deployment to AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@cfworker/json-schema": "^4.1.1",
|
|
65
|
-
"@lunora/client": "1.0.0-alpha.
|
|
65
|
+
"@lunora/client": "1.0.0-alpha.234",
|
|
66
66
|
"@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
|
|
67
|
-
"@lunora/shard-engine": "1.0.0-alpha.
|
|
67
|
+
"@lunora/shard-engine": "1.0.0-alpha.147",
|
|
68
68
|
"@lunora/wire": "1.0.0-alpha.1",
|
|
69
69
|
"@modelcontextprotocol/sdk": "^1.32.0"
|
|
70
70
|
},
|
|
@@ -1 +0,0 @@
|
|
|
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};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
const T=10,A=50,h="docs";const _={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"},L=[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},y=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,L as DOCS_TOOL_DEFINITIONS,A as MAX_SEARCH_LIMIT,y as docsTools,E as normalizeDocUrl};
|
|
@@ -1 +0,0 @@
|
|
|
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-CiHy_Dbz.mjs";import{docsResources as E}from"./DOCS_URI_SCHEME-C35UQ5Fo.mjs";import{docsTools as y}from"./DEFAULT_SEARCH_LIMIT-B8C5JM2y.mjs";import{E as R,a as O,t as T,b as _,c as S}from"./tools-DSqDlu_n.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};
|
|
@@ -1 +0,0 @@
|
|
|
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};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{D as T,O as E,R as _,e as s,S as o,W as a,d as S,c as D,s as N,t as A}from"./tools-DSqDlu_n.mjs";import{O as L}from"./observability-tools-D4Z8dGS-.mjs";export{T as DATA_READ_SCOPE,E as OBSERVABILITY_SCOPE,L as OBSERVABILITY_TOOL_DEFINITIONS,_ as READ_ONLY_TOOL_DEFINITIONS,s as ROW_READ_TOOL_DEFINITIONS,o as SCOPED_GATES,a as WRITE_TOOL_DEFINITIONS,S as WRITE_TOOL_NAMES,D as callTool,N as scopeForTool,A as toolDefinitions};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as x}from"@lunora/errors";import{Server as M}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as B,ListToolsRequestSchema as P}from"@modelcontextprotocol/sdk/types.js";import{CfWorkerJsonSchemaValidator as b}from"@modelcontextprotocol/sdk/validation/cfworker";import{m as H}from"./promise-memo-OqVPWoUX.mjs";import{readScreenedBody as N,serveStateless as T}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{c as S}from"./tool-call-Bs-QeKp6.mjs";let E;const U=()=>(E??=new b,E),C=(r,n,s)=>{const l=r.validate(s);return l.valid?void 0:{content:[{text:`invalid arguments for tool "${n}": ${l.errorMessage}`,type:"text"}],isError:!0}},V=async()=>{try{return(await import("@lunora/x402/charge")).createChargeMiddleware}catch(r){throw new x("INTERNAL",`paid MCP tools need the optional peer "@lunora/x402" — install it alongside @lunora/mcp to charge for tools (${r instanceof Error?r.message:String(r)})`)}},$={name:"lunora-paid-mcp",version:"0.0.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=async(r,n,s,l,p)=>{const m={reached:!1},u=new M({name:"lunora-paid-mcp-preflight",version:"0.0.0"},{capabilities:{tools:{}}});u.setRequestHandler(B,h=>{const f=C(l,s,h.params.arguments??{});return m.reached=f===void 0&&h.params.name===s,f??{content:[]}});const v=await T(u,r,{maxRequestBytes:p,parsedBody:n});return m.reached?void 0:v},Q=r=>{const n=new Map,s=new Map,l=new Map,p=r.serverInfo??$,m=(e,t,a)=>{if(n.has(e.name))throw new x("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),n.set(e.name,{definition:o,handler:t,validate:U().getValidator(o.inputSchema)}),a!==void 0&&s.set(e.name,a)},u=()=>{const e=new M(p,{capabilities:{tools:{}}});return e.setRequestHandler(P,()=>({tools:[...n.values()].map(t=>t.definition)})),e.setRequestHandler(B,async t=>{const a=n.get(t.params.name);if(a===void 0)return{content:[{text:`unknown tool: ${t.params.name}`,type:"text"}],isError:!0};const o=t.params.arguments??{},i=C(a,t.params.name,o);if(i!==void 0)return i;try{return await a.handler(o)}catch(c){return{content:[{text:c instanceof Error?c.message:String(c),type:"text"}],isError:!0}}}),e},v=(e,t)=>H(l,e,async()=>(await V())({...r.charge,price:t},{resource:e}));return{fetchHandler:async(e,t,a)=>{const o=await N(e.clone(),r.maxRequestBytes);if("response"in o)return o.response;const{parsedBody:i}=o,c=()=>T(u(),e,{maxRequestBytes:r.maxRequestBytes,parsedBody:i});if(Array.isArray(i))return i.some(w=>s.has(S(w)??""))?q():c();const d=S(i),y=d===void 0?void 0:s.get(d),g=d===void 0?void 0:n.get(d);if(d===void 0||y===void 0||g===void 0)return c();const R=await I(e,i,d,g,r.maxRequestBytes);if(R!==void 0)return R;const A=await v(d,y),j=typeof a?.waitUntil=="function"?{waitUntil:w=>{a.waitUntil?.(w)}}:void 0;return A.handle(e,c,j)},paidTool:(e,t)=>{m(e,t,e.price)},tool:(e,t)=>{m(e,t)}}};export{Q as createPaidMcpServer};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{Server as l}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as d,CallToolRequestSchema as f,ListResourcesRequestSchema as p,ReadResourceRequestSchema as R}from"@modelcontextprotocol/sdk/types.js";const v=(c,u,o)=>{const s=new Map;for(const e of u)s.has(e.definition.name)||s.set(e.definition.name,e);const m=[...s.values()].map(e=>e.definition),r=new l(c,{capabilities:o===void 0?{tools:{}}:{resources:{},tools:{}}});return r.setRequestHandler(d,()=>({tools:m})),r.setRequestHandler(f,async e=>{const{arguments:n,name:t}=e.params,i=s.get(t);if(i===void 0)return{content:[{text:`unknown tool: ${t}`,type:"text"}],isError:!0};try{return await i.handle(n??{})}catch(a){return{content:[{text:a instanceof Error?a.message:String(a),type:"text"}],isError:!0}}}),o!==void 0&&(r.setRequestHandler(p,async()=>({resources:[...await o.list()]})),r.setRequestHandler(R,async e=>{const{uri:n}=e.params,t=await o.read(n);if(t===void 0)throw new Error(`unknown resource: ${n}`);return{contents:[{mimeType:t.mimeType??"text/plain",text:t.text,uri:n}]}})),r};export{v as createToolServer};
|
|
@@ -1 +0,0 @@
|
|
|
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};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{getCatalogEntry as F,resolveHint as q,findIssueSolution as j,isInternalCode as N,LunoraError as a}from"@lunora/errors";import{e as l,o as Q,a as u,t as Y,f as G,b as J,O as V,c as X}from"./observability-tools-D4Z8dGS-.mjs";import{ADMIN_FUNCTIONS as z}from"@lunora/shard-engine";import{stableStringify as Z}from"@lunora/wire";import{m as ee}from"./promise-memo-OqVPWoUX.mjs";const te={destructiveHint:!1,idempotentHint:!0,openWorldHint:!1,readOnlyHint:!0},_="https://lunora.sh/docs/errors",ne={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"},oe={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"},I=[{annotations:{...te,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:ne,name:"lunora_explain_error",outputSchema:oe}],ie=new Set(I.map(e=>e.name)),A=e=>typeof e=="string"&&e.length>0?e:void 0,re=e=>typeof e=="string"?e:e.join(`
|
|
2
|
-
`),ae=(e,t)=>t?.docsUrl!==void 0?t.docsUrl:e===void 0||t===void 0||N(e)?_:`${_}#${e.toLowerCase()}`,se=(e,t)=>{const n=e===void 0?void 0:F(e),o=e===void 0?void 0:q({code:e}),i=t===void 0?void 0:j(t),r={docsUrl:ae(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=re(o)),i!==void 0&&(r.solution={body:i.body,header:i.header,id:i.id}),r},ce=(e,t)=>{if(e!=="lunora_explain_error")return l(`unknown error tool: ${e}`);const n=A(t.code),o=A(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))},de={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},le={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"},D=[{annotations:{...de,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:le,name:"lunora_find_related"}],ue=new Set(D.map(e=>e.name)),he=e=>{const{cursor:t,depth:n,direction:o,edges:i,limit:r}=e;if(t!=null&&typeof t!="string")throw new a("BAD_REQUEST","findRelated: `cursor` must be a string");if(n!==void 0&&typeof n!="number")throw new a("BAD_REQUEST","findRelated: `depth` must be a number");if(o!==void 0&&o!=="both"&&o!=="in"&&o!=="out")throw new a("BAD_REQUEST",'findRelated: `direction` must be one of "in", "out" or "both"');if(r!==void 0&&typeof r!="number")throw new a("BAD_REQUEST","findRelated: `limit` must be a number");if(i!==void 0&&(!Array.isArray(i)||!i.every(s=>typeof s=="string")))throw new a("BAD_REQUEST","findRelated: `edges` must be an array of edge-name strings")},pe=e=>{const{cursor:t,depth:n,direction:o,edges:i,id:r,limit:s,shardKey:c,table:d}=e;if(typeof d!="string"||d.trim()==="")throw new a("BAD_REQUEST","findRelated: `table` is required");if(typeof r!="string"||r.trim()==="")throw new a("BAD_REQUEST","findRelated: `id` is required");if(he(e),c!==void 0&&(typeof c!="string"||c.trim()===""))throw new a("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:c}},fe=async(e,t,n)=>{if(t!=="lunora_find_related")throw new a("INTERNAL",`unknown row-read tool: ${t}`);const{args:o,shardKey:i}=pe(n),r={__lunoraRef:z.findRelated};return u(await e.query(r,o,{...i===void 0?{}:{shardKey:i}}))},y=new TextEncoder,me=Array.from({length:32},(e,t)=>t);new RegExp(`[${me.map(e=>String.fromCodePoint(e)).join("")}]`,"u");const ge=64,ye=new Map,C=async e=>ee(ye,e,async()=>crypto.subtle.importKey("raw",y.encode(e),{hash:"SHA-256",name:"HMAC"},!1,["sign","verify"]),ge),we=async(e,t)=>{const n=await C(e),o=await crypto.subtle.sign("HMAC",n,y.encode(t));return Y(new Uint8Array(o))},_e=async(e,t,n)=>{const o=await C(e);return crypto.subtle.verify("HMAC",o,n,y.encode(t))},Ae="lunora-mcp-write-confirmation-v1",L=600*1e3,k=".",U=e=>{const t=e.getAuthToken();if(t===null||t==="")throw new a("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`${Ae}\0${e.url}\0${t}`},H=(e,t)=>`${Z(e)}\0${String(t)}`,Ee=async(e,t)=>{const n=Date.now()+L,o=await we(U(e),H(t,n));return{actionDigest:`${String(n)}${k}${o}`,expiresAt:n}},be=async(e,t,n)=>{const o=n.indexOf(k);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=G(n.slice(o+1))}catch{return"mismatch"}return await _e(U(e),H(t,i),r)?"valid":"mismatch"},Te="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.",Oe={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:Te,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}},ve=(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 ve(t,await Ee(e,t));const o=await be(e,t,n.actionDigest);if(o!=="valid")return o==="expired"?l(`confirmation rejected: this actionDigest has expired. A confirmation is good for ${String(L/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.`)},P={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"},Ne={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},h={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},x=[{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:Ne,name:"lunora_get_function_schema"},{annotations:{...h,title:"Run a query"},description:"Run a query and return its result. Read-only.",inputSchema:P,name:"lunora_run_query"}],Ie=new Set(x.map(e=>e.name)),b={properties:{...P.properties,...Oe},required:["functionPath"],type:"object"},T='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.',M=[{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run a mutation (writes data)"},description:`Run a mutation. Writes data. ${T}`,inputSchema:b,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. ${T}`,inputSchema:b,name:"lunora_run_action"}],De=new Set(M.map(e=>e.name)),$=e=>{const{functionPath:t}=e;if(typeof t!="string"||t.length===0)throw new a("BAD_REQUEST",'"functionPath" is required and must be a non-empty string');return t},O=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 a("BAD_REQUEST",'"args" must be a JSON object; received a string that is not valid JSON')}if(!O(t))throw new a("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${R(t)}`);return t}if(!O(e))throw new a("BAD_REQUEST",`"args" must be a JSON object, got ${R(e)}`);return e},K=e=>{const t=$(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,p=new WeakMap,g=e=>{const t=Date.now(),n=p.get(e);if(n!==void 0&&n.expiresAt>t)return n.promise;const o=e.listFunctions().catch(i=>{throw p.get(e)?.promise===o&&p.delete(e),i});return p.set(e,{expiresAt:t+Le,promise:o}),o},W=async(e,t,n)=>{const i=(await g(e)).find(r=>r.path===t);if(i===void 0)throw new a("NOT_FOUND",`function not found or not public: ${t}`);if(i.kind!==n)throw new a("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=$(n),r=(await g(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 g(e));case"lunora_list_tables":return u(await e.listGlobalTables());case"lunora_run_query":{const{args:o,functionPath:i,shardKey:r}=K(n);return await W(e,i,"query"),u(await e.query(m(i),o,{shardKey:r}))}default:throw new a("INTERNAL",`unknown read-only tool: ${t}`)}},He=async(e,t,n)=>{if(t!=="lunora_run_action"&&t!=="lunora_run_mutation")throw new a("INTERNAL",`unknown write tool: ${t}`);const o=t==="lunora_run_action"?"action":"mutation",{args:i,functionPath:r,shardKey:s}=K(n);await W(e,r,o);const c=await ke(e,t,o,{args:i,functionPath:r,shardKey:s},n);return c!==void 0?c:u(o==="action"?await e.action(m(r),i,{shardKey:s}):await e.mutation(m(r),i,{shardKey:s}))},v="lunora:data-read",S="lunora:observability",Pe=e=>e===!0,f=[{call:Ue,definitions:x,names:Ie},{call:(e,t,n)=>ce(t,n),definitions:I,names:ie},{call:fe,definitions:D,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 or the allowDataReads option; on the authed path the token also needs the ${v} scope.`,scope:v},names:ue},{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 or the allowObservability option; on the authed path the token also needs the ${S} scope.`,scope:S},names:J},{call:He,definitions:M,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}],w=(e,t)=>e.gate!==void 0&&!Pe(t[e.gate.flag])?e.gate:void 0,Be=(e,t=!1,n=!1)=>{const o={allowDataReads:n,allowObservability:t,allowWrites:e};return f.filter(i=>w(i,o)===void 0).flatMap(i=>i.definitions)},Fe=async(e,t,n,o=!1,i=!1,r=!1)=>{const s=f.find(d=>d.names.has(t));if(s===void 0)return l(`unknown tool: ${t}`);const c=w(s,{allowDataReads:r,allowObservability:i,allowWrites:o});if(c!==void 0)return l(c.refuse(t));try{return await s.call(e,t,n)}catch(d){const B=d instanceof Error?d.message:String(d);return l(B)}},qe=(e,t)=>{const n=f.find(o=>o.names.has(e));if(!(n?.gate?.scope===void 0||w(n,t)!==void 0))return n.gate.scope},je=f.flatMap(e=>e.gate?.scope===void 0?[]:[{flag:e.gate.flag,scope:e.gate.scope}]);export{v as D,I as E,S as O,x as R,je as S,M as W,ce as a,ie as b,Fe as c,De as d,D as e,qe as s,Be as t};
|