create-arc1-extension について
ARC-1 にフォークせずカスタムツールを追加する、開発者向けの TypeScript 拡張スキルです。SAP API と安全設定の選択から、ひな型作成、読み込み、SAP での動作確認まで案内します。
主な機能
- 01マニフェストと TypeScript の実装経路
- 02SAP API と操作種別の選択
- 03書き込み・実行の安全ゲート
- 04プラグインのひな型と読み込み手順
- 05ライブテストとエラーの読み解き
活用シーン
- 01既存の SAP エンドポイントを ARC-1 ツール化
- 02ARC-1 をフォークせず読み書きツールを追加
- 03許可されたコンソールクラスやレポートを実行
MCP 対応
ARC-1 の MCP インターフェースにカスタムツールを公開する拡張の作成を案内します。tools/list でツールの登録を確認する手順も含みます。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ arc-mcp/ arc-1 --skill create-arc1-extensionインストール後、Claude Code で/create-arc1-extensionを入力して読み込まれたか確認します。
詳細
ARC-1 拡張機能の作成
開発者が ARC-1 拡張機能(フォークせずに ARC-1 インスタンスへ Custom_* ツールを追加するローカルプラグイン)を構築できるように案内します。ARC-1 の認証済み SAP クライアント、7 スコープと allow* による安全性の上限、監査、PP を再利用します。フレームワークの構築(PR1~PR5)で得た知見と、S/4HANA でのライブ検証の内容をまとめています。
正確な情報源 — まずはこちらを読み、内容に沿ってください:
- ユーザーガイド(開発者をこちらへ案内してください):
docs_page/extensions.md— 基準となる手順ガイドです(ティア、ctx.http/ctx.run、セキュリティ、CF/Docker デプロイ)。ドキュメントサイトの ARC-1 の使用 → 拡張機能(カスタムツール) に掲載されています。 - 仕様:
docs/research/2026-06-17-extension-framework-spec.md(v1)およびextension-framework-v2-spec.md(延期された内容)。 - 実装例:
arc-mcp/arc-1-extension-sample— ADT と OData の 読み取り、マニフェストツール、Custom_RunClassとCustom_RunReport(名前を指定した実行のゲート制御)、OData の 書き込み(Custom_CreateSalesOrder)、および LISA カスタム ICF 統合(Custom_ListLanguages/GetTranslation/SetTranslation)一式を含みます。すべて S/4HANA でライブ検証済みです(実際の HTTP 201/200 書き込み)。最も近いツールをコピーして調整してください。
v1 の実態(誤解しないでください): 読み取りは制限なく利用できます(ctx.http.get/head)。書き込み(ctx.http.post/put/delete)は、オプトイン設定 SAP_ALLOW_PLUGIN_RAW_WRITES のもとで、ADT 以外のパス(OData/ICF)に対してのみ動作します。コンソールクラスとクラシックレポートは、オプトイン設定 SAP_ALLOW_PLUGIN_EXECUTE のもとで、ctx.run.classRun/ctx.run.programRun を使って実行します。ADT オブジェクトへの書き込み(/sap/bc/adt/… 経由の CLAS/DDLS/…)は常に拒否されます。これらは v2 のパッケージ対応 ctx.write で扱います。
トリガー
- 「ARC-1 にカスタムツール/プラグイン/拡張機能を追加したい」
- 「この SAP/ADT/OData エンドポイントを MCP ツールとしてラップしたい」
- 「フォークせずに独自の ARC-1 ツールを作りたい」
- 「ARC-1 上に診断ツールを構築したい」(SM37/SLG1/ゲートウェイログなど)
ステップ 1 — 方針を決める(推測せずに質問する)
AskUserQuestion を使用します。最初の質問で可否を確認します。
- バックエンド。 このツールは、ARC-1 が接続する同じ SAP システムとHTTP 経由で通信しますか(ADT、
OData、またはカスタム ICF/REST サービス)?
- いいえ — 別の SAP 製品(Cloud ALM、BTP services、BW、HANA、Datasphere、SuccessFactors) または HTTP 以外のプロトコル(ネイティブ RFC、SAP GUI scripting)→ これは拡張機能ではありません。 独立した MCP サーバー(BTP 認証モジュールを基盤とする「独自サーバー」方式)です。ここで終了し、 そちらを案内してください。
- はい → 続けます。
- ティア。
- マニフェストティア(宣言型 JSON、コード不要)— ツールが「入力を検証 → 1 回の読み取り GET → 返却」で済む場合。ロジックや書き込みはありません。
- コードティア(
defineTool、TypeScript)— ロジック、レスポンスの整形、複数回の読み取り、 OData/ICF サービスへの書き込み(ctx.http.post/put/delete)、またはコンソール クラスやクラシックレポートの実行(ctx.run.classRun/ctx.run.programRun)が必要な場合。
- SAP API — ADT(
/sap/bc/adt/…)、OData(/sap/opu/odata/…)、またはカスタム ICF(/sap/bc/http/…)。 カスタムエンドポイントの場合、SAP 上にすでに存在している必要があります — 拡張機能には ABAP は含まれません。 - 実行内容、およびスコープとopType:
- 読み取り(3 種の API のいずれか)→
scope: 'read'、opType: OperationType.Read—ctx.http.getを使用します。 - OData/ICF サービスへの書き込み(
ctx.http.post/put/delete)→scope: 'write'、opTypeCreate/Update/Delete。管理者がSAP_ALLOW_PLUGIN_RAW_WRITES=true+SAP_ALLOW_WRITES=trueを設定しない限り、拒否されます。パスは ADT 以外(/sap/opu/odata/…または/sap/bc/http/…)である必要があります。 - コンソールクラスの実行(
IF_OO_ADT_CLASSRUN)→scope: 'write'、opType: OperationType.Workflow—ctx.run.classRunを使用します。SAP_ALLOW_PLUGIN_EXECUTE=true+SAP_ALLOW_WRITES=trueを設定しない限り、拒否されます。 - クラシックレポートの実行(
PROG、オンプレミス)→scope: 'write'、opType: OperationType.Workflow、availableOn: 'onprem'—ctx.run.programRunを使用します。名前を入力し、テキストを出力する方式です。選択パラメーターやバリアントには対応していません。同じ実行および書き込みの許可条件が適用されます。 - ADT オブジェクトの作成/更新/削除(
/sap/bc/adt/…経由の CLAS/DDLS/…)→ v1 では利用できません — 常に拒否されます。これは v2 のパッケージ対応ctx.writeで可能になります。ツールで必要な場合は、その旨を伝えて終了してください。
- 読み取り(3 種の API のいずれか)→
ステップ 2 — スキャフォールドを作成する(arc-1-extension-sample に倣う)
新しいリポジトリ arc1-plugin-<name> を作成します(純粋な TS、ABAP なし)。
package.json—"type":"module"、peerDep"arc-1": ">=<ver>"、devDepstypescript+zod、 build"tsc && node -e \"require('node:fs').cpSync('manifests','dist/manifests',{recursive:true})\""(マニフェストがある場合のみ)。任意の"arc1": { "apiVersion": 1 }ブロックは将来に向けた宣言です — v1 ではローダーはapiVersionをPluginのデフォルトエクスポート(src/index.ts)から読み取り、requires:{scopes,packages}は v2(宣言済みですが、まだ適用されません)なので、これに依存しないでください。- 読み取り(コードティア) →
src/tools/Custom_<X>.ts:import { z } from 'zod'; import { defineTool, OperationType } from 'arc-1/public'; export default defineTool({ name: 'Custom_<X>', // MUST start with Custom_ description: '…', schema: z.object({ /* … */ }), policy: { scope: 'read', opType: OperationType.Read }, async handler(args, ctx) { const res = await ctx.http.get(`/sap/bc/adt/…`, { Accept: 'text/plain' }); return { content: [{ type: 'text', text: /* shape res.body */ }] }; }, }); - マニフェストティア →
manifests/Custom_<X>.tool.json:{ "name": "Custom_<X>", "description": "…", "scope": "read", "inputSchema": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string", "pattern": "^[A-Za-z0-9_/]{1,40}$" } } }, "request": { "method": "GET", "path": "/sap/bc/adt/…/{name}/source/main", "pathParams": { "name": "$.name" }, "accept": "text/plain" }, "response": { "maxBytes": 50000 } } - 書き込み(コードティア) — OData / カスタム ICF の
POST/PUT/DELETE→src/tools/Custom_<X>.ts:import { z } from 'zod'; import { defineTool, OperationType } from 'arc-1/public'; export default defineTool({ name: 'Custom_<X>', description: 'Create something via an OData/ICF service.', schema: z.object({ /* … */ }), policy: { scope: 'write', opType: OperationType.Create }, // POST→Create / PUT→Update / DELETE→Delete async handler(args, ctx) { const body = JSON.stringify(/* entity / payload */); // path MUST be non-ADT (OData/ICF); CSRF is fetched + attached automatically. const res = await ctx.http.post('/sap/opu/odata/<ns>/<SERVICE>/<EntitySet>', body, 'application/json', { Accept: 'application/json' }); return { content: [{ type: 'text', text: `HTTP ${res.statusCode}\n${res.body}` }] }; }, }); - 実行(コードティア) — コンソールクラスまたはクラシックレポートの実行 →
src/tools/Custom_<X>.ts。 コンソールクラスの例:
クラシックレポートの例:import { z } from 'zod'; import { defineTool, OperationType } from 'arc-1/public'; export default defineTool({ name: 'Custom_<X>', description: 'Execute an ABAP console class and return its output.', schema: z.object({ className: z.string().min(1).max(40) }), policy: { scope: 'write', opType: OperationType.Workflow }, // execute ⇒ write-class op async handler(args, ctx) { const out = await ctx.run.classRun((args as { className: string }).className); // gated; see Step 1.4 return { content: [{ type: 'text', text: out }] }; }, });import { z } from 'zod'; import { defineTool, OperationType } from 'arc-1/public'; export default defineTool({ name: 'Custom_<X>', description: 'Execute a classic ABAP report and return SAP list or error text.', schema: z.object({ reportName: z.string().min(1).max(40) }), policy: { scope: 'write', opType: OperationType.Workflow }, availableOn: 'onprem', async handler(args, ctx) { const out = await ctx.run.programRun((args as { reportName: string }).reportName); return { content: [{ type: 'text', text: out }] }; }, });programRunが受け取るのはレポート名のみです。レポートに実行時の入力が必要な場合は、小規模なIF_OO_ADT_CLASSRUNクラスを使用してください。名前付きの両操作は SAP のレスポンステキストをそのまま返します。SAP は、オブジェクトが見つからない場合や実行エラーを HTTP 200 のレスポンスに含めることがあります。 src/index.ts—export default { name, version, apiVersion: 1, tools: [...], manifests: ['manifests/Custom_<X>.tool.json'] } satisfies Plugin;- README — 機能の説明と読み込みコマンド。
ステップ 3 — ビルド + 読み込み + テスト(これは実環境で検証済みです)
# until arc-1 is published with the public API, link the local build:
( cd /path/to/arc-1 && npm link )
npm install && npm link arc-1 && npm run build
# load into an instance…
ARC1_PLUGINS=$PWD/dist/index.js arc1 --transport http-streamable
# …or drive one read call (args MUST be --json, not positional):
ARC1_PLUGINS=$PWD/dist/index.js arc1-cli call Custom_<X> --json '{"name":"RSPARAM"}'
# …a WRITE tool (OData/ICF) needs the raw-write opt-ins (else it's refused):
SAP_ALLOW_PLUGIN_RAW_WRITES=true SAP_ALLOW_WRITES=true \
ARC1_PLUGINS=$PWD/dist/index.js arc1-cli call Custom_<X> --json '{ … }'
# …an EXECUTE tool needs the execute opt-ins:
SAP_ALLOW_PLUGIN_EXECUTE=true SAP_ALLOW_WRITES=true \
ARC1_PLUGINS=$PWD/dist/index.js arc1-cli call Custom_<X> --json '{"className":"ZCL_FOO"}'
# …or, for an on-premise classic report:
SAP_ALLOW_PLUGIN_EXECUTE=true SAP_ALLOW_WRITES=true \
ARC1_PLUGINS=$PWD/dist/index.js arc1-cli call Custom_<X> --json '{"reportName":"ZREPORT"}'
終了コードだけでなく、結果を確認してください。ゲートによる拒否は AdtSafetyError(「…disabled」/「may not
write to an ADT path」)です。SAP 側の問題(パスの誤り、サービスの不足、不正なペイロード)は、SAP のステータスと本文を含む
AdtApiError です。これはゲートを通過し、リクエストが SAP に到達したことを意味します
(有用な手がかりです)。名前付きクラス/レポートの実行は例外です。SAP は実行エラーを HTTP 200 のテキストレスポンスに含めることがあり、その場合 ctx.run はそのまま返します。SAP のエラーをもとにパス/ペイロードを調整してください。HTTP ステータスだけから成功を判断しないでください。
ツールが tools/list に表示され、呼び出しで実際の SAP データが返されることを確認してください。プラグインを BTP Cloud Foundry または Docker にデプロイする場合(所有者チェック/--chown の注意点、イメージとビルドパックとボリュームのトレードオフ)については、開発者にdocs_page/extensions.md の拡張機能のデプロイセクションを案内してください。
つまずきやすい点(苦労して学んだこと)
Custom_名前空間は必須で、名前が重複するとサーバーの起動に失敗します(フェイルファスト)。ctx.httpでは読み取り(GET/HEAD)を自由に行えますが、書き込み(post/put/delete)が可能なのは NON-ADT パスのみです。また、管理者がSAP_ALLOW_PLUGIN_RAW_WRITES=true(およびSAP_ALLOW_WRITES=true)を設定し、ツールがscope:'write'を宣言している場合に限ります。/sap/bc/adt/…のオブジェクトパスへの書き込みは常に拒否されます(生の書き込みではパッケージ許可リストを適用できません。ADT オブジェクトの書き込みには v2 のctx.writeボキャブラリーを使用します)。CSRF トークンは自動的に取得され、付与されます。OData/カスタム ICF 書き込みサービスにはこちらを使用してください。ctx.clientは実行時の プレーン読み取り専用 ビューです。.http/.safetyと、データ/SQL の読み取り(getTableContents/runQuery/runTableQuery)は実行時にブロックされます(キャストするとundefinedになります)。v1 プラグインにはデータ/SQL の機能はありません。プレーン読み取りメソッド(またはctx.http.get)を使用してください。policy.opTypeはscopeと一致している必要があります。宣言したスコープが opType に必要なスコープを満たす必要があります(例:opType:'U'にはscope:'write'が必要です)。満たさない場合、プラグインのサーバー起動に失敗します。前述の例と一致させてください。- 名前を指定した実行には特権が必要です。
ctx.run.classRun(name)はIF_OO_ADT_CLASSRUNコンソールクラスを実行します。ctx.run.programRun(name)はオンプレミスの従来型レポートを実行し、一覧/エラーテキストを返します(選択条件やバリアントはありません)。どちらもSAP_ALLOW_PLUGIN_EXECUTE=trueおよびSAP_ALLOW_WRITES=trueおよびwriteスコープのツールが必要です。名前は検証されます。デフォルトでは無効です。 - OData のパスの特定 — 403
/IWFND/MED/170 "No service found"は通常、サービスが無効なだけでなく、パスが間違っていることを示します。 サービス名と名前空間の両方が重要です。たとえば、EPM デモは/sap/opu/odata/iwbep/GWSAMPLE_BASICであり、/sap/opu/odata/sap/ZGWSAMPLE_BASICではありません。GET …/$metadataで正しいパスを確認してください(200 = 正しいパス、403 = パスが間違っているか、実際にサービスが無効です。その場合は/IWFND/MAINT_SERVICEを確認してください)。 - OData V2 の 作成 に関する注意点:
POSTに$format=jsonクエリオプションを付けてはいけません(これは SystemQueryOption であり、400 "not allowed for this Request Type"が返されます)。代わりにAcceptヘッダーで JSON を指定してください。必須のエンティティフィールドは異なる場合があります。構造を確認するにはGET …/<EntitySet>?$top=1を実行してください。 - カスタム ICF(LISA スタイル)サービス: 通常は JSON ボディを付けて
POST /sap/bc/http/sap/<SERVICE>/<action>を実行します(アクションは URL パスに、パラメーターはボディに指定します)。これにより、次の 2 点に注意が必要です。(1)サービスが読み取りにもPOSTを使う場合、その読み取りツールにもSAP_ALLOW_PLUGIN_RAW_WRITESとscope:'write'が必要です(ctx.httpは HTTP メソッドに基づいて制御します)。操作の意図を正しく示すにはopType: Readを宣言してください。(2)書き込みにはオープン中の移送依頼が必要な場合があります。SAPTransportで作成してください(STMS ルートのないシステムではローカル依頼になりますが、問題ありません)。 - SAP に書き込みが到達しても、2xx が返るとは限りません。 ゲート、CSRF、POST がすべて成功しても、SAP が 4xx を返すことがあります(ペイロードの不正、サービスが無効、移送の欠落など)。これはゲートによる拒否ではなく
AdtApiErrorです。リクエストを修正してください。フレームワークは正しく動作しています。 - マニフェスト層は読み取り専用の GET のみで、
additionalProperties:falseが必須です。pathはホストを含まないテンプレートで、パスパラメーターはパーセントエンコードされます(パストラバーサル対策)。v1 では POST/ボディは使用できません。 availableOn: 'onprem' | 'btp'(任意、デフォルトはall)を指定すると、解決されたシステムタイプが異なる場合にtools/listからツールが非表示になります。ハイパーフォーカスモードではプラグインツールは一切表示されません。ctxのelicit/notify/samplingは機能によって制御されます。MCP クライアントが対応している場合にのみ利用でき(CLI/stdio 経由では利用できません)、それ以外の場合は存在しません。arc-1/public/testingのcreateMockToolContextを使ってハンドラーを単体テストしてください(ctx.http/ctx.run.classRun/ctx.run.programRunの呼び出しを記録し、設定済みの出力を返します。稼働中の SAP は不要です)。- 管理者用キルスイッチ:
SAP_DENY_ACTIONS=Custom_*(すべて)またはCustom_Foo(1 つ)を指定すると、プラグインツールが削除されます。
デプロイ(質問された場合)
docs_page/extensions.md の 拡張機能のデプロイを案内してください。
主なポイント:プラグインは ローカルファイルであり、絶対パスの ARC1_PLUGINS($HOME は展開されません)から読み込まれます。BTP CF では、派生 Docker イメージ(FROM ghcr.io/arc-mcp/arc-1、COPY --chown=arc1:arc1 を使用します。通常の COPY では root 所有になり、ローダーが所有者以外のファイル、または全ユーザーが書き込み可能なファイルを拒否します)を使用するか、ビルド済みの dist/ をビルドパックアプリのファイル領域(/home/vcap/app/...、vcap 所有)に一緒にデプロイします。ホットリロードには対応していません(変更するには再デプロイが必要です)。プラグインの追加に XSUAA の変更は不要です(スコープは再利用されます)。