@lunora/mcp 1.0.0-alpha.25 → 1.0.0-alpha.250
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-fnFScxPw.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-CPjNPMx3.mjs +3 -0
- package/dist/packem_shared/DOCS_SERVER_NAME-Bk6vD8lk.mjs +1 -0
- package/dist/packem_shared/DOCS_URI_SCHEME-Dd_rZo2y.mjs +3 -0
- package/dist/packem_shared/LOCAL_SERVER_NAME-DfC1rbyh.mjs +1 -0
- package/dist/packem_shared/OBSERVABILITY_TOOL_DEFINITIONS-Dz7pHc8X.mjs +1 -0
- package/dist/packem_shared/READ_ONLY_TOOL_DEFINITIONS-gYI6MQSL.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-Bhvmpb81.mjs +1 -0
- package/dist/packem_shared/createAuthedMcpFetchHandler-D-_8Rb95.mjs +1 -0
- package/dist/packem_shared/createLunoraMcpServer-iQFtbxDj.mjs +1 -0
- package/dist/packem_shared/createMcpFetchHandler-D3vS-kTl.mjs +1 -0
- package/dist/packem_shared/createPaidMcpServer-BMuBkZCP.mjs +1 -0
- package/dist/packem_shared/createToolServer-DV1g-DGe.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-D3oW8Fxx.mjs +1 -0
- package/dist/packem_shared/tools-DiCVoZgA.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
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
|
|
3
|
-
const RUN_INPUT_SCHEMA = {
|
|
4
|
-
properties: {
|
|
5
|
-
args: { description: "Arguments object passed to the function", type: "object" },
|
|
6
|
-
functionPath: { description: 'Function reference, e.g. "messages:send"', type: "string" },
|
|
7
|
-
shardKey: { description: "Optional shard key when the function is .shardBy()-partitioned", type: "string" }
|
|
8
|
-
},
|
|
9
|
-
required: ["functionPath"],
|
|
10
|
-
type: "object"
|
|
11
|
-
};
|
|
12
|
-
const NO_INPUT_SCHEMA = { properties: {}, type: "object" };
|
|
13
|
-
const FUNCTION_PATH_INPUT_SCHEMA = {
|
|
14
|
-
properties: {
|
|
15
|
-
functionPath: { description: 'Function reference, e.g. "messages:send"', type: "string" }
|
|
16
|
-
},
|
|
17
|
-
required: ["functionPath"],
|
|
18
|
-
type: "object"
|
|
19
|
-
};
|
|
20
|
-
const READ_ONLY_TOOL_DEFINITIONS = [
|
|
21
|
-
{
|
|
22
|
-
description: "List the deployment's public functions (queries, mutations, actions) with their kinds.",
|
|
23
|
-
inputSchema: NO_INPUT_SCHEMA,
|
|
24
|
-
name: "lunora_list_functions"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
description: "List the deployment's .global() tables and their column shapes.",
|
|
28
|
-
inputSchema: NO_INPUT_SCHEMA,
|
|
29
|
-
name: "lunora_list_tables"
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
description: "Return a function's argument JSON Schema and kind, so a caller can construct a valid arguments object. Call lunora_list_functions first to discover available function paths.",
|
|
33
|
-
inputSchema: FUNCTION_PATH_INPUT_SCHEMA,
|
|
34
|
-
name: "lunora_get_function_schema"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
description: "Run a query and return its result. Read-only.",
|
|
38
|
-
inputSchema: RUN_INPUT_SCHEMA,
|
|
39
|
-
name: "lunora_run_query"
|
|
40
|
-
}
|
|
41
|
-
];
|
|
42
|
-
const WRITE_TOOL_DEFINITIONS = [
|
|
43
|
-
{
|
|
44
|
-
description: "Run a mutation and return its result. Writes data — use with care.",
|
|
45
|
-
inputSchema: RUN_INPUT_SCHEMA,
|
|
46
|
-
name: "lunora_run_mutation"
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
description: "Run an action and return its result. May call external services.",
|
|
50
|
-
inputSchema: RUN_INPUT_SCHEMA,
|
|
51
|
-
name: "lunora_run_action"
|
|
52
|
-
}
|
|
53
|
-
];
|
|
54
|
-
const WRITE_TOOL_NAMES = new Set(WRITE_TOOL_DEFINITIONS.map((tool) => tool.name));
|
|
55
|
-
const toolDefinitions = (allowWrites) => (
|
|
56
|
-
// Fail closed: only the boolean `true` opts in. These are exported helpers, so
|
|
57
|
-
// an env-plumbed/JS caller could pass a truthy string like `"false"`/`"0"` —
|
|
58
|
-
// the explicit `=== true` guards that despite the declared `boolean` type.
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare -- intentional runtime guard at an exported API boundary against non-boolean callers
|
|
60
|
-
allowWrites === true ? [...READ_ONLY_TOOL_DEFINITIONS, ...WRITE_TOOL_DEFINITIONS] : READ_ONLY_TOOL_DEFINITIONS
|
|
61
|
-
);
|
|
62
|
-
const readFunctionPath = (input) => {
|
|
63
|
-
const { functionPath } = input;
|
|
64
|
-
if (typeof functionPath !== "string" || functionPath.length === 0) {
|
|
65
|
-
throw new LunoraError("INTERNAL", '"functionPath" is required and must be a non-empty string');
|
|
66
|
-
}
|
|
67
|
-
return functionPath;
|
|
68
|
-
};
|
|
69
|
-
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70
|
-
const describeArgs = (value) => Array.isArray(value) ? "an array" : `a ${typeof value}`;
|
|
71
|
-
const readArgumentsBag = (raw) => {
|
|
72
|
-
if (raw === void 0 || raw === null) {
|
|
73
|
-
return {};
|
|
74
|
-
}
|
|
75
|
-
if (typeof raw === "string") {
|
|
76
|
-
let parsed;
|
|
77
|
-
try {
|
|
78
|
-
parsed = JSON.parse(raw);
|
|
79
|
-
} catch {
|
|
80
|
-
throw new LunoraError("BAD_REQUEST", `"args" must be a JSON object; received a string that is not valid JSON`);
|
|
81
|
-
}
|
|
82
|
-
if (!isPlainObject(parsed)) {
|
|
83
|
-
throw new LunoraError("BAD_REQUEST", `"args" must be a JSON object; the provided string decoded to ${describeArgs(parsed)}`);
|
|
84
|
-
}
|
|
85
|
-
return parsed;
|
|
86
|
-
}
|
|
87
|
-
if (!isPlainObject(raw)) {
|
|
88
|
-
throw new LunoraError("BAD_REQUEST", `"args" must be a JSON object, got ${describeArgs(raw)}`);
|
|
89
|
-
}
|
|
90
|
-
return raw;
|
|
91
|
-
};
|
|
92
|
-
const readRunArguments = (input) => {
|
|
93
|
-
const functionPath = readFunctionPath(input);
|
|
94
|
-
const args = readArgumentsBag(input.args);
|
|
95
|
-
const shardKey = typeof input.shardKey === "string" && input.shardKey.length > 0 ? input.shardKey : void 0;
|
|
96
|
-
return { args, functionPath, shardKey };
|
|
97
|
-
};
|
|
98
|
-
const reference = (functionPath) => {
|
|
99
|
-
return { __lunoraRef: functionPath };
|
|
100
|
-
};
|
|
101
|
-
const bytesToBase64 = (bytes) => {
|
|
102
|
-
let binary = "";
|
|
103
|
-
const chunk = 32768;
|
|
104
|
-
for (let index = 0; index < bytes.length; index += chunk) {
|
|
105
|
-
binary += String.fromCharCode(...bytes.subarray(index, index + chunk));
|
|
106
|
-
}
|
|
107
|
-
return btoa(binary);
|
|
108
|
-
};
|
|
109
|
-
const jsonResultReplacer = (_key, value) => {
|
|
110
|
-
if (typeof value === "bigint") {
|
|
111
|
-
return value.toString();
|
|
112
|
-
}
|
|
113
|
-
if (value instanceof ArrayBuffer) {
|
|
114
|
-
return bytesToBase64(new Uint8Array(value));
|
|
115
|
-
}
|
|
116
|
-
if (ArrayBuffer.isView(value)) {
|
|
117
|
-
const view = value;
|
|
118
|
-
return bytesToBase64(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
|
119
|
-
}
|
|
120
|
-
return value;
|
|
121
|
-
};
|
|
122
|
-
const ok = (value) => {
|
|
123
|
-
const text = value === void 0 ? "null" : JSON.stringify(value, jsonResultReplacer, 2);
|
|
124
|
-
return { content: [{ text, type: "text" }] };
|
|
125
|
-
};
|
|
126
|
-
const FUNCTIONS_CACHE_TTL_MS = 3e4;
|
|
127
|
-
const functionsCache = /* @__PURE__ */ new WeakMap();
|
|
128
|
-
const listFunctionsCached = (client) => {
|
|
129
|
-
const now = Date.now();
|
|
130
|
-
const cached = functionsCache.get(client);
|
|
131
|
-
if (cached !== void 0 && cached.expiresAt > now) {
|
|
132
|
-
return cached.promise;
|
|
133
|
-
}
|
|
134
|
-
const promise = client.listFunctions().catch((error) => {
|
|
135
|
-
if (functionsCache.get(client)?.promise === promise) {
|
|
136
|
-
functionsCache.delete(client);
|
|
137
|
-
}
|
|
138
|
-
throw error;
|
|
139
|
-
});
|
|
140
|
-
functionsCache.set(client, { expiresAt: now + FUNCTIONS_CACHE_TTL_MS, promise });
|
|
141
|
-
return promise;
|
|
142
|
-
};
|
|
143
|
-
const assertRunnable = async (client, functionPath, expectedKind) => {
|
|
144
|
-
const functions = await listFunctionsCached(client);
|
|
145
|
-
const descriptor = functions.find((function_) => function_.path === functionPath);
|
|
146
|
-
if (descriptor === void 0) {
|
|
147
|
-
throw new LunoraError("NOT_FOUND", `function not found or not public: ${functionPath}`);
|
|
148
|
-
}
|
|
149
|
-
if (descriptor.kind !== expectedKind) {
|
|
150
|
-
throw new LunoraError("BAD_REQUEST", `function ${functionPath} is a ${descriptor.kind}, not a ${expectedKind}`);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
const callTool = async (client, name, input, allowWrites = false) => {
|
|
154
|
-
try {
|
|
155
|
-
if (allowWrites !== true && WRITE_TOOL_NAMES.has(name)) {
|
|
156
|
-
return {
|
|
157
|
-
content: [
|
|
158
|
-
{ text: `tool "${name}" is disabled: this MCP server is read-only. Enable writes with the LUNORA_MCP_ALLOW_WRITES env var.`, type: "text" }
|
|
159
|
-
],
|
|
160
|
-
isError: true
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
switch (name) {
|
|
164
|
-
case "lunora_get_function_schema": {
|
|
165
|
-
const functionPath = readFunctionPath(input);
|
|
166
|
-
const functions = await listFunctionsCached(client);
|
|
167
|
-
const descriptor = functions.find((function_) => function_.path === functionPath);
|
|
168
|
-
if (descriptor === void 0) {
|
|
169
|
-
return { content: [{ text: `function not found: ${functionPath}`, type: "text" }], isError: true };
|
|
170
|
-
}
|
|
171
|
-
return ok({ args: descriptor.args ?? [], kind: descriptor.kind, path: descriptor.path });
|
|
172
|
-
}
|
|
173
|
-
case "lunora_list_functions": {
|
|
174
|
-
return ok(await listFunctionsCached(client));
|
|
175
|
-
}
|
|
176
|
-
case "lunora_list_tables": {
|
|
177
|
-
return ok(await client.listGlobalTables());
|
|
178
|
-
}
|
|
179
|
-
case "lunora_run_action": {
|
|
180
|
-
const { args, functionPath, shardKey } = readRunArguments(input);
|
|
181
|
-
await assertRunnable(client, functionPath, "action");
|
|
182
|
-
return ok(await client.action(reference(functionPath), args, { shardKey }));
|
|
183
|
-
}
|
|
184
|
-
case "lunora_run_mutation": {
|
|
185
|
-
const { args, functionPath, shardKey } = readRunArguments(input);
|
|
186
|
-
await assertRunnable(client, functionPath, "mutation");
|
|
187
|
-
return ok(await client.mutation(reference(functionPath), args, { shardKey }));
|
|
188
|
-
}
|
|
189
|
-
case "lunora_run_query": {
|
|
190
|
-
const { args, functionPath, shardKey } = readRunArguments(input);
|
|
191
|
-
await assertRunnable(client, functionPath, "query");
|
|
192
|
-
return ok(await client.query(reference(functionPath), args, { shardKey }));
|
|
193
|
-
}
|
|
194
|
-
default: {
|
|
195
|
-
return { content: [{ text: `unknown tool: ${name}`, type: "text" }], isError: true };
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
} catch (error) {
|
|
199
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
200
|
-
return { content: [{ text: message, type: "text" }], isError: true };
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
export { READ_ONLY_TOOL_DEFINITIONS, WRITE_TOOL_DEFINITIONS, callTool, toolDefinitions };
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { LunoraClient } from '@lunora/client';
|
|
5
|
-
import { LunoraError } from '@lunora/errors';
|
|
6
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
|
-
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
-
import { agentToolDefinitions, isAgentToolName, callAgentTool } from './AGENT_RUN_INPUT_SCHEMA-DWobae3P.mjs';
|
|
10
|
-
import { toolDefinitions, callTool } from './READ_ONLY_TOOL_DEFINITIONS-CWb_wFd5.mjs';
|
|
11
|
-
|
|
12
|
-
const resolveVersion = () => {
|
|
13
|
-
try {
|
|
14
|
-
let directory = dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
for (let depth = 0; depth < 8; depth += 1) {
|
|
16
|
-
try {
|
|
17
|
-
const raw = readFileSync(join(directory, "package.json"), "utf8");
|
|
18
|
-
const pkg = JSON.parse(raw);
|
|
19
|
-
if (pkg.name === "@lunora/mcp" && typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
20
|
-
return pkg.version;
|
|
21
|
-
}
|
|
22
|
-
} catch {
|
|
23
|
-
}
|
|
24
|
-
const parent = dirname(directory);
|
|
25
|
-
if (parent === directory) {
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
directory = parent;
|
|
29
|
-
}
|
|
30
|
-
} catch {
|
|
31
|
-
}
|
|
32
|
-
return "0.0.0";
|
|
33
|
-
};
|
|
34
|
-
const SERVER_INFO = { name: "lunora", version: resolveVersion() };
|
|
35
|
-
const resolveClient = (options) => {
|
|
36
|
-
if (options.client !== void 0) {
|
|
37
|
-
return options.client;
|
|
38
|
-
}
|
|
39
|
-
if (options.url === void 0) {
|
|
40
|
-
throw new LunoraError("INTERNAL", "createLunoraMcpServer requires either a `client` or a `url`");
|
|
41
|
-
}
|
|
42
|
-
const client = new LunoraClient({ fetch: options.fetch, url: options.url });
|
|
43
|
-
if (options.token !== void 0) {
|
|
44
|
-
client.setAuthToken(options.token);
|
|
45
|
-
}
|
|
46
|
-
return client;
|
|
47
|
-
};
|
|
48
|
-
const createLunoraMcpServer = (options) => {
|
|
49
|
-
const client = resolveClient(options);
|
|
50
|
-
const allowWrites = options.allowWrites ?? false;
|
|
51
|
-
const allowAgents = options.allowAgents ?? false;
|
|
52
|
-
const agents = options.agents ?? [];
|
|
53
|
-
const server = new Server(SERVER_INFO, { capabilities: { tools: {} } });
|
|
54
|
-
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
55
|
-
return { tools: [...toolDefinitions(allowWrites), ...agentToolDefinitions(agents, allowAgents)] };
|
|
56
|
-
});
|
|
57
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
58
|
-
const { arguments: rawArguments, name } = request.params;
|
|
59
|
-
const input = rawArguments ?? {};
|
|
60
|
-
const result = isAgentToolName(name, agents) ? await callAgentTool(client, name, input, {
|
|
61
|
-
allowAgents,
|
|
62
|
-
exposures: agents,
|
|
63
|
-
...options.agentMaxWaitMs === void 0 ? {} : { maxWaitMs: options.agentMaxWaitMs },
|
|
64
|
-
...options.agentPollIntervalMs === void 0 ? {} : { pollIntervalMs: options.agentPollIntervalMs }
|
|
65
|
-
}) : await callTool(client, name, input, allowWrites);
|
|
66
|
-
return result;
|
|
67
|
-
});
|
|
68
|
-
return server;
|
|
69
|
-
};
|
|
70
|
-
const connectStdio = async (options) => {
|
|
71
|
-
const server = createLunoraMcpServer(options);
|
|
72
|
-
await server.connect(new StdioServerTransport());
|
|
73
|
-
return server;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export { connectStdio, createLunoraMcpServer };
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
2
|
-
import { createLunoraMcpServer } from './connectStdio-bkJcaXYE.mjs';
|
|
3
|
-
|
|
4
|
-
const serveStateless = async (server, request, options) => {
|
|
5
|
-
const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true, sessionIdGenerator: void 0 });
|
|
6
|
-
await server.connect(transport);
|
|
7
|
-
const response = await transport.handleRequest(request, options);
|
|
8
|
-
transport.close().catch(() => void 0);
|
|
9
|
-
server.close().catch(() => void 0);
|
|
10
|
-
return response;
|
|
11
|
-
};
|
|
12
|
-
const createMcpFetchHandler = (options) => (request) => serveStateless(createLunoraMcpServer(options), request);
|
|
13
|
-
|
|
14
|
-
export { createMcpFetchHandler, serveStateless };
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { createChargeMiddleware } from '@lunora/x402/charge';
|
|
3
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
-
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
-
import { serveStateless } from './createMcpFetchHandler-DfzBSLrm.mjs';
|
|
6
|
-
|
|
7
|
-
const DEFAULT_SERVER_INFO = { name: "lunora-paid-mcp", version: "0.0.0" };
|
|
8
|
-
const CALL_TOOL_METHOD = "tools/call";
|
|
9
|
-
const callToolName = (message) => {
|
|
10
|
-
if (typeof message !== "object" || message === null) {
|
|
11
|
-
return void 0;
|
|
12
|
-
}
|
|
13
|
-
const { method, params } = message;
|
|
14
|
-
if (method !== CALL_TOOL_METHOD || typeof params !== "object" || params === null) {
|
|
15
|
-
return void 0;
|
|
16
|
-
}
|
|
17
|
-
const { name } = params;
|
|
18
|
-
return typeof name === "string" ? name : void 0;
|
|
19
|
-
};
|
|
20
|
-
const refuseBatch = () => Response.json({ error: "A JSON-RPC batch may not reference a paid MCP tool; send paid tools/call requests individually." }, { status: 400 });
|
|
21
|
-
const createPaidMcpServer = (config) => {
|
|
22
|
-
const tools = /* @__PURE__ */ new Map();
|
|
23
|
-
const prices = /* @__PURE__ */ new Map();
|
|
24
|
-
const middlewareByTool = /* @__PURE__ */ new Map();
|
|
25
|
-
const serverInfo = config.serverInfo ?? DEFAULT_SERVER_INFO;
|
|
26
|
-
const register = (options, handler, price) => {
|
|
27
|
-
if (tools.has(options.name)) {
|
|
28
|
-
throw new LunoraError("BAD_REQUEST", `MCP tool "${options.name}" is already registered.`);
|
|
29
|
-
}
|
|
30
|
-
const definition = { description: options.description, inputSchema: options.inputSchema, name: options.name };
|
|
31
|
-
if (options.annotations !== void 0) {
|
|
32
|
-
definition.annotations = options.annotations;
|
|
33
|
-
}
|
|
34
|
-
tools.set(options.name, { definition, handler });
|
|
35
|
-
if (price !== void 0) {
|
|
36
|
-
prices.set(options.name, price);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
const buildServer = () => {
|
|
40
|
-
const server = new Server(serverInfo, { capabilities: { tools: {} } });
|
|
41
|
-
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
42
|
-
return { tools: [...tools.values()].map((entry) => entry.definition) };
|
|
43
|
-
});
|
|
44
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
45
|
-
const entry = tools.get(request.params.name);
|
|
46
|
-
if (entry === void 0) {
|
|
47
|
-
return { content: [{ text: `unknown tool: ${request.params.name}`, type: "text" }], isError: true };
|
|
48
|
-
}
|
|
49
|
-
const result = await entry.handler(request.params.arguments ?? {});
|
|
50
|
-
return result;
|
|
51
|
-
});
|
|
52
|
-
return server;
|
|
53
|
-
};
|
|
54
|
-
const gateFor = (name, price) => {
|
|
55
|
-
let pending = middlewareByTool.get(name);
|
|
56
|
-
if (pending === void 0) {
|
|
57
|
-
pending = createChargeMiddleware({ ...config.charge, price }, { resource: name }).catch((error) => {
|
|
58
|
-
middlewareByTool.delete(name);
|
|
59
|
-
throw error;
|
|
60
|
-
});
|
|
61
|
-
middlewareByTool.set(name, pending);
|
|
62
|
-
}
|
|
63
|
-
return pending;
|
|
64
|
-
};
|
|
65
|
-
const fetchHandler = async (request) => {
|
|
66
|
-
let parsedBody;
|
|
67
|
-
try {
|
|
68
|
-
parsedBody = await request.clone().json();
|
|
69
|
-
} catch {
|
|
70
|
-
parsedBody = void 0;
|
|
71
|
-
}
|
|
72
|
-
const dispatch = () => serveStateless(buildServer(), request, parsedBody === void 0 ? void 0 : { parsedBody });
|
|
73
|
-
if (Array.isArray(parsedBody)) {
|
|
74
|
-
return parsedBody.some((message) => prices.has(callToolName(message) ?? "")) ? refuseBatch() : dispatch();
|
|
75
|
-
}
|
|
76
|
-
const name = callToolName(parsedBody);
|
|
77
|
-
const price = name === void 0 ? void 0 : prices.get(name);
|
|
78
|
-
if (name === void 0 || price === void 0) {
|
|
79
|
-
return dispatch();
|
|
80
|
-
}
|
|
81
|
-
const middleware = await gateFor(name, price);
|
|
82
|
-
return middleware.handle(request, dispatch);
|
|
83
|
-
};
|
|
84
|
-
const paidTool = (options, handler) => {
|
|
85
|
-
register(options, handler, options.price);
|
|
86
|
-
};
|
|
87
|
-
const tool = (options, handler) => {
|
|
88
|
-
register(options, handler);
|
|
89
|
-
};
|
|
90
|
-
return { fetchHandler, paidTool, tool };
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
export { createPaidMcpServer };
|