@lunora/mcp 1.0.0-alpha.24 → 1.0.0-alpha.241
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/LICENSE.md +120 -0
- package/README.md +200 -14
- package/dist/bin.mjs +4 -47
- package/dist/docs/index.d.mts +259 -0
- package/dist/docs/index.d.ts +259 -0
- package/dist/docs/index.mjs +1 -0
- package/dist/index.d.mts +84 -82
- package/dist/index.d.ts +84 -82
- package/dist/index.mjs +1 -5
- 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/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs +1 -0
- package/dist/packem_shared/DEFAULT_DOCS_BASE_URL-CiHy_Dbz.mjs +4 -0
- package/dist/packem_shared/DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs +1 -0
- package/dist/packem_shared/DEFAULT_SEARCH_LIMIT-B8C5JM2y.mjs +3 -0
- package/dist/packem_shared/DOCS_SERVER_NAME-CUnLrk1a.mjs +1 -0
- package/dist/packem_shared/DOCS_URI_SCHEME-C35UQ5Fo.mjs +3 -0
- package/dist/packem_shared/LOCAL_SERVER_NAME-Bj3l_7WG.mjs +1 -0
- package/dist/packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-mO4JuHgV.mjs +1 -0
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-CihHZdB6.mjs +1 -0
- package/dist/packem_shared/compose.d-BVnP3uAy.d.mts +111 -0
- package/dist/packem_shared/compose.d-BVnP3uAy.d.ts +111 -0
- package/dist/packem_shared/connectStdio-DYI-3vnF.mjs +1 -0
- package/dist/packem_shared/createAuthedMcpFetchHandler-D_SQpDhH.mjs +1 -0
- package/dist/packem_shared/createLunoraMcpServer-CQJIwdik.mjs +1 -0
- package/dist/packem_shared/createMcpFetchHandler-TkLzJUNn.mjs +1 -0
- package/dist/packem_shared/createPaidMcpServer-BH_mly77.mjs +1 -0
- package/dist/packem_shared/createToolServer-BtGuPyMU.mjs +1 -0
- package/dist/packem_shared/observability-tools-D4Z8dGS-.mjs +1 -0
- package/dist/packem_shared/promise-memo-OqVPWoUX.mjs +1 -0
- package/dist/packem_shared/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/toDocsSearchHits-CBLmtWXt.mjs +1 -0
- package/dist/packem_shared/tool-call-Bs-QeKp6.mjs +1 -0
- package/dist/packem_shared/tools-BOfWz78m.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 +26 -5
- package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-DWobae3P.mjs +0 -168
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-CWb_wFd5.mjs +0 -204
- package/dist/packem_shared/connectStdio-bkJcaXYE.mjs +0 -76
- package/dist/packem_shared/createMcpFetchHandler-DfzBSLrm.mjs +0 -14
- package/dist/packem_shared/createPaidMcpServer-b-qp_aEF.mjs +0 -93
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The MCP wire shapes every tool surface in this package speaks — the
|
|
4
|
+
* deployment tools (`./tools`), the agent tools (`./agent-tools`), and the
|
|
5
|
+
* documentation tools (`./docs`).
|
|
6
|
+
*
|
|
7
|
+
* They live in their own module (rather than in `./tools`) so a consumer that
|
|
8
|
+
* only wants the docs surface can import the types without dragging
|
|
9
|
+
* `@lunora/client` into its bundle. `./tools` re-exports them, so the package's
|
|
10
|
+
* public API is unchanged.
|
|
11
|
+
*/
|
|
12
|
+
/** A JSON-Schema object describing a tool's arguments, per the MCP spec. */
|
|
13
|
+
interface ToolInputSchema {
|
|
14
|
+
properties: Record<string, unknown>;
|
|
15
|
+
required?: ReadonlyArray<string>;
|
|
16
|
+
type: "object";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* MCP tool annotations — hints a client may use to decide how to present a tool
|
|
20
|
+
* (badge it read-only, confirm before a destructive call).
|
|
21
|
+
*
|
|
22
|
+
* They are hints, not enforcement: this package's actual guarantees are made at
|
|
23
|
+
* dispatch, where a write tool is refused unless `allowWrites` is set. These
|
|
24
|
+
* exist so a client can *show* the user what the server already enforces,
|
|
25
|
+
* instead of every tool looking equally dangerous.
|
|
26
|
+
*/
|
|
27
|
+
interface ToolAnnotations {
|
|
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
|
+
}
|
|
39
|
+
interface ToolDefinition {
|
|
40
|
+
annotations?: ToolAnnotations;
|
|
41
|
+
description: string;
|
|
42
|
+
inputSchema: ToolInputSchema;
|
|
43
|
+
name: string;
|
|
44
|
+
/**
|
|
45
|
+
* JSON Schema for the tool's `structuredContent`. Optional: a tool that
|
|
46
|
+
* emits only a text block declares none, and a client that negotiated an
|
|
47
|
+
* MCP revision older than `2025-06-18` ignores it either way.
|
|
48
|
+
*/
|
|
49
|
+
outputSchema?: ToolInputSchema;
|
|
50
|
+
}
|
|
51
|
+
/** The MCP `CallToolResult` shape this package's tools return. */
|
|
52
|
+
interface ToolResult {
|
|
53
|
+
content: {
|
|
54
|
+
text: string;
|
|
55
|
+
type: "text";
|
|
56
|
+
}[];
|
|
57
|
+
isError?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The machine-readable result, mirroring the text block. Present only for
|
|
60
|
+
* tools that declare an `outputSchema`; per the MCP spec it must be a JSON
|
|
61
|
+
* OBJECT (never a bare array) and must survive `JSON.stringify` — the
|
|
62
|
+
* transport serializes it, so an un-mapped `bigint` here fails the whole
|
|
63
|
+
* response rather than one field.
|
|
64
|
+
*/
|
|
65
|
+
structuredContent?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
/** A tool: what to advertise in `ListTools`, and what to run on `CallTool`. */
|
|
68
|
+
interface McpTool {
|
|
69
|
+
definition: ToolDefinition;
|
|
70
|
+
handle: (input: Record<string, unknown>) => Promise<ToolResult>;
|
|
71
|
+
}
|
|
72
|
+
/** Name/version pair reported in the MCP `initialize` handshake. */
|
|
73
|
+
interface McpServerInfo {
|
|
74
|
+
name: string;
|
|
75
|
+
version: string;
|
|
76
|
+
}
|
|
77
|
+
/** One addressable document a client can list and read directly. */
|
|
78
|
+
interface McpResourceSummary {
|
|
79
|
+
description?: string;
|
|
80
|
+
mimeType?: string;
|
|
81
|
+
name: string;
|
|
82
|
+
uri: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A set of resources the server exposes alongside its tools.
|
|
86
|
+
*
|
|
87
|
+
* Tools are calls a model chooses to make; resources are documents a *client*
|
|
88
|
+
* can enumerate and attach on its own — the user picking a page to put in
|
|
89
|
+
* context, without the model having to guess a search query first. The same
|
|
90
|
+
* corpus is worth offering both ways.
|
|
91
|
+
*/
|
|
92
|
+
interface McpResourceProvider {
|
|
93
|
+
/** Every resource, for `resources/list`. */
|
|
94
|
+
list: () => Promise<ReadonlyArray<McpResourceSummary>>;
|
|
95
|
+
/** One resource's body, or `undefined` when the uri is unknown. */
|
|
96
|
+
read: (uri: string) => Promise<{
|
|
97
|
+
mimeType?: string;
|
|
98
|
+
text: string;
|
|
99
|
+
} | undefined>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Build an MCP `Server` that advertises and dispatches `tools`.
|
|
103
|
+
*
|
|
104
|
+
* On duplicate names the FIRST wins, so a caller can layer surfaces in
|
|
105
|
+
* precedence order without the later list silently shadowing the earlier one.
|
|
106
|
+
* Thrown errors become `isError` results rather than rejections, per the MCP
|
|
107
|
+
* convention that a tool failure is output the model can read and react to,
|
|
108
|
+
* not a protocol-level fault.
|
|
109
|
+
*/
|
|
110
|
+
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, ToolResult as c, createToolServer as d, McpResourceProvider as e, McpResourceSummary as f };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The MCP wire shapes every tool surface in this package speaks — the
|
|
4
|
+
* deployment tools (`./tools`), the agent tools (`./agent-tools`), and the
|
|
5
|
+
* documentation tools (`./docs`).
|
|
6
|
+
*
|
|
7
|
+
* They live in their own module (rather than in `./tools`) so a consumer that
|
|
8
|
+
* only wants the docs surface can import the types without dragging
|
|
9
|
+
* `@lunora/client` into its bundle. `./tools` re-exports them, so the package's
|
|
10
|
+
* public API is unchanged.
|
|
11
|
+
*/
|
|
12
|
+
/** A JSON-Schema object describing a tool's arguments, per the MCP spec. */
|
|
13
|
+
interface ToolInputSchema {
|
|
14
|
+
properties: Record<string, unknown>;
|
|
15
|
+
required?: ReadonlyArray<string>;
|
|
16
|
+
type: "object";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* MCP tool annotations — hints a client may use to decide how to present a tool
|
|
20
|
+
* (badge it read-only, confirm before a destructive call).
|
|
21
|
+
*
|
|
22
|
+
* They are hints, not enforcement: this package's actual guarantees are made at
|
|
23
|
+
* dispatch, where a write tool is refused unless `allowWrites` is set. These
|
|
24
|
+
* exist so a client can *show* the user what the server already enforces,
|
|
25
|
+
* instead of every tool looking equally dangerous.
|
|
26
|
+
*/
|
|
27
|
+
interface ToolAnnotations {
|
|
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
|
+
}
|
|
39
|
+
interface ToolDefinition {
|
|
40
|
+
annotations?: ToolAnnotations;
|
|
41
|
+
description: string;
|
|
42
|
+
inputSchema: ToolInputSchema;
|
|
43
|
+
name: string;
|
|
44
|
+
/**
|
|
45
|
+
* JSON Schema for the tool's `structuredContent`. Optional: a tool that
|
|
46
|
+
* emits only a text block declares none, and a client that negotiated an
|
|
47
|
+
* MCP revision older than `2025-06-18` ignores it either way.
|
|
48
|
+
*/
|
|
49
|
+
outputSchema?: ToolInputSchema;
|
|
50
|
+
}
|
|
51
|
+
/** The MCP `CallToolResult` shape this package's tools return. */
|
|
52
|
+
interface ToolResult {
|
|
53
|
+
content: {
|
|
54
|
+
text: string;
|
|
55
|
+
type: "text";
|
|
56
|
+
}[];
|
|
57
|
+
isError?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The machine-readable result, mirroring the text block. Present only for
|
|
60
|
+
* tools that declare an `outputSchema`; per the MCP spec it must be a JSON
|
|
61
|
+
* OBJECT (never a bare array) and must survive `JSON.stringify` — the
|
|
62
|
+
* transport serializes it, so an un-mapped `bigint` here fails the whole
|
|
63
|
+
* response rather than one field.
|
|
64
|
+
*/
|
|
65
|
+
structuredContent?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
/** A tool: what to advertise in `ListTools`, and what to run on `CallTool`. */
|
|
68
|
+
interface McpTool {
|
|
69
|
+
definition: ToolDefinition;
|
|
70
|
+
handle: (input: Record<string, unknown>) => Promise<ToolResult>;
|
|
71
|
+
}
|
|
72
|
+
/** Name/version pair reported in the MCP `initialize` handshake. */
|
|
73
|
+
interface McpServerInfo {
|
|
74
|
+
name: string;
|
|
75
|
+
version: string;
|
|
76
|
+
}
|
|
77
|
+
/** One addressable document a client can list and read directly. */
|
|
78
|
+
interface McpResourceSummary {
|
|
79
|
+
description?: string;
|
|
80
|
+
mimeType?: string;
|
|
81
|
+
name: string;
|
|
82
|
+
uri: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A set of resources the server exposes alongside its tools.
|
|
86
|
+
*
|
|
87
|
+
* Tools are calls a model chooses to make; resources are documents a *client*
|
|
88
|
+
* can enumerate and attach on its own — the user picking a page to put in
|
|
89
|
+
* context, without the model having to guess a search query first. The same
|
|
90
|
+
* corpus is worth offering both ways.
|
|
91
|
+
*/
|
|
92
|
+
interface McpResourceProvider {
|
|
93
|
+
/** Every resource, for `resources/list`. */
|
|
94
|
+
list: () => Promise<ReadonlyArray<McpResourceSummary>>;
|
|
95
|
+
/** One resource's body, or `undefined` when the uri is unknown. */
|
|
96
|
+
read: (uri: string) => Promise<{
|
|
97
|
+
mimeType?: string;
|
|
98
|
+
text: string;
|
|
99
|
+
} | undefined>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Build an MCP `Server` that advertises and dispatches `tools`.
|
|
103
|
+
*
|
|
104
|
+
* On duplicate names the FIRST wins, so a caller can layer surfaces in
|
|
105
|
+
* precedence order without the later list silently shadowing the earlier one.
|
|
106
|
+
* Thrown errors become `isError` results rather than rejections, per the MCP
|
|
107
|
+
* convention that a tool failure is output the model can read and react to,
|
|
108
|
+
* not a protocol-level fault.
|
|
109
|
+
*/
|
|
110
|
+
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, 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-CQJIwdik.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-CQJIwdik.mjs";import{c as M}from"./tool-call-Bs-QeKp6.mjs";import{d as E,s as _,S as k}from"./tools-BOfWz78m.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-BOfWz78m.mjs";var N="1.0.0-alpha.240";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-CQJIwdik.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 @@
|
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as N}from"@lunora/errors";import{ADMIN_FUNCTIONS as l}from"@lunora/shard-engine";const A=e=>{let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)},R=e=>{const t=atob(e),r=new Uint8Array(t.length);for(let s=0;s<t.length;s+=1)r[s]=t.codePointAt(s)??0;return r},a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",F=e=>{let t="",r=0;const s=e.length-2;for(;r<s;r+=3){const n=e[r]<<16|e[r+1]<<8|e[r+2];t+=a.charAt(n>>18&63)+a.charAt(n>>12&63)+a.charAt(n>>6&63)+a.charAt(n&63)}const o=e.length-r;if(o===1){const n=e[r]<<16;t+=a.charAt(n>>18&63)+a.charAt(n>>12&63)}else if(o===2){const n=e[r]<<16|e[r+1]<<8;t+=a.charAt(n>>18&63)+a.charAt(n>>12&63)+a.charAt(n>>6&63)}return t},$=e=>{const t=e.replaceAll("-","+").replaceAll("_","/"),r=t+"=".repeat((4-t.length%4)%4);return R(r)},b=(e,t)=>{if(typeof t=="bigint")return t.toString();if(t instanceof ArrayBuffer)return A(new Uint8Array(t));if(ArrayBuffer.isView(t)){const r=t;return A(new Uint8Array(r.buffer,r.byteOffset,r.byteLength))}return t},U=e=>JSON.parse(JSON.stringify(e,b)),P=e=>({content:[{text:e===void 0?"null":JSON.stringify(e,b,2),type:"text"}]}),p=e=>({...P(e),structuredContent:U(e)}),J=e=>({content:[{text:e,type:"text"}],isError:!0}),u={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},I=50,w=500,E=["1m","5m","15m","1h"],O=["open","resolved","ignored"],v=["trace","debug","log","info","warn","error","fatal"],g={description:"Shard to read from on a .shardBy()-partitioned deployment. Omit for the default (unsharded) shard — these reads are PER-SHARD, not deployment-wide.",type:"string"},y={description:`Maximum rows to return (default ${I.toString()}, clamped to ${w.toString()}).`,type:"number"},L=e=>{const t=typeof e=="number"?e:Number.NaN;return Number.isFinite(t)?Math.max(1,Math.min(Math.floor(t),w)):I},S=(e,t)=>t.includes(e)?e:void 0,T=e=>typeof e=="string"&&e.length>0?e:void 0,M={properties:{level:{description:`Keep only entries at this severity. One of: ${v.join(", ")}.`,type:"string"},limit:y,shardKey:g},type:"object"},k={properties:{functionPathPrefix:{description:'Keep only Issues whose function path starts with this, e.g. "messages:".',type:"string"},limit:y,shardKey:g,status:{description:`Triage status to keep. One of: ${O.join(", ")}. Default: all.`,type:"string"}},type:"object"},H={properties:{limit:y,shardKey:g},type:"object"},x={properties:{limit:y,range:{description:`Time window to report over. One of: ${E.join(", ")}. Default: 15m.`,type:"string"},shardKey:g},type:"object"},C={properties:{shardKey:g},type:"object"},j={properties:{dropped:{description:"Entries the shard's in-memory ring EVICTED before this read — they are gone and cannot be fetched. Non-zero means `entries` + `total` describe only the newest slice of what the deployment logged.",type:"number"},entries:{description:"Recent log entries, NEWEST FIRST: { level, message, timestamp, functionPath?, fields? }.",type:"array"},total:{description:"Entries still in the ring matching `level`, before `limit` narrowed them. NOT the number of lines logged — see `dropped`.",type:"number"}},required:["dropped","entries","total"],type:"object"},q={properties:{issues:{description:"Grouped error Issues, newest first: { hash, title, count, status, functionPath, lastSeen, … }.",type:"array"}},required:["issues"],type:"object"},B={properties:{advisories:{description:"Schema/query advisories: { id, level, title, detail, … }.",type:"array"},total:{description:"Advisories available before `limit` narrowed them.",type:"number"}},required:["advisories","total"],type:"object"},D={properties:{buckets:{description:"Combined throughput/latency series across the range.",type:"array"},capped:{description:"True when the deployment's tracked-statement cap was reached, so coverage is partial.",type:"boolean"},entries:{description:"Per-statement activity in the range, hottest first.",type:"array"},total:{description:"Statements available before `limit` narrowed them.",type:"number"},trackedStatements:{description:"Distinct statements the deployment is tracking.",type:"number"}},required:["entries","buckets"],type:"object"},G={properties:{migrations:{description:"Every declared migration with its applied/pending state.",type:"array"}},required:["migrations"],type:"object"},K=[{annotations:{...u,title:"Read recent logs"},description:"Read the deployment's recent log entries (newest first) after running a function, to see what it printed and where it failed. In-memory and per-shard: resets when the shard hibernates.",inputSchema:M,name:"lunora_get_logs",outputSchema:j},{annotations:{...u,title:"List grouped error Issues"},description:"List errors grouped into Issues by fingerprint, with occurrence counts and triage status — the first call when asking what is currently broken, rather than reading raw logs.",inputSchema:k,name:"lunora_get_issues",outputSchema:q},{annotations:{...u,title:"List schema and query advisories"},description:"List the deployment's schema/query advisories (missing indexes, unsafe policies, and similar lints) before or after changing the schema.",inputSchema:H,name:"lunora_get_advisories",outputSchema:B},{annotations:{...u,title:"Read query insights"},description:"Read per-statement execution counts and latency over a recent time window, to find which query is slow or hot before optimizing one.",inputSchema:x,name:"lunora_get_query_insights",outputSchema:D},{annotations:{...u,title:"Read migration status"},description:"Read which migrations have been applied and which are pending, to check whether a schema change has actually landed on the deployment.",inputSchema:C,name:"lunora_get_migration_status",outputSchema:G}],z=new Set(K.map(e=>e.name)),h=async(e,t,r,s)=>{const o={__lunoraRef:t};return e.query(o,r,{...s===void 0?{}:{shardKey:s}})},m=(e,t)=>{const r=e?.[t];return Array.isArray(r)?r:[]},W=async(e,t,r)=>{const s=T(r.shardKey),o=L(r.limit);switch(t){case"lunora_get_advisories":{const n=await h(e,l.getAdvisories,{},s),i=m(n,"advisories");return p({advisories:i.slice(0,o),total:i.length})}case"lunora_get_issues":{const n=S(r.status,O),i=T(r.functionPathPrefix),c=await h(e,l.getIssues,{limit:o,...n===void 0?{}:{status:n},...i===void 0?{}:{functionPathPrefix:i}},s);return p({issues:m(c,"issues")})}case"lunora_get_logs":{const n=S(r.level,v),i=await h(e,l.getLogs,{},s),c=m(i,"entries").filter(f=>n===void 0||f.level===n),{dropped:d}=i??{};return p({dropped:typeof d=="number"?d:0,entries:c.slice(0,o),total:c.length})}case"lunora_get_migration_status":{const n=await h(e,l.migrationStatus,{},s);return p({migrations:m(n,"migrations")})}case"lunora_get_query_insights":{const n=S(r.range,E),i=await h(e,l.getQueryInsights,{...n===void 0?{}:{range:n}},s),c=m(i,"entries"),{buckets:d,capped:f,trackedStatements:_}=i??{};return p({buckets:Array.isArray(d)?d:[],capped:f===!0,entries:c.slice(0,o),total:c.length,trackedStatements:typeof _=="number"?_:c.length})}default:throw new N("INTERNAL",`unknown observability tool: ${t}`)}};export{I as D,w as M,K as O,P as a,z as b,W as c,J as e,$ as f,p as o,F as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const d=(e,t)=>{if(e.size<t)return;const o=e.keys().next().value;o!==void 0&&e.delete(o)},r=(e,t,o,s)=>{const n=e.get(t);if(n!==void 0)return n;s!==void 0&&d(e,s);const i=o().catch(c=>{throw e.get(t)===i&&e.delete(t),c});return e.set(t,i),i};export{d as e,r as m};
|
|
@@ -0,0 +1,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 };
|