@lunora/mcp 1.0.0-alpha.6 → 1.0.0-alpha.60

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/index.d.mts CHANGED
@@ -1,30 +1,302 @@
1
1
  import { LunoraClient } from '@lunora/client';
2
+ import { T as ToolDefinition, f as ToolResult, e as ToolInputSchema, a as McpFetchHandler, b as McpTool } from "./packem_shared/serve-stateless.d-DX0di_lv.mjs";
3
+ export { type d as McpServerInfo, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-DX0di_lv.mjs";
2
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
6
+ import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
7
+ /** The read-only tool surface: introspection + query. Always exposed. */
8
+ declare const READ_ONLY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
9
+ /** The write tool surface (mutations + actions). Exposed ONLY when writes are enabled. */
10
+ declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
11
+ /**
12
+ * The tools this server advertises. When `allowWrites` is false (the default),
13
+ * only the read-only surface is exposed — the mutation/action tools are omitted
14
+ * from `ListTools` entirely, so an AI agent can't invoke a write it can't see.
15
+ */
16
+ declare const toolDefinitions: (allowWrites: boolean) => ReadonlyArray<ToolDefinition>;
17
+ /**
18
+ * Dispatch a tool call against `client`. Unknown tools and thrown errors are
19
+ * returned as `isError` results (rather than rejections) so the calling model
20
+ * sees the failure as tool output, per the MCP convention.
21
+ *
22
+ * `allowWrites` gates the mutation/action tools: when false (the default) a call
23
+ * to a write tool is refused even if the client somehow names it, so the
24
+ * read-only guarantee holds at dispatch, not just in the advertised tool list.
25
+ */
26
+ declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean) => Promise<ToolResult>;
27
+ /**
28
+ * Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
29
+ * MCP tool an external agent can call. The capability boundary is the MCP-server
30
+ * process + its token, so WHICH agents are exposed is config on the server (like
31
+ * `allowWrites`), not on `defineAgent` — keeping `@lunora/agent` codegen
32
+ * byte-identical.
33
+ */
34
+ interface McpAgentExposure {
35
+ /** What the agent does — shown to the calling model, which decides from it. */
36
+ description: string;
37
+ /** The agent's export name (its `ctx.agents.<name>` / `AGENT_<NAME>` binding). */
38
+ name: string;
39
+ /** Override the model-facing tool name (default `agent_<name>`). */
40
+ toolName?: string;
41
+ }
42
+ /** The generic status/poll tool advertised alongside the per-agent tools. */
43
+ declare const AGENT_STATUS_TOOL_NAME = "lunora_agent_status";
44
+ /**
45
+ * The uniform input schema every agent tool advertises. Agents share ONE run
46
+ * input (`@lunora/agent` has no per-agent validator), so there is nothing to
47
+ * derive per agent — a single static schema is reused for every agent tool.
48
+ */
49
+ declare const AGENT_RUN_INPUT_SCHEMA: ToolInputSchema;
50
+ /**
51
+ * Parse `LUNORA_MCP_AGENTS` — a `;`-separated list of `name:description` pairs,
52
+ * e.g. `"support:Handles support questions;billing:Billing help"`. The
53
+ * description may itself contain colons (only the FIRST colon splits). Blank
54
+ * entries and entries with an empty name/description are skipped.
55
+ */
56
+ declare const parseAgentsEnv: (raw: string | undefined) => McpAgentExposure[];
57
+ /**
58
+ * The tools this module advertises. Fail-closed: only the boolean `true` opts
59
+ * in (an env-plumbed caller could pass a truthy string), and the tools appear
60
+ * ONLY when at least one agent is exposed — so an agent-free or non-opted-in
61
+ * server never lists them.
62
+ */
63
+ declare const agentToolDefinitions: (exposures: ReadonlyArray<McpAgentExposure>, allowAgents: boolean) => ReadonlyArray<ToolDefinition>;
64
+ /** Options threaded into a single agent tool dispatch. */
65
+ interface CallAgentToolOptions {
66
+ /** Opt-in gate — must be exactly `true` or the call is refused fail-closed. */
67
+ allowAgents: boolean;
68
+ /** The exposures advertised by this server. */
69
+ exposures: ReadonlyArray<McpAgentExposure>;
70
+ /** Wall-clock budget a single call awaits before returning a pending result. */
71
+ maxWaitMs?: number;
72
+ /** Delay between thread-status polls. */
73
+ pollIntervalMs?: number;
74
+ /** Test seam replacing the between-poll wait; production uses a real timer. */
75
+ wait?: (ms: number) => Promise<void>;
76
+ }
77
+ /**
78
+ * Dispatch an agent tool call: start a durable run via `agents:agentRun`, then
79
+ * await-with-timeout — poll `agents:agentThread` until terminal (returning the
80
+ * final answer from `agents:agentMessages`) or, on budget exhaustion, return a
81
+ * NON-error pending payload the caller resumes with `lunora_agent_status`.
82
+ *
83
+ * Fail-closed: refused at dispatch unless `allowAgents === true`, mirroring the
84
+ * `allowWrites` guard — starting a run is a side effect and must not ride the
85
+ * read-only default.
86
+ */
87
+ declare const callAgentTool: (client: LunoraClient, name: string, input: Record<string, unknown>, options: CallAgentToolOptions) => Promise<ToolResult>;
3
88
  interface LunoraMcpServerOptions {
89
+ /** Wall-clock budget a single agent tool call awaits before returning a pending result. */
90
+ agentMaxWaitMs?: number;
91
+ /** Delay between agent thread-status polls. */
92
+ agentPollIntervalMs?: number;
93
+ /** The agents this server fronts as MCP tools (see `allowAgents`). */
94
+ agents?: ReadonlyArray<McpAgentExposure>;
95
+ /**
96
+ * Expose the per-agent tools (`agent_<name>` + the generic
97
+ * `lunora_agent_status`). Defaults to `false`, mirroring `allowWrites`:
98
+ * starting a durable agent run is a side effect, so the agent tools are
99
+ * omitted from the advertised list AND refused at dispatch unless explicitly
100
+ * opted in. Only takes effect together with a non-empty `agents` list.
101
+ */
102
+ allowAgents?: boolean;
103
+ /**
104
+ * Expose the write tools (`lunora_run_mutation` / `lunora_run_action`).
105
+ * Defaults to `false`: the server is READ-ONLY unless explicitly opted in,
106
+ * so a prompt-injected or misaligned agent can't mutate the deployment with
107
+ * the configured token. When false the write tools are omitted from the
108
+ * advertised tool list AND refused at dispatch.
109
+ */
110
+ allowWrites?: boolean;
111
+ /**
112
+ * Pre-built client (test injection). When omitted a `LunoraClient` is
113
+ * created from `url`/`token`/`fetch`.
114
+ */
4
115
  client?: LunoraClient;
116
+ /** `fetch` implementation; defaults to the ambient global. */
5
117
  fetch?: typeof fetch;
118
+ /**
119
+ * Bearer token sent on every RPC. This must be the deployment's **admin
120
+ * bearer**: the introspection/allowlist path every tool depends on
121
+ * (`lunora_list_functions`, `lunora_list_tables`, and the `assertRunnable`
122
+ * precheck that runs before every `run` tool) hits admin-gated
123
+ * `/_lunora/admin/*` routes, so no scoped/app token works today — it would
124
+ * 403 (`ADMIN_FORBIDDEN`) on the first tool call. The read-only guarantee is
125
+ * therefore NOT enforced by the token's scope; it is enforced in-process via
126
+ * `allowWrites: false` (the default), which omits the write tools from the
127
+ * advertised list and refuses them at dispatch.
128
+ */
6
129
  token?: string;
130
+ /** Base URL of the deployed Lunora Worker. Required unless `client` is given. */
7
131
  url?: string;
8
132
  }
133
+ /**
134
+ * Build an MCP `Server` whose tools talk to a Lunora deployment. The server is
135
+ * transport-agnostic — call `.connect(transport)` yourself, or use
136
+ * `connectStdio` for the common stdio case.
137
+ *
138
+ * Tool calls are dispatched through `callTool`, which the deployment reaches
139
+ * over HTTP RPC. No WebSocket is opened (the tools never subscribe), so this is
140
+ * safe to run as a short-lived stdio process.
141
+ */
9
142
  declare const createLunoraMcpServer: (options: LunoraMcpServerOptions) => Server;
143
+ /**
144
+ * Build the server and connect it over stdio — the transport MCP clients use
145
+ * when they spawn the `lunora-mcp` binary. Resolves once the transport is
146
+ * connected; the process then stays alive serving requests.
147
+ */
10
148
  declare const connectStdio: (options: LunoraMcpServerOptions) => Promise<Server>;
11
- interface ToolInputSchema {
12
- properties: Record<string, unknown>;
13
- required?: ReadonlyArray<string>;
14
- type: "object";
149
+ /**
150
+ * Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
151
+ * invocation constructs a fresh proxy server and serves the request through
152
+ * {@link serveStateless}.
153
+ */
154
+ declare const createMcpFetchHandler: (options: LunoraMcpServerOptions) => McpFetchHandler;
155
+ /** A Lunora deployment the tools dispatch against. */
156
+ interface LocalDeployment {
157
+ token?: string;
158
+ url: string;
15
159
  }
16
- interface ToolDefinition {
160
+ /**
161
+ * Where the deployment comes from: a fixed value, or a function consulted on
162
+ * every tool call.
163
+ *
164
+ * The resolver form exists because an editor spawns this server when the
165
+ * project opens — routinely *before* `lunora dev` is running, and it keeps the
166
+ * process alive across every restart afterwards. A URL captured once at startup
167
+ * would therefore be absent for the entire first session and stale after the
168
+ * first restart.
169
+ */
170
+ type LocalDeploymentSource = (() => LocalDeployment | undefined) | LocalDeployment;
171
+ interface LocalMcpServerOptions {
172
+ /**
173
+ * Expose the deployment write tools (`lunora_run_mutation` /
174
+ * `lunora_run_action`). Defaults to `false` — the same fail-closed default
175
+ * as the remote server. Locally the blast radius is dev data rather than
176
+ * production, but a mutation is still a side effect an agent should be
177
+ * granted deliberately.
178
+ */
179
+ allowWrites?: boolean;
180
+ /**
181
+ * The Lunora deployment (usually the running dev server) to expose. Omit to
182
+ * leave the deployment tools out entirely.
183
+ */
184
+ deployment?: LocalDeploymentSource;
185
+ /** Docs site origin backing the documentation tools; `false` omits them. */
186
+ docs?: false | {
187
+ baseUrl?: string;
188
+ };
189
+ /** Extra tools to compose in, e.g. the CLI's local dev-server tools. */
190
+ extraTools?: ReadonlyArray<McpTool>;
191
+ /** `fetch` implementation; defaults to the ambient global. */
192
+ fetch?: typeof fetch;
193
+ /** Version reported in the MCP handshake — the host CLI's, not this package's. */
194
+ version?: string;
195
+ }
196
+ /** Server identity advertised in the MCP `initialize` handshake. */
197
+ declare const LOCAL_SERVER_NAME = "lunora";
198
+ /** Shown when a deployment tool is called and no dev server can be found. */
199
+ declare const NO_DEPLOYMENT_MESSAGE = "no Lunora dev server is running for this project — start one with `lunora dev`, then call this tool again (call lunora_dev_status to check).";
200
+ /**
201
+ * Assemble the tool list, in the order it is advertised: docs first (the
202
+ * surface that always works), then the caller's extras, then the deployment
203
+ * tools. Order also decides precedence — `createToolServer` keeps the first
204
+ * registration of a duplicated name.
205
+ *
206
+ * `clientFor` is the shared client cache built once by
207
+ * {@link createLocalMcpServer} and threaded into both the tool and resource
208
+ * surfaces. Exported (and called directly by tests) without going through
209
+ * `createLocalMcpServer`, so a caller that omits it gets a private,
210
+ * call-scoped cache — same shape as before this surface was shared, just
211
+ * without the cross-surface sharing that only matters once a resource
212
+ * surface exists alongside it.
213
+ */
214
+ declare const localTools: (options: LocalMcpServerOptions, clientFor?: (deployment: LocalDeployment) => LunoraClient) => ReadonlyArray<McpTool>;
215
+ /** Build the composed local server without connecting a transport. */
216
+ declare const createLocalMcpServer: (options?: LocalMcpServerOptions) => Server;
217
+ /**
218
+ * Build the composed local server and connect it over stdio — the transport an
219
+ * MCP client uses when it spawns `lunora mcp serve`. Resolves once connected;
220
+ * the process then stays alive serving requests.
221
+ */
222
+ declare const connectLocalStdio: (options?: LocalMcpServerOptions) => Promise<Server>;
223
+ /** A tool handler: receives the call's `arguments` bag, returns an MCP tool result. */
224
+ /**
225
+ * The x402 vocabulary this module needs, declared here rather than imported.
226
+ *
227
+ * The x402 package is an optional peer, and a type import from its `charge`
228
+ * entry puts its `.d.ts` back into this package's build graph:
229
+ * a consumer that never installs x402 never builds it either, so the dts bundler
230
+ * looks for a `dist/` that does not exist and fails. That is not hypothetical —
231
+ * it broke the docs site build, which runs a filtered build over the docs app and its dependency closure
232
+ * and therefore never builds x402.
233
+ *
234
+ * Declaring them locally is safe because this module never *inspects* a charge
235
+ * config; it forwards it whole to `createChargeMiddleware`. The index signature
236
+ * keeps a real charge config assignable as x402 grows fields.
237
+ */
238
+ /** Mirrors x402's `X402Price` — a decimal string like `"$0.05"`, or a number. */
239
+ type X402Price = number | string;
240
+ /** Mirrors x402's charge config with the per-tool price omitted. */
241
+ interface X402ChargeSettings {
242
+ /** Network this resource settles on. */
243
+ readonly network: string;
244
+ /** Everything else x402 accepts, forwarded untouched. */
245
+ readonly [key: string]: unknown;
246
+ /** Payout wallet(s), per network family. */
247
+ readonly recipient: {
248
+ readonly evm?: string;
249
+ readonly svm?: string;
250
+ };
251
+ }
252
+ type ToolHandler = (arguments_: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
253
+ /** Registration shape for a free tool. */
254
+ interface RegisterToolOptions {
255
+ /** Optional MCP tool annotations (`readOnlyHint`, `title`, …). */
256
+ annotations?: Tool["annotations"];
257
+ /** Human/model-facing description of what the tool does. */
17
258
  description: string;
259
+ /** JSON-Schema object describing the tool's arguments. */
18
260
  inputSchema: ToolInputSchema;
261
+ /** Unique tool name (the MCP `tools/call` `name`). */
19
262
  name: string;
20
263
  }
21
- interface ToolResult {
22
- content: {
23
- text: string;
24
- type: "text";
25
- }[];
26
- isError?: boolean;
264
+ /** Registration shape for a paid tool: a {@link RegisterToolOptions} plus its USD price. */
265
+ interface RegisterPaidToolOptions extends RegisterToolOptions {
266
+ /** USD price per call (e.g. `"$0.05"`), charged via x402 before dispatch. */
267
+ price: X402Price;
268
+ }
269
+ /** x402 settlement vocabulary shared by every paid tool (network, recipient, facilitator); price is per-tool. */
270
+ type PaidMcpChargeConfig = X402ChargeSettings;
271
+ /** Config for `createPaidMcpServer`. */
272
+ interface PaidMcpServerConfig {
273
+ /** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
274
+ charge: PaidMcpChargeConfig;
275
+ /** Name/version advertised in the MCP `initialize` handshake. Defaults to `lunora-paid-mcp`. */
276
+ serverInfo?: {
277
+ name: string;
278
+ version: string;
279
+ };
280
+ }
281
+ /** A paid MCP server: register free/paid tools, then serve over Streamable HTTP. */
282
+ interface PaidMcpServer {
283
+ /** The Streamable-HTTP fetch handler; gates each paid `tools/call` behind x402. */
284
+ readonly fetchHandler: McpFetchHandler;
285
+ /** Register a **paid** tool: its dispatch runs the x402 charge middleware first. */
286
+ paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
287
+ /** Register a **free** tool (coexists with paid tools on the same server). */
288
+ tool: (options: RegisterToolOptions, handler: ToolHandler) => void;
27
289
  }
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 };
290
+ /**
291
+ * Create a paid MCP server. Register free tools with `tool()` and priced tools
292
+ * with `paidTool()` (they coexist), then serve `fetchHandler` over HTTP.
293
+ *
294
+ * The server is **stateless**: `fetchHandler` builds a fresh `Server` per
295
+ * request (reading the live tool registry), so tools registered before the
296
+ * first request are all visible. Each priced tool memoises one initialised
297
+ * `ChargeMiddleware` (keyed by tool name, baking that tool's price and naming
298
+ * the tool as the challenge `resource`); a failed init is not cached, so a
299
+ * transient facilitator outage retries on the next call.
300
+ */
301
+ declare const createPaidMcpServer: (config: PaidMcpServerConfig) => PaidMcpServer;
302
+ export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDeploymentSource, type LocalMcpServerOptions, type LunoraMcpServerOptions, type McpAgentExposure, type McpFetchHandler, type McpTool, NO_DEPLOYMENT_MESSAGE, 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, agentToolDefinitions, callAgentTool, callTool, connectLocalStdio, connectStdio, createLocalMcpServer, createLunoraMcpServer, createMcpFetchHandler, createPaidMcpServer, localTools, parseAgentsEnv, toolDefinitions };
package/dist/index.d.ts CHANGED
@@ -1,30 +1,302 @@
1
1
  import { LunoraClient } from '@lunora/client';
2
+ import { T as ToolDefinition, f as ToolResult, e as ToolInputSchema, a as McpFetchHandler, b as McpTool } from "./packem_shared/serve-stateless.d-DX0di_lv.js";
3
+ export { type d as McpServerInfo, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-DX0di_lv.js";
2
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
6
+ import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
7
+ /** The read-only tool surface: introspection + query. Always exposed. */
8
+ declare const READ_ONLY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
9
+ /** The write tool surface (mutations + actions). Exposed ONLY when writes are enabled. */
10
+ declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
11
+ /**
12
+ * The tools this server advertises. When `allowWrites` is false (the default),
13
+ * only the read-only surface is exposed — the mutation/action tools are omitted
14
+ * from `ListTools` entirely, so an AI agent can't invoke a write it can't see.
15
+ */
16
+ declare const toolDefinitions: (allowWrites: boolean) => ReadonlyArray<ToolDefinition>;
17
+ /**
18
+ * Dispatch a tool call against `client`. Unknown tools and thrown errors are
19
+ * returned as `isError` results (rather than rejections) so the calling model
20
+ * sees the failure as tool output, per the MCP convention.
21
+ *
22
+ * `allowWrites` gates the mutation/action tools: when false (the default) a call
23
+ * to a write tool is refused even if the client somehow names it, so the
24
+ * read-only guarantee holds at dispatch, not just in the advertised tool list.
25
+ */
26
+ declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean) => Promise<ToolResult>;
27
+ /**
28
+ * Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
29
+ * MCP tool an external agent can call. The capability boundary is the MCP-server
30
+ * process + its token, so WHICH agents are exposed is config on the server (like
31
+ * `allowWrites`), not on `defineAgent` — keeping `@lunora/agent` codegen
32
+ * byte-identical.
33
+ */
34
+ interface McpAgentExposure {
35
+ /** What the agent does — shown to the calling model, which decides from it. */
36
+ description: string;
37
+ /** The agent's export name (its `ctx.agents.<name>` / `AGENT_<NAME>` binding). */
38
+ name: string;
39
+ /** Override the model-facing tool name (default `agent_<name>`). */
40
+ toolName?: string;
41
+ }
42
+ /** The generic status/poll tool advertised alongside the per-agent tools. */
43
+ declare const AGENT_STATUS_TOOL_NAME = "lunora_agent_status";
44
+ /**
45
+ * The uniform input schema every agent tool advertises. Agents share ONE run
46
+ * input (`@lunora/agent` has no per-agent validator), so there is nothing to
47
+ * derive per agent — a single static schema is reused for every agent tool.
48
+ */
49
+ declare const AGENT_RUN_INPUT_SCHEMA: ToolInputSchema;
50
+ /**
51
+ * Parse `LUNORA_MCP_AGENTS` — a `;`-separated list of `name:description` pairs,
52
+ * e.g. `"support:Handles support questions;billing:Billing help"`. The
53
+ * description may itself contain colons (only the FIRST colon splits). Blank
54
+ * entries and entries with an empty name/description are skipped.
55
+ */
56
+ declare const parseAgentsEnv: (raw: string | undefined) => McpAgentExposure[];
57
+ /**
58
+ * The tools this module advertises. Fail-closed: only the boolean `true` opts
59
+ * in (an env-plumbed caller could pass a truthy string), and the tools appear
60
+ * ONLY when at least one agent is exposed — so an agent-free or non-opted-in
61
+ * server never lists them.
62
+ */
63
+ declare const agentToolDefinitions: (exposures: ReadonlyArray<McpAgentExposure>, allowAgents: boolean) => ReadonlyArray<ToolDefinition>;
64
+ /** Options threaded into a single agent tool dispatch. */
65
+ interface CallAgentToolOptions {
66
+ /** Opt-in gate — must be exactly `true` or the call is refused fail-closed. */
67
+ allowAgents: boolean;
68
+ /** The exposures advertised by this server. */
69
+ exposures: ReadonlyArray<McpAgentExposure>;
70
+ /** Wall-clock budget a single call awaits before returning a pending result. */
71
+ maxWaitMs?: number;
72
+ /** Delay between thread-status polls. */
73
+ pollIntervalMs?: number;
74
+ /** Test seam replacing the between-poll wait; production uses a real timer. */
75
+ wait?: (ms: number) => Promise<void>;
76
+ }
77
+ /**
78
+ * Dispatch an agent tool call: start a durable run via `agents:agentRun`, then
79
+ * await-with-timeout — poll `agents:agentThread` until terminal (returning the
80
+ * final answer from `agents:agentMessages`) or, on budget exhaustion, return a
81
+ * NON-error pending payload the caller resumes with `lunora_agent_status`.
82
+ *
83
+ * Fail-closed: refused at dispatch unless `allowAgents === true`, mirroring the
84
+ * `allowWrites` guard — starting a run is a side effect and must not ride the
85
+ * read-only default.
86
+ */
87
+ declare const callAgentTool: (client: LunoraClient, name: string, input: Record<string, unknown>, options: CallAgentToolOptions) => Promise<ToolResult>;
3
88
  interface LunoraMcpServerOptions {
89
+ /** Wall-clock budget a single agent tool call awaits before returning a pending result. */
90
+ agentMaxWaitMs?: number;
91
+ /** Delay between agent thread-status polls. */
92
+ agentPollIntervalMs?: number;
93
+ /** The agents this server fronts as MCP tools (see `allowAgents`). */
94
+ agents?: ReadonlyArray<McpAgentExposure>;
95
+ /**
96
+ * Expose the per-agent tools (`agent_<name>` + the generic
97
+ * `lunora_agent_status`). Defaults to `false`, mirroring `allowWrites`:
98
+ * starting a durable agent run is a side effect, so the agent tools are
99
+ * omitted from the advertised list AND refused at dispatch unless explicitly
100
+ * opted in. Only takes effect together with a non-empty `agents` list.
101
+ */
102
+ allowAgents?: boolean;
103
+ /**
104
+ * Expose the write tools (`lunora_run_mutation` / `lunora_run_action`).
105
+ * Defaults to `false`: the server is READ-ONLY unless explicitly opted in,
106
+ * so a prompt-injected or misaligned agent can't mutate the deployment with
107
+ * the configured token. When false the write tools are omitted from the
108
+ * advertised tool list AND refused at dispatch.
109
+ */
110
+ allowWrites?: boolean;
111
+ /**
112
+ * Pre-built client (test injection). When omitted a `LunoraClient` is
113
+ * created from `url`/`token`/`fetch`.
114
+ */
4
115
  client?: LunoraClient;
116
+ /** `fetch` implementation; defaults to the ambient global. */
5
117
  fetch?: typeof fetch;
118
+ /**
119
+ * Bearer token sent on every RPC. This must be the deployment's **admin
120
+ * bearer**: the introspection/allowlist path every tool depends on
121
+ * (`lunora_list_functions`, `lunora_list_tables`, and the `assertRunnable`
122
+ * precheck that runs before every `run` tool) hits admin-gated
123
+ * `/_lunora/admin/*` routes, so no scoped/app token works today — it would
124
+ * 403 (`ADMIN_FORBIDDEN`) on the first tool call. The read-only guarantee is
125
+ * therefore NOT enforced by the token's scope; it is enforced in-process via
126
+ * `allowWrites: false` (the default), which omits the write tools from the
127
+ * advertised list and refuses them at dispatch.
128
+ */
6
129
  token?: string;
130
+ /** Base URL of the deployed Lunora Worker. Required unless `client` is given. */
7
131
  url?: string;
8
132
  }
133
+ /**
134
+ * Build an MCP `Server` whose tools talk to a Lunora deployment. The server is
135
+ * transport-agnostic — call `.connect(transport)` yourself, or use
136
+ * `connectStdio` for the common stdio case.
137
+ *
138
+ * Tool calls are dispatched through `callTool`, which the deployment reaches
139
+ * over HTTP RPC. No WebSocket is opened (the tools never subscribe), so this is
140
+ * safe to run as a short-lived stdio process.
141
+ */
9
142
  declare const createLunoraMcpServer: (options: LunoraMcpServerOptions) => Server;
143
+ /**
144
+ * Build the server and connect it over stdio — the transport MCP clients use
145
+ * when they spawn the `lunora-mcp` binary. Resolves once the transport is
146
+ * connected; the process then stays alive serving requests.
147
+ */
10
148
  declare const connectStdio: (options: LunoraMcpServerOptions) => Promise<Server>;
11
- interface ToolInputSchema {
12
- properties: Record<string, unknown>;
13
- required?: ReadonlyArray<string>;
14
- type: "object";
149
+ /**
150
+ * Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
151
+ * invocation constructs a fresh proxy server and serves the request through
152
+ * {@link serveStateless}.
153
+ */
154
+ declare const createMcpFetchHandler: (options: LunoraMcpServerOptions) => McpFetchHandler;
155
+ /** A Lunora deployment the tools dispatch against. */
156
+ interface LocalDeployment {
157
+ token?: string;
158
+ url: string;
15
159
  }
16
- interface ToolDefinition {
160
+ /**
161
+ * Where the deployment comes from: a fixed value, or a function consulted on
162
+ * every tool call.
163
+ *
164
+ * The resolver form exists because an editor spawns this server when the
165
+ * project opens — routinely *before* `lunora dev` is running, and it keeps the
166
+ * process alive across every restart afterwards. A URL captured once at startup
167
+ * would therefore be absent for the entire first session and stale after the
168
+ * first restart.
169
+ */
170
+ type LocalDeploymentSource = (() => LocalDeployment | undefined) | LocalDeployment;
171
+ interface LocalMcpServerOptions {
172
+ /**
173
+ * Expose the deployment write tools (`lunora_run_mutation` /
174
+ * `lunora_run_action`). Defaults to `false` — the same fail-closed default
175
+ * as the remote server. Locally the blast radius is dev data rather than
176
+ * production, but a mutation is still a side effect an agent should be
177
+ * granted deliberately.
178
+ */
179
+ allowWrites?: boolean;
180
+ /**
181
+ * The Lunora deployment (usually the running dev server) to expose. Omit to
182
+ * leave the deployment tools out entirely.
183
+ */
184
+ deployment?: LocalDeploymentSource;
185
+ /** Docs site origin backing the documentation tools; `false` omits them. */
186
+ docs?: false | {
187
+ baseUrl?: string;
188
+ };
189
+ /** Extra tools to compose in, e.g. the CLI's local dev-server tools. */
190
+ extraTools?: ReadonlyArray<McpTool>;
191
+ /** `fetch` implementation; defaults to the ambient global. */
192
+ fetch?: typeof fetch;
193
+ /** Version reported in the MCP handshake — the host CLI's, not this package's. */
194
+ version?: string;
195
+ }
196
+ /** Server identity advertised in the MCP `initialize` handshake. */
197
+ declare const LOCAL_SERVER_NAME = "lunora";
198
+ /** Shown when a deployment tool is called and no dev server can be found. */
199
+ declare const NO_DEPLOYMENT_MESSAGE = "no Lunora dev server is running for this project — start one with `lunora dev`, then call this tool again (call lunora_dev_status to check).";
200
+ /**
201
+ * Assemble the tool list, in the order it is advertised: docs first (the
202
+ * surface that always works), then the caller's extras, then the deployment
203
+ * tools. Order also decides precedence — `createToolServer` keeps the first
204
+ * registration of a duplicated name.
205
+ *
206
+ * `clientFor` is the shared client cache built once by
207
+ * {@link createLocalMcpServer} and threaded into both the tool and resource
208
+ * surfaces. Exported (and called directly by tests) without going through
209
+ * `createLocalMcpServer`, so a caller that omits it gets a private,
210
+ * call-scoped cache — same shape as before this surface was shared, just
211
+ * without the cross-surface sharing that only matters once a resource
212
+ * surface exists alongside it.
213
+ */
214
+ declare const localTools: (options: LocalMcpServerOptions, clientFor?: (deployment: LocalDeployment) => LunoraClient) => ReadonlyArray<McpTool>;
215
+ /** Build the composed local server without connecting a transport. */
216
+ declare const createLocalMcpServer: (options?: LocalMcpServerOptions) => Server;
217
+ /**
218
+ * Build the composed local server and connect it over stdio — the transport an
219
+ * MCP client uses when it spawns `lunora mcp serve`. Resolves once connected;
220
+ * the process then stays alive serving requests.
221
+ */
222
+ declare const connectLocalStdio: (options?: LocalMcpServerOptions) => Promise<Server>;
223
+ /** A tool handler: receives the call's `arguments` bag, returns an MCP tool result. */
224
+ /**
225
+ * The x402 vocabulary this module needs, declared here rather than imported.
226
+ *
227
+ * The x402 package is an optional peer, and a type import from its `charge`
228
+ * entry puts its `.d.ts` back into this package's build graph:
229
+ * a consumer that never installs x402 never builds it either, so the dts bundler
230
+ * looks for a `dist/` that does not exist and fails. That is not hypothetical —
231
+ * it broke the docs site build, which runs a filtered build over the docs app and its dependency closure
232
+ * and therefore never builds x402.
233
+ *
234
+ * Declaring them locally is safe because this module never *inspects* a charge
235
+ * config; it forwards it whole to `createChargeMiddleware`. The index signature
236
+ * keeps a real charge config assignable as x402 grows fields.
237
+ */
238
+ /** Mirrors x402's `X402Price` — a decimal string like `"$0.05"`, or a number. */
239
+ type X402Price = number | string;
240
+ /** Mirrors x402's charge config with the per-tool price omitted. */
241
+ interface X402ChargeSettings {
242
+ /** Network this resource settles on. */
243
+ readonly network: string;
244
+ /** Everything else x402 accepts, forwarded untouched. */
245
+ readonly [key: string]: unknown;
246
+ /** Payout wallet(s), per network family. */
247
+ readonly recipient: {
248
+ readonly evm?: string;
249
+ readonly svm?: string;
250
+ };
251
+ }
252
+ type ToolHandler = (arguments_: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
253
+ /** Registration shape for a free tool. */
254
+ interface RegisterToolOptions {
255
+ /** Optional MCP tool annotations (`readOnlyHint`, `title`, …). */
256
+ annotations?: Tool["annotations"];
257
+ /** Human/model-facing description of what the tool does. */
17
258
  description: string;
259
+ /** JSON-Schema object describing the tool's arguments. */
18
260
  inputSchema: ToolInputSchema;
261
+ /** Unique tool name (the MCP `tools/call` `name`). */
19
262
  name: string;
20
263
  }
21
- interface ToolResult {
22
- content: {
23
- text: string;
24
- type: "text";
25
- }[];
26
- isError?: boolean;
264
+ /** Registration shape for a paid tool: a {@link RegisterToolOptions} plus its USD price. */
265
+ interface RegisterPaidToolOptions extends RegisterToolOptions {
266
+ /** USD price per call (e.g. `"$0.05"`), charged via x402 before dispatch. */
267
+ price: X402Price;
268
+ }
269
+ /** x402 settlement vocabulary shared by every paid tool (network, recipient, facilitator); price is per-tool. */
270
+ type PaidMcpChargeConfig = X402ChargeSettings;
271
+ /** Config for `createPaidMcpServer`. */
272
+ interface PaidMcpServerConfig {
273
+ /** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
274
+ charge: PaidMcpChargeConfig;
275
+ /** Name/version advertised in the MCP `initialize` handshake. Defaults to `lunora-paid-mcp`. */
276
+ serverInfo?: {
277
+ name: string;
278
+ version: string;
279
+ };
280
+ }
281
+ /** A paid MCP server: register free/paid tools, then serve over Streamable HTTP. */
282
+ interface PaidMcpServer {
283
+ /** The Streamable-HTTP fetch handler; gates each paid `tools/call` behind x402. */
284
+ readonly fetchHandler: McpFetchHandler;
285
+ /** Register a **paid** tool: its dispatch runs the x402 charge middleware first. */
286
+ paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
287
+ /** Register a **free** tool (coexists with paid tools on the same server). */
288
+ tool: (options: RegisterToolOptions, handler: ToolHandler) => void;
27
289
  }
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 };
290
+ /**
291
+ * Create a paid MCP server. Register free tools with `tool()` and priced tools
292
+ * with `paidTool()` (they coexist), then serve `fetchHandler` over HTTP.
293
+ *
294
+ * The server is **stateless**: `fetchHandler` builds a fresh `Server` per
295
+ * request (reading the live tool registry), so tools registered before the
296
+ * first request are all visible. Each priced tool memoises one initialised
297
+ * `ChargeMiddleware` (keyed by tool name, baking that tool's price and naming
298
+ * the tool as the challenge `resource`); a failed init is not cached, so a
299
+ * transient facilitator outage retries on the next call.
300
+ */
301
+ declare const createPaidMcpServer: (config: PaidMcpServerConfig) => PaidMcpServer;
302
+ export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDeploymentSource, type LocalMcpServerOptions, type LunoraMcpServerOptions, type McpAgentExposure, type McpFetchHandler, type McpTool, NO_DEPLOYMENT_MESSAGE, 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, agentToolDefinitions, callAgentTool, callTool, connectLocalStdio, connectStdio, createLocalMcpServer, createLunoraMcpServer, createMcpFetchHandler, createPaidMcpServer, localTools, parseAgentsEnv, toolDefinitions };
package/dist/index.mjs CHANGED
@@ -1,2 +1 @@
1
- export { connectStdio, createLunoraMcpServer } from './packem_shared/connectStdio-C_mvQBs2.mjs';
2
- export { TOOL_DEFINITIONS, callTool } from './packem_shared/TOOL_DEFINITIONS-Dpiu38ji.mjs';
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-ihZg6vIZ.mjs";import{createToolServer as E}from"./packem_shared/createToolServer-CP5bNUcS.mjs";import{createMcpFetchHandler as _}from"./packem_shared/createMcpFetchHandler-DjvXAX80.mjs";import{LOCAL_SERVER_NAME as p,NO_DEPLOYMENT_MESSAGE as N,connectLocalStdio as O,createLocalMcpServer as A,localTools as f}from"./packem_shared/LOCAL_SERVER_NAME-BoQv1iu8.mjs";import{createPaidMcpServer as i}from"./packem_shared/createPaidMcpServer-CwdcSPpN.mjs";import{connectStdio as m,createLunoraMcpServer as s}from"./packem_shared/connectStdio-Dt-DqEgA.mjs";import{READ_ONLY_TOOL_DEFINITIONS as I,WRITE_TOOL_DEFINITIONS as v,callTool as D,toolDefinitions as R}from"./packem_shared/READ_ONLY_TOOL_DEFINITIONS-48jMcpI-.mjs";import{serveStateless as g}from"./packem_shared/serveStateless-BqoRk-xU.mjs";export{r as AGENT_RUN_INPUT_SCHEMA,t as AGENT_STATUS_TOOL_NAME,p as LOCAL_SERVER_NAME,N as NO_DEPLOYMENT_MESSAGE,I as READ_ONLY_TOOL_DEFINITIONS,v as WRITE_TOOL_DEFINITIONS,c as agentToolDefinitions,T as callAgentTool,D as callTool,O as connectLocalStdio,m as connectStdio,A as createLocalMcpServer,s as createLunoraMcpServer,_ as createMcpFetchHandler,i as createPaidMcpServer,E as createToolServer,f as localTools,a as parseAgentsEnv,g as serveStateless,R 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};