@pecot/tools-mcp 0.0.0-stage → 1.0.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.
Files changed (71) hide show
  1. package/.claude-plugin/plugin.json +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -2
  4. package/bin/pecot-tools-mcp.mjs +10 -0
  5. package/cli/dist/cli.d.ts +9 -0
  6. package/cli/dist/cli.js +65 -0
  7. package/cli/dist/domain/allowlist.d.ts +228 -0
  8. package/cli/dist/domain/allowlist.js +657 -0
  9. package/cli/dist/domain/arguments.d.ts +37 -0
  10. package/cli/dist/domain/arguments.js +453 -0
  11. package/cli/dist/domain/athena-query.d.ts +22 -0
  12. package/cli/dist/domain/athena-query.js +191 -0
  13. package/cli/dist/domain/failure.d.ts +22 -0
  14. package/cli/dist/domain/failure.js +99 -0
  15. package/cli/dist/domain/redact.d.ts +5 -0
  16. package/cli/dist/domain/redact.js +38 -0
  17. package/cli/dist/domain/s3-input.d.ts +20 -0
  18. package/cli/dist/domain/s3-input.js +50 -0
  19. package/cli/dist/domain/sso-output.d.ts +23 -0
  20. package/cli/dist/domain/sso-output.js +70 -0
  21. package/cli/dist/infra/awscli.d.ts +104 -0
  22. package/cli/dist/infra/awscli.js +245 -0
  23. package/cli/dist/infra/ignore-rejection.d.ts +6 -0
  24. package/cli/dist/infra/ignore-rejection.js +13 -0
  25. package/cli/dist/infra/local-file.d.ts +9 -0
  26. package/cli/dist/infra/local-file.js +21 -0
  27. package/cli/dist/infra/sso/login-registry.d.ts +99 -0
  28. package/cli/dist/infra/sso/login-registry.js +314 -0
  29. package/cli/dist/mcp/executor.d.ts +14 -0
  30. package/cli/dist/mcp/executor.js +44 -0
  31. package/cli/dist/mcp/result.d.ts +57 -0
  32. package/cli/dist/mcp/result.js +53 -0
  33. package/cli/dist/mcp/serve.d.ts +70 -0
  34. package/cli/dist/mcp/serve.js +185 -0
  35. package/cli/dist/mcp/server.d.ts +69 -0
  36. package/cli/dist/mcp/server.js +115 -0
  37. package/cli/dist/mcp/tools/_shared.d.ts +141 -0
  38. package/cli/dist/mcp/tools/_shared.js +290 -0
  39. package/cli/dist/mcp/tools/athena.d.ts +4 -0
  40. package/cli/dist/mcp/tools/athena.js +82 -0
  41. package/cli/dist/mcp/tools/cloudwatch.d.ts +4 -0
  42. package/cli/dist/mcp/tools/cloudwatch.js +46 -0
  43. package/cli/dist/mcp/tools/ec2.d.ts +4 -0
  44. package/cli/dist/mcp/tools/ec2.js +31 -0
  45. package/cli/dist/mcp/tools/ecr.d.ts +4 -0
  46. package/cli/dist/mcp/tools/ecr.js +37 -0
  47. package/cli/dist/mcp/tools/index.d.ts +7 -0
  48. package/cli/dist/mcp/tools/index.js +37 -0
  49. package/cli/dist/mcp/tools/lambda.d.ts +4 -0
  50. package/cli/dist/mcp/tools/lambda.js +36 -0
  51. package/cli/dist/mcp/tools/logs.d.ts +4 -0
  52. package/cli/dist/mcp/tools/logs.js +74 -0
  53. package/cli/dist/mcp/tools/rds.d.ts +4 -0
  54. package/cli/dist/mcp/tools/rds.js +60 -0
  55. package/cli/dist/mcp/tools/s3.d.ts +4 -0
  56. package/cli/dist/mcp/tools/s3.js +96 -0
  57. package/cli/dist/mcp/tools/scheduler.d.ts +4 -0
  58. package/cli/dist/mcp/tools/scheduler.js +44 -0
  59. package/cli/dist/mcp/tools/sqs.d.ts +4 -0
  60. package/cli/dist/mcp/tools/sqs.js +33 -0
  61. package/cli/dist/mcp/tools/sso.d.ts +11 -0
  62. package/cli/dist/mcp/tools/sso.js +240 -0
  63. package/cli/dist/mcp/tools/sts.d.ts +4 -0
  64. package/cli/dist/mcp/tools/sts.js +15 -0
  65. package/cli/dist/node-runtime.d.ts +20 -0
  66. package/cli/dist/node-runtime.js +84 -0
  67. package/cli/dist/package.json +1 -0
  68. package/cli/dist/version.d.ts +19 -0
  69. package/cli/dist/version.js +44 -0
  70. package/npm-shrinkwrap.json +67 -0
  71. package/package.json +21 -4
@@ -0,0 +1,70 @@
1
+ import type { Readable } from 'node:stream';
2
+ import { Writable } from 'node:stream';
3
+ import type { McpServer, Transport } from '@modelcontextprotocol/server';
4
+ import { type StdioServerHandle } from '@modelcontextprotocol/server/stdio';
5
+ /**
6
+ process.stdout / process.stderr のうち、ガードが必要とする最小の書き込み面。
7
+ */
8
+ export interface WritableTarget {
9
+ write(chunk: string | Uint8Array, callback?: (error?: Error | null) => void): boolean;
10
+ }
11
+ /**
12
+ stdio の stdout を JSON-RPC 専有にするガードを張る。
13
+
14
+ 返した Writable だけが本物の stdout へ届き、それ以外の stdout 書き込み
15
+ (console.log・依存ライブラリの進捗表示など) は stderr へ逃がす。
16
+
17
+ 「process.stdout.write を差し替えるだけ」では、transport 自身の JSON-RPC 応答まで stderr に流れる。
18
+ するとクライアントが無応答になる。
19
+ そのため、差し替え前に束縛した元の write を使う専用 Writable を transport へ渡す、という二段構えが必須になる。
20
+
21
+ 同じ stdout に対しては冪等で、2 回目以降は初回に作った Writable を返す。
22
+ */
23
+ export declare function installStdoutGuard(stdout: WritableTarget, stderr: WritableTarget): Writable;
24
+ /**
25
+ SIGTERM / SIGINT / 'exit' の配線先。既定は process 本体で、テストはフェイクを渡す。
26
+ */
27
+ export interface ProcessSignals {
28
+ on(event: 'SIGTERM' | 'SIGINT' | 'exit', listener: () => void): void;
29
+ exit(code: number): void;
30
+ }
31
+ export interface McpServeDependencies {
32
+ readonly createServer: () => McpServer;
33
+ readonly createTransport: (stdin: Readable, stdout: Writable) => Transport;
34
+ /**
35
+ stdout ガードを張り、JSON-RPC 専用の Writable を返す。
36
+ */
37
+ readonly installStdoutGuard: () => Writable;
38
+ readonly stdin: Readable;
39
+ readonly stderr: WritableTarget;
40
+ /**
41
+ 終了時に呼ぶ後始末。
42
+
43
+ 実行中の awscli 子プロセスのように「子プロセスを持つ機能」がここへ登録する。
44
+ 子を kill しない限り親は終了できない (子の stdio パイプが Node のイベントループを
45
+ 保持するため、stdin の EOF で server.close() を呼んでも待ち続ける)。
46
+ */
47
+ readonly onShutdown: readonly (() => void)[];
48
+ readonly signals: ProcessSignals;
49
+ }
50
+ /**
51
+ MCP サーバを stdio で待ち受ける。
52
+
53
+ serveStdio が接続ごとに 2025 系と 2026-07-28 系のどちらかを選び、factory のインスタンスを
54
+ 1 つ固定して配る。factory は 1 接続で複数回呼ばれ得る (2026-07-28 は探査用インスタンスを捨てることがある)。
55
+ そのため createServer() と onerror の設定以外の副作用は持たせない。
56
+ 後始末の配線 (signals / stdin) は factory の外で 1 回だけ行う。
57
+
58
+ 接続が完了しても待ち受けは続く。クライアントが stdin を閉じたら子プロセスを落として
59
+ 接続を閉じ、SIGTERM を待たずにプロセスが自力で終了できるようにする。
60
+ */
61
+ export declare function runMcpServe(dependencies: Readonly<McpServeDependencies>): StdioServerHandle;
62
+ /**
63
+ `pecot-tools mcp` の action 本体。
64
+
65
+ program.parse は action の Promise を await しない。
66
+ そのため例外はここで捕まえて、process.exitCode へ反映する (unhandled rejection にしない)。
67
+ */
68
+ export declare function executeMcpServe(buildDependencies: () => McpServeDependencies): void;
69
+ export declare function mcpServeDefaultDependencies(): McpServeDependencies;
70
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1,185 @@
1
+ // `pecot-tools mcp` — MCP サーバとして stdio で待ち受ける実装。
2
+ //
3
+ // cli.ts は薄い Command だけを持つ。
4
+ // 本モジュールは action 内の dynamic import で初めて読み込まれる (MCP SDK の読み込みを mcp 実行時に限定するため)。
5
+ //
6
+ // exit code 規約:
7
+ // 0 = 正常終了 (クライアントが stdin を閉じるまで待ち受ける)
8
+ // 1 = 起動失敗 (transport 接続エラー等) / stdout への書き込み失敗
9
+ import { Writable } from 'node:stream';
10
+ import { serveStdio, StdioServerTransport } from '@modelcontextprotocol/server/stdio';
11
+ import { killRunningAwscli } from '../infra/awscli.js';
12
+ import { shutdownLoginRegistry } from '../infra/sso/login-registry.js';
13
+ import { createMcpServer } from './server.js';
14
+ import { allTools } from './tools/index.js';
15
+ import { ignoreRejection } from '../infra/ignore-rejection.js';
16
+ /**
17
+ 診断行の接頭辞。stdout は JSON-RPC 専有なので、報告先は stderr のみ。
18
+ */
19
+ const LOG_PREFIX = 'pecot-tools mcp:';
20
+ /**
21
+ SIGTERM / SIGINT を受けたときの終了コード (シェルの慣習どおり 128 + シグナル番号)。
22
+ */
23
+ const SIGTERM_EXIT_CODE = 143;
24
+ const SIGINT_EXIT_CODE = 130;
25
+ // 同じ stdout に二重でガードを張ると、2 回目が「1 回目の転送用 write」を元の write として束縛する。
26
+ // すると JSON-RPC が stderr へ回る。張り済みの stdout には、最初に作った Writable をそのまま返す。
27
+ // キーが stream オブジェクトなので、テストごとに別のフェイクを使っても混入しない。
28
+ const installedGuards = new WeakMap();
29
+ /**
30
+ stdio の stdout を JSON-RPC 専有にするガードを張る。
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 を返す。
40
+ */
41
+ export function installStdoutGuard(stdout, stderr) {
42
+ const installed = installedGuards.get(stdout);
43
+ if (installed !== undefined)
44
+ return installed;
45
+ const isOriginalWrite = stdout.write.bind(stdout);
46
+ const isForwardToStderr = (chunk, encodingOrCallback, callback) => {
47
+ // `process.stdout.write(chunk, encoding?, callback?)` の両シグネチャを受ける。
48
+ // encoding は意図的に捨てる: 逃がす先の stderr は既定の utf8 で解釈すればよく、
49
+ // ここで扱うのは console 系が書く utf8 文字列か Buffer に限られる。
50
+ const done = typeof encodingOrCallback === 'function' ? encodingOrCallback : callback;
51
+ const isWritten = stderr.write(chunk);
52
+ done?.();
53
+ return isWritten;
54
+ };
55
+ // 代入ではなく defineProperty を使うのは、オーバーロード付きの write に単一シグネチャの
56
+ // 関数を代入する際の型アサーションを避けるため。
57
+ Object.defineProperty(stdout, 'write', {
58
+ value: isForwardToStderr,
59
+ writable: true,
60
+ configurable: true,
61
+ });
62
+ const protocolStdout = new Writable({
63
+ write(chunk, _encoding, callback) {
64
+ isOriginalWrite(chunk, callback);
65
+ },
66
+ });
67
+ // stdout の相手が消えた後の書き込み (EPIPE) は Writable の 'error' として上がる。
68
+ // 拾わないと uncaught 例外でスタックトレースごと落ちるため、1 行報告して終了コードを立てる。
69
+ protocolStdout.on('error', (error) => {
70
+ stderr.write(`${LOG_PREFIX} stdout への書き込みに失敗しました: ${error.message}\n`);
71
+ process.exitCode = 1;
72
+ });
73
+ installedGuards.set(stdout, protocolStdout);
74
+ return protocolStdout;
75
+ }
76
+ function runShutdown(dependencies) {
77
+ for (const hook of dependencies.onShutdown)
78
+ hook();
79
+ }
80
+ /**
81
+ 後始末を終了処理へ配線する。
82
+
83
+ SIGTERM / SIGINT はハンドラを登録した時点で既定の即終了が無効になるので、明示的に exit する。
84
+ 'exit' は最後の網で、同期処理しか実行されないため後始末だけを呼ぶ。
85
+ */
86
+ function installShutdownHandlers(dependencies) {
87
+ dependencies.signals.on('SIGTERM', () => {
88
+ runShutdown(dependencies);
89
+ dependencies.signals.exit(SIGTERM_EXIT_CODE);
90
+ });
91
+ dependencies.signals.on('SIGINT', () => {
92
+ runShutdown(dependencies);
93
+ dependencies.signals.exit(SIGINT_EXIT_CODE);
94
+ });
95
+ dependencies.signals.on('exit', () => {
96
+ runShutdown(dependencies);
97
+ });
98
+ }
99
+ /**
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 を待たずにプロセスが自力で終了できるようにする。
109
+ */
110
+ export function runMcpServe(dependencies) {
111
+ const protocolStdout = dependencies.installStdoutGuard();
112
+ const transport = dependencies.createTransport(dependencies.stdin, protocolStdout);
113
+ installShutdownHandlers(dependencies);
114
+ // 不正な JSON-RPC やハンドラ不在など、リクエスト単位の応答にならない異常は onerror にしか出てこない。
115
+ // stdout は JSON-RPC 専有なので stderr へ 1 行流す。
116
+ // 接続側 (options.onerror) とインスタンス内部 (server.server.onerror) は通知元が別なので、同じ報告関数を両方に渡す。
117
+ // serveStdio は接続確定後の異常を、接続側とインスタンスの両方へ流す。
118
+ // そのため同じ Error は 1 回だけ報告する。
119
+ const reported = new WeakSet();
120
+ const report = (error) => {
121
+ if (reported.has(error))
122
+ return;
123
+ reported.add(error);
124
+ dependencies.stderr.write(`${LOG_PREFIX} ${error.message}\n`);
125
+ };
126
+ const handle = serveStdio(() => {
127
+ try {
128
+ const server = dependencies.createServer();
129
+ // eslint-disable-next-line unicorn/prefer-add-event-listener -- SDK の Protocol は onerror 経由で通知
130
+ server.server.onerror = report;
131
+ return server;
132
+ }
133
+ catch (error) {
134
+ // factory の失敗は serveStdio が onerror (report) へ流して接続側へ内部エラーを返す。
135
+ // 起動に失敗したプロセスは待ち受けを続けても役に立たないため、終了コードを立てて閉じる。
136
+ process.exitCode = 1;
137
+ // 終了処理中の close 失敗は報告先が無いため握り潰す。
138
+ void ignoreRejection(handle.close());
139
+ throw error;
140
+ }
141
+ }, { transport, onerror: report });
142
+ dependencies.stdin.once('end', () => {
143
+ // 子プロセスを先に落とす。子の stdio パイプがイベントループを保持するため、
144
+ // 残したままだと接続を閉じるだけでは自力終了できない。
145
+ runShutdown(dependencies);
146
+ // 終了処理中の close 失敗は報告先が無いため握り潰す。
147
+ void ignoreRejection(handle.close());
148
+ });
149
+ return handle;
150
+ }
151
+ /**
152
+ `pecot-tools mcp` の action 本体。
153
+
154
+ program.parse は action の Promise を await しない。
155
+ そのため例外はここで捕まえて、process.exitCode へ反映する (unhandled rejection にしない)。
156
+ */
157
+ export function executeMcpServe(buildDependencies) {
158
+ const dependencies = buildDependencies();
159
+ try {
160
+ runMcpServe(dependencies);
161
+ }
162
+ catch (error) {
163
+ dependencies.stderr.write(`${LOG_PREFIX} ${formatError(error)}\n`);
164
+ process.exitCode = 1;
165
+ }
166
+ }
167
+ export function mcpServeDefaultDependencies() {
168
+ return {
169
+ createServer: () => createMcpServer(allTools()),
170
+ createTransport: (stdin, stdout) => new StdioServerTransport(stdin, stdout),
171
+ /* c8 ignore next 2 -- 実 process.stdout を差し替えるため単体テストからは起動できない (installStdoutGuard 本体はフェイク stream で検証) */
172
+ installStdoutGuard: () => installStdoutGuard(process.stdout, process.stderr),
173
+ stdin: process.stdin,
174
+ stderr: process.stderr,
175
+ // tools/call 実行中の awscli 子プロセスと、SSO ログインで待機中の子プロセスを落とす。
176
+ // 親が先に消えると、子は PPID 1 へ再親付けされる。
177
+ // timeout を張っていた親が居なくなるので、打ち切りも働かなくなる(SSO は最長 10 分待つ)。
178
+ onShutdown: [killRunningAwscli, shutdownLoginRegistry],
179
+ signals: process,
180
+ };
181
+ }
182
+ function formatError(error) {
183
+ return error instanceof Error ? error.message : String(error);
184
+ }
185
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1,69 @@
1
+ import { McpServer } from '@modelcontextprotocol/server';
2
+ import type { CallToolResult, ToolAnnotations } from '@modelcontextprotocol/server';
3
+ import type { z } from 'zod';
4
+ /**
5
+ MCP サーバ名。initialize の serverInfo.name として返る値。
6
+
7
+ `.mcp.json` の `mcpServers` の登録キーと必ず揃える。食い違うとクライアント側の表示と
8
+ ツール名の名前空間(`mcp__plugin_pecot-t_<サーバ名>__*`)が噛み合わなくなる。
9
+ */
10
+ export declare const MCP_SERVER_NAME = "pecot-tools";
11
+ /**
12
+ registerTool に渡すツールのメタ情報。
13
+
14
+ inputSchema / outputSchema は自前の zod の `z.object()` で書く。SDK は渡されたスキーマ自身から JSON Schema を作る。
15
+ そのため SDK 同梱の zod と混在しない。引数を取らないツールは inputSchema に
16
+ `z.object({})` を渡す。
17
+ */
18
+ export interface ToolConfig {
19
+ readonly title?: string;
20
+ readonly description?: string;
21
+ readonly inputSchema: z.ZodObject;
22
+ /**
23
+ 指定すると SDK が structuredContent をこのスキーマで検証する。
24
+ */
25
+ readonly outputSchema?: z.ZodObject;
26
+ /**
27
+ readOnlyHint / destructiveHint 等、クライアントの実行判断に使われるヒント。
28
+ */
29
+ readonly annotations?: ToolAnnotations;
30
+ }
31
+ /**
32
+ tools/call ハンドラの第 2 引数。ハンドラが使う面だけを SDK の ServerContext から切り出す。
33
+
34
+ signal はクライアントのキャンセル (notifications/cancelled) と stdin の EOF で abort される。
35
+ SDK の型を直接渡さないのは、ツール側とテストのフェイクを SDK の型から切り離すため。
36
+ */
37
+ export interface ToolCallContext {
38
+ readonly signal: AbortSignal;
39
+ }
40
+ /**
41
+ tools/call のハンドラ。arguments_ は inputSchema で検証済みの値。
42
+ */
43
+ export type ToolHandler = (arguments_: Readonly<Record<string, unknown>>, call: ToolCallContext) => Promise<CallToolResult>;
44
+ /**
45
+ サーバへ登録するツール 1 件の定義。
46
+ */
47
+ export interface ToolDefinition {
48
+ readonly name: string;
49
+ readonly config: ToolConfig;
50
+ readonly handler: ToolHandler;
51
+ }
52
+ interface McpServerOptions {
53
+ /**
54
+ progress ハートビートの送出周期 (ms)。既定は PROGRESS_HEARTBEAT_INTERVAL_MS で、テストが短縮する。
55
+ */
56
+ readonly progressIntervalMs?: number;
57
+ }
58
+ /**
59
+ MCP サーバを生成し、渡されたツール定義を登録する。
60
+
61
+ 各ハンドラは登録時に実行器で包む。tools/call が並行に届いても、同時に実行されるのは 4 件までになる
62
+ (上限はここで構造的に保証し、ツール実装側には依存しない)。
63
+ あわせて progress ハートビートもここで包む。全ツールに一律で適用され、ツール実装側の適用判断が要らなくなる。
64
+
65
+ 呼び出しごとに新しい McpServer と実行器を生成する (singleton にしない)。
66
+ */
67
+ export declare function createMcpServer(tools?: readonly ToolDefinition[], options?: McpServerOptions): McpServer;
68
+ export {};
69
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1,115 @@
1
+ // MCP サーバのファクトリ。ツール登録シームと上限付き並行実行器の所有をここで持つ。
2
+ //
3
+ // ツール 1 件以上のときは McpServer.registerTool だけを使う。
4
+ // 低レベルの setRequestHandler('tools/list', ...) で一覧ハンドラを先に張る。
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 のコールバック型と適合しなくなる。 */
12
+ import { McpServer } from '@modelcontextprotocol/server';
13
+ import { readVersion } from '../version.js';
14
+ import { createBoundedExecutor } from './executor.js';
15
+ import { ignoreRejection } from '../infra/ignore-rejection.js';
16
+ /**
17
+ progress ハートビートの送出周期 (ms)。
18
+
19
+ 守る相手は MCP クライアントの「無音タイムアウト」。
20
+ Claude Code で計測した値は、30 分の idle watchdog (progress 受信でリセット・判定周期 30s)。
21
+ 10s はそこから十分な余裕があり、かつ長時間ツール 1 件で通知が積み上がりすぎない粒度になる。
22
+ */
23
+ const PROGRESS_HEARTBEAT_INTERVAL_MS = 10_000;
24
+ /**
25
+ tools/call の同時実行数の上限 (research R9)。
26
+ */
27
+ const CONCURRENCY_LIMIT = 4;
28
+ /**
29
+ MCP サーバ名。initialize の serverInfo.name として返る値。
30
+
31
+ `.mcp.json` の `mcpServers` の登録キーと必ず揃える。食い違うとクライアント側の表示と
32
+ ツール名の名前空間(`mcp__plugin_pecot-t_<サーバ名>__*`)が噛み合わなくなる。
33
+ */
34
+ export const MCP_SERVER_NAME = 'pecot-tools';
35
+ /**
36
+ tools/call 1 件分の progress 通知を開始し、即座に 1 発目 (順番待ち中) を送る。
37
+
38
+ MCP では progressToken を伴わないリクエストに notifications/progress を返せない。
39
+ そのためクライアントが token を付けてこなかった場合は、何も送らず undefined を返す。
40
+
41
+ 送出は run() の前から始める。守りたいのは実行中だけでなく「上限に掛かって順番待ちしている
42
+ 間も無音時計が進む」区間で、ハンドラ内に置くラッパーでは待機中を守れない。
43
+ */
44
+ function startProgressHeartbeat(toolName, serverContext, intervalMs) {
45
+ const { signal } = serverContext.mcpReq;
46
+ const progressToken = serverContext.mcpReq._meta?.progressToken;
47
+ if (progressToken === undefined)
48
+ return undefined;
49
+ let progress = 0;
50
+ let phase = '順番待ち中';
51
+ const send = () => {
52
+ progress += 1;
53
+ // 通知はベストエフォート。クライアント切断後の reject を await の流れに乗せると、
54
+ // ハンドラの例外に化けて ok: false になる。そのため、ここで握り潰す。
55
+ void ignoreRejection(serverContext.mcpReq.notify({
56
+ method: 'notifications/progress',
57
+ params: { progressToken, progress, message: `${toolName}: ${phase}` },
58
+ }));
59
+ };
60
+ const timer = setInterval(send, intervalMs);
61
+ // キャンセル済みの呼び出しへ通知を送ると、クライアントは破棄済みのトークン宛の progress を
62
+ // 受けて onerror に落とす。abort した時点で周期送出を止める。
63
+ const stop = () => {
64
+ clearInterval(timer);
65
+ signal.removeEventListener('abort', stop);
66
+ };
67
+ if (signal.aborted) {
68
+ stop();
69
+ }
70
+ else {
71
+ signal.addEventListener('abort', stop, { once: true });
72
+ send();
73
+ }
74
+ return {
75
+ markRunning: () => { phase = '実行中'; },
76
+ stop,
77
+ };
78
+ }
79
+ /**
80
+ MCP サーバを生成し、渡されたツール定義を登録する。
81
+
82
+ 各ハンドラは登録時に実行器で包む。tools/call が並行に届いても、同時に実行されるのは 4 件までになる
83
+ (上限はここで構造的に保証し、ツール実装側には依存しない)。
84
+ あわせて progress ハートビートもここで包む。全ツールに一律で適用され、ツール実装側の適用判断が要らなくなる。
85
+
86
+ 呼び出しごとに新しい McpServer と実行器を生成する (singleton にしない)。
87
+ */
88
+ export function createMcpServer(tools = [], options = {}) {
89
+ const server = new McpServer({ name: MCP_SERVER_NAME, version: readVersion() });
90
+ if (tools.length === 0) {
91
+ // registerTool を 1 件もしないと tools capability 自体が宣言されないため、明示的に張る。
92
+ // コンストラクタの capabilities で宣言すると listChanged: true が付くので使わない。
93
+ server.server.registerCapabilities({ tools: {} });
94
+ server.server.setRequestHandler('tools/list', () => ({ tools: [] }));
95
+ return server;
96
+ }
97
+ const executor = createBoundedExecutor(CONCURRENCY_LIMIT);
98
+ const intervalMs = options.progressIntervalMs ?? PROGRESS_HEARTBEAT_INTERVAL_MS;
99
+ for (const tool of tools) {
100
+ server.registerTool(tool.name, tool.config, async (arguments_, serverContext) => {
101
+ const heartbeat = startProgressHeartbeat(tool.name, serverContext, intervalMs);
102
+ try {
103
+ return await executor.run(() => {
104
+ heartbeat?.markRunning();
105
+ return tool.handler(arguments_, { signal: serverContext.mcpReq.signal });
106
+ });
107
+ }
108
+ finally {
109
+ heartbeat?.stop();
110
+ }
111
+ });
112
+ }
113
+ return server;
114
+ }
115
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1,141 @@
1
+ import { z } from 'zod';
2
+ import { type AllowlistEntry, type FailureCategory } from '../../domain/allowlist.js';
3
+ import type { LoginRegistry } from '../../infra/sso/login-registry.js';
4
+ import { type ExecutionResult } from '../result.js';
5
+ import type { ToolCallContext, ToolDefinition } from '../server.js';
6
+ /**
7
+ 1 回の awscli 呼び出しに与える既定の制限時間。
8
+ */
9
+ export declare const TOOL_TIMEOUT_MS = 120000;
10
+ /**
11
+ awscli を起動せずに拒否したときの終了コード(UsageError 相当)。
12
+ */
13
+ export declare const PRE_EXECUTION_EXIT_CODE = 2;
14
+ /**
15
+ 時間切れの終了コード(coreutils timeout の慣習)。
16
+ */
17
+ export declare const TIMEOUT_EXIT_CODE = 124;
18
+ /**
19
+ コマンドが見つからないときの終了コード(シェルの慣習)。
20
+ */
21
+ export declare const COMMAND_NOT_FOUND_EXIT_CODE = 127;
22
+ /**
23
+ 終了コードを持たない失敗(シグナル終了等)の既定値。
24
+ */
25
+ export declare const UNKNOWN_FAILURE_EXIT_CODE = 1;
26
+ /**
27
+ `profile` だけの入力。
28
+
29
+ SSO の 2 件は region を取らない(contracts/mcp-tools.md §SSO 認証)。
30
+ そのため共通入力から profile だけを切り出し、両方が同じ書式・同じ説明文を公開できるようにする。
31
+ */
32
+ export declare const PROFILE_INPUT_SHAPE: {
33
+ profile: z.ZodOptional<z.ZodString>;
34
+ };
35
+ /**
36
+ 全ツール共通の任意入力。
37
+
38
+ 書式の定義は domain/allowlist.ts の PROFILE_PATTERN / REGION_PATTERN にある。
39
+ ここはその定数をそのまま inputSchema へ載せる(契約書の共通規約を tools/list で見せるため)。
40
+ 値を書き写さないので、定義は 1 箇所のまま。書式違反はハンドラ到達前に入力検証エラーとなる。awscli は起動しない。
41
+ */
42
+ export declare const COMMON_INPUT_SHAPE: {
43
+ profile: z.ZodOptional<z.ZodString>;
44
+ region: z.ZodOptional<z.ZodString>;
45
+ };
46
+ /**
47
+ クライアント側ページング(`--max-items` / `--starting-token`)を持つツールの入力。
48
+ */
49
+ export declare const MAX_ITEMS_PAGINATION_SHAPE: {
50
+ max_items: z.ZodOptional<z.ZodNumber>;
51
+ starting_token: z.ZodOptional<z.ZodString>;
52
+ };
53
+ /**
54
+ API 固有ページング(`logs get-log-events`)を持つツールの入力。
55
+ */
56
+ export declare const LIMIT_NEXT_TOKEN_PAGINATION_SHAPE: {
57
+ limit: z.ZodOptional<z.ZodNumber>;
58
+ next_token: z.ZodOptional<z.ZodString>;
59
+ start_from_head: z.ZodDefault<z.ZodBoolean>;
60
+ };
61
+ /**
62
+ ExecutionResult と同形の outputSchema。全ツールで共有する。
63
+ */
64
+ export declare const TOOL_OUTPUT_SCHEMA: z.ZodObject<{
65
+ ok: z.ZodBoolean;
66
+ exitCode: z.ZodNumber;
67
+ category: z.ZodOptional<z.ZodString>;
68
+ data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
69
+ artifacts: z.ZodArray<z.ZodString>;
70
+ message: z.ZodString;
71
+ stderr: z.ZodOptional<z.ZodString>;
72
+ }, z.core.$strip>;
73
+ /**
74
+ 実行前に拒否したときの理由。awscli は起動しない。
75
+ */
76
+ export interface PreExecutionRejection {
77
+ readonly category: 'not-allowed' | 'invalid-input';
78
+ readonly message: string;
79
+ }
80
+ export interface ExecuteOptions {
81
+ /**
82
+ 既定は TOOL_TIMEOUT_MS。
83
+ */
84
+ readonly timeoutMs?: number;
85
+ /**
86
+ 許可リストの静的宣言だけでは表せない拒否条件(Athena の変更系クエリ、既存ファイルの上書き)。
87
+
88
+ validateInput の直後・awscli の存在確認より前に評価する。存在確認を先にすると、
89
+ awscli 未導入の環境で「拒否されるはずの呼び出し」が awscli-missing に化ける。
90
+ */
91
+ readonly reject?: (arguments_: Readonly<Record<string, unknown>>) => PreExecutionRejection | undefined;
92
+ /**
93
+ 成功時に artifacts へ載せる書き出し先。切り詰めの有無に依らず適用する。
94
+ */
95
+ readonly artifactsOf?: (arguments_: Readonly<Record<string, unknown>>) => readonly string[];
96
+ }
97
+ /**
98
+ ツール定義を組み立てるときの調整点。テストが timeoutMs を短縮する。
99
+ */
100
+ export interface ToolOptions {
101
+ readonly timeoutMs?: number;
102
+ /**
103
+ SSO の 2 件が使うログインレジストリ。既定はプロセス共有のインスタンス。
104
+ */
105
+ readonly loginRegistry?: LoginRegistry;
106
+ }
107
+ /**
108
+ allowlistedTool へ渡すツール 1 件の宣言。
109
+ */
110
+ export interface AllowlistedToolSpec {
111
+ readonly entry: AllowlistEntry;
112
+ readonly title: string;
113
+ readonly description: string;
114
+ readonly inputSchema: z.ZodObject;
115
+ readonly reject?: ExecuteOptions['reject'];
116
+ readonly artifactsOf?: ExecuteOptions['artifactsOf'];
117
+ }
118
+ /**
119
+ 失敗種別と awscli の終了コードから応答の exitCode を決める。
120
+
121
+ exitCode を持たない失敗(シグナル終了・awscli を起動していない SSO の状態遷移)は
122
+ 省略するか null で渡し、1 以上へ畳む。ok: false と exitCode 0 が同居しないようにする。
123
+ */
124
+ export declare function resolveExitCode(category: FailureCategory, exitCode?: number | null): number;
125
+ /**
126
+ 許可リストのエントリ 1 件を実行し、ExecutionResult を返す。
127
+
128
+ 検証で弾いた場合は awscli を起動しない(exitCode 2)。awscli の失敗は種別判定にかけ、
129
+ 応答へ載せる stdout / stderr は必ず秘匿化と上限を通す。
130
+ */
131
+ export declare function executeAllowlisted(entry: Readonly<AllowlistEntry>, arguments_: Readonly<Record<string, unknown>>, call: ToolCallContext, options?: Readonly<ExecuteOptions>): Promise<ExecutionResult>;
132
+ /**
133
+ 許可リストのエントリ 1 件を MCP ツール定義にする。
134
+
135
+ 入力の検証は 2 段になる。
136
+ inputSchema(型・列挙・書式)は SDK がハンドラ到達前に見て、入力検証エラーを返す。
137
+ 許可リスト側の規則(先頭 '-'・排他・shorthand の構成要素)は、executeAllowlisted が見て isError の応答にする。ハンドラで再度 zod parse はしない
138
+ (SDK が既に未知キーの除去と default の適用を済ませている)。
139
+ */
140
+ export declare function allowlistedTool(spec: Readonly<AllowlistedToolSpec>, options?: Readonly<ToolOptions>): ToolDefinition;
141
+ //# sourceMappingURL=_shared.d.ts.map