@ampless/mcp-server 1.0.0-beta.66 → 1.0.0-beta.68
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 +15 -3
- package/README.md +15 -3
- package/dist/chunk-4REBPD2N.js +211 -0
- package/dist/jsonrpc/index.d.ts +64 -2
- package/dist/jsonrpc/index.js +21 -116
- package/dist/public/index.js +12 -8
- package/package.json +4 -3
package/README.ja.md
CHANGED
|
@@ -71,13 +71,25 @@ import type { ToolDefinition, ToolContext, ResolvedSite } from '@ampless/mcp-ser
|
|
|
71
71
|
### `./jsonrpc`
|
|
72
72
|
|
|
73
73
|
```typescript
|
|
74
|
-
import {
|
|
75
|
-
|
|
74
|
+
import {
|
|
75
|
+
dispatchJsonRpcMessage,
|
|
76
|
+
dispatchJsonRpc,
|
|
77
|
+
ToolUserError,
|
|
78
|
+
MAX_BATCH,
|
|
79
|
+
} from '@ampless/mcp-server/jsonrpc'
|
|
80
|
+
import type {
|
|
81
|
+
JsonRpcRequest,
|
|
82
|
+
JsonRpcResponse,
|
|
83
|
+
JsonRpcMessageResult,
|
|
84
|
+
} from '@ampless/mcp-server/jsonrpc'
|
|
76
85
|
```
|
|
77
86
|
|
|
78
87
|
| エクスポート | 説明 |
|
|
79
88
|
|---|---|
|
|
80
|
-
| `
|
|
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` |
|
|
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、内部詳細、未処理のユーザ入力を絶対に入れないこと |
|
|
92
|
+
| `MAX_BATCH` | batch 要素数の既定上限(`50`) |
|
|
81
93
|
| `jsonRpcResult` / `jsonRpcError` / `JSON_RPC_*` | エンベロープヘルパ + 標準エラーコード |
|
|
82
94
|
| `SUPPORTED_PROTOCOL_VERSIONS` | `['2025-03-26', '2024-11-05']` |
|
|
83
95
|
|
package/README.md
CHANGED
|
@@ -71,13 +71,25 @@ import type { ToolDefinition, ToolContext, ResolvedSite } from '@ampless/mcp-ser
|
|
|
71
71
|
### `./jsonrpc`
|
|
72
72
|
|
|
73
73
|
```typescript
|
|
74
|
-
import {
|
|
75
|
-
|
|
74
|
+
import {
|
|
75
|
+
dispatchJsonRpcMessage,
|
|
76
|
+
dispatchJsonRpc,
|
|
77
|
+
ToolUserError,
|
|
78
|
+
MAX_BATCH,
|
|
79
|
+
} from '@ampless/mcp-server/jsonrpc'
|
|
80
|
+
import type {
|
|
81
|
+
JsonRpcRequest,
|
|
82
|
+
JsonRpcResponse,
|
|
83
|
+
JsonRpcMessageResult,
|
|
84
|
+
} from '@ampless/mcp-server/jsonrpc'
|
|
76
85
|
```
|
|
77
86
|
|
|
78
87
|
| Export | Description |
|
|
79
88
|
|---|---|
|
|
80
|
-
| `
|
|
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`. |
|
|
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. |
|
|
92
|
+
| `MAX_BATCH` | Default batch-element cap (`50`) |
|
|
81
93
|
| `jsonRpcResult` / `jsonRpcError` / `JSON_RPC_*` | Envelope helpers + standard error codes |
|
|
82
94
|
| `SUPPORTED_PROTOCOL_VERSIONS` | `['2025-03-26', '2024-11-05']` |
|
|
83
95
|
|
|
@@ -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
|
}
|
|
@@ -65,6 +78,55 @@ interface JsonRpcDispatchOptions<TCtx> {
|
|
|
65
78
|
* `dispatchJsonRpc` itself.
|
|
66
79
|
*/
|
|
67
80
|
declare function hasValidJsonRpcId(req: object): boolean;
|
|
81
|
+
declare const MAX_BATCH = 50;
|
|
82
|
+
/**
|
|
83
|
+
* Result of dispatching an *unvalidated* decoded JSON-RPC message
|
|
84
|
+
* (single object or batch array) through `dispatchJsonRpcMessage`. The
|
|
85
|
+
* `status` tag tells the HTTP transport which response to emit:
|
|
86
|
+
*
|
|
87
|
+
* - `invalid` → 400 (top-level malformed: non-object scalar, empty
|
|
88
|
+
* batch, or over-size batch). `body` is the single
|
|
89
|
+
* error envelope to return.
|
|
90
|
+
* - `ok` → 200. `body` is a single response, or a batch's
|
|
91
|
+
* array of responses (invalid *elements* of a batch
|
|
92
|
+
* are error responses *inside* this array, still 200).
|
|
93
|
+
* - `no-content` → 202 empty body (a lone notification, or a batch
|
|
94
|
+
* made entirely of notifications).
|
|
95
|
+
*/
|
|
96
|
+
type JsonRpcMessageResult = {
|
|
97
|
+
status: 'invalid';
|
|
98
|
+
body: JsonRpcResponse;
|
|
99
|
+
} | {
|
|
100
|
+
status: 'ok';
|
|
101
|
+
body: JsonRpcResponse | JsonRpcResponse[];
|
|
102
|
+
} | {
|
|
103
|
+
status: 'no-content';
|
|
104
|
+
};
|
|
105
|
+
interface JsonRpcDispatchMessageOptions<TCtx> extends JsonRpcDispatchOptions<TCtx> {
|
|
106
|
+
/** Batch element cap. Defaults to `MAX_BATCH` (50). */
|
|
107
|
+
maxBatch?: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Dispatch an *unvalidated* decoded JSON-RPC message — the single entry
|
|
111
|
+
* point every transport should use once it has `JSON.parse`d the body.
|
|
112
|
+
* Envelope validation, batch handling, and MCP method semantics are all
|
|
113
|
+
* centralised here so the admin (backend Lambda) and public (runtime)
|
|
114
|
+
* transports share one implementation; the transport keeps only HTTP
|
|
115
|
+
* framing (body decode, status codes, CORS, auth).
|
|
116
|
+
*
|
|
117
|
+
* Batch semantics (JSON-RPC 2.0):
|
|
118
|
+
* - An array is a batch. Empty / over-`maxBatch` batches are rejected
|
|
119
|
+
* wholesale as a single top-level INVALID_REQUEST (`status: 'invalid'`).
|
|
120
|
+
* - Elements are processed **sequentially** (never `Promise.all`) so a
|
|
121
|
+
* batch of tool calls cannot fan out into concurrent handler runs.
|
|
122
|
+
* - Malformed elements yield an INVALID_REQUEST response inside the
|
|
123
|
+
* result array (id echoed when valid, else null); `initialize` is
|
|
124
|
+
* not allowed as a batch element. Notifications execute but emit no
|
|
125
|
+
* response. Non-notification responses keep the input order.
|
|
126
|
+
* - A batch that yields no responses at all (all notifications) →
|
|
127
|
+
* `no-content`.
|
|
128
|
+
*/
|
|
129
|
+
declare function dispatchJsonRpcMessage<TCtx>(input: unknown, opts: JsonRpcDispatchMessageOptions<TCtx>): Promise<JsonRpcMessageResult>;
|
|
68
130
|
/**
|
|
69
131
|
* Dispatch one parsed JSON-RPC request against a tool registry.
|
|
70
132
|
* Returns the response envelope, or `null` for a notification (the
|
|
@@ -77,4 +139,4 @@ declare function hasValidJsonRpcId(req: object): boolean;
|
|
|
77
139
|
*/
|
|
78
140
|
declare function dispatchJsonRpc<TCtx>(req: JsonRpcRequest, opts: JsonRpcDispatchOptions<TCtx>): Promise<JsonRpcResponse | null>;
|
|
79
141
|
|
|
80
|
-
export { JSON_RPC_INTERNAL_ERROR, JSON_RPC_INVALID_PARAMS, JSON_RPC_INVALID_REQUEST, JSON_RPC_METHOD_NOT_FOUND, JSON_RPC_PARSE_ERROR, type JsonRpcDispatchOptions, type JsonRpcRequest, type JsonRpcResponse, LATEST_SUPPORTED_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, dispatchJsonRpc, 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,119 +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
|
-
function toolAnnotations(t) {
|
|
29
|
-
const annotations = {};
|
|
30
|
-
if (typeof t.readOnly === "boolean") annotations.readOnlyHint = t.readOnly;
|
|
31
|
-
if (typeof t.destructive === "boolean") annotations.destructiveHint = t.destructive;
|
|
32
|
-
return annotations;
|
|
33
|
-
}
|
|
34
|
-
async function dispatchJsonRpc(req, opts) {
|
|
35
|
-
if (!hasValidJsonRpcId(req)) {
|
|
36
|
-
return jsonRpcError(
|
|
37
|
-
null,
|
|
38
|
-
JSON_RPC_INVALID_REQUEST,
|
|
39
|
-
"Invalid Request: `id` must be a string or an integer"
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
const response = await dispatchMethod(req, opts);
|
|
43
|
-
return isNotification(req) ? null : response;
|
|
44
|
-
}
|
|
45
|
-
async function dispatchMethod(req, opts) {
|
|
46
|
-
const id = req.id ?? null;
|
|
47
|
-
switch (req.method) {
|
|
48
|
-
case "initialize": {
|
|
49
|
-
const requested = req.params?.protocolVersion;
|
|
50
|
-
if (typeof requested !== "string") {
|
|
51
|
-
return jsonRpcError(
|
|
52
|
-
id,
|
|
53
|
-
JSON_RPC_INVALID_PARAMS,
|
|
54
|
-
"initialize requires a string `protocolVersion` parameter"
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(
|
|
58
|
-
requested
|
|
59
|
-
) ? requested : LATEST_SUPPORTED_PROTOCOL_VERSION;
|
|
60
|
-
return jsonRpcResult(id, {
|
|
61
|
-
protocolVersion,
|
|
62
|
-
capabilities: { tools: {} },
|
|
63
|
-
serverInfo: opts.serverInfo
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
case "notifications/initialized":
|
|
67
|
-
return jsonRpcResult(id, {});
|
|
68
|
-
case "tools/list":
|
|
69
|
-
return jsonRpcResult(id, {
|
|
70
|
-
tools: opts.tools.map((t) => ({
|
|
71
|
-
name: t.name,
|
|
72
|
-
description: t.description,
|
|
73
|
-
inputSchema: t.inputSchema,
|
|
74
|
-
annotations: toolAnnotations(t)
|
|
75
|
-
}))
|
|
76
|
-
});
|
|
77
|
-
case "tools/call": {
|
|
78
|
-
const params = req.params;
|
|
79
|
-
if (!params || typeof params.name !== "string") {
|
|
80
|
-
return jsonRpcError(id, JSON_RPC_INVALID_PARAMS, "tools/call requires a `name` parameter");
|
|
81
|
-
}
|
|
82
|
-
const rawArgs = params.arguments;
|
|
83
|
-
if (rawArgs !== void 0 && !isPlainObject(rawArgs)) {
|
|
84
|
-
return jsonRpcError(
|
|
85
|
-
id,
|
|
86
|
-
JSON_RPC_INVALID_PARAMS,
|
|
87
|
-
"tools/call `arguments` must be an object"
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
const tool = opts.tools.find((t) => t.name === params.name);
|
|
91
|
-
if (!tool) {
|
|
92
|
-
return jsonRpcError(id, JSON_RPC_METHOD_NOT_FOUND, `unknown tool: ${params.name}`);
|
|
93
|
-
}
|
|
94
|
-
const ctx = opts.getContext();
|
|
95
|
-
try {
|
|
96
|
-
const result = await tool.handler(rawArgs ?? {}, ctx);
|
|
97
|
-
return jsonRpcResult(id, {
|
|
98
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
99
|
-
});
|
|
100
|
-
} catch (err) {
|
|
101
|
-
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
102
|
-
console.error("[mcp-jsonrpc] tool dispatch failed", {
|
|
103
|
-
tool: params.name,
|
|
104
|
-
message: rawMessage
|
|
105
|
-
});
|
|
106
|
-
const message = opts.formatToolError ? opts.formatToolError(err) : rawMessage;
|
|
107
|
-
return jsonRpcResult(id, {
|
|
108
|
-
isError: true,
|
|
109
|
-
content: [{ type: "text", text: message }]
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
default:
|
|
114
|
-
return jsonRpcError(id, JSON_RPC_METHOD_NOT_FOUND, `Method not found: ${req.method}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
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";
|
|
117
18
|
export {
|
|
118
19
|
JSON_RPC_INTERNAL_ERROR,
|
|
119
20
|
JSON_RPC_INVALID_PARAMS,
|
|
@@ -121,9 +22,13 @@ export {
|
|
|
121
22
|
JSON_RPC_METHOD_NOT_FOUND,
|
|
122
23
|
JSON_RPC_PARSE_ERROR,
|
|
123
24
|
LATEST_SUPPORTED_PROTOCOL_VERSION,
|
|
25
|
+
MAX_BATCH,
|
|
124
26
|
SUPPORTED_PROTOCOL_VERSIONS,
|
|
27
|
+
ToolUserError,
|
|
125
28
|
dispatchJsonRpc,
|
|
29
|
+
dispatchJsonRpcMessage,
|
|
126
30
|
hasValidJsonRpcId,
|
|
31
|
+
isToolUserError,
|
|
127
32
|
jsonRpcError,
|
|
128
33
|
jsonRpcResult
|
|
129
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.68",
|
|
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.61"
|
|
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
|
}
|