@lunora/mcp 1.0.0-alpha.30 → 1.0.0-alpha.32

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 CHANGED
@@ -1,48 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { parseAgentsEnv } from './packem_shared/AGENT_RUN_INPUT_SCHEMA-DWobae3P.mjs';
3
- import { connectStdio } from './packem_shared/connectStdio-bkJcaXYE.mjs';
4
-
5
- const ENABLED_ENV_VALUES = /* @__PURE__ */ new Set(["1", "on", "true", "yes"]);
6
- const isEnvEnabled = (value) => value !== void 0 && ENABLED_ENV_VALUES.has(value.trim().toLowerCase());
7
- class BinError extends Error {
8
- code;
9
- constructor(message, code) {
10
- super(message);
11
- this.name = "BinError";
12
- this.code = code;
13
- }
14
- }
15
- const runBin = async (environment, dependencies = {}) => {
16
- const connect = dependencies.connect ?? connectStdio;
17
- const writeError = dependencies.writeError ?? ((message) => {
18
- process.stderr.write(message);
19
- });
20
- const url = environment.LUNORA_URL;
21
- if (url === void 0 || url.length === 0) {
22
- writeError("lunora-mcp: LUNORA_URL environment variable is required\n");
23
- throw new BinError("LUNORA_URL environment variable is required", 1);
24
- }
25
- const rawTimeout = Number(environment.LUNORA_MCP_AGENT_TIMEOUT_MS);
26
- const agentMaxWaitMs = Number.isFinite(rawTimeout) && rawTimeout > 0 ? rawTimeout : void 0;
27
- try {
28
- await connect({
29
- agents: parseAgentsEnv(environment.LUNORA_MCP_AGENTS),
30
- allowAgents: isEnvEnabled(environment.LUNORA_MCP_ALLOW_AGENTS),
31
- allowWrites: isEnvEnabled(environment.LUNORA_MCP_ALLOW_WRITES),
32
- token: environment.LUNORA_ADMIN_TOKEN,
33
- url,
34
- ...agentMaxWaitMs === void 0 ? {} : { agentMaxWaitMs }
35
- });
36
- } catch (error) {
37
- const message = error instanceof Error ? error.message : String(error);
38
- writeError(`lunora-mcp: failed to start — ${message}
39
- `);
40
- throw new BinError(`failed to start — ${message}`, 1);
41
- }
42
- };
43
-
44
- try {
45
- await runBin(process.env);
46
- } catch (error) {
47
- process.exit(error instanceof BinError ? error.code : 1);
48
- }
2
+ import{parseAgentsEnv as A}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-ihZg6vIZ.mjs";import{connectStdio as N}from"./packem_shared/connectStdio-Dtos_i1h.mjs";const d=new Set(["1","on","true","yes"]),L=r=>r!==void 0&&d.has(r.trim().toLowerCase());class a extends Error{code;constructor(e,o){super(e),this.name="BinError",this.code=o}}const l=async(r,e={})=>{const o=e.connect??N,n=e.writeError??(t=>{process.stderr.write(t)}),s=r.LUNORA_URL;if(s===void 0||s.length===0)throw n(`lunora-mcp: LUNORA_URL environment variable is required
3
+ `),new a("LUNORA_URL environment variable is required",1);const i=Number(r.LUNORA_MCP_AGENT_TIMEOUT_MS),c=Number.isFinite(i)&&i>0?i:void 0;try{await o({agents:A(r.LUNORA_MCP_AGENTS),allowAgents:L(r.LUNORA_MCP_ALLOW_AGENTS),allowWrites:L(r.LUNORA_MCP_ALLOW_WRITES),token:r.LUNORA_ADMIN_TOKEN,url:s,...c===void 0?{}:{agentMaxWaitMs:c}})}catch(t){const _=t instanceof Error?t.message:String(t);throw n(`lunora-mcp: failed to start — ${_}
4
+ `),new a(`failed to start — ${_}`,1)}};try{await l(process.env)}catch(r){process.exit(r instanceof a?r.code:1)}
package/dist/index.mjs CHANGED
@@ -1,5 +1 @@
1
- export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, agentToolDefinitions, callAgentTool, parseAgentsEnv } from './packem_shared/AGENT_RUN_INPUT_SCHEMA-DWobae3P.mjs';
2
- export { createMcpFetchHandler } from './packem_shared/createMcpFetchHandler-DfzBSLrm.mjs';
3
- export { createPaidMcpServer } from './packem_shared/createPaidMcpServer-b-qp_aEF.mjs';
4
- export { connectStdio, createLunoraMcpServer } from './packem_shared/connectStdio-bkJcaXYE.mjs';
5
- export { READ_ONLY_TOOL_DEFINITIONS, WRITE_TOOL_DEFINITIONS, callTool, toolDefinitions } from './packem_shared/READ_ONLY_TOOL_DEFINITIONS-CWb_wFd5.mjs';
1
+ import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as T,callAgentTool as n,parseAgentsEnv as c}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-ihZg6vIZ.mjs";import{createMcpFetchHandler as a}from"./packem_shared/createMcpFetchHandler-CHssdLE-.mjs";import{createPaidMcpServer as l}from"./packem_shared/createPaidMcpServer-CQP3p1-0.mjs";import{connectStdio as E,createLunoraMcpServer as O}from"./packem_shared/connectStdio-Dtos_i1h.mjs";import{READ_ONLY_TOOL_DEFINITIONS as A,WRITE_TOOL_DEFINITIONS as I,callTool as S,toolDefinitions as f}from"./packem_shared/READ_ONLY_TOOL_DEFINITIONS-J_O1zHbG.mjs";export{r as AGENT_RUN_INPUT_SCHEMA,t as AGENT_STATUS_TOOL_NAME,A as READ_ONLY_TOOL_DEFINITIONS,I as WRITE_TOOL_DEFINITIONS,T as agentToolDefinitions,n as callAgentTool,S as callTool,E as connectStdio,O as createLunoraMcpServer,a as createMcpFetchHandler,l as createPaidMcpServer,c as parseAgentsEnv,f as toolDefinitions};
@@ -0,0 +1 @@
1
+ const N="agents:agentRun",w="agents:agentThread",S="agents:agentMessages",p="lunora_agent_status",_=new Set(["cancelled","error","idle"]),E=6e4,M=600,k={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"},O={properties:{threadKey:{description:"The thread key returned by an agent tool call.",type:"string"}},required:["threadKey"],type:"object"},y=t=>t.toolName??`agent_${t.name}`,I=t=>{if(t===void 0)return[];const n=[];for(const e of t.split(";")){const r=e.trim();if(r.length===0)continue;const a=r.indexOf(":");if(a<=0)continue;const s=r.slice(0,a).trim(),o=r.slice(a+1).trim();s.length===0||o.length===0||n.push({description:o,name:s})}return n},L=(t,n)=>n!==!0||t.length===0?[]:[...t.map(e=>({description:`${e.description} Starts a durable agent run and returns its final answer.`,inputSchema:k,name:y(e)})),{description:"Check the status of a durable agent run (and its answer if finished) by its threadKey.",inputSchema:O,name:p}],P=(t,n)=>t===p||n.some(e=>y(e)===t),q=()=>`mcp-${crypto.randomUUID()}`,c=t=>({__lunoraRef:t}),U=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}),l=(t,n)=>{const e=t[n];return typeof e=="string"&&e.length>0?e:void 0},C=t=>{for(let n=t.length-1;n>=0;n-=1){const e=t[n],r=e?.toolCalls,a=Array.isArray(r)&&r.length>0;if(e?.role==="assistant"&&!a)return typeof e.content=="string"?e.content:""}return""},A=t=>t!==null&&typeof t=="object"&&typeof t.status=="string"?t.status:"unknown",v=async(t,n,e,r)=>{const a=await t.query(c(S),{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:C(a),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(c(w),{key:e}),a=A(r);return _.has(a)?v(t,e,a,r):d({status:a==="unknown"?"running":a,threadKey:e})},$=async(t,n,e,r)=>{try{if(r.allowAgents!==!0)return i(`tool "${n}" is disabled: agent tools are off. Enable them with the LUNORA_MCP_ALLOW_AGENTS env var.`);if(n===p)return await R(t,e);const a=r.exposures.find(u=>y(u)===n);if(a===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 o=l(e,"threadKey")??q(),h=l(e,"title"),{id:T}=await t.mutation(c(N),{agent:a.name,input:s,threadKey:o,...h===void 0?{}:{title:h}}),g=r.pollIntervalMs??M,b=r.maxWaitMs??E,K=r.wait??U,x=Math.max(1,Math.ceil(b/g));for(let u=0;u<x;u+=1){const f=await t.query(c(w),{key:o}),m=A(f);if(_.has(m))return await v(t,o,m,f);await K(g)}return d({hint:"call lunora_agent_status with this threadKey to poll for the answer",runId:T,status:"running",threadKey:o})}catch(a){const s=a instanceof Error?a.message:String(a);return i(s)}};export{k as AGENT_RUN_INPUT_SCHEMA,p as AGENT_STATUS_TOOL_NAME,L as agentToolDefinitions,$ as callAgentTool,C as finalAnswer,P as isAgentToolName,I as parseAgentsEnv};
@@ -0,0 +1 @@
1
+ import{LunoraError as u}from"@lunora/errors";const f={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"},y={properties:{},type:"object"},E={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},g=[{description:"List the deployment's public functions (queries, mutations, actions) with their kinds.",inputSchema:y,name:"lunora_list_functions"},{description:"List the deployment's .global() tables and their column shapes.",inputSchema:y,name:"lunora_list_tables"},{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:E,name:"lunora_get_function_schema"},{description:"Run a query and return its result. Read-only.",inputSchema:f,name:"lunora_run_query"}],b=[{description:"Run a mutation and return its result. Writes data — use with care.",inputSchema:f,name:"lunora_run_mutation"},{description:"Run an action and return its result. May call external services.",inputSchema:f,name:"lunora_run_action"}],O=new Set(b.map(t=>t.name)),T=t=>t===!0?[...g,...b]:g,S=t=>{const{functionPath:e}=t;if(typeof e!="string"||e.length===0)throw new u("INTERNAL",'"functionPath" is required and must be a non-empty string');return e},m=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),_=t=>Array.isArray(t)?"an array":`a ${typeof t}`,A=t=>{if(t==null)return{};if(typeof t=="string"){let e;try{e=JSON.parse(t)}catch{throw new u("BAD_REQUEST",'"args" must be a JSON object; received a string that is not valid JSON')}if(!m(e))throw new u("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${_(e)}`);return e}if(!m(t))throw new u("BAD_REQUEST",`"args" must be a JSON object, got ${_(t)}`);return t},l=t=>{const e=S(t),r=A(t.args),i=typeof t.shardKey=="string"&&t.shardKey.length>0?t.shardKey:void 0;return{args:r,functionPath:e,shardKey:i}},d=t=>({__lunoraRef:t}),w=t=>{let e="";for(let r=0;r<t.length;r+=32768)e+=String.fromCharCode(...t.subarray(r,r+32768));return btoa(e)},N=(t,e)=>{if(typeof e=="bigint")return e.toString();if(e instanceof ArrayBuffer)return w(new Uint8Array(e));if(ArrayBuffer.isView(e)){const r=e;return w(new Uint8Array(r.buffer,r.byteOffset,r.byteLength))}return e},s=t=>({content:[{text:t===void 0?"null":JSON.stringify(t,N,2),type:"text"}]}),v=3e4,c=new WeakMap,p=t=>{const e=Date.now(),r=c.get(t);if(r!==void 0&&r.expiresAt>e)return r.promise;const i=t.listFunctions().catch(n=>{throw c.get(t)?.promise===i&&c.delete(t),n});return c.set(t,{expiresAt:e+v,promise:i}),i},h=async(t,e,r)=>{const i=(await p(t)).find(n=>n.path===e);if(i===void 0)throw new u("NOT_FOUND",`function not found or not public: ${e}`);if(i.kind!==r)throw new u("BAD_REQUEST",`function ${e} is a ${i.kind}, not a ${r}`)},x=async(t,e,r,i=!1)=>{try{if(i!==!0&&O.has(e))return{content:[{text:`tool "${e}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`,type:"text"}],isError:!0};switch(e){case"lunora_get_function_schema":{const n=S(r),a=(await p(t)).find(o=>o.path===n);return a===void 0?{content:[{text:`function not found: ${n}`,type:"text"}],isError:!0}:s({args:a.args??[],kind:a.kind,path:a.path})}case"lunora_list_functions":return s(await p(t));case"lunora_list_tables":return s(await t.listGlobalTables());case"lunora_run_action":{const{args:n,functionPath:a,shardKey:o}=l(r);return await h(t,a,"action"),s(await t.action(d(a),n,{shardKey:o}))}case"lunora_run_mutation":{const{args:n,functionPath:a,shardKey:o}=l(r);return await h(t,a,"mutation"),s(await t.mutation(d(a),n,{shardKey:o}))}case"lunora_run_query":{const{args:n,functionPath:a,shardKey:o}=l(r);return await h(t,a,"query"),s(await t.query(d(a),n,{shardKey:o}))}default:return{content:[{text:`unknown tool: ${e}`,type:"text"}],isError:!0}}}catch(n){return{content:[{text:n instanceof Error?n.message:String(n),type:"text"}],isError:!0}}};export{g as READ_ONLY_TOOL_DEFINITIONS,b as WRITE_TOOL_DEFINITIONS,x as callTool,T as toolDefinitions};
@@ -0,0 +1 @@
1
+ import{readFileSync as u}from"node:fs";import{dirname as l,join as f}from"node:path";import{fileURLToPath as p}from"node:url";import{LunoraClient as v}from"@lunora/client";import{LunoraError as g}from"@lunora/errors";import{Server as d}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as h}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as w,CallToolRequestSchema as M}from"@modelcontextprotocol/sdk/types.js";import{agentToolDefinitions as S,isAgentToolName as T,callAgentTool as k}from"./AGENT_RUN_INPUT_SCHEMA-ihZg6vIZ.mjs";import{toolDefinitions as L,callTool as x}from"./READ_ONLY_TOOL_DEFINITIONS-J_O1zHbG.mjs";const y=()=>{try{let t=l(p(import.meta.url));for(let r=0;r<8;r+=1){try{const a=u(f(t,"package.json"),"utf8"),o=JSON.parse(a);if(o.name==="@lunora/mcp"&&typeof o.version=="string"&&o.version.length>0)return o.version}catch{}const e=l(t);if(e===t)break;t=e}}catch{}return"0.0.0"},A={name:"lunora",version:y()},R=t=>{if(t.client!==void 0)return t.client;if(t.url===void 0)throw new g("INTERNAL","createLunoraMcpServer requires either a `client` or a `url`");const r=new v({fetch:t.fetch,url:t.url});return t.token!==void 0&&r.setAuthToken(t.token),r},q=t=>{const r=R(t),e=t.allowWrites??!1,a=t.allowAgents??!1,o=t.agents??[],n=new d(A,{capabilities:{tools:{}}});return n.setRequestHandler(w,()=>({tools:[...L(e),...S(o,a)]})),n.setRequestHandler(M,async c=>{const{arguments:m,name:s}=c.params,i=m??{};return T(s,o)?await k(r,s,i,{allowAgents:a,exposures:o,...t.agentMaxWaitMs===void 0?{}:{maxWaitMs:t.agentMaxWaitMs},...t.agentPollIntervalMs===void 0?{}:{pollIntervalMs:t.agentPollIntervalMs}}):await x(r,s,i,e)}),n},O=async t=>{const r=q(t);return await r.connect(new h),r};export{O as connectStdio,q as createLunoraMcpServer};
@@ -0,0 +1 @@
1
+ import{WebStandardStreamableHTTPServerTransport as c}from"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";import{createLunoraMcpServer as n}from"./connectStdio-Dtos_i1h.mjs";const o=async(e,t,a)=>{const r=new c({enableJsonResponse:!0,sessionIdGenerator:void 0});await e.connect(r);const s=await r.handleRequest(t,a);return r.close().catch(()=>{}),e.close().catch(()=>{}),s},d=e=>t=>o(n(e),t);export{d as createMcpFetchHandler,o as serveStateless};
@@ -0,0 +1 @@
1
+ import{LunoraError as h}from"@lunora/errors";import{createChargeMiddleware as f}from"@lunora/x402/charge";import{Server as y}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as w,CallToolRequestSchema as S}from"@modelcontextprotocol/sdk/types.js";import{serveStateless as g}from"./createMcpFetchHandler-CHssdLE-.mjs";const M={name:"lunora-paid-mcp",version:"0.0.0"},R="tools/call",d=n=>{if(typeof n!="object"||n===null)return;const{method:s,params:a}=n;if(s!==R||typeof a!="object"||a===null)return;const{name:i}=a;return typeof i=="string"?i:void 0},C=()=>Response.json({error:"A JSON-RPC batch may not reference a paid MCP tool; send paid tools/call requests individually."},{status:400}),P=n=>{const s=new Map,a=new Map,i=new Map,m=n.serverInfo??M,c=(e,t,o)=>{if(s.has(e.name))throw new h("BAD_REQUEST",`MCP tool "${e.name}" is already registered.`);const r={description:e.description,inputSchema:e.inputSchema,name:e.name};e.annotations!==void 0&&(r.annotations=e.annotations),s.set(e.name,{definition:r,handler:t}),o!==void 0&&a.set(e.name,o)},p=()=>{const e=new y(m,{capabilities:{tools:{}}});return e.setRequestHandler(w,()=>({tools:[...s.values()].map(t=>t.definition)})),e.setRequestHandler(S,async t=>{const o=s.get(t.params.name);return o===void 0?{content:[{text:`unknown tool: ${t.params.name}`,type:"text"}],isError:!0}:await o.handler(t.params.arguments??{})}),e},u=(e,t)=>{let o=i.get(e);return o===void 0&&(o=f({...n.charge,price:t},{resource:e}).catch(r=>{throw i.delete(e),r}),i.set(e,o)),o};return{fetchHandler:async e=>{let t;try{t=await e.clone().json()}catch{t=void 0}const o=()=>g(p(),e,t===void 0?void 0:{parsedBody:t});if(Array.isArray(t))return t.some(v=>a.has(d(v)??""))?C():o();const r=d(t),l=r===void 0?void 0:a.get(r);return r===void 0||l===void 0?o():(await u(r,l)).handle(e,o)},paidTool:(e,t)=>{c(e,t,e.price)},tool:(e,t)=>{c(e,t)}}};export{P as createPaidMcpServer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/mcp",
3
- "version": "1.0.0-alpha.30",
3
+ "version": "1.0.0-alpha.32",
4
4
  "description": "Model Context Protocol server exposing a Lunora deployment to AI agents",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -49,9 +49,9 @@
49
49
  "access": "public"
50
50
  },
51
51
  "dependencies": {
52
- "@lunora/client": "1.0.0-alpha.28",
52
+ "@lunora/client": "1.0.0-alpha.30",
53
53
  "@lunora/errors": "1.0.0-alpha.8",
54
- "@lunora/x402": "1.0.0-alpha.6",
54
+ "@lunora/x402": "1.0.0-alpha.7",
55
55
  "@modelcontextprotocol/sdk": "^1.29.0"
56
56
  },
57
57
  "engines": {
@@ -1,168 +0,0 @@
1
- const AGENT_RUN_PATH = "agents:agentRun";
2
- const AGENT_THREAD_PATH = "agents:agentThread";
3
- const AGENT_MESSAGES_PATH = "agents:agentMessages";
4
- const AGENT_STATUS_TOOL_NAME = "lunora_agent_status";
5
- const TERMINAL_STATUSES = /* @__PURE__ */ new Set(["cancelled", "error", "idle"]);
6
- const DEFAULT_MAX_WAIT_MS = 6e4;
7
- const DEFAULT_POLL_INTERVAL_MS = 600;
8
- const AGENT_RUN_INPUT_SCHEMA = {
9
- properties: {
10
- prompt: { description: "The task or message for the agent.", type: "string" },
11
- threadKey: { description: "Reuse to continue a conversation; omit to start a new thread.", type: "string" },
12
- title: { description: "Optional thread title (first run only).", type: "string" }
13
- },
14
- required: ["prompt"],
15
- type: "object"
16
- };
17
- const AGENT_STATUS_INPUT_SCHEMA = {
18
- properties: {
19
- threadKey: { description: "The thread key returned by an agent tool call.", type: "string" }
20
- },
21
- required: ["threadKey"],
22
- type: "object"
23
- };
24
- const agentToolName = (exposure) => exposure.toolName ?? `agent_${exposure.name}`;
25
- const parseAgentsEnv = (raw) => {
26
- if (raw === void 0) {
27
- return [];
28
- }
29
- const exposures = [];
30
- for (const entry of raw.split(";")) {
31
- const trimmed = entry.trim();
32
- if (trimmed.length === 0) {
33
- continue;
34
- }
35
- const separator = trimmed.indexOf(":");
36
- if (separator <= 0) {
37
- continue;
38
- }
39
- const name = trimmed.slice(0, separator).trim();
40
- const description = trimmed.slice(separator + 1).trim();
41
- if (name.length === 0 || description.length === 0) {
42
- continue;
43
- }
44
- exposures.push({ description, name });
45
- }
46
- return exposures;
47
- };
48
- const agentToolDefinitions = (exposures, allowAgents) => {
49
- if (allowAgents !== true || exposures.length === 0) {
50
- return [];
51
- }
52
- const perAgent = exposures.map((exposure) => {
53
- return {
54
- description: `${exposure.description} Starts a durable agent run and returns its final answer.`,
55
- inputSchema: AGENT_RUN_INPUT_SCHEMA,
56
- name: agentToolName(exposure)
57
- };
58
- });
59
- return [
60
- ...perAgent,
61
- {
62
- description: "Check the status of a durable agent run (and its answer if finished) by its threadKey.",
63
- inputSchema: AGENT_STATUS_INPUT_SCHEMA,
64
- name: AGENT_STATUS_TOOL_NAME
65
- }
66
- ];
67
- };
68
- const isAgentToolName = (name, exposures) => name === AGENT_STATUS_TOOL_NAME || exposures.some((exposure) => agentToolName(exposure) === name);
69
- const freshThreadKey = () => `mcp-${crypto.randomUUID()}`;
70
- const reference = (path) => {
71
- return { __lunoraRef: path };
72
- };
73
- const defaultWait = async (ms) => {
74
- await new Promise((resolve) => {
75
- setTimeout(resolve, ms);
76
- });
77
- };
78
- const ok = (value) => {
79
- return { content: [{ text: JSON.stringify(value, void 0, 2), type: "text" }] };
80
- };
81
- const fail = (text) => {
82
- return { content: [{ text, type: "text" }], isError: true };
83
- };
84
- const readString = (input, key) => {
85
- const value = input[key];
86
- return typeof value === "string" && value.length > 0 ? value : void 0;
87
- };
88
- const finalAnswer = (messages) => {
89
- for (let index = messages.length - 1; index >= 0; index -= 1) {
90
- const row = messages[index];
91
- const toolCalls = row?.["toolCalls"];
92
- const pending = Array.isArray(toolCalls) && toolCalls.length > 0;
93
- if (row?.["role"] === "assistant" && !pending) {
94
- return typeof row["content"] === "string" ? row["content"] : "";
95
- }
96
- }
97
- return "";
98
- };
99
- const readThreadStatus = (thread) => {
100
- if (thread !== null && typeof thread === "object" && typeof thread.status === "string") {
101
- return thread.status;
102
- }
103
- return "unknown";
104
- };
105
- const readTerminal = async (client, threadKey, status, thread) => {
106
- const messages = await client.query(reference(AGENT_MESSAGES_PATH), { key: threadKey });
107
- if (status === "error") {
108
- const error = thread !== null && typeof thread === "object" ? thread.error : void 0;
109
- return ok({ error: typeof error === "string" ? error : "the agent run failed", status, threadKey });
110
- }
111
- return ok({ status, text: finalAnswer(messages), threadKey });
112
- };
113
- const callAgentStatus = async (client, input) => {
114
- const threadKey = readString(input, "threadKey");
115
- if (threadKey === void 0) {
116
- return fail('"threadKey" is required and must be a non-empty string');
117
- }
118
- const thread = await client.query(reference(AGENT_THREAD_PATH), { key: threadKey });
119
- const status = readThreadStatus(thread);
120
- if (TERMINAL_STATUSES.has(status)) {
121
- return readTerminal(client, threadKey, status, thread);
122
- }
123
- return ok({ status: status === "unknown" ? "running" : status, threadKey });
124
- };
125
- const callAgentTool = async (client, name, input, options) => {
126
- try {
127
- if (options.allowAgents !== true) {
128
- return fail(`tool "${name}" is disabled: agent tools are off. Enable them with the LUNORA_MCP_ALLOW_AGENTS env var.`);
129
- }
130
- if (name === AGENT_STATUS_TOOL_NAME) {
131
- return await callAgentStatus(client, input);
132
- }
133
- const exposure = options.exposures.find((candidate) => agentToolName(candidate) === name);
134
- if (exposure === void 0) {
135
- return fail(`agent tool "${name}" is not exposed by this MCP server`);
136
- }
137
- const prompt = readString(input, "prompt");
138
- if (prompt === void 0) {
139
- return fail('"prompt" is required and must be a non-empty string');
140
- }
141
- const threadKey = readString(input, "threadKey") ?? freshThreadKey();
142
- const title = readString(input, "title");
143
- const { id } = await client.mutation(reference(AGENT_RUN_PATH), {
144
- agent: exposure.name,
145
- input: prompt,
146
- threadKey,
147
- ...title === void 0 ? {} : { title }
148
- });
149
- const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
150
- const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
151
- const wait = options.wait ?? defaultWait;
152
- const maxPolls = Math.max(1, Math.ceil(maxWaitMs / pollIntervalMs));
153
- for (let attempt = 0; attempt < maxPolls; attempt += 1) {
154
- const thread = await client.query(reference(AGENT_THREAD_PATH), { key: threadKey });
155
- const status = readThreadStatus(thread);
156
- if (TERMINAL_STATUSES.has(status)) {
157
- return await readTerminal(client, threadKey, status, thread);
158
- }
159
- await wait(pollIntervalMs);
160
- }
161
- return ok({ hint: "call lunora_agent_status with this threadKey to poll for the answer", runId: id, status: "running", threadKey });
162
- } catch (error) {
163
- const message = error instanceof Error ? error.message : String(error);
164
- return fail(message);
165
- }
166
- };
167
-
168
- export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, agentToolDefinitions, callAgentTool, finalAnswer, isAgentToolName, parseAgentsEnv };
@@ -1,204 +0,0 @@
1
- import { LunoraError } from '@lunora/errors';
2
-
3
- const RUN_INPUT_SCHEMA = {
4
- properties: {
5
- args: { description: "Arguments object passed to the function", type: "object" },
6
- functionPath: { description: 'Function reference, e.g. "messages:send"', type: "string" },
7
- shardKey: { description: "Optional shard key when the function is .shardBy()-partitioned", type: "string" }
8
- },
9
- required: ["functionPath"],
10
- type: "object"
11
- };
12
- const NO_INPUT_SCHEMA = { properties: {}, type: "object" };
13
- const FUNCTION_PATH_INPUT_SCHEMA = {
14
- properties: {
15
- functionPath: { description: 'Function reference, e.g. "messages:send"', type: "string" }
16
- },
17
- required: ["functionPath"],
18
- type: "object"
19
- };
20
- const READ_ONLY_TOOL_DEFINITIONS = [
21
- {
22
- description: "List the deployment's public functions (queries, mutations, actions) with their kinds.",
23
- inputSchema: NO_INPUT_SCHEMA,
24
- name: "lunora_list_functions"
25
- },
26
- {
27
- description: "List the deployment's .global() tables and their column shapes.",
28
- inputSchema: NO_INPUT_SCHEMA,
29
- name: "lunora_list_tables"
30
- },
31
- {
32
- 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.",
33
- inputSchema: FUNCTION_PATH_INPUT_SCHEMA,
34
- name: "lunora_get_function_schema"
35
- },
36
- {
37
- description: "Run a query and return its result. Read-only.",
38
- inputSchema: RUN_INPUT_SCHEMA,
39
- name: "lunora_run_query"
40
- }
41
- ];
42
- const WRITE_TOOL_DEFINITIONS = [
43
- {
44
- description: "Run a mutation and return its result. Writes data — use with care.",
45
- inputSchema: RUN_INPUT_SCHEMA,
46
- name: "lunora_run_mutation"
47
- },
48
- {
49
- description: "Run an action and return its result. May call external services.",
50
- inputSchema: RUN_INPUT_SCHEMA,
51
- name: "lunora_run_action"
52
- }
53
- ];
54
- const WRITE_TOOL_NAMES = new Set(WRITE_TOOL_DEFINITIONS.map((tool) => tool.name));
55
- const toolDefinitions = (allowWrites) => (
56
- // Fail closed: only the boolean `true` opts in. These are exported helpers, so
57
- // an env-plumbed/JS caller could pass a truthy string like `"false"`/`"0"` —
58
- // the explicit `=== true` guards that despite the declared `boolean` type.
59
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare -- intentional runtime guard at an exported API boundary against non-boolean callers
60
- allowWrites === true ? [...READ_ONLY_TOOL_DEFINITIONS, ...WRITE_TOOL_DEFINITIONS] : READ_ONLY_TOOL_DEFINITIONS
61
- );
62
- const readFunctionPath = (input) => {
63
- const { functionPath } = input;
64
- if (typeof functionPath !== "string" || functionPath.length === 0) {
65
- throw new LunoraError("INTERNAL", '"functionPath" is required and must be a non-empty string');
66
- }
67
- return functionPath;
68
- };
69
- const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
70
- const describeArgs = (value) => Array.isArray(value) ? "an array" : `a ${typeof value}`;
71
- const readArgumentsBag = (raw) => {
72
- if (raw === void 0 || raw === null) {
73
- return {};
74
- }
75
- if (typeof raw === "string") {
76
- let parsed;
77
- try {
78
- parsed = JSON.parse(raw);
79
- } catch {
80
- throw new LunoraError("BAD_REQUEST", `"args" must be a JSON object; received a string that is not valid JSON`);
81
- }
82
- if (!isPlainObject(parsed)) {
83
- throw new LunoraError("BAD_REQUEST", `"args" must be a JSON object; the provided string decoded to ${describeArgs(parsed)}`);
84
- }
85
- return parsed;
86
- }
87
- if (!isPlainObject(raw)) {
88
- throw new LunoraError("BAD_REQUEST", `"args" must be a JSON object, got ${describeArgs(raw)}`);
89
- }
90
- return raw;
91
- };
92
- const readRunArguments = (input) => {
93
- const functionPath = readFunctionPath(input);
94
- const args = readArgumentsBag(input.args);
95
- const shardKey = typeof input.shardKey === "string" && input.shardKey.length > 0 ? input.shardKey : void 0;
96
- return { args, functionPath, shardKey };
97
- };
98
- const reference = (functionPath) => {
99
- return { __lunoraRef: functionPath };
100
- };
101
- const bytesToBase64 = (bytes) => {
102
- let binary = "";
103
- const chunk = 32768;
104
- for (let index = 0; index < bytes.length; index += chunk) {
105
- binary += String.fromCharCode(...bytes.subarray(index, index + chunk));
106
- }
107
- return btoa(binary);
108
- };
109
- const jsonResultReplacer = (_key, value) => {
110
- if (typeof value === "bigint") {
111
- return value.toString();
112
- }
113
- if (value instanceof ArrayBuffer) {
114
- return bytesToBase64(new Uint8Array(value));
115
- }
116
- if (ArrayBuffer.isView(value)) {
117
- const view = value;
118
- return bytesToBase64(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
119
- }
120
- return value;
121
- };
122
- const ok = (value) => {
123
- const text = value === void 0 ? "null" : JSON.stringify(value, jsonResultReplacer, 2);
124
- return { content: [{ text, type: "text" }] };
125
- };
126
- const FUNCTIONS_CACHE_TTL_MS = 3e4;
127
- const functionsCache = /* @__PURE__ */ new WeakMap();
128
- const listFunctionsCached = (client) => {
129
- const now = Date.now();
130
- const cached = functionsCache.get(client);
131
- if (cached !== void 0 && cached.expiresAt > now) {
132
- return cached.promise;
133
- }
134
- const promise = client.listFunctions().catch((error) => {
135
- if (functionsCache.get(client)?.promise === promise) {
136
- functionsCache.delete(client);
137
- }
138
- throw error;
139
- });
140
- functionsCache.set(client, { expiresAt: now + FUNCTIONS_CACHE_TTL_MS, promise });
141
- return promise;
142
- };
143
- const assertRunnable = async (client, functionPath, expectedKind) => {
144
- const functions = await listFunctionsCached(client);
145
- const descriptor = functions.find((function_) => function_.path === functionPath);
146
- if (descriptor === void 0) {
147
- throw new LunoraError("NOT_FOUND", `function not found or not public: ${functionPath}`);
148
- }
149
- if (descriptor.kind !== expectedKind) {
150
- throw new LunoraError("BAD_REQUEST", `function ${functionPath} is a ${descriptor.kind}, not a ${expectedKind}`);
151
- }
152
- };
153
- const callTool = async (client, name, input, allowWrites = false) => {
154
- try {
155
- if (allowWrites !== true && WRITE_TOOL_NAMES.has(name)) {
156
- return {
157
- content: [
158
- { text: `tool "${name}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`, type: "text" }
159
- ],
160
- isError: true
161
- };
162
- }
163
- switch (name) {
164
- case "lunora_get_function_schema": {
165
- const functionPath = readFunctionPath(input);
166
- const functions = await listFunctionsCached(client);
167
- const descriptor = functions.find((function_) => function_.path === functionPath);
168
- if (descriptor === void 0) {
169
- return { content: [{ text: `function not found: ${functionPath}`, type: "text" }], isError: true };
170
- }
171
- return ok({ args: descriptor.args ?? [], kind: descriptor.kind, path: descriptor.path });
172
- }
173
- case "lunora_list_functions": {
174
- return ok(await listFunctionsCached(client));
175
- }
176
- case "lunora_list_tables": {
177
- return ok(await client.listGlobalTables());
178
- }
179
- case "lunora_run_action": {
180
- const { args, functionPath, shardKey } = readRunArguments(input);
181
- await assertRunnable(client, functionPath, "action");
182
- return ok(await client.action(reference(functionPath), args, { shardKey }));
183
- }
184
- case "lunora_run_mutation": {
185
- const { args, functionPath, shardKey } = readRunArguments(input);
186
- await assertRunnable(client, functionPath, "mutation");
187
- return ok(await client.mutation(reference(functionPath), args, { shardKey }));
188
- }
189
- case "lunora_run_query": {
190
- const { args, functionPath, shardKey } = readRunArguments(input);
191
- await assertRunnable(client, functionPath, "query");
192
- return ok(await client.query(reference(functionPath), args, { shardKey }));
193
- }
194
- default: {
195
- return { content: [{ text: `unknown tool: ${name}`, type: "text" }], isError: true };
196
- }
197
- }
198
- } catch (error) {
199
- const message = error instanceof Error ? error.message : String(error);
200
- return { content: [{ text: message, type: "text" }], isError: true };
201
- }
202
- };
203
-
204
- export { READ_ONLY_TOOL_DEFINITIONS, WRITE_TOOL_DEFINITIONS, callTool, toolDefinitions };
@@ -1,76 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import { LunoraClient } from '@lunora/client';
5
- import { LunoraError } from '@lunora/errors';
6
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
- import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
9
- import { agentToolDefinitions, isAgentToolName, callAgentTool } from './AGENT_RUN_INPUT_SCHEMA-DWobae3P.mjs';
10
- import { toolDefinitions, callTool } from './READ_ONLY_TOOL_DEFINITIONS-CWb_wFd5.mjs';
11
-
12
- const resolveVersion = () => {
13
- try {
14
- let directory = dirname(fileURLToPath(import.meta.url));
15
- for (let depth = 0; depth < 8; depth += 1) {
16
- try {
17
- const raw = readFileSync(join(directory, "package.json"), "utf8");
18
- const pkg = JSON.parse(raw);
19
- if (pkg.name === "@lunora/mcp" && typeof pkg.version === "string" && pkg.version.length > 0) {
20
- return pkg.version;
21
- }
22
- } catch {
23
- }
24
- const parent = dirname(directory);
25
- if (parent === directory) {
26
- break;
27
- }
28
- directory = parent;
29
- }
30
- } catch {
31
- }
32
- return "0.0.0";
33
- };
34
- const SERVER_INFO = { name: "lunora", version: resolveVersion() };
35
- const resolveClient = (options) => {
36
- if (options.client !== void 0) {
37
- return options.client;
38
- }
39
- if (options.url === void 0) {
40
- throw new LunoraError("INTERNAL", "createLunoraMcpServer requires either a `client` or a `url`");
41
- }
42
- const client = new LunoraClient({ fetch: options.fetch, url: options.url });
43
- if (options.token !== void 0) {
44
- client.setAuthToken(options.token);
45
- }
46
- return client;
47
- };
48
- const createLunoraMcpServer = (options) => {
49
- const client = resolveClient(options);
50
- const allowWrites = options.allowWrites ?? false;
51
- const allowAgents = options.allowAgents ?? false;
52
- const agents = options.agents ?? [];
53
- const server = new Server(SERVER_INFO, { capabilities: { tools: {} } });
54
- server.setRequestHandler(ListToolsRequestSchema, () => {
55
- return { tools: [...toolDefinitions(allowWrites), ...agentToolDefinitions(agents, allowAgents)] };
56
- });
57
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
58
- const { arguments: rawArguments, name } = request.params;
59
- const input = rawArguments ?? {};
60
- const result = isAgentToolName(name, agents) ? await callAgentTool(client, name, input, {
61
- allowAgents,
62
- exposures: agents,
63
- ...options.agentMaxWaitMs === void 0 ? {} : { maxWaitMs: options.agentMaxWaitMs },
64
- ...options.agentPollIntervalMs === void 0 ? {} : { pollIntervalMs: options.agentPollIntervalMs }
65
- }) : await callTool(client, name, input, allowWrites);
66
- return result;
67
- });
68
- return server;
69
- };
70
- const connectStdio = async (options) => {
71
- const server = createLunoraMcpServer(options);
72
- await server.connect(new StdioServerTransport());
73
- return server;
74
- };
75
-
76
- export { connectStdio, createLunoraMcpServer };
@@ -1,14 +0,0 @@
1
- import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
2
- import { createLunoraMcpServer } from './connectStdio-bkJcaXYE.mjs';
3
-
4
- const serveStateless = async (server, request, options) => {
5
- const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true, sessionIdGenerator: void 0 });
6
- await server.connect(transport);
7
- const response = await transport.handleRequest(request, options);
8
- transport.close().catch(() => void 0);
9
- server.close().catch(() => void 0);
10
- return response;
11
- };
12
- const createMcpFetchHandler = (options) => (request) => serveStateless(createLunoraMcpServer(options), request);
13
-
14
- export { createMcpFetchHandler, serveStateless };
@@ -1,93 +0,0 @@
1
- import { LunoraError } from '@lunora/errors';
2
- import { createChargeMiddleware } from '@lunora/x402/charge';
3
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
- import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
- import { serveStateless } from './createMcpFetchHandler-DfzBSLrm.mjs';
6
-
7
- const DEFAULT_SERVER_INFO = { name: "lunora-paid-mcp", version: "0.0.0" };
8
- const CALL_TOOL_METHOD = "tools/call";
9
- const callToolName = (message) => {
10
- if (typeof message !== "object" || message === null) {
11
- return void 0;
12
- }
13
- const { method, params } = message;
14
- if (method !== CALL_TOOL_METHOD || typeof params !== "object" || params === null) {
15
- return void 0;
16
- }
17
- const { name } = params;
18
- return typeof name === "string" ? name : void 0;
19
- };
20
- const refuseBatch = () => Response.json({ error: "A JSON-RPC batch may not reference a paid MCP tool; send paid tools/call requests individually." }, { status: 400 });
21
- const createPaidMcpServer = (config) => {
22
- const tools = /* @__PURE__ */ new Map();
23
- const prices = /* @__PURE__ */ new Map();
24
- const middlewareByTool = /* @__PURE__ */ new Map();
25
- const serverInfo = config.serverInfo ?? DEFAULT_SERVER_INFO;
26
- const register = (options, handler, price) => {
27
- if (tools.has(options.name)) {
28
- throw new LunoraError("BAD_REQUEST", `MCP tool "${options.name}" is already registered.`);
29
- }
30
- const definition = { description: options.description, inputSchema: options.inputSchema, name: options.name };
31
- if (options.annotations !== void 0) {
32
- definition.annotations = options.annotations;
33
- }
34
- tools.set(options.name, { definition, handler });
35
- if (price !== void 0) {
36
- prices.set(options.name, price);
37
- }
38
- };
39
- const buildServer = () => {
40
- const server = new Server(serverInfo, { capabilities: { tools: {} } });
41
- server.setRequestHandler(ListToolsRequestSchema, () => {
42
- return { tools: [...tools.values()].map((entry) => entry.definition) };
43
- });
44
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
45
- const entry = tools.get(request.params.name);
46
- if (entry === void 0) {
47
- return { content: [{ text: `unknown tool: ${request.params.name}`, type: "text" }], isError: true };
48
- }
49
- const result = await entry.handler(request.params.arguments ?? {});
50
- return result;
51
- });
52
- return server;
53
- };
54
- const gateFor = (name, price) => {
55
- let pending = middlewareByTool.get(name);
56
- if (pending === void 0) {
57
- pending = createChargeMiddleware({ ...config.charge, price }, { resource: name }).catch((error) => {
58
- middlewareByTool.delete(name);
59
- throw error;
60
- });
61
- middlewareByTool.set(name, pending);
62
- }
63
- return pending;
64
- };
65
- const fetchHandler = async (request) => {
66
- let parsedBody;
67
- try {
68
- parsedBody = await request.clone().json();
69
- } catch {
70
- parsedBody = void 0;
71
- }
72
- const dispatch = () => serveStateless(buildServer(), request, parsedBody === void 0 ? void 0 : { parsedBody });
73
- if (Array.isArray(parsedBody)) {
74
- return parsedBody.some((message) => prices.has(callToolName(message) ?? "")) ? refuseBatch() : dispatch();
75
- }
76
- const name = callToolName(parsedBody);
77
- const price = name === void 0 ? void 0 : prices.get(name);
78
- if (name === void 0 || price === void 0) {
79
- return dispatch();
80
- }
81
- const middleware = await gateFor(name, price);
82
- return middleware.handle(request, dispatch);
83
- };
84
- const paidTool = (options, handler) => {
85
- register(options, handler, options.price);
86
- };
87
- const tool = (options, handler) => {
88
- register(options, handler);
89
- };
90
- return { fetchHandler, paidTool, tool };
91
- };
92
-
93
- export { createPaidMcpServer };