@ampless/mcp-server 1.0.0-beta.67 → 1.0.0-beta.69
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.ja.md +7 -1
- package/README.md +7 -1
- package/dist/chunk-4REBPD2N.js +211 -0
- package/dist/jsonrpc/index.d.ts +15 -2
- package/dist/jsonrpc/index.js +19 -179
- package/dist/public/index.js +12 -8
- package/package.json +4 -3
package/README.ja.md
CHANGED
|
@@ -71,7 +71,12 @@ import type { ToolDefinition, ToolContext, ResolvedSite } from '@ampless/mcp-ser
|
|
|
71
71
|
### `./jsonrpc`
|
|
72
72
|
|
|
73
73
|
```typescript
|
|
74
|
-
import {
|
|
74
|
+
import {
|
|
75
|
+
dispatchJsonRpcMessage,
|
|
76
|
+
dispatchJsonRpc,
|
|
77
|
+
ToolUserError,
|
|
78
|
+
MAX_BATCH,
|
|
79
|
+
} from '@ampless/mcp-server/jsonrpc'
|
|
75
80
|
import type {
|
|
76
81
|
JsonRpcRequest,
|
|
77
82
|
JsonRpcResponse,
|
|
@@ -83,6 +88,7 @@ import type {
|
|
|
83
88
|
|---|---|
|
|
84
89
|
| `dispatchJsonRpcMessage(input, opts)` | **推奨エントリポイント。** 未検証のデコード済みメッセージ(`unknown` — 単一オブジェクト **または** batch 配列)を受け取り、タグ付き `JsonRpcMessageResult` を返す:`{ status: 'invalid', body }`(HTTP 400 — 非オブジェクト / 空 / 過大 batch)、`{ status: 'ok', body }`(HTTP 200 — 単一レスポンス、または batch のレスポンス配列)、`{ status: 'no-content' }`(HTTP 202 — notification のみ)。エンベロープ検証と batch 処理(逐次・順序維持・batch 内 `initialize` 禁止)をここに集約したので、トランスポートは `JSON.parse` の生の結果をそのまま渡せる。`opts.maxBatch` の既定は `MAX_BATCH` |
|
|
85
90
|
| `dispatchJsonRpc(req, opts)` | **検証済みの** JSON-RPC リクエストを 1 件ツールレジストリに対して実行(`initialize` のバージョンネゴシエーション、annotations 付き `tools/list`、`tools/call`、notification 処理)。notification(`id` 無し)は method を実行しつつレスポンスの代わりに `null` を返す。`id: null` / 小数 id は `INVALID_REQUEST` で拒否。ワイヤから来たものはエンベロープ検証と batch 対応を集約した `dispatchJsonRpcMessage` を使うこと |
|
|
91
|
+
| `ToolUserError` / `isToolUserError` | client に返して安全な message を持つ想定内の tool failure を示す。独立 bundle の package entry 間でも認識でき、dispatcher はこの class に対して `formatToolError` と logging を迂回する。message に secret、内部詳細、未処理のユーザ入力を絶対に入れないこと |
|
|
86
92
|
| `MAX_BATCH` | batch 要素数の既定上限(`50`) |
|
|
87
93
|
| `jsonRpcResult` / `jsonRpcError` / `JSON_RPC_*` | エンベロープヘルパ + 標準エラーコード |
|
|
88
94
|
| `SUPPORTED_PROTOCOL_VERSIONS` | `['2025-03-26', '2024-11-05']` |
|
package/README.md
CHANGED
|
@@ -71,7 +71,12 @@ import type { ToolDefinition, ToolContext, ResolvedSite } from '@ampless/mcp-ser
|
|
|
71
71
|
### `./jsonrpc`
|
|
72
72
|
|
|
73
73
|
```typescript
|
|
74
|
-
import {
|
|
74
|
+
import {
|
|
75
|
+
dispatchJsonRpcMessage,
|
|
76
|
+
dispatchJsonRpc,
|
|
77
|
+
ToolUserError,
|
|
78
|
+
MAX_BATCH,
|
|
79
|
+
} from '@ampless/mcp-server/jsonrpc'
|
|
75
80
|
import type {
|
|
76
81
|
JsonRpcRequest,
|
|
77
82
|
JsonRpcResponse,
|
|
@@ -83,6 +88,7 @@ import type {
|
|
|
83
88
|
|---|---|
|
|
84
89
|
| `dispatchJsonRpcMessage(input, opts)` | **Preferred entry point.** Takes an *unvalidated* decoded message (`unknown` — a single object **or** a batch array) and returns a tagged `JsonRpcMessageResult`: `{ status: 'invalid', body }` (HTTP 400 — top-level malformed / empty / over-size batch), `{ status: 'ok', body }` (HTTP 200 — single response or a batch's response array), or `{ status: 'no-content' }` (HTTP 202 — notification(s) only). Envelope validation and batch handling (sequential, order-preserving, `initialize` forbidden inside a batch) live here, so transports pass their raw `JSON.parse` result straight through. `opts.maxBatch` defaults to `MAX_BATCH`. |
|
|
85
90
|
| `dispatchJsonRpc(req, opts)` | Runs **one already-validated** JSON-RPC request against a tool registry (`initialize` with protocol negotiation, `tools/list` with annotations, `tools/call`, notification handling). A notification (`id` absent) still executes the method but returns `null` instead of a response; `id: null` / fractional ids are rejected as `INVALID_REQUEST`. Prefer `dispatchJsonRpcMessage` for anything coming off the wire — it centralises envelope checks and batch support. |
|
|
91
|
+
| `ToolUserError` / `isToolUserError` | Marks an expected tool failure whose message is safe to return to clients, including across independently bundled package entry points. The dispatcher bypasses `formatToolError` and logging for this class. Never put secrets, internal details, or unprocessed user input in its message. |
|
|
86
92
|
| `MAX_BATCH` | Default batch-element cap (`50`) |
|
|
87
93
|
| `jsonRpcResult` / `jsonRpcError` / `JSON_RPC_*` | Envelope helpers + standard error codes |
|
|
88
94
|
| `SUPPORTED_PROTOCOL_VERSIONS` | `['2025-03-26', '2024-11-05']` |
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// src/jsonrpc/index.ts
|
|
2
|
+
var JSON_RPC_PARSE_ERROR = -32700;
|
|
3
|
+
var JSON_RPC_INVALID_REQUEST = -32600;
|
|
4
|
+
var JSON_RPC_METHOD_NOT_FOUND = -32601;
|
|
5
|
+
var JSON_RPC_INVALID_PARAMS = -32602;
|
|
6
|
+
var JSON_RPC_INTERNAL_ERROR = -32603;
|
|
7
|
+
var TOOL_USER_ERROR_BRAND = /* @__PURE__ */ Symbol.for("ampless.mcp.toolUserError");
|
|
8
|
+
var ToolUserError = class extends Error {
|
|
9
|
+
[TOOL_USER_ERROR_BRAND] = true;
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "ToolUserError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function isToolUserError(error) {
|
|
16
|
+
return error instanceof Error && error[TOOL_USER_ERROR_BRAND] === true;
|
|
17
|
+
}
|
|
18
|
+
var SUPPORTED_PROTOCOL_VERSIONS = ["2025-03-26", "2024-11-05"];
|
|
19
|
+
var LATEST_SUPPORTED_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_VERSIONS[0];
|
|
20
|
+
function jsonRpcResult(id, result) {
|
|
21
|
+
return { jsonrpc: "2.0", id, result };
|
|
22
|
+
}
|
|
23
|
+
function jsonRpcError(id, code, message, data) {
|
|
24
|
+
const error = { code, message };
|
|
25
|
+
if (data !== void 0) error.data = data;
|
|
26
|
+
return { jsonrpc: "2.0", id, error };
|
|
27
|
+
}
|
|
28
|
+
function isNotification(req) {
|
|
29
|
+
return req.id === void 0;
|
|
30
|
+
}
|
|
31
|
+
function hasValidJsonRpcId(req) {
|
|
32
|
+
const id = req.id;
|
|
33
|
+
if (id === void 0) return true;
|
|
34
|
+
return typeof id === "string" || typeof id === "number" && Number.isInteger(id);
|
|
35
|
+
}
|
|
36
|
+
function isPlainObject(v) {
|
|
37
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
38
|
+
}
|
|
39
|
+
var MAX_BATCH = 50;
|
|
40
|
+
function idForError(input) {
|
|
41
|
+
if (isPlainObject(input) && hasValidJsonRpcId(input)) {
|
|
42
|
+
return input.id ?? null;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function validateEnvelope(input, inBatch) {
|
|
47
|
+
if (!isPlainObject(input)) {
|
|
48
|
+
return { ok: false, error: jsonRpcError(null, JSON_RPC_INVALID_REQUEST, "Invalid Request") };
|
|
49
|
+
}
|
|
50
|
+
const jsonrpc = input.jsonrpc;
|
|
51
|
+
const method = input.method;
|
|
52
|
+
if (jsonrpc !== "2.0" || typeof method !== "string" || !hasValidJsonRpcId(input)) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
error: jsonRpcError(idForError(input), JSON_RPC_INVALID_REQUEST, "Invalid Request")
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (inBatch && method === "initialize") {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
error: jsonRpcError(
|
|
62
|
+
idForError(input),
|
|
63
|
+
JSON_RPC_INVALID_REQUEST,
|
|
64
|
+
"Invalid Request: `initialize` is not allowed in a batch"
|
|
65
|
+
)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, req: input };
|
|
69
|
+
}
|
|
70
|
+
async function dispatchJsonRpcMessage(input, opts) {
|
|
71
|
+
const maxBatch = opts.maxBatch ?? MAX_BATCH;
|
|
72
|
+
if (Array.isArray(input)) {
|
|
73
|
+
if (input.length === 0 || input.length > maxBatch) {
|
|
74
|
+
return {
|
|
75
|
+
status: "invalid",
|
|
76
|
+
body: jsonRpcError(
|
|
77
|
+
null,
|
|
78
|
+
JSON_RPC_INVALID_REQUEST,
|
|
79
|
+
input.length === 0 ? "Invalid Request: empty batch" : `Invalid Request: batch exceeds the maximum of ${maxBatch} elements`
|
|
80
|
+
)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const responses = [];
|
|
84
|
+
for (const element of input) {
|
|
85
|
+
const validated2 = validateEnvelope(element, true);
|
|
86
|
+
if (!validated2.ok) {
|
|
87
|
+
responses.push(validated2.error);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const res2 = await dispatchJsonRpc(validated2.req, opts);
|
|
91
|
+
if (res2 !== null) responses.push(res2);
|
|
92
|
+
}
|
|
93
|
+
return responses.length === 0 ? { status: "no-content" } : { status: "ok", body: responses };
|
|
94
|
+
}
|
|
95
|
+
const validated = validateEnvelope(input, false);
|
|
96
|
+
if (!validated.ok) {
|
|
97
|
+
return { status: "invalid", body: validated.error };
|
|
98
|
+
}
|
|
99
|
+
const res = await dispatchJsonRpc(validated.req, opts);
|
|
100
|
+
return res === null ? { status: "no-content" } : { status: "ok", body: res };
|
|
101
|
+
}
|
|
102
|
+
function toolAnnotations(t) {
|
|
103
|
+
const annotations = {};
|
|
104
|
+
if (typeof t.readOnly === "boolean") annotations.readOnlyHint = t.readOnly;
|
|
105
|
+
if (typeof t.destructive === "boolean") annotations.destructiveHint = t.destructive;
|
|
106
|
+
return annotations;
|
|
107
|
+
}
|
|
108
|
+
async function dispatchJsonRpc(req, opts) {
|
|
109
|
+
if (!hasValidJsonRpcId(req)) {
|
|
110
|
+
return jsonRpcError(
|
|
111
|
+
null,
|
|
112
|
+
JSON_RPC_INVALID_REQUEST,
|
|
113
|
+
"Invalid Request: `id` must be a string or an integer"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const response = await dispatchMethod(req, opts);
|
|
117
|
+
return isNotification(req) ? null : response;
|
|
118
|
+
}
|
|
119
|
+
async function dispatchMethod(req, opts) {
|
|
120
|
+
const id = req.id ?? null;
|
|
121
|
+
switch (req.method) {
|
|
122
|
+
case "initialize": {
|
|
123
|
+
const requested = req.params?.protocolVersion;
|
|
124
|
+
if (typeof requested !== "string") {
|
|
125
|
+
return jsonRpcError(
|
|
126
|
+
id,
|
|
127
|
+
JSON_RPC_INVALID_PARAMS,
|
|
128
|
+
"initialize requires a string `protocolVersion` parameter"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(
|
|
132
|
+
requested
|
|
133
|
+
) ? requested : LATEST_SUPPORTED_PROTOCOL_VERSION;
|
|
134
|
+
return jsonRpcResult(id, {
|
|
135
|
+
protocolVersion,
|
|
136
|
+
capabilities: { tools: {} },
|
|
137
|
+
serverInfo: opts.serverInfo
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
case "notifications/initialized":
|
|
141
|
+
return jsonRpcResult(id, {});
|
|
142
|
+
case "tools/list":
|
|
143
|
+
return jsonRpcResult(id, {
|
|
144
|
+
tools: opts.tools.map((t) => ({
|
|
145
|
+
name: t.name,
|
|
146
|
+
description: t.description,
|
|
147
|
+
inputSchema: t.inputSchema,
|
|
148
|
+
annotations: toolAnnotations(t)
|
|
149
|
+
}))
|
|
150
|
+
});
|
|
151
|
+
case "tools/call": {
|
|
152
|
+
const params = req.params;
|
|
153
|
+
if (!params || typeof params.name !== "string") {
|
|
154
|
+
return jsonRpcError(id, JSON_RPC_INVALID_PARAMS, "tools/call requires a `name` parameter");
|
|
155
|
+
}
|
|
156
|
+
const rawArgs = params.arguments;
|
|
157
|
+
if (rawArgs !== void 0 && !isPlainObject(rawArgs)) {
|
|
158
|
+
return jsonRpcError(
|
|
159
|
+
id,
|
|
160
|
+
JSON_RPC_INVALID_PARAMS,
|
|
161
|
+
"tools/call `arguments` must be an object"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const tool = opts.tools.find((t) => t.name === params.name);
|
|
165
|
+
if (!tool) {
|
|
166
|
+
return jsonRpcError(id, JSON_RPC_METHOD_NOT_FOUND, `unknown tool: ${params.name}`);
|
|
167
|
+
}
|
|
168
|
+
const ctx = opts.getContext();
|
|
169
|
+
try {
|
|
170
|
+
const result = await tool.handler(rawArgs ?? {}, ctx);
|
|
171
|
+
return jsonRpcResult(id, {
|
|
172
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
173
|
+
});
|
|
174
|
+
} catch (err) {
|
|
175
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
176
|
+
const userError = isToolUserError(err);
|
|
177
|
+
if (!userError) {
|
|
178
|
+
console.error("[mcp-jsonrpc] tool dispatch failed", {
|
|
179
|
+
tool: params.name,
|
|
180
|
+
message: rawMessage
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const message = userError ? rawMessage : opts.formatToolError ? opts.formatToolError(err) : rawMessage;
|
|
184
|
+
return jsonRpcResult(id, {
|
|
185
|
+
isError: true,
|
|
186
|
+
content: [{ type: "text", text: message }]
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
default:
|
|
191
|
+
return jsonRpcError(id, JSON_RPC_METHOD_NOT_FOUND, `Method not found: ${req.method}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export {
|
|
196
|
+
JSON_RPC_PARSE_ERROR,
|
|
197
|
+
JSON_RPC_INVALID_REQUEST,
|
|
198
|
+
JSON_RPC_METHOD_NOT_FOUND,
|
|
199
|
+
JSON_RPC_INVALID_PARAMS,
|
|
200
|
+
JSON_RPC_INTERNAL_ERROR,
|
|
201
|
+
ToolUserError,
|
|
202
|
+
isToolUserError,
|
|
203
|
+
SUPPORTED_PROTOCOL_VERSIONS,
|
|
204
|
+
LATEST_SUPPORTED_PROTOCOL_VERSION,
|
|
205
|
+
jsonRpcResult,
|
|
206
|
+
jsonRpcError,
|
|
207
|
+
hasValidJsonRpcId,
|
|
208
|
+
MAX_BATCH,
|
|
209
|
+
dispatchJsonRpcMessage,
|
|
210
|
+
dispatchJsonRpc
|
|
211
|
+
};
|
package/dist/jsonrpc/index.d.ts
CHANGED
|
@@ -6,6 +6,18 @@ declare const JSON_RPC_INVALID_REQUEST = -32600;
|
|
|
6
6
|
declare const JSON_RPC_METHOD_NOT_FOUND = -32601;
|
|
7
7
|
declare const JSON_RPC_INVALID_PARAMS = -32602;
|
|
8
8
|
declare const JSON_RPC_INTERNAL_ERROR = -32603;
|
|
9
|
+
declare const TOOL_USER_ERROR_BRAND: unique symbol;
|
|
10
|
+
/**
|
|
11
|
+
* A tool failure whose message is intentionally safe to expose to MCP
|
|
12
|
+
* clients. Messages must not include secrets, internal details, or
|
|
13
|
+
* unprocessed user input.
|
|
14
|
+
*/
|
|
15
|
+
declare class ToolUserError extends Error {
|
|
16
|
+
readonly [TOOL_USER_ERROR_BRAND] = true;
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/** Recognises ToolUserError instances across independently bundled entry points. */
|
|
20
|
+
declare function isToolUserError(error: unknown): error is ToolUserError;
|
|
9
21
|
declare const SUPPORTED_PROTOCOL_VERSIONS: readonly ["2025-03-26", "2024-11-05"];
|
|
10
22
|
declare const LATEST_SUPPORTED_PROTOCOL_VERSION: "2025-03-26";
|
|
11
23
|
interface JsonRpcRequest {
|
|
@@ -49,7 +61,8 @@ interface JsonRpcDispatchOptions<TCtx> {
|
|
|
49
61
|
* Converts a tool handler exception into the string surfaced to the
|
|
50
62
|
* client. Omit to expose the raw error message (admin transport).
|
|
51
63
|
* The public transport passes a fixed message here and logs the
|
|
52
|
-
* detail server-side instead.
|
|
64
|
+
* detail server-side instead. ToolUserError messages bypass this
|
|
65
|
+
* formatter because they are explicitly marked as client-safe.
|
|
53
66
|
*/
|
|
54
67
|
formatToolError?: (err: unknown) => string;
|
|
55
68
|
}
|
|
@@ -126,4 +139,4 @@ declare function dispatchJsonRpcMessage<TCtx>(input: unknown, opts: JsonRpcDispa
|
|
|
126
139
|
*/
|
|
127
140
|
declare function dispatchJsonRpc<TCtx>(req: JsonRpcRequest, opts: JsonRpcDispatchOptions<TCtx>): Promise<JsonRpcResponse | null>;
|
|
128
141
|
|
|
129
|
-
export { JSON_RPC_INTERNAL_ERROR, JSON_RPC_INVALID_PARAMS, JSON_RPC_INVALID_REQUEST, JSON_RPC_METHOD_NOT_FOUND, JSON_RPC_PARSE_ERROR, type JsonRpcDispatchMessageOptions, type JsonRpcDispatchOptions, type JsonRpcMessageResult, type JsonRpcRequest, type JsonRpcResponse, LATEST_SUPPORTED_PROTOCOL_VERSION, MAX_BATCH, SUPPORTED_PROTOCOL_VERSIONS, dispatchJsonRpc, dispatchJsonRpcMessage, hasValidJsonRpcId, jsonRpcError, jsonRpcResult };
|
|
142
|
+
export { JSON_RPC_INTERNAL_ERROR, JSON_RPC_INVALID_PARAMS, JSON_RPC_INVALID_REQUEST, JSON_RPC_METHOD_NOT_FOUND, JSON_RPC_PARSE_ERROR, type JsonRpcDispatchMessageOptions, type JsonRpcDispatchOptions, type JsonRpcMessageResult, type JsonRpcRequest, type JsonRpcResponse, LATEST_SUPPORTED_PROTOCOL_VERSION, MAX_BATCH, SUPPORTED_PROTOCOL_VERSIONS, ToolUserError, dispatchJsonRpc, dispatchJsonRpcMessage, hasValidJsonRpcId, isToolUserError, jsonRpcError, jsonRpcResult };
|
package/dist/jsonrpc/index.js
CHANGED
|
@@ -1,182 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return req.id === void 0;
|
|
19
|
-
}
|
|
20
|
-
function hasValidJsonRpcId(req) {
|
|
21
|
-
const id = req.id;
|
|
22
|
-
if (id === void 0) return true;
|
|
23
|
-
return typeof id === "string" || typeof id === "number" && Number.isInteger(id);
|
|
24
|
-
}
|
|
25
|
-
function isPlainObject(v) {
|
|
26
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
27
|
-
}
|
|
28
|
-
var MAX_BATCH = 50;
|
|
29
|
-
function idForError(input) {
|
|
30
|
-
if (isPlainObject(input) && hasValidJsonRpcId(input)) {
|
|
31
|
-
return input.id ?? null;
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
function validateEnvelope(input, inBatch) {
|
|
36
|
-
if (!isPlainObject(input)) {
|
|
37
|
-
return { ok: false, error: jsonRpcError(null, JSON_RPC_INVALID_REQUEST, "Invalid Request") };
|
|
38
|
-
}
|
|
39
|
-
const jsonrpc = input.jsonrpc;
|
|
40
|
-
const method = input.method;
|
|
41
|
-
if (jsonrpc !== "2.0" || typeof method !== "string" || !hasValidJsonRpcId(input)) {
|
|
42
|
-
return {
|
|
43
|
-
ok: false,
|
|
44
|
-
error: jsonRpcError(idForError(input), JSON_RPC_INVALID_REQUEST, "Invalid Request")
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
if (inBatch && method === "initialize") {
|
|
48
|
-
return {
|
|
49
|
-
ok: false,
|
|
50
|
-
error: jsonRpcError(
|
|
51
|
-
idForError(input),
|
|
52
|
-
JSON_RPC_INVALID_REQUEST,
|
|
53
|
-
"Invalid Request: `initialize` is not allowed in a batch"
|
|
54
|
-
)
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
return { ok: true, req: input };
|
|
58
|
-
}
|
|
59
|
-
async function dispatchJsonRpcMessage(input, opts) {
|
|
60
|
-
const maxBatch = opts.maxBatch ?? MAX_BATCH;
|
|
61
|
-
if (Array.isArray(input)) {
|
|
62
|
-
if (input.length === 0 || input.length > maxBatch) {
|
|
63
|
-
return {
|
|
64
|
-
status: "invalid",
|
|
65
|
-
body: jsonRpcError(
|
|
66
|
-
null,
|
|
67
|
-
JSON_RPC_INVALID_REQUEST,
|
|
68
|
-
input.length === 0 ? "Invalid Request: empty batch" : `Invalid Request: batch exceeds the maximum of ${maxBatch} elements`
|
|
69
|
-
)
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
const responses = [];
|
|
73
|
-
for (const element of input) {
|
|
74
|
-
const validated2 = validateEnvelope(element, true);
|
|
75
|
-
if (!validated2.ok) {
|
|
76
|
-
responses.push(validated2.error);
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const res2 = await dispatchJsonRpc(validated2.req, opts);
|
|
80
|
-
if (res2 !== null) responses.push(res2);
|
|
81
|
-
}
|
|
82
|
-
return responses.length === 0 ? { status: "no-content" } : { status: "ok", body: responses };
|
|
83
|
-
}
|
|
84
|
-
const validated = validateEnvelope(input, false);
|
|
85
|
-
if (!validated.ok) {
|
|
86
|
-
return { status: "invalid", body: validated.error };
|
|
87
|
-
}
|
|
88
|
-
const res = await dispatchJsonRpc(validated.req, opts);
|
|
89
|
-
return res === null ? { status: "no-content" } : { status: "ok", body: res };
|
|
90
|
-
}
|
|
91
|
-
function toolAnnotations(t) {
|
|
92
|
-
const annotations = {};
|
|
93
|
-
if (typeof t.readOnly === "boolean") annotations.readOnlyHint = t.readOnly;
|
|
94
|
-
if (typeof t.destructive === "boolean") annotations.destructiveHint = t.destructive;
|
|
95
|
-
return annotations;
|
|
96
|
-
}
|
|
97
|
-
async function dispatchJsonRpc(req, opts) {
|
|
98
|
-
if (!hasValidJsonRpcId(req)) {
|
|
99
|
-
return jsonRpcError(
|
|
100
|
-
null,
|
|
101
|
-
JSON_RPC_INVALID_REQUEST,
|
|
102
|
-
"Invalid Request: `id` must be a string or an integer"
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
const response = await dispatchMethod(req, opts);
|
|
106
|
-
return isNotification(req) ? null : response;
|
|
107
|
-
}
|
|
108
|
-
async function dispatchMethod(req, opts) {
|
|
109
|
-
const id = req.id ?? null;
|
|
110
|
-
switch (req.method) {
|
|
111
|
-
case "initialize": {
|
|
112
|
-
const requested = req.params?.protocolVersion;
|
|
113
|
-
if (typeof requested !== "string") {
|
|
114
|
-
return jsonRpcError(
|
|
115
|
-
id,
|
|
116
|
-
JSON_RPC_INVALID_PARAMS,
|
|
117
|
-
"initialize requires a string `protocolVersion` parameter"
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(
|
|
121
|
-
requested
|
|
122
|
-
) ? requested : LATEST_SUPPORTED_PROTOCOL_VERSION;
|
|
123
|
-
return jsonRpcResult(id, {
|
|
124
|
-
protocolVersion,
|
|
125
|
-
capabilities: { tools: {} },
|
|
126
|
-
serverInfo: opts.serverInfo
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
case "notifications/initialized":
|
|
130
|
-
return jsonRpcResult(id, {});
|
|
131
|
-
case "tools/list":
|
|
132
|
-
return jsonRpcResult(id, {
|
|
133
|
-
tools: opts.tools.map((t) => ({
|
|
134
|
-
name: t.name,
|
|
135
|
-
description: t.description,
|
|
136
|
-
inputSchema: t.inputSchema,
|
|
137
|
-
annotations: toolAnnotations(t)
|
|
138
|
-
}))
|
|
139
|
-
});
|
|
140
|
-
case "tools/call": {
|
|
141
|
-
const params = req.params;
|
|
142
|
-
if (!params || typeof params.name !== "string") {
|
|
143
|
-
return jsonRpcError(id, JSON_RPC_INVALID_PARAMS, "tools/call requires a `name` parameter");
|
|
144
|
-
}
|
|
145
|
-
const rawArgs = params.arguments;
|
|
146
|
-
if (rawArgs !== void 0 && !isPlainObject(rawArgs)) {
|
|
147
|
-
return jsonRpcError(
|
|
148
|
-
id,
|
|
149
|
-
JSON_RPC_INVALID_PARAMS,
|
|
150
|
-
"tools/call `arguments` must be an object"
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
const tool = opts.tools.find((t) => t.name === params.name);
|
|
154
|
-
if (!tool) {
|
|
155
|
-
return jsonRpcError(id, JSON_RPC_METHOD_NOT_FOUND, `unknown tool: ${params.name}`);
|
|
156
|
-
}
|
|
157
|
-
const ctx = opts.getContext();
|
|
158
|
-
try {
|
|
159
|
-
const result = await tool.handler(rawArgs ?? {}, ctx);
|
|
160
|
-
return jsonRpcResult(id, {
|
|
161
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
162
|
-
});
|
|
163
|
-
} catch (err) {
|
|
164
|
-
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
165
|
-
console.error("[mcp-jsonrpc] tool dispatch failed", {
|
|
166
|
-
tool: params.name,
|
|
167
|
-
message: rawMessage
|
|
168
|
-
});
|
|
169
|
-
const message = opts.formatToolError ? opts.formatToolError(err) : rawMessage;
|
|
170
|
-
return jsonRpcResult(id, {
|
|
171
|
-
isError: true,
|
|
172
|
-
content: [{ type: "text", text: message }]
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
default:
|
|
177
|
-
return jsonRpcError(id, JSON_RPC_METHOD_NOT_FOUND, `Method not found: ${req.method}`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
JSON_RPC_INTERNAL_ERROR,
|
|
3
|
+
JSON_RPC_INVALID_PARAMS,
|
|
4
|
+
JSON_RPC_INVALID_REQUEST,
|
|
5
|
+
JSON_RPC_METHOD_NOT_FOUND,
|
|
6
|
+
JSON_RPC_PARSE_ERROR,
|
|
7
|
+
LATEST_SUPPORTED_PROTOCOL_VERSION,
|
|
8
|
+
MAX_BATCH,
|
|
9
|
+
SUPPORTED_PROTOCOL_VERSIONS,
|
|
10
|
+
ToolUserError,
|
|
11
|
+
dispatchJsonRpc,
|
|
12
|
+
dispatchJsonRpcMessage,
|
|
13
|
+
hasValidJsonRpcId,
|
|
14
|
+
isToolUserError,
|
|
15
|
+
jsonRpcError,
|
|
16
|
+
jsonRpcResult
|
|
17
|
+
} from "../chunk-4REBPD2N.js";
|
|
180
18
|
export {
|
|
181
19
|
JSON_RPC_INTERNAL_ERROR,
|
|
182
20
|
JSON_RPC_INVALID_PARAMS,
|
|
@@ -186,9 +24,11 @@ export {
|
|
|
186
24
|
LATEST_SUPPORTED_PROTOCOL_VERSION,
|
|
187
25
|
MAX_BATCH,
|
|
188
26
|
SUPPORTED_PROTOCOL_VERSIONS,
|
|
27
|
+
ToolUserError,
|
|
189
28
|
dispatchJsonRpc,
|
|
190
29
|
dispatchJsonRpcMessage,
|
|
191
30
|
hasValidJsonRpcId,
|
|
31
|
+
isToolUserError,
|
|
192
32
|
jsonRpcError,
|
|
193
33
|
jsonRpcResult
|
|
194
34
|
};
|
package/dist/public/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToolUserError
|
|
3
|
+
} from "../chunk-4REBPD2N.js";
|
|
4
|
+
|
|
1
5
|
// src/public/types.ts
|
|
2
6
|
function toPublicSummary(post) {
|
|
3
7
|
const summary = {
|
|
@@ -28,33 +32,33 @@ function clampInt(v, fallback, min, max) {
|
|
|
28
32
|
}
|
|
29
33
|
function validateSlug(v) {
|
|
30
34
|
if (typeof v !== "string") {
|
|
31
|
-
throw new
|
|
35
|
+
throw new ToolUserError("`slug` is required and must be a string");
|
|
32
36
|
}
|
|
33
37
|
if (v.length === 0) {
|
|
34
|
-
throw new
|
|
38
|
+
throw new ToolUserError("`slug` must not be empty");
|
|
35
39
|
}
|
|
36
40
|
if (v.length > MAX_SLUG_LEN) {
|
|
37
|
-
throw new
|
|
41
|
+
throw new ToolUserError(`\`slug\` must be at most ${MAX_SLUG_LEN} characters`);
|
|
38
42
|
}
|
|
39
43
|
return v;
|
|
40
44
|
}
|
|
41
45
|
function validateCursor(v) {
|
|
42
46
|
if (v === void 0 || v === null) return void 0;
|
|
43
47
|
if (typeof v !== "string") {
|
|
44
|
-
throw new
|
|
48
|
+
throw new ToolUserError("`cursor` must be a string");
|
|
45
49
|
}
|
|
46
50
|
if (v.length > MAX_CURSOR_LEN) {
|
|
47
|
-
throw new
|
|
51
|
+
throw new ToolUserError(`\`cursor\` must be at most ${MAX_CURSOR_LEN} characters`);
|
|
48
52
|
}
|
|
49
53
|
return v;
|
|
50
54
|
}
|
|
51
55
|
function validateQuery(v) {
|
|
52
56
|
if (typeof v !== "string") {
|
|
53
|
-
throw new
|
|
57
|
+
throw new ToolUserError("`query` is required and must be a string");
|
|
54
58
|
}
|
|
55
59
|
const trimmed = v.trim();
|
|
56
60
|
if (trimmed.length === 0) {
|
|
57
|
-
throw new
|
|
61
|
+
throw new ToolUserError("`query` must not be empty");
|
|
58
62
|
}
|
|
59
63
|
return trimmed.length > MAX_QUERY_LEN ? trimmed.slice(0, MAX_QUERY_LEN) : trimmed;
|
|
60
64
|
}
|
|
@@ -134,7 +138,7 @@ var getPostTool = {
|
|
|
134
138
|
const frontmatter = typeof args.frontmatter === "boolean" ? args.frontmatter : true;
|
|
135
139
|
const post = await ctx.getPublishedPost(slug);
|
|
136
140
|
if (!post) {
|
|
137
|
-
throw new
|
|
141
|
+
throw new ToolUserError("No published post found for the requested slug.");
|
|
138
142
|
}
|
|
139
143
|
const summary = toPublicSummary(post);
|
|
140
144
|
const rendered = await ctx.postToMarkdown(post, { frontmatter });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/mcp-server",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.69",
|
|
4
4
|
"description": "MCP tool registry shared by @ampless/backend mcp-handler Lambda. Installed transitively via @ampless/admin / @ampless/backend; no direct install needed.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"bugs": "https://github.com/heavymoons/ampless/issues",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"fflate": "^0.8.3",
|
|
37
|
-
"ampless": "1.0.0-beta.
|
|
37
|
+
"ampless": "1.0.0-beta.62"
|
|
38
38
|
},
|
|
39
39
|
"keywords": [
|
|
40
40
|
"ampless",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"build": "tsup",
|
|
46
46
|
"dev": "tsup --watch",
|
|
47
47
|
"lint": "tsc --noEmit",
|
|
48
|
-
"test": "vitest run --passWithNoTests"
|
|
48
|
+
"test": "vitest run --passWithNoTests",
|
|
49
|
+
"test:dist": "node scripts/tool-user-error-dist-smoke.mjs"
|
|
49
50
|
}
|
|
50
51
|
}
|