@lunora/mcp 1.0.0-alpha.2 → 1.0.0-alpha.21

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,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { connectStdio } from './packem_shared/connectStdio-C_mvQBs2.mjs';
2
+ import { connectStdio } from './packem_shared/connectStdio-mHv2dT3u.mjs';
3
3
 
4
+ const ENABLED_ENV_VALUES = /* @__PURE__ */ new Set(["1", "on", "true", "yes"]);
5
+ const isEnvEnabled = (value) => value !== void 0 && ENABLED_ENV_VALUES.has(value.trim().toLowerCase());
4
6
  class BinError extends Error {
5
7
  code;
6
8
  constructor(message, code) {
@@ -20,7 +22,7 @@ const runBin = async (environment, dependencies = {}) => {
20
22
  throw new BinError("LUNORA_URL environment variable is required", 1);
21
23
  }
22
24
  try {
23
- await connect({ token: environment.LUNORA_ADMIN_TOKEN, url });
25
+ await connect({ allowWrites: isEnvEnabled(environment.LUNORA_MCP_ALLOW_WRITES), token: environment.LUNORA_ADMIN_TOKEN, url });
24
26
  } catch (error) {
25
27
  const message = error instanceof Error ? error.message : String(error);
26
28
  writeError(`lunora-mcp: failed to start — ${message}
package/dist/index.d.mts CHANGED
@@ -1,6 +1,10 @@
1
- import { LunoraClient } from '@lunora/client';
2
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
3
+ import { LunoraClient } from '@lunora/client';
4
+ import { X402ChargeConfig, X402Price } from '@lunora/x402/charge';
5
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
3
6
  interface LunoraMcpServerOptions {
7
+ allowWrites?: boolean;
4
8
  client?: LunoraClient;
5
9
  fetch?: typeof fetch;
6
10
  token?: string;
@@ -8,6 +12,8 @@ interface LunoraMcpServerOptions {
8
12
  }
9
13
  declare const createLunoraMcpServer: (options: LunoraMcpServerOptions) => Server;
10
14
  declare const connectStdio: (options: LunoraMcpServerOptions) => Promise<Server>;
15
+ type McpFetchHandler = (request: Request) => Promise<Response>;
16
+ declare const createMcpFetchHandler: (options: LunoraMcpServerOptions) => McpFetchHandler;
11
17
  interface ToolInputSchema {
12
18
  properties: Record<string, unknown>;
13
19
  required?: ReadonlyArray<string>;
@@ -25,6 +31,32 @@ interface ToolResult {
25
31
  }[];
26
32
  isError?: boolean;
27
33
  }
28
- declare const TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
29
- declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>) => Promise<ToolResult>;
30
- export { type LunoraMcpServerOptions, TOOL_DEFINITIONS, type ToolDefinition, type ToolInputSchema, type ToolResult, callTool, connectStdio, createLunoraMcpServer };
34
+ declare const READ_ONLY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
35
+ declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
36
+ declare const toolDefinitions: (allowWrites: boolean) => ReadonlyArray<ToolDefinition>;
37
+ declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean) => Promise<ToolResult>;
38
+ type ToolHandler = (arguments_: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
39
+ interface RegisterToolOptions {
40
+ annotations?: Tool["annotations"];
41
+ description: string;
42
+ inputSchema: ToolInputSchema;
43
+ name: string;
44
+ }
45
+ interface RegisterPaidToolOptions extends RegisterToolOptions {
46
+ price: X402Price;
47
+ }
48
+ type PaidMcpChargeConfig = Omit<X402ChargeConfig, "price">;
49
+ interface PaidMcpServerConfig {
50
+ charge: PaidMcpChargeConfig;
51
+ serverInfo?: {
52
+ name: string;
53
+ version: string;
54
+ };
55
+ }
56
+ interface PaidMcpServer {
57
+ readonly fetchHandler: McpFetchHandler;
58
+ paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
59
+ tool: (options: RegisterToolOptions, handler: ToolHandler) => void;
60
+ }
61
+ declare const createPaidMcpServer: (config: PaidMcpServerConfig) => PaidMcpServer;
62
+ export { type LunoraMcpServerOptions, type McpFetchHandler, type PaidMcpChargeConfig, type PaidMcpServer, type PaidMcpServerConfig, READ_ONLY_TOOL_DEFINITIONS, type RegisterPaidToolOptions, type RegisterToolOptions, type ToolDefinition, type ToolHandler, type ToolInputSchema, type ToolResult, WRITE_TOOL_DEFINITIONS, callTool, connectStdio, createLunoraMcpServer, createMcpFetchHandler, createPaidMcpServer, toolDefinitions };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
- import { LunoraClient } from '@lunora/client';
2
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
3
+ import { LunoraClient } from '@lunora/client';
4
+ import { X402ChargeConfig, X402Price } from '@lunora/x402/charge';
5
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
3
6
  interface LunoraMcpServerOptions {
7
+ allowWrites?: boolean;
4
8
  client?: LunoraClient;
5
9
  fetch?: typeof fetch;
6
10
  token?: string;
@@ -8,6 +12,8 @@ interface LunoraMcpServerOptions {
8
12
  }
9
13
  declare const createLunoraMcpServer: (options: LunoraMcpServerOptions) => Server;
10
14
  declare const connectStdio: (options: LunoraMcpServerOptions) => Promise<Server>;
15
+ type McpFetchHandler = (request: Request) => Promise<Response>;
16
+ declare const createMcpFetchHandler: (options: LunoraMcpServerOptions) => McpFetchHandler;
11
17
  interface ToolInputSchema {
12
18
  properties: Record<string, unknown>;
13
19
  required?: ReadonlyArray<string>;
@@ -25,6 +31,32 @@ interface ToolResult {
25
31
  }[];
26
32
  isError?: boolean;
27
33
  }
28
- declare const TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
29
- declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>) => Promise<ToolResult>;
30
- export { type LunoraMcpServerOptions, TOOL_DEFINITIONS, type ToolDefinition, type ToolInputSchema, type ToolResult, callTool, connectStdio, createLunoraMcpServer };
34
+ declare const READ_ONLY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
35
+ declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
36
+ declare const toolDefinitions: (allowWrites: boolean) => ReadonlyArray<ToolDefinition>;
37
+ declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean) => Promise<ToolResult>;
38
+ type ToolHandler = (arguments_: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
39
+ interface RegisterToolOptions {
40
+ annotations?: Tool["annotations"];
41
+ description: string;
42
+ inputSchema: ToolInputSchema;
43
+ name: string;
44
+ }
45
+ interface RegisterPaidToolOptions extends RegisterToolOptions {
46
+ price: X402Price;
47
+ }
48
+ type PaidMcpChargeConfig = Omit<X402ChargeConfig, "price">;
49
+ interface PaidMcpServerConfig {
50
+ charge: PaidMcpChargeConfig;
51
+ serverInfo?: {
52
+ name: string;
53
+ version: string;
54
+ };
55
+ }
56
+ interface PaidMcpServer {
57
+ readonly fetchHandler: McpFetchHandler;
58
+ paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
59
+ tool: (options: RegisterToolOptions, handler: ToolHandler) => void;
60
+ }
61
+ declare const createPaidMcpServer: (config: PaidMcpServerConfig) => PaidMcpServer;
62
+ export { type LunoraMcpServerOptions, type McpFetchHandler, type PaidMcpChargeConfig, type PaidMcpServer, type PaidMcpServerConfig, READ_ONLY_TOOL_DEFINITIONS, type RegisterPaidToolOptions, type RegisterToolOptions, type ToolDefinition, type ToolHandler, type ToolInputSchema, type ToolResult, WRITE_TOOL_DEFINITIONS, callTool, connectStdio, createLunoraMcpServer, createMcpFetchHandler, createPaidMcpServer, toolDefinitions };
package/dist/index.mjs CHANGED
@@ -1,2 +1,4 @@
1
- export { connectStdio, createLunoraMcpServer } from './packem_shared/connectStdio-C_mvQBs2.mjs';
2
- export { TOOL_DEFINITIONS, callTool } from './packem_shared/TOOL_DEFINITIONS-Dpiu38ji.mjs';
1
+ export { createMcpFetchHandler } from './packem_shared/createMcpFetchHandler-CGqlLHxj.mjs';
2
+ export { createPaidMcpServer } from './packem_shared/createPaidMcpServer-S7RdldZo.mjs';
3
+ export { connectStdio, createLunoraMcpServer } from './packem_shared/connectStdio-mHv2dT3u.mjs';
4
+ export { READ_ONLY_TOOL_DEFINITIONS, WRITE_TOOL_DEFINITIONS, callTool, toolDefinitions } from './packem_shared/READ_ONLY_TOOL_DEFINITIONS-B5x61zt4.mjs';
@@ -1,3 +1,5 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
1
3
  const RUN_INPUT_SCHEMA = {
2
4
  properties: {
3
5
  args: { description: "Arguments object passed to the function", type: "object" },
@@ -15,7 +17,7 @@ const FUNCTION_PATH_INPUT_SCHEMA = {
15
17
  required: ["functionPath"],
16
18
  type: "object"
17
19
  };
18
- const TOOL_DEFINITIONS = [
20
+ const READ_ONLY_TOOL_DEFINITIONS = [
19
21
  {
20
22
  description: "List the deployment's public functions (queries, mutations, actions) with their kinds.",
21
23
  inputSchema: NO_INPUT_SCHEMA,
@@ -35,7 +37,9 @@ const TOOL_DEFINITIONS = [
35
37
  description: "Run a query and return its result. Read-only.",
36
38
  inputSchema: RUN_INPUT_SCHEMA,
37
39
  name: "lunora_run_query"
38
- },
40
+ }
41
+ ];
42
+ const WRITE_TOOL_DEFINITIONS = [
39
43
  {
40
44
  description: "Run a mutation and return its result. Writes data — use with care.",
41
45
  inputSchema: RUN_INPUT_SCHEMA,
@@ -47,10 +51,18 @@ const TOOL_DEFINITIONS = [
47
51
  name: "lunora_run_action"
48
52
  }
49
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
+ );
50
62
  const readFunctionPath = (input) => {
51
63
  const { functionPath } = input;
52
64
  if (typeof functionPath !== "string" || functionPath.length === 0) {
53
- throw new Error('"functionPath" is required and must be a non-empty string');
65
+ throw new LunoraError("INTERNAL", '"functionPath" is required and must be a non-empty string');
54
66
  }
55
67
  return functionPath;
56
68
  };
@@ -69,8 +81,26 @@ const ok = (value) => {
69
81
  const text = value === void 0 ? "null" : JSON.stringify(value, void 0, 2);
70
82
  return { content: [{ text, type: "text" }] };
71
83
  };
72
- const callTool = async (client, name, input) => {
84
+ const assertRunnable = async (client, functionPath, expectedKind) => {
85
+ const functions = await client.listFunctions();
86
+ const descriptor = functions.find((function_) => function_.path === functionPath);
87
+ if (descriptor === void 0) {
88
+ throw new LunoraError("NOT_FOUND", `function not found or not public: ${functionPath}`);
89
+ }
90
+ if (descriptor.kind !== expectedKind) {
91
+ throw new LunoraError("BAD_REQUEST", `function ${functionPath} is a ${descriptor.kind}, not a ${expectedKind}`);
92
+ }
93
+ };
94
+ const callTool = async (client, name, input, allowWrites = false) => {
73
95
  try {
96
+ if (allowWrites !== true && WRITE_TOOL_NAMES.has(name)) {
97
+ return {
98
+ content: [
99
+ { text: `tool "${name}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`, type: "text" }
100
+ ],
101
+ isError: true
102
+ };
103
+ }
74
104
  switch (name) {
75
105
  case "lunora_get_function_schema": {
76
106
  const functionPath = readFunctionPath(input);
@@ -89,14 +119,17 @@ const callTool = async (client, name, input) => {
89
119
  }
90
120
  case "lunora_run_action": {
91
121
  const { args, functionPath, shardKey } = readRunArguments(input);
122
+ await assertRunnable(client, functionPath, "action");
92
123
  return ok(await client.action(reference(functionPath), args, { shardKey }));
93
124
  }
94
125
  case "lunora_run_mutation": {
95
126
  const { args, functionPath, shardKey } = readRunArguments(input);
127
+ await assertRunnable(client, functionPath, "mutation");
96
128
  return ok(await client.mutation(reference(functionPath), args, { shardKey }));
97
129
  }
98
130
  case "lunora_run_query": {
99
131
  const { args, functionPath, shardKey } = readRunArguments(input);
132
+ await assertRunnable(client, functionPath, "query");
100
133
  return ok(await client.query(reference(functionPath), args, { shardKey }));
101
134
  }
102
135
  default: {
@@ -109,4 +142,4 @@ const callTool = async (client, name, input) => {
109
142
  }
110
143
  };
111
144
 
112
- export { TOOL_DEFINITIONS, callTool };
145
+ export { READ_ONLY_TOOL_DEFINITIONS, WRITE_TOOL_DEFINITIONS, callTool, toolDefinitions };
@@ -2,10 +2,11 @@ import { readFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { LunoraClient } from '@lunora/client';
5
+ import { LunoraError } from '@lunora/errors';
5
6
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
7
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
8
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
8
- import { TOOL_DEFINITIONS, callTool } from './TOOL_DEFINITIONS-Dpiu38ji.mjs';
9
+ import { toolDefinitions, callTool } from './READ_ONLY_TOOL_DEFINITIONS-B5x61zt4.mjs';
9
10
 
10
11
  const resolveVersion = () => {
11
12
  try {
@@ -35,7 +36,7 @@ const resolveClient = (options) => {
35
36
  return options.client;
36
37
  }
37
38
  if (options.url === void 0) {
38
- throw new Error("createLunoraMcpServer requires either a `client` or a `url`");
39
+ throw new LunoraError("INTERNAL", "createLunoraMcpServer requires either a `client` or a `url`");
39
40
  }
40
41
  const client = new LunoraClient({ fetch: options.fetch, url: options.url });
41
42
  if (options.token !== void 0) {
@@ -45,12 +46,13 @@ const resolveClient = (options) => {
45
46
  };
46
47
  const createLunoraMcpServer = (options) => {
47
48
  const client = resolveClient(options);
49
+ const allowWrites = options.allowWrites ?? false;
48
50
  const server = new Server(SERVER_INFO, { capabilities: { tools: {} } });
49
51
  server.setRequestHandler(ListToolsRequestSchema, () => {
50
- return { tools: [...TOOL_DEFINITIONS] };
52
+ return { tools: [...toolDefinitions(allowWrites)] };
51
53
  });
52
54
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
53
- const result = await callTool(client, request.params.name, request.params.arguments ?? {});
55
+ const result = await callTool(client, request.params.name, request.params.arguments ?? {}, allowWrites);
54
56
  return result;
55
57
  });
56
58
  return server;
@@ -0,0 +1,14 @@
1
+ import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
2
+ import { createLunoraMcpServer } from './connectStdio-mHv2dT3u.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 };
@@ -0,0 +1,93 @@
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-CGqlLHxj.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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/mcp",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.21",
4
4
  "description": "Model Context Protocol server exposing a Lunora deployment to AI agents",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -28,7 +28,7 @@
28
28
  "lunora-mcp": "./dist/bin.mjs"
29
29
  },
30
30
  "files": [
31
- "dist",
31
+ "./dist",
32
32
  "__assets__",
33
33
  "README.md",
34
34
  "LICENSE.md"
@@ -49,7 +49,9 @@
49
49
  "access": "public"
50
50
  },
51
51
  "dependencies": {
52
- "@lunora/client": "1.0.0-alpha.2",
52
+ "@lunora/client": "1.0.0-alpha.20",
53
+ "@lunora/errors": "1.0.0-alpha.3",
54
+ "@lunora/x402": "1.0.0-alpha.1",
53
55
  "@modelcontextprotocol/sdk": "^1.29.0"
54
56
  },
55
57
  "engines": {