@lunora/mcp 1.0.0-alpha.46 → 1.0.0-alpha.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md CHANGED
@@ -108,4 +108,124 @@ specific language governing permissions and limitations under the License.
108
108
  <!-- /DEPENDENCIES -->
109
109
 
110
110
  <!-- TYPE_DEPENDENCIES -->
111
+
112
+ # Licenses of bundled types
113
+ The published @lunora/mcp artifact additionally contains code with the following licenses:
114
+ FSL-1.1-Apache-2.0
115
+
116
+ # Bundled types:
117
+ ## @lunora/x402
118
+ License: FSL-1.1-Apache-2.0
119
+ By: Daniel Bannert
120
+ Repository: git+https://github.com/anolilab/lunora.git
121
+
122
+ > # Functional Source License, Version 1.1, Apache 2.0 Future License
123
+ >
124
+ > ## Abbreviation
125
+ >
126
+ > FSL-1.1-Apache-2.0
127
+ >
128
+ > ## Notice
129
+ >
130
+ > Copyright 2026 anolilab and contributors
131
+ >
132
+ > ## Terms and Conditions
133
+ >
134
+ > ### Licensor ("We")
135
+ >
136
+ > The party offering the Software under these Terms and Conditions.
137
+ >
138
+ > ### The Software
139
+ >
140
+ > The "Software" is each version of the software that we make available under
141
+ > these Terms and Conditions, as indicated by our inclusion of these Terms and
142
+ > Conditions with the Software.
143
+ >
144
+ > ### License Grant
145
+ >
146
+ > Subject to your compliance with this License Grant and the Patents,
147
+ > Redistribution and Trademark clauses below, we hereby grant you the right to
148
+ > use, copy, modify, create derivative works, publicly perform, publicly display
149
+ > and redistribute the Software for any Permitted Purpose identified below.
150
+ >
151
+ > ### Permitted Purpose
152
+ >
153
+ > A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
154
+ > means making the Software available to others in a commercial product or service
155
+ > that:
156
+ >
157
+ > 1. substitutes for the Software;
158
+ >
159
+ > 2. substitutes for any other product or service we offer using the Software that
160
+ > exists as of the date we make the Software available; or
161
+ >
162
+ > 3. offers the same or substantially similar functionality as the Software.
163
+ >
164
+ > Permitted Purposes specifically include using the Software:
165
+ >
166
+ > 1. for your internal use and access;
167
+ >
168
+ > 2. for non-commercial education;
169
+ >
170
+ > 3. for non-commercial research; and
171
+ >
172
+ > 4. in connection with professional services that you provide to a licensee using
173
+ > the Software in accordance with these Terms and Conditions.
174
+ >
175
+ > ### Patents
176
+ >
177
+ > To the extent your use for a Permitted Purpose would necessarily infringe our
178
+ > patents, the license grant above includes a license under our patents. If you
179
+ > make a claim against any party that the Software infringes or contributes to the
180
+ > infringement of any patent, then your patent license to the Software ends
181
+ > immediately.
182
+ >
183
+ > ### Redistribution
184
+ >
185
+ > The Terms and Conditions apply to all copies, modifications and derivatives of
186
+ > the Software.
187
+ >
188
+ > If you redistribute any copies, modifications or derivatives of the Software,
189
+ > you must include a copy of or a link to these Terms and Conditions and not
190
+ > remove any copyright notices provided in or with the Software.
191
+ >
192
+ > ### Disclaimer
193
+ >
194
+ > THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
195
+ > IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
196
+ > PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
197
+ >
198
+ > IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
199
+ > SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, EVEN
200
+ > IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
201
+ >
202
+ > ### Trademarks
203
+ >
204
+ > Except for displaying the License Details and identifying us as the origin of
205
+ > the Software, you have no right under these Terms and Conditions to use our
206
+ > trademarks, trade names, service marks or product names.
207
+ >
208
+ > ## Grant of Future License
209
+ >
210
+ > We hereby irrevocably grant you an additional license to use the Software under
211
+ > the Apache License, Version 2.0 that is effective on the second anniversary of
212
+ > the date we make the Software available. On or after that date, you may use the
213
+ > Software under the Apache License, Version 2.0, in which case the following will
214
+ > apply:
215
+ >
216
+ > Licensed under the Apache License, Version 2.0 (the "License"); you may not use
217
+ > this file except in compliance with the License.
218
+ >
219
+ > You may obtain a copy of the License at
220
+ >
221
+ > http://www.apache.org/licenses/LICENSE-2.0
222
+ >
223
+ > Unless required by applicable law or agreed to in writing, software distributed
224
+ > under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
225
+ > CONDITIONS OF ANY KIND, either express or implied. See the License for the
226
+ > specific language governing permissions and limitations under the License.
227
+ >
228
+ > <!-- DEPENDENCIES -->
229
+ > <!-- /DEPENDENCIES -->
230
+
111
231
  <!-- /TYPE_DEPENDENCIES -->
package/README.md CHANGED
@@ -34,7 +34,12 @@
34
34
 
35
35
  ---
36
36
 
37
- A [Model Context Protocol](https://modelcontextprotocol.io) server that exposes a deployed Lunora app to AI agents. It registers tools for introspecting a deployment (`lunora_list_functions`, `lunora_list_tables`, `lunora_get_function_schema`) and invoking its functions (`lunora_run_query`, `lunora_run_mutation`, `lunora_run_action`), each backed by `@lunora/client` over HTTP RPC.
37
+ [Model Context Protocol](https://modelcontextprotocol.io) servers for Lunora, in two flavours:
38
+
39
+ - **Deployment** (the main entry) — exposes a deployed Lunora app to AI agents: introspection tools (`lunora_list_functions`, `lunora_list_tables`, `lunora_get_function_schema`) and invocation tools (`lunora_run_query`, `lunora_run_mutation`, `lunora_run_action`), each backed by `@lunora/client` over HTTP RPC. Needs an admin token.
40
+ - **Documentation** ([`@lunora/mcp/docs`](#documentation-server)) — exposes the framework's _docs_ so an agent writing Lunora code can look up the real API instead of guessing. Credential-free, and safe to host publicly; Lunora runs it at `https://lunora.sh/mcp`.
41
+
42
+ Most users never install this package directly — `lunora mcp install` wires both servers into their editor. See [`@lunora/cli`](https://www.npmjs.com/package/@lunora/cli).
38
43
 
39
44
  Part of the [Lunora](https://github.com/anolilab/lunora) framework — a type-safe, real-time backend on Cloudflare Workers + Durable Objects with a Vite-first DX.
40
45
 
@@ -72,6 +77,8 @@ Part of the [Lunora](https://github.com/anolilab/lunora) framework — a type-sa
72
77
  npm install @lunora/mcp
73
78
  ```
74
79
 
80
+ Paid MCP tools (`createPaidMcpServer`) additionally need the optional peer [`@lunora/x402`](https://www.npmjs.com/package/@lunora/x402); it is loaded lazily, so installs that never charge for a tool don't pay for its dependency tree.
81
+
75
82
  ```sh
76
83
  yarn add @lunora/mcp
77
84
  ```
@@ -137,6 +144,72 @@ Each exposed agent gets an `agent_<name>` tool taking `prompt` (required), an op
137
144
 
138
145
  Runs are **owner-scoped** to the identity the configured token resolves to. Grant a **least-privilege** token mapped to a bot identity so an agent's threads stay isolated per deployment — never the admin token.
139
146
 
147
+ ## Resources and annotations
148
+
149
+ Both servers implement MCP **tools**; the documentation server additionally exposes every page as an MCP **resource** (`lunora-docs:/docs/…`, `text/markdown`), so a client can enumerate and attach a page directly instead of the model having to guess a search query first.
150
+
151
+ Every tool carries **annotations** — `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` and a human-facing `title` — so a client can badge the read-only surface and confirm before a write. They are hints; the actual guarantee is still made at dispatch, where a write tool is refused unless writes are enabled.
152
+
153
+ ## Documentation server
154
+
155
+ `@lunora/mcp/docs` is a second, independent surface: it serves **published documentation**, not a deployment. No credentials, no writes, no `@lunora/client` — and no Node built-ins, so it runs unchanged on Workers, Netlify/Vercel functions, Deno, and Bun.
156
+
157
+ | Tool | Description |
158
+ | -------------------- | --------------------------------------------------------------------- |
159
+ | `lunora_search_docs` | Search the docs; returns matching pages and sections with their URLs. |
160
+ | `lunora_get_doc` | Return one page in full, as Markdown. |
161
+ | `lunora_list_docs` | List every page with its title and description. |
162
+
163
+ The tools read a `DocsIndex`, which has two implementations. A docs site wires up its own in-process index and mounts the server as an HTTP route:
164
+
165
+ ```ts
166
+ import { createDocsMcpFetchHandler } from "@lunora/mcp/docs";
167
+
168
+ const handle = createDocsMcpFetchHandler({ index: myDocsIndex });
169
+
170
+ // e.g. in a Worker: export default { fetch: handle }
171
+ ```
172
+
173
+ Anything else reads a published site over HTTP:
174
+
175
+ ```ts
176
+ import { createDocsMcpServer, createRemoteDocsIndex } from "@lunora/mcp/docs";
177
+
178
+ const server = createDocsMcpServer({ index: createRemoteDocsIndex({ baseUrl: "https://lunora.sh" }) });
179
+ ```
180
+
181
+ Point a client at the hosted endpoint with no install at all:
182
+
183
+ ```sh
184
+ claude mcp add --transport http lunora-docs https://lunora.sh/mcp
185
+ ```
186
+
187
+ ### Hosting it safely
188
+
189
+ `createDocsMcpFetchHandler` screens each request before the transport sees it, because this surface is meant to be public and unauthenticated:
190
+
191
+ - **Bodies are capped** (128 KiB by default; override with `maxRequestBytes`).
192
+ - **JSON-RPC batches are refused.** The stateless transport buffers a whole batch's replies into one response, so a single small request carrying thousands of `tools/call` messages would amplify into hundreds of megabytes out, with no session to rate-limit against. A docs client gains nothing from batching.
193
+ - `lunora_search_docs` bounds its `query`, and `lunora_list_docs` caps how many pages it serialises.
194
+
195
+ ## Local development server
196
+
197
+ `createLocalMcpServer` / `connectLocalStdio` compose the docs tools, the deployment tools, and any extra tools a host supplies into one stdio server — this is what `lunora mcp serve` runs.
198
+
199
+ ```ts
200
+ import { connectLocalStdio } from "@lunora/mcp";
201
+
202
+ await connectLocalStdio({
203
+ // Consulted per tool call, so a dev server started later is picked up
204
+ // without reconnecting.
205
+ deployment: () => readMyDevServer(),
206
+ docs: { baseUrl: "https://lunora.sh" },
207
+ extraTools: myLocalTools,
208
+ });
209
+ ```
210
+
211
+ The deployment tools are advertised even when the resolver currently returns nothing — MCP clients cache the tool list, so a surface that appeared only when the dev server happened to be up would stay invisible for the rest of the session. Calling one with nothing running returns an actionable error instead.
212
+
140
213
  > This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs)**.
141
214
 
142
215
  ## Related
package/dist/bin.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
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
2
+ import{parseAgentsEnv as A}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-ihZg6vIZ.mjs";import{connectStdio as N}from"./packem_shared/connectStdio-Dt-DqEgA.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
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
4
  `),new a(`failed to start — ${_}`,1)}};try{await l(process.env)}catch(r){process.exit(r instanceof a?r.code:1)}
@@ -0,0 +1,150 @@
1
+ import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-DX0di_lv.mjs";
2
+ export type { c as McpResourceSummary, d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-DX0di_lv.mjs";
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
5
+ /**
6
+ * The documentation source the docs tools read.
7
+ *
8
+ * The tools are written against this interface rather than against a concrete
9
+ * search engine so the same tool surface can be backed two ways: in-process by
10
+ * the docs site itself (fumadocs `source` + its Orama index), or over HTTP by a
11
+ * remote reader that only has the published site (see `./remote-index`). Both
12
+ * expose the identical tool names and result shapes to the model.
13
+ */
14
+ /** One hit from {@link DocsIndex.search}. */
15
+ interface DocsSearchHit {
16
+ /** The matched text, when the backend returns one (headings/paragraph hits). */
17
+ excerpt?: string;
18
+ /** Breadcrumb trail to the matched section, e.g. `"Guides › Sharding"`. */
19
+ section?: string;
20
+ /** Page title. */
21
+ title: string;
22
+ /** Site-relative page URL, e.g. `"/docs/sharding"` — feed this to `lunora_get_doc`. */
23
+ url: string;
24
+ }
25
+ /** A page listed by {@link DocsIndex.listPages}. */
26
+ interface DocsPageSummary {
27
+ description?: string;
28
+ title: string;
29
+ /** Site-relative page URL, e.g. `"/docs/sharding"`. */
30
+ url: string;
31
+ }
32
+ /** A page's full text, as returned by {@link DocsIndex.getPage}. */
33
+ interface DocsPage extends DocsPageSummary {
34
+ /** The page body as Markdown. */
35
+ content: string;
36
+ }
37
+ interface DocsIndex {
38
+ /**
39
+ * The page at a site-relative URL, or `undefined` when there is no such
40
+ * page. Implementations should accept the URL exactly as it appears in a
41
+ * search hit.
42
+ */
43
+ getPage: (url: string) => Promise<DocsPage | undefined>;
44
+ /** Every indexed page, for a model that wants to browse rather than search. */
45
+ listPages: () => Promise<ReadonlyArray<DocsPageSummary>>;
46
+ /**
47
+ * Full-text search, returning whatever the backend found.
48
+ *
49
+ * Deliberately unbounded: the tool layer decides how many hits reach the
50
+ * model's context, so there is one truncation site rather than one per
51
+ * backend plus one in the tool.
52
+ */
53
+ search: (query: string) => Promise<ReadonlyArray<DocsSearchHit>>;
54
+ }
55
+ /** The subset of a fumadocs `SortedResult` these tools consume. */
56
+ interface FumadocsSearchResult {
57
+ breadcrumbs?: string[];
58
+ content?: string;
59
+ type?: string;
60
+ url?: string;
61
+ }
62
+ /** Map every result, skipping any entry {@link toDocsSearchHit} rejects. */
63
+ declare const toDocsSearchHits: (results: ReadonlyArray<unknown>) => DocsSearchHit[];
64
+ /** The public docs site the remote index reads when no base URL is configured. */
65
+ declare const DEFAULT_DOCS_BASE_URL = "https://lunora.sh";
66
+ interface RemoteDocsIndexOptions {
67
+ /** Origin of the docs site, e.g. `"https://lunora.sh"`. Defaults to {@link DEFAULT_DOCS_BASE_URL}. */
68
+ baseUrl?: string;
69
+ /** `fetch` implementation; defaults to the ambient global. */
70
+ fetch?: typeof fetch;
71
+ /** Per-request deadline in ms. Defaults to {@link DEFAULT_REQUEST_TIMEOUT_MS}. */
72
+ timeoutMs?: number;
73
+ }
74
+ declare const createRemoteDocsIndex: (options?: RemoteDocsIndexOptions) => DocsIndex;
75
+ /** URI scheme identifying a Lunora documentation page. */
76
+ declare const DOCS_URI_SCHEME = "lunora-docs:";
77
+ /** `"/docs/sharding"` → `"lunora-docs:/docs/sharding"`. */
78
+ declare const toDocsUri: (url: string) => string;
79
+ /** The inverse, or `undefined` for a uri this provider does not own. */
80
+ declare const fromDocsUri: (uri: string) => string | undefined;
81
+ /** Expose `index`'s pages as listable, readable resources. */
82
+ declare const docsResources: (index: DocsIndex) => McpResourceProvider;
83
+ /** Server identity advertised in the MCP `initialize` handshake. */
84
+ declare const DOCS_SERVER_NAME = "lunora-docs";
85
+ interface DocsMcpServerOptions {
86
+ /** The documentation source the tools read. */
87
+ index: DocsIndex;
88
+ /** Largest accepted request body. Defaults to {@link DEFAULT_MAX_REQUEST_BYTES}. */
89
+ maxRequestBytes?: number;
90
+ /**
91
+ * Version reported in the handshake. Defaults to `"0.0.0"` — a docs site
92
+ * bundles this code rather than installing it, so it, not the package, is
93
+ * the thing whose version a client would want to see.
94
+ */
95
+ version?: string;
96
+ }
97
+ /**
98
+ * Build a transport-agnostic MCP server exposing the documentation tools.
99
+ * Connect it yourself, or use {@link createDocsMcpFetchHandler} for the remote
100
+ * HTTP case.
101
+ */
102
+ declare const createDocsMcpServer: (options: DocsMcpServerOptions) => Server;
103
+ /**
104
+ * Build a stateless Streamable-HTTP fetch handler serving the documentation
105
+ * tools — the `Request` → `Response` function a docs site mounts at `/mcp`.
106
+ *
107
+ * A fresh server per request keeps the handler safe on platforms that fan
108
+ * requests across isolates, where nothing may be assumed to persist between
109
+ * them.
110
+ */
111
+ declare const createDocsMcpFetchHandler: (options: DocsMcpServerOptions) => McpFetchHandler;
112
+ /** Hits returned when the caller doesn't ask for a specific number. */
113
+ declare const DEFAULT_SEARCH_LIMIT = 10;
114
+ /**
115
+ * Ceiling on hits per call. Search results are pasted into the model's context
116
+ * verbatim, so an unbounded `limit` is a context-exhaustion foot-gun rather
117
+ * than a useful option.
118
+ */
119
+ declare const MAX_SEARCH_LIMIT = 50;
120
+ /** The advertised surface, in the order a caller should reach for it. */
121
+ declare const DOCS_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
122
+ /**
123
+ * Normalize whatever a model passes as a page URL into the site-relative form
124
+ * an index stores.
125
+ *
126
+ * Models copy URLs from anywhere — a search hit (`/docs/sharding`), the browser
127
+ * (`https://lunora.sh/docs/sharding`), or their own memory of the slug
128
+ * (`sharding`, `docs/sharding`). All four are the same page, and failing three
129
+ * of them would push the model into a guess-and-retry loop, so resolve them to
130
+ * one form. A trailing slash is dropped for the same reason.
131
+ *
132
+ * A `..` segment is REJECTED rather than resolved. The remote backend appends
133
+ * this path to `/llms.mdx`, so `../../api/search` would walk back out of the
134
+ * documentation tree and pull an unrelated path on the docs origin into the
135
+ * model's context — harmless against a public site, less so against the
136
+ * internal host a self-hosted `--docs-url` may point at.
137
+ */
138
+ declare const normalizeDocUrl: (raw: string) => string;
139
+ /**
140
+ * The documentation surface, bound to `index`.
141
+ *
142
+ * Each tool carries its own handler rather than routing through a shared
143
+ * `switch`: `createToolServer` already dispatches by name, so a second switch
144
+ * here would be a duplicate table with an unreachable `default`. It also owns
145
+ * the throw-to-`isError` conversion, so these handlers signal argument problems
146
+ * by throwing and return `isError` only for the expected misses a model should
147
+ * read and act on.
148
+ */
149
+ declare const docsTools: (index: DocsIndex) => ReadonlyArray<McpTool>;
150
+ export { DEFAULT_DOCS_BASE_URL, DEFAULT_SEARCH_LIMIT, DOCS_SERVER_NAME, DOCS_TOOL_DEFINITIONS, DOCS_URI_SCHEME, type DocsIndex, type DocsMcpServerOptions, type DocsPage, type DocsPageSummary, type DocsSearchHit, type FumadocsSearchResult, MAX_SEARCH_LIMIT, type McpFetchHandler, type McpResourceProvider, type McpTool, type RemoteDocsIndexOptions, type ToolDefinition, createDocsMcpFetchHandler, createDocsMcpServer, createRemoteDocsIndex, docsResources, docsTools, fromDocsUri, normalizeDocUrl, toDocsSearchHits, toDocsUri };
@@ -0,0 +1,150 @@
1
+ import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-DX0di_lv.js";
2
+ export type { c as McpResourceSummary, d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-DX0di_lv.js";
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
5
+ /**
6
+ * The documentation source the docs tools read.
7
+ *
8
+ * The tools are written against this interface rather than against a concrete
9
+ * search engine so the same tool surface can be backed two ways: in-process by
10
+ * the docs site itself (fumadocs `source` + its Orama index), or over HTTP by a
11
+ * remote reader that only has the published site (see `./remote-index`). Both
12
+ * expose the identical tool names and result shapes to the model.
13
+ */
14
+ /** One hit from {@link DocsIndex.search}. */
15
+ interface DocsSearchHit {
16
+ /** The matched text, when the backend returns one (headings/paragraph hits). */
17
+ excerpt?: string;
18
+ /** Breadcrumb trail to the matched section, e.g. `"Guides › Sharding"`. */
19
+ section?: string;
20
+ /** Page title. */
21
+ title: string;
22
+ /** Site-relative page URL, e.g. `"/docs/sharding"` — feed this to `lunora_get_doc`. */
23
+ url: string;
24
+ }
25
+ /** A page listed by {@link DocsIndex.listPages}. */
26
+ interface DocsPageSummary {
27
+ description?: string;
28
+ title: string;
29
+ /** Site-relative page URL, e.g. `"/docs/sharding"`. */
30
+ url: string;
31
+ }
32
+ /** A page's full text, as returned by {@link DocsIndex.getPage}. */
33
+ interface DocsPage extends DocsPageSummary {
34
+ /** The page body as Markdown. */
35
+ content: string;
36
+ }
37
+ interface DocsIndex {
38
+ /**
39
+ * The page at a site-relative URL, or `undefined` when there is no such
40
+ * page. Implementations should accept the URL exactly as it appears in a
41
+ * search hit.
42
+ */
43
+ getPage: (url: string) => Promise<DocsPage | undefined>;
44
+ /** Every indexed page, for a model that wants to browse rather than search. */
45
+ listPages: () => Promise<ReadonlyArray<DocsPageSummary>>;
46
+ /**
47
+ * Full-text search, returning whatever the backend found.
48
+ *
49
+ * Deliberately unbounded: the tool layer decides how many hits reach the
50
+ * model's context, so there is one truncation site rather than one per
51
+ * backend plus one in the tool.
52
+ */
53
+ search: (query: string) => Promise<ReadonlyArray<DocsSearchHit>>;
54
+ }
55
+ /** The subset of a fumadocs `SortedResult` these tools consume. */
56
+ interface FumadocsSearchResult {
57
+ breadcrumbs?: string[];
58
+ content?: string;
59
+ type?: string;
60
+ url?: string;
61
+ }
62
+ /** Map every result, skipping any entry {@link toDocsSearchHit} rejects. */
63
+ declare const toDocsSearchHits: (results: ReadonlyArray<unknown>) => DocsSearchHit[];
64
+ /** The public docs site the remote index reads when no base URL is configured. */
65
+ declare const DEFAULT_DOCS_BASE_URL = "https://lunora.sh";
66
+ interface RemoteDocsIndexOptions {
67
+ /** Origin of the docs site, e.g. `"https://lunora.sh"`. Defaults to {@link DEFAULT_DOCS_BASE_URL}. */
68
+ baseUrl?: string;
69
+ /** `fetch` implementation; defaults to the ambient global. */
70
+ fetch?: typeof fetch;
71
+ /** Per-request deadline in ms. Defaults to {@link DEFAULT_REQUEST_TIMEOUT_MS}. */
72
+ timeoutMs?: number;
73
+ }
74
+ declare const createRemoteDocsIndex: (options?: RemoteDocsIndexOptions) => DocsIndex;
75
+ /** URI scheme identifying a Lunora documentation page. */
76
+ declare const DOCS_URI_SCHEME = "lunora-docs:";
77
+ /** `"/docs/sharding"` → `"lunora-docs:/docs/sharding"`. */
78
+ declare const toDocsUri: (url: string) => string;
79
+ /** The inverse, or `undefined` for a uri this provider does not own. */
80
+ declare const fromDocsUri: (uri: string) => string | undefined;
81
+ /** Expose `index`'s pages as listable, readable resources. */
82
+ declare const docsResources: (index: DocsIndex) => McpResourceProvider;
83
+ /** Server identity advertised in the MCP `initialize` handshake. */
84
+ declare const DOCS_SERVER_NAME = "lunora-docs";
85
+ interface DocsMcpServerOptions {
86
+ /** The documentation source the tools read. */
87
+ index: DocsIndex;
88
+ /** Largest accepted request body. Defaults to {@link DEFAULT_MAX_REQUEST_BYTES}. */
89
+ maxRequestBytes?: number;
90
+ /**
91
+ * Version reported in the handshake. Defaults to `"0.0.0"` — a docs site
92
+ * bundles this code rather than installing it, so it, not the package, is
93
+ * the thing whose version a client would want to see.
94
+ */
95
+ version?: string;
96
+ }
97
+ /**
98
+ * Build a transport-agnostic MCP server exposing the documentation tools.
99
+ * Connect it yourself, or use {@link createDocsMcpFetchHandler} for the remote
100
+ * HTTP case.
101
+ */
102
+ declare const createDocsMcpServer: (options: DocsMcpServerOptions) => Server;
103
+ /**
104
+ * Build a stateless Streamable-HTTP fetch handler serving the documentation
105
+ * tools — the `Request` → `Response` function a docs site mounts at `/mcp`.
106
+ *
107
+ * A fresh server per request keeps the handler safe on platforms that fan
108
+ * requests across isolates, where nothing may be assumed to persist between
109
+ * them.
110
+ */
111
+ declare const createDocsMcpFetchHandler: (options: DocsMcpServerOptions) => McpFetchHandler;
112
+ /** Hits returned when the caller doesn't ask for a specific number. */
113
+ declare const DEFAULT_SEARCH_LIMIT = 10;
114
+ /**
115
+ * Ceiling on hits per call. Search results are pasted into the model's context
116
+ * verbatim, so an unbounded `limit` is a context-exhaustion foot-gun rather
117
+ * than a useful option.
118
+ */
119
+ declare const MAX_SEARCH_LIMIT = 50;
120
+ /** The advertised surface, in the order a caller should reach for it. */
121
+ declare const DOCS_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
122
+ /**
123
+ * Normalize whatever a model passes as a page URL into the site-relative form
124
+ * an index stores.
125
+ *
126
+ * Models copy URLs from anywhere — a search hit (`/docs/sharding`), the browser
127
+ * (`https://lunora.sh/docs/sharding`), or their own memory of the slug
128
+ * (`sharding`, `docs/sharding`). All four are the same page, and failing three
129
+ * of them would push the model into a guess-and-retry loop, so resolve them to
130
+ * one form. A trailing slash is dropped for the same reason.
131
+ *
132
+ * A `..` segment is REJECTED rather than resolved. The remote backend appends
133
+ * this path to `/llms.mdx`, so `../../api/search` would walk back out of the
134
+ * documentation tree and pull an unrelated path on the docs origin into the
135
+ * model's context — harmless against a public site, less so against the
136
+ * internal host a self-hosted `--docs-url` may point at.
137
+ */
138
+ declare const normalizeDocUrl: (raw: string) => string;
139
+ /**
140
+ * The documentation surface, bound to `index`.
141
+ *
142
+ * Each tool carries its own handler rather than routing through a shared
143
+ * `switch`: `createToolServer` already dispatches by name, so a second switch
144
+ * here would be a duplicate table with an unreachable `default`. It also owns
145
+ * the throw-to-`isError` conversion, so these handlers signal argument problems
146
+ * by throwing and return `isError` only for the expected misses a model should
147
+ * read and act on.
148
+ */
149
+ declare const docsTools: (index: DocsIndex) => ReadonlyArray<McpTool>;
150
+ export { DEFAULT_DOCS_BASE_URL, DEFAULT_SEARCH_LIMIT, DOCS_SERVER_NAME, DOCS_TOOL_DEFINITIONS, DOCS_URI_SCHEME, type DocsIndex, type DocsMcpServerOptions, type DocsPage, type DocsPageSummary, type DocsSearchHit, type FumadocsSearchResult, MAX_SEARCH_LIMIT, type McpFetchHandler, type McpResourceProvider, type McpTool, type RemoteDocsIndexOptions, type ToolDefinition, createDocsMcpFetchHandler, createDocsMcpServer, createRemoteDocsIndex, docsResources, docsTools, fromDocsUri, normalizeDocUrl, toDocsSearchHits, toDocsUri };
@@ -0,0 +1 @@
1
+ import{toDocsSearchHits as e}from"../packem_shared/toDocsSearchHits-C1xXPRPy.mjs";import{DEFAULT_DOCS_BASE_URL as D,createRemoteDocsIndex as t}from"../packem_shared/DEFAULT_DOCS_BASE_URL-DhaPz6PK.mjs";import{DOCS_URI_SCHEME as s,docsResources as S,fromDocsUri as E,toDocsUri as I}from"../packem_shared/DOCS_URI_SCHEME-DbyotZvf.mjs";import{DOCS_SERVER_NAME as R,createDocsMcpFetchHandler as p,createDocsMcpServer as A}from"../packem_shared/DOCS_SERVER_NAME-BYSXEkgD.mjs";import{DEFAULT_SEARCH_LIMIT as M,DOCS_TOOL_DEFINITIONS as O,MAX_SEARCH_LIMIT as T,docsTools as U,normalizeDocUrl as a}from"../packem_shared/DEFAULT_SEARCH_LIMIT-CKtOASrB.mjs";export{D as DEFAULT_DOCS_BASE_URL,M as DEFAULT_SEARCH_LIMIT,R as DOCS_SERVER_NAME,O as DOCS_TOOL_DEFINITIONS,s as DOCS_URI_SCHEME,T as MAX_SEARCH_LIMIT,p as createDocsMcpFetchHandler,A as createDocsMcpServer,t as createRemoteDocsIndex,S as docsResources,U as docsTools,E as fromDocsUri,a as normalizeDocUrl,e as toDocsSearchHits,I as toDocsUri};
package/dist/index.d.mts CHANGED
@@ -1,35 +1,9 @@
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';
3
- import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
4
- import { X402ChargeConfig, X402Price } from '@lunora/x402/charge';
5
5
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
6
- /**
7
- * The tool surface this MCP server exposes. Each tool maps onto a method the
8
- * `LunoraClient` already provides, so an AI agent can introspect a deployment
9
- * (functions, global tables) and invoke its functions over HTTP RPC.
10
- *
11
- * Definitions and dispatch live here — separate from the server wiring — so the
12
- * behaviour is unit-testable against a mock client without driving a transport.
13
- */
14
- /** A JSON-Schema object describing a tool's arguments, per the MCP spec. */
15
- interface ToolInputSchema {
16
- properties: Record<string, unknown>;
17
- required?: ReadonlyArray<string>;
18
- type: "object";
19
- }
20
- interface ToolDefinition {
21
- description: string;
22
- inputSchema: ToolInputSchema;
23
- name: string;
24
- }
25
- /** The MCP `CallToolResult` shape this server returns. */
26
- interface ToolResult {
27
- content: {
28
- text: string;
29
- type: "text";
30
- }[];
31
- isError?: boolean;
32
- }
6
+ import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
33
7
  /** The read-only tool surface: introspection + query. Always exposed. */
34
8
  declare const READ_ONLY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
35
9
  /** The write tool surface (mutations + actions). Exposed ONLY when writes are enabled. */
@@ -172,15 +146,101 @@ declare const createLunoraMcpServer: (options: LunoraMcpServerOptions) => Server
172
146
  * connected; the process then stays alive serving requests.
173
147
  */
174
148
  declare const connectStdio: (options: LunoraMcpServerOptions) => Promise<Server>;
175
- /** A Web-Standard fetch handler: takes a `Request`, returns the MCP `Response`. */
176
- type McpFetchHandler = (request: Request) => Promise<Response>;
177
149
  /**
178
150
  * Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
179
151
  * invocation constructs a fresh proxy server and serves the request through
180
152
  * {@link serveStateless}.
181
153
  */
182
154
  declare const createMcpFetchHandler: (options: LunoraMcpServerOptions) => McpFetchHandler;
155
+ /** A Lunora deployment the tools dispatch against. */
156
+ interface LocalDeployment {
157
+ token?: string;
158
+ url: string;
159
+ }
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
+ declare const localTools: (options: LocalMcpServerOptions) => ReadonlyArray<McpTool>;
207
+ /** Build the composed local server without connecting a transport. */
208
+ declare const createLocalMcpServer: (options?: LocalMcpServerOptions) => Server;
209
+ /**
210
+ * Build the composed local server and connect it over stdio — the transport an
211
+ * MCP client uses when it spawns `lunora mcp serve`. Resolves once connected;
212
+ * the process then stays alive serving requests.
213
+ */
214
+ declare const connectLocalStdio: (options?: LocalMcpServerOptions) => Promise<Server>;
183
215
  /** A tool handler: receives the call's `arguments` bag, returns an MCP tool result. */
216
+ /**
217
+ * The x402 vocabulary this module needs, declared here rather than imported.
218
+ *
219
+ * The x402 package is an optional peer, and a type import from its `charge`
220
+ * entry puts its `.d.ts` back into this package's build graph:
221
+ * a consumer that never installs x402 never builds it either, so the dts bundler
222
+ * looks for a `dist/` that does not exist and fails. That is not hypothetical —
223
+ * it broke the docs site build, which runs a filtered build over the docs app and its dependency closure
224
+ * and therefore never builds x402.
225
+ *
226
+ * Declaring them locally is safe because this module never *inspects* a charge
227
+ * config; it forwards it whole to `createChargeMiddleware`. The index signature
228
+ * keeps a real charge config assignable as x402 grows fields.
229
+ */
230
+ /** Mirrors x402's `X402Price` — a decimal string like `"$0.05"`, or a number. */
231
+ type X402Price = number | string;
232
+ /** Mirrors x402's charge config with the per-tool price omitted. */
233
+ interface X402ChargeSettings {
234
+ /** Network this resource settles on. */
235
+ readonly network: string;
236
+ /** Everything else x402 accepts, forwarded untouched. */
237
+ readonly [key: string]: unknown;
238
+ /** Payout wallet(s), per network family. */
239
+ readonly recipient: {
240
+ readonly evm?: string;
241
+ readonly svm?: string;
242
+ };
243
+ }
184
244
  type ToolHandler = (arguments_: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
185
245
  /** Registration shape for a free tool. */
186
246
  interface RegisterToolOptions {
@@ -199,7 +259,7 @@ interface RegisterPaidToolOptions extends RegisterToolOptions {
199
259
  price: X402Price;
200
260
  }
201
261
  /** x402 settlement vocabulary shared by every paid tool (network, recipient, facilitator); price is per-tool. */
202
- type PaidMcpChargeConfig = Omit<X402ChargeConfig, "price">;
262
+ type PaidMcpChargeConfig = X402ChargeSettings;
203
263
  /** Config for `createPaidMcpServer`. */
204
264
  interface PaidMcpServerConfig {
205
265
  /** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
@@ -231,4 +291,4 @@ interface PaidMcpServer {
231
291
  * transient facilitator outage retries on the next call.
232
292
  */
233
293
  declare const createPaidMcpServer: (config: PaidMcpServerConfig) => PaidMcpServer;
234
- export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type CallAgentToolOptions, type LunoraMcpServerOptions, type McpAgentExposure, 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, agentToolDefinitions, callAgentTool, callTool, connectStdio, createLunoraMcpServer, createMcpFetchHandler, createPaidMcpServer, parseAgentsEnv, toolDefinitions };
294
+ 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,35 +1,9 @@
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';
3
- import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
4
- import { X402ChargeConfig, X402Price } from '@lunora/x402/charge';
5
5
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
6
- /**
7
- * The tool surface this MCP server exposes. Each tool maps onto a method the
8
- * `LunoraClient` already provides, so an AI agent can introspect a deployment
9
- * (functions, global tables) and invoke its functions over HTTP RPC.
10
- *
11
- * Definitions and dispatch live here — separate from the server wiring — so the
12
- * behaviour is unit-testable against a mock client without driving a transport.
13
- */
14
- /** A JSON-Schema object describing a tool's arguments, per the MCP spec. */
15
- interface ToolInputSchema {
16
- properties: Record<string, unknown>;
17
- required?: ReadonlyArray<string>;
18
- type: "object";
19
- }
20
- interface ToolDefinition {
21
- description: string;
22
- inputSchema: ToolInputSchema;
23
- name: string;
24
- }
25
- /** The MCP `CallToolResult` shape this server returns. */
26
- interface ToolResult {
27
- content: {
28
- text: string;
29
- type: "text";
30
- }[];
31
- isError?: boolean;
32
- }
6
+ import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
33
7
  /** The read-only tool surface: introspection + query. Always exposed. */
34
8
  declare const READ_ONLY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
35
9
  /** The write tool surface (mutations + actions). Exposed ONLY when writes are enabled. */
@@ -172,15 +146,101 @@ declare const createLunoraMcpServer: (options: LunoraMcpServerOptions) => Server
172
146
  * connected; the process then stays alive serving requests.
173
147
  */
174
148
  declare const connectStdio: (options: LunoraMcpServerOptions) => Promise<Server>;
175
- /** A Web-Standard fetch handler: takes a `Request`, returns the MCP `Response`. */
176
- type McpFetchHandler = (request: Request) => Promise<Response>;
177
149
  /**
178
150
  * Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
179
151
  * invocation constructs a fresh proxy server and serves the request through
180
152
  * {@link serveStateless}.
181
153
  */
182
154
  declare const createMcpFetchHandler: (options: LunoraMcpServerOptions) => McpFetchHandler;
155
+ /** A Lunora deployment the tools dispatch against. */
156
+ interface LocalDeployment {
157
+ token?: string;
158
+ url: string;
159
+ }
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
+ declare const localTools: (options: LocalMcpServerOptions) => ReadonlyArray<McpTool>;
207
+ /** Build the composed local server without connecting a transport. */
208
+ declare const createLocalMcpServer: (options?: LocalMcpServerOptions) => Server;
209
+ /**
210
+ * Build the composed local server and connect it over stdio — the transport an
211
+ * MCP client uses when it spawns `lunora mcp serve`. Resolves once connected;
212
+ * the process then stays alive serving requests.
213
+ */
214
+ declare const connectLocalStdio: (options?: LocalMcpServerOptions) => Promise<Server>;
183
215
  /** A tool handler: receives the call's `arguments` bag, returns an MCP tool result. */
216
+ /**
217
+ * The x402 vocabulary this module needs, declared here rather than imported.
218
+ *
219
+ * The x402 package is an optional peer, and a type import from its `charge`
220
+ * entry puts its `.d.ts` back into this package's build graph:
221
+ * a consumer that never installs x402 never builds it either, so the dts bundler
222
+ * looks for a `dist/` that does not exist and fails. That is not hypothetical —
223
+ * it broke the docs site build, which runs a filtered build over the docs app and its dependency closure
224
+ * and therefore never builds x402.
225
+ *
226
+ * Declaring them locally is safe because this module never *inspects* a charge
227
+ * config; it forwards it whole to `createChargeMiddleware`. The index signature
228
+ * keeps a real charge config assignable as x402 grows fields.
229
+ */
230
+ /** Mirrors x402's `X402Price` — a decimal string like `"$0.05"`, or a number. */
231
+ type X402Price = number | string;
232
+ /** Mirrors x402's charge config with the per-tool price omitted. */
233
+ interface X402ChargeSettings {
234
+ /** Network this resource settles on. */
235
+ readonly network: string;
236
+ /** Everything else x402 accepts, forwarded untouched. */
237
+ readonly [key: string]: unknown;
238
+ /** Payout wallet(s), per network family. */
239
+ readonly recipient: {
240
+ readonly evm?: string;
241
+ readonly svm?: string;
242
+ };
243
+ }
184
244
  type ToolHandler = (arguments_: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
185
245
  /** Registration shape for a free tool. */
186
246
  interface RegisterToolOptions {
@@ -199,7 +259,7 @@ interface RegisterPaidToolOptions extends RegisterToolOptions {
199
259
  price: X402Price;
200
260
  }
201
261
  /** x402 settlement vocabulary shared by every paid tool (network, recipient, facilitator); price is per-tool. */
202
- type PaidMcpChargeConfig = Omit<X402ChargeConfig, "price">;
262
+ type PaidMcpChargeConfig = X402ChargeSettings;
203
263
  /** Config for `createPaidMcpServer`. */
204
264
  interface PaidMcpServerConfig {
205
265
  /** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
@@ -231,4 +291,4 @@ interface PaidMcpServer {
231
291
  * transient facilitator outage retries on the next call.
232
292
  */
233
293
  declare const createPaidMcpServer: (config: PaidMcpServerConfig) => PaidMcpServer;
234
- export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type CallAgentToolOptions, type LunoraMcpServerOptions, type McpAgentExposure, 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, agentToolDefinitions, callAgentTool, callTool, connectStdio, createLunoraMcpServer, createMcpFetchHandler, createPaidMcpServer, parseAgentsEnv, toolDefinitions };
294
+ 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 +1 @@
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};
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-CIbH2DKO.mjs";import{createPaidMcpServer as i}from"./packem_shared/createPaidMcpServer-Ba3uY4WX.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,4 @@
1
+ import{toDocsSearchHits as h}from"./toDocsSearchHits-C1xXPRPy.mjs";const m="https://lunora.sh",f=1e4,d=t=>t.endsWith("/")?t.slice(0,-1):t,g="\\",p=(t,e,i)=>{for(let s=i;s<t.length;s+=1)if(t[s]===e&&t[s-1]!==g)return s;return-1},u=t=>t.replaceAll(String.raw`\[`,"[").replaceAll(String.raw`\]`,"]").replaceAll(String.raw`\(`,"(").replaceAll(String.raw`\)`,")"),S=t=>{const e=t.trim();if(!e.startsWith("- ["))return;const i=e.lastIndexOf("](");if(i===-1)return;const s=p(e,")",i+2);if(s===-1)return;const a=u(e.slice(3,i).trim()),c=u(e.slice(i+2,s).trim());if(a.length===0||c.length===0)return;const n=e.slice(s+1).trim(),r=n.startsWith(":")?n.slice(1).trim():"";return r.length>0?{description:r,title:a,url:c}:{title:a,url:c}},x=t=>{if(!t.endsWith(")"))return t;const e=t.lastIndexOf(" (");if(e===-1)return t;const i=t.slice(e+2,-1);return i.startsWith("/")||i.startsWith("http")?t.slice(0,e).trim():t},y=(t,e)=>{for(const i of t.split(`
2
+ `)){if(i.startsWith("# "))return x(i.slice(2).trim());if(i.trim().length>0)break}return e},w=t=>{if(!t.startsWith("# "))return t;const e=t.indexOf(`
3
+ `);return e===-1?"":t.slice(e+1).trimStart()},A=(t={})=>{const e=d(t.baseUrl??m),i=t.fetch??globalThis.fetch,s=t.timeoutMs??f,a=async n=>{try{const r=await i(`${e}${n}`,{headers:{accept:"text/plain, application/json"},signal:AbortSignal.timeout(s)});return r.ok?{body:await r.text()}:{missing:!0}}catch(r){return{unreachable:r instanceof Error?r.message:String(r)}}},c=(n,r)=>{if("unreachable"in n)throw new Error(`could not reach the documentation site at ${e}${r}: ${n.unreachable}`);return"missing"in n?void 0:n.body};return{getPage:async n=>{const r=c(await a(`/llms.mdx${n}`),`/llms.mdx${n}`);if(!(r===void 0||r.trim().length===0))return{content:w(r),title:y(r,n),url:n}},listPages:async()=>{const n=c(await a("/llms.txt"),"/llms.txt");if(n===void 0)return[];const r=[];for(const o of n.split(`
4
+ `)){const l=S(o);l!==void 0&&r.push(l)}return r},search:async n=>{const r=`/api/search?query=${encodeURIComponent(n)}`,o=c(await a(r),r);if(o===void 0)return[];let l;try{l=JSON.parse(o)}catch{return[]}return Array.isArray(l)?h(l):[]}}};export{m as DEFAULT_DOCS_BASE_URL,f as DEFAULT_REQUEST_TIMEOUT_MS,A as createRemoteDocsIndex,S as parseIndexLine};
@@ -0,0 +1,3 @@
1
+ const w=10,S=50,h="docs",L=500,p={properties:{limit:{description:`Maximum hits to return (default ${String(10)}, max ${String(50)})`,type:"number"},query:{description:'Search terms, e.g. "shardBy" or "optimistic updates"',type:"string"}},required:["query"],type:"object"},g={properties:{url:{description:'Site-relative page URL from a search hit, e.g. "/docs/sharding"',type:"string"}},required:["url"],type:"object"},m={properties:{},type:"object"},i={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},u={annotations:{...i,title:"Search the Lunora documentation"},description:"Search the Lunora documentation and return matching pages and sections. Use this before writing Lunora code (schema, queries, mutations, actions, sharding, .global(), client hooks) so the answer reflects the framework's current API rather than a guess. Follow a hit with lunora_get_doc to read the full page.",inputSchema:p,name:"lunora_search_docs"},l={annotations:{...i,title:"Read a Lunora documentation page"},description:"Return one Lunora documentation page in full, as Markdown. Takes the `url` of a lunora_search_docs hit or a lunora_list_docs entry.",inputSchema:g,name:"lunora_get_doc"},d={annotations:{...i,title:"List the Lunora documentation pages"},description:"List every Lunora documentation page with its title and description. Prefer lunora_search_docs when you know what you're looking for.",inputSchema:m,name:"lunora_list_docs"},$=[u,l,d],o=e=>({content:[{text:JSON.stringify(e,void 0,2),type:"text"}]}),f=e=>({content:[{text:e,type:"text"}],isError:!0}),a=512,c=(e,t)=>{const n=e[t];if(typeof n!="string"||n.trim().length===0)throw new TypeError(`"${t}" is required and must be a non-empty string`);if(n.length>a)throw new RangeError(`"${t}" must be at most ${String(a)} characters`);return n.trim()},_=e=>{const t=typeof e=="string"?Number(e):e;return typeof t!="number"||!Number.isFinite(t)?10:Math.min(50,Math.max(1,Math.floor(t)))},y=e=>{let t=e.trim();if(t.startsWith("http://")||t.startsWith("https://")){const n=t.slice(t.indexOf("//")+2),r=n.indexOf("/");t=r===-1?"/":n.slice(r)}for(const n of["?","#"]){const r=t.indexOf(n);r!==-1&&(t=t.slice(0,r))}for(;t.endsWith("/")&&t.length>1;)t=t.slice(0,-1);if(t.startsWith("/")||(t=t.startsWith("docs/")?`/${t}`:`/${h}/${t}`),t.split("/").includes(".."))throw new RangeError(`"url" must not contain ".." segments: ${e}`);return t},x=e=>[{definition:u,handle:async t=>{const n=c(t,"query"),r=_(t.limit),s=(await e.search(n)).slice(0,r);return s.length===0?o({hits:[],note:`no documentation matched "${n}" — try fewer or more general terms, or lunora_list_docs to browse`}):o({hits:s})}},{definition:l,handle:async t=>{const n=y(c(t,"url")),r=await e.getPage(n);return r===void 0?f(`documentation page not found: ${n}. Use lunora_search_docs or lunora_list_docs to find a valid url.`):{content:[{text:`# ${r.title} (${r.url})
2
+
3
+ ${r.content}`,type:"text"}]}}},{definition:d,handle:async()=>{const t=await e.listPages();return t.length>500?o({note:`showing the first ${String(500)} of ${String(t.length)} pages — use lunora_search_docs to find the rest`,pages:t.slice(0,500)}):o(t)}}];export{w as DEFAULT_SEARCH_LIMIT,$ as DOCS_TOOL_DEFINITIONS,a as MAX_ARGUMENT_LENGTH,L as MAX_LISTED_PAGES,S as MAX_SEARCH_LIMIT,x as docsTools,y as normalizeDocUrl};
@@ -0,0 +1 @@
1
+ import{createToolServer as d}from"./createToolServer-CP5bNUcS.mjs";import{serveStateless as c}from"./serveStateless-BqoRk-xU.mjs";import{docsResources as i}from"./DOCS_URI_SCHEME-DbyotZvf.mjs";import{docsTools as p}from"./DEFAULT_SEARCH_LIMIT-CKtOASrB.mjs";const u="lunora-docs",m=128*1024,y=e=>({name:u,version:e??"0.0.0"}),S=e=>d(y(e.version),p(e.index),i(e.index)),l=e=>new TextEncoder().encode(e).length,n=(e,r,s)=>Response.json({error:{code:r,message:s},id:null,jsonrpc:"2.0"},{status:e}),v=async(e,r)=>{if(e.method!=="POST")return{parsedBody:void 0};const s={response:n(413,-32600,`request body exceeds ${String(r)} bytes`)},o=Number(e.headers.get("content-length"));if(Number.isFinite(o)&&o>r)return s;const a=await e.text();if(l(a)>r)return s;let t;try{t=JSON.parse(a)}catch{return{response:n(400,-32700,"parse error: body is not valid JSON")}}return Array.isArray(t)?{response:n(400,-32600,"batched requests are not supported: send one JSON-RPC message per request")}:{parsedBody:t}},B=e=>{const r=e.maxRequestBytes??m;return async s=>{const o=await v(s,r);return"response"in o?o.response:c(S(e),s,o.parsedBody===void 0?void 0:{parsedBody:o.parsedBody})}};export{m as DEFAULT_MAX_REQUEST_BYTES,u as DOCS_SERVER_NAME,B as createDocsMcpFetchHandler,S as createDocsMcpServer};
@@ -0,0 +1,3 @@
1
+ const s="lunora-docs:",r=t=>`${s}${t}`,a=t=>t.startsWith(s)?t.slice(s.length):void 0,n=t=>({list:async()=>(await t.listPages()).map(i=>({...i.description===void 0?{}:{description:i.description},mimeType:"text/markdown",name:i.title,uri:r(i.url)})),read:async i=>{const e=a(i);if(e===void 0)return;const o=await t.getPage(e);return o===void 0?void 0:{mimeType:"text/markdown",text:`# ${o.title} (${o.url})
2
+
3
+ ${o.content}`}}});export{s as DOCS_URI_SCHEME,n as docsResources,a as fromDocsUri,r as toDocsUri};
@@ -0,0 +1 @@
1
+ import{LunoraClient as l}from"@lunora/client";import{StdioServerTransport as d}from"@modelcontextprotocol/sdk/server/stdio.js";import{createToolServer as f}from"./createToolServer-CP5bNUcS.mjs";import{createRemoteDocsIndex as a}from"./DEFAULT_DOCS_BASE_URL-DhaPz6PK.mjs";import{docsResources as u}from"./DOCS_URI_SCHEME-DbyotZvf.mjs";import{docsTools as m}from"./DEFAULT_SEARCH_LIMIT-CKtOASrB.mjs";import{toolDefinitions as p,callTool as v}from"./READ_ONLY_TOOL_DEFINITIONS-48jMcpI-.mjs";const h="lunora",S="no Lunora dev server is running for this project — start one with `lunora dev`, then call this tool again (call lunora_dev_status to check).",T=o=>{const t=new Map;return e=>{const n=JSON.stringify([e.url,e.token??""]),s=t.get(n);if(s!==void 0)return s;const r=new l({fetch:o,url:e.url});return e.token!==void 0&&e.token.length>0&&r.setAuthToken(e.token),t.set(n,r),r}},y=(o,t,e)=>{const n=typeof o=="function"?o:()=>o,s=T(e);return p(t).map(r=>({definition:r,handle:async i=>{const c=n();return c===void 0?{content:[{text:S,type:"text"}],isError:!0}:v(s(c),r.name,i,t)}}))},E=o=>{const t=[];return o.docs!==!1&&t.push(...m(a({...o.docs?.baseUrl===void 0?{}:{baseUrl:o.docs.baseUrl},...o.fetch===void 0?{}:{fetch:o.fetch}}))),t.push(...o.extraTools??[]),o.deployment!==void 0&&t.push(...y(o.deployment,o.allowWrites??!1,o.fetch)),t},L=(o={})=>{const t=o.docs===!1?void 0:a({...o.docs?.baseUrl===void 0?{}:{baseUrl:o.docs.baseUrl},...o.fetch===void 0?{}:{fetch:o.fetch}});return f({name:h,version:o.version??"0.0.0"},E(o),t===void 0?void 0:u(t))},x=async(o={})=>{const t=L(o);return await t.connect(new d),t};export{h as LOCAL_SERVER_NAME,S as NO_DEPLOYMENT_MESSAGE,x as connectLocalStdio,L as createLocalMcpServer,E as localTools};
@@ -0,0 +1 @@
1
+ import{LunoraError as u}from"@lunora/errors";const h={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"},m={properties:{},type:"object"},E={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},c={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},g=[{annotations:{...c,title:"List deployment functions"},description:"List the deployment's public functions (queries, mutations, actions) with their kinds.",inputSchema:m,name:"lunora_list_functions"},{annotations:{...c,title:"List global tables"},description:"List the deployment's .global() tables and their column shapes.",inputSchema:m,name:"lunora_list_tables"},{annotations:{...c,title:"Describe a function's arguments"},description:"Return a function's argument JSON Schema and kind, so a caller can construct a valid arguments object. Call lunora_list_functions first to discover available function paths.",inputSchema:E,name:"lunora_get_function_schema"},{annotations:{...c,title:"Run a query"},description:"Run a query and return its result. Read-only.",inputSchema:h,name:"lunora_run_query"}],S=[{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run a mutation (writes data)"},description:"Run a mutation and return its result. Writes data — use with care.",inputSchema:h,name:"lunora_run_mutation"},{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run an action (may call external services)"},description:"Run an action and return its result. May call external services.",inputSchema:h,name:"lunora_run_action"}],A=new Set(S.map(t=>t.name)),T=t=>t===!0?[...g,...S]:g,O=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},_=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),w=t=>Array.isArray(t)?"an array":`a ${typeof t}`,v=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(!_(e))throw new u("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${w(e)}`);return e}if(!_(t))throw new u("BAD_REQUEST",`"args" must be a JSON object, got ${w(t)}`);return t},d=t=>{const e=O(t),n=v(t.args),i=typeof t.shardKey=="string"&&t.shardKey.length>0?t.shardKey:void 0;return{args:n,functionPath:e,shardKey:i}},f=t=>({__lunoraRef:t}),b=t=>{let e="";for(let n=0;n<t.length;n+=32768)e+=String.fromCharCode(...t.subarray(n,n+32768));return btoa(e)},R=(t,e)=>{if(typeof e=="bigint")return e.toString();if(e instanceof ArrayBuffer)return b(new Uint8Array(e));if(ArrayBuffer.isView(e)){const n=e;return b(new Uint8Array(n.buffer,n.byteOffset,n.byteLength))}return e},s=t=>({content:[{text:t===void 0?"null":JSON.stringify(t,R,2),type:"text"}]}),N=3e4,l=new WeakMap,y=t=>{const e=Date.now(),n=l.get(t);if(n!==void 0&&n.expiresAt>e)return n.promise;const i=t.listFunctions().catch(r=>{throw l.get(t)?.promise===i&&l.delete(t),r});return l.set(t,{expiresAt:e+N,promise:i}),i},p=async(t,e,n)=>{const i=(await y(t)).find(r=>r.path===e);if(i===void 0)throw new u("NOT_FOUND",`function not found or not public: ${e}`);if(i.kind!==n)throw new u("BAD_REQUEST",`function ${e} is a ${i.kind}, not a ${n}`)},L=async(t,e,n,i=!1)=>{try{if(i!==!0&&A.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 r=O(n),a=(await y(t)).find(o=>o.path===r);return a===void 0?{content:[{text:`function not found: ${r}`,type:"text"}],isError:!0}:s({args:a.args??[],kind:a.kind,path:a.path})}case"lunora_list_functions":return s(await y(t));case"lunora_list_tables":return s(await t.listGlobalTables());case"lunora_run_action":{const{args:r,functionPath:a,shardKey:o}=d(n);return await p(t,a,"action"),s(await t.action(f(a),r,{shardKey:o}))}case"lunora_run_mutation":{const{args:r,functionPath:a,shardKey:o}=d(n);return await p(t,a,"mutation"),s(await t.mutation(f(a),r,{shardKey:o}))}case"lunora_run_query":{const{args:r,functionPath:a,shardKey:o}=d(n);return await p(t,a,"query"),s(await t.query(f(a),r,{shardKey:o}))}default:return{content:[{text:`unknown tool: ${e}`,type:"text"}],isError:!0}}}catch(r){return{content:[{text:r instanceof Error?r.message:String(r),type:"text"}],isError:!0}}};export{g as READ_ONLY_TOOL_DEFINITIONS,S as WRITE_TOOL_DEFINITIONS,L as callTool,T as toolDefinitions};
@@ -1 +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};
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 L}from"./AGENT_RUN_INPUT_SCHEMA-ihZg6vIZ.mjs";import{toolDefinitions as k,callTool as x}from"./READ_ONLY_TOOL_DEFINITIONS-48jMcpI-.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:[...k(e),...S(o,a)]})),n.setRequestHandler(M,async c=>{const{arguments:m,name:s}=c.params,i=m??{};return T(s,o)?await L(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{serveStateless as s}from"./serveStateless-BqoRk-xU.mjs";import{createLunoraMcpServer as t}from"./connectStdio-Dt-DqEgA.mjs";const o=e=>r=>s(t(e),r);export{o as createMcpFetchHandler,s as serveStateless};
@@ -0,0 +1 @@
1
+ import{LunoraError as p}from"@lunora/errors";import{Server as f}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as y,CallToolRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{serveStateless as g}from"./serveStateless-BqoRk-xU.mjs";const S=async()=>{try{return(await import("@lunora/x402/charge")).createChargeMiddleware}catch(a){throw new p("INTERNAL",`paid MCP tools need the optional peer "@lunora/x402" — install it alongside @lunora/mcp to charge for tools (${a instanceof Error?a.message:String(a)})`)}},M={name:"lunora-paid-mcp",version:"0.0.0"},R="tools/call",d=a=>{if(typeof a!="object"||a===null)return;const{method:s,params:n}=a;if(s!==R||typeof n!="object"||n===null)return;const{name:i}=n;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}),T=a=>{const s=new Map,n=new Map,i=new Map,m=a.serverInfo??M,l=(e,t,o)=>{if(s.has(e.name))throw new p("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&&n.set(e.name,o)},u=()=>{const e=new f(m,{capabilities:{tools:{}}});return e.setRequestHandler(y,()=>({tools:[...s.values()].map(t=>t.definition)})),e.setRequestHandler(w,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},h=(e,t)=>{let o=i.get(e);if(o===void 0){const r=S().then(async c=>c({...a.charge,price:t},{resource:e})).catch(c=>{throw i.delete(e),c});o=r,i.set(e,r)}return o};return{fetchHandler:async e=>{let t;try{t=await e.clone().json()}catch{t=void 0}const o=()=>g(u(),e,t===void 0?void 0:{parsedBody:t});if(Array.isArray(t))return t.some(v=>n.has(d(v)??""))?C():o();const r=d(t),c=r===void 0?void 0:n.get(r);return r===void 0||c===void 0?o():(await h(r,c)).handle(e,o)},paidTool:(e,t)=>{l(e,t,e.price)},tool:(e,t)=>{l(e,t)}}};export{T as createPaidMcpServer};
@@ -0,0 +1 @@
1
+ import{Server as l}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as d,CallToolRequestSchema as p,ListResourcesRequestSchema as R,ReadResourceRequestSchema as f}from"@modelcontextprotocol/sdk/types.js";const h=(c,u,s)=>{const n=new Map;for(const e of u)n.has(e.definition.name)||n.set(e.definition.name,e);const m=[...n.values()].map(e=>e.definition),r=new l(c,{capabilities:s===void 0?{tools:{}}:{resources:{},tools:{}}});return r.setRequestHandler(d,()=>({tools:m})),r.setRequestHandler(p,async e=>{const{arguments:o,name:t}=e.params,i=n.get(t);if(i===void 0)return{content:[{text:`unknown tool: ${t}`,type:"text"}],isError:!0};try{return await i.handle(o??{})}catch(a){return{content:[{text:a instanceof Error?a.message:String(a),type:"text"}],isError:!0}}}),s!==void 0&&(r.setRequestHandler(R,async()=>({resources:[...await s.list()]})),r.setRequestHandler(f,async e=>{const{uri:o}=e.params,t=await s.read(o);if(t===void 0)throw new Error(`unknown resource: ${o}`);return{contents:[{mimeType:t.mimeType??"text/plain",text:t.text,uri:o}]}})),r};export{h as createToolServer};
@@ -0,0 +1,119 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { HandleRequestOptions } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
3
+ /**
4
+ * The MCP wire shapes every tool surface in this package speaks — the
5
+ * deployment tools (`./tools`), the agent tools (`./agent-tools`), and the
6
+ * documentation tools (`./docs`).
7
+ *
8
+ * They live in their own module (rather than in `./tools`) so a consumer that
9
+ * only wants the docs surface can import the types without dragging
10
+ * `@lunora/client` into its bundle. `./tools` re-exports them, so the package's
11
+ * public API is unchanged.
12
+ */
13
+ /** A JSON-Schema object describing a tool's arguments, per the MCP spec. */
14
+ interface ToolInputSchema {
15
+ properties: Record<string, unknown>;
16
+ required?: ReadonlyArray<string>;
17
+ type: "object";
18
+ }
19
+ /**
20
+ * MCP tool annotations — hints a client may use to decide how to present a tool
21
+ * (badge it read-only, confirm before a destructive call).
22
+ *
23
+ * They are hints, not enforcement: this package's actual guarantees are made at
24
+ * dispatch, where a write tool is refused unless `allowWrites` is set. These
25
+ * exist so a client can *show* the user what the server already enforces,
26
+ * instead of every tool looking equally dangerous.
27
+ */
28
+ interface ToolAnnotations {
29
+ /** The tool may perform irreversible changes. Only meaningful when `readOnlyHint` is false. */
30
+ destructiveHint?: boolean;
31
+ /** Repeating the call with the same arguments has no additional effect. */
32
+ idempotentHint?: boolean;
33
+ /** The tool reaches systems beyond this server (the network, external services). */
34
+ openWorldHint?: boolean;
35
+ /** The tool does not modify anything. */
36
+ readOnlyHint?: boolean;
37
+ /** Human-facing title, shown instead of the raw tool name. */
38
+ title?: string;
39
+ }
40
+ interface ToolDefinition {
41
+ annotations?: ToolAnnotations;
42
+ description: string;
43
+ inputSchema: ToolInputSchema;
44
+ name: string;
45
+ }
46
+ /** The MCP `CallToolResult` shape this package's tools return. */
47
+ interface ToolResult {
48
+ content: {
49
+ text: string;
50
+ type: "text";
51
+ }[];
52
+ isError?: boolean;
53
+ }
54
+ /** A tool: what to advertise in `ListTools`, and what to run on `CallTool`. */
55
+ interface McpTool {
56
+ definition: ToolDefinition;
57
+ handle: (input: Record<string, unknown>) => Promise<ToolResult>;
58
+ }
59
+ /** Name/version pair reported in the MCP `initialize` handshake. */
60
+ interface McpServerInfo {
61
+ name: string;
62
+ version: string;
63
+ }
64
+ /** One addressable document a client can list and read directly. */
65
+ interface McpResourceSummary {
66
+ description?: string;
67
+ mimeType?: string;
68
+ name: string;
69
+ uri: string;
70
+ }
71
+ /**
72
+ * A set of resources the server exposes alongside its tools.
73
+ *
74
+ * Tools are calls a model chooses to make; resources are documents a *client*
75
+ * can enumerate and attach on its own — the user picking a page to put in
76
+ * context, without the model having to guess a search query first. The same
77
+ * corpus is worth offering both ways.
78
+ */
79
+ interface McpResourceProvider {
80
+ /** Every resource, for `resources/list`. */
81
+ list: () => Promise<ReadonlyArray<McpResourceSummary>>;
82
+ /** One resource's body, or `undefined` when the uri is unknown. */
83
+ read: (uri: string) => Promise<{
84
+ mimeType?: string;
85
+ text: string;
86
+ } | undefined>;
87
+ }
88
+ /**
89
+ * Build an MCP `Server` that advertises and dispatches `tools`.
90
+ *
91
+ * On duplicate names the FIRST wins, so a caller can layer surfaces in
92
+ * precedence order without the later list silently shadowing the earlier one.
93
+ * Thrown errors become `isError` results rather than rejections, per the MCP
94
+ * convention that a tool failure is output the model can read and react to,
95
+ * not a protocol-level fault.
96
+ */
97
+ declare const createToolServer: (info: McpServerInfo, tools: ReadonlyArray<McpTool>, resources?: McpResourceProvider) => Server;
98
+ /** A Web-Standard fetch handler: takes a `Request`, returns the MCP `Response`. */
99
+ type McpFetchHandler = (request: Request) => Promise<Response>;
100
+ /**
101
+ * Drive one request through a fresh **stateless** Streamable-HTTP transport bound
102
+ * to `server`, then tear both down.
103
+ *
104
+ * `sessionIdGenerator: undefined` → stateless (no cross-request session store);
105
+ * `enableJsonResponse: true` → the whole JSON-RPC response is buffered into the
106
+ * returned `Response`, so closing the transport/server afterwards can't truncate
107
+ * an in-flight body. Cleanup is fire-and-forget with swallowed rejections (a
108
+ * `.catch()`-terminated chain) so it never delays or masks the resolved response.
109
+ *
110
+ * `options.parsedBody` lets a caller hand over a body it already read (e.g. the
111
+ * paid-tool gate, which peeks the JSON-RPC message to price the call) so the
112
+ * transport doesn't re-read a consumed stream.
113
+ *
114
+ * Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
115
+ * would otherwise skip it and leak a server + transport per failed request,
116
+ * which on a public endpoint is exactly the request an attacker can repeat.
117
+ */
118
+ declare const serveStateless: (server: Server, request: Request, options?: HandleRequestOptions) => Promise<Response>;
119
+ export { McpResourceProvider as M, ToolDefinition as T, McpFetchHandler as a, McpTool as b, McpResourceSummary as c, McpServerInfo as d, ToolInputSchema as e, ToolResult as f, createToolServer as g, serveStateless as s };
@@ -0,0 +1,119 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { HandleRequestOptions } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
3
+ /**
4
+ * The MCP wire shapes every tool surface in this package speaks — the
5
+ * deployment tools (`./tools`), the agent tools (`./agent-tools`), and the
6
+ * documentation tools (`./docs`).
7
+ *
8
+ * They live in their own module (rather than in `./tools`) so a consumer that
9
+ * only wants the docs surface can import the types without dragging
10
+ * `@lunora/client` into its bundle. `./tools` re-exports them, so the package's
11
+ * public API is unchanged.
12
+ */
13
+ /** A JSON-Schema object describing a tool's arguments, per the MCP spec. */
14
+ interface ToolInputSchema {
15
+ properties: Record<string, unknown>;
16
+ required?: ReadonlyArray<string>;
17
+ type: "object";
18
+ }
19
+ /**
20
+ * MCP tool annotations — hints a client may use to decide how to present a tool
21
+ * (badge it read-only, confirm before a destructive call).
22
+ *
23
+ * They are hints, not enforcement: this package's actual guarantees are made at
24
+ * dispatch, where a write tool is refused unless `allowWrites` is set. These
25
+ * exist so a client can *show* the user what the server already enforces,
26
+ * instead of every tool looking equally dangerous.
27
+ */
28
+ interface ToolAnnotations {
29
+ /** The tool may perform irreversible changes. Only meaningful when `readOnlyHint` is false. */
30
+ destructiveHint?: boolean;
31
+ /** Repeating the call with the same arguments has no additional effect. */
32
+ idempotentHint?: boolean;
33
+ /** The tool reaches systems beyond this server (the network, external services). */
34
+ openWorldHint?: boolean;
35
+ /** The tool does not modify anything. */
36
+ readOnlyHint?: boolean;
37
+ /** Human-facing title, shown instead of the raw tool name. */
38
+ title?: string;
39
+ }
40
+ interface ToolDefinition {
41
+ annotations?: ToolAnnotations;
42
+ description: string;
43
+ inputSchema: ToolInputSchema;
44
+ name: string;
45
+ }
46
+ /** The MCP `CallToolResult` shape this package's tools return. */
47
+ interface ToolResult {
48
+ content: {
49
+ text: string;
50
+ type: "text";
51
+ }[];
52
+ isError?: boolean;
53
+ }
54
+ /** A tool: what to advertise in `ListTools`, and what to run on `CallTool`. */
55
+ interface McpTool {
56
+ definition: ToolDefinition;
57
+ handle: (input: Record<string, unknown>) => Promise<ToolResult>;
58
+ }
59
+ /** Name/version pair reported in the MCP `initialize` handshake. */
60
+ interface McpServerInfo {
61
+ name: string;
62
+ version: string;
63
+ }
64
+ /** One addressable document a client can list and read directly. */
65
+ interface McpResourceSummary {
66
+ description?: string;
67
+ mimeType?: string;
68
+ name: string;
69
+ uri: string;
70
+ }
71
+ /**
72
+ * A set of resources the server exposes alongside its tools.
73
+ *
74
+ * Tools are calls a model chooses to make; resources are documents a *client*
75
+ * can enumerate and attach on its own — the user picking a page to put in
76
+ * context, without the model having to guess a search query first. The same
77
+ * corpus is worth offering both ways.
78
+ */
79
+ interface McpResourceProvider {
80
+ /** Every resource, for `resources/list`. */
81
+ list: () => Promise<ReadonlyArray<McpResourceSummary>>;
82
+ /** One resource's body, or `undefined` when the uri is unknown. */
83
+ read: (uri: string) => Promise<{
84
+ mimeType?: string;
85
+ text: string;
86
+ } | undefined>;
87
+ }
88
+ /**
89
+ * Build an MCP `Server` that advertises and dispatches `tools`.
90
+ *
91
+ * On duplicate names the FIRST wins, so a caller can layer surfaces in
92
+ * precedence order without the later list silently shadowing the earlier one.
93
+ * Thrown errors become `isError` results rather than rejections, per the MCP
94
+ * convention that a tool failure is output the model can read and react to,
95
+ * not a protocol-level fault.
96
+ */
97
+ declare const createToolServer: (info: McpServerInfo, tools: ReadonlyArray<McpTool>, resources?: McpResourceProvider) => Server;
98
+ /** A Web-Standard fetch handler: takes a `Request`, returns the MCP `Response`. */
99
+ type McpFetchHandler = (request: Request) => Promise<Response>;
100
+ /**
101
+ * Drive one request through a fresh **stateless** Streamable-HTTP transport bound
102
+ * to `server`, then tear both down.
103
+ *
104
+ * `sessionIdGenerator: undefined` → stateless (no cross-request session store);
105
+ * `enableJsonResponse: true` → the whole JSON-RPC response is buffered into the
106
+ * returned `Response`, so closing the transport/server afterwards can't truncate
107
+ * an in-flight body. Cleanup is fire-and-forget with swallowed rejections (a
108
+ * `.catch()`-terminated chain) so it never delays or masks the resolved response.
109
+ *
110
+ * `options.parsedBody` lets a caller hand over a body it already read (e.g. the
111
+ * paid-tool gate, which peeks the JSON-RPC message to price the call) so the
112
+ * transport doesn't re-read a consumed stream.
113
+ *
114
+ * Teardown runs in a `finally`: a rejection from `connect` or `handleRequest`
115
+ * would otherwise skip it and leak a server + transport per failed request,
116
+ * which on a public endpoint is exactly the request an attacker can repeat.
117
+ */
118
+ declare const serveStateless: (server: Server, request: Request, options?: HandleRequestOptions) => Promise<Response>;
119
+ export { McpResourceProvider as M, ToolDefinition as T, McpFetchHandler as a, McpTool as b, McpResourceSummary as c, McpServerInfo as d, ToolInputSchema as e, ToolResult as f, createToolServer as g, serveStateless as s };
@@ -0,0 +1 @@
1
+ import{WebStandardStreamableHTTPServerTransport as n}from"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";const o=async(a,s,t)=>{const e=new n({enableJsonResponse:!0,sessionIdGenerator:void 0});try{return await a.connect(e),await e.handleRequest(s,t)}finally{e.close().catch(()=>{}),a.close().catch(()=>{})}};export{o as serveStateless};
@@ -0,0 +1 @@
1
+ const i=r=>typeof r=="object"&&r!==null,l=r=>r.split("<mark>").join("").split("</mark>").join(""),c=r=>Array.isArray(r)?r.filter(t=>typeof t=="string"):[],u=r=>{if(!i(r)||typeof r.url!="string"||r.url.length===0)return;const t=r,n=typeof t.content=="string"?l(t.content):"",e=c(t.breadcrumbs),o=t.type==="page",s=o?n:e.at(-1)??n;return{...o||n.length===0?{}:{excerpt:n},...e.length>0?{section:e.join(" › ")}:{},title:s.length>0?s:r.url,url:r.url}},p=r=>{const t=[];for(const n of r){const e=u(n);e!==void 0&&t.push(e)}return t};export{p as toDocsSearchHits};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/mcp",
3
- "version": "1.0.0-alpha.46",
3
+ "version": "1.0.0-alpha.48",
4
4
  "description": "Model Context Protocol server exposing a Lunora deployment to AI agents",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -43,6 +43,10 @@
43
43
  "types": "./dist/index.d.ts",
44
44
  "import": "./dist/index.mjs"
45
45
  },
46
+ "./docs": {
47
+ "types": "./dist/docs/index.d.ts",
48
+ "import": "./dist/docs/index.mjs"
49
+ },
46
50
  "./package.json": "./package.json"
47
51
  },
48
52
  "publishConfig": {
@@ -51,9 +55,16 @@
51
55
  "dependencies": {
52
56
  "@lunora/client": "1.0.0-alpha.31",
53
57
  "@lunora/errors": "1.0.0-alpha.8",
54
- "@lunora/x402": "1.0.0-alpha.21",
55
58
  "@modelcontextprotocol/sdk": "^1.29.0"
56
59
  },
60
+ "peerDependencies": {
61
+ "@lunora/x402": ">=1.0.0-alpha.21 <2.0.0-0"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "@lunora/x402": {
65
+ "optional": true
66
+ }
67
+ },
57
68
  "engines": {
58
69
  "node": "^22.15.0 || >=24.11.0"
59
70
  }
@@ -1 +0,0 @@
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};
@@ -1 +0,0 @@
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};
@@ -1 +0,0 @@
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};