@oobe-protocol-labs/sap-mcp-server 0.2.1 → 0.7.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 (162) hide show
  1. package/.env.example +3 -0
  2. package/CHANGELOG.md +238 -1
  3. package/README.md +64 -1
  4. package/USER_DOCS/00_START_HERE.md +23 -1
  5. package/USER_DOCS/01_HOSTED_REMOTE_MCP.md +55 -0
  6. package/USER_DOCS/03_PAYMENTS_X402_PAYSH.md +63 -7
  7. package/USER_DOCS/04_CLIENT_CONFIGS.md +164 -11
  8. package/USER_DOCS/06_DESKTOP_GUI_WIZARD.md +262 -0
  9. package/dist/adapters/mcp/sdk-compat.d.ts.map +1 -1
  10. package/dist/adapters/mcp/sdk-compat.js +32 -18
  11. package/dist/adapters/mcp/sdk-compat.js.map +1 -1
  12. package/dist/config/mcp-client-injection.d.ts +72 -2
  13. package/dist/config/mcp-client-injection.d.ts.map +1 -1
  14. package/dist/config/mcp-client-injection.js +691 -15
  15. package/dist/config/mcp-client-injection.js.map +1 -1
  16. package/dist/config/setup.js +1 -1
  17. package/dist/config/setup.js.map +1 -1
  18. package/dist/config/wizard.d.ts.map +1 -1
  19. package/dist/config/wizard.js +164 -85
  20. package/dist/config/wizard.js.map +1 -1
  21. package/dist/core/constants.d.ts +1 -1
  22. package/dist/core/constants.js +1 -1
  23. package/dist/core/logger.js +1 -1
  24. package/dist/payments/facilitator-rpc-fallback.d.ts +43 -0
  25. package/dist/payments/facilitator-rpc-fallback.d.ts.map +1 -0
  26. package/dist/payments/facilitator-rpc-fallback.js +228 -0
  27. package/dist/payments/facilitator-rpc-fallback.js.map +1 -0
  28. package/dist/payments/json-rpc.d.ts.map +1 -1
  29. package/dist/payments/json-rpc.js +2 -1
  30. package/dist/payments/json-rpc.js.map +1 -1
  31. package/dist/payments/monetization-gate.d.ts +43 -2
  32. package/dist/payments/monetization-gate.d.ts.map +1 -1
  33. package/dist/payments/monetization-gate.js +618 -9
  34. package/dist/payments/monetization-gate.js.map +1 -1
  35. package/dist/payments/oobe-facilitator-server.d.ts +12 -1
  36. package/dist/payments/oobe-facilitator-server.d.ts.map +1 -1
  37. package/dist/payments/oobe-facilitator-server.js +109 -8
  38. package/dist/payments/oobe-facilitator-server.js.map +1 -1
  39. package/dist/payments/pay-sh-spec.js +1 -1
  40. package/dist/payments/pricing.d.ts.map +1 -1
  41. package/dist/payments/pricing.js +1 -0
  42. package/dist/payments/pricing.js.map +1 -1
  43. package/dist/payments/usage-ledger.d.ts +10 -0
  44. package/dist/payments/usage-ledger.d.ts.map +1 -1
  45. package/dist/payments/usage-ledger.js +51 -0
  46. package/dist/payments/usage-ledger.js.map +1 -1
  47. package/dist/payments/x402-paid-call.d.ts +53 -0
  48. package/dist/payments/x402-paid-call.d.ts.map +1 -0
  49. package/dist/payments/x402-paid-call.js +370 -0
  50. package/dist/payments/x402-paid-call.js.map +1 -0
  51. package/dist/prompts/context/sap-agent-context.prompt.js +33 -6
  52. package/dist/prompts/context/sap-agent-context.prompt.js.map +1 -1
  53. package/dist/prompts/developer/debug-sap-error.prompt.js +1 -1
  54. package/dist/prompts/developer/debug-sap-error.prompt.js.map +1 -1
  55. package/dist/prompts/developer/generate-sap-integration.prompt.js +6 -2
  56. package/dist/prompts/developer/generate-sap-integration.prompt.js.map +1 -1
  57. package/dist/prompts/execution-proof/explain-proof-of-execution.prompt.js +1 -1
  58. package/dist/prompts/execution-proof/explain-proof-of-execution.prompt.js.map +1 -1
  59. package/dist/prompts/execution-proof/verify-execution-proof.prompt.js +1 -1
  60. package/dist/prompts/execution-proof/verify-execution-proof.prompt.js.map +1 -1
  61. package/dist/prompts/payments/create-paid-api.prompt.js +1 -1
  62. package/dist/prompts/payments/create-paid-api.prompt.js.map +1 -1
  63. package/dist/prompts/payments/explain-x402-settlement.prompt.js +1 -1
  64. package/dist/prompts/payments/explain-x402-settlement.prompt.js.map +1 -1
  65. package/dist/prompts/register-prompts.js +2 -2
  66. package/dist/prompts/register-prompts.js.map +1 -1
  67. package/dist/prompts/registry/analyze-sap-agent.prompt.js +1 -1
  68. package/dist/prompts/registry/analyze-sap-agent.prompt.js.map +1 -1
  69. package/dist/prompts/registry/register-sap-agent.prompt.js +2 -2
  70. package/dist/prompts/registry/register-sap-agent.prompt.js.map +1 -1
  71. package/dist/remote/server.d.ts +150 -1
  72. package/dist/remote/server.d.ts.map +1 -1
  73. package/dist/remote/server.js +1176 -84
  74. package/dist/remote/server.js.map +1 -1
  75. package/dist/resources/current/sap-current-config.resource.d.ts.map +1 -1
  76. package/dist/resources/current/sap-current-config.resource.js +22 -2
  77. package/dist/resources/current/sap-current-config.resource.js.map +1 -1
  78. package/dist/resources/execution-proof/sap-execution-record.resource.js +1 -1
  79. package/dist/resources/execution-proof/sap-execution-record.resource.js.map +1 -1
  80. package/dist/resources/memory/sap-memory.resource.js +1 -1
  81. package/dist/resources/memory/sap-memory.resource.js.map +1 -1
  82. package/dist/resources/profile/sap-active-profile.resource.js +1 -1
  83. package/dist/resources/profile/sap-active-profile.resource.js.map +1 -1
  84. package/dist/resources/register-resources.js +2 -2
  85. package/dist/resources/register-resources.js.map +1 -1
  86. package/dist/resources/registry/sap-agent.resource.js +1 -1
  87. package/dist/resources/registry/sap-agent.resource.js.map +1 -1
  88. package/dist/resources/registry/sap-global-registry.resource.js +1 -1
  89. package/dist/resources/registry/sap-global-registry.resource.js.map +1 -1
  90. package/dist/resources/reputation/sap-reputation.resource.js +1 -1
  91. package/dist/resources/reputation/sap-reputation.resource.js.map +1 -1
  92. package/dist/resources/stats/sap-network-stats.resource.js +1 -1
  93. package/dist/resources/stats/sap-network-stats.resource.js.map +1 -1
  94. package/dist/resources/tool-schema/sap-tool-schema.resource.js +1 -1
  95. package/dist/resources/tool-schema/sap-tool-schema.resource.js.map +1 -1
  96. package/dist/sap/sap-client-manager.js +6 -6
  97. package/dist/sap/sap-client-manager.js.map +1 -1
  98. package/dist/security/tool-permissions.d.ts.map +1 -1
  99. package/dist/security/tool-permissions.js +1 -0
  100. package/dist/security/tool-permissions.js.map +1 -1
  101. package/dist/server/create-server.js +5 -5
  102. package/dist/server/create-server.js.map +1 -1
  103. package/dist/server/register-capabilities.js +4 -4
  104. package/dist/server/register-capabilities.js.map +1 -1
  105. package/dist/server/server-metadata.js +1 -1
  106. package/dist/signer/signer-resolver.js +12 -5
  107. package/dist/signer/signer-resolver.js.map +1 -1
  108. package/dist/tools/client-sdk-tools.js +7 -7
  109. package/dist/tools/client-sdk-tools.js.map +1 -1
  110. package/dist/tools/index.d.ts +2 -0
  111. package/dist/tools/index.d.ts.map +1 -1
  112. package/dist/tools/index.js +4 -0
  113. package/dist/tools/index.js.map +1 -1
  114. package/dist/tools/magicblock-tools.d.ts +28 -0
  115. package/dist/tools/magicblock-tools.d.ts.map +1 -0
  116. package/dist/tools/magicblock-tools.js +578 -0
  117. package/dist/tools/magicblock-tools.js.map +1 -0
  118. package/dist/tools/profile-tools.d.ts.map +1 -1
  119. package/dist/tools/profile-tools.js +44 -0
  120. package/dist/tools/profile-tools.js.map +1 -1
  121. package/dist/tools/register-tools.d.ts.map +1 -1
  122. package/dist/tools/register-tools.js +11 -2
  123. package/dist/tools/register-tools.js.map +1 -1
  124. package/dist/tools/sap-sdk-tools.js +2 -2
  125. package/dist/tools/sap-sdk-tools.js.map +1 -1
  126. package/dist/tools/sap-sns-tools.js +2 -2
  127. package/dist/tools/sap-sns-tools.js.map +1 -1
  128. package/dist/tools/skills-tools.d.ts +1 -1
  129. package/dist/tools/skills-tools.d.ts.map +1 -1
  130. package/dist/tools/skills-tools.js +10 -1
  131. package/dist/tools/skills-tools.js.map +1 -1
  132. package/dist/tools/tool-aliases.d.ts +10 -0
  133. package/dist/tools/tool-aliases.d.ts.map +1 -0
  134. package/dist/tools/tool-aliases.js +23 -0
  135. package/dist/tools/tool-aliases.js.map +1 -0
  136. package/dist/tools/x402-paid-call-tool.d.ts +12 -0
  137. package/dist/tools/x402-paid-call-tool.d.ts.map +1 -0
  138. package/dist/tools/x402-paid-call-tool.js +118 -0
  139. package/dist/tools/x402-paid-call-tool.js.map +1 -0
  140. package/dist/tui/config-wizard.js +2 -2
  141. package/dist/tui/config-wizard.js.map +1 -1
  142. package/dist/wizard-core/desktop-flow.d.ts +82 -0
  143. package/dist/wizard-core/desktop-flow.d.ts.map +1 -0
  144. package/dist/wizard-core/desktop-flow.js +181 -0
  145. package/dist/wizard-core/desktop-flow.js.map +1 -0
  146. package/docs/00_README.md +3 -0
  147. package/docs/03_CONFIGURATION_AND_WIZARD.md +31 -5
  148. package/docs/06_PAYMENTS_X402_AND_PAYSH.md +70 -7
  149. package/docs/07_ENDPOINTS_AND_CLIENTS.md +56 -1
  150. package/docs/10_OPERATIONS_RELEASE_AND_PM2.md +7 -6
  151. package/docs/13_BOUNTY_PROGRAM_PROPOSAL.md +469 -0
  152. package/docs/14_DESKTOP_WIZARD_RELEASE.md +264 -0
  153. package/docs/README.md +104 -0
  154. package/docs/_sidebar.md +31 -0
  155. package/docs/magicblock-tools.md +166 -0
  156. package/docs/x402-protocol-spec.md +984 -0
  157. package/package.json +23 -2
  158. package/server.json +71 -0
  159. package/skills/README.md +3 -2
  160. package/skills/sap-defi/SKILL.md +8 -0
  161. package/skills/sap-mcp/SKILL.md +124 -2
  162. package/skills/sap-payments-x402/SKILL.md +54 -2
@@ -2,6 +2,14 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from
2
2
  import { homedir } from 'os';
3
3
  import { dirname, join } from 'path';
4
4
  const SAP_SERVER_NAME = 'sap';
5
+ const SAP_PAYMENT_BRIDGE_SERVER_NAME = 'sap_payments';
6
+ const X402_PAID_CALL_ADDON_ID = 'x402-paid-call';
7
+ const X402_PAID_CALL_COMMAND = 'sap-mcp-x402-paid-call';
8
+ const X402_PAYMENT_BRIDGE_TOOLS = [
9
+ 'sap_x402_paid_call',
10
+ 'sap_profile_current',
11
+ 'sap_x402_estimate_cost',
12
+ ];
5
13
  /**
6
14
  * @name HOSTED_SAP_MCP_URL
7
15
  * @description Public OOBE hosted SAP MCP Streamable HTTP endpoint used in manual client setup snippets.
@@ -30,6 +38,16 @@ function isRecord(value) {
30
38
  function escapeRegExp(value) {
31
39
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
32
40
  }
41
+ /**
42
+ * @name shellQuote
43
+ * @description Quotes a command argument for copy/paste shell snippets.
44
+ */
45
+ function shellQuote(value) {
46
+ if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) {
47
+ return value;
48
+ }
49
+ return `'${value.replace(/'/g, "'\\''")}'`;
50
+ }
33
51
  /**
34
52
  * @name readTargetContent
35
53
  * @description Reads an existing target file or returns a format-appropriate empty config.
@@ -81,19 +99,55 @@ export function createCanonicalServerConfig(workspaceRoot = process.cwd()) {
81
99
  * @description Builds copy/paste JSON snippets for hosted remote and local active-profile MCP client setup.
82
100
  */
83
101
  export function createManualMcpJsonSnippets(canonical, hostedUrl = HOSTED_SAP_MCP_URL) {
102
+ const hostedConfig = {
103
+ url: hostedUrl,
104
+ transport: 'streamable-http',
105
+ };
84
106
  return [
85
107
  {
86
- title: 'Hosted SAP MCP JSON',
87
- description: 'Use this after creating the user SAP profile with the wizard; the agent connects remotely while signatures stay user-controlled.',
108
+ title: 'Hosted SAP MCP JSON (Claude and generic MCP clients)',
109
+ description: 'Use this for clients whose config expects a root mcpServers map. Do not paste this into Codex config.toml or OpenClaw gateway config.',
88
110
  content: formatJson({
89
111
  mcpServers: {
90
- [SAP_SERVER_NAME]: {
91
- url: hostedUrl,
92
- transport: 'streamable-http',
93
- },
112
+ [SAP_SERVER_NAME]: hostedConfig,
94
113
  },
95
114
  }),
96
115
  },
116
+ {
117
+ title: 'Hosted SAP MCP TOML (Codex config.toml)',
118
+ description: 'Use this for Codex ~/.codex/config.toml. Codex supports Streamable HTTP MCP servers with a url-based TOML entry.',
119
+ content: hostedTomlServerBlock(hostedConfig),
120
+ },
121
+ {
122
+ title: 'Hosted SAP MCP JSON (Hermes global mcp.json)',
123
+ description: 'Use this for ~/.hermes/mcp.json, which expects flat server entries rather than a nested mcpServers map.',
124
+ content: formatJson({
125
+ [SAP_SERVER_NAME]: hostedConfig,
126
+ }),
127
+ },
128
+ {
129
+ title: 'Hosted SAP MCP YAML (Hermes profile config.yaml)',
130
+ description: 'Use this inside Hermes profile YAML files under the top-level mcp_servers section.',
131
+ content: [
132
+ 'mcp_servers:',
133
+ ` ${SAP_SERVER_NAME}:`,
134
+ ` url: ${hostedUrl}`,
135
+ ' transport: streamable-http',
136
+ '',
137
+ ].join('\n'),
138
+ },
139
+ {
140
+ title: 'Hosted SAP MCP YAML (OpenClaw gateway config)',
141
+ description: 'Use this inside OpenClaw gateway YAML config under the top-level mcp.servers section.',
142
+ content: [
143
+ 'mcp:',
144
+ ' servers:',
145
+ ` ${SAP_SERVER_NAME}:`,
146
+ ` url: ${hostedUrl}`,
147
+ ' transport: streamable-http',
148
+ '',
149
+ ].join('\n'),
150
+ },
97
151
  {
98
152
  title: 'Local SAP MCP JSON',
99
153
  description: 'Use this when the agent runs SAP MCP locally and follows ~/.config/mcp-sap/.active-profile.',
@@ -103,28 +157,267 @@ export function createManualMcpJsonSnippets(canonical, hostedUrl = HOSTED_SAP_MC
103
157
  },
104
158
  }),
105
159
  },
160
+ {
161
+ title: 'Local SAP MCP TOML (Codex config.toml)',
162
+ description: 'Use this when you specifically want Codex to launch the local stdio SAP MCP server through npx and follow the active profile manager.',
163
+ content: tomlServerBlock(createNpxCodexServerConfig()),
164
+ },
106
165
  ];
107
166
  }
108
167
  /**
109
- * @name getKnownClientTargets
110
- * @description Returns known MCP client config paths for the current operating system.
168
+ * @name createNpxCodexServerConfig
169
+ * @description Builds a portable Codex stdio config that works without a source checkout.
111
170
  */
112
- export function getKnownClientTargets(homeDir = homedir()) {
113
- const targets = [
171
+ export function createNpxCodexServerConfig() {
172
+ const command = process.platform === 'win32' ? 'npx.cmd' : 'npx';
173
+ return {
174
+ command,
175
+ args: [
176
+ '--yes',
177
+ '--package',
178
+ '@oobe-protocol-labs/sap-mcp-server',
179
+ 'sap-mcp-server',
180
+ ],
181
+ env: {
182
+ SAP_MCP_ALLOW_ENV_CONFIG_OVERRIDE: 'false',
183
+ SAP_LOG_LEVEL: 'info',
184
+ },
185
+ };
186
+ }
187
+ /**
188
+ * @name createNpxPaymentBridgeServerConfig
189
+ * @description Builds a portable local stdio MCP config that exposes only x402 payment helper tools.
190
+ */
191
+ export function createNpxPaymentBridgeServerConfig() {
192
+ const command = process.platform === 'win32' ? 'npx.cmd' : 'npx';
193
+ return {
194
+ command,
195
+ args: [
196
+ '--yes',
197
+ '--package',
198
+ '@oobe-protocol-labs/sap-mcp-server',
199
+ 'sap-mcp-server',
200
+ ],
201
+ env: {
202
+ SAP_MCP_ALLOW_ENV_CONFIG_OVERRIDE: 'false',
203
+ SAP_ALLOWED_TOOLS: X402_PAYMENT_BRIDGE_TOOLS.join(','),
204
+ SAP_LOG_LEVEL: 'info',
205
+ },
206
+ };
207
+ }
208
+ /**
209
+ * @name createX402PaidCallAddonSnippets
210
+ * @description Builds manual plugin/addon snippets for clients that can call a local x402 payment helper.
211
+ */
212
+ export function createX402PaidCallAddonSnippets() {
213
+ const paymentBridge = createNpxPaymentBridgeServerConfig();
214
+ return [
215
+ {
216
+ title: 'Codex Payment Bridge TOML',
217
+ description: 'Use this in ~/.codex/config.toml beside the hosted [mcp_servers.sap] entry. Codex will see only payment helper tools from this local bridge.',
218
+ content: codexPaymentBridgeTomlBlock(paymentBridge),
219
+ },
220
+ {
221
+ title: 'Claude Code Payment Bridge Commands',
222
+ description: 'Use the official Claude Code MCP CLI flow: add hosted SAP MCP over HTTP, then add this local stdio bridge for x402 payment retries.',
223
+ content: [
224
+ `claude mcp add --transport http ${SAP_SERVER_NAME} ${HOSTED_SAP_MCP_URL}`,
225
+ `claude mcp add --transport stdio ${SAP_PAYMENT_BRIDGE_SERVER_NAME} -- ${paymentBridge.command} ${paymentBridge.args.map(shellQuote).join(' ')}`,
226
+ '',
227
+ 'Then ask Claude to call sap_payments.sap_x402_paid_call for hosted paid/write SAP MCP tools.',
228
+ '',
229
+ ].join('\n'),
230
+ },
231
+ {
232
+ title: 'Claude Desktop Payment Bridge JSON',
233
+ description: 'Use this for Claude-style JSON config files with a root mcpServers map.',
234
+ content: formatJson({
235
+ mcpServers: {
236
+ [SAP_SERVER_NAME]: {
237
+ type: 'http',
238
+ url: HOSTED_SAP_MCP_URL,
239
+ },
240
+ [SAP_PAYMENT_BRIDGE_SERVER_NAME]: paymentBridge,
241
+ },
242
+ }),
243
+ },
244
+ {
245
+ title: 'Hermes Payment Bridge YAML',
246
+ description: 'Use this inside Hermes profile config.yaml under mcp_servers when Hermes does not already have the x402 plugin installed.',
247
+ content: [
248
+ 'mcp_servers:',
249
+ ` ${SAP_SERVER_NAME}:`,
250
+ ` url: ${HOSTED_SAP_MCP_URL}`,
251
+ ' transport: streamable-http',
252
+ ` ${SAP_PAYMENT_BRIDGE_SERVER_NAME}:`,
253
+ ` command: ${paymentBridge.command}`,
254
+ ' args:',
255
+ ...paymentBridge.args.map((arg) => ` - ${arg}`),
256
+ ' env:',
257
+ ...Object.entries(paymentBridge.env).map(([key, value]) => ` ${key}: ${value === 'false' || value === 'true' ? `"${value}"` : value}`),
258
+ '',
259
+ ].join('\n'),
260
+ },
261
+ {
262
+ title: 'OpenClaw Payment Bridge JSON',
263
+ description: 'Use this for OpenClaw gateway JSON config, which expects named servers under mcp.servers.',
264
+ content: formatJson({
265
+ mcp: {
266
+ servers: {
267
+ [SAP_SERVER_NAME]: {
268
+ url: HOSTED_SAP_MCP_URL,
269
+ transport: 'streamable-http',
270
+ },
271
+ [SAP_PAYMENT_BRIDGE_SERVER_NAME]: paymentBridge,
272
+ },
273
+ },
274
+ }),
275
+ },
276
+ {
277
+ title: 'Generic Payment Bridge JSON',
278
+ description: 'Use this only for MCP clients that explicitly accept JSON mcpServers entries for one remote server plus one local stdio bridge.',
279
+ content: formatJson({
280
+ mcpServers: {
281
+ [SAP_SERVER_NAME]: {
282
+ url: HOSTED_SAP_MCP_URL,
283
+ transport: 'streamable-http',
284
+ },
285
+ [SAP_PAYMENT_BRIDGE_SERVER_NAME]: paymentBridge,
286
+ },
287
+ }),
288
+ },
289
+ {
290
+ title: 'Hermes Addon: x402_paid_call',
291
+ description: 'Use this concept for Hermes plugin/addon registries that expose local command-backed tools.',
292
+ content: formatJson({
293
+ plugins: {
294
+ x402_paid_call: {
295
+ command: X402_PAID_CALL_COMMAND,
296
+ description: 'Pay and retry hosted SAP MCP x402 tool calls with the active local SAP MCP profile.',
297
+ args: [],
298
+ env: {
299
+ SAP_MCP_ALLOW_ENV_CONFIG_OVERRIDE: 'false',
300
+ },
301
+ },
302
+ },
303
+ }),
304
+ },
305
+ {
306
+ title: 'Claude/Codex/OpenClaw Addon Command',
307
+ description: 'Use this command from a local tool/plugin wrapper when the client supports command-backed addons.',
308
+ content: [
309
+ `${X402_PAID_CALL_COMMAND} \\`,
310
+ ' --tool sap_list_all_agents \\',
311
+ ' --arguments \'{"limit":5}\' \\',
312
+ ' --max-usd 0.02 \\',
313
+ ' --confirm',
314
+ '',
315
+ ].join('\n'),
316
+ },
317
+ {
318
+ title: 'Local MCP Tool Alternative',
319
+ description: 'When the local SAP MCP stdio server is installed, agents can call sap_x402_paid_call instead of using a runtime-specific plugin.',
320
+ content: formatJson({
321
+ name: 'sap_x402_paid_call',
322
+ arguments: {
323
+ toolName: 'sap_list_all_agents',
324
+ arguments: { limit: 5 },
325
+ maxPriceUsd: 0.02,
326
+ confirm: true,
327
+ },
328
+ }),
329
+ },
330
+ ];
331
+ }
332
+ /**
333
+ * @name installX402PaidCallAddon
334
+ * @description Installs a portable local addon bundle with manifest and client snippets.
335
+ */
336
+ export function installX402PaidCallAddon(targetDir = join(homedir(), '.config', 'mcp-sap', 'addons', X402_PAID_CALL_ADDON_ID)) {
337
+ mkdirSync(targetDir, { recursive: true, mode: 0o700 });
338
+ const manifestPath = join(targetDir, 'manifest.json');
339
+ const readmePath = join(targetDir, 'README.md');
340
+ const snippetsPath = join(targetDir, 'client-snippets.json');
341
+ const files = [manifestPath, readmePath, snippetsPath];
342
+ writeFileSync(manifestPath, formatJson({
343
+ id: X402_PAID_CALL_ADDON_ID,
344
+ name: 'SAP MCP x402 Paid Call',
345
+ command: X402_PAID_CALL_COMMAND,
346
+ toolName: 'x402_paid_call',
347
+ localMcpToolName: 'sap_x402_paid_call',
348
+ description: 'Local payment helper for hosted SAP MCP paid tools. It signs x402 payment payloads with the user-controlled SAP MCP profile signer.',
349
+ security: {
350
+ readsLocalKeypairFileForSigning: true,
351
+ printsKeypairBytes: false,
352
+ sendsKeypairBytesToHostedServer: false,
353
+ requiresConfirm: true,
354
+ requiresMaxPriceUsd: true,
355
+ },
356
+ }), { encoding: 'utf-8', mode: 0o600 });
357
+ writeFileSync(readmePath, [
358
+ '# SAP MCP x402 Paid Call Addon',
359
+ '',
360
+ 'This addon lets local agent runtimes pay hosted SAP MCP x402 tool challenges without giving OOBE or the hosted server user keypair bytes.',
361
+ '',
362
+ 'Use the command directly from a runtime-specific plugin wrapper:',
363
+ '',
364
+ '```bash',
365
+ `${X402_PAID_CALL_COMMAND} --tool sap_list_all_agents --arguments '{"limit":5}' --max-usd 0.02 --confirm`,
366
+ '```',
367
+ '',
368
+ 'Or call the local SAP MCP tool `sap_x402_paid_call` when local stdio SAP MCP is available.',
369
+ '',
370
+ 'Security rules:',
371
+ '',
372
+ '- keep keypair files under `~/.config/mcp-sap` or use a future external signing bridge;',
373
+ '- never paste keypair bytes into agent config;',
374
+ '- always set `maxPriceUsd` / `--max-usd`;',
375
+ '- `confirm: true` / `--confirm` is required because a payment payload is signed.',
376
+ '',
377
+ ].join('\n'), { encoding: 'utf-8', mode: 0o600 });
378
+ writeFileSync(snippetsPath, formatJson({
379
+ snippets: createX402PaidCallAddonSnippets(),
380
+ }), { encoding: 'utf-8', mode: 0o600 });
381
+ return {
382
+ addonId: X402_PAID_CALL_ADDON_ID,
383
+ targetDir,
384
+ files,
385
+ };
386
+ }
387
+ /**
388
+ * @name getClaudeConfigTargets
389
+ * @description Returns Claude config targets for the host platform without relying on shell-specific environment variables.
390
+ */
391
+ function getClaudeConfigTargets(homeDir, platform) {
392
+ const baseDir = platform === 'win32'
393
+ ? join(homeDir, 'AppData', 'Roaming', 'Claude')
394
+ : platform === 'darwin'
395
+ ? join(homeDir, 'Library', 'Application Support', 'Claude')
396
+ : join(homeDir, '.config', 'Claude');
397
+ return [
114
398
  {
115
399
  id: 'claude',
116
400
  label: 'Claude Desktop',
117
- path: join(homeDir, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
401
+ path: join(baseDir, 'claude_desktop_config.json'),
118
402
  format: 'json',
119
403
  exists: false,
120
404
  },
121
405
  {
122
406
  id: 'claude',
123
407
  label: 'Claude App Config',
124
- path: join(homeDir, 'Library', 'Application Support', 'Claude', 'config.json'),
408
+ path: join(baseDir, 'config.json'),
125
409
  format: 'json',
126
410
  exists: false,
127
411
  },
412
+ ];
413
+ }
414
+ /**
415
+ * @name getKnownClientTargets
416
+ * @description Returns known MCP client config paths for the current operating system.
417
+ */
418
+ export function getKnownClientTargets(homeDir = homedir(), platform = process.platform) {
419
+ const targets = [
420
+ ...getClaudeConfigTargets(homeDir, platform),
128
421
  {
129
422
  id: 'hermes',
130
423
  label: 'Hermes Global MCP',
@@ -177,13 +470,15 @@ export function getKnownClientTargets(homeDir = homedir()) {
177
470
  * @name discoverMcpClientTargets
178
471
  * @description Finds existing client config files and keeps create-capable standard targets.
179
472
  */
180
- export function discoverMcpClientTargets(homeDir = homedir()) {
181
- const targets = getKnownClientTargets(homeDir);
473
+ export function discoverMcpClientTargets(homeDir = homedir(), platform = process.platform) {
474
+ const targets = getKnownClientTargets(homeDir, platform);
182
475
  return targets.filter((target) => {
183
476
  if (target.exists) {
184
477
  return true;
185
478
  }
186
479
  return target.path.endsWith('claude_desktop_config.json')
480
+ || target.path.endsWith(join('.hermes', 'mcp.json'))
481
+ || target.path.endsWith(join('.codex', 'config.toml'))
187
482
  || target.path.endsWith(join('.openclaw', 'mcp.json'));
188
483
  });
189
484
  }
@@ -250,9 +545,91 @@ function buildJsonContent(content, canonical, mode) {
250
545
  hadSapConfig,
251
546
  };
252
547
  }
548
+ /**
549
+ * @name hostedJsonServerConfig
550
+ * @description Builds the hosted SAP MCP server object for JSON-based clients.
551
+ */
552
+ function hostedJsonServerConfig(target) {
553
+ if (target.id === 'claude') {
554
+ return {
555
+ type: 'http',
556
+ url: HOSTED_SAP_MCP_URL,
557
+ };
558
+ }
559
+ return {
560
+ url: HOSTED_SAP_MCP_URL,
561
+ transport: 'streamable-http',
562
+ };
563
+ }
564
+ /**
565
+ * @name buildHostedPaymentBridgeJsonContent
566
+ * @description Builds JSON config with hosted SAP MCP plus local x402 payment bridge.
567
+ */
568
+ function buildHostedPaymentBridgeJsonContent(content, target) {
569
+ const parsed = content.trim() ? JSON.parse(content) : {};
570
+ const root = isRecord(parsed) ? parsed : {};
571
+ const paymentBridge = createNpxPaymentBridgeServerConfig();
572
+ if (target.id === 'openclaw') {
573
+ return buildOpenClawHostedPaymentBridgeJsonContent(root, paymentBridge);
574
+ }
575
+ if (target.id === 'hermes') {
576
+ const hadSapConfig = isRecord(root[SAP_SERVER_NAME]) || isRecord(root[SAP_PAYMENT_BRIDGE_SERVER_NAME]);
577
+ return {
578
+ nextContent: formatJson({
579
+ ...root,
580
+ [SAP_SERVER_NAME]: hostedJsonServerConfig(target),
581
+ [SAP_PAYMENT_BRIDGE_SERVER_NAME]: paymentBridge,
582
+ }),
583
+ hadSapConfig,
584
+ };
585
+ }
586
+ const serversRaw = root.mcpServers;
587
+ const servers = isRecord(serversRaw) ? { ...serversRaw } : {};
588
+ const hadSapConfig = isRecord(servers[SAP_SERVER_NAME]) || isRecord(servers[SAP_PAYMENT_BRIDGE_SERVER_NAME]);
589
+ servers[SAP_SERVER_NAME] = hostedJsonServerConfig(target);
590
+ servers[SAP_PAYMENT_BRIDGE_SERVER_NAME] = paymentBridge;
591
+ return {
592
+ nextContent: formatJson({ ...root, mcpServers: servers }),
593
+ hadSapConfig,
594
+ };
595
+ }
596
+ /**
597
+ * @name buildOpenClawHostedPaymentBridgeJsonContent
598
+ * @description Builds OpenClaw gateway JSON using the documented `mcp.servers` shape.
599
+ */
600
+ function buildOpenClawHostedPaymentBridgeJsonContent(root, paymentBridge) {
601
+ const mcp = isRecord(root.mcp) ? { ...root.mcp } : {};
602
+ const servers = isRecord(mcp.servers) ? { ...mcp.servers } : {};
603
+ const legacyServers = isRecord(root.mcpServers) ? { ...root.mcpServers } : undefined;
604
+ const hadSapConfig = isRecord(servers[SAP_SERVER_NAME])
605
+ || isRecord(servers[SAP_PAYMENT_BRIDGE_SERVER_NAME])
606
+ || Boolean(legacyServers && (isRecord(legacyServers[SAP_SERVER_NAME])
607
+ || isRecord(legacyServers[SAP_PAYMENT_BRIDGE_SERVER_NAME])));
608
+ servers[SAP_SERVER_NAME] = hostedJsonServerConfig({ id: 'openclaw' });
609
+ servers[SAP_PAYMENT_BRIDGE_SERVER_NAME] = paymentBridge;
610
+ mcp.servers = servers;
611
+ const nextRoot = {
612
+ ...root,
613
+ mcp,
614
+ };
615
+ if (legacyServers) {
616
+ delete legacyServers[SAP_SERVER_NAME];
617
+ delete legacyServers[SAP_PAYMENT_BRIDGE_SERVER_NAME];
618
+ if (Object.keys(legacyServers).length > 0) {
619
+ nextRoot.mcpServers = legacyServers;
620
+ }
621
+ else {
622
+ delete nextRoot.mcpServers;
623
+ }
624
+ }
625
+ return {
626
+ nextContent: formatJson(nextRoot),
627
+ hadSapConfig,
628
+ };
629
+ }
253
630
  /**
254
631
  * @name yamlServerBlock
255
- * @description Renders a Hermes/OpenClaw-compatible YAML MCP server block.
632
+ * @description Renders a Hermes-compatible YAML MCP server block.
256
633
  */
257
634
  function yamlServerBlock(canonical, baseIndent = 0) {
258
635
  const indent = ' '.repeat(baseIndent);
@@ -276,6 +653,45 @@ function yamlServerBlock(canonical, baseIndent = 0) {
276
653
  }
277
654
  return lines;
278
655
  }
656
+ /**
657
+ * @name yamlHostedServerBlock
658
+ * @description Renders a hosted Streamable HTTP MCP server block for Hermes/OpenClaw YAML configs.
659
+ */
660
+ function yamlHostedServerBlock(serverName, baseIndent = 0) {
661
+ const indent = ' '.repeat(baseIndent);
662
+ const child = ' '.repeat(baseIndent + 2);
663
+ return [
664
+ `${indent}${serverName}:`,
665
+ `${child}url: ${HOSTED_SAP_MCP_URL}`,
666
+ `${child}transport: streamable-http`,
667
+ ];
668
+ }
669
+ /**
670
+ * @name yamlCommandServerBlock
671
+ * @description Renders a command-backed MCP server block for Hermes/OpenClaw YAML configs.
672
+ */
673
+ function yamlCommandServerBlock(serverName, canonical, baseIndent = 0) {
674
+ const indent = ' '.repeat(baseIndent);
675
+ const child = ' '.repeat(baseIndent + 2);
676
+ const grandchild = ' '.repeat(baseIndent + 4);
677
+ const lines = [
678
+ `${indent}${serverName}:`,
679
+ `${child}command: ${canonical.command}`,
680
+ `${child}args:`,
681
+ ];
682
+ for (const arg of canonical.args) {
683
+ lines.push(`${child}- ${arg}`);
684
+ }
685
+ if (canonical.cwd) {
686
+ lines.push(`${child}cwd: ${canonical.cwd}`);
687
+ }
688
+ lines.push(`${child}env:`);
689
+ for (const [key, value] of Object.entries(canonical.env)) {
690
+ const rendered = value === 'false' || value === 'true' ? `"${value}"` : value;
691
+ lines.push(`${grandchild}${key}: ${rendered}`);
692
+ }
693
+ return lines;
694
+ }
279
695
  /**
280
696
  * @name countLeadingSpaces
281
697
  * @description Counts indentation spaces at the start of a YAML line.
@@ -322,6 +738,99 @@ function replaceYamlSapBlock(content, canonical) {
322
738
  hadSapConfig: true,
323
739
  };
324
740
  }
741
+ /**
742
+ * @name replaceYamlHostedPaymentBridgeBlocks
743
+ * @description Replaces or appends hosted SAP MCP plus local x402 bridge blocks under mcp_servers.
744
+ */
745
+ function replaceYamlHostedPaymentBridgeBlocks(content) {
746
+ const lines = content.split(/\r?\n/);
747
+ const paymentBridge = createNpxPaymentBridgeServerConfig();
748
+ const replacement = [
749
+ ...yamlHostedServerBlock(SAP_SERVER_NAME, 2),
750
+ ...yamlCommandServerBlock(SAP_PAYMENT_BRIDGE_SERVER_NAME, paymentBridge, 2),
751
+ ];
752
+ const mcpIndex = lines.findIndex((line) => /^mcp_servers:\s*$/.test(line));
753
+ if (mcpIndex === -1) {
754
+ const prefix = content.trimEnd();
755
+ const appended = ['mcp_servers:', ...replacement].join('\n');
756
+ return {
757
+ nextContent: `${prefix ? `${prefix}\n` : ''}${appended}\n`,
758
+ hadSapConfig: false,
759
+ };
760
+ }
761
+ const nextLines = [...lines];
762
+ let hadSapConfig = false;
763
+ for (const serverName of [SAP_SERVER_NAME, SAP_PAYMENT_BRIDGE_SERVER_NAME]) {
764
+ const serverIndex = nextLines.findIndex((line, index) => index > mcpIndex && new RegExp(`^ {2}${escapeRegExp(serverName)}:\\s*$`).test(line));
765
+ if (serverIndex === -1) {
766
+ continue;
767
+ }
768
+ hadSapConfig = true;
769
+ let endIndex = serverIndex + 1;
770
+ while (endIndex < nextLines.length) {
771
+ const line = nextLines[endIndex];
772
+ if (line.trim() && countLeadingSpaces(line) <= 2) {
773
+ break;
774
+ }
775
+ endIndex += 1;
776
+ }
777
+ nextLines.splice(serverIndex, endIndex - serverIndex);
778
+ }
779
+ nextLines.splice(mcpIndex + 1, 0, ...replacement);
780
+ return {
781
+ nextContent: `${nextLines.join('\n').replace(/\n*$/, '')}\n`,
782
+ hadSapConfig,
783
+ };
784
+ }
785
+ /**
786
+ * @name replaceOpenClawYamlHostedPaymentBridgeBlocks
787
+ * @description Replaces or appends hosted SAP MCP plus local x402 bridge blocks under OpenClaw `mcp.servers`.
788
+ */
789
+ function replaceOpenClawYamlHostedPaymentBridgeBlocks(content) {
790
+ const lines = content.split(/\r?\n/);
791
+ const paymentBridge = createNpxPaymentBridgeServerConfig();
792
+ const replacement = [
793
+ ...yamlHostedServerBlock(SAP_SERVER_NAME, 4),
794
+ ...yamlCommandServerBlock(SAP_PAYMENT_BRIDGE_SERVER_NAME, paymentBridge, 4),
795
+ ];
796
+ const mcpIndex = lines.findIndex((line) => /^mcp:\s*$/.test(line));
797
+ if (mcpIndex === -1) {
798
+ const prefix = content.trimEnd();
799
+ const appended = ['mcp:', ' servers:', ...replacement].join('\n');
800
+ return {
801
+ nextContent: `${prefix ? `${prefix}\n` : ''}${appended}\n`,
802
+ hadSapConfig: false,
803
+ };
804
+ }
805
+ const nextLines = [...lines];
806
+ let serversIndex = nextLines.findIndex((line, index) => index > mcpIndex && /^ {2}servers:\s*$/.test(line));
807
+ if (serversIndex === -1) {
808
+ serversIndex = mcpIndex + 1;
809
+ nextLines.splice(serversIndex, 0, ' servers:');
810
+ }
811
+ let hadSapConfig = false;
812
+ for (const serverName of [SAP_SERVER_NAME, SAP_PAYMENT_BRIDGE_SERVER_NAME]) {
813
+ const serverIndex = nextLines.findIndex((line, index) => index > serversIndex && new RegExp(`^ {4}${escapeRegExp(serverName)}:\\s*$`).test(line));
814
+ if (serverIndex === -1) {
815
+ continue;
816
+ }
817
+ hadSapConfig = true;
818
+ let endIndex = serverIndex + 1;
819
+ while (endIndex < nextLines.length) {
820
+ const line = nextLines[endIndex];
821
+ if (line.trim() && countLeadingSpaces(line) <= 4) {
822
+ break;
823
+ }
824
+ endIndex += 1;
825
+ }
826
+ nextLines.splice(serverIndex, endIndex - serverIndex);
827
+ }
828
+ nextLines.splice(serversIndex + 1, 0, ...replacement);
829
+ return {
830
+ nextContent: `${nextLines.join('\n').replace(/\n*$/, '')}\n`,
831
+ hadSapConfig,
832
+ };
833
+ }
325
834
  /**
326
835
  * @name tomlServerBlock
327
836
  * @description Renders a Codex-compatible TOML MCP server block.
@@ -343,6 +852,38 @@ function tomlServerBlock(canonical) {
343
852
  }
344
853
  return lines.join('\n');
345
854
  }
855
+ /**
856
+ * @name hostedTomlServerBlock
857
+ * @description Renders a Codex-compatible TOML block for a hosted Streamable HTTP MCP server.
858
+ */
859
+ function hostedTomlServerBlock(hosted) {
860
+ return [
861
+ `[mcp_servers.${SAP_SERVER_NAME}]`,
862
+ `url = ${JSON.stringify(hosted.url)}`,
863
+ '',
864
+ ].join('\n');
865
+ }
866
+ /**
867
+ * @name codexPaymentBridgeTomlBlock
868
+ * @description Renders the Codex TOML block for the local x402 payment bridge MCP server.
869
+ */
870
+ function codexPaymentBridgeTomlBlock(canonical) {
871
+ const args = `[${canonical.args.map((arg) => JSON.stringify(arg)).join(', ')}]`;
872
+ const enabledTools = `[${X402_PAYMENT_BRIDGE_TOOLS.map((tool) => JSON.stringify(tool)).join(', ')}]`;
873
+ const lines = [
874
+ `[mcp_servers.${SAP_PAYMENT_BRIDGE_SERVER_NAME}]`,
875
+ `command = ${JSON.stringify(canonical.command)}`,
876
+ `args = ${args}`,
877
+ `enabled_tools = ${enabledTools}`,
878
+ 'tool_timeout_sec = 300',
879
+ ];
880
+ lines.push('');
881
+ lines.push(`[mcp_servers.${SAP_PAYMENT_BRIDGE_SERVER_NAME}.env]`);
882
+ for (const [key, value] of Object.entries(canonical.env)) {
883
+ lines.push(`${key} = ${JSON.stringify(value)}`);
884
+ }
885
+ return lines.join('\n');
886
+ }
346
887
  /**
347
888
  * @name removeTomlSapSections
348
889
  * @description Removes existing Codex SAP MCP TOML sections before appending the canonical one.
@@ -370,6 +911,34 @@ function removeTomlSapSections(content) {
370
911
  hadSapConfig,
371
912
  };
372
913
  }
914
+ /**
915
+ * @name removeTomlMcpServerSections
916
+ * @description Removes named Codex MCP server TOML sections before appending canonical blocks.
917
+ */
918
+ function removeTomlMcpServerSections(content, serverNames) {
919
+ const lines = content.split(/\r?\n/);
920
+ const nextLines = [];
921
+ let skipping = false;
922
+ let hadConfig = false;
923
+ const escapedNames = serverNames.map(escapeRegExp).join('|');
924
+ const header = new RegExp(`^\\[mcp_servers\\.(${escapedNames})(?:\\.|\\])`);
925
+ for (const line of lines) {
926
+ if (/^\[/.test(line)) {
927
+ skipping = header.test(line);
928
+ if (skipping) {
929
+ hadConfig = true;
930
+ continue;
931
+ }
932
+ }
933
+ if (!skipping) {
934
+ nextLines.push(line);
935
+ }
936
+ }
937
+ return {
938
+ content: nextLines.join('\n').replace(/\n*$/, ''),
939
+ hadConfig,
940
+ };
941
+ }
373
942
  /**
374
943
  * @name buildTomlContent
375
944
  * @description Builds a merged TOML MCP client config.
@@ -382,6 +951,113 @@ function buildTomlContent(content, canonical) {
382
951
  hadSapConfig: stripped.hadSapConfig,
383
952
  };
384
953
  }
954
+ /**
955
+ * @name buildCodexHostedPaymentBridgeContent
956
+ * @description Builds Codex TOML with hosted SAP MCP plus a local sap_payments x402 bridge.
957
+ */
958
+ export function buildCodexHostedPaymentBridgeContent(content, hostedUrl = HOSTED_SAP_MCP_URL) {
959
+ const stripped = removeTomlMcpServerSections(content, [SAP_SERVER_NAME, SAP_PAYMENT_BRIDGE_SERVER_NAME]);
960
+ const hosted = {
961
+ url: hostedUrl,
962
+ transport: 'streamable-http',
963
+ };
964
+ const prefix = stripped.content.trimEnd();
965
+ const nextContent = [
966
+ prefix,
967
+ hostedTomlServerBlock(hosted).trimEnd(),
968
+ codexPaymentBridgeTomlBlock(createNpxPaymentBridgeServerConfig()).trimEnd(),
969
+ ].filter(Boolean).join('\n\n');
970
+ return {
971
+ nextContent: `${nextContent}\n`,
972
+ hadSapConfig: stripped.hadConfig,
973
+ };
974
+ }
975
+ /**
976
+ * @name installCodexHostedPaymentBridgeConfig
977
+ * @description Writes Codex config.toml with hosted SAP MCP and the local x402 payment bridge.
978
+ */
979
+ export function installCodexHostedPaymentBridgeConfig(homeDir = homedir()) {
980
+ const target = {
981
+ id: 'codex',
982
+ label: 'Codex',
983
+ path: join(homeDir, '.codex', 'config.toml'),
984
+ format: 'toml',
985
+ exists: existsSync(join(homeDir, '.codex', 'config.toml')),
986
+ };
987
+ const before = readTargetContent(target);
988
+ const built = buildCodexHostedPaymentBridgeContent(before);
989
+ const backupPath = target.exists ? `${target.path}.bak-${Date.now()}` : undefined;
990
+ mkdirSync(dirname(target.path), { recursive: true });
991
+ if (backupPath) {
992
+ writeFileSync(backupPath, before, 'utf-8');
993
+ }
994
+ writeFileSync(target.path, built.nextContent, 'utf-8');
995
+ return {
996
+ target,
997
+ mode: built.hadSapConfig ? 'override' : 'merge',
998
+ written: true,
999
+ backupPath,
1000
+ message: built.hadSapConfig
1001
+ ? 'Updated Codex hosted SAP MCP and local x402 payment bridge config'
1002
+ : 'Created Codex hosted SAP MCP and local x402 payment bridge config',
1003
+ };
1004
+ }
1005
+ /**
1006
+ * @name buildHostedPaymentBridgeContent
1007
+ * @description Builds runtime-specific config with hosted SAP MCP and local x402 bridge.
1008
+ */
1009
+ export function buildHostedPaymentBridgeContent(target, content) {
1010
+ if (target.format === 'toml') {
1011
+ return buildCodexHostedPaymentBridgeContent(content);
1012
+ }
1013
+ if (target.format === 'json') {
1014
+ return buildHostedPaymentBridgeJsonContent(content, target);
1015
+ }
1016
+ if (target.id === 'openclaw') {
1017
+ return replaceOpenClawYamlHostedPaymentBridgeBlocks(content);
1018
+ }
1019
+ return replaceYamlHostedPaymentBridgeBlocks(content);
1020
+ }
1021
+ /**
1022
+ * @name installHostedPaymentBridgeConfig
1023
+ * @description Installs or repairs hosted SAP MCP plus local x402 bridge config for a concrete runtime target.
1024
+ */
1025
+ export function installHostedPaymentBridgeConfig(target) {
1026
+ if (target.id === 'codex') {
1027
+ return installCodexHostedPaymentBridgeConfig(dirname(dirname(target.path)));
1028
+ }
1029
+ const currentTarget = {
1030
+ ...target,
1031
+ exists: existsSync(target.path),
1032
+ };
1033
+ const before = readTargetContent(currentTarget);
1034
+ const built = buildHostedPaymentBridgeContent(currentTarget, before);
1035
+ const backupPath = currentTarget.exists ? `${currentTarget.path}.bak-${Date.now()}` : undefined;
1036
+ mkdirSync(dirname(currentTarget.path), { recursive: true, mode: 0o700 });
1037
+ if (backupPath) {
1038
+ writeFileSync(backupPath, before, 'utf-8');
1039
+ }
1040
+ writeFileSync(currentTarget.path, built.nextContent, { encoding: 'utf-8', mode: 0o600 });
1041
+ return {
1042
+ target: currentTarget,
1043
+ mode: built.hadSapConfig ? 'override' : 'merge',
1044
+ written: true,
1045
+ backupPath,
1046
+ message: built.hadSapConfig
1047
+ ? `Repaired hosted SAP MCP and local x402 payment bridge config in ${currentTarget.label}`
1048
+ : `Installed hosted SAP MCP and local x402 payment bridge config in ${currentTarget.label}`,
1049
+ };
1050
+ }
1051
+ /**
1052
+ * @name installHostedPaymentBridgeConfigs
1053
+ * @description Installs payment bridge configs for all selected runtime ids and detected/create-capable targets.
1054
+ */
1055
+ export function installHostedPaymentBridgeConfigs(runtimeIds, homeDir = homedir(), platform = process.platform) {
1056
+ const selected = new Set(runtimeIds);
1057
+ const targets = discoverMcpClientTargets(homeDir, platform)
1058
+ .filter((target) => selected.has(target.id));
1059
+ return targets.map((target) => installHostedPaymentBridgeConfig(target));
1060
+ }
385
1061
  /**
386
1062
  * @name summarizeContent
387
1063
  * @description Produces a short safe summary for wizard preview output.