@lunora/mcp 1.0.0-alpha.240 → 1.0.0-alpha.242
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/dist/bin.mjs +1 -1
- package/dist/docs/index.d.mts +4 -3
- package/dist/docs/index.d.ts +4 -3
- package/dist/index.d.mts +11 -473
- package/dist/index.d.ts +11 -473
- package/dist/index.mjs +1 -1
- package/dist/node/index.d.mts +90 -0
- package/dist/node/index.d.ts +90 -0
- package/dist/node/index.mjs +1 -0
- package/dist/packem_shared/{LOCAL_SERVER_NAME-DM_NJ-eY.mjs → LOCAL_SERVER_NAME-Ci-W2me-.mjs} +1 -1
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-DT0XSV-2.mjs +1 -0
- package/dist/packem_shared/{serve-stateless.d-CKsbI0fP.d.mts → compose.d-BVnP3uAy.d.mts} +1 -44
- package/dist/packem_shared/{serve-stateless.d-CKsbI0fP.d.ts → compose.d-BVnP3uAy.d.ts} +1 -44
- package/dist/packem_shared/connectStdio-DHQHl6rX.mjs +1 -0
- package/dist/packem_shared/createAuthedMcpFetchHandler-BXBFeTtN.mjs +1 -0
- package/dist/packem_shared/createLunoraMcpServer-CuxvnEnG.mjs +1 -0
- package/dist/packem_shared/createMcpFetchHandler-DKTmo27x.mjs +1 -0
- package/dist/packem_shared/createPaidMcpServer-BH_mly77.mjs +1 -0
- package/dist/packem_shared/serve-stateless.d-DD5arqFx.d.mts +45 -0
- package/dist/packem_shared/serve-stateless.d-DD5arqFx.d.ts +45 -0
- package/dist/packem_shared/server.d-D3rejkVc.d.mts +152 -0
- package/dist/packem_shared/server.d-DQgZyRkG.d.ts +152 -0
- package/dist/packem_shared/tools-DSqDlu_n.mjs +2 -0
- package/dist/worker/index.d.mts +258 -0
- package/dist/worker/index.d.ts +258 -0
- package/dist/worker/index.mjs +1 -0
- package/package.json +12 -3
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-DA4wD25I.mjs +0 -1
- package/dist/packem_shared/connectStdio-n-RHjcwN.mjs +0 -1
- package/dist/packem_shared/createAuthedMcpFetchHandler-DSTZ2ftz.mjs +0 -1
- package/dist/packem_shared/createMcpFetchHandler-CNEfLA-N.mjs +0 -1
- package/dist/packem_shared/createPaidMcpServer-C_V1beyQ.mjs +0 -1
- package/dist/packem_shared/tools-Dmnb3M2T.mjs +0 -2
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { LunoraClient } from '@lunora/client';
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { a as McpTool } from "../packem_shared/compose.d-BVnP3uAy.js";
|
|
4
|
+
import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-DQgZyRkG.js";
|
|
5
|
+
export { d as createLunoraMcpServer } from "../packem_shared/server.d-DQgZyRkG.js";
|
|
6
|
+
/** A Lunora deployment the tools dispatch against. */
|
|
7
|
+
interface LocalDeployment {
|
|
8
|
+
token?: string;
|
|
9
|
+
url: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Where the deployment comes from: a fixed value, or a function consulted on
|
|
13
|
+
* every tool call.
|
|
14
|
+
*
|
|
15
|
+
* The resolver form exists because an editor spawns this server when the
|
|
16
|
+
* project opens — routinely *before* `lunora dev` is running, and it keeps the
|
|
17
|
+
* process alive across every restart afterwards. A URL captured once at startup
|
|
18
|
+
* would therefore be absent for the entire first session and stale after the
|
|
19
|
+
* first restart.
|
|
20
|
+
*/
|
|
21
|
+
type LocalDeploymentSource = (() => LocalDeployment | undefined) | LocalDeployment;
|
|
22
|
+
interface LocalMcpServerOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Expose the deployment write tools (`lunora_run_mutation` /
|
|
25
|
+
* `lunora_run_action`). Defaults to `false` — the same fail-closed default
|
|
26
|
+
* as the remote server. Locally the blast radius is dev data rather than
|
|
27
|
+
* production, but a mutation is still a side effect an agent should be
|
|
28
|
+
* granted deliberately.
|
|
29
|
+
*/
|
|
30
|
+
allowWrites?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The Lunora deployment (usually the running dev server) to expose. Omit to
|
|
33
|
+
* leave the deployment tools out entirely.
|
|
34
|
+
*/
|
|
35
|
+
deployment?: LocalDeploymentSource;
|
|
36
|
+
/** Docs site origin backing the documentation tools; `false` omits them. */
|
|
37
|
+
docs?: false | {
|
|
38
|
+
baseUrl?: string;
|
|
39
|
+
};
|
|
40
|
+
/** Extra tools to compose in, e.g. the CLI's local dev-server tools. */
|
|
41
|
+
extraTools?: ReadonlyArray<McpTool>;
|
|
42
|
+
/** `fetch` implementation; defaults to the ambient global. */
|
|
43
|
+
fetch?: typeof fetch;
|
|
44
|
+
/** Version reported in the MCP handshake — the host CLI's, not this package's. */
|
|
45
|
+
version?: string;
|
|
46
|
+
}
|
|
47
|
+
/** Server identity advertised in the MCP `initialize` handshake. */
|
|
48
|
+
declare const LOCAL_SERVER_NAME = "lunora";
|
|
49
|
+
/** Shown when a deployment tool is called and no dev server can be found. */
|
|
50
|
+
declare const NO_DEPLOYMENT_MESSAGE = "no Lunora dev server is running for this project — start one with `lunora dev`, then call this tool again (call lunora_dev_status to check).";
|
|
51
|
+
/**
|
|
52
|
+
* Assemble the tool list, in the order it is advertised: docs first (the
|
|
53
|
+
* surface that always works), then the caller's extras, then the error catalog
|
|
54
|
+
* (compiled in, so it needs no deployment either), then the deployment tools.
|
|
55
|
+
* Order also decides precedence — `createToolServer` keeps the first
|
|
56
|
+
* registration of a duplicated name.
|
|
57
|
+
*
|
|
58
|
+
* `clientFor` is the shared client cache built once by
|
|
59
|
+
* {@link createLocalMcpServer} and threaded into both the tool and resource
|
|
60
|
+
* surfaces. Exported (and called directly by tests) without going through
|
|
61
|
+
* `createLocalMcpServer`, so a caller that omits it gets a private,
|
|
62
|
+
* call-scoped cache — same shape as before this surface was shared, just
|
|
63
|
+
* without the cross-surface sharing that only matters once a resource
|
|
64
|
+
* surface exists alongside it.
|
|
65
|
+
*/
|
|
66
|
+
declare const localTools: (options: LocalMcpServerOptions, clientFor?: (deployment: LocalDeployment) => LunoraClient) => ReadonlyArray<McpTool>;
|
|
67
|
+
/** Build the composed local server without connecting a transport. */
|
|
68
|
+
declare const createLocalMcpServer: (options?: LocalMcpServerOptions) => Server;
|
|
69
|
+
/**
|
|
70
|
+
* Build the composed local server and connect it over stdio — the transport an
|
|
71
|
+
* MCP client uses when it spawns `lunora mcp serve`. Resolves once connected;
|
|
72
|
+
* the process then stays alive serving requests.
|
|
73
|
+
*/
|
|
74
|
+
declare const connectLocalStdio: (options?: LocalMcpServerOptions) => Promise<Server>;
|
|
75
|
+
/**
|
|
76
|
+
* Build the server and connect it over stdio — the transport MCP clients use
|
|
77
|
+
* when they spawn the `lunora-mcp` binary. Resolves once the transport is
|
|
78
|
+
* connected; the process then stays alive serving requests.
|
|
79
|
+
*
|
|
80
|
+
* Lives apart from `server.ts` so the Worker entry's module graph never pulls
|
|
81
|
+
* in the stdio transport (and its `node:process` import).
|
|
82
|
+
*/
|
|
83
|
+
declare const connectStdio: (options: LunoraMcpServerOptions) => Promise<Server>;
|
|
84
|
+
export { LOCAL_SERVER_NAME,
|
|
85
|
+
/** Node-only entry: the stdio transport and the local/deployment servers. */
|
|
86
|
+
type LocalDeployment,
|
|
87
|
+
/** Node-only entry: the stdio transport and the local/deployment servers. */
|
|
88
|
+
type LocalDeploymentSource,
|
|
89
|
+
/** Node-only entry: the stdio transport and the local/deployment servers. */
|
|
90
|
+
type LocalMcpServerOptions, type LunoraMcpServerOptions, NO_DEPLOYMENT_MESSAGE, connectLocalStdio, connectStdio, createLocalMcpServer, localTools };
|
|
@@ -0,0 +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-Ci-W2me-.mjs";import{createLunoraMcpServer as l}from"../packem_shared/createLunoraMcpServer-CuxvnEnG.mjs";import{default as n}from"../packem_shared/connectStdio-DHQHl6rX.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};
|
package/dist/packem_shared/{LOCAL_SERVER_NAME-DM_NJ-eY.mjs → LOCAL_SERVER_NAME-Ci-W2me-.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraClient as f}from"@lunora/client";import{StdioServerTransport as m}from"@modelcontextprotocol/sdk/server/stdio.js";import{e as p}from"./promise-memo-OqVPWoUX.mjs";import{createToolServer as v}from"./createToolServer-BtGuPyMU.mjs";import{createRemoteDocsIndex as h}from"./DEFAULT_DOCS_BASE_URL-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,
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
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,5 +1,4 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { HandleRequestOptions } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
3
2
|
/**
|
|
4
3
|
* The MCP wire shapes every tool surface in this package speaks — the
|
|
5
4
|
* deployment tools (`./tools`), the agent tools (`./agent-tools`), and the
|
|
@@ -109,46 +108,4 @@ interface McpResourceProvider {
|
|
|
109
108
|
* not a protocol-level fault.
|
|
110
109
|
*/
|
|
111
110
|
declare const createToolServer: (info: McpServerInfo, tools: ReadonlyArray<McpTool>, resources?: McpResourceProvider) => Server;
|
|
112
|
-
|
|
113
|
-
type McpFetchHandler = (request: Request) => Promise<Response>;
|
|
114
|
-
/**
|
|
115
|
-
* Largest request body served. Every MCP call is a short JSON-RPC message — a
|
|
116
|
-
* few hundred bytes — so this is orders of magnitude of headroom while still
|
|
117
|
-
* bounding what a caller can push through the parser.
|
|
118
|
-
*/
|
|
119
|
-
declare const DEFAULT_MAX_REQUEST_BYTES: number;
|
|
120
|
-
/** What {@link serveStateless} accepts on top of the transport's own options. */
|
|
121
|
-
interface ServeStatelessOptions extends HandleRequestOptions {
|
|
122
|
-
/**
|
|
123
|
-
* Largest accepted request body, in bytes — enforced while the body streams
|
|
124
|
-
* in, not after it is buffered. Defaults to
|
|
125
|
-
* {@link DEFAULT_MAX_REQUEST_BYTES}, which a value that is not a
|
|
126
|
-
* non-negative safe integer also falls back to (see `resolveLimit`).
|
|
127
|
-
*/
|
|
128
|
-
maxRequestBytes?: number;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Drive one request through a fresh **stateless** Streamable-HTTP transport bound
|
|
132
|
-
* to `server`, then tear both down.
|
|
133
|
-
*
|
|
134
|
-
* `sessionIdGenerator: undefined` → stateless (no cross-request session store);
|
|
135
|
-
* `enableJsonResponse: true` → the whole JSON-RPC response is buffered into the
|
|
136
|
-
* returned `Response`, so closing the transport/server afterwards can't truncate
|
|
137
|
-
* an in-flight body. Cleanup is fire-and-forget with swallowed rejections (a
|
|
138
|
-
* `.catch()`-terminated chain) so it never delays or masks the resolved response.
|
|
139
|
-
*
|
|
140
|
-
* The request is screened first ({@link screenRequest}); an oversized body or a
|
|
141
|
-
* JSON-RPC batch is refused here and no server is ever constructed for it.
|
|
142
|
-
*
|
|
143
|
-
* `options.parsedBody` lets a caller hand over a body it already read (e.g. the
|
|
144
|
-
* paid-tool gate, which peeks the JSON-RPC message to price the call) so the
|
|
145
|
-
* transport doesn't re-read a consumed stream. It is trusted as already
|
|
146
|
-
* screened, so read it with {@link readScreenedBody} — anything else hands this
|
|
147
|
-
* surface an unbounded body.
|
|
148
|
-
*
|
|
149
|
-
* Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
|
|
150
|
-
* would otherwise skip it and leak a server + transport per failed request,
|
|
151
|
-
* which on a public endpoint is exactly the request an attacker can repeat.
|
|
152
|
-
*/
|
|
153
|
-
declare const serveStateless: (server: Server, request: Request, options?: ServeStatelessOptions) => Promise<Response>;
|
|
154
|
-
export { DEFAULT_MAX_REQUEST_BYTES as D, McpResourceProvider as M, ServeStatelessOptions as S, ToolDefinition as T, McpFetchHandler as a, McpTool as b, McpResourceSummary as c, McpServerInfo as d, ToolInputSchema as e, ToolResult as f, createToolServer as g, serveStateless as s };
|
|
111
|
+
export { McpServerInfo as M, ToolDefinition as T, McpTool as a, ToolInputSchema as b, ToolResult as c, createToolServer as d, McpResourceProvider as e, McpResourceSummary as f };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { HandleRequestOptions } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
3
2
|
/**
|
|
4
3
|
* The MCP wire shapes every tool surface in this package speaks — the
|
|
5
4
|
* deployment tools (`./tools`), the agent tools (`./agent-tools`), and the
|
|
@@ -109,46 +108,4 @@ interface McpResourceProvider {
|
|
|
109
108
|
* not a protocol-level fault.
|
|
110
109
|
*/
|
|
111
110
|
declare const createToolServer: (info: McpServerInfo, tools: ReadonlyArray<McpTool>, resources?: McpResourceProvider) => Server;
|
|
112
|
-
|
|
113
|
-
type McpFetchHandler = (request: Request) => Promise<Response>;
|
|
114
|
-
/**
|
|
115
|
-
* Largest request body served. Every MCP call is a short JSON-RPC message — a
|
|
116
|
-
* few hundred bytes — so this is orders of magnitude of headroom while still
|
|
117
|
-
* bounding what a caller can push through the parser.
|
|
118
|
-
*/
|
|
119
|
-
declare const DEFAULT_MAX_REQUEST_BYTES: number;
|
|
120
|
-
/** What {@link serveStateless} accepts on top of the transport's own options. */
|
|
121
|
-
interface ServeStatelessOptions extends HandleRequestOptions {
|
|
122
|
-
/**
|
|
123
|
-
* Largest accepted request body, in bytes — enforced while the body streams
|
|
124
|
-
* in, not after it is buffered. Defaults to
|
|
125
|
-
* {@link DEFAULT_MAX_REQUEST_BYTES}, which a value that is not a
|
|
126
|
-
* non-negative safe integer also falls back to (see `resolveLimit`).
|
|
127
|
-
*/
|
|
128
|
-
maxRequestBytes?: number;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Drive one request through a fresh **stateless** Streamable-HTTP transport bound
|
|
132
|
-
* to `server`, then tear both down.
|
|
133
|
-
*
|
|
134
|
-
* `sessionIdGenerator: undefined` → stateless (no cross-request session store);
|
|
135
|
-
* `enableJsonResponse: true` → the whole JSON-RPC response is buffered into the
|
|
136
|
-
* returned `Response`, so closing the transport/server afterwards can't truncate
|
|
137
|
-
* an in-flight body. Cleanup is fire-and-forget with swallowed rejections (a
|
|
138
|
-
* `.catch()`-terminated chain) so it never delays or masks the resolved response.
|
|
139
|
-
*
|
|
140
|
-
* The request is screened first ({@link screenRequest}); an oversized body or a
|
|
141
|
-
* JSON-RPC batch is refused here and no server is ever constructed for it.
|
|
142
|
-
*
|
|
143
|
-
* `options.parsedBody` lets a caller hand over a body it already read (e.g. the
|
|
144
|
-
* paid-tool gate, which peeks the JSON-RPC message to price the call) so the
|
|
145
|
-
* transport doesn't re-read a consumed stream. It is trusted as already
|
|
146
|
-
* screened, so read it with {@link readScreenedBody} — anything else hands this
|
|
147
|
-
* surface an unbounded body.
|
|
148
|
-
*
|
|
149
|
-
* Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
|
|
150
|
-
* would otherwise skip it and leak a server + transport per failed request,
|
|
151
|
-
* which on a public endpoint is exactly the request an attacker can repeat.
|
|
152
|
-
*/
|
|
153
|
-
declare const serveStateless: (server: Server, request: Request, options?: ServeStatelessOptions) => Promise<Response>;
|
|
154
|
-
export { DEFAULT_MAX_REQUEST_BYTES as D, McpResourceProvider as M, ServeStatelessOptions as S, ToolDefinition as T, McpFetchHandler as a, McpTool as b, McpResourceSummary as c, McpServerInfo as d, ToolInputSchema as e, ToolResult as f, createToolServer as g, serveStateless as s };
|
|
111
|
+
export { McpServerInfo as M, ToolDefinition as T, McpTool as a, ToolInputSchema as b, ToolResult as c, createToolServer as d, McpResourceProvider as e, McpResourceSummary as f };
|
|
@@ -0,0 +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-CuxvnEnG.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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as y}from"@lunora/errors";import{isAgentToolName as S}from"./AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{readScreenedBody as g,serveStateless as w}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{resolveClient as d,createLunoraMcpServer as T}from"./createLunoraMcpServer-CuxvnEnG.mjs";import{c as M}from"./tool-call-Bs-QeKp6.mjs";import{d as E,s as _,S as k}from"./tools-DSqDlu_n.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};
|
|
@@ -0,0 +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-hKbpa3Dg.mjs";import{t as R,c as k}from"./tools-DSqDlu_n.mjs";var N="1.0.0-alpha.241";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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{serveStateless as c}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{DEFAULT_MAX_REQUEST_BYTES as u}from"./DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{resolveClient as s,createLunoraMcpServer as a}from"./createLunoraMcpServer-CuxvnEnG.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 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};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { HandleRequestOptions } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
3
|
+
/** A Web-Standard fetch handler: takes a `Request`, returns the MCP `Response`. */
|
|
4
|
+
type McpFetchHandler = (request: Request) => Promise<Response>;
|
|
5
|
+
/**
|
|
6
|
+
* Largest request body served. Every MCP call is a short JSON-RPC message — a
|
|
7
|
+
* few hundred bytes — so this is orders of magnitude of headroom while still
|
|
8
|
+
* bounding what a caller can push through the parser.
|
|
9
|
+
*/
|
|
10
|
+
declare const DEFAULT_MAX_REQUEST_BYTES: number;
|
|
11
|
+
/** What {@link serveStateless} accepts on top of the transport's own options. */
|
|
12
|
+
interface ServeStatelessOptions extends HandleRequestOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
15
|
+
* in, not after it is buffered. Defaults to
|
|
16
|
+
* {@link DEFAULT_MAX_REQUEST_BYTES}, which a value that is not a
|
|
17
|
+
* non-negative safe integer also falls back to (see `resolveLimit`).
|
|
18
|
+
*/
|
|
19
|
+
maxRequestBytes?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Drive one request through a fresh **stateless** Streamable-HTTP transport bound
|
|
23
|
+
* to `server`, then tear both down.
|
|
24
|
+
*
|
|
25
|
+
* `sessionIdGenerator: undefined` → stateless (no cross-request session store);
|
|
26
|
+
* `enableJsonResponse: true` → the whole JSON-RPC response is buffered into the
|
|
27
|
+
* returned `Response`, so closing the transport/server afterwards can't truncate
|
|
28
|
+
* an in-flight body. Cleanup is fire-and-forget with swallowed rejections (a
|
|
29
|
+
* `.catch()`-terminated chain) so it never delays or masks the resolved response.
|
|
30
|
+
*
|
|
31
|
+
* The request is screened first ({@link screenRequest}); an oversized body or a
|
|
32
|
+
* JSON-RPC batch is refused here and no server is ever constructed for it.
|
|
33
|
+
*
|
|
34
|
+
* `options.parsedBody` lets a caller hand over a body it already read (e.g. the
|
|
35
|
+
* paid-tool gate, which peeks the JSON-RPC message to price the call) so the
|
|
36
|
+
* transport doesn't re-read a consumed stream. It is trusted as already
|
|
37
|
+
* screened, so read it with {@link readScreenedBody} — anything else hands this
|
|
38
|
+
* surface an unbounded body.
|
|
39
|
+
*
|
|
40
|
+
* Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
|
|
41
|
+
* would otherwise skip it and leak a server + transport per failed request,
|
|
42
|
+
* which on a public endpoint is exactly the request an attacker can repeat.
|
|
43
|
+
*/
|
|
44
|
+
declare const serveStateless: (server: Server, request: Request, options?: ServeStatelessOptions) => Promise<Response>;
|
|
45
|
+
export { DEFAULT_MAX_REQUEST_BYTES as D, McpFetchHandler as M, ServeStatelessOptions as S, serveStateless as s };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { HandleRequestOptions } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
3
|
+
/** A Web-Standard fetch handler: takes a `Request`, returns the MCP `Response`. */
|
|
4
|
+
type McpFetchHandler = (request: Request) => Promise<Response>;
|
|
5
|
+
/**
|
|
6
|
+
* Largest request body served. Every MCP call is a short JSON-RPC message — a
|
|
7
|
+
* few hundred bytes — so this is orders of magnitude of headroom while still
|
|
8
|
+
* bounding what a caller can push through the parser.
|
|
9
|
+
*/
|
|
10
|
+
declare const DEFAULT_MAX_REQUEST_BYTES: number;
|
|
11
|
+
/** What {@link serveStateless} accepts on top of the transport's own options. */
|
|
12
|
+
interface ServeStatelessOptions extends HandleRequestOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
15
|
+
* in, not after it is buffered. Defaults to
|
|
16
|
+
* {@link DEFAULT_MAX_REQUEST_BYTES}, which a value that is not a
|
|
17
|
+
* non-negative safe integer also falls back to (see `resolveLimit`).
|
|
18
|
+
*/
|
|
19
|
+
maxRequestBytes?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Drive one request through a fresh **stateless** Streamable-HTTP transport bound
|
|
23
|
+
* to `server`, then tear both down.
|
|
24
|
+
*
|
|
25
|
+
* `sessionIdGenerator: undefined` → stateless (no cross-request session store);
|
|
26
|
+
* `enableJsonResponse: true` → the whole JSON-RPC response is buffered into the
|
|
27
|
+
* returned `Response`, so closing the transport/server afterwards can't truncate
|
|
28
|
+
* an in-flight body. Cleanup is fire-and-forget with swallowed rejections (a
|
|
29
|
+
* `.catch()`-terminated chain) so it never delays or masks the resolved response.
|
|
30
|
+
*
|
|
31
|
+
* The request is screened first ({@link screenRequest}); an oversized body or a
|
|
32
|
+
* JSON-RPC batch is refused here and no server is ever constructed for it.
|
|
33
|
+
*
|
|
34
|
+
* `options.parsedBody` lets a caller hand over a body it already read (e.g. the
|
|
35
|
+
* paid-tool gate, which peeks the JSON-RPC message to price the call) so the
|
|
36
|
+
* transport doesn't re-read a consumed stream. It is trusted as already
|
|
37
|
+
* screened, so read it with {@link readScreenedBody} — anything else hands this
|
|
38
|
+
* surface an unbounded body.
|
|
39
|
+
*
|
|
40
|
+
* Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
|
|
41
|
+
* would otherwise skip it and leak a server + transport per failed request,
|
|
42
|
+
* which on a public endpoint is exactly the request an attacker can repeat.
|
|
43
|
+
*/
|
|
44
|
+
declare const serveStateless: (server: Server, request: Request, options?: ServeStatelessOptions) => Promise<Response>;
|
|
45
|
+
export { DEFAULT_MAX_REQUEST_BYTES as D, McpFetchHandler as M, ServeStatelessOptions as S, serveStateless as s };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { LunoraClient } from '@lunora/client';
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { b as ToolInputSchema, T as ToolDefinition, c as ToolResult } from "./compose.d-BVnP3uAy.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
|
|
6
|
+
* MCP tool an external agent can call. The capability boundary is the MCP-server
|
|
7
|
+
* process + its token, so WHICH agents are exposed is config on the server (like
|
|
8
|
+
* `allowWrites`), not on `defineAgent` — keeping `@lunora/agent` codegen
|
|
9
|
+
* byte-identical.
|
|
10
|
+
*/
|
|
11
|
+
interface McpAgentExposure {
|
|
12
|
+
/** What the agent does — shown to the calling model, which decides from it. */
|
|
13
|
+
description: string;
|
|
14
|
+
/** The agent's export name (its `ctx.agents.<name>` / `AGENT_<NAME>` binding). */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Override the model-facing tool name (default `agent_<name>`). */
|
|
17
|
+
toolName?: string;
|
|
18
|
+
}
|
|
19
|
+
/** The generic status/poll tool advertised alongside the per-agent tools. */
|
|
20
|
+
declare const AGENT_STATUS_TOOL_NAME = "lunora_agent_status";
|
|
21
|
+
/**
|
|
22
|
+
* The uniform input schema every agent tool advertises. Agents share ONE run
|
|
23
|
+
* input (`@lunora/agent` has no per-agent validator), so there is nothing to
|
|
24
|
+
* derive per agent — a single static schema is reused for every agent tool.
|
|
25
|
+
*/
|
|
26
|
+
declare const AGENT_RUN_INPUT_SCHEMA: ToolInputSchema;
|
|
27
|
+
/**
|
|
28
|
+
* Parse `LUNORA_MCP_AGENTS` — a `;`-separated list of `name:description` pairs,
|
|
29
|
+
* e.g. `"support:Handles support questions;billing:Billing help"`. The
|
|
30
|
+
* description may itself contain colons (only the FIRST colon splits). Blank
|
|
31
|
+
* entries and entries with an empty name/description are skipped.
|
|
32
|
+
*/
|
|
33
|
+
declare const parseAgentsEnv: (raw: string | undefined) => McpAgentExposure[];
|
|
34
|
+
/**
|
|
35
|
+
* The tools this module advertises. Fail-closed: only the boolean `true` opts
|
|
36
|
+
* in (an env-plumbed caller could pass a truthy string), and the tools appear
|
|
37
|
+
* ONLY when at least one agent is exposed — so an agent-free or non-opted-in
|
|
38
|
+
* server never lists them.
|
|
39
|
+
*/
|
|
40
|
+
declare const agentToolDefinitions: (exposures: ReadonlyArray<McpAgentExposure>, allowAgents: boolean) => ReadonlyArray<ToolDefinition>;
|
|
41
|
+
/** Options threaded into a single agent tool dispatch. */
|
|
42
|
+
interface CallAgentToolOptions {
|
|
43
|
+
/** Opt-in gate — must be exactly `true` or the call is refused fail-closed. */
|
|
44
|
+
allowAgents: boolean;
|
|
45
|
+
/** The exposures advertised by this server. */
|
|
46
|
+
exposures: ReadonlyArray<McpAgentExposure>;
|
|
47
|
+
/** Wall-clock budget a single call awaits before returning a pending result. */
|
|
48
|
+
maxWaitMs?: number;
|
|
49
|
+
/** Delay between thread-status polls. */
|
|
50
|
+
pollIntervalMs?: number;
|
|
51
|
+
/** Test seam replacing the between-poll wait; production uses a real timer. */
|
|
52
|
+
wait?: (ms: number) => Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Dispatch an agent tool call: start a durable run via `agents:agentRun`, then
|
|
56
|
+
* await-with-timeout — poll `agents:agentThread` until terminal (returning the
|
|
57
|
+
* final answer from `agents:agentMessages`) or, on budget exhaustion, return a
|
|
58
|
+
* NON-error pending payload the caller resumes with `lunora_agent_status`.
|
|
59
|
+
*
|
|
60
|
+
* Fail-closed: refused at dispatch unless `allowAgents === true`, mirroring the
|
|
61
|
+
* `allowWrites` guard — starting a run is a side effect and must not ride the
|
|
62
|
+
* read-only default.
|
|
63
|
+
*/
|
|
64
|
+
declare const callAgentTool: (client: LunoraClient, name: string, input: Record<string, unknown>, options: CallAgentToolOptions) => Promise<ToolResult>;
|
|
65
|
+
interface LunoraMcpServerOptions {
|
|
66
|
+
/** Wall-clock budget a single agent tool call awaits before returning a pending result. */
|
|
67
|
+
agentMaxWaitMs?: number;
|
|
68
|
+
/** Delay between agent thread-status polls. */
|
|
69
|
+
agentPollIntervalMs?: number;
|
|
70
|
+
/** The agents this server fronts as MCP tools (see `allowAgents`). */
|
|
71
|
+
agents?: ReadonlyArray<McpAgentExposure>;
|
|
72
|
+
/**
|
|
73
|
+
* Expose the per-agent tools (`agent_<name>` + the generic
|
|
74
|
+
* `lunora_agent_status`). Defaults to `false`, mirroring `allowWrites`:
|
|
75
|
+
* starting a durable agent run is a side effect, so the agent tools are
|
|
76
|
+
* omitted from the advertised list AND refused at dispatch unless explicitly
|
|
77
|
+
* opted in. Only takes effect together with a non-empty `agents` list.
|
|
78
|
+
*/
|
|
79
|
+
allowAgents?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Expose `lunora_find_related`, which returns raw table rows read through the
|
|
82
|
+
* admin writer, so RLS policies and column masks do not apply. Defaults to
|
|
83
|
+
* `false`, for the same reason as `allowObservability`. Only takes effect
|
|
84
|
+
* when a `token` resolved. On the OAuth-protected path the verified token must
|
|
85
|
+
* also carry the data-read scope (see `DATA_READ_SCOPE` in `./tools`).
|
|
86
|
+
*/
|
|
87
|
+
allowDataReads?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Expose the observability tools (`lunora_get_logs`, `lunora_get_issues`,
|
|
90
|
+
* `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
91
|
+
* `lunora_get_migration_status`). Defaults to `false`, mirroring
|
|
92
|
+
* `allowWrites`: they are read-only, but every row they return — log lines,
|
|
93
|
+
* request metadata, grouped error messages — is production user data that
|
|
94
|
+
* lands in the model's context and therefore at its provider. Holding the
|
|
95
|
+
* admin bearer is not consent to ship that, so it is a separate opt-in;
|
|
96
|
+
* without it the tools are omitted from the advertised list AND refused at
|
|
97
|
+
* dispatch. Only takes effect when a `token` resolved. On the OAuth-protected
|
|
98
|
+
* path the verified token must also carry the observability scope (see
|
|
99
|
+
* `OBSERVABILITY_SCOPE` in `./tools`); this option stays the outer gate.
|
|
100
|
+
*/
|
|
101
|
+
allowObservability?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Expose the write tools (`lunora_run_mutation` / `lunora_run_action`).
|
|
104
|
+
* Defaults to `false`: the server is READ-ONLY unless explicitly opted in,
|
|
105
|
+
* so a prompt-injected or misaligned agent can't mutate the deployment with
|
|
106
|
+
* the configured token. When false the write tools are omitted from the
|
|
107
|
+
* advertised tool list AND refused at dispatch.
|
|
108
|
+
*/
|
|
109
|
+
allowWrites?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Pre-built client (test injection). When omitted a `LunoraClient` is
|
|
112
|
+
* created from `url`/`token`/`fetch`.
|
|
113
|
+
*/
|
|
114
|
+
client?: LunoraClient;
|
|
115
|
+
/** `fetch` implementation; defaults to the ambient global. */
|
|
116
|
+
fetch?: typeof fetch;
|
|
117
|
+
/**
|
|
118
|
+
* Bearer token sent on every RPC. **Required** alongside `url`, and it must
|
|
119
|
+
* be the deployment's **admin bearer**: the introspection/allowlist path
|
|
120
|
+
* every tool depends on (`lunora_list_functions`, `lunora_list_tables`, and
|
|
121
|
+
* the `assertRunnable` precheck that runs before every `run` tool) hits
|
|
122
|
+
* admin-gated `/_lunora/admin/*` routes, so no scoped/app token works
|
|
123
|
+
* today — it would 403 (`ADMIN_FORBIDDEN`) on the first tool call. The
|
|
124
|
+
* read-only guarantee is therefore NOT enforced by the token's scope; it is
|
|
125
|
+
* enforced in-process via `allowWrites: false` (the default), which omits
|
|
126
|
+
* the write tools from the advertised list and refuses them at dispatch.
|
|
127
|
+
*
|
|
128
|
+
* Because EVERY tool needs it, omitting it is a misconfiguration rather than
|
|
129
|
+
* a reduced-capability mode, and `createLunoraMcpServer` says so at
|
|
130
|
+
* construction instead of advertising a surface that 403s on first use. The
|
|
131
|
+
* one exception is the `client` injection seam (tests / a pre-authenticated
|
|
132
|
+
* client), where this server cannot know what the client can reach and so
|
|
133
|
+
* reads "unknown" fail-closed: the privileged observability tools stay
|
|
134
|
+
* unadvertised and are refused at dispatch.
|
|
135
|
+
*/
|
|
136
|
+
token?: string;
|
|
137
|
+
/** Base URL of the deployed Lunora Worker. Required unless `client` is given. */
|
|
138
|
+
url?: string;
|
|
139
|
+
/** Version reported in the `initialize` handshake. Defaults to the version bundled at build time. */
|
|
140
|
+
version?: string;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Build an MCP `Server` whose tools talk to a Lunora deployment. The server is
|
|
144
|
+
* transport-agnostic — call `.connect(transport)` yourself, or use
|
|
145
|
+
* `connectStdio` for the common stdio case.
|
|
146
|
+
*
|
|
147
|
+
* Tool calls are dispatched through `callTool`, which the deployment reaches
|
|
148
|
+
* over HTTP RPC. No WebSocket is opened (the tools never subscribe), so this is
|
|
149
|
+
* safe to run as a short-lived stdio process.
|
|
150
|
+
*/
|
|
151
|
+
declare const createLunoraMcpServer: (options: LunoraMcpServerOptions) => Server;
|
|
152
|
+
export { AGENT_RUN_INPUT_SCHEMA as A, CallAgentToolOptions as C, LunoraMcpServerOptions as L, McpAgentExposure as M, AGENT_STATUS_TOOL_NAME as a, agentToolDefinitions as b, callAgentTool as c, createLunoraMcpServer as d, parseAgentsEnv as p };
|