@pecot/tools-mcp 1.0.1 → 1.1.0
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/.claude-plugin/plugin.json +1 -1
- package/cli/dist/cli.d.ts +1 -2
- package/cli/dist/cli.js +9 -13
- package/cli/dist/diagnostics.d.ts +38 -0
- package/cli/dist/diagnostics.js +46 -0
- package/cli/dist/domain/arguments.d.ts +2 -15
- package/cli/dist/domain/arguments.js +38 -99
- package/cli/dist/domain/athena-query.d.ts +2 -8
- package/cli/dist/domain/athena-query.js +10 -35
- package/cli/dist/domain/s3-input.d.ts +2 -9
- package/cli/dist/domain/s3-input.js +8 -21
- package/cli/dist/infra/awscli.d.ts +8 -36
- package/cli/dist/infra/awscli.js +15 -55
- package/cli/dist/infra/sso/login-registry.d.ts +4 -21
- package/cli/dist/infra/sso/login-registry.js +13 -56
- package/cli/dist/mcp/serve.d.ts +6 -28
- package/cli/dist/mcp/serve.js +20 -64
- package/cli/dist/mcp/server.d.ts +4 -20
- package/cli/dist/mcp/server.js +9 -35
- package/cli/dist/mcp/tools/_shared.d.ts +6 -27
- package/cli/dist/mcp/tools/_shared.js +36 -65
- package/cli/dist/mcp/tools/athena.js +17 -27
- package/cli/dist/mcp/tools/cloudwatch.js +13 -15
- package/cli/dist/mcp/tools/ec2.js +7 -10
- package/cli/dist/mcp/tools/ecr.js +10 -16
- package/cli/dist/mcp/tools/index.js +1 -5
- package/cli/dist/mcp/tools/lambda.js +10 -10
- package/cli/dist/mcp/tools/logs.js +22 -22
- package/cli/dist/mcp/tools/rds.js +15 -18
- package/cli/dist/mcp/tools/s3.js +19 -29
- package/cli/dist/mcp/tools/scheduler.js +10 -13
- package/cli/dist/mcp/tools/sqs.js +8 -8
- package/cli/dist/mcp/tools/sso.d.ts +1 -5
- package/cli/dist/mcp/tools/sso.js +33 -59
- package/cli/dist/mcp/tools/sts.js +2 -2
- package/cli/dist/node-runtime.d.ts +6 -1
- package/cli/dist/node-runtime.js +16 -8
- package/npm-shrinkwrap.json +3 -3
- package/package.json +2 -2
package/cli/dist/mcp/serve.js
CHANGED
|
@@ -1,42 +1,22 @@
|
|
|
1
|
-
// `pecot-tools mcp`
|
|
2
|
-
//
|
|
3
|
-
// cli.ts は薄い Command だけを持つ。
|
|
4
|
-
// 本モジュールは action 内の dynamic import で初めて読み込まれる (MCP SDK の読み込みを mcp 実行時に限定するため)。
|
|
5
|
-
//
|
|
6
|
-
// exit code 規約:
|
|
7
|
-
// 0 = 正常終了 (クライアントが stdin を閉じるまで待ち受ける)
|
|
8
|
-
// 1 = 起動失敗 (transport 接続エラー等) / stdout への書き込み失敗
|
|
1
|
+
// `pecot-tools mcp` の実装。cli.ts の dynamic import で読む。exit code は 0=正常 / 1=起動失敗・stdout 書き込み失敗。
|
|
9
2
|
import { Writable } from 'node:stream';
|
|
10
3
|
import { serveStdio, StdioServerTransport } from '@modelcontextprotocol/server/stdio';
|
|
4
|
+
import { createDiagnostics } from '../diagnostics.js';
|
|
11
5
|
import { killRunningAwscli } from '../infra/awscli.js';
|
|
12
6
|
import { shutdownLoginRegistry } from '../infra/sso/login-registry.js';
|
|
13
7
|
import { createMcpServer } from './server.js';
|
|
14
8
|
import { allTools } from './tools/index.js';
|
|
15
9
|
import { ignoreRejection } from '../infra/ignore-rejection.js';
|
|
16
10
|
/**
|
|
17
|
-
診断行の接頭辞。stdout は JSON-RPC 専有なので、報告先は stderr のみ。
|
|
18
|
-
*/
|
|
19
|
-
const LOG_PREFIX = 'pecot-tools mcp:';
|
|
20
|
-
/**
|
|
21
11
|
SIGTERM / SIGINT を受けたときの終了コード (シェルの慣習どおり 128 + シグナル番号)。
|
|
22
12
|
*/
|
|
23
13
|
const SIGTERM_EXIT_CODE = 143;
|
|
24
14
|
const SIGINT_EXIT_CODE = 130;
|
|
25
|
-
//
|
|
26
|
-
// すると JSON-RPC が stderr へ回る。張り済みの stdout には、最初に作った Writable をそのまま返す。
|
|
27
|
-
// キーが stream オブジェクトなので、テストごとに別のフェイクを使っても混入しない。
|
|
15
|
+
// 二重に張ると 2 回目が転送用 write を元の write として束縛し、JSON-RPC が stderr へ回る。張り済みなら初回の Writable を返す。
|
|
28
16
|
const installedGuards = new WeakMap();
|
|
29
17
|
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
返した Writable だけが本物の stdout へ届き、それ以外の stdout 書き込み
|
|
33
|
-
(console.log・依存ライブラリの進捗表示など) は stderr へ逃がす。
|
|
34
|
-
|
|
35
|
-
「process.stdout.write を差し替えるだけ」では、transport 自身の JSON-RPC 応答まで stderr に流れる。
|
|
36
|
-
するとクライアントが無応答になる。
|
|
37
|
-
そのため、差し替え前に束縛した元の write を使う専用 Writable を transport へ渡す、という二段構えが必須になる。
|
|
38
|
-
|
|
39
|
-
同じ stdout に対しては冪等で、2 回目以降は初回に作った Writable を返す。
|
|
18
|
+
stdout を JSON-RPC 専有にする。返した Writable だけが本物の stdout へ届き、ほかの書き込みは stderr へ逃がす。
|
|
19
|
+
同じ stdout に対しては冪等。
|
|
40
20
|
*/
|
|
41
21
|
export function installStdoutGuard(stdout, stderr) {
|
|
42
22
|
const installed = installedGuards.get(stdout);
|
|
@@ -44,16 +24,14 @@ export function installStdoutGuard(stdout, stderr) {
|
|
|
44
24
|
return installed;
|
|
45
25
|
const isOriginalWrite = stdout.write.bind(stdout);
|
|
46
26
|
const isForwardToStderr = (chunk, encodingOrCallback, callback) => {
|
|
47
|
-
//
|
|
48
|
-
// encoding は意図的に捨てる: 逃がす先の stderr は既定の utf8 で解釈すればよく、
|
|
49
|
-
// ここで扱うのは console 系が書く utf8 文字列か Buffer に限られる。
|
|
27
|
+
// encoding は捨てる。逃がす先の stderr は既定の utf8 で足りる。
|
|
50
28
|
const done = typeof encodingOrCallback === 'function' ? encodingOrCallback : callback;
|
|
29
|
+
// eslint-disable-next-line no-restricted-syntax -- stdout へ書かれた生の chunk を、行に整えず stderr へ逃がす
|
|
51
30
|
const isWritten = stderr.write(chunk);
|
|
52
31
|
done?.();
|
|
53
32
|
return isWritten;
|
|
54
33
|
};
|
|
55
|
-
//
|
|
56
|
-
// 関数を代入する際の型アサーションを避けるため。
|
|
34
|
+
// 代入だと、オーバーロード付きの write への型アサーションが要る。
|
|
57
35
|
Object.defineProperty(stdout, 'write', {
|
|
58
36
|
value: isForwardToStderr,
|
|
59
37
|
writable: true,
|
|
@@ -64,10 +42,9 @@ export function installStdoutGuard(stdout, stderr) {
|
|
|
64
42
|
isOriginalWrite(chunk, callback);
|
|
65
43
|
},
|
|
66
44
|
});
|
|
67
|
-
//
|
|
68
|
-
// 拾わないと uncaught 例外でスタックトレースごと落ちるため、1 行報告して終了コードを立てる。
|
|
45
|
+
// EPIPE は 'error' として上がる。拾わないと uncaught 例外で落ちる。
|
|
69
46
|
protocolStdout.on('error', (error) => {
|
|
70
|
-
stderr.
|
|
47
|
+
createDiagnostics(stderr).error(`failed to write to stdout: ${error.message}`);
|
|
71
48
|
process.exitCode = 1;
|
|
72
49
|
});
|
|
73
50
|
installedGuards.set(stdout, protocolStdout);
|
|
@@ -78,10 +55,7 @@ function runShutdown(dependencies) {
|
|
|
78
55
|
hook();
|
|
79
56
|
}
|
|
80
57
|
/**
|
|
81
|
-
後始末を終了処理へ配線する。
|
|
82
|
-
|
|
83
|
-
SIGTERM / SIGINT はハンドラを登録した時点で既定の即終了が無効になるので、明示的に exit する。
|
|
84
|
-
'exit' は最後の網で、同期処理しか実行されないため後始末だけを呼ぶ。
|
|
58
|
+
後始末を終了処理へ配線する。SIGTERM / SIGINT は既定の即終了が無効になるので、明示的に exit する
|
|
85
59
|
*/
|
|
86
60
|
function installShutdownHandlers(dependencies) {
|
|
87
61
|
dependencies.signals.on('SIGTERM', () => {
|
|
@@ -97,31 +71,20 @@ function installShutdownHandlers(dependencies) {
|
|
|
97
71
|
});
|
|
98
72
|
}
|
|
99
73
|
/**
|
|
100
|
-
MCP サーバを stdio で待ち受ける。
|
|
101
|
-
|
|
102
|
-
serveStdio が接続ごとに 2025 系と 2026-07-28 系のどちらかを選び、factory のインスタンスを
|
|
103
|
-
1 つ固定して配る。factory は 1 接続で複数回呼ばれ得る (2026-07-28 は探査用インスタンスを捨てることがある)。
|
|
104
|
-
そのため createServer() と onerror の設定以外の副作用は持たせない。
|
|
105
|
-
後始末の配線 (signals / stdin) は factory の外で 1 回だけ行う。
|
|
106
|
-
|
|
107
|
-
接続が完了しても待ち受けは続く。クライアントが stdin を閉じたら子プロセスを落として
|
|
108
|
-
接続を閉じ、SIGTERM を待たずにプロセスが自力で終了できるようにする。
|
|
74
|
+
MCP サーバを stdio で待ち受ける。factory は 1 接続で複数回呼ばれ得るため、createServer() と onerror 以外の副作用を持たせない。
|
|
75
|
+
stdin が閉じたら子プロセスを落として接続を閉じる
|
|
109
76
|
*/
|
|
110
77
|
export function runMcpServe(dependencies) {
|
|
111
78
|
const protocolStdout = dependencies.installStdoutGuard();
|
|
112
79
|
const transport = dependencies.createTransport(dependencies.stdin, protocolStdout);
|
|
113
80
|
installShutdownHandlers(dependencies);
|
|
114
|
-
//
|
|
115
|
-
// stdout は JSON-RPC 専有なので stderr へ 1 行流す。
|
|
116
|
-
// 接続側 (options.onerror) とインスタンス内部 (server.server.onerror) は通知元が別なので、同じ報告関数を両方に渡す。
|
|
117
|
-
// serveStdio は接続確定後の異常を、接続側とインスタンスの両方へ流す。
|
|
118
|
-
// そのため同じ Error は 1 回だけ報告する。
|
|
81
|
+
// 接続側とインスタンスの両方の onerror に同じ Error が流れるので、1 回だけ報告する。
|
|
119
82
|
const reported = new WeakSet();
|
|
120
83
|
const report = (error) => {
|
|
121
84
|
if (reported.has(error))
|
|
122
85
|
return;
|
|
123
86
|
reported.add(error);
|
|
124
|
-
dependencies.stderr.
|
|
87
|
+
createDiagnostics(dependencies.stderr).error(error.message);
|
|
125
88
|
};
|
|
126
89
|
const handle = serveStdio(() => {
|
|
127
90
|
try {
|
|
@@ -131,8 +94,7 @@ export function runMcpServe(dependencies) {
|
|
|
131
94
|
return server;
|
|
132
95
|
}
|
|
133
96
|
catch (error) {
|
|
134
|
-
//
|
|
135
|
-
// 起動に失敗したプロセスは待ち受けを続けても役に立たないため、終了コードを立てて閉じる。
|
|
97
|
+
// 起動に失敗したプロセスは待ち受けても役に立たないため、終了コードを立てて閉じる。
|
|
136
98
|
process.exitCode = 1;
|
|
137
99
|
// 終了処理中の close 失敗は報告先が無いため握り潰す。
|
|
138
100
|
void ignoreRejection(handle.close());
|
|
@@ -140,8 +102,7 @@ export function runMcpServe(dependencies) {
|
|
|
140
102
|
}
|
|
141
103
|
}, { transport, onerror: report });
|
|
142
104
|
dependencies.stdin.once('end', () => {
|
|
143
|
-
//
|
|
144
|
-
// 残したままだと接続を閉じるだけでは自力終了できない。
|
|
105
|
+
// 子を先に落とさないと、接続を閉じても自力終了できない。
|
|
145
106
|
runShutdown(dependencies);
|
|
146
107
|
// 終了処理中の close 失敗は報告先が無いため握り潰す。
|
|
147
108
|
void ignoreRejection(handle.close());
|
|
@@ -149,10 +110,7 @@ export function runMcpServe(dependencies) {
|
|
|
149
110
|
return handle;
|
|
150
111
|
}
|
|
151
112
|
/**
|
|
152
|
-
`pecot-tools mcp` の action 本体。
|
|
153
|
-
|
|
154
|
-
program.parse は action の Promise を await しない。
|
|
155
|
-
そのため例外はここで捕まえて、process.exitCode へ反映する (unhandled rejection にしない)。
|
|
113
|
+
`pecot-tools mcp` の action 本体。program.parse は Promise を await しないので、例外はここで process.exitCode へ反映する
|
|
156
114
|
*/
|
|
157
115
|
export function executeMcpServe(buildDependencies) {
|
|
158
116
|
const dependencies = buildDependencies();
|
|
@@ -160,7 +118,7 @@ export function executeMcpServe(buildDependencies) {
|
|
|
160
118
|
runMcpServe(dependencies);
|
|
161
119
|
}
|
|
162
120
|
catch (error) {
|
|
163
|
-
dependencies.stderr.
|
|
121
|
+
createDiagnostics(dependencies.stderr).error(formatError(error));
|
|
164
122
|
process.exitCode = 1;
|
|
165
123
|
}
|
|
166
124
|
}
|
|
@@ -172,9 +130,7 @@ export function mcpServeDefaultDependencies() {
|
|
|
172
130
|
installStdoutGuard: () => installStdoutGuard(process.stdout, process.stderr),
|
|
173
131
|
stdin: process.stdin,
|
|
174
132
|
stderr: process.stderr,
|
|
175
|
-
//
|
|
176
|
-
// 親が先に消えると、子は PPID 1 へ再親付けされる。
|
|
177
|
-
// timeout を張っていた親が居なくなるので、打ち切りも働かなくなる(SSO は最長 10 分待つ)。
|
|
133
|
+
// 親が先に消えると子が残り、timeout も働かない(SSO は最長 10 分待つ)。
|
|
178
134
|
onShutdown: [killRunningAwscli, shutdownLoginRegistry],
|
|
179
135
|
signals: process,
|
|
180
136
|
};
|
package/cli/dist/mcp/server.d.ts
CHANGED
|
@@ -2,18 +2,11 @@ import { McpServer } from '@modelcontextprotocol/server';
|
|
|
2
2
|
import type { CallToolResult, ToolAnnotations } from '@modelcontextprotocol/server';
|
|
3
3
|
import type { z } from 'zod';
|
|
4
4
|
/**
|
|
5
|
-
MCP
|
|
6
|
-
|
|
7
|
-
`.mcp.json` の `mcpServers` の登録キーと必ず揃える。食い違うとクライアント側の表示と
|
|
8
|
-
ツール名の名前空間(`mcp__plugin_pecot-t_<サーバ名>__*`)が噛み合わなくなる。
|
|
5
|
+
MCP サーバ名。`.mcp.json` の `mcpServers` の登録キーと揃える。食い違うとツール名の名前空間が噛み合わない。
|
|
9
6
|
*/
|
|
10
7
|
export declare const MCP_SERVER_NAME = "pecot-tools";
|
|
11
8
|
/**
|
|
12
|
-
registerTool
|
|
13
|
-
|
|
14
|
-
inputSchema / outputSchema は自前の zod の `z.object()` で書く。SDK は渡されたスキーマ自身から JSON Schema を作る。
|
|
15
|
-
そのため SDK 同梱の zod と混在しない。引数を取らないツールは inputSchema に
|
|
16
|
-
`z.object({})` を渡す。
|
|
9
|
+
registerTool に渡すツールのメタ情報。スキーマは自前の zod で書き、SDK 同梱の zod と混在させない。
|
|
17
10
|
*/
|
|
18
11
|
export interface ToolConfig {
|
|
19
12
|
readonly title?: string;
|
|
@@ -29,10 +22,7 @@ export interface ToolConfig {
|
|
|
29
22
|
readonly annotations?: ToolAnnotations;
|
|
30
23
|
}
|
|
31
24
|
/**
|
|
32
|
-
tools/call ハンドラの第 2
|
|
33
|
-
|
|
34
|
-
signal はクライアントのキャンセル (notifications/cancelled) と stdin の EOF で abort される。
|
|
35
|
-
SDK の型を直接渡さないのは、ツール側とテストのフェイクを SDK の型から切り離すため。
|
|
25
|
+
tools/call ハンドラの第 2 引数。使う面だけを切り出し、ツール側とテストのフェイクを SDK の型から切り離す。
|
|
36
26
|
*/
|
|
37
27
|
export interface ToolCallContext {
|
|
38
28
|
readonly signal: AbortSignal;
|
|
@@ -56,13 +46,7 @@ interface McpServerOptions {
|
|
|
56
46
|
readonly progressIntervalMs?: number;
|
|
57
47
|
}
|
|
58
48
|
/**
|
|
59
|
-
MCP
|
|
60
|
-
|
|
61
|
-
各ハンドラは登録時に実行器で包む。tools/call が並行に届いても、同時に実行されるのは 4 件までになる
|
|
62
|
-
(上限はここで構造的に保証し、ツール実装側には依存しない)。
|
|
63
|
-
あわせて progress ハートビートもここで包む。全ツールに一律で適用され、ツール実装側の適用判断が要らなくなる。
|
|
64
|
-
|
|
65
|
-
呼び出しごとに新しい McpServer と実行器を生成する (singleton にしない)。
|
|
49
|
+
MCP サーバを生成し、ツール定義を登録する。ハンドラは実行器と progress ハートビートで包み、呼び出しごとに新しく作る。
|
|
66
50
|
*/
|
|
67
51
|
export declare function createMcpServer(tools?: readonly ToolDefinition[], options?: McpServerOptions): McpServer;
|
|
68
52
|
export {};
|
package/cli/dist/mcp/server.js
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
// MCP サーバのファクトリ。ツール登録シームと上限付き並行実行器の所有をここで持つ。
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
// すると、後から registerTool したときに SDK が「handler already exists」で throw する。
|
|
6
|
-
// 一方でツール 0 件の McpServer は、tools capability と一覧ハンドラを持たない。
|
|
7
|
-
// そのため tools/list に -32601 を返す。
|
|
8
|
-
// 0 件のときだけ明示登録する。両者は排他。
|
|
9
|
-
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- ツールハンドラの第 2 引数は mutable。
|
|
10
|
-
SDK の ServerContext が signal / notify 等のメンバを持つ。
|
|
11
|
-
readonly 化すると、registerTool のコールバック型と適合しなくなる。 */
|
|
2
|
+
// 1 件以上は registerTool だけを使う(先に一覧ハンドラを張ると SDK が throw する)。
|
|
3
|
+
// 0 件のときだけ一覧ハンドラを明示登録する。
|
|
4
|
+
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- ツールハンドラの第 2 引数は mutable */
|
|
12
5
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
13
6
|
import { readVersion } from '../version.js';
|
|
14
7
|
import { createBoundedExecutor } from './executor.js';
|
|
15
8
|
import { ignoreRejection } from '../infra/ignore-rejection.js';
|
|
16
9
|
/**
|
|
17
|
-
progress ハートビートの送出周期 (ms)
|
|
18
|
-
|
|
19
|
-
守る相手は MCP クライアントの「無音タイムアウト」。
|
|
20
|
-
Claude Code で計測した値は、30 分の idle watchdog (progress 受信でリセット・判定周期 30s)。
|
|
21
|
-
10s はそこから十分な余裕があり、かつ長時間ツール 1 件で通知が積み上がりすぎない粒度になる。
|
|
10
|
+
progress ハートビートの送出周期 (ms)。クライアントの無音タイムアウト(Claude Code で 30 分)に十分な余裕を取る。
|
|
22
11
|
*/
|
|
23
12
|
const PROGRESS_HEARTBEAT_INTERVAL_MS = 10_000;
|
|
24
13
|
/**
|
|
@@ -26,20 +15,11 @@ tools/call の同時実行数の上限 (research R9)。
|
|
|
26
15
|
*/
|
|
27
16
|
const CONCURRENCY_LIMIT = 4;
|
|
28
17
|
/**
|
|
29
|
-
MCP
|
|
30
|
-
|
|
31
|
-
`.mcp.json` の `mcpServers` の登録キーと必ず揃える。食い違うとクライアント側の表示と
|
|
32
|
-
ツール名の名前空間(`mcp__plugin_pecot-t_<サーバ名>__*`)が噛み合わなくなる。
|
|
18
|
+
MCP サーバ名。`.mcp.json` の `mcpServers` の登録キーと揃える。食い違うとツール名の名前空間が噛み合わない。
|
|
33
19
|
*/
|
|
34
20
|
export const MCP_SERVER_NAME = 'pecot-tools';
|
|
35
21
|
/**
|
|
36
|
-
tools/call 1 件分の progress
|
|
37
|
-
|
|
38
|
-
MCP では progressToken を伴わないリクエストに notifications/progress を返せない。
|
|
39
|
-
そのためクライアントが token を付けてこなかった場合は、何も送らず undefined を返す。
|
|
40
|
-
|
|
41
|
-
送出は run() の前から始める。守りたいのは実行中だけでなく「上限に掛かって順番待ちしている
|
|
42
|
-
間も無音時計が進む」区間で、ハンドラ内に置くラッパーでは待機中を守れない。
|
|
22
|
+
tools/call 1 件分の progress 通知を開始する。token が無ければ undefined。順番待ちの間も守るため、run() の前から始める。
|
|
43
23
|
*/
|
|
44
24
|
function startProgressHeartbeat(toolName, serverContext, intervalMs) {
|
|
45
25
|
const { signal } = serverContext.mcpReq;
|
|
@@ -47,7 +27,7 @@ function startProgressHeartbeat(toolName, serverContext, intervalMs) {
|
|
|
47
27
|
if (progressToken === undefined)
|
|
48
28
|
return undefined;
|
|
49
29
|
let progress = 0;
|
|
50
|
-
let phase = '
|
|
30
|
+
let phase = 'queued';
|
|
51
31
|
const send = () => {
|
|
52
32
|
progress += 1;
|
|
53
33
|
// 通知はベストエフォート。クライアント切断後の reject を await の流れに乗せると、
|
|
@@ -72,18 +52,12 @@ function startProgressHeartbeat(toolName, serverContext, intervalMs) {
|
|
|
72
52
|
send();
|
|
73
53
|
}
|
|
74
54
|
return {
|
|
75
|
-
markRunning: () => { phase = '
|
|
55
|
+
markRunning: () => { phase = 'running'; },
|
|
76
56
|
stop,
|
|
77
57
|
};
|
|
78
58
|
}
|
|
79
59
|
/**
|
|
80
|
-
MCP
|
|
81
|
-
|
|
82
|
-
各ハンドラは登録時に実行器で包む。tools/call が並行に届いても、同時に実行されるのは 4 件までになる
|
|
83
|
-
(上限はここで構造的に保証し、ツール実装側には依存しない)。
|
|
84
|
-
あわせて progress ハートビートもここで包む。全ツールに一律で適用され、ツール実装側の適用判断が要らなくなる。
|
|
85
|
-
|
|
86
|
-
呼び出しごとに新しい McpServer と実行器を生成する (singleton にしない)。
|
|
60
|
+
MCP サーバを生成し、ツール定義を登録する。ハンドラは実行器と progress ハートビートで包み、呼び出しごとに新しく作る。
|
|
87
61
|
*/
|
|
88
62
|
export function createMcpServer(tools = [], options = {}) {
|
|
89
63
|
const server = new McpServer({ name: MCP_SERVER_NAME, version: readVersion() });
|
|
@@ -24,20 +24,13 @@ export declare const COMMAND_NOT_FOUND_EXIT_CODE = 127;
|
|
|
24
24
|
*/
|
|
25
25
|
export declare const UNKNOWN_FAILURE_EXIT_CODE = 1;
|
|
26
26
|
/**
|
|
27
|
-
`profile` だけの入力。
|
|
28
|
-
|
|
29
|
-
SSO の 2 件は region を取らない(contracts/mcp-tools.md §SSO 認証)。
|
|
30
|
-
そのため共通入力から profile だけを切り出し、両方が同じ書式・同じ説明文を公開できるようにする。
|
|
27
|
+
`profile` だけの入力。SSO の 2 件は region を取らないので、共通入力から切り出す。
|
|
31
28
|
*/
|
|
32
29
|
export declare const PROFILE_INPUT_SHAPE: {
|
|
33
30
|
profile: z.ZodOptional<z.ZodString>;
|
|
34
31
|
};
|
|
35
32
|
/**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
書式の定義は domain/allowlist.ts の PROFILE_PATTERN / REGION_PATTERN にある。
|
|
39
|
-
ここはその定数をそのまま inputSchema へ載せる(契約書の共通規約を tools/list で見せるため)。
|
|
40
|
-
値を書き写さないので、定義は 1 箇所のまま。書式違反はハンドラ到達前に入力検証エラーとなる。awscli は起動しない。
|
|
33
|
+
全ツール共通の任意入力。書式は allowlist.ts の PROFILE_PATTERN / REGION_PATTERN を載せ、tools/list で契約を見せる。
|
|
41
34
|
*/
|
|
42
35
|
export declare const COMMON_INPUT_SHAPE: {
|
|
43
36
|
profile: z.ZodOptional<z.ZodString>;
|
|
@@ -83,10 +76,7 @@ export interface ExecuteOptions {
|
|
|
83
76
|
*/
|
|
84
77
|
readonly timeoutMs?: number;
|
|
85
78
|
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
validateInput の直後・awscli の存在確認より前に評価する。存在確認を先にすると、
|
|
89
|
-
awscli 未導入の環境で「拒否されるはずの呼び出し」が awscli-missing に化ける。
|
|
79
|
+
静的宣言で表せない拒否条件。awscli 未導入で拒否が awscli-missing に化けないよう、存在確認より前に評価する。
|
|
90
80
|
*/
|
|
91
81
|
readonly reject?: (arguments_: Readonly<Record<string, unknown>>) => PreExecutionRejection | undefined;
|
|
92
82
|
/**
|
|
@@ -116,26 +106,15 @@ export interface AllowlistedToolSpec {
|
|
|
116
106
|
readonly artifactsOf?: ExecuteOptions['artifactsOf'];
|
|
117
107
|
}
|
|
118
108
|
/**
|
|
119
|
-
失敗種別と awscli の終了コードから応答の exitCode を決める。
|
|
120
|
-
|
|
121
|
-
exitCode を持たない失敗(シグナル終了・awscli を起動していない SSO の状態遷移)は
|
|
122
|
-
省略するか null で渡し、1 以上へ畳む。ok: false と exitCode 0 が同居しないようにする。
|
|
109
|
+
失敗種別と awscli の終了コードから応答の exitCode を決める。exitCode を持たない失敗は 1 以上へ畳む。
|
|
123
110
|
*/
|
|
124
111
|
export declare function resolveExitCode(category: FailureCategory, exitCode?: number | null): number;
|
|
125
112
|
/**
|
|
126
|
-
許可リストのエントリ 1 件を実行し、ExecutionResult
|
|
127
|
-
|
|
128
|
-
検証で弾いた場合は awscli を起動しない(exitCode 2)。awscli の失敗は種別判定にかけ、
|
|
129
|
-
応答へ載せる stdout / stderr は必ず秘匿化と上限を通す。
|
|
113
|
+
許可リストのエントリ 1 件を実行し、ExecutionResult を返す。検証で弾けば awscli を起動せず(exitCode 2)、出力は秘匿化と上限を通す。
|
|
130
114
|
*/
|
|
131
115
|
export declare function executeAllowlisted(entry: Readonly<AllowlistEntry>, arguments_: Readonly<Record<string, unknown>>, call: ToolCallContext, options?: Readonly<ExecuteOptions>): Promise<ExecutionResult>;
|
|
132
116
|
/**
|
|
133
|
-
許可リストのエントリ 1 件を MCP ツール定義にする。
|
|
134
|
-
|
|
135
|
-
入力の検証は 2 段になる。
|
|
136
|
-
inputSchema(型・列挙・書式)は SDK がハンドラ到達前に見て、入力検証エラーを返す。
|
|
137
|
-
許可リスト側の規則(先頭 '-'・排他・shorthand の構成要素)は、executeAllowlisted が見て isError の応答にする。ハンドラで再度 zod parse はしない
|
|
138
|
-
(SDK が既に未知キーの除去と default の適用を済ませている)。
|
|
117
|
+
許可リストのエントリ 1 件を MCP ツール定義にする。zod の検証は SDK が済ませるので、ハンドラで再 parse しない。
|
|
139
118
|
*/
|
|
140
119
|
export declare function allowlistedTool(spec: Readonly<AllowlistedToolSpec>, options?: Readonly<ToolOptions>): ToolDefinition;
|
|
141
120
|
//# sourceMappingURL=_shared.d.ts.map
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
// 全ツール共通の入出力スキーマと、許可リストのエントリを 1 本の実行手順へ通す処理。
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
// 検証 → 実行前拒否 → 引数組み立て → awscli 起動 → 失敗種別判定 → 秘匿化 → 切り詰め、という手順はここに 1 本だけ置く。
|
|
5
|
-
// 手順をツール側へ複製すると、秘匿化や上限の適用を忘れるおそれがある。
|
|
6
|
-
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- call.signal は mutable なメンバを持つ。
|
|
7
|
-
readonly 化すると、SDK 側の型と適合しなくなる。 */
|
|
2
|
+
// 秘匿化と上限の掛け忘れを防ぐため、検証から切り詰めまでの手順はここに 1 本だけ置く。
|
|
3
|
+
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- call.signal は mutable */
|
|
8
4
|
import { z } from 'zod';
|
|
9
5
|
import { PROFILE_PATTERN, REGION_PATTERN, } from '../../domain/allowlist.js';
|
|
10
6
|
import { buildArguments, validateInput } from '../../domain/arguments.js';
|
|
@@ -35,77 +31,70 @@ export const COMMAND_NOT_FOUND_EXIT_CODE = 127;
|
|
|
35
31
|
*/
|
|
36
32
|
export const UNKNOWN_FAILURE_EXIT_CODE = 1;
|
|
37
33
|
/**
|
|
38
|
-
`profile` だけの入力。
|
|
39
|
-
|
|
40
|
-
SSO の 2 件は region を取らない(contracts/mcp-tools.md §SSO 認証)。
|
|
41
|
-
そのため共通入力から profile だけを切り出し、両方が同じ書式・同じ説明文を公開できるようにする。
|
|
34
|
+
`profile` だけの入力。SSO の 2 件は region を取らないので、共通入力から切り出す。
|
|
42
35
|
*/
|
|
43
36
|
export const PROFILE_INPUT_SHAPE = {
|
|
44
37
|
profile: z.string().regex(PROFILE_PATTERN).optional()
|
|
45
|
-
.describe('AWS
|
|
38
|
+
.describe('AWS profile name. If omitted, the awscli default resolution applies'),
|
|
46
39
|
};
|
|
47
40
|
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
書式の定義は domain/allowlist.ts の PROFILE_PATTERN / REGION_PATTERN にある。
|
|
51
|
-
ここはその定数をそのまま inputSchema へ載せる(契約書の共通規約を tools/list で見せるため)。
|
|
52
|
-
値を書き写さないので、定義は 1 箇所のまま。書式違反はハンドラ到達前に入力検証エラーとなる。awscli は起動しない。
|
|
41
|
+
全ツール共通の任意入力。書式は allowlist.ts の PROFILE_PATTERN / REGION_PATTERN を載せ、tools/list で契約を見せる。
|
|
53
42
|
*/
|
|
54
43
|
export const COMMON_INPUT_SHAPE = {
|
|
55
44
|
...PROFILE_INPUT_SHAPE,
|
|
56
45
|
region: z.string().regex(REGION_PATTERN).optional()
|
|
57
|
-
.describe('AWS
|
|
46
|
+
.describe('AWS region (e.g. ap-northeast-1). If omitted, the awscli default resolution applies'),
|
|
58
47
|
};
|
|
59
48
|
/**
|
|
60
49
|
クライアント側ページング(`--max-items` / `--starting-token`)を持つツールの入力。
|
|
61
50
|
*/
|
|
62
51
|
export const MAX_ITEMS_PAGINATION_SHAPE = {
|
|
63
|
-
max_items: z.number().int().min(1).optional().describe('
|
|
64
|
-
starting_token: z.string().optional().describe('
|
|
52
|
+
max_items: z.number().int().min(1).optional().describe('Maximum number of items in one response'),
|
|
53
|
+
starting_token: z.string().optional().describe('nextToken from the previous response. Fetches the next page'),
|
|
65
54
|
};
|
|
66
55
|
/**
|
|
67
56
|
API 固有ページング(`logs get-log-events`)を持つツールの入力。
|
|
68
57
|
*/
|
|
69
58
|
export const LIMIT_NEXT_TOKEN_PAGINATION_SHAPE = {
|
|
70
|
-
limit: z.number().int().min(1).max(10_000).optional().describe('
|
|
71
|
-
next_token: z.string().optional().describe('
|
|
72
|
-
start_from_head: z.boolean().default(false).describe('true
|
|
59
|
+
limit: z.number().int().min(1).max(10_000).optional().describe('Maximum number of events to return (1-10000)'),
|
|
60
|
+
next_token: z.string().optional().describe('nextForwardToken / nextBackwardToken from the previous response'),
|
|
61
|
+
start_from_head: z.boolean().default(false).describe('If true, returns the oldest events first'),
|
|
73
62
|
};
|
|
74
63
|
/**
|
|
75
64
|
ExecutionResult と同形の outputSchema。全ツールで共有する。
|
|
76
65
|
*/
|
|
77
66
|
export const TOOL_OUTPUT_SCHEMA = z.object({
|
|
78
|
-
ok: z.boolean().describe('awscli
|
|
79
|
-
exitCode: z.number().describe('
|
|
80
|
-
category: z.string().optional().describe('
|
|
81
|
-
data: z.record(z.string(), z.unknown()).describe('awscli
|
|
82
|
-
artifacts: z.array(z.string()).describe('
|
|
83
|
-
message: z.string().describe('
|
|
84
|
-
stderr: z.string().optional().describe('
|
|
67
|
+
ok: z.boolean().describe('Whether the awscli run succeeded'),
|
|
68
|
+
exitCode: z.number().describe('Exit code (0 = success, 2 = rejected before running, 124 = timeout, 127 = not installed)'),
|
|
69
|
+
category: z.string().optional().describe('Failure category (omitted on success)'),
|
|
70
|
+
data: z.record(z.string(), z.unknown()).describe('awscli JSON output, or an array of lines'),
|
|
71
|
+
artifacts: z.array(z.string()).describe('Absolute paths of the local files written'),
|
|
72
|
+
message: z.string().describe('One-line summary for the calling agent'),
|
|
73
|
+
stderr: z.string().optional().describe('Redacted awscli stderr (only on failure)'),
|
|
85
74
|
});
|
|
86
75
|
/**
|
|
87
76
|
失敗種別ごとの再試行方法。message の後半に載せる。
|
|
88
77
|
*/
|
|
89
78
|
const FAILURE_HINTS = {
|
|
90
|
-
'not-allowed': '
|
|
91
|
-
'invalid-input': '
|
|
92
|
-
'awscli-missing': 'awscli v2 (2.22
|
|
93
|
-
'auth-missing': '
|
|
94
|
-
'auth-expired': '
|
|
95
|
-
'permission-denied': '
|
|
96
|
-
'resource-not-found': '
|
|
97
|
-
'timeout': '
|
|
98
|
-
'other': 'awscli
|
|
79
|
+
'not-allowed': 'Operation not allowed. Use only the exposed tools',
|
|
80
|
+
'invalid-input': 'Fix the input and run again',
|
|
81
|
+
'awscli-missing': 'Install awscli v2 (2.22 or later) and add it to PATH',
|
|
82
|
+
'auth-missing': 'Not authenticated. Run aws_sso_login or specify a profile',
|
|
83
|
+
'auth-expired': 'Authentication has expired. Run aws_sso_login to sign in again',
|
|
84
|
+
'permission-denied': 'Permission denied. Check the profile / role',
|
|
85
|
+
'resource-not-found': 'Resource not found. Check the name and region',
|
|
86
|
+
'timeout': 'Timed out. Narrow the range and run again',
|
|
87
|
+
'other': 'awscli failed. Check stderr',
|
|
99
88
|
};
|
|
100
89
|
/**
|
|
101
90
|
出力が上限を超えたときの案内。
|
|
102
91
|
*/
|
|
103
|
-
const TRUNCATION_MESSAGE =
|
|
104
|
-
+ 'max_items
|
|
92
|
+
const TRUNCATION_MESSAGE = `Output exceeded the limit of ${String(OUTPUT_LIMIT_BYTES)} bytes, so only the beginning is returned. `
|
|
93
|
+
+ 'Reduce max_items or split the request with starting_token.';
|
|
105
94
|
/**
|
|
106
95
|
stderr が上限で切り詰められたときに message へ足す注記。
|
|
107
96
|
*/
|
|
108
|
-
const STDERR_TRUNCATION_NOTE = '
|
|
97
|
+
const STDERR_TRUNCATION_NOTE = '(stderr exceeded the limit, so only the beginning is included)';
|
|
109
98
|
function isRecord(value) {
|
|
110
99
|
return value instanceof Object && !Array.isArray(value);
|
|
111
100
|
}
|
|
@@ -132,10 +121,7 @@ function toLines(text) {
|
|
|
132
121
|
return body === '' ? [] : body.split('\n');
|
|
133
122
|
}
|
|
134
123
|
/**
|
|
135
|
-
失敗種別と awscli の終了コードから応答の exitCode を決める。
|
|
136
|
-
|
|
137
|
-
exitCode を持たない失敗(シグナル終了・awscli を起動していない SSO の状態遷移)は
|
|
138
|
-
省略するか null で渡し、1 以上へ畳む。ok: false と exitCode 0 が同居しないようにする。
|
|
124
|
+
失敗種別と awscli の終了コードから応答の exitCode を決める。exitCode を持たない失敗は 1 以上へ畳む。
|
|
139
125
|
*/
|
|
140
126
|
export function resolveExitCode(category, exitCode) {
|
|
141
127
|
if (category === 'timeout')
|
|
@@ -146,7 +132,7 @@ export function resolveExitCode(category, exitCode) {
|
|
|
146
132
|
}
|
|
147
133
|
function failureMessage(toolName, category) {
|
|
148
134
|
// eslint-disable-next-line security/detect-object-injection -- category は FailureCategory の列挙値
|
|
149
|
-
return `${toolName}
|
|
135
|
+
return `${toolName} failed (${category}). ${FAILURE_HINTS[category]}`;
|
|
150
136
|
}
|
|
151
137
|
/**
|
|
152
138
|
成功時の data。JSON 出力はそのまま、非 JSON 出力は行配列にする。
|
|
@@ -159,10 +145,7 @@ function successData(entry, stdout) {
|
|
|
159
145
|
return typeof nextToken === 'string' && entry.pagination === 'max-items' ? { ...parsed, nextToken } : parsed;
|
|
160
146
|
}
|
|
161
147
|
/**
|
|
162
|
-
許可リストのエントリ 1 件を実行し、ExecutionResult
|
|
163
|
-
|
|
164
|
-
検証で弾いた場合は awscli を起動しない(exitCode 2)。awscli の失敗は種別判定にかけ、
|
|
165
|
-
応答へ載せる stdout / stderr は必ず秘匿化と上限を通す。
|
|
148
|
+
許可リストのエントリ 1 件を実行し、ExecutionResult を返す。検証で弾けば awscli を起動せず(exitCode 2)、出力は秘匿化と上限を通す。
|
|
166
149
|
*/
|
|
167
150
|
export async function executeAllowlisted(entry, arguments_, call, options = {}) {
|
|
168
151
|
const invalid = validateInput(entry, arguments_);
|
|
@@ -241,18 +224,11 @@ export async function executeAllowlisted(entry, arguments_, call, options = {})
|
|
|
241
224
|
exitCode: 0,
|
|
242
225
|
data: successData(entry, limited.text),
|
|
243
226
|
artifacts,
|
|
244
|
-
message: `${entry.toolName}
|
|
227
|
+
message: `${entry.toolName} succeeded`,
|
|
245
228
|
};
|
|
246
229
|
}
|
|
247
230
|
/**
|
|
248
|
-
annotations を許可リストの kind から導く。
|
|
249
|
-
|
|
250
|
-
契約表の「読取専用」列は read だけが true で、fetch(ローカルへ書き出す)と
|
|
251
|
-
execute(Athena のクエリ起動)は false。destructiveHint は fetch だけ true にする:
|
|
252
|
-
`overwrite: true` の呼び出しはローカルの既存ファイルを上書きして戻せない。
|
|
253
|
-
openWorldHint は AWS API へ到達するので全件 true。
|
|
254
|
-
|
|
255
|
-
契約書の共通規約も destructiveHint を fetch 2 件だけ true と定めており、ここはそれと一致する。
|
|
231
|
+
annotations を許可リストの kind から導く。destructiveHint は fetch だけ true(`overwrite: true` で既存ファイルを上書きするため)。
|
|
256
232
|
*/
|
|
257
233
|
function annotationsOf(entry) {
|
|
258
234
|
return {
|
|
@@ -262,12 +238,7 @@ function annotationsOf(entry) {
|
|
|
262
238
|
};
|
|
263
239
|
}
|
|
264
240
|
/**
|
|
265
|
-
許可リストのエントリ 1 件を MCP ツール定義にする。
|
|
266
|
-
|
|
267
|
-
入力の検証は 2 段になる。
|
|
268
|
-
inputSchema(型・列挙・書式)は SDK がハンドラ到達前に見て、入力検証エラーを返す。
|
|
269
|
-
許可リスト側の規則(先頭 '-'・排他・shorthand の構成要素)は、executeAllowlisted が見て isError の応答にする。ハンドラで再度 zod parse はしない
|
|
270
|
-
(SDK が既に未知キーの除去と default の適用を済ませている)。
|
|
241
|
+
許可リストのエントリ 1 件を MCP ツール定義にする。zod の検証は SDK が済ませるので、ハンドラで再 parse しない。
|
|
271
242
|
*/
|
|
272
243
|
export function allowlistedTool(spec, options = {}) {
|
|
273
244
|
const executeOptions = {
|