@lunora/mcp 1.0.0-alpha.25 → 1.0.0-alpha.251
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 +120 -0
- package/README.md +202 -14
- package/dist/bin.mjs +4 -47
- package/dist/docs/index.d.mts +259 -0
- package/dist/docs/index.d.ts +259 -0
- package/dist/docs/index.mjs +1 -0
- package/dist/index.d.mts +83 -82
- package/dist/index.d.ts +83 -82
- package/dist/index.mjs +1 -5
- package/dist/node/index.d.mts +90 -0
- package/dist/node/index.d.ts +90 -0
- package/dist/node/index.mjs +1 -0
- package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-BBVvXopY.mjs +1 -0
- package/dist/packem_shared/DEFAULT_DOCS_BASE_URL-CiHy_Dbz.mjs +4 -0
- package/dist/packem_shared/DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs +1 -0
- package/dist/packem_shared/DEFAULT_SEARCH_LIMIT-DpGKT-Xt.mjs +3 -0
- package/dist/packem_shared/DOCS_SERVER_NAME-B4fL5sGF.mjs +1 -0
- package/dist/packem_shared/DOCS_URI_SCHEME-B_C2tA0y.mjs +3 -0
- package/dist/packem_shared/LOCAL_SERVER_NAME-3ydSaBjA.mjs +1 -0
- package/dist/packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-DrY5Gp38.mjs +1 -0
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-BEaoA4xz.mjs +1 -0
- package/dist/packem_shared/compose.d-CNiBViBW.d.mts +153 -0
- package/dist/packem_shared/compose.d-CNiBViBW.d.ts +153 -0
- package/dist/packem_shared/connectStdio-D_skPLhV.mjs +1 -0
- package/dist/packem_shared/createAuthedMcpFetchHandler-3-m_Hrp0.mjs +1 -0
- package/dist/packem_shared/createLunoraMcpServer-BYOigZMZ.mjs +1 -0
- package/dist/packem_shared/createMcpFetchHandler-Bw2OFJ7f.mjs +1 -0
- package/dist/packem_shared/createPaidMcpServer-DQmcP8ls.mjs +1 -0
- package/dist/packem_shared/createToolServer-Bf_IxX3S.mjs +1 -0
- package/dist/packem_shared/promise-memo-OqVPWoUX.mjs +1 -0
- package/dist/packem_shared/serve-stateless.d-DD5arqFx.d.mts +45 -0
- package/dist/packem_shared/serve-stateless.d-DD5arqFx.d.ts +45 -0
- package/dist/packem_shared/server.d-C7ciln6z.d.mts +152 -0
- package/dist/packem_shared/server.d-CXO7cw_h.d.ts +152 -0
- package/dist/packem_shared/toDocsSearchHits-CBLmtWXt.mjs +1 -0
- package/dist/packem_shared/tool-call-Bs-QeKp6.mjs +1 -0
- package/dist/packem_shared/tool-result--Yq36pE-.mjs +1 -0
- package/dist/packem_shared/tools-BrvavFhl.mjs +2 -0
- package/dist/worker/index.d.mts +257 -0
- package/dist/worker/index.d.ts +257 -0
- package/dist/worker/index.mjs +1 -0
- package/package.json +27 -5
- package/dist/packem_shared/AGENT_RUN_INPUT_SCHEMA-DWobae3P.mjs +0 -168
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-CWb_wFd5.mjs +0 -204
- package/dist/packem_shared/connectStdio-bkJcaXYE.mjs +0 -76
- package/dist/packem_shared/createMcpFetchHandler-DfzBSLrm.mjs +0 -14
- package/dist/packem_shared/createPaidMcpServer-b-qp_aEF.mjs +0 -93
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { L as LunoraMcpServerOptions } from "../packem_shared/server.d-CXO7cw_h.js";
|
|
2
|
+
export { A as AGENT_RUN_INPUT_SCHEMA, a as AGENT_STATUS_TOOL_NAME,
|
|
3
|
+
/** Worker-safe entry: fetch handlers and tool composition, with no stdio transport or `node:*` imports. */
|
|
4
|
+
type C as CallAgentToolOptions,
|
|
5
|
+
/** Worker-safe entry: fetch handlers and tool composition, with no stdio transport or `node:*` imports. */
|
|
6
|
+
type M as McpAgentExposure, b as agentToolDefinitions, c as callAgentTool, p as parseAgentsEnv } from "../packem_shared/server.d-CXO7cw_h.js";
|
|
7
|
+
import { M as McpFetchHandler } from "../packem_shared/serve-stateless.d-DD5arqFx.js";
|
|
8
|
+
export { D as DEFAULT_MAX_REQUEST_BYTES, type S as ServeStatelessOptions, s as serveStateless } from "../packem_shared/serve-stateless.d-DD5arqFx.js";
|
|
9
|
+
import { T as ToolDefinition, d as ToolResult, h as ToolAnnotations, b as ToolInputSchema } from "../packem_shared/compose.d-CNiBViBW.js";
|
|
10
|
+
export { type M as McpServerInfo, type a as McpTool, e as createToolServer } from "../packem_shared/compose.d-CNiBViBW.js";
|
|
11
|
+
import { LunoraClient } from '@lunora/client';
|
|
12
|
+
import '@modelcontextprotocol/sdk/server/index.js';
|
|
13
|
+
import '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
14
|
+
/**
|
|
15
|
+
* The observability tools. Descriptions say WHEN to call the tool, not just
|
|
16
|
+
* what it returns — an agent picking between five similar reads needs the
|
|
17
|
+
* trigger, and it pays for these strings on every turn.
|
|
18
|
+
*/
|
|
19
|
+
declare const OBSERVABILITY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
|
|
20
|
+
/** The read-only tool surface: introspection + query. Always exposed. */
|
|
21
|
+
declare const READ_ONLY_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
|
|
22
|
+
/** The write tool surface (mutations + actions). Exposed ONLY when writes are enabled. */
|
|
23
|
+
declare const WRITE_TOOL_DEFINITIONS: ReadonlyArray<ToolDefinition>;
|
|
24
|
+
/**
|
|
25
|
+
* The tools this server advertises: every family whose gate is open, in table
|
|
26
|
+
* order. A gated family is OMITTED rather than refused — an AI agent can't
|
|
27
|
+
* invoke what it can't see — and {@link callTool} re-checks the same gate at
|
|
28
|
+
* dispatch, so the guarantee does not depend on a client honouring this list.
|
|
29
|
+
*/
|
|
30
|
+
declare const toolDefinitions: (allowWrites: boolean, allowObservability?: boolean, allowDataReads?: boolean) => ReadonlyArray<ToolDefinition>;
|
|
31
|
+
/**
|
|
32
|
+
* Dispatch a tool call against `client`: find the family that owns `name`, check
|
|
33
|
+
* its gate, hand off. Unknown tools and thrown errors are returned as `isError`
|
|
34
|
+
* results (rather than rejections) so the calling model sees the failure as tool
|
|
35
|
+
* output, per the MCP convention.
|
|
36
|
+
*
|
|
37
|
+
* Each gate is enforced HERE as well as in {@link toolDefinitions}, so a call to
|
|
38
|
+
* a gated tool is refused even if the client somehow names one it was never
|
|
39
|
+
* shown.
|
|
40
|
+
*/
|
|
41
|
+
declare const callTool: (client: LunoraClient, name: string, input: Record<string, unknown>, allowWrites?: boolean, allowObservability?: boolean, allowDataReads?: boolean) => Promise<ToolResult>;
|
|
42
|
+
/**
|
|
43
|
+
* The verified access-token payload better-auth hands a protected handler.
|
|
44
|
+
*
|
|
45
|
+
* A JWT payload is an open bag of claims, so this is deliberately an index
|
|
46
|
+
* signature with the two entries this module reads named. It is structurally
|
|
47
|
+
* satisfied by `jose`'s `JWTPayload`, which is what better-auth passes.
|
|
48
|
+
*/
|
|
49
|
+
interface McpAccessTokenClaims {
|
|
50
|
+
readonly [claim: string]: unknown;
|
|
51
|
+
/** Space-delimited granted scopes (RFC 6749 §3.3). */
|
|
52
|
+
readonly scope?: unknown;
|
|
53
|
+
/** Subject — the user the token was issued for. */
|
|
54
|
+
readonly sub?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The MCP auth gate: wraps a claims-aware handler into a plain fetch handler.
|
|
58
|
+
*
|
|
59
|
+
* Declared structurally here rather than imported from `@better-auth/mcp`,
|
|
60
|
+
* following the same rule `./paid` follows for `@lunora/x402`: a type import
|
|
61
|
+
* from a package this one does not depend on puts that package's `.d.ts` into
|
|
62
|
+
* the build graph, and a consumer that never installs it never builds it
|
|
63
|
+
* either — so the dts bundler looks for a `dist/` that does not exist and fails
|
|
64
|
+
* the build. Structural typing costs nothing here because this module never
|
|
65
|
+
* inspects the gate; it only applies it.
|
|
66
|
+
*
|
|
67
|
+
* Both better-auth entry points partially apply to this shape:
|
|
68
|
+
* `(handler) => requireMcpAuth(auth, handler, opts)` and
|
|
69
|
+
* `(handler) => createMcpProtectedRequestHandler(options, handler)`.
|
|
70
|
+
*/
|
|
71
|
+
type McpAuthProtect = (handler: (request: Request, claims: McpAccessTokenClaims) => Promise<Response>) => McpFetchHandler;
|
|
72
|
+
/** Server options, or a function deriving them from the request's verified claims. */
|
|
73
|
+
type AuthedMcpServerOptions = ((claims: McpAccessTokenClaims) => LunoraMcpServerOptions | Promise<LunoraMcpServerOptions>) | LunoraMcpServerOptions;
|
|
74
|
+
interface AuthedMcpFetchHandlerOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
77
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
78
|
+
* (128 KiB), which a value that is not a non-negative safe integer also
|
|
79
|
+
* falls back to.
|
|
80
|
+
*/
|
|
81
|
+
maxRequestBytes?: number;
|
|
82
|
+
/**
|
|
83
|
+
* The OAuth gate to mount the MCP server behind. Pass
|
|
84
|
+
* `(handler) => requireMcpAuth(auth, handler, opts)` from
|
|
85
|
+
* `@lunora/auth/plugins`.
|
|
86
|
+
*/
|
|
87
|
+
protect: McpAuthProtect;
|
|
88
|
+
/**
|
|
89
|
+
* The Lunora MCP server to serve once a request is authorized — either a
|
|
90
|
+
* fixed options object, or a function of the verified token claims so tool
|
|
91
|
+
* exposure can follow the scopes the token actually carries.
|
|
92
|
+
*/
|
|
93
|
+
server: AuthedMcpServerOptions;
|
|
94
|
+
/**
|
|
95
|
+
* Step-up for the side-effecting tools: the write tools
|
|
96
|
+
* (`lunora_run_mutation` / `lunora_run_action`) and the agent tools (each
|
|
97
|
+
* `agent_<name>` and `lunora_agent_status`, since starting a durable run is a
|
|
98
|
+
* side effect). A token without `scope` still sees them in `tools/list`, and
|
|
99
|
+
* a `tools/call` to one throws `challenge([scope])` instead of running, so the
|
|
100
|
+
* client can re-authorize for the scope and retry. Unset, tools follow
|
|
101
|
+
* `allowWrites` / `allowAgents` alone. The raw-row and observability families
|
|
102
|
+
* need their own token scopes with or without `stepUp`; without it a call
|
|
103
|
+
* without the scope is refused as a tool error, not challenged.
|
|
104
|
+
*/
|
|
105
|
+
stepUp?: {
|
|
106
|
+
/**
|
|
107
|
+
* Build the error that makes the gate answer 403 `insufficient_scope`
|
|
108
|
+
* naming `scopes`. Pass `createInsufficientScopeError` from
|
|
109
|
+
* `@lunora/auth/plugins`: better-auth only turns an error that factory
|
|
110
|
+
* created into the challenge.
|
|
111
|
+
*/
|
|
112
|
+
challenge: (scopes: string[]) => unknown;
|
|
113
|
+
/** The scope a call to a side-effecting tool needs, e.g. `"lunora:write"`. */
|
|
114
|
+
scope: string;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parse an access token's `scope` claim into a set.
|
|
119
|
+
*
|
|
120
|
+
* RFC 6749 §3.3 makes `scope` a space-delimited string, and better-auth issues
|
|
121
|
+
* it that way; anything else (absent, or a non-string an extension wrote)
|
|
122
|
+
* yields an empty set rather than throwing, so a scope check on a malformed
|
|
123
|
+
* token denies instead of crashing the tool call.
|
|
124
|
+
*/
|
|
125
|
+
declare const mcpTokenScopes: (claims: McpAccessTokenClaims) => ReadonlySet<string>;
|
|
126
|
+
declare const createAuthedMcpFetchHandler: (options: AuthedMcpFetchHandlerOptions) => McpFetchHandler;
|
|
127
|
+
/** {@link createMcpFetchHandler} options: the server's, plus this transport's body limit. */
|
|
128
|
+
interface McpFetchHandlerOptions extends LunoraMcpServerOptions {
|
|
129
|
+
/**
|
|
130
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
131
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
132
|
+
* (128 KiB), which a value that is not a non-negative safe integer also
|
|
133
|
+
* falls back to.
|
|
134
|
+
*/
|
|
135
|
+
maxRequestBytes?: number;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build a stateless Streamable-HTTP fetch handler for a Lunora MCP server. Each
|
|
139
|
+
* invocation constructs a fresh proxy server and serves the request through
|
|
140
|
+
* {@link serveStateless}.
|
|
141
|
+
*
|
|
142
|
+
* The `LunoraClient` is resolved ONCE, here, and shared by every request: it is
|
|
143
|
+
* the same deployment on each one, and the public-function registry memo in
|
|
144
|
+
* `./tools` is keyed by client identity, so a per-request client would re-fetch
|
|
145
|
+
* that registry on every tool call. A misconfiguration (`url` without `token`)
|
|
146
|
+
* therefore throws when the handler is built rather than on first request,
|
|
147
|
+
* which is where `createLunoraMcpServer` already documents reporting it.
|
|
148
|
+
*/
|
|
149
|
+
declare const createMcpFetchHandler: (options: McpFetchHandlerOptions) => McpFetchHandler;
|
|
150
|
+
/** A tool handler: receives the call's `arguments` bag, returns an MCP tool result. */
|
|
151
|
+
/**
|
|
152
|
+
* The x402 vocabulary this module needs, declared here rather than imported.
|
|
153
|
+
*
|
|
154
|
+
* The x402 package is an optional peer, and a type import from its `charge`
|
|
155
|
+
* entry puts its `.d.ts` back into this package's build graph:
|
|
156
|
+
* a consumer that never installs x402 never builds it either, so the dts bundler
|
|
157
|
+
* looks for a `dist/` that does not exist and fails. That is not hypothetical —
|
|
158
|
+
* it broke the docs site build, which runs a filtered build over the docs app and its dependency closure
|
|
159
|
+
* and therefore never builds x402.
|
|
160
|
+
*
|
|
161
|
+
* Declaring them locally is safe because this module never *inspects* a charge
|
|
162
|
+
* config; it forwards it whole to `createChargeMiddleware`. The index signature
|
|
163
|
+
* keeps a real charge config assignable as x402 grows fields.
|
|
164
|
+
*/
|
|
165
|
+
/** Mirrors x402's `X402Price` — a decimal string like `"$0.05"`, or a number. */
|
|
166
|
+
type X402Price = number | string;
|
|
167
|
+
/** Mirrors x402's charge config with the per-tool price omitted. */
|
|
168
|
+
interface X402ChargeSettings {
|
|
169
|
+
/** Network this resource settles on. */
|
|
170
|
+
readonly network: string;
|
|
171
|
+
/** Everything else x402 accepts, forwarded untouched. */
|
|
172
|
+
readonly [key: string]: unknown;
|
|
173
|
+
/** Payout wallet(s), per network family. */
|
|
174
|
+
readonly recipient: {
|
|
175
|
+
readonly evm?: string;
|
|
176
|
+
readonly svm?: string;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
type ToolHandler = (arguments_: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
|
|
180
|
+
/** Registration shape for a free tool. */
|
|
181
|
+
interface RegisterToolOptions {
|
|
182
|
+
/** MCP tool annotations. Required: a paid tool declares its access the same as every other tool. */
|
|
183
|
+
annotations: ToolAnnotations;
|
|
184
|
+
/** Human/model-facing description of what the tool does. */
|
|
185
|
+
description: string;
|
|
186
|
+
/** JSON-Schema object describing the tool's arguments. */
|
|
187
|
+
inputSchema: ToolInputSchema;
|
|
188
|
+
/** Unique tool name (the MCP `tools/call` `name`). */
|
|
189
|
+
name: string;
|
|
190
|
+
}
|
|
191
|
+
/** Registration shape for a paid tool: a {@link RegisterToolOptions} plus its USD price. */
|
|
192
|
+
interface RegisterPaidToolOptions extends RegisterToolOptions {
|
|
193
|
+
/** USD price per call (e.g. `"$0.05"`), charged via x402 before dispatch. */
|
|
194
|
+
price: X402Price;
|
|
195
|
+
}
|
|
196
|
+
/** x402 settlement vocabulary shared by every paid tool (network, recipient, facilitator); price is per-tool. */
|
|
197
|
+
type PaidMcpChargeConfig = X402ChargeSettings;
|
|
198
|
+
/** Config for `createPaidMcpServer`. */
|
|
199
|
+
interface PaidMcpServerConfig {
|
|
200
|
+
/** The worker-level x402 charge config; each paid tool supplies only its own `price`. */
|
|
201
|
+
charge: PaidMcpChargeConfig;
|
|
202
|
+
/**
|
|
203
|
+
* Largest accepted request body, in bytes — enforced while the body streams
|
|
204
|
+
* in, not after it is buffered. Defaults to `DEFAULT_MAX_REQUEST_BYTES`
|
|
205
|
+
* (128 KiB), which a value that is not a non-negative safe integer also
|
|
206
|
+
* falls back to.
|
|
207
|
+
*/
|
|
208
|
+
maxRequestBytes?: number;
|
|
209
|
+
/** Name/version advertised in the MCP `initialize` handshake. Defaults to `lunora-paid-mcp`. */
|
|
210
|
+
serverInfo?: {
|
|
211
|
+
name: string;
|
|
212
|
+
version: string;
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* The Worker execution context, as this module reads it: only `waitUntil`, and
|
|
217
|
+
* structurally, so the package takes no `@cloudflare/workers-types` dependency.
|
|
218
|
+
*/
|
|
219
|
+
interface PaidMcpExecutionContext {
|
|
220
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The paid server's fetch handler.
|
|
224
|
+
*
|
|
225
|
+
* Unlike the free `McpFetchHandler` it takes the Worker's full
|
|
226
|
+
* `(request, env, ctx)` triple — the shape `export default { fetch }` is called
|
|
227
|
+
* with — because the x402 receipt sink NEEDS `ctx.waitUntil`: work that is
|
|
228
|
+
* neither awaited into the response nor registered with it is cancelled when the
|
|
229
|
+
* request ends, so an async `onReceipt` (inserting the settled payment into a
|
|
230
|
+
* durable table) frequently never runs while the money has already moved
|
|
231
|
+
* on-chain. `env` is accepted and ignored so the handler drops straight into the
|
|
232
|
+
* default export; both are optional, so a non-Workers caller may still invoke it
|
|
233
|
+
* with a bare `Request` (the middleware then simply sees no `waitUntil`).
|
|
234
|
+
*/
|
|
235
|
+
type PaidMcpFetchHandler = (request: Request, env?: unknown, context?: PaidMcpExecutionContext) => Promise<Response>;
|
|
236
|
+
/** A paid MCP server: register free/paid tools, then serve over Streamable HTTP. */
|
|
237
|
+
interface PaidMcpServer {
|
|
238
|
+
/** The Streamable-HTTP fetch handler; gates each paid `tools/call` behind x402. */
|
|
239
|
+
readonly fetchHandler: PaidMcpFetchHandler;
|
|
240
|
+
/** Register a **paid** tool: its dispatch runs the x402 charge middleware first. */
|
|
241
|
+
paidTool: (options: RegisterPaidToolOptions, handler: ToolHandler) => void;
|
|
242
|
+
/** Register a **free** tool (coexists with paid tools on the same server). */
|
|
243
|
+
tool: (options: RegisterToolOptions, handler: ToolHandler) => void;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Create a paid MCP server. Register free tools with `tool()` and priced tools
|
|
247
|
+
* with `paidTool()` (they coexist), then serve `fetchHandler` over HTTP.
|
|
248
|
+
*
|
|
249
|
+
* The server is **stateless**: `fetchHandler` builds a fresh `Server` per
|
|
250
|
+
* request (reading the live tool registry), so tools registered before the
|
|
251
|
+
* first request are all visible. Each priced tool memoises one initialised
|
|
252
|
+
* `ChargeMiddleware` (keyed by tool name, baking that tool's price and naming
|
|
253
|
+
* the tool as the challenge `resource`); a failed init is not cached, so a
|
|
254
|
+
* transient facilitator outage retries on the next call.
|
|
255
|
+
*/
|
|
256
|
+
declare const createPaidMcpServer: (config: PaidMcpServerConfig) => PaidMcpServer;
|
|
257
|
+
export { type AuthedMcpFetchHandlerOptions, type AuthedMcpServerOptions, type McpAccessTokenClaims, type McpAuthProtect, type McpFetchHandler, type McpFetchHandlerOptions, OBSERVABILITY_TOOL_DEFINITIONS, type PaidMcpChargeConfig, type PaidMcpExecutionContext, type PaidMcpFetchHandler, type PaidMcpServer, type PaidMcpServerConfig, READ_ONLY_TOOL_DEFINITIONS, type RegisterPaidToolOptions, type RegisterToolOptions, type ToolDefinition, type ToolHandler, type ToolInputSchema, type ToolResult, WRITE_TOOL_DEFINITIONS, callTool, createAuthedMcpFetchHandler, createMcpFetchHandler, createPaidMcpServer, mcpTokenScopes, toolDefinitions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AGENT_RUN_INPUT_SCHEMA as r,AGENT_STATUS_TOOL_NAME as t,agentToolDefinitions as T,callAgentTool as a,parseAgentsEnv as _}from"../packem_shared/AGENT_RUN_INPUT_SCHEMA-BBVvXopY.mjs";import{createAuthedMcpFetchHandler as c,mcpTokenScopes as p}from"../packem_shared/createAuthedMcpFetchHandler-3-m_Hrp0.mjs";import{createToolServer as O}from"../packem_shared/createToolServer-Bf_IxX3S.mjs";import{createMcpFetchHandler as l}from"../packem_shared/createMcpFetchHandler-Bw2OFJ7f.mjs";import{createPaidMcpServer as A}from"../packem_shared/createPaidMcpServer-DQmcP8ls.mjs";import{R as n,W as f,c as m,t as x}from"../packem_shared/tools-BrvavFhl.mjs";import{DEFAULT_MAX_REQUEST_BYTES as D,serveStateless as L}from"../packem_shared/DEFAULT_MAX_REQUEST_BYTES-CbbpkHRK.mjs";import{OBSERVABILITY_TOOL_DEFINITIONS as M}from"../packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-DrY5Gp38.mjs";export{r as AGENT_RUN_INPUT_SCHEMA,t as AGENT_STATUS_TOOL_NAME,D as DEFAULT_MAX_REQUEST_BYTES,M as OBSERVABILITY_TOOL_DEFINITIONS,n as READ_ONLY_TOOL_DEFINITIONS,f as WRITE_TOOL_DEFINITIONS,T as agentToolDefinitions,a as callAgentTool,m as callTool,c as createAuthedMcpFetchHandler,l as createMcpFetchHandler,A as createPaidMcpServer,O as createToolServer,p as mcpTokenScopes,_ as parseAgentsEnv,L as serveStateless,x as toolDefinitions};
|
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.251",
|
|
4
4
|
"description": "Model Context Protocol server exposing a Lunora deployment to AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -43,16 +43,38 @@
|
|
|
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
|
+
},
|
|
50
|
+
"./node": {
|
|
51
|
+
"types": "./dist/node/index.d.ts",
|
|
52
|
+
"import": "./dist/node/index.mjs"
|
|
53
|
+
},
|
|
54
|
+
"./worker": {
|
|
55
|
+
"types": "./dist/worker/index.d.ts",
|
|
56
|
+
"import": "./dist/worker/index.mjs"
|
|
57
|
+
},
|
|
46
58
|
"./package.json": "./package.json"
|
|
47
59
|
},
|
|
48
60
|
"publishConfig": {
|
|
49
61
|
"access": "public"
|
|
50
62
|
},
|
|
51
63
|
"dependencies": {
|
|
52
|
-
"@
|
|
53
|
-
"@lunora/
|
|
54
|
-
"@lunora/
|
|
55
|
-
"@
|
|
64
|
+
"@cfworker/json-schema": "^4.1.1",
|
|
65
|
+
"@lunora/client": "1.0.0-alpha.236",
|
|
66
|
+
"@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
|
|
67
|
+
"@lunora/shard-engine": "1.0.0-alpha.149",
|
|
68
|
+
"@lunora/wire": "1.0.0-alpha.2",
|
|
69
|
+
"@modelcontextprotocol/sdk": "^1.32.0"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"@lunora/x402": ">=1.0.0-alpha.21 <2.0.0-0"
|
|
73
|
+
},
|
|
74
|
+
"peerDependenciesMeta": {
|
|
75
|
+
"@lunora/x402": {
|
|
76
|
+
"optional": true
|
|
77
|
+
}
|
|
56
78
|
},
|
|
57
79
|
"engines": {
|
|
58
80
|
"node": "^22.15.0 || >=24.11.0"
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
const AGENT_RUN_PATH = "agents:agentRun";
|
|
2
|
-
const AGENT_THREAD_PATH = "agents:agentThread";
|
|
3
|
-
const AGENT_MESSAGES_PATH = "agents:agentMessages";
|
|
4
|
-
const AGENT_STATUS_TOOL_NAME = "lunora_agent_status";
|
|
5
|
-
const TERMINAL_STATUSES = /* @__PURE__ */ new Set(["cancelled", "error", "idle"]);
|
|
6
|
-
const DEFAULT_MAX_WAIT_MS = 6e4;
|
|
7
|
-
const DEFAULT_POLL_INTERVAL_MS = 600;
|
|
8
|
-
const AGENT_RUN_INPUT_SCHEMA = {
|
|
9
|
-
properties: {
|
|
10
|
-
prompt: { description: "The task or message for the agent.", type: "string" },
|
|
11
|
-
threadKey: { description: "Reuse to continue a conversation; omit to start a new thread.", type: "string" },
|
|
12
|
-
title: { description: "Optional thread title (first run only).", type: "string" }
|
|
13
|
-
},
|
|
14
|
-
required: ["prompt"],
|
|
15
|
-
type: "object"
|
|
16
|
-
};
|
|
17
|
-
const AGENT_STATUS_INPUT_SCHEMA = {
|
|
18
|
-
properties: {
|
|
19
|
-
threadKey: { description: "The thread key returned by an agent tool call.", type: "string" }
|
|
20
|
-
},
|
|
21
|
-
required: ["threadKey"],
|
|
22
|
-
type: "object"
|
|
23
|
-
};
|
|
24
|
-
const agentToolName = (exposure) => exposure.toolName ?? `agent_${exposure.name}`;
|
|
25
|
-
const parseAgentsEnv = (raw) => {
|
|
26
|
-
if (raw === void 0) {
|
|
27
|
-
return [];
|
|
28
|
-
}
|
|
29
|
-
const exposures = [];
|
|
30
|
-
for (const entry of raw.split(";")) {
|
|
31
|
-
const trimmed = entry.trim();
|
|
32
|
-
if (trimmed.length === 0) {
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
const separator = trimmed.indexOf(":");
|
|
36
|
-
if (separator <= 0) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
const name = trimmed.slice(0, separator).trim();
|
|
40
|
-
const description = trimmed.slice(separator + 1).trim();
|
|
41
|
-
if (name.length === 0 || description.length === 0) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
exposures.push({ description, name });
|
|
45
|
-
}
|
|
46
|
-
return exposures;
|
|
47
|
-
};
|
|
48
|
-
const agentToolDefinitions = (exposures, allowAgents) => {
|
|
49
|
-
if (allowAgents !== true || exposures.length === 0) {
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
const perAgent = exposures.map((exposure) => {
|
|
53
|
-
return {
|
|
54
|
-
description: `${exposure.description} Starts a durable agent run and returns its final answer.`,
|
|
55
|
-
inputSchema: AGENT_RUN_INPUT_SCHEMA,
|
|
56
|
-
name: agentToolName(exposure)
|
|
57
|
-
};
|
|
58
|
-
});
|
|
59
|
-
return [
|
|
60
|
-
...perAgent,
|
|
61
|
-
{
|
|
62
|
-
description: "Check the status of a durable agent run (and its answer if finished) by its threadKey.",
|
|
63
|
-
inputSchema: AGENT_STATUS_INPUT_SCHEMA,
|
|
64
|
-
name: AGENT_STATUS_TOOL_NAME
|
|
65
|
-
}
|
|
66
|
-
];
|
|
67
|
-
};
|
|
68
|
-
const isAgentToolName = (name, exposures) => name === AGENT_STATUS_TOOL_NAME || exposures.some((exposure) => agentToolName(exposure) === name);
|
|
69
|
-
const freshThreadKey = () => `mcp-${crypto.randomUUID()}`;
|
|
70
|
-
const reference = (path) => {
|
|
71
|
-
return { __lunoraRef: path };
|
|
72
|
-
};
|
|
73
|
-
const defaultWait = async (ms) => {
|
|
74
|
-
await new Promise((resolve) => {
|
|
75
|
-
setTimeout(resolve, ms);
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
const ok = (value) => {
|
|
79
|
-
return { content: [{ text: JSON.stringify(value, void 0, 2), type: "text" }] };
|
|
80
|
-
};
|
|
81
|
-
const fail = (text) => {
|
|
82
|
-
return { content: [{ text, type: "text" }], isError: true };
|
|
83
|
-
};
|
|
84
|
-
const readString = (input, key) => {
|
|
85
|
-
const value = input[key];
|
|
86
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
87
|
-
};
|
|
88
|
-
const finalAnswer = (messages) => {
|
|
89
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
90
|
-
const row = messages[index];
|
|
91
|
-
const toolCalls = row?.["toolCalls"];
|
|
92
|
-
const pending = Array.isArray(toolCalls) && toolCalls.length > 0;
|
|
93
|
-
if (row?.["role"] === "assistant" && !pending) {
|
|
94
|
-
return typeof row["content"] === "string" ? row["content"] : "";
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return "";
|
|
98
|
-
};
|
|
99
|
-
const readThreadStatus = (thread) => {
|
|
100
|
-
if (thread !== null && typeof thread === "object" && typeof thread.status === "string") {
|
|
101
|
-
return thread.status;
|
|
102
|
-
}
|
|
103
|
-
return "unknown";
|
|
104
|
-
};
|
|
105
|
-
const readTerminal = async (client, threadKey, status, thread) => {
|
|
106
|
-
const messages = await client.query(reference(AGENT_MESSAGES_PATH), { key: threadKey });
|
|
107
|
-
if (status === "error") {
|
|
108
|
-
const error = thread !== null && typeof thread === "object" ? thread.error : void 0;
|
|
109
|
-
return ok({ error: typeof error === "string" ? error : "the agent run failed", status, threadKey });
|
|
110
|
-
}
|
|
111
|
-
return ok({ status, text: finalAnswer(messages), threadKey });
|
|
112
|
-
};
|
|
113
|
-
const callAgentStatus = async (client, input) => {
|
|
114
|
-
const threadKey = readString(input, "threadKey");
|
|
115
|
-
if (threadKey === void 0) {
|
|
116
|
-
return fail('"threadKey" is required and must be a non-empty string');
|
|
117
|
-
}
|
|
118
|
-
const thread = await client.query(reference(AGENT_THREAD_PATH), { key: threadKey });
|
|
119
|
-
const status = readThreadStatus(thread);
|
|
120
|
-
if (TERMINAL_STATUSES.has(status)) {
|
|
121
|
-
return readTerminal(client, threadKey, status, thread);
|
|
122
|
-
}
|
|
123
|
-
return ok({ status: status === "unknown" ? "running" : status, threadKey });
|
|
124
|
-
};
|
|
125
|
-
const callAgentTool = async (client, name, input, options) => {
|
|
126
|
-
try {
|
|
127
|
-
if (options.allowAgents !== true) {
|
|
128
|
-
return fail(`tool "${name}" is disabled: agent tools are off. Enable them with the LUNORA_MCP_ALLOW_AGENTS env var.`);
|
|
129
|
-
}
|
|
130
|
-
if (name === AGENT_STATUS_TOOL_NAME) {
|
|
131
|
-
return await callAgentStatus(client, input);
|
|
132
|
-
}
|
|
133
|
-
const exposure = options.exposures.find((candidate) => agentToolName(candidate) === name);
|
|
134
|
-
if (exposure === void 0) {
|
|
135
|
-
return fail(`agent tool "${name}" is not exposed by this MCP server`);
|
|
136
|
-
}
|
|
137
|
-
const prompt = readString(input, "prompt");
|
|
138
|
-
if (prompt === void 0) {
|
|
139
|
-
return fail('"prompt" is required and must be a non-empty string');
|
|
140
|
-
}
|
|
141
|
-
const threadKey = readString(input, "threadKey") ?? freshThreadKey();
|
|
142
|
-
const title = readString(input, "title");
|
|
143
|
-
const { id } = await client.mutation(reference(AGENT_RUN_PATH), {
|
|
144
|
-
agent: exposure.name,
|
|
145
|
-
input: prompt,
|
|
146
|
-
threadKey,
|
|
147
|
-
...title === void 0 ? {} : { title }
|
|
148
|
-
});
|
|
149
|
-
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
150
|
-
const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
|
|
151
|
-
const wait = options.wait ?? defaultWait;
|
|
152
|
-
const maxPolls = Math.max(1, Math.ceil(maxWaitMs / pollIntervalMs));
|
|
153
|
-
for (let attempt = 0; attempt < maxPolls; attempt += 1) {
|
|
154
|
-
const thread = await client.query(reference(AGENT_THREAD_PATH), { key: threadKey });
|
|
155
|
-
const status = readThreadStatus(thread);
|
|
156
|
-
if (TERMINAL_STATUSES.has(status)) {
|
|
157
|
-
return await readTerminal(client, threadKey, status, thread);
|
|
158
|
-
}
|
|
159
|
-
await wait(pollIntervalMs);
|
|
160
|
-
}
|
|
161
|
-
return ok({ hint: "call lunora_agent_status with this threadKey to poll for the answer", runId: id, status: "running", threadKey });
|
|
162
|
-
} catch (error) {
|
|
163
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
164
|
-
return fail(message);
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
export { AGENT_RUN_INPUT_SCHEMA, AGENT_STATUS_TOOL_NAME, agentToolDefinitions, callAgentTool, finalAnswer, isAgentToolName, parseAgentsEnv };
|