@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.
- package/.env.example +3 -0
- package/CHANGELOG.md +238 -1
- package/README.md +64 -1
- package/USER_DOCS/00_START_HERE.md +23 -1
- package/USER_DOCS/01_HOSTED_REMOTE_MCP.md +55 -0
- package/USER_DOCS/03_PAYMENTS_X402_PAYSH.md +63 -7
- package/USER_DOCS/04_CLIENT_CONFIGS.md +164 -11
- package/USER_DOCS/06_DESKTOP_GUI_WIZARD.md +262 -0
- package/dist/adapters/mcp/sdk-compat.d.ts.map +1 -1
- package/dist/adapters/mcp/sdk-compat.js +32 -18
- package/dist/adapters/mcp/sdk-compat.js.map +1 -1
- package/dist/config/mcp-client-injection.d.ts +72 -2
- package/dist/config/mcp-client-injection.d.ts.map +1 -1
- package/dist/config/mcp-client-injection.js +691 -15
- package/dist/config/mcp-client-injection.js.map +1 -1
- package/dist/config/setup.js +1 -1
- package/dist/config/setup.js.map +1 -1
- package/dist/config/wizard.d.ts.map +1 -1
- package/dist/config/wizard.js +164 -85
- package/dist/config/wizard.js.map +1 -1
- package/dist/core/constants.d.ts +1 -1
- package/dist/core/constants.js +1 -1
- package/dist/core/logger.js +1 -1
- package/dist/payments/facilitator-rpc-fallback.d.ts +43 -0
- package/dist/payments/facilitator-rpc-fallback.d.ts.map +1 -0
- package/dist/payments/facilitator-rpc-fallback.js +228 -0
- package/dist/payments/facilitator-rpc-fallback.js.map +1 -0
- package/dist/payments/json-rpc.d.ts.map +1 -1
- package/dist/payments/json-rpc.js +2 -1
- package/dist/payments/json-rpc.js.map +1 -1
- package/dist/payments/monetization-gate.d.ts +43 -2
- package/dist/payments/monetization-gate.d.ts.map +1 -1
- package/dist/payments/monetization-gate.js +618 -9
- package/dist/payments/monetization-gate.js.map +1 -1
- package/dist/payments/oobe-facilitator-server.d.ts +12 -1
- package/dist/payments/oobe-facilitator-server.d.ts.map +1 -1
- package/dist/payments/oobe-facilitator-server.js +109 -8
- package/dist/payments/oobe-facilitator-server.js.map +1 -1
- package/dist/payments/pay-sh-spec.js +1 -1
- package/dist/payments/pricing.d.ts.map +1 -1
- package/dist/payments/pricing.js +1 -0
- package/dist/payments/pricing.js.map +1 -1
- package/dist/payments/usage-ledger.d.ts +10 -0
- package/dist/payments/usage-ledger.d.ts.map +1 -1
- package/dist/payments/usage-ledger.js +51 -0
- package/dist/payments/usage-ledger.js.map +1 -1
- package/dist/payments/x402-paid-call.d.ts +53 -0
- package/dist/payments/x402-paid-call.d.ts.map +1 -0
- package/dist/payments/x402-paid-call.js +370 -0
- package/dist/payments/x402-paid-call.js.map +1 -0
- package/dist/prompts/context/sap-agent-context.prompt.js +33 -6
- package/dist/prompts/context/sap-agent-context.prompt.js.map +1 -1
- package/dist/prompts/developer/debug-sap-error.prompt.js +1 -1
- package/dist/prompts/developer/debug-sap-error.prompt.js.map +1 -1
- package/dist/prompts/developer/generate-sap-integration.prompt.js +6 -2
- package/dist/prompts/developer/generate-sap-integration.prompt.js.map +1 -1
- package/dist/prompts/execution-proof/explain-proof-of-execution.prompt.js +1 -1
- package/dist/prompts/execution-proof/explain-proof-of-execution.prompt.js.map +1 -1
- package/dist/prompts/execution-proof/verify-execution-proof.prompt.js +1 -1
- package/dist/prompts/execution-proof/verify-execution-proof.prompt.js.map +1 -1
- package/dist/prompts/payments/create-paid-api.prompt.js +1 -1
- package/dist/prompts/payments/create-paid-api.prompt.js.map +1 -1
- package/dist/prompts/payments/explain-x402-settlement.prompt.js +1 -1
- package/dist/prompts/payments/explain-x402-settlement.prompt.js.map +1 -1
- package/dist/prompts/register-prompts.js +2 -2
- package/dist/prompts/register-prompts.js.map +1 -1
- package/dist/prompts/registry/analyze-sap-agent.prompt.js +1 -1
- package/dist/prompts/registry/analyze-sap-agent.prompt.js.map +1 -1
- package/dist/prompts/registry/register-sap-agent.prompt.js +2 -2
- package/dist/prompts/registry/register-sap-agent.prompt.js.map +1 -1
- package/dist/remote/server.d.ts +150 -1
- package/dist/remote/server.d.ts.map +1 -1
- package/dist/remote/server.js +1176 -84
- package/dist/remote/server.js.map +1 -1
- package/dist/resources/current/sap-current-config.resource.d.ts.map +1 -1
- package/dist/resources/current/sap-current-config.resource.js +22 -2
- package/dist/resources/current/sap-current-config.resource.js.map +1 -1
- package/dist/resources/execution-proof/sap-execution-record.resource.js +1 -1
- package/dist/resources/execution-proof/sap-execution-record.resource.js.map +1 -1
- package/dist/resources/memory/sap-memory.resource.js +1 -1
- package/dist/resources/memory/sap-memory.resource.js.map +1 -1
- package/dist/resources/profile/sap-active-profile.resource.js +1 -1
- package/dist/resources/profile/sap-active-profile.resource.js.map +1 -1
- package/dist/resources/register-resources.js +2 -2
- package/dist/resources/register-resources.js.map +1 -1
- package/dist/resources/registry/sap-agent.resource.js +1 -1
- package/dist/resources/registry/sap-agent.resource.js.map +1 -1
- package/dist/resources/registry/sap-global-registry.resource.js +1 -1
- package/dist/resources/registry/sap-global-registry.resource.js.map +1 -1
- package/dist/resources/reputation/sap-reputation.resource.js +1 -1
- package/dist/resources/reputation/sap-reputation.resource.js.map +1 -1
- package/dist/resources/stats/sap-network-stats.resource.js +1 -1
- package/dist/resources/stats/sap-network-stats.resource.js.map +1 -1
- package/dist/resources/tool-schema/sap-tool-schema.resource.js +1 -1
- package/dist/resources/tool-schema/sap-tool-schema.resource.js.map +1 -1
- package/dist/sap/sap-client-manager.js +6 -6
- package/dist/sap/sap-client-manager.js.map +1 -1
- package/dist/security/tool-permissions.d.ts.map +1 -1
- package/dist/security/tool-permissions.js +1 -0
- package/dist/security/tool-permissions.js.map +1 -1
- package/dist/server/create-server.js +5 -5
- package/dist/server/create-server.js.map +1 -1
- package/dist/server/register-capabilities.js +4 -4
- package/dist/server/register-capabilities.js.map +1 -1
- package/dist/server/server-metadata.js +1 -1
- package/dist/signer/signer-resolver.js +12 -5
- package/dist/signer/signer-resolver.js.map +1 -1
- package/dist/tools/client-sdk-tools.js +7 -7
- package/dist/tools/client-sdk-tools.js.map +1 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/magicblock-tools.d.ts +28 -0
- package/dist/tools/magicblock-tools.d.ts.map +1 -0
- package/dist/tools/magicblock-tools.js +578 -0
- package/dist/tools/magicblock-tools.js.map +1 -0
- package/dist/tools/profile-tools.d.ts.map +1 -1
- package/dist/tools/profile-tools.js +44 -0
- package/dist/tools/profile-tools.js.map +1 -1
- package/dist/tools/register-tools.d.ts.map +1 -1
- package/dist/tools/register-tools.js +11 -2
- package/dist/tools/register-tools.js.map +1 -1
- package/dist/tools/sap-sdk-tools.js +2 -2
- package/dist/tools/sap-sdk-tools.js.map +1 -1
- package/dist/tools/sap-sns-tools.js +2 -2
- package/dist/tools/sap-sns-tools.js.map +1 -1
- package/dist/tools/skills-tools.d.ts +1 -1
- package/dist/tools/skills-tools.d.ts.map +1 -1
- package/dist/tools/skills-tools.js +10 -1
- package/dist/tools/skills-tools.js.map +1 -1
- package/dist/tools/tool-aliases.d.ts +10 -0
- package/dist/tools/tool-aliases.d.ts.map +1 -0
- package/dist/tools/tool-aliases.js +23 -0
- package/dist/tools/tool-aliases.js.map +1 -0
- package/dist/tools/x402-paid-call-tool.d.ts +12 -0
- package/dist/tools/x402-paid-call-tool.d.ts.map +1 -0
- package/dist/tools/x402-paid-call-tool.js +118 -0
- package/dist/tools/x402-paid-call-tool.js.map +1 -0
- package/dist/tui/config-wizard.js +2 -2
- package/dist/tui/config-wizard.js.map +1 -1
- package/dist/wizard-core/desktop-flow.d.ts +82 -0
- package/dist/wizard-core/desktop-flow.d.ts.map +1 -0
- package/dist/wizard-core/desktop-flow.js +181 -0
- package/dist/wizard-core/desktop-flow.js.map +1 -0
- package/docs/00_README.md +3 -0
- package/docs/03_CONFIGURATION_AND_WIZARD.md +31 -5
- package/docs/06_PAYMENTS_X402_AND_PAYSH.md +70 -7
- package/docs/07_ENDPOINTS_AND_CLIENTS.md +56 -1
- package/docs/10_OPERATIONS_RELEASE_AND_PM2.md +7 -6
- package/docs/13_BOUNTY_PROGRAM_PROPOSAL.md +469 -0
- package/docs/14_DESKTOP_WIZARD_RELEASE.md +264 -0
- package/docs/README.md +104 -0
- package/docs/_sidebar.md +31 -0
- package/docs/magicblock-tools.md +166 -0
- package/docs/x402-protocol-spec.md +984 -0
- package/package.json +23 -2
- package/server.json +71 -0
- package/skills/README.md +3 -2
- package/skills/sap-defi/SKILL.md +8 -0
- package/skills/sap-mcp/SKILL.md +124 -2
- 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
|
|
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
|
|
110
|
-
* @description
|
|
168
|
+
* @name createNpxCodexServerConfig
|
|
169
|
+
* @description Builds a portable Codex stdio config that works without a source checkout.
|
|
111
170
|
*/
|
|
112
|
-
export function
|
|
113
|
-
const
|
|
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(
|
|
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(
|
|
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
|
|
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.
|