@lunora/mcp 1.0.0-alpha.105 → 1.0.0-alpha.106
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -5
- package/dist/bin.mjs +4 -4
- package/dist/docs/index.d.mts +3 -3
- package/dist/docs/index.d.ts +3 -3
- package/dist/docs/index.mjs +1 -1
- package/dist/index.d.mts +30 -15
- package/dist/index.d.ts +30 -15
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs +1 -0
- package/dist/packem_shared/DEFAULT_SEARCH_LIMIT-BqSYN5vr.mjs +3 -0
- package/dist/packem_shared/DOCS_SERVER_NAME-hHomMT1b.mjs +1 -0
- package/dist/packem_shared/{LOCAL_SERVER_NAME-BwlBnhX-.mjs → LOCAL_SERVER_NAME-BPPFBI2g.mjs} +1 -1
- package/dist/packem_shared/{READ_ONLY_TOOL_DEFINITIONS-aD-b7Hw2.mjs → READ_ONLY_TOOL_DEFINITIONS-CMhsP7pi.mjs} +1 -1
- package/dist/packem_shared/connectStdio-D2XPJMyB.mjs +1 -0
- package/dist/packem_shared/{createAuthedMcpFetchHandler-LLzMY9SW.mjs → createAuthedMcpFetchHandler-D9RPx2pl.mjs} +1 -1
- package/dist/packem_shared/createMcpFetchHandler-oQqcp6i5.mjs +1 -0
- package/dist/packem_shared/{createPaidMcpServer-DyNwdCq2.mjs → createPaidMcpServer-XUo-pELu.mjs} +1 -1
- package/dist/packem_shared/{serve-stateless.d-B_q8q39X.d.mts → serve-stateless.d-Qu1g6Pf4.d.mts} +10 -2
- package/dist/packem_shared/{serve-stateless.d-B_q8q39X.d.ts → serve-stateless.d-Qu1g6Pf4.d.ts} +10 -2
- package/dist/packem_shared/serveStateless-B3nFSJ-z.mjs +1 -0
- package/package.json +4 -4
- package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-DGpNvk5K.mjs +0 -1
- package/dist/packem_shared/DEFAULT_SEARCH_LIMIT-V7pCZ1wt.mjs +0 -3
- package/dist/packem_shared/DOCS_SERVER_NAME-BNYVF715.mjs +0 -1
- package/dist/packem_shared/connectStdio-CDsCt1IT.mjs +0 -1
- package/dist/packem_shared/createMcpFetchHandler-B9koGwkW.mjs +0 -1
- package/dist/packem_shared/serveStateless-Db7J3pAO.mjs +0 -1
package/README.md
CHANGED
|
@@ -75,11 +75,13 @@ Part of the [Lunora](https://github.com/anolilab/lunora) framework — a type-sa
|
|
|
75
75
|
The five `lunora_get_*` observability tools are read-only, but they surface the
|
|
76
76
|
deployment's **operational data** — log lines, request metadata, and grouped
|
|
77
77
|
error messages, all of which may contain user data, and all of which land in the
|
|
78
|
-
model's context (and therefore at its provider). They are
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
model's context (and therefore at its provider). They are therefore **off by
|
|
79
|
+
default**: set `LUNORA_MCP_ALLOW_OBSERVABILITY=1` (or pass
|
|
80
|
+
`allowObservability: true`) to expose them. Without it they are omitted from
|
|
81
|
+
`ListTools` entirely and refused at dispatch, the same omit-don't-refuse rule the
|
|
82
|
+
write tools use. They are independent of `--allow-writes`, which is about
|
|
83
|
+
changing data, not reading operational data — and independent of the admin
|
|
84
|
+
bearer, which every tool already needs, so holding it is not the opt-in.
|
|
83
85
|
|
|
84
86
|
They return `structuredContent` alongside the usual text block, described by each
|
|
85
87
|
tool's `outputSchema` (MCP revision `2025-06-18` and later; older clients keep
|
|
@@ -240,6 +242,8 @@ await connectLocalStdio({
|
|
|
240
242
|
|
|
241
243
|
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.
|
|
242
244
|
|
|
245
|
+
The observability tools are the exception: their gate is snapshotted when the tool list is built, so a session started before `lunora dev` never advertises them (and the cached list keeps them absent afterwards). Restart the MCP server once the dev server is up.
|
|
246
|
+
|
|
243
247
|
> This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs)**.
|
|
244
248
|
|
|
245
249
|
## Related
|
package/dist/bin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{parseAgentsEnv as
|
|
3
|
-
`),new o("LUNORA_URL environment variable is required",1);const a=r.LUNORA_ADMIN_TOKEN;if(a===void 0||a.length===0)throw
|
|
4
|
-
`),new o("LUNORA_ADMIN_TOKEN environment variable is required",1);const c=Number(r.LUNORA_MCP_AGENT_TIMEOUT_MS),
|
|
5
|
-
`),new o(`failed to start — ${
|
|
2
|
+
import{parseAgentsEnv as N}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{connectStdio as E}from"./packem_shared/connectStdio-D2XPJMyB.mjs";const l=new Set(["1","on","true","yes"]),_=r=>r!==void 0&&l.has(r.trim().toLowerCase());class o extends Error{code;constructor(t,s){super(t),this.name="BinError",this.code=s}}const O=async(r,t={})=>{const s=t.connect??E,i=t.writeError??(e=>{process.stderr.write(e)}),n=r.LUNORA_URL;if(n===void 0||n.length===0)throw i(`lunora-mcp: LUNORA_URL environment variable is required
|
|
3
|
+
`),new o("LUNORA_URL environment variable is required",1);const a=r.LUNORA_ADMIN_TOKEN;if(a===void 0||a.length===0)throw i(`lunora-mcp: LUNORA_ADMIN_TOKEN environment variable is required (every tool reads admin-gated routes)
|
|
4
|
+
`),new o("LUNORA_ADMIN_TOKEN environment variable is required",1);const c=Number(r.LUNORA_MCP_AGENT_TIMEOUT_MS),A=Number.isFinite(c)&&c>0?c:void 0;try{await s({agents:N(r.LUNORA_MCP_AGENTS),allowAgents:_(r.LUNORA_MCP_ALLOW_AGENTS),allowObservability:_(r.LUNORA_MCP_ALLOW_OBSERVABILITY),allowWrites:_(r.LUNORA_MCP_ALLOW_WRITES),token:a,url:n,...A===void 0?{}:{agentMaxWaitMs:A}})}catch(e){const L=e instanceof Error?e.message:String(e);throw i(`lunora-mcp: failed to start — ${L}
|
|
5
|
+
`),new o(`failed to start — ${L}`,1)}};try{await O(process.env)}catch(r){process.exit(r instanceof o?r.code:1)}
|
package/dist/docs/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-
|
|
1
|
+
import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-Qu1g6Pf4.mjs";
|
|
2
2
|
export type {
|
|
3
3
|
/**
|
|
4
4
|
* `@lunora/mcp/docs` — the documentation tool surface: `lunora_search_docs`,
|
|
@@ -49,7 +49,7 @@ c as McpResourceSummary,
|
|
|
49
49
|
* (`createToolServer`) is exported from the package root, so a consumer using
|
|
50
50
|
* both entries gets one implementation rather than two copies.
|
|
51
51
|
*/
|
|
52
|
-
d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-
|
|
52
|
+
d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-Qu1g6Pf4.mjs";
|
|
53
53
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
54
54
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
55
55
|
/**
|
|
@@ -135,7 +135,7 @@ declare const DOCS_SERVER_NAME = "lunora-docs";
|
|
|
135
135
|
interface DocsMcpServerOptions {
|
|
136
136
|
/** The documentation source the tools read. */
|
|
137
137
|
index: DocsIndex;
|
|
138
|
-
/** Largest accepted request body. Defaults to
|
|
138
|
+
/** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES`, re-exported at the foot of this module. */
|
|
139
139
|
maxRequestBytes?: number;
|
|
140
140
|
/**
|
|
141
141
|
* Version reported in the handshake. Defaults to `"0.0.0"` — a docs site
|
package/dist/docs/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-
|
|
1
|
+
import { M as McpResourceProvider, a as McpFetchHandler, T as ToolDefinition, b as McpTool } from "../packem_shared/serve-stateless.d-Qu1g6Pf4.js";
|
|
2
2
|
export type {
|
|
3
3
|
/**
|
|
4
4
|
* `@lunora/mcp/docs` — the documentation tool surface: `lunora_search_docs`,
|
|
@@ -49,7 +49,7 @@ c as McpResourceSummary,
|
|
|
49
49
|
* (`createToolServer`) is exported from the package root, so a consumer using
|
|
50
50
|
* both entries gets one implementation rather than two copies.
|
|
51
51
|
*/
|
|
52
|
-
d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-
|
|
52
|
+
d as McpServerInfo, e as ToolInputSchema, f as ToolResult } from "../packem_shared/serve-stateless.d-Qu1g6Pf4.js";
|
|
53
53
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
54
54
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
55
55
|
/**
|
|
@@ -135,7 +135,7 @@ declare const DOCS_SERVER_NAME = "lunora-docs";
|
|
|
135
135
|
interface DocsMcpServerOptions {
|
|
136
136
|
/** The documentation source the tools read. */
|
|
137
137
|
index: DocsIndex;
|
|
138
|
-
/** Largest accepted request body. Defaults to
|
|
138
|
+
/** Largest accepted request body, in bytes. Defaults to `DEFAULT_MAX_REQUEST_BYTES`, re-exported at the foot of this module. */
|
|
139
139
|
maxRequestBytes?: number;
|
|
140
140
|
/**
|
|
141
141
|
* Version reported in the handshake. Defaults to `"0.0.0"` — a docs site
|
package/dist/docs/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toDocsSearchHits as e}from"../packem_shared/toDocsSearchHits-CBLmtWXt.mjs";import{DEFAULT_DOCS_BASE_URL as D,createRemoteDocsIndex as t}from"../packem_shared/DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{DOCS_URI_SCHEME as s,docsResources as S,fromDocsUri as E,toDocsUri as I}from"../packem_shared/DOCS_URI_SCHEME-CFANc3vr.mjs";import{DOCS_SERVER_NAME as R,createDocsMcpFetchHandler as p,createDocsMcpServer as A}from"../packem_shared/DOCS_SERVER_NAME-
|
|
1
|
+
import{toDocsSearchHits as e}from"../packem_shared/toDocsSearchHits-CBLmtWXt.mjs";import{DEFAULT_DOCS_BASE_URL as D,createRemoteDocsIndex as t}from"../packem_shared/DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{DOCS_URI_SCHEME as s,docsResources as S,fromDocsUri as E,toDocsUri as I}from"../packem_shared/DOCS_URI_SCHEME-CFANc3vr.mjs";import{DOCS_SERVER_NAME as R,createDocsMcpFetchHandler as p,createDocsMcpServer as A}from"../packem_shared/DOCS_SERVER_NAME-hHomMT1b.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-BqSYN5vr.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,6 +1,6 @@
|
|
|
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-
|
|
3
|
-
export { type d as McpServerInfo, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-
|
|
2
|
+
import { T as ToolDefinition, f as ToolResult, e as ToolInputSchema, a as McpFetchHandler, b as McpTool } from "./packem_shared/serve-stateless.d-Qu1g6Pf4.mjs";
|
|
3
|
+
export { type d as McpServerInfo, type S as ServeStatelessOptions, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-Qu1g6Pf4.mjs";
|
|
4
4
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
5
|
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
6
6
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
@@ -18,27 +18,28 @@ declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
|
|
|
18
18
|
* The tools this server advertises, in three tiers:
|
|
19
19
|
*
|
|
20
20
|
* - the read-only surface, always exposed;
|
|
21
|
-
* - the observability surface, exposed only when
|
|
22
|
-
* read-only, but it
|
|
23
|
-
*
|
|
21
|
+
* - the observability surface, exposed only when `allowObservability` is set —
|
|
22
|
+
* read-only, but every row it returns (log lines, request metadata, grouped
|
|
23
|
+
* error messages) is user data that lands in the model's context and therefore
|
|
24
|
+
* at its provider, so it is opt-in rather than implied by holding a token;
|
|
24
25
|
* - the write surface, exposed only when `allowWrites` is set.
|
|
25
26
|
*
|
|
26
27
|
* Both gates OMIT rather than refuse: an AI agent can't invoke what it can't
|
|
27
28
|
* see. Dispatch re-checks both in {@link callTool}, so the guarantee does not
|
|
28
29
|
* depend on a client honouring the advertised list.
|
|
29
30
|
*/
|
|
30
|
-
declare const toolDefinitions: (allowWrites: boolean,
|
|
31
|
+
declare const toolDefinitions: (allowWrites: boolean, allowObservability?: boolean) => ReadonlyArray<ToolDefinition>;
|
|
31
32
|
/**
|
|
32
33
|
* Dispatch a tool call against `client`. Unknown tools and thrown errors are
|
|
33
34
|
* returned as `isError` results (rather than rejections) so the calling model
|
|
34
35
|
* sees the failure as tool output, per the MCP convention.
|
|
35
36
|
*
|
|
36
|
-
* `allowWrites` gates the mutation/action tools and `
|
|
37
|
-
* observability tools: when either is false a call to the gated tool is
|
|
38
|
-
* even if the client somehow names it, so both guarantees hold at
|
|
39
|
-
* just in the advertised tool list.
|
|
37
|
+
* `allowWrites` gates the mutation/action tools and `allowObservability` gates
|
|
38
|
+
* the observability tools: when either is false a call to the gated tool is
|
|
39
|
+
* refused even if the client somehow names it, so both guarantees hold at
|
|
40
|
+
* dispatch, not just in the advertised tool list.
|
|
40
41
|
*/
|
|
41
|
-
declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean,
|
|
42
|
+
declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, allowObservability?: boolean) => Promise<ToolResult>;
|
|
42
43
|
/**
|
|
43
44
|
* Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
|
|
44
45
|
* MCP tool an external agent can call. The capability boundary is the MCP-server
|
|
@@ -115,6 +116,18 @@ interface LunoraMcpServerOptions {
|
|
|
115
116
|
* opted in. Only takes effect together with a non-empty `agents` list.
|
|
116
117
|
*/
|
|
117
118
|
allowAgents?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Expose the observability tools (`lunora_get_logs`, `lunora_get_issues`,
|
|
121
|
+
* `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
122
|
+
* `lunora_get_migration_status`). Defaults to `false`, mirroring
|
|
123
|
+
* `allowWrites`: they are read-only, but every row they return — log lines,
|
|
124
|
+
* request metadata, grouped error messages — is production user data that
|
|
125
|
+
* lands in the model's context and therefore at its provider. Holding the
|
|
126
|
+
* admin bearer is not consent to ship that, so it is a separate opt-in;
|
|
127
|
+
* without it the tools are omitted from the advertised list AND refused at
|
|
128
|
+
* dispatch. Only takes effect when a `token` resolved.
|
|
129
|
+
*/
|
|
130
|
+
allowObservability?: boolean;
|
|
118
131
|
/**
|
|
119
132
|
* Expose the write tools (`lunora_run_mutation` / `lunora_run_action`).
|
|
120
133
|
* Defaults to `false`: the server is READ-ONLY unless explicitly opted in,
|
|
@@ -401,8 +414,9 @@ export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type AuthedMcpFetchHand
|
|
|
401
414
|
* writes are enabled), each backed by `LunoraClient` over HTTP RPC. It also
|
|
402
415
|
* exposes the deployment's observability reads (`lunora_get_logs`,
|
|
403
416
|
* `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
404
|
-
* `lunora_get_migration_status`)
|
|
405
|
-
*
|
|
417
|
+
* `lunora_get_migration_status`) when `allowObservability` (or the
|
|
418
|
+
* LUNORA_MCP_ALLOW_OBSERVABILITY env) is set — read-only, but they return
|
|
419
|
+
* production user data, so they are omitted entirely without it. The server is
|
|
406
420
|
* read-only by default — the write tools are exposed only when `allowWrites`
|
|
407
421
|
* (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
|
|
408
422
|
* allowlisted against the deployment's discovered public functions. It can also
|
|
@@ -432,8 +446,9 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
|
|
|
432
446
|
* writes are enabled), each backed by `LunoraClient` over HTTP RPC. It also
|
|
433
447
|
* exposes the deployment's observability reads (`lunora_get_logs`,
|
|
434
448
|
* `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
435
|
-
* `lunora_get_migration_status`)
|
|
436
|
-
*
|
|
449
|
+
* `lunora_get_migration_status`) when `allowObservability` (or the
|
|
450
|
+
* LUNORA_MCP_ALLOW_OBSERVABILITY env) is set — read-only, but they return
|
|
451
|
+
* production user data, so they are omitted entirely without it. The server is
|
|
437
452
|
* read-only by default — the write tools are exposed only when `allowWrites`
|
|
438
453
|
* (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
|
|
439
454
|
* allowlisted against the deployment's discovered public functions. It can also
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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-
|
|
3
|
-
export { type d as McpServerInfo, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-
|
|
2
|
+
import { T as ToolDefinition, f as ToolResult, e as ToolInputSchema, a as McpFetchHandler, b as McpTool } from "./packem_shared/serve-stateless.d-Qu1g6Pf4.js";
|
|
3
|
+
export { type d as McpServerInfo, type S as ServeStatelessOptions, g as createToolServer, s as serveStateless } from "./packem_shared/serve-stateless.d-Qu1g6Pf4.js";
|
|
4
4
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
5
|
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
6
6
|
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
@@ -18,27 +18,28 @@ declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
|
|
|
18
18
|
* The tools this server advertises, in three tiers:
|
|
19
19
|
*
|
|
20
20
|
* - the read-only surface, always exposed;
|
|
21
|
-
* - the observability surface, exposed only when
|
|
22
|
-
* read-only, but it
|
|
23
|
-
*
|
|
21
|
+
* - the observability surface, exposed only when `allowObservability` is set —
|
|
22
|
+
* read-only, but every row it returns (log lines, request metadata, grouped
|
|
23
|
+
* error messages) is user data that lands in the model's context and therefore
|
|
24
|
+
* at its provider, so it is opt-in rather than implied by holding a token;
|
|
24
25
|
* - the write surface, exposed only when `allowWrites` is set.
|
|
25
26
|
*
|
|
26
27
|
* Both gates OMIT rather than refuse: an AI agent can't invoke what it can't
|
|
27
28
|
* see. Dispatch re-checks both in {@link callTool}, so the guarantee does not
|
|
28
29
|
* depend on a client honouring the advertised list.
|
|
29
30
|
*/
|
|
30
|
-
declare const toolDefinitions: (allowWrites: boolean,
|
|
31
|
+
declare const toolDefinitions: (allowWrites: boolean, allowObservability?: boolean) => ReadonlyArray<ToolDefinition>;
|
|
31
32
|
/**
|
|
32
33
|
* Dispatch a tool call against `client`. Unknown tools and thrown errors are
|
|
33
34
|
* returned as `isError` results (rather than rejections) so the calling model
|
|
34
35
|
* sees the failure as tool output, per the MCP convention.
|
|
35
36
|
*
|
|
36
|
-
* `allowWrites` gates the mutation/action tools and `
|
|
37
|
-
* observability tools: when either is false a call to the gated tool is
|
|
38
|
-
* even if the client somehow names it, so both guarantees hold at
|
|
39
|
-
* just in the advertised tool list.
|
|
37
|
+
* `allowWrites` gates the mutation/action tools and `allowObservability` gates
|
|
38
|
+
* the observability tools: when either is false a call to the gated tool is
|
|
39
|
+
* refused even if the client somehow names it, so both guarantees hold at
|
|
40
|
+
* dispatch, not just in the advertised tool list.
|
|
40
41
|
*/
|
|
41
|
-
declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean,
|
|
42
|
+
declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, allowObservability?: boolean) => Promise<ToolResult>;
|
|
42
43
|
/**
|
|
43
44
|
* Agent exposure for the MCP server: a durable `@lunora/agent` run fronted as an
|
|
44
45
|
* MCP tool an external agent can call. The capability boundary is the MCP-server
|
|
@@ -115,6 +116,18 @@ interface LunoraMcpServerOptions {
|
|
|
115
116
|
* opted in. Only takes effect together with a non-empty `agents` list.
|
|
116
117
|
*/
|
|
117
118
|
allowAgents?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Expose the observability tools (`lunora_get_logs`, `lunora_get_issues`,
|
|
121
|
+
* `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
122
|
+
* `lunora_get_migration_status`). Defaults to `false`, mirroring
|
|
123
|
+
* `allowWrites`: they are read-only, but every row they return — log lines,
|
|
124
|
+
* request metadata, grouped error messages — is production user data that
|
|
125
|
+
* lands in the model's context and therefore at its provider. Holding the
|
|
126
|
+
* admin bearer is not consent to ship that, so it is a separate opt-in;
|
|
127
|
+
* without it the tools are omitted from the advertised list AND refused at
|
|
128
|
+
* dispatch. Only takes effect when a `token` resolved.
|
|
129
|
+
*/
|
|
130
|
+
allowObservability?: boolean;
|
|
118
131
|
/**
|
|
119
132
|
* Expose the write tools (`lunora_run_mutation` / `lunora_run_action`).
|
|
120
133
|
* Defaults to `false`: the server is READ-ONLY unless explicitly opted in,
|
|
@@ -401,8 +414,9 @@ export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, type AuthedMcpFetchHand
|
|
|
401
414
|
* writes are enabled), each backed by `LunoraClient` over HTTP RPC. It also
|
|
402
415
|
* exposes the deployment's observability reads (`lunora_get_logs`,
|
|
403
416
|
* `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
404
|
-
* `lunora_get_migration_status`)
|
|
405
|
-
*
|
|
417
|
+
* `lunora_get_migration_status`) when `allowObservability` (or the
|
|
418
|
+
* LUNORA_MCP_ALLOW_OBSERVABILITY env) is set — read-only, but they return
|
|
419
|
+
* production user data, so they are omitted entirely without it. The server is
|
|
406
420
|
* read-only by default — the write tools are exposed only when `allowWrites`
|
|
407
421
|
* (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
|
|
408
422
|
* allowlisted against the deployment's discovered public functions. It can also
|
|
@@ -432,8 +446,9 @@ type CallAgentToolOptions, LOCAL_SERVER_NAME, type LocalDeployment, type LocalDe
|
|
|
432
446
|
* writes are enabled), each backed by `LunoraClient` over HTTP RPC. It also
|
|
433
447
|
* exposes the deployment's observability reads (`lunora_get_logs`,
|
|
434
448
|
* `lunora_get_issues`, `lunora_get_advisories`, `lunora_get_query_insights`,
|
|
435
|
-
* `lunora_get_migration_status`)
|
|
436
|
-
*
|
|
449
|
+
* `lunora_get_migration_status`) when `allowObservability` (or the
|
|
450
|
+
* LUNORA_MCP_ALLOW_OBSERVABILITY env) is set — read-only, but they return
|
|
451
|
+
* production user data, so they are omitted entirely without it. The server is
|
|
437
452
|
* read-only by default — the write tools are exposed only when `allowWrites`
|
|
438
453
|
* (or the `LUNORA_MCP_ALLOW_WRITES` env) is set, and every run tool is
|
|
439
454
|
* allowlisted against the deployment's discovered public functions. It can also
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as c,callAgentTool as T,parseAgentsEnv as a}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-
|
|
1
|
+
import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as c,callAgentTool as T,parseAgentsEnv as a}from"./packem_shared/AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{createAuthedMcpFetchHandler as E,mcpTokenScopes as S}from"./packem_shared/createAuthedMcpFetchHandler-D9RPx2pl.mjs";import{createToolServer as O}from"./packem_shared/createToolServer-BtGuPyMU.mjs";import{createMcpFetchHandler as n}from"./packem_shared/createMcpFetchHandler-oQqcp6i5.mjs";import{LOCAL_SERVER_NAME as A,NO_DEPLOYMENT_MESSAGE as I,connectLocalStdio as f,createLocalMcpServer as L,localTools as m}from"./packem_shared/LOCAL_SERVER_NAME-BPPFBI2g.mjs";import{createPaidMcpServer as x}from"./packem_shared/createPaidMcpServer-XUo-pELu.mjs";import{connectStdio as i,createLunoraMcpServer as D}from"./packem_shared/connectStdio-D2XPJMyB.mjs";import{READ_ONLY_TOOL_DEFINITIONS as v,WRITE_TOOL_DEFINITIONS as R,callTool as F,toolDefinitions as g}from"./packem_shared/READ_ONLY_TOOL_DEFINITIONS-CMhsP7pi.mjs";import{O as G}from"./packem_shared/observability-tools-Ce0YK58T.mjs";import{serveStateless as P}from"./packem_shared/serveStateless-B3nFSJ-z.mjs";export{r as AGENT_RUN_INPUT_SCHEMA,t as AGENT_STATUS_TOOL_NAME,A as LOCAL_SERVER_NAME,I as NO_DEPLOYMENT_MESSAGE,G as OBSERVABILITY_TOOL_DEFINITIONS,v as READ_ONLY_TOOL_DEFINITIONS,R as WRITE_TOOL_DEFINITIONS,c as agentToolDefinitions,T as callAgentTool,F as callTool,f as connectLocalStdio,i as connectStdio,E as createAuthedMcpFetchHandler,L as createLocalMcpServer,D as createLunoraMcpServer,n as createMcpFetchHandler,x as createPaidMcpServer,O as createToolServer,m as localTools,S as mcpTokenScopes,a as parseAgentsEnv,P as serveStateless,g as toolDefinitions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const b="agents:agentRun",T="agents:agentThread",H="agents:agentMessages",p="lunora_agent_status",_=new Set(["awaiting_input","cancelled","error","idle"]),O=6e4,x=600,P={properties:{prompt:{description:"The task or message for the agent.",type:"string"},threadKey:{description:"Reuse to continue a conversation; omit to start a new thread.",type:"string"},title:{description:"Optional thread title (first run only).",type:"string"}},required:["prompt"],type:"object"},U={properties:{threadKey:{description:"The thread key returned by an agent tool call.",type:"string"}},required:["threadKey"],type:"object"},g=t=>t.toolName??`agent_${t.name}`,C=t=>{if(t===void 0)return[];const n=[];for(const e of t.split(";")){const r=e.trim();if(r.length===0)continue;const o=r.indexOf(":");if(o<=0)continue;const a=r.slice(0,o).trim(),s=r.slice(o+1).trim();a.length===0||s.length===0||n.push({description:s,name:a})}return n},L={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},K=(t,n)=>n!==!0||t.length===0?[]:[...t.map(r=>({annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:`Run the ${r.name} agent (starts a durable run)`},description:`${r.description} Starts a durable agent run and returns its final answer.`,inputSchema:P,name:g(r)})),{annotations:{...L,title:"Check a durable agent run"},description:"Check the status of a durable agent run (and its answer if finished) by its threadKey.",inputSchema:U,name:p}],G=(t,n)=>t===p||n.some(e=>g(e)===t),R=()=>`mcp-${crypto.randomUUID()}`,d=t=>({__lunoraRef:t}),k=async t=>{await new Promise(n=>{setTimeout(n,t)})},c=t=>({content:[{text:JSON.stringify(t,void 0,2),type:"text"}]}),i=t=>({content:[{text:t,type:"text"}],isError:!0}),l=(t,n)=>{const e=t[n];return typeof e=="string"&&e.length>0?e:void 0},A=t=>{for(let n=t.length-1;n>=0;n-=1){const e=t[n],r=e?.toolCalls,o=Array.isArray(r)&&r.length>0;if(e?.role==="assistant"&&!o)return typeof e.content=="string"?e.content:""}return""},w=t=>t!==null&&typeof t=="object"&&typeof t.status=="string"?t.status:"unknown",S=async(t,n,e,r)=>{const o=await t.query(d(H),{key:n});if(e==="error"){const a=r!==null&&typeof r=="object"?r.error:void 0;return c({error:typeof a=="string"?a:"the agent run failed",status:e,threadKey:n})}return c(e==="awaiting_input"?{hint:"This run is paused on a human-in-the-loop tool approval. Approve or reject it in the app that owns the agent; MCP cannot supply the input. Poll lunora_agent_status with this threadKey afterwards.",status:e,text:A(o),threadKey:n}:{status:e,text:A(o),threadKey:n})},I=async(t,n)=>{const e=l(n,"threadKey");if(e===void 0)return i('"threadKey" is required and must be a non-empty string');const r=await t.query(d(T),{key:e}),o=w(r);return _.has(o)?S(t,e,o,r):c({status:o==="unknown"?"running":o,threadKey:e})},q=async(t,n,e,r)=>{try{if(r.allowAgents!==!0)return i(`tool "${n}" is disabled: agent tools are off. Enable them with the LUNORA_MCP_ALLOW_AGENTS env var.`);if(n===p)return await I(t,e);const o=r.exposures.find(u=>g(u)===n);if(o===void 0)return i(`agent tool "${n}" is not exposed by this MCP server`);const a=l(e,"prompt");if(a===void 0)return i('"prompt" is required and must be a non-empty string');const s=l(e,"threadKey")??R(),h=l(e,"title"),{id:E}=await t.mutation(d(b),{agent:o.name,input:a,threadKey:s,...h===void 0?{}:{title:h}}),f=r.pollIntervalMs??x,N=r.maxWaitMs??O,v=r.wait??k,M=Math.max(1,Math.ceil(N/f));for(let u=0;u<M;u+=1){const y=await t.query(d(T),{key:s}),m=w(y);if(_.has(m))return await S(t,s,m,y);await v(f)}return c({hint:"call lunora_agent_status with this threadKey to poll for the answer",runId:E,status:"running",threadKey:s})}catch(o){const a=o instanceof Error?o.message:String(o);return i(a)}};export{P as AGENT_RUN_INPUT_SCHEMA,p as AGENT_STATUS_TOOL_NAME,K as agentToolDefinitions,q as callAgentTool,A as finalAnswer,G as isAgentToolName,C as parseAgentsEnv};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const T=10,A=50,h="docs",L=500,_={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"},m={properties:{url:{description:'Site-relative page URL from a search hit, e.g. "/docs/sharding"',type:"string"}},required:["url"],type:"object"},g={properties:{},type:"object"},s={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},u={annotations:{...s,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:_,name:"lunora_search_docs"},l={annotations:{...s,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:m,name:"lunora_get_doc"},d={annotations:{...s,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:g,name:"lunora_list_docs"},y=[u,l,d],r=n=>({content:[{text:JSON.stringify(n,void 0,2),type:"text"}]}),p=n=>({content:[{text:n,type:"text"}],isError:!0}),a=512,c=(n,t)=>{const e=n[t];if(typeof e!="string"||e.trim().length===0)throw new TypeError(`"${t}" is required and must be a non-empty string`);if(e.length>a)throw new RangeError(`"${t}" must be at most ${String(a)} characters`);return e.trim()},f=n=>{const t=typeof n=="string"?Number(n):n;return typeof t!="number"||!Number.isFinite(t)?10:Math.min(50,Math.max(1,Math.floor(t)))},S=(n,t)=>{let e;try{e=decodeURIComponent(n)}catch{throw new RangeError(`"url" contains a malformed percent-escape: ${t}`)}if(e.includes("%")||e.includes("\\"))throw new RangeError(`"url" must not contain ".." or encoded segments: ${t}`);for(const o of e.split("/"))if(o===".."||o===".")throw new RangeError(`"url" must not contain ".." or encoded segments: ${t}`)},E=n=>{let t=n.trim().replaceAll("\\","/");if(t.startsWith("http://")||t.startsWith("https://")){const e=t.slice(t.indexOf("//")+2),o=e.indexOf("/");t=o===-1?"/":e.slice(o)}for(const e of["?","#"]){const o=t.indexOf(e);o!==-1&&(t=t.slice(0,o))}for(;t.endsWith("/")&&t.length>1;)t=t.slice(0,-1);return t.startsWith("/")||(t=t.startsWith("docs/")?`/${t}`:`/${h}/${t}`),S(t,n),t},O=n=>[{definition:u,handle:async t=>{const e=c(t,"query"),o=f(t.limit),i=(await n.search(e)).slice(0,o);return i.length===0?r({hits:[],note:`no documentation matched "${e}" — try fewer or more general terms, or lunora_list_docs to browse`}):r({hits:i})}},{definition:l,handle:async t=>{const e=E(c(t,"url")),o=await n.getPage(e);return o===void 0?p(`documentation page not found: ${e}. Use lunora_search_docs or lunora_list_docs to find a valid url.`):{content:[{text:`# ${o.title} (${o.url})
|
|
2
|
+
|
|
3
|
+
${o.content}`,type:"text"}]}}},{definition:d,handle:async()=>{const t=await n.listPages();return t.length>500?r({note:`showing the first ${String(500)} of ${String(t.length)} pages — use lunora_search_docs to find the rest`,pages:t.slice(0,500)}):r(t)}}];export{T as DEFAULT_SEARCH_LIMIT,y as DOCS_TOOL_DEFINITIONS,a as MAX_ARGUMENT_LENGTH,L as MAX_LISTED_PAGES,A as MAX_SEARCH_LIMIT,O as docsTools,E as normalizeDocUrl};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createToolServer as o}from"./createToolServer-BtGuPyMU.mjs";import{serveStateless as s}from"./serveStateless-B3nFSJ-z.mjs";import{DEFAULT_MAX_REQUEST_BYTES as i}from"./serveStateless-B3nFSJ-z.mjs";import{docsResources as t}from"./DOCS_URI_SCHEME-CFANc3vr.mjs";import{docsTools as c}from"./DEFAULT_SEARCH_LIMIT-BqSYN5vr.mjs";const m="lunora-docs",n=e=>({name:m,version:e??"0.0.0"}),a=e=>o(n(e.version),c(e.index),t(e.index)),v=e=>r=>s(a(e),r,{maxRequestBytes:e.maxRequestBytes});export{i as DEFAULT_MAX_REQUEST_BYTES,m as DOCS_SERVER_NAME,v as createDocsMcpFetchHandler,a as createDocsMcpServer};
|
package/dist/packem_shared/{LOCAL_SERVER_NAME-BwlBnhX-.mjs → LOCAL_SERVER_NAME-BPPFBI2g.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraClient as f}from"@lunora/client";import{StdioServerTransport as p}from"@modelcontextprotocol/sdk/server/stdio.js";import{createToolServer as m}from"./createToolServer-BtGuPyMU.mjs";import{createRemoteDocsIndex as v}from"./DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{docsResources as h}from"./DOCS_URI_SCHEME-CFANc3vr.mjs";import{docsTools as y}from"./DEFAULT_SEARCH_LIMIT-
|
|
1
|
+
import{LunoraClient as f}from"@lunora/client";import{StdioServerTransport as p}from"@modelcontextprotocol/sdk/server/stdio.js";import{createToolServer as m}from"./createToolServer-BtGuPyMU.mjs";import{createRemoteDocsIndex as v}from"./DEFAULT_DOCS_BASE_URL-CZ3fVsSc.mjs";import{docsResources as h}from"./DOCS_URI_SCHEME-CFANc3vr.mjs";import{docsTools as y}from"./DEFAULT_SEARCH_LIMIT-BqSYN5vr.mjs";import{toolDefinitions as E,callTool as R}from"./READ_ONLY_TOOL_DEFINITIONS-CMhsP7pi.mjs";const O=(e,n)=>{if(e.size<n)return;const t=e.keys().next().value;t!==void 0&&e.delete(t)},l=e=>e.docs===!1?void 0:v({...e.docs?.baseUrl===void 0?{}:{baseUrl:e.docs.baseUrl},...e.fetch===void 0?{}:{fetch:e.fetch}}),_="lunora",S="no Lunora dev server is running for this project — start one with `lunora dev`, then call this tool again (call lunora_dev_status to check).",T=8,d=e=>{const n=new Map;return t=>{const r=JSON.stringify([t.url,t.token??""]),c=n.get(r);if(c!==void 0)return c;const o=new f({fetch:e,url:t.url});return t.token!==void 0&&t.token.length>0&&o.setAuthToken(t.token),O(n,T),n.set(r,o),o}},i=e=>e?.token!==void 0&&e.token.length>0,C=(e,n,t)=>{const r=typeof e=="function"?e:()=>e;return E(n,i(r())).map(c=>({definition:c,handle:async o=>{const s=r();return s===void 0?{content:[{text:S,type:"text"}],isError:!0}:R(t(s),c.name,o,n,i(s))}}))},P="lunora-spec:openrpc",g="lunora-spec:openapi",a=[{description:"The deployment's generated OpenRPC 1.x document — every RPC function's path, kind, and argument schema in one read, instead of list_functions plus one get_function_schema call per function.",fetch:async e=>e.fetchOpenRpc(),name:"OpenRPC specification",uri:P},{description:"The deployment's generated OpenAPI 3.1 document.",fetch:async e=>e.fetchOpenApi(),name:"OpenAPI specification",uri:g}],k=(e,n)=>{const t=typeof e=="function"?e:()=>e,r=async c=>{const o=t();if(o!==void 0)try{return await c.fetch(n(o))}catch{return}};return{list:async()=>(await Promise.all(a.map(async o=>await r(o)===void 0?void 0:{description:o.description,mimeType:"application/json",name:o.name,uri:o.uri}))).filter(o=>o!==void 0),read:async c=>{const o=a.find(u=>u.uri===c);if(o===void 0)return;const s=await r(o);return s===void 0?void 0:{mimeType:"application/json",text:JSON.stringify(s,void 0,2)}}}},w=e=>({list:async()=>(await Promise.all(e.map(async t=>t.list()))).flat(),read:async n=>{for(const t of e){const r=await t.read(n);if(r!==void 0)return r}}}),x=(e,n)=>{const t=[],r=l(e);return r!==void 0&&t.push(...y(r)),t.push(...e.extraTools??[]),e.deployment!==void 0&&t.push(...C(e.deployment,e.allowWrites??!1,n??d(e.fetch))),t},A=(e={})=>{const n=l(e),t=[];n!==void 0&&t.push(h(n));const r=d(e.fetch);return e.deployment!==void 0&&t.push(k(e.deployment,r)),m({name:_,version:e.version??"0.0.0"},x(e,r),t.length===0?void 0:w(t))},j=async(e={})=>{const n=A(e);return await n.connect(new p),n};export{_ as LOCAL_SERVER_NAME,S as NO_DEPLOYMENT_MESSAGE,g as OPENAPI_RESOURCE_URI,P as OPENRPC_RESOURCE_URI,j as connectLocalStdio,A as createLocalMcpServer,x as localTools};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as
|
|
1
|
+
import{LunoraError as c}from"@lunora/errors";import{O as T,e as d,a as S,c as E,o as u}from"./observability-tools-Ce0YK58T.mjs";const m={properties:{args:{description:"Arguments object passed to the function",type:"object"},functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"},shardKey:{description:"Optional shard key when the function is .shardBy()-partitioned",type:"string"}},required:["functionPath"],type:"object"},g={properties:{},type:"object"},R={properties:{functionPath:{description:'Function reference, e.g. "messages:send"',type:"string"}},required:["functionPath"],type:"object"},l={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},I=[{annotations:{...l,title:"List deployment functions"},description:"List the deployment's public functions (queries, mutations, actions) with their kinds.",inputSchema:g,name:"lunora_list_functions"},{annotations:{...l,title:"List global tables"},description:"List the deployment's .global() tables with their row counts. Names and row counts only — no column shapes.",inputSchema:g,name:"lunora_list_tables"},{annotations:{...l,title:"Describe a function's arguments"},description:"Return a function's argument descriptors (name, validator kind, whether it is optional) and its kind, so a caller can construct a valid arguments object. Call lunora_list_functions first to discover available function paths.",inputSchema:R,name:"lunora_get_function_schema"},{annotations:{...l,title:"Run a query"},description:"Run a query and return its result. Read-only.",inputSchema:m,name:"lunora_run_query"}],b=[{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run a mutation (writes data)"},description:"Run a mutation and return its result. Writes data — use with care.",inputSchema:m,name:"lunora_run_mutation"},{annotations:{destructiveHint:!0,idempotentHint:!1,openWorldHint:!0,readOnlyHint:!1,title:"Run an action (may call external services)"},description:"Run an action and return its result. May call external services.",inputSchema:m,name:"lunora_run_action"}],L=new Set(b.map(t=>t.name)),D=(t,n=!1)=>[...I,...n===!0?T:[],...t===!0?b:[]],N=t=>{const{functionPath:n}=t;if(typeof n!="string"||n.length===0)throw new c("INTERNAL",'"functionPath" is required and must be a non-empty string');return n},O=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 n;try{n=JSON.parse(t)}catch{throw new c("BAD_REQUEST",'"args" must be a JSON object; received a string that is not valid JSON')}if(!O(n))throw new c("BAD_REQUEST",`"args" must be a JSON object; the provided string decoded to ${w(n)}`);return n}if(!O(t))throw new c("BAD_REQUEST",`"args" must be a JSON object, got ${w(t)}`);return t},f=t=>{const n=N(t),e=v(t.args),o=typeof t.shardKey=="string"&&t.shardKey.length>0?t.shardKey:void 0;return{args:e,functionPath:n,shardKey:o}},p=t=>({__lunoraRef:t}),P=3e4,h=new WeakMap,y=t=>{const n=Date.now(),e=h.get(t);if(e!==void 0&&e.expiresAt>n)return e.promise;const o=t.listFunctions().catch(i=>{throw h.get(t)?.promise===o&&h.delete(t),i});return h.set(t,{expiresAt:n+P,promise:o}),o},_=async(t,n,e)=>{const i=(await y(t)).find(r=>r.path===n);if(i===void 0)throw new c("NOT_FOUND",`function not found or not public: ${n}`);if(i.kind!==e)throw new c("BAD_REQUEST",`function ${n} is a ${i.kind}, not a ${e}`)},U=async(t,n,e,o=!1,i=!1)=>{try{if(o!==!0&&L.has(n))return d(`tool "${n}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`);if(S.has(n))return i!==!0?d(`tool "${n}" is disabled: it reads the deployment's logs, request metadata and grouped errors — user data that would land at the model provider. Enable it with the LUNORA_MCP_ALLOW_OBSERVABILITY env var.`):await E(t,n,e);switch(n){case"lunora_get_function_schema":{const r=N(e),s=(await y(t)).find(A=>A.path===r);return s===void 0?d(`function not found: ${r}`):u({args:s.args??[],kind:s.kind,path:s.path})}case"lunora_list_functions":return u(await y(t));case"lunora_list_tables":return u(await t.listGlobalTables());case"lunora_run_action":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"action"),u(await t.action(p(a),r,{shardKey:s}))}case"lunora_run_mutation":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"mutation"),u(await t.mutation(p(a),r,{shardKey:s}))}case"lunora_run_query":{const{args:r,functionPath:a,shardKey:s}=f(e);return await _(t,a,"query"),u(await t.query(p(a),r,{shardKey:s}))}default:return d(`unknown tool: ${n}`)}}catch(r){const a=r instanceof Error?r.message:String(r);return d(a)}};export{T as OBSERVABILITY_TOOL_DEFINITIONS,I as READ_ONLY_TOOL_DEFINITIONS,b as WRITE_TOOL_DEFINITIONS,U as callTool,D as toolDefinitions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFileSync as g}from"node:fs";import{dirname as c,join as d}from"node:path";import{fileURLToPath as w}from"node:url";import{LunoraClient as h}from"@lunora/client";import{LunoraError as u}from"@lunora/errors";import{Server as T}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as y}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as p,CallToolRequestSchema as A}from"@modelcontextprotocol/sdk/types.js";import{agentToolDefinitions as M,isAgentToolName as S,callAgentTool as k}from"./AGENT_RUN_INPUT_SCHEMA-hKbpa3Dg.mjs";import{toolDefinitions as N,callTool as L}from"./READ_ONLY_TOOL_DEFINITIONS-CMhsP7pi.mjs";const R=()=>{try{let e=c(w(import.meta.url));for(let r=0;r<8;r+=1){try{const a=g(d(e,"package.json"),"utf8"),t=JSON.parse(a);if(t.name==="@lunora/mcp"&&typeof t.version=="string"&&t.version.length>0)return t.version}catch{}const n=c(e);if(n===e)break;e=n}}catch{}return"0.0.0"},E={name:"lunora",version:R()},I=e=>{if(e.client!==void 0)return e.client;if(e.url===void 0)throw new u("INTERNAL","createLunoraMcpServer requires either a `client` or a `url`");if(e.token===void 0||e.token.length===0)throw new u("UNAUTHENTICATED","createLunoraMcpServer requires a `token` (LUNORA_ADMIN_TOKEN) alongside `url`: every tool reaches admin-gated /_lunora/admin/* routes, so an unauthenticated server can only 403. Writes stay off unless `allowWrites` is set.");const r=new h({fetch:e.fetch,url:e.url});return r.setAuthToken(e.token),r},W=e=>{const r=I(e),n=e.allowWrites??!1,a=e.allowAgents??!1,t=e.agents??[],m=typeof e.token=="string"&&e.token.length>0,s=e.allowObservability===!0&&m,o=new T(E,{capabilities:{tools:{}}});return o.setRequestHandler(p,()=>({tools:[...N(n,s),...M(t,a)]})),o.setRequestHandler(A,async f=>{const{arguments:v,name:l}=f.params,i=v??{};return S(l,t)?await k(r,l,i,{allowAgents:a,exposures:t,...e.agentMaxWaitMs===void 0?{}:{maxWaitMs:e.agentMaxWaitMs},...e.agentPollIntervalMs===void 0?{}:{pollIntervalMs:e.agentPollIntervalMs}}):await L(r,l,i,n,s)}),o},F=async e=>{const r=W(e);return await r.connect(new y),r};export{F as connectStdio,W as createLunoraMcpServer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{serveStateless as n}from"./serveStateless-
|
|
1
|
+
import{serveStateless as n}from"./serveStateless-B3nFSJ-z.mjs";import{createLunoraMcpServer as o}from"./connectStdio-D2XPJMyB.mjs";const a=e=>typeof e.scope!="string"?new Set:new Set(e.scope.split(" ").filter(r=>r!=="")),f=e=>e.protect(async(r,t)=>{const c=typeof e.server=="function"?await e.server(t):e.server;return await n(o(c),r)});export{f as createAuthedMcpFetchHandler,a as mcpTokenScopes};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{serveStateless as t}from"./serveStateless-B3nFSJ-z.mjs";import{createLunoraMcpServer as o}from"./connectStdio-D2XPJMyB.mjs";const p=e=>r=>t(o(e),r);export{p as createMcpFetchHandler,t as serveStateless};
|
package/dist/packem_shared/{createPaidMcpServer-DyNwdCq2.mjs → createPaidMcpServer-XUo-pELu.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as p}from"@lunora/errors";import{Server as v}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as w,CallToolRequestSchema as y}from"@modelcontextprotocol/sdk/types.js";import{serveStateless as g}from"./serveStateless-
|
|
1
|
+
import{LunoraError as p}from"@lunora/errors";import{Server as v}from"@modelcontextprotocol/sdk/server/index.js";import{ListToolsRequestSchema as w,CallToolRequestSchema as y}from"@modelcontextprotocol/sdk/types.js";import{serveStateless as g}from"./serveStateless-B3nFSJ-z.mjs";const S=(o,r,n,c)=>{const d=o.get(r);if(d!==void 0)return d;const i=n().catch(l=>{throw o.get(r)===i&&o.delete(r),l});return o.set(r,i),i},T=async()=>{try{return(await import("@lunora/x402/charge")).createChargeMiddleware}catch(o){throw new p("INTERNAL",`paid MCP tools need the optional peer "@lunora/x402" — install it alongside @lunora/mcp to charge for tools (${o instanceof Error?o.message:String(o)})`)}},E={name:"lunora-paid-mcp",version:"0.0.0"},M="tools/call",u=o=>{if(typeof o!="object"||o===null)return;const{method:r,params:n}=o;if(r!==M||typeof n!="object"||n===null)return;const{name:c}=n;return typeof c=="string"?c:void 0},R=()=>Response.json({error:"A JSON-RPC batch may not reference a paid MCP tool; send paid tools/call requests individually."},{status:400}),b=()=>Response.json({error:"The request body could not be parsed as JSON; a priced MCP tool is registered, so the call cannot be gated."},{status:400}),B=o=>{const r=new Map,n=new Map,c=new Map,d=o.serverInfo??E,i=(e,t,a)=>{if(r.has(e.name))throw new p("BAD_REQUEST",`MCP tool "${e.name}" is already registered.`);const s={description:e.description,inputSchema:e.inputSchema,name:e.name};e.annotations!==void 0&&(s.annotations=e.annotations),r.set(e.name,{definition:s,handler:t}),a!==void 0&&n.set(e.name,a)},l=()=>{const e=new v(d,{capabilities:{tools:{}}});return e.setRequestHandler(w,()=>({tools:[...r.values()].map(t=>t.definition)})),e.setRequestHandler(y,async t=>{const a=r.get(t.params.name);return a===void 0?{content:[{text:`unknown tool: ${t.params.name}`,type:"text"}],isError:!0}:await a.handler(t.params.arguments??{})}),e},h=(e,t)=>S(c,e,async()=>(await T())({...o.charge,price:t},{resource:e}));return{fetchHandler:async e=>{let t;try{t=await e.clone().json()}catch{t=void 0}const a=()=>g(l(),e,t===void 0?void 0:{parsedBody:t});if(t===void 0&&n.size>0&&e.method==="POST")return b();if(Array.isArray(t))return t.some(f=>n.has(u(f)??""))?R():a();const s=u(t),m=s===void 0?void 0:n.get(s);return s===void 0||m===void 0?a():(await h(s,m)).handle(e,a)},paidTool:(e,t)=>{i(e,t,e.price)},tool:(e,t)=>{i(e,t)}}};export{B as createPaidMcpServer};
|
package/dist/packem_shared/{serve-stateless.d-B_q8q39X.d.mts → serve-stateless.d-Qu1g6Pf4.d.mts}
RENAMED
|
@@ -111,6 +111,11 @@ interface McpResourceProvider {
|
|
|
111
111
|
declare const createToolServer: (info: McpServerInfo, tools: ReadonlyArray<McpTool>, resources?: McpResourceProvider) => Server;
|
|
112
112
|
/** A Web-Standard fetch handler: takes a `Request`, returns the MCP `Response`. */
|
|
113
113
|
type McpFetchHandler = (request: Request) => Promise<Response>;
|
|
114
|
+
/** What {@link serveStateless} accepts on top of the transport's own options. */
|
|
115
|
+
interface ServeStatelessOptions extends HandleRequestOptions {
|
|
116
|
+
/** Largest accepted request body, in bytes. Defaults to {@link DEFAULT_MAX_REQUEST_BYTES}. */
|
|
117
|
+
maxRequestBytes?: number;
|
|
118
|
+
}
|
|
114
119
|
/**
|
|
115
120
|
* Drive one request through a fresh **stateless** Streamable-HTTP transport bound
|
|
116
121
|
* to `server`, then tear both down.
|
|
@@ -121,6 +126,9 @@ type McpFetchHandler = (request: Request) => Promise<Response>;
|
|
|
121
126
|
* an in-flight body. Cleanup is fire-and-forget with swallowed rejections (a
|
|
122
127
|
* `.catch()`-terminated chain) so it never delays or masks the resolved response.
|
|
123
128
|
*
|
|
129
|
+
* The request is screened first ({@link screenRequest}); an oversized body or a
|
|
130
|
+
* JSON-RPC batch is refused here and no server is ever constructed for it.
|
|
131
|
+
*
|
|
124
132
|
* `options.parsedBody` lets a caller hand over a body it already read (e.g. the
|
|
125
133
|
* paid-tool gate, which peeks the JSON-RPC message to price the call) so the
|
|
126
134
|
* transport doesn't re-read a consumed stream.
|
|
@@ -129,5 +137,5 @@ type McpFetchHandler = (request: Request) => Promise<Response>;
|
|
|
129
137
|
* would otherwise skip it and leak a server + transport per failed request,
|
|
130
138
|
* which on a public endpoint is exactly the request an attacker can repeat.
|
|
131
139
|
*/
|
|
132
|
-
declare const serveStateless: (server: Server, request: Request, options?:
|
|
133
|
-
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 };
|
|
140
|
+
declare const serveStateless: (server: Server, request: Request, options?: ServeStatelessOptions) => Promise<Response>;
|
|
141
|
+
export { McpResourceProvider as M, ServeStatelessOptions as S, 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 };
|
package/dist/packem_shared/{serve-stateless.d-B_q8q39X.d.ts → serve-stateless.d-Qu1g6Pf4.d.ts}
RENAMED
|
@@ -111,6 +111,11 @@ interface McpResourceProvider {
|
|
|
111
111
|
declare const createToolServer: (info: McpServerInfo, tools: ReadonlyArray<McpTool>, resources?: McpResourceProvider) => Server;
|
|
112
112
|
/** A Web-Standard fetch handler: takes a `Request`, returns the MCP `Response`. */
|
|
113
113
|
type McpFetchHandler = (request: Request) => Promise<Response>;
|
|
114
|
+
/** What {@link serveStateless} accepts on top of the transport's own options. */
|
|
115
|
+
interface ServeStatelessOptions extends HandleRequestOptions {
|
|
116
|
+
/** Largest accepted request body, in bytes. Defaults to {@link DEFAULT_MAX_REQUEST_BYTES}. */
|
|
117
|
+
maxRequestBytes?: number;
|
|
118
|
+
}
|
|
114
119
|
/**
|
|
115
120
|
* Drive one request through a fresh **stateless** Streamable-HTTP transport bound
|
|
116
121
|
* to `server`, then tear both down.
|
|
@@ -121,6 +126,9 @@ type McpFetchHandler = (request: Request) => Promise<Response>;
|
|
|
121
126
|
* an in-flight body. Cleanup is fire-and-forget with swallowed rejections (a
|
|
122
127
|
* `.catch()`-terminated chain) so it never delays or masks the resolved response.
|
|
123
128
|
*
|
|
129
|
+
* The request is screened first ({@link screenRequest}); an oversized body or a
|
|
130
|
+
* JSON-RPC batch is refused here and no server is ever constructed for it.
|
|
131
|
+
*
|
|
124
132
|
* `options.parsedBody` lets a caller hand over a body it already read (e.g. the
|
|
125
133
|
* paid-tool gate, which peeks the JSON-RPC message to price the call) so the
|
|
126
134
|
* transport doesn't re-read a consumed stream.
|
|
@@ -129,5 +137,5 @@ type McpFetchHandler = (request: Request) => Promise<Response>;
|
|
|
129
137
|
* would otherwise skip it and leak a server + transport per failed request,
|
|
130
138
|
* which on a public endpoint is exactly the request an attacker can repeat.
|
|
131
139
|
*/
|
|
132
|
-
declare const serveStateless: (server: Server, request: Request, options?:
|
|
133
|
-
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 };
|
|
140
|
+
declare const serveStateless: (server: Server, request: Request, options?: ServeStatelessOptions) => Promise<Response>;
|
|
141
|
+
export { McpResourceProvider as M, ServeStatelessOptions as S, 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 y}from"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";const p=128*1024,i=e=>new TextEncoder().encode(e).length,a=(e,r,s)=>Response.json({error:{code:r,message:s},id:null,jsonrpc:"2.0"},{status:e}),c=()=>a(400,-32600,"batched requests are not supported: send one JSON-RPC message per request"),u=async(e,r)=>{if(e.method!=="POST")return{parsedBody:r?.parsedBody};if(r?.parsedBody!==void 0)return Array.isArray(r.parsedBody)?{response:c()}:{parsedBody:r.parsedBody};const s=r?.maxRequestBytes??p,t={response:a(413,-32600,`request body exceeds ${String(s)} bytes`)},n=Number(e.headers.get("content-length"));if(Number.isFinite(n)&&n>s)return t;const d=await e.text();if(i(d)>s)return t;let o;try{o=JSON.parse(d)}catch{return{response:a(400,-32700,"parse error: body is not valid JSON")}}return Array.isArray(o)?{response:c()}:{parsedBody:o}},h=async(e,r,s)=>{const t=await u(r,s);if("response"in t)return t.response;const n=new y({enableJsonResponse:!0,sessionIdGenerator:void 0});try{return await e.connect(n),await n.handleRequest(r,{authInfo:s?.authInfo,parsedBody:t.parsedBody})}finally{n.close().catch(()=>{}),e.close().catch(()=>{})}};export{p as DEFAULT_MAX_REQUEST_BYTES,h as serveStateless};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/mcp",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.106",
|
|
4
4
|
"description": "Model Context Protocol server exposing a Lunora deployment to AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@lunora/client": "1.0.0-alpha.
|
|
57
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
58
|
-
"@lunora/shard-engine": "1.0.0-alpha.
|
|
56
|
+
"@lunora/client": "1.0.0-alpha.72",
|
|
57
|
+
"@lunora/errors": "1.0.0-alpha.30",
|
|
58
|
+
"@lunora/shard-engine": "1.0.0-alpha.53",
|
|
59
59
|
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const b="agents:agentRun",T="agents:agentThread",x="agents:agentMessages",d="lunora_agent_status",_=new Set(["awaiting_input","cancelled","error","idle"]),P=6e4,U=600,L={properties:{prompt:{description:"The task or message for the agent.",type:"string"},threadKey:{description:"Reuse to continue a conversation; omit to start a new thread.",type:"string"},title:{description:"Optional thread title (first run only).",type:"string"}},required:["prompt"],type:"object"},k={properties:{threadKey:{description:"The thread key returned by an agent tool call.",type:"string"}},required:["threadKey"],type:"object"},g=t=>t.toolName??`agent_${t.name}`,C=t=>{if(t===void 0)return[];const n=[];for(const e of t.split(";")){const r=e.trim();if(r.length===0)continue;const o=r.indexOf(":");if(o<=0)continue;const s=r.slice(0,o).trim(),a=r.slice(o+1).trim();s.length===0||a.length===0||n.push({description:a,name:s})}return n},G=(t,n)=>n!==!0||t.length===0?[]:[...t.map(r=>({description:`${r.description} Starts a durable agent run and returns its final answer.`,inputSchema:L,name:g(r)})),{description:"Check the status of a durable agent run (and its answer if finished) by its threadKey.",inputSchema:k,name:d}],O=(t,n)=>t===d||n.some(e=>g(e)===t),I=()=>`mcp-${crypto.randomUUID()}`,p=t=>({__lunoraRef:t}),K=async t=>{await new Promise(n=>{setTimeout(n,t)})},c=t=>({content:[{text:JSON.stringify(t,void 0,2),type:"text"}]}),i=t=>({content:[{text:t,type:"text"}],isError:!0}),l=(t,n)=>{const e=t[n];return typeof e=="string"&&e.length>0?e:void 0},A=t=>{for(let n=t.length-1;n>=0;n-=1){const e=t[n],r=e?.toolCalls,o=Array.isArray(r)&&r.length>0;if(e?.role==="assistant"&&!o)return typeof e.content=="string"?e.content:""}return""},w=t=>t!==null&&typeof t=="object"&&typeof t.status=="string"?t.status:"unknown",S=async(t,n,e,r)=>{const o=await t.query(p(x),{key:n});if(e==="error"){const s=r!==null&&typeof r=="object"?r.error:void 0;return c({error:typeof s=="string"?s:"the agent run failed",status:e,threadKey:n})}return c(e==="awaiting_input"?{hint:"This run is paused on a human-in-the-loop tool approval. Approve or reject it in the app that owns the agent; MCP cannot supply the input. Poll lunora_agent_status with this threadKey afterwards.",status:e,text:A(o),threadKey:n}:{status:e,text:A(o),threadKey:n})},R=async(t,n)=>{const e=l(n,"threadKey");if(e===void 0)return i('"threadKey" is required and must be a non-empty string');const r=await t.query(p(T),{key:e}),o=w(r);return _.has(o)?S(t,e,o,r):c({status:o==="unknown"?"running":o,threadKey:e})},q=async(t,n,e,r)=>{try{if(r.allowAgents!==!0)return i(`tool "${n}" is disabled: agent tools are off. Enable them with the LUNORA_MCP_ALLOW_AGENTS env var.`);if(n===d)return await R(t,e);const o=r.exposures.find(u=>g(u)===n);if(o===void 0)return i(`agent tool "${n}" is not exposed by this MCP server`);const s=l(e,"prompt");if(s===void 0)return i('"prompt" is required and must be a non-empty string');const a=l(e,"threadKey")??I(),h=l(e,"title"),{id:E}=await t.mutation(p(b),{agent:o.name,input:s,threadKey:a,...h===void 0?{}:{title:h}}),f=r.pollIntervalMs??U,N=r.maxWaitMs??P,v=r.wait??K,M=Math.max(1,Math.ceil(N/f));for(let u=0;u<M;u+=1){const y=await t.query(p(T),{key:a}),m=w(y);if(_.has(m))return await S(t,a,m,y);await v(f)}return c({hint:"call lunora_agent_status with this threadKey to poll for the answer",runId:E,status:"running",threadKey:a})}catch(o){const s=o instanceof Error?o.message:String(o);return i(s)}};export{L as AGENT_RUN_INPUT_SCHEMA,d as AGENT_STATUS_TOOL_NAME,G as agentToolDefinitions,q as callAgentTool,A as finalAnswer,O as isAgentToolName,C as parseAgentsEnv};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
const T=10,A=50,h="docs",L=500,_={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"},m={properties:{url:{description:'Site-relative page URL from a search hit, e.g. "/docs/sharding"',type:"string"}},required:["url"],type:"object"},p={properties:{},type:"object"},s={destructiveHint:!1,idempotentHint:!0,openWorldHint:!0,readOnlyHint:!0},u={annotations:{...s,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:_,name:"lunora_search_docs"},l={annotations:{...s,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:m,name:"lunora_get_doc"},d={annotations:{...s,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:p,name:"lunora_list_docs"},y=[u,l,d],r=e=>({content:[{text:JSON.stringify(e,void 0,2),type:"text"}]}),g=e=>({content:[{text:e,type:"text"}],isError:!0}),a=512,c=(e,t)=>{const o=e[t];if(typeof o!="string"||o.trim().length===0)throw new TypeError(`"${t}" is required and must be a non-empty string`);if(o.length>a)throw new RangeError(`"${t}" must be at most ${String(a)} characters`);return o.trim()},f=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)))},S=(e,t)=>{for(const o of e.split("/")){let n;try{n=decodeURIComponent(o)}catch{throw new RangeError(`"url" contains a malformed percent-escape: ${t}`)}if(n===".."||n==="."||n.includes("%")||n.includes("\\"))throw new RangeError(`"url" must not contain ".." or encoded segments: ${t}`)}},E=e=>{let t=e.trim().replaceAll("\\","/");if(t.startsWith("http://")||t.startsWith("https://")){const o=t.slice(t.indexOf("//")+2),n=o.indexOf("/");t=n===-1?"/":o.slice(n)}for(const o of["?","#"]){const n=t.indexOf(o);n!==-1&&(t=t.slice(0,n))}for(;t.endsWith("/")&&t.length>1;)t=t.slice(0,-1);return t.startsWith("/")||(t=t.startsWith("docs/")?`/${t}`:`/${h}/${t}`),S(t,e),t},O=e=>[{definition:u,handle:async t=>{const o=c(t,"query"),n=f(t.limit),i=(await e.search(o)).slice(0,n);return i.length===0?r({hits:[],note:`no documentation matched "${o}" — try fewer or more general terms, or lunora_list_docs to browse`}):r({hits:i})}},{definition:l,handle:async t=>{const o=E(c(t,"url")),n=await e.getPage(o);return n===void 0?g(`documentation page not found: ${o}. Use lunora_search_docs or lunora_list_docs to find a valid url.`):{content:[{text:`# ${n.title} (${n.url})
|
|
2
|
-
|
|
3
|
-
${n.content}`,type:"text"}]}}},{definition:d,handle:async()=>{const t=await e.listPages();return t.length>500?r({note:`showing the first ${String(500)} of ${String(t.length)} pages — use lunora_search_docs to find the rest`,pages:t.slice(0,500)}):r(t)}}];export{T as DEFAULT_SEARCH_LIMIT,y as DOCS_TOOL_DEFINITIONS,a as MAX_ARGUMENT_LENGTH,L as MAX_LISTED_PAGES,A as MAX_SEARCH_LIMIT,O as docsTools,E as normalizeDocUrl};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createToolServer as d}from"./createToolServer-BtGuPyMU.mjs";import{serveStateless as a}from"./serveStateless-Db7J3pAO.mjs";import{docsResources as i}from"./DOCS_URI_SCHEME-CFANc3vr.mjs";import{docsTools as p}from"./DEFAULT_SEARCH_LIMIT-V7pCZ1wt.mjs";const u="lunora-docs",y=128*1024,m=e=>({name:u,version:e??"0.0.0"}),l=e=>d(m(e.version),p(e.index),i(e.index)),S=e=>new TextEncoder().encode(e).length,t=(e,r,o)=>Response.json({error:{code:r,message:o},id:null,jsonrpc:"2.0"},{status:e}),f=async(e,r)=>{if(e.method!=="POST")return{parsedBody:void 0};const o={response:t(413,-32600,`request body exceeds ${String(r)} bytes`)},s=Number(e.headers.get("content-length"));if(Number.isFinite(s)&&s>r)return o;const c=await e.text();if(S(c)>r)return o;let n;try{n=JSON.parse(c)}catch{return{response:t(400,-32700,"parse error: body is not valid JSON")}}return Array.isArray(n)?{response:t(400,-32600,"batched requests are not supported: send one JSON-RPC message per request")}:{parsedBody:n}},g=e=>{const r=e.maxRequestBytes??y;return async o=>{const s=await f(o,r);return"response"in s?s.response:a(l(e),o,s.parsedBody===void 0?void 0:{parsedBody:s.parsedBody})}};export{y as DEFAULT_MAX_REQUEST_BYTES,u as DOCS_SERVER_NAME,g as createDocsMcpFetchHandler,l as createDocsMcpServer};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{readFileSync as v}from"node:fs";import{dirname as c,join as g}from"node:path";import{fileURLToPath as d}from"node:url";import{LunoraClient as h}from"@lunora/client";import{LunoraError as u}from"@lunora/errors";import{Server as w}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as T}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as p,CallToolRequestSchema as A}from"@modelcontextprotocol/sdk/types.js";import{agentToolDefinitions as M,isAgentToolName as S,callAgentTool as k}from"./AGENT_RUN_INPUT_SCHEMA-DGpNvk5K.mjs";import{toolDefinitions as y,callTool as N}from"./READ_ONLY_TOOL_DEFINITIONS-aD-b7Hw2.mjs";const L=()=>{try{let e=c(d(import.meta.url));for(let r=0;r<8;r+=1){try{const o=v(g(e,"package.json"),"utf8"),t=JSON.parse(o);if(t.name==="@lunora/mcp"&&typeof t.version=="string"&&t.version.length>0)return t.version}catch{}const n=c(e);if(n===e)break;e=n}}catch{}return"0.0.0"},R={name:"lunora",version:L()},E=e=>{if(e.client!==void 0)return e.client;if(e.url===void 0)throw new u("INTERNAL","createLunoraMcpServer requires either a `client` or a `url`");if(e.token===void 0||e.token.length===0)throw new u("UNAUTHENTICATED","createLunoraMcpServer requires a `token` (LUNORA_ADMIN_TOKEN) alongside `url`: every tool reaches admin-gated /_lunora/admin/* routes, so an unauthenticated server can only 403. Writes stay off unless `allowWrites` is set.");const r=new h({fetch:e.fetch,url:e.url});return r.setAuthToken(e.token),r},I=e=>{const r=E(e),n=e.allowWrites??!1,o=e.allowAgents??!1,t=e.agents??[],s=typeof e.token=="string"&&e.token.length>0,a=new w(R,{capabilities:{tools:{}}});return a.setRequestHandler(p,()=>({tools:[...y(n,s),...M(t,o)]})),a.setRequestHandler(A,async m=>{const{arguments:f,name:l}=m.params,i=f??{};return S(l,t)?await k(r,l,i,{allowAgents:o,exposures:t,...e.agentMaxWaitMs===void 0?{}:{maxWaitMs:e.agentMaxWaitMs},...e.agentPollIntervalMs===void 0?{}:{pollIntervalMs:e.agentPollIntervalMs}}):await N(r,l,i,n,s)}),a},j=async e=>{const r=I(e);return await r.connect(new T),r};export{j as connectStdio,I as createLunoraMcpServer};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{serveStateless as t}from"./serveStateless-Db7J3pAO.mjs";import{createLunoraMcpServer as o}from"./connectStdio-CDsCt1IT.mjs";const p=e=>r=>t(o(e),r);export{p as createMcpFetchHandler,t as serveStateless};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{WebStandardStreamableHTTPServerTransport as o}from"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";const s=async(t,a,n)=>{const e=new o({enableJsonResponse:!0,sessionIdGenerator:void 0});try{return await t.connect(e),await e.handleRequest(a,n)}finally{e.close().catch(()=>{}),t.close().catch(()=>{})}};export{s as serveStateless};
|