@lunora/mcp 1.0.0-alpha.100 → 1.0.0-alpha.102
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 +8 -2
- package/dist/bin.mjs +4 -3
- package/dist/index.d.mts +16 -12
- package/dist/index.d.ts +16 -12
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-DGpNvk5K.mjs +1 -0
- package/dist/packem_shared/{LOCAL_SERVER_NAME-CisKTqzv.mjs → LOCAL_SERVER_NAME-BwlBnhX-.mjs} +1 -1
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-aD-b7Hw2.mjs +1 -0
- package/dist/packem_shared/connectStdio-CDsCt1IT.mjs +1 -0
- package/dist/packem_shared/{createAuthedMcpFetchHandler-BUoCjrxu.mjs → createAuthedMcpFetchHandler-LLzMY9SW.mjs} +1 -1
- package/dist/packem_shared/{createMcpFetchHandler-TKDnZTGs.mjs → createMcpFetchHandler-B9koGwkW.mjs} +1 -1
- package/package.json +4 -4
- package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-DRSY74ed.mjs +0 -1
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-DGexf3Tn.mjs +0 -1
- package/dist/packem_shared/connectStdio-BCeI0Wqu.mjs +0 -1
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ pnpm add @lunora/mcp
|
|
|
113
113
|
|
|
114
114
|
## Usage
|
|
115
115
|
|
|
116
|
-
MCP clients spawn the `lunora-mcp` binary over stdio. Configuration comes from `LUNORA_URL`
|
|
116
|
+
MCP clients spawn the `lunora-mcp` binary over stdio. Configuration comes from `LUNORA_URL` and `LUNORA_ADMIN_TOKEN` — both required (see [Tokens](#tokens)):
|
|
117
117
|
|
|
118
118
|
```jsonc
|
|
119
119
|
{
|
|
@@ -166,7 +166,13 @@ Enable it with two env vars (or the matching `createLunoraMcpServer` options):
|
|
|
166
166
|
|
|
167
167
|
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.
|
|
168
168
|
|
|
169
|
-
Runs are **owner-scoped** to the identity the configured token resolves to
|
|
169
|
+
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.
|
|
170
|
+
|
|
171
|
+
### Tokens
|
|
172
|
+
|
|
173
|
+
`LUNORA_ADMIN_TOKEN` must be the deployment's **admin bearer**. It cannot be scoped down: `lunora_list_functions`, `lunora_list_tables`, and the allowlist precheck that runs before _every_ `lunora_run_*` call all hit admin-gated `/_lunora/admin/*` routes, so a least-privilege token returns `ADMIN_FORBIDDEN` on the first tool call. Constructing a server without one fails fast rather than advertising tools that cannot work.
|
|
174
|
+
|
|
175
|
+
The read-only guarantee therefore does **not** come from the token's scope — it comes from `LUNORA_MCP_ALLOW_WRITES` defaulting off, which omits the write tools from `tools/list` _and_ refuses them at dispatch. Treat the MCP server itself as the trust boundary: give it the admin token, and gate who can reach it (the OAuth-protected `createAuthedMcpFetchHandler` is the supported way to expose it beyond a local stdio process).
|
|
170
176
|
|
|
171
177
|
## Resources and annotations
|
|
172
178
|
|
package/dist/bin.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{parseAgentsEnv as
|
|
3
|
-
`),new
|
|
4
|
-
`),new
|
|
2
|
+
import{parseAgentsEnv as E}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-DGpNvk5K.mjs";import{connectStdio as L}from"./packem_shared/connectStdio-CDsCt1IT.mjs";const d=new Set(["1","on","true","yes"]),A=r=>r!==void 0&&d.has(r.trim().toLowerCase());class o extends Error{code;constructor(t,s){super(t),this.name="BinError",this.code=s}}const l=async(r,t={})=>{const s=t.connect??L,n=t.writeError??(e=>{process.stderr.write(e)}),i=r.LUNORA_URL;if(i===void 0||i.length===0)throw n(`lunora-mcp: LUNORA_URL environment variable is required
|
|
3
|
+
`),new o("LUNORA_URL environment variable is required",1);const a=r.LUNORA_ADMIN_TOKEN;if(a===void 0||a.length===0)throw n(`lunora-mcp: LUNORA_ADMIN_TOKEN environment variable is required (every tool reads admin-gated routes)
|
|
4
|
+
`),new o("LUNORA_ADMIN_TOKEN environment variable is required",1);const c=Number(r.LUNORA_MCP_AGENT_TIMEOUT_MS),N=Number.isFinite(c)&&c>0?c:void 0;try{await s({agents:E(r.LUNORA_MCP_AGENTS),allowAgents:A(r.LUNORA_MCP_ALLOW_AGENTS),allowWrites:A(r.LUNORA_MCP_ALLOW_WRITES),token:a,url:i,...N===void 0?{}:{agentMaxWaitMs:N}})}catch(e){const _=e instanceof Error?e.message:String(e);throw n(`lunora-mcp: failed to start — ${_}
|
|
5
|
+
`),new o(`failed to start — ${_}`,1)}};try{await l(process.env)}catch(r){process.exit(r instanceof o?r.code:1)}
|
package/dist/index.d.mts
CHANGED
|
@@ -131,19 +131,23 @@ interface LunoraMcpServerOptions {
|
|
|
131
131
|
/** `fetch` implementation; defaults to the ambient global. */
|
|
132
132
|
fetch?: typeof fetch;
|
|
133
133
|
/**
|
|
134
|
-
* Bearer token sent on every RPC.
|
|
135
|
-
* bearer**: the introspection/allowlist path
|
|
136
|
-
* (`lunora_list_functions`, `lunora_list_tables`, and
|
|
137
|
-
* precheck that runs before every `run` tool) hits
|
|
138
|
-
* `/_lunora/admin/*` routes, so no scoped/app token works
|
|
139
|
-
* 403 (`ADMIN_FORBIDDEN`) on the first tool call. The
|
|
140
|
-
* therefore NOT enforced by the token's scope; it is
|
|
141
|
-
* `allowWrites: false` (the default), which omits
|
|
142
|
-
* advertised list and refuses them at dispatch.
|
|
134
|
+
* Bearer token sent on every RPC. **Required** alongside `url`, and it must
|
|
135
|
+
* be the deployment's **admin bearer**: the introspection/allowlist path
|
|
136
|
+
* every tool depends on (`lunora_list_functions`, `lunora_list_tables`, and
|
|
137
|
+
* the `assertRunnable` precheck that runs before every `run` tool) hits
|
|
138
|
+
* admin-gated `/_lunora/admin/*` routes, so no scoped/app token works
|
|
139
|
+
* today — it would 403 (`ADMIN_FORBIDDEN`) on the first tool call. The
|
|
140
|
+
* read-only guarantee is therefore NOT enforced by the token's scope; it is
|
|
141
|
+
* enforced in-process via `allowWrites: false` (the default), which omits
|
|
142
|
+
* the write tools from the advertised list and refuses them at dispatch.
|
|
143
143
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
144
|
+
* Because EVERY tool needs it, omitting it is a misconfiguration rather than
|
|
145
|
+
* a reduced-capability mode, and `createLunoraMcpServer` says so at
|
|
146
|
+
* construction instead of advertising a surface that 403s on first use. The
|
|
147
|
+
* one exception is the `client` injection seam (tests / a pre-authenticated
|
|
148
|
+
* client), where this server cannot know what the client can reach and so
|
|
149
|
+
* reads "unknown" fail-closed: the privileged observability tools stay
|
|
150
|
+
* unadvertised and are refused at dispatch.
|
|
147
151
|
*/
|
|
148
152
|
token?: string;
|
|
149
153
|
/** Base URL of the deployed Lunora Worker. Required unless `client` is given. */
|
package/dist/index.d.ts
CHANGED
|
@@ -131,19 +131,23 @@ interface LunoraMcpServerOptions {
|
|
|
131
131
|
/** `fetch` implementation; defaults to the ambient global. */
|
|
132
132
|
fetch?: typeof fetch;
|
|
133
133
|
/**
|
|
134
|
-
* Bearer token sent on every RPC.
|
|
135
|
-
* bearer**: the introspection/allowlist path
|
|
136
|
-
* (`lunora_list_functions`, `lunora_list_tables`, and
|
|
137
|
-
* precheck that runs before every `run` tool) hits
|
|
138
|
-
* `/_lunora/admin/*` routes, so no scoped/app token works
|
|
139
|
-
* 403 (`ADMIN_FORBIDDEN`) on the first tool call. The
|
|
140
|
-
* therefore NOT enforced by the token's scope; it is
|
|
141
|
-
* `allowWrites: false` (the default), which omits
|
|
142
|
-
* advertised list and refuses them at dispatch.
|
|
134
|
+
* Bearer token sent on every RPC. **Required** alongside `url`, and it must
|
|
135
|
+
* be the deployment's **admin bearer**: the introspection/allowlist path
|
|
136
|
+
* every tool depends on (`lunora_list_functions`, `lunora_list_tables`, and
|
|
137
|
+
* the `assertRunnable` precheck that runs before every `run` tool) hits
|
|
138
|
+
* admin-gated `/_lunora/admin/*` routes, so no scoped/app token works
|
|
139
|
+
* today — it would 403 (`ADMIN_FORBIDDEN`) on the first tool call. The
|
|
140
|
+
* read-only guarantee is therefore NOT enforced by the token's scope; it is
|
|
141
|
+
* enforced in-process via `allowWrites: false` (the default), which omits
|
|
142
|
+
* the write tools from the advertised list and refuses them at dispatch.
|
|
143
143
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
144
|
+
* Because EVERY tool needs it, omitting it is a misconfiguration rather than
|
|
145
|
+
* a reduced-capability mode, and `createLunoraMcpServer` says so at
|
|
146
|
+
* construction instead of advertising a surface that 403s on first use. The
|
|
147
|
+
* one exception is the `client` injection seam (tests / a pre-authenticated
|
|
148
|
+
* client), where this server cannot know what the client can reach and so
|
|
149
|
+
* reads "unknown" fail-closed: the privileged observability tools stay
|
|
150
|
+
* unadvertised and are refused at dispatch.
|
|
147
151
|
*/
|
|
148
152
|
token?: string;
|
|
149
153
|
/** Base URL of the deployed Lunora Worker. Required unless `client` is given. */
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as c,callAgentTool as T,parseAgentsEnv as a}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-
|
|
1
|
+
import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as c,callAgentTool as T,parseAgentsEnv as a}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-DGpNvk5K.mjs";import{createAuthedMcpFetchHandler as E,mcpTokenScopes as S}from"./packem_shared/createAuthedMcpFetchHandler-LLzMY9SW.mjs";import{createToolServer as O}from"./packem_shared/createToolServer-BtGuPyMU.mjs";import{createMcpFetchHandler as n}from"./packem_shared/createMcpFetchHandler-B9koGwkW.mjs";import{LOCAL_SERVER_NAME as A,NO_DEPLOYMENT_MESSAGE as I,connectLocalStdio as f,createLocalMcpServer as L,localTools as m}from"./packem_shared/LOCAL_SERVER_NAME-BwlBnhX-.mjs";import{createPaidMcpServer as x}from"./packem_shared/createPaidMcpServer-DyNwdCq2.mjs";import{connectStdio as i,createLunoraMcpServer as D}from"./packem_shared/connectStdio-CDsCt1IT.mjs";import{READ_ONLY_TOOL_DEFINITIONS as v,WRITE_TOOL_DEFINITIONS as R,callTool as F,toolDefinitions as g}from"./packem_shared/READ_ONLY_TOOL_DEFINITIONS-aD-b7Hw2.mjs";import{O as G}from"./packem_shared/observability-tools-Ce0YK58T.mjs";import{serveStateless as P}from"./packem_shared/serveStateless-Db7J3pAO.mjs";export{r as AGENT_RUN_INPUT_SCHEMA,t as AGENT_STATUS_TOOL_NAME,A as LOCAL_SERVER_NAME,I as NO_DEPLOYMENT_MESSAGE,G as OBSERVABILITY_TOOL_DEFINITIONS,v as READ_ONLY_TOOL_DEFINITIONS,R as WRITE_TOOL_DEFINITIONS,c as agentToolDefinitions,T as callAgentTool,F as callTool,f as connectLocalStdio,i as connectStdio,E as createAuthedMcpFetchHandler,L as createLocalMcpServer,D as createLunoraMcpServer,n as createMcpFetchHandler,x as createPaidMcpServer,O as createToolServer,m as localTools,S as mcpTokenScopes,a as parseAgentsEnv,P as serveStateless,g as toolDefinitions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const b="agents:agentRun",T="agents:agentThread",x="agents:agentMessages",d="lunora_agent_status",_=new Set(["awaiting_input","cancelled","error","idle"]),P=6e4,U=600,L={properties:{prompt:{description:"The task or message for the agent.",type:"string"},threadKey:{description:"Reuse to continue a conversation; omit to start a new thread.",type:"string"},title:{description:"Optional thread title (first run only).",type:"string"}},required:["prompt"],type:"object"},k={properties:{threadKey:{description:"The thread key returned by an agent tool call.",type:"string"}},required:["threadKey"],type:"object"},g=t=>t.toolName??`agent_${t.name}`,C=t=>{if(t===void 0)return[];const n=[];for(const e of t.split(";")){const r=e.trim();if(r.length===0)continue;const o=r.indexOf(":");if(o<=0)continue;const s=r.slice(0,o).trim(),a=r.slice(o+1).trim();s.length===0||a.length===0||n.push({description:a,name:s})}return n},G=(t,n)=>n!==!0||t.length===0?[]:[...t.map(r=>({description:`${r.description} Starts a durable agent run and returns its final answer.`,inputSchema:L,name:g(r)})),{description:"Check the status of a durable agent run (and its answer if finished) by its threadKey.",inputSchema:k,name:d}],O=(t,n)=>t===d||n.some(e=>g(e)===t),I=()=>`mcp-${crypto.randomUUID()}`,p=t=>({__lunoraRef:t}),K=async t=>{await new Promise(n=>{setTimeout(n,t)})},c=t=>({content:[{text:JSON.stringify(t,void 0,2),type:"text"}]}),i=t=>({content:[{text:t,type:"text"}],isError:!0}),l=(t,n)=>{const e=t[n];return typeof e=="string"&&e.length>0?e:void 0},A=t=>{for(let n=t.length-1;n>=0;n-=1){const e=t[n],r=e?.toolCalls,o=Array.isArray(r)&&r.length>0;if(e?.role==="assistant"&&!o)return typeof e.content=="string"?e.content:""}return""},w=t=>t!==null&&typeof t=="object"&&typeof t.status=="string"?t.status:"unknown",S=async(t,n,e,r)=>{const o=await t.query(p(x),{key:n});if(e==="error"){const s=r!==null&&typeof r=="object"?r.error:void 0;return c({error:typeof s=="string"?s:"the agent run failed",status:e,threadKey:n})}return c(e==="awaiting_input"?{hint:"This run is paused on a human-in-the-loop tool approval. Approve or reject it in the app that owns the agent; MCP cannot supply the input. Poll lunora_agent_status with this threadKey afterwards.",status:e,text:A(o),threadKey:n}:{status:e,text:A(o),threadKey:n})},R=async(t,n)=>{const e=l(n,"threadKey");if(e===void 0)return i('"threadKey" is required and must be a non-empty string');const r=await t.query(p(T),{key:e}),o=w(r);return _.has(o)?S(t,e,o,r):c({status:o==="unknown"?"running":o,threadKey:e})},q=async(t,n,e,r)=>{try{if(r.allowAgents!==!0)return i(`tool "${n}" is disabled: agent tools are off. Enable them with the LUNORA_MCP_ALLOW_AGENTS env var.`);if(n===d)return await R(t,e);const o=r.exposures.find(u=>g(u)===n);if(o===void 0)return i(`agent tool "${n}" is not exposed by this MCP server`);const s=l(e,"prompt");if(s===void 0)return i('"prompt" is required and must be a non-empty string');const a=l(e,"threadKey")??I(),h=l(e,"title"),{id:E}=await t.mutation(p(b),{agent:o.name,input:s,threadKey:a,...h===void 0?{}:{title:h}}),f=r.pollIntervalMs??U,N=r.maxWaitMs??P,v=r.wait??K,M=Math.max(1,Math.ceil(N/f));for(let u=0;u<M;u+=1){const y=await t.query(p(T),{key:a}),m=w(y);if(_.has(m))return await S(t,a,m,y);await v(f)}return c({hint:"call lunora_agent_status with this threadKey to poll for the answer",runId:E,status:"running",threadKey:a})}catch(o){const s=o instanceof Error?o.message:String(o);return i(s)}};export{L as AGENT_RUN_INPUT_SCHEMA,d as AGENT_STATUS_TOOL_NAME,G as agentToolDefinitions,q as callAgentTool,A as finalAnswer,O as isAgentToolName,C as parseAgentsEnv};
|
package/dist/packem_shared/{LOCAL_SERVER_NAME-CisKTqzv.mjs → LOCAL_SERVER_NAME-BwlBnhX-.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraClient as f}from"@lunora/client";import{StdioServerTransport as p}from"@modelcontextprotocol/sdk/server/stdio.js";import{createToolServer as m}from"./createToolServer-BtGuPyMU.mjs";import{createRemoteDocsIndex as v}from"./DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{docsResources as h}from"./DOCS_URI_SCHEME-CFANc3vr.mjs";import{docsTools as y}from"./DEFAULT_SEARCH_LIMIT-V7pCZ1wt.mjs";import{toolDefinitions as E,callTool as R}from"./READ_ONLY_TOOL_DEFINITIONS-
|
|
1
|
+
import{LunoraClient as f}from"@lunora/client";import{StdioServerTransport as p}from"@modelcontextprotocol/sdk/server/stdio.js";import{createToolServer as m}from"./createToolServer-BtGuPyMU.mjs";import{createRemoteDocsIndex as v}from"./DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{docsResources as h}from"./DOCS_URI_SCHEME-CFANc3vr.mjs";import{docsTools as y}from"./DEFAULT_SEARCH_LIMIT-V7pCZ1wt.mjs";import{toolDefinitions as E,callTool as R}from"./READ_ONLY_TOOL_DEFINITIONS-aD-b7Hw2.mjs";const O=(e,n)=>{if(e.size<n)return;const t=e.keys().next().value;t!==void 0&&e.delete(t)},l=e=>e.docs===!1?void 0:v({...e.docs?.baseUrl===void 0?{}:{baseUrl:e.docs.baseUrl},...e.fetch===void 0?{}:{fetch:e.fetch}}),_="lunora",S="no Lunora dev server is running for this project — start one with `lunora dev`, then call this tool again (call lunora_dev_status to check).",T=8,d=e=>{const n=new Map;return t=>{const r=JSON.stringify([t.url,t.token??""]),c=n.get(r);if(c!==void 0)return c;const o=new f({fetch:e,url:t.url});return t.token!==void 0&&t.token.length>0&&o.setAuthToken(t.token),O(n,T),n.set(r,o),o}},i=e=>e?.token!==void 0&&e.token.length>0,C=(e,n,t)=>{const r=typeof e=="function"?e:()=>e;return E(n,i(r())).map(c=>({definition:c,handle:async o=>{const s=r();return s===void 0?{content:[{text:S,type:"text"}],isError:!0}:R(t(s),c.name,o,n,i(s))}}))},P="lunora-spec:openrpc",g="lunora-spec:openapi",a=[{description:"The deployment's generated OpenRPC 1.x document — every RPC function's path, kind, and argument schema in one read, instead of list_functions plus one get_function_schema call per function.",fetch:async e=>e.fetchOpenRpc(),name:"OpenRPC specification",uri:P},{description:"The deployment's generated OpenAPI 3.1 document.",fetch:async e=>e.fetchOpenApi(),name:"OpenAPI specification",uri:g}],k=(e,n)=>{const t=typeof e=="function"?e:()=>e,r=async c=>{const o=t();if(o!==void 0)try{return await c.fetch(n(o))}catch{return}};return{list:async()=>(await Promise.all(a.map(async o=>await r(o)===void 0?void 0:{description:o.description,mimeType:"application/json",name:o.name,uri:o.uri}))).filter(o=>o!==void 0),read:async c=>{const o=a.find(u=>u.uri===c);if(o===void 0)return;const s=await r(o);return s===void 0?void 0:{mimeType:"application/json",text:JSON.stringify(s,void 0,2)}}}},w=e=>({list:async()=>(await Promise.all(e.map(async t=>t.list()))).flat(),read:async n=>{for(const t of e){const r=await t.read(n);if(r!==void 0)return r}}}),x=(e,n)=>{const t=[],r=l(e);return r!==void 0&&t.push(...y(r)),t.push(...e.extraTools??[]),e.deployment!==void 0&&t.push(...C(e.deployment,e.allowWrites??!1,n??d(e.fetch))),t},A=(e={})=>{const n=l(e),t=[];n!==void 0&&t.push(h(n));const r=d(e.fetch);return e.deployment!==void 0&&t.push(k(e.deployment,r)),m({name:_,version:e.version??"0.0.0"},x(e,r),t.length===0?void 0:w(t))},j=async(e={})=>{const n=A(e);return await n.connect(new p),n};export{_ as LOCAL_SERVER_NAME,S as NO_DEPLOYMENT_MESSAGE,g as OPENAPI_RESOURCE_URI,P as OPENRPC_RESOURCE_URI,j as connectLocalStdio,A as createLocalMcpServer,x as localTools};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as u}from"@lunora/errors";import{O as A,e as d,a as S,c as E,o as c}from"./observability-tools-Ce0YK58T.mjs";const m={properties:{args:{description:"Arguments object passed to the function",type:"object"},functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"},shardKey:{description:"Optional shard key when the function is .shardBy()-partitioned",type:"string"}},required:["functionPath"],type:"object"},g={properties:{},type:"object"},R={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},l={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},I=[{annotations:{...l,title:"List deployment functions"},description:"List the deployment's public functions (queries, mutations, actions) with their kinds.",inputSchema:g,name:"lunora_list_functions"},{annotations:{...l,title:"List global tables"},description:"List the deployment's .global() tables with their row counts. Names and row counts only — no column shapes.",inputSchema:g,name:"lunora_list_tables"},{annotations:{...l,title:"Describe a function's arguments"},description:"Return a function's argument descriptors (name, validator kind, whether it is optional) and its kind, so a caller can construct a valid arguments object. Call lunora_list_functions first to discover available function paths.",inputSchema:R,name:"lunora_get_function_schema"},{annotations:{...l,title:"Run a query"},description:"Run a query and return its result. Read-only.",inputSchema:m,name:"lunora_run_query"}],w=[{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run a mutation (writes data)"},description:"Run a mutation and return its result. Writes data — use with care.",inputSchema:m,name:"lunora_run_mutation"},{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run an action (may call external services)"},description:"Run an action and return its result. May call external services.",inputSchema:m,name:"lunora_run_action"}],L=new Set(w.map(t=>t.name)),k=(t,n=!1)=>[...I,...n===!0?A:[],...t===!0?w:[]],b=t=>{const{functionPath:n}=t;if(typeof n!="string"||n.length===0)throw new u("INTERNAL",'"functionPath" is required and must be a non-empty string');return n},O=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),N=t=>Array.isArray(t)?"an array":`a ${typeof t}`,v=t=>{if(t==null)return{};if(typeof t=="string"){let n;try{n=JSON.parse(t)}catch{throw new u("BAD_REQUEST",'"args" must be a JSON object; received a string that is not valid JSON')}if(!O(n))throw new u("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${N(n)}`);return n}if(!O(t))throw new u("BAD_REQUEST",`"args" must be a JSON object, got ${N(t)}`);return t},f=t=>{const n=b(t),e=v(t.args),o=typeof t.shardKey=="string"&&t.shardKey.length>0?t.shardKey:void 0;return{args:e,functionPath:n,shardKey:o}},p=t=>({__lunoraRef:t}),H=3e4,h=new WeakMap,y=t=>{const n=Date.now(),e=h.get(t);if(e!==void 0&&e.expiresAt>n)return e.promise;const o=t.listFunctions().catch(i=>{throw h.get(t)?.promise===o&&h.delete(t),i});return h.set(t,{expiresAt:n+H,promise:o}),o},_=async(t,n,e)=>{const i=(await y(t)).find(r=>r.path===n);if(i===void 0)throw new u("NOT_FOUND",`function not found or not public: ${n}`);if(i.kind!==e)throw new u("BAD_REQUEST",`function ${n} is a ${i.kind}, not a ${e}`)},U=async(t,n,e,o=!1,i=!1)=>{try{if(o!==!0&&L.has(n))return d(`tool "${n}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`);if(S.has(n))return i!==!0?d(`tool "${n}" is unavailable: it reads the deployment's logs and errors, which needs an admin token. Set LUNORA_ADMIN_TOKEN (or pass --token) and reconnect.`):await E(t,n,e);switch(n){case"lunora_get_function_schema":{const r=b(e),a=(await y(t)).find(T=>T.path===r);return a===void 0?d(`function not found: ${r}`):c({args:a.args??[],kind:a.kind,path:a.path})}case"lunora_list_functions":return c(await y(t));case"lunora_list_tables":return c(await t.listGlobalTables());case"lunora_run_action":{const{args:r,functionPath:s,shardKey:a}=f(e);return await _(t,s,"action"),c(await t.action(p(s),r,{shardKey:a}))}case"lunora_run_mutation":{const{args:r,functionPath:s,shardKey:a}=f(e);return await _(t,s,"mutation"),c(await t.mutation(p(s),r,{shardKey:a}))}case"lunora_run_query":{const{args:r,functionPath:s,shardKey:a}=f(e);return await _(t,s,"query"),c(await t.query(p(s),r,{shardKey:a}))}default:return d(`unknown tool: ${n}`)}}catch(r){const s=r instanceof Error?r.message:String(r);return d(s)}};export{A as OBSERVABILITY_TOOL_DEFINITIONS,I as READ_ONLY_TOOL_DEFINITIONS,w as WRITE_TOOL_DEFINITIONS,U as callTool,k as toolDefinitions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFileSync as v}from"node:fs";import{dirname as c,join as g}from"node:path";import{fileURLToPath as d}from"node:url";import{LunoraClient as h}from"@lunora/client";import{LunoraError as u}from"@lunora/errors";import{Server as w}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as T}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as p,CallToolRequestSchema as A}from"@modelcontextprotocol/sdk/types.js";import{agentToolDefinitions as M,isAgentToolName as S,callAgentTool as k}from"./AGENT_RUN_INPUT_SCHEMA-DGpNvk5K.mjs";import{toolDefinitions as y,callTool as N}from"./READ_ONLY_TOOL_DEFINITIONS-aD-b7Hw2.mjs";const L=()=>{try{let e=c(d(import.meta.url));for(let r=0;r<8;r+=1){try{const o=v(g(e,"package.json"),"utf8"),t=JSON.parse(o);if(t.name==="@lunora/mcp"&&typeof t.version=="string"&&t.version.length>0)return t.version}catch{}const n=c(e);if(n===e)break;e=n}}catch{}return"0.0.0"},R={name:"lunora",version:L()},E=e=>{if(e.client!==void 0)return e.client;if(e.url===void 0)throw new u("INTERNAL","createLunoraMcpServer requires either a `client` or a `url`");if(e.token===void 0||e.token.length===0)throw new u("UNAUTHENTICATED","createLunoraMcpServer requires a `token` (LUNORA_ADMIN_TOKEN) alongside `url`: every tool reaches admin-gated /_lunora/admin/* routes, so an unauthenticated server can only 403. Writes stay off unless `allowWrites` is set.");const r=new h({fetch:e.fetch,url:e.url});return r.setAuthToken(e.token),r},I=e=>{const r=E(e),n=e.allowWrites??!1,o=e.allowAgents??!1,t=e.agents??[],s=typeof e.token=="string"&&e.token.length>0,a=new w(R,{capabilities:{tools:{}}});return a.setRequestHandler(p,()=>({tools:[...y(n,s),...M(t,o)]})),a.setRequestHandler(A,async m=>{const{arguments:f,name:l}=m.params,i=f??{};return S(l,t)?await k(r,l,i,{allowAgents:o,exposures:t,...e.agentMaxWaitMs===void 0?{}:{maxWaitMs:e.agentMaxWaitMs},...e.agentPollIntervalMs===void 0?{}:{pollIntervalMs:e.agentPollIntervalMs}}):await N(r,l,i,n,s)}),a},j=async e=>{const r=I(e);return await r.connect(new T),r};export{j as connectStdio,I as createLunoraMcpServer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{serveStateless as n}from"./serveStateless-Db7J3pAO.mjs";import{createLunoraMcpServer as o}from"./connectStdio-
|
|
1
|
+
import{serveStateless as n}from"./serveStateless-Db7J3pAO.mjs";import{createLunoraMcpServer as o}from"./connectStdio-CDsCt1IT.mjs";const a=e=>typeof e.scope!="string"?new Set:new Set(e.scope.split(" ").filter(r=>r!=="")),f=e=>e.protect(async(r,t)=>{const c=typeof e.server=="function"?await e.server(t):e.server;return await n(o(c),r)});export{f as createAuthedMcpFetchHandler,a as mcpTokenScopes};
|
package/dist/packem_shared/{createMcpFetchHandler-TKDnZTGs.mjs → createMcpFetchHandler-B9koGwkW.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{serveStateless as t}from"./serveStateless-Db7J3pAO.mjs";import{createLunoraMcpServer as o}from"./connectStdio-
|
|
1
|
+
import{serveStateless as t}from"./serveStateless-Db7J3pAO.mjs";import{createLunoraMcpServer as o}from"./connectStdio-CDsCt1IT.mjs";const p=e=>r=>t(o(e),r);export{p as createMcpFetchHandler,t as serveStateless};
|
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.102",
|
|
4
4
|
"description": "Model Context Protocol server exposing a Lunora deployment to AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@lunora/client": "1.0.0-alpha.
|
|
57
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
58
|
-
"@lunora/shard-engine": "1.0.0-alpha.
|
|
56
|
+
"@lunora/client": "1.0.0-alpha.68",
|
|
57
|
+
"@lunora/errors": "1.0.0-alpha.27",
|
|
58
|
+
"@lunora/shard-engine": "1.0.0-alpha.49",
|
|
59
59
|
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const v="agents:agentRun",A="agents:agentThread",b="agents:agentMessages",g="lunora_agent_status",T=new Set(["cancelled","error","idle"]),x=6e4,U=600,L={properties:{prompt:{description:"The task or message for the agent.",type:"string"},threadKey:{description:"Reuse to continue a conversation; omit to start a new thread.",type:"string"},title:{description:"Optional thread title (first run only).",type:"string"}},required:["prompt"],type:"object"},P={properties:{threadKey:{description:"The thread key returned by an agent tool call.",type:"string"}},required:["threadKey"],type:"object"},p=t=>t.toolName??`agent_${t.name}`,G=t=>{if(t===void 0)return[];const n=[];for(const e of t.split(";")){const r=e.trim();if(r.length===0)continue;const o=r.indexOf(":");if(o<=0)continue;const s=r.slice(0,o).trim(),a=r.slice(o+1).trim();s.length===0||a.length===0||n.push({description:a,name:s})}return n},O=(t,n)=>n!==!0||t.length===0?[]:[...t.map(r=>({description:`${r.description} Starts a durable agent run and returns its final answer.`,inputSchema:L,name:p(r)})),{description:"Check the status of a durable agent run (and its answer if finished) by its threadKey.",inputSchema:P,name:g}],q=(t,n)=>t===g||n.some(e=>p(e)===t),k=()=>`mcp-${crypto.randomUUID()}`,l=t=>({__lunoraRef:t}),I=async t=>{await new Promise(n=>{setTimeout(n,t)})},d=t=>({content:[{text:JSON.stringify(t,void 0,2),type:"text"}]}),i=t=>({content:[{text:t,type:"text"}],isError:!0}),u=(t,n)=>{const e=t[n];return typeof e=="string"&&e.length>0?e:void 0},K=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""},_=t=>t!==null&&typeof t=="object"&&typeof t.status=="string"?t.status:"unknown",S=async(t,n,e,r)=>{const o=await t.query(l(b),{key:n});if(e==="error"){const s=r!==null&&typeof r=="object"?r.error:void 0;return d({error:typeof s=="string"?s:"the agent run failed",status:e,threadKey:n})}return d({status:e,text:K(o),threadKey:n})},R=async(t,n)=>{const e=u(n,"threadKey");if(e===void 0)return i('"threadKey" is required and must be a non-empty string');const r=await t.query(l(A),{key:e}),o=_(r);return T.has(o)?S(t,e,o,r):d({status:o==="unknown"?"running":o,threadKey:e})},C=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===g)return await R(t,e);const o=r.exposures.find(c=>p(c)===n);if(o===void 0)return i(`agent tool "${n}" is not exposed by this MCP server`);const s=u(e,"prompt");if(s===void 0)return i('"prompt" is required and must be a non-empty string');const a=u(e,"threadKey")??k(),f=u(e,"title"),{id:w}=await t.mutation(l(v),{agent:o.name,input:s,threadKey:a,...f===void 0?{}:{title:f}}),y=r.pollIntervalMs??U,E=r.maxWaitMs??x,N=r.wait??I,M=Math.max(1,Math.ceil(E/y));for(let c=0;c<M;c+=1){const h=await t.query(l(A),{key:a}),m=_(h);if(T.has(m))return await S(t,a,m,h);await N(y)}return d({hint:"call lunora_agent_status with this threadKey to poll for the answer",runId:w,status:"running",threadKey:a})}catch(o){const s=o instanceof Error?o.message:String(o);return i(s)}};export{L as AGENT_RUN_INPUT_SCHEMA,g as AGENT_STATUS_TOOL_NAME,O as agentToolDefinitions,C as callAgentTool,K as finalAnswer,q as isAgentToolName,G as parseAgentsEnv};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as u}from"@lunora/errors";import{O as A,e as d,a as w,c as E,o as c}from"./observability-tools-Ce0YK58T.mjs";const m={properties:{args:{description:"Arguments object passed to the function",type:"object"},functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"},shardKey:{description:"Optional shard key when the function is .shardBy()-partitioned",type:"string"}},required:["functionPath"],type:"object"},g={properties:{},type:"object"},R={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},l={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},I=[{annotations:{...l,title:"List deployment functions"},description:"List the deployment's public functions (queries, mutations, actions) with their kinds.",inputSchema:g,name:"lunora_list_functions"},{annotations:{...l,title:"List global tables"},description:"List the deployment's .global() tables and their column shapes.",inputSchema:g,name:"lunora_list_tables"},{annotations:{...l,title:"Describe a function's arguments"},description:"Return a function's argument JSON Schema and kind, so a caller can construct a valid arguments object. Call lunora_list_functions first to discover available function paths.",inputSchema:R,name:"lunora_get_function_schema"},{annotations:{...l,title:"Run a query"},description:"Run a query and return its result. Read-only.",inputSchema:m,name:"lunora_run_query"}],b=[{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run a mutation (writes data)"},description:"Run a mutation and return its result. Writes data — use with care.",inputSchema:m,name:"lunora_run_mutation"},{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run an action (may call external services)"},description:"Run an action and return its result. May call external services.",inputSchema:m,name:"lunora_run_action"}],L=new Set(b.map(t=>t.name)),U=(t,n=!1)=>[...I,...n===!0?A:[],...t===!0?b:[]],S=t=>{const{functionPath:n}=t;if(typeof n!="string"||n.length===0)throw new u("INTERNAL",'"functionPath" is required and must be a non-empty string');return n},O=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),N=t=>Array.isArray(t)?"an array":`a ${typeof t}`,v=t=>{if(t==null)return{};if(typeof t=="string"){let n;try{n=JSON.parse(t)}catch{throw new u("BAD_REQUEST",'"args" must be a JSON object; received a string that is not valid JSON')}if(!O(n))throw new u("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${N(n)}`);return n}if(!O(t))throw new u("BAD_REQUEST",`"args" must be a JSON object, got ${N(t)}`);return t},f=t=>{const n=S(t),e=v(t.args),o=typeof t.shardKey=="string"&&t.shardKey.length>0?t.shardKey:void 0;return{args:e,functionPath:n,shardKey:o}},p=t=>({__lunoraRef:t}),H=3e4,h=new WeakMap,y=t=>{const n=Date.now(),e=h.get(t);if(e!==void 0&&e.expiresAt>n)return e.promise;const o=t.listFunctions().catch(i=>{throw h.get(t)?.promise===o&&h.delete(t),i});return h.set(t,{expiresAt:n+H,promise:o}),o},_=async(t,n,e)=>{const i=(await y(t)).find(r=>r.path===n);if(i===void 0)throw new u("NOT_FOUND",`function not found or not public: ${n}`);if(i.kind!==e)throw new u("BAD_REQUEST",`function ${n} is a ${i.kind}, not a ${e}`)},k=async(t,n,e,o=!1,i=!1)=>{try{if(o!==!0&&L.has(n))return d(`tool "${n}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`);if(w.has(n))return i!==!0?d(`tool "${n}" is unavailable: it reads the deployment's logs and errors, which needs an admin token. Set LUNORA_ADMIN_TOKEN (or pass --token) and reconnect.`):await E(t,n,e);switch(n){case"lunora_get_function_schema":{const r=S(e),s=(await y(t)).find(T=>T.path===r);return s===void 0?d(`function not found: ${r}`):c({args:s.args??[],kind:s.kind,path:s.path})}case"lunora_list_functions":return c(await y(t));case"lunora_list_tables":return c(await t.listGlobalTables());case"lunora_run_action":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"action"),c(await t.action(p(a),r,{shardKey:s}))}case"lunora_run_mutation":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"mutation"),c(await t.mutation(p(a),r,{shardKey:s}))}case"lunora_run_query":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"query"),c(await t.query(p(a),r,{shardKey:s}))}default:return d(`unknown tool: ${n}`)}}catch(r){const a=r instanceof Error?r.message:String(r);return d(a)}};export{A as OBSERVABILITY_TOOL_DEFINITIONS,I as READ_ONLY_TOOL_DEFINITIONS,b as WRITE_TOOL_DEFINITIONS,k as callTool,U as toolDefinitions};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{readFileSync as f}from"node:fs";import{dirname as c,join as v}from"node:path";import{fileURLToPath as g}from"node:url";import{LunoraClient as d}from"@lunora/client";import{LunoraError as h}from"@lunora/errors";import{Server as w}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as p}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as S,CallToolRequestSchema as T}from"@modelcontextprotocol/sdk/types.js";import{agentToolDefinitions as M,isAgentToolName as k,callAgentTool as y}from"./AGENT_RUN_INPUT_SCHEMA-DRSY74ed.mjs";import{toolDefinitions as A,callTool as R}from"./READ_ONLY_TOOL_DEFINITIONS-DGexf3Tn.mjs";const L=()=>{try{let e=c(g(import.meta.url));for(let r=0;r<8;r+=1){try{const o=f(v(e,"package.json"),"utf8"),t=JSON.parse(o);if(t.name==="@lunora/mcp"&&typeof t.version=="string"&&t.version.length>0)return t.version}catch{}const n=c(e);if(n===e)break;e=n}}catch{}return"0.0.0"},q={name:"lunora",version:L()},x=e=>{if(e.client!==void 0)return e.client;if(e.url===void 0)throw new h("INTERNAL","createLunoraMcpServer requires either a `client` or a `url`");const r=new d({fetch:e.fetch,url:e.url});return e.token!==void 0&&r.setAuthToken(e.token),r},I=e=>{const r=x(e),n=e.allowWrites??!1,o=e.allowAgents??!1,t=e.agents??[],s=typeof e.token=="string"&&e.token.length>0,a=new w(q,{capabilities:{tools:{}}});return a.setRequestHandler(S,()=>({tools:[...A(n,s),...M(t,o)]})),a.setRequestHandler(T,async m=>{const{arguments:u,name:l}=m.params,i=u??{};return k(l,t)?await y(r,l,i,{allowAgents:o,exposures:t,...e.agentMaxWaitMs===void 0?{}:{maxWaitMs:e.agentMaxWaitMs},...e.agentPollIntervalMs===void 0?{}:{pollIntervalMs:e.agentPollIntervalMs}}):await R(r,l,i,n,s)}),a},V=async e=>{const r=I(e);return await r.connect(new p),r};export{V as connectStdio,I as createLunoraMcpServer};
|