@oobe-protocol-labs/sap-mcp-server 0.7.3 → 0.8.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 (90) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/README.md +14 -7
  3. package/USER_DOCS/00_START_HERE.md +45 -7
  4. package/USER_DOCS/01_HOSTED_REMOTE_MCP.md +2 -0
  5. package/USER_DOCS/03_PAYMENTS_X402_PAYSH.md +13 -5
  6. package/USER_DOCS/04_CLIENT_CONFIGS.md +25 -15
  7. package/USER_DOCS/06_DESKTOP_GUI_WIZARD.md +36 -21
  8. package/assets/logos/drift.svg +12 -0
  9. package/assets/logos/jupiter.ico +0 -0
  10. package/assets/logos/mcp.svg +11 -0
  11. package/assets/logos/meteora.png +0 -0
  12. package/assets/logos/orca.ico +0 -0
  13. package/assets/logos/raydium.ico +0 -0
  14. package/assets/logos/smithery.svg +11 -0
  15. package/assets/oobe-logo.png +0 -0
  16. package/dist/config/mcp-client-injection.d.ts +26 -7
  17. package/dist/config/mcp-client-injection.d.ts.map +1 -1
  18. package/dist/config/mcp-client-injection.js +182 -58
  19. package/dist/config/mcp-client-injection.js.map +1 -1
  20. package/dist/config/wizard.d.ts.map +1 -1
  21. package/dist/config/wizard.js +92 -57
  22. package/dist/config/wizard.js.map +1 -1
  23. package/dist/core/constants.d.ts +1 -1
  24. package/dist/core/constants.js +1 -1
  25. package/dist/payments/pricing.d.ts.map +1 -1
  26. package/dist/payments/pricing.js +11 -0
  27. package/dist/payments/pricing.js.map +1 -1
  28. package/dist/payments/x402-paid-call.d.ts +85 -1
  29. package/dist/payments/x402-paid-call.d.ts.map +1 -1
  30. package/dist/payments/x402-paid-call.js +168 -4
  31. package/dist/payments/x402-paid-call.js.map +1 -1
  32. package/dist/prompts/context/sap-agent-context.prompt.js +6 -3
  33. package/dist/prompts/context/sap-agent-context.prompt.js.map +1 -1
  34. package/dist/remote/public-home/escape.d.ts +11 -0
  35. package/dist/remote/public-home/escape.d.ts.map +1 -0
  36. package/dist/remote/public-home/escape.js +20 -0
  37. package/dist/remote/public-home/escape.js.map +1 -0
  38. package/dist/remote/public-home/index.d.ts +8 -0
  39. package/dist/remote/public-home/index.d.ts.map +1 -0
  40. package/dist/remote/public-home/index.js +57 -0
  41. package/dist/remote/public-home/index.js.map +1 -0
  42. package/dist/remote/public-home/scripts.d.ts +6 -0
  43. package/dist/remote/public-home/scripts.d.ts.map +1 -0
  44. package/dist/remote/public-home/scripts.js +106 -0
  45. package/dist/remote/public-home/scripts.js.map +1 -0
  46. package/dist/remote/public-home/sections.d.ts +67 -0
  47. package/dist/remote/public-home/sections.d.ts.map +1 -0
  48. package/dist/remote/public-home/sections.js +727 -0
  49. package/dist/remote/public-home/sections.js.map +1 -0
  50. package/dist/remote/public-home/styles.d.ts +6 -0
  51. package/dist/remote/public-home/styles.d.ts.map +1 -0
  52. package/dist/remote/public-home/styles.js +1534 -0
  53. package/dist/remote/public-home/styles.js.map +1 -0
  54. package/dist/remote/public-home/types.d.ts +95 -0
  55. package/dist/remote/public-home/types.d.ts.map +1 -0
  56. package/dist/remote/public-home/types.js +2 -0
  57. package/dist/remote/public-home/types.js.map +1 -0
  58. package/dist/remote/server.d.ts +41 -1
  59. package/dist/remote/server.d.ts.map +1 -1
  60. package/dist/remote/server.js +109 -292
  61. package/dist/remote/server.js.map +1 -1
  62. package/dist/security/tool-permissions.d.ts.map +1 -1
  63. package/dist/security/tool-permissions.js +5 -0
  64. package/dist/security/tool-permissions.js.map +1 -1
  65. package/dist/tools/magicblock-tools.js +6 -6
  66. package/dist/tools/magicblock-tools.js.map +1 -1
  67. package/dist/tools/profile-tools.d.ts.map +1 -1
  68. package/dist/tools/profile-tools.js +136 -33
  69. package/dist/tools/profile-tools.js.map +1 -1
  70. package/dist/tools/register-tools.d.ts.map +1 -1
  71. package/dist/tools/register-tools.js +5 -0
  72. package/dist/tools/register-tools.js.map +1 -1
  73. package/dist/tools/x402-paid-call-tool.d.ts +1 -1
  74. package/dist/tools/x402-paid-call-tool.d.ts.map +1 -1
  75. package/dist/tools/x402-paid-call-tool.js +314 -28
  76. package/dist/tools/x402-paid-call-tool.js.map +1 -1
  77. package/dist/wizard-core/desktop-flow.d.ts +40 -1
  78. package/dist/wizard-core/desktop-flow.d.ts.map +1 -1
  79. package/dist/wizard-core/desktop-flow.js +206 -15
  80. package/dist/wizard-core/desktop-flow.js.map +1 -1
  81. package/docs/03_CONFIGURATION_AND_WIZARD.md +6 -2
  82. package/docs/06_PAYMENTS_X402_AND_PAYSH.md +29 -7
  83. package/docs/07_ENDPOINTS_AND_CLIENTS.md +1 -1
  84. package/docs/14_DESKTOP_WIZARD_RELEASE.md +24 -5
  85. package/docs/15_DASHBOARD_SCREENSHARE_SCRIPT.md +137 -0
  86. package/docs/README.md +23 -2
  87. package/package.json +1 -1
  88. package/server.json +3 -3
  89. package/skills/sap-mcp/SKILL.md +23 -7
  90. package/skills/sap-payments-x402/SKILL.md +37 -13
@@ -17,9 +17,12 @@ import { AuthManager } from './auth/index.js';
17
17
  import { McpMonetizationGate, resolvePaymentNetwork } from '../payments/index.js';
18
18
  import { generatePayShProviderYaml } from '../payments/pay-sh-spec.js';
19
19
  import { RemoteRateLimiter, buildRemoteRateLimitConfigFromEnv } from './rate-limiter.js';
20
+ import { renderLandingPage } from './public-home/index.js';
20
21
  const PUBLIC_SERVER_TITLE = 'SAP MCP Server | OOBE Protocol';
21
22
  const PUBLIC_SERVER_DESCRIPTION = 'Hosted Solana-native MCP gateway for Synapse Agent Protocol tools, x402/pay.sh monetization, SNS identity, and agent operations.';
22
23
  const LOGO_ASSET_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'assets', 'explorer_logo.png');
24
+ const OOBE_LOGO_ASSET_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'assets', 'oobe-logo.png');
25
+ const LOGO_ASSET_ROOT_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'assets', 'logos');
23
26
  const DOCS_ROOT_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'docs');
24
27
  const USER_DOCS_ROOT_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'USER_DOCS');
25
28
  const PAYMENT_STATS_CACHE_MS = 15_000;
@@ -33,8 +36,18 @@ const USDC_MAINNET_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
33
36
  const USDC_DEVNET_MINT = '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU';
34
37
  const MCP_REGISTRY_AUTH_PATH = '/.well-known/mcp-registry-auth';
35
38
  let logoAssetCache;
39
+ let oobeLogoAssetCache;
36
40
  let paymentStatsCache;
37
41
  let serverCardCapabilitiesCache;
42
+ const PUBLIC_LOGO_ASSETS = {
43
+ '/logos/drift.svg': { filename: 'drift.svg', contentType: 'image/svg+xml' },
44
+ '/logos/jupiter.ico': { filename: 'jupiter.ico', contentType: 'image/x-icon' },
45
+ '/logos/mcp.svg': { filename: 'mcp.svg', contentType: 'image/svg+xml' },
46
+ '/logos/meteora.png': { filename: 'meteora.png', contentType: 'image/png' },
47
+ '/logos/orca.ico': { filename: 'orca.ico', contentType: 'image/x-icon' },
48
+ '/logos/raydium.ico': { filename: 'raydium.ico', contentType: 'image/x-icon' },
49
+ '/logos/smithery.svg': { filename: 'smithery.svg', contentType: 'image/svg+xml' },
50
+ };
38
51
  /**
39
52
  * @name parseApiKeys
40
53
  * @description Parses comma-separated `apiKey=userId` entries from environment configuration.
@@ -376,6 +389,23 @@ export function resolvePublicLogoAsset(method, pathname) {
376
389
  if (method !== 'GET' && method !== 'HEAD') {
377
390
  return undefined;
378
391
  }
392
+ const logoAsset = PUBLIC_LOGO_ASSETS[pathname];
393
+ if (logoAsset !== undefined) {
394
+ const image = readFileSync(join(LOGO_ASSET_ROOT_PATH, logoAsset.filename));
395
+ return {
396
+ contentType: logoAsset.contentType,
397
+ contentLength: image.byteLength,
398
+ body: method === 'HEAD' ? undefined : image,
399
+ };
400
+ }
401
+ if (pathname === '/oobe-logo.png') {
402
+ const image = readOobeLogoAsset();
403
+ return {
404
+ contentType: 'image/png',
405
+ contentLength: image.byteLength,
406
+ body: method === 'HEAD' ? undefined : image,
407
+ };
408
+ }
379
409
  const image = readLogoAsset();
380
410
  const body = method === 'HEAD' ? undefined : image;
381
411
  if (['/favicon.png', '/apple-touch-icon.png', '/og.png'].includes(pathname)) {
@@ -415,6 +445,14 @@ function readLogoAsset() {
415
445
  logoAssetCache ??= readFileSync(LOGO_ASSET_PATH);
416
446
  return logoAssetCache;
417
447
  }
448
+ /**
449
+ * @name readOobeLogoAsset
450
+ * @description Loads the bundled OOBE mark used by the public navigation link.
451
+ */
452
+ function readOobeLogoAsset() {
453
+ oobeLogoAssetCache ??= readFileSync(OOBE_LOGO_ASSET_PATH);
454
+ return oobeLogoAssetCache;
455
+ }
418
456
  /**
419
457
  * @name readPublicPaymentStats
420
458
  * @description Reads cached public-safe payment totals from the append-only usage ledger.
@@ -530,11 +568,31 @@ function buildPublicBaseUrl(req, config) {
530
568
  export function buildPublicServerInfo(req, config) {
531
569
  const baseUrl = buildPublicBaseUrl(req, config);
532
570
  const authRequired = config.auth.type !== 'none';
571
+ const homepage = `${baseUrl}/`;
572
+ const iconUrl = `${baseUrl}/favicon.png`;
573
+ const icons = [
574
+ {
575
+ src: iconUrl,
576
+ mimeType: 'image/png',
577
+ sizes: ['512x512'],
578
+ },
579
+ ];
533
580
  return {
534
581
  name: 'sap-mcp-server',
535
582
  title: PUBLIC_SERVER_TITLE,
583
+ displayName: PUBLIC_SERVER_TITLE,
584
+ display_name: PUBLIC_SERVER_TITLE,
536
585
  description: PUBLIC_SERVER_DESCRIPTION,
537
586
  version: MCP_SERVER_VERSION,
587
+ homepage,
588
+ websiteUrl: homepage,
589
+ website_url: homepage,
590
+ icon: iconUrl,
591
+ iconUrl,
592
+ icon_url: iconUrl,
593
+ image: iconUrl,
594
+ logo: iconUrl,
595
+ icons,
538
596
  status: 'online',
539
597
  protocol: {
540
598
  primary: 'mcp',
@@ -623,23 +681,47 @@ export async function buildStaticServerCard(req, config, appConfig) {
623
681
  const baseUrl = buildPublicBaseUrl(req, config);
624
682
  const capabilities = await getStaticServerCardCapabilities(appConfig);
625
683
  const authRequired = config.auth.type !== 'none';
684
+ const homepage = `${baseUrl}/`;
685
+ const iconUrl = `${baseUrl}/favicon.png`;
686
+ const icons = [
687
+ {
688
+ src: iconUrl,
689
+ mimeType: 'image/png',
690
+ sizes: ['512x512'],
691
+ },
692
+ ];
626
693
  return {
694
+ name: 'sap-mcp-server',
695
+ displayName: PUBLIC_SERVER_TITLE,
696
+ display_name: PUBLIC_SERVER_TITLE,
697
+ title: PUBLIC_SERVER_TITLE,
698
+ version: MCP_SERVER_VERSION,
699
+ description: PUBLIC_SERVER_DESCRIPTION,
700
+ homepage,
701
+ websiteUrl: homepage,
702
+ website_url: homepage,
703
+ icon: iconUrl,
704
+ iconUrl,
705
+ icon_url: iconUrl,
706
+ image: iconUrl,
707
+ logo: iconUrl,
708
+ icons,
627
709
  serverInfo: {
628
710
  name: 'sap-mcp-server',
629
711
  displayName: PUBLIC_SERVER_TITLE,
712
+ display_name: PUBLIC_SERVER_TITLE,
630
713
  title: PUBLIC_SERVER_TITLE,
631
714
  version: MCP_SERVER_VERSION,
632
715
  description: PUBLIC_SERVER_DESCRIPTION,
633
- homepage: `${baseUrl}/`,
634
- websiteUrl: `${baseUrl}/`,
635
- icon: `${baseUrl}/favicon.png`,
636
- icons: [
637
- {
638
- src: `${baseUrl}/favicon.png`,
639
- mimeType: 'image/png',
640
- sizes: ['512x512'],
641
- },
642
- ],
716
+ homepage,
717
+ websiteUrl: homepage,
718
+ website_url: homepage,
719
+ icon: iconUrl,
720
+ iconUrl,
721
+ icon_url: iconUrl,
722
+ image: iconUrl,
723
+ logo: iconUrl,
724
+ icons,
643
725
  },
644
726
  authentication: {
645
727
  required: authRequired,
@@ -684,7 +766,7 @@ export function buildOpenApiSpec(req, config) {
684
766
  title: 'SAP MCP Server',
685
767
  version: MCP_SERVER_VERSION,
686
768
  description: 'MCP gateway for Solana DeFi, SAP agent registry, and x402 micropayments. 248 tools across Jupiter, Drift, Metaplex, Pump.fun, Raydium, Orca, and more.',
687
- 'x-guidance': 'Connect to POST /mcp with Accept: application/json, text/event-stream. Initialize an MCP session first, then call tools. Paid tools return a payment_required JSON-RPC error (HTTP 200 for MCP SDK compatibility) or HTTP 402 for non-MCP clients. Use x402_paid_call or npx sap-mcp-x402-paid-call to self-pay and retry.',
769
+ 'x-guidance': 'Connect to POST /mcp with Accept: application/json, text/event-stream. Initialize an MCP session first, then call tools. Paid tools return a payment_required JSON-RPC error (HTTP 200 for MCP SDK compatibility) or HTTP 402 for non-MCP clients. Use the local sap_payments_call_paid_tool bridge or npx sap-mcp-x402-paid-call to self-pay and retry.',
688
770
  contact: {
689
771
  name: 'OOBE Protocol Labs',
690
772
  url: 'https://github.com/OOBE-PROTOCOL/sap-mcp',
@@ -755,7 +837,11 @@ export function buildOpenApiSpec(req, config) {
755
837
  description: 'MCP method.',
756
838
  examples: ['initialize', 'tools/list', 'prompts/list', 'resources/list', 'ping'],
757
839
  },
758
- params: { type: 'object', additionalProperties: true },
840
+ params: {
841
+ type: 'object',
842
+ description: 'JSON-RPC parameters for the requested MCP method. Use an empty object when the method has no parameters.',
843
+ additionalProperties: true,
844
+ },
759
845
  },
760
846
  required: ['jsonrpc', 'id', 'method'],
761
847
  },
@@ -894,262 +980,17 @@ export function buildLandingHtml(req, config, endpoint = 'root') {
894
980
  const title = endpoint === 'mcp'
895
981
  ? 'SAP MCP Streamable HTTP Endpoint | OOBE Protocol'
896
982
  : info.title;
897
- const endpointLabel = endpoint === 'mcp' ? 'Streamable HTTP MCP endpoint' : 'Hosted MCP gateway';
898
- const wizardCommand = WIZARD_NPM_COMMAND;
899
- const installScriptCommand = 'curl -fsSL https://mcp.sap.oobeprotocol.ai/wizard/install.sh | sh';
900
- const x402PaidCallCommand = X402_PAID_CALL_NPX_COMMAND;
901
- const publicInfo = JSON.stringify(info, null, 2).replace(/</g, '\\u003c');
902
- return `<!doctype html>
903
- <html lang="en">
904
- <head>
905
- <meta charset="utf-8">
906
- <meta name="viewport" content="width=device-width, initial-scale=1">
907
- <title>${escapeHtml(title)}</title>
908
- <meta name="description" content="${escapeHtml(info.description)}">
909
- <meta name="robots" content="index,follow">
910
- <link rel="canonical" href="${escapeHtml(pageUrl)}">
911
- <link rel="icon" type="image/x-icon" href="/favicon.ico">
912
- <link rel="icon" type="image/png" href="/favicon.png">
913
- <link rel="shortcut icon" href="/favicon.ico">
914
- <link rel="apple-touch-icon" href="/apple-touch-icon.png">
915
- <meta property="og:type" content="website">
916
- <meta property="og:title" content="${escapeHtml(title)}">
917
- <meta property="og:description" content="${escapeHtml(info.description)}">
918
- <meta property="og:url" content="${escapeHtml(pageUrl)}">
919
- <meta property="og:image" content="${escapeHtml(info.endpoints.favicon)}">
920
- <meta name="twitter:card" content="summary">
921
- <meta name="twitter:title" content="${escapeHtml(title)}">
922
- <meta name="twitter:description" content="${escapeHtml(info.description)}">
923
- <meta name="twitter:image" content="${escapeHtml(info.endpoints.favicon)}">
924
- <script type="application/ld+json">${publicInfo}</script>
925
- <style>
926
- :root {
927
- color-scheme: dark;
928
- --aqua: #28d8e8;
929
- --aqua-soft: rgba(40,216,232,.13);
930
- --ink: #f4fbff;
931
- --muted: #a8bdc6;
932
- --subtle: #76909a;
933
- --panel: rgba(255,255,255,.062);
934
- --panel-strong: rgba(255,255,255,.095);
935
- --line: rgba(255,255,255,.14);
936
- --line-strong: rgba(40,216,232,.38);
937
- --surface: #080d12;
938
- --success: #8ef3c5;
939
- --warning: #ffd38a;
940
- }
941
- * { box-sizing: border-box; }
942
- html { scroll-behavior: smooth; }
943
- body {
944
- margin: 0;
945
- min-height: 100vh;
946
- font: 16px/1.55 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
947
- color: var(--ink);
948
- background:
949
- linear-gradient(115deg, rgba(40,216,232,.13), transparent 34%),
950
- linear-gradient(180deg, #0a1218 0%, var(--surface) 46%, #05080b 100%);
951
- }
952
- body::before {
953
- content: "";
954
- position: fixed;
955
- inset: 0;
956
- pointer-events: none;
957
- background-image: linear-gradient(rgba(255,255,255,.035) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px);
958
- background-size: 64px 64px;
959
- mask-image: linear-gradient(to bottom, rgba(0,0,0,.75), transparent 72%);
960
- }
961
- main { width: min(1180px, calc(100% - 32px)); margin: 0 auto; padding: 56px 0 40px; position: relative; }
962
- header { display: grid; grid-template-columns: 92px 1fr; gap: 24px; align-items: center; margin-bottom: 30px; }
963
- img.logo { width: 92px; height: 92px; border-radius: 24px; box-shadow: 0 18px 70px rgba(40,216,232,.22); }
964
- h1 { margin: 0; max-width: 920px; font-size: clamp(2.2rem, 5vw, 5rem); line-height: .95; letter-spacing: 0; }
965
- p { margin: 10px 0 0; color: var(--muted); max-width: 760px; }
966
- a { color: var(--aqua); text-underline-offset: 4px; }
967
- a:focus-visible { outline: 2px solid var(--aqua); outline-offset: 3px; border-radius: 6px; }
968
- code {
969
- color: #e9fcff;
970
- overflow-wrap: anywhere;
971
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
972
- font-size: .92em;
973
- }
974
- .badge { display: inline-flex; align-items: center; gap: 8px; color: var(--aqua); font-weight: 750; text-transform: uppercase; font-size: .78rem; letter-spacing: 0; }
975
- .hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 22px; }
976
- .button {
977
- display: inline-flex;
978
- min-height: 42px;
979
- align-items: center;
980
- justify-content: center;
981
- border: 1px solid var(--line-strong);
982
- border-radius: 8px;
983
- padding: 9px 14px;
984
- background: var(--aqua-soft);
985
- color: var(--ink);
986
- font-weight: 700;
987
- text-decoration: none;
988
- }
989
- .button.secondary { background: transparent; border-color: var(--line); color: var(--aqua); }
990
- .bento { display: grid; grid-template-columns: repeat(12, 1fr); gap: 14px; margin-top: 28px; }
991
- .card {
992
- min-height: 148px;
993
- border: 1px solid var(--line);
994
- border-radius: 8px;
995
- background: var(--panel);
996
- padding: 18px;
997
- color: inherit;
998
- text-decoration: none;
999
- }
1000
- .card.strong { background: linear-gradient(180deg, var(--panel-strong), var(--panel)); border-color: var(--line-strong); }
1001
- .span-3 { grid-column: span 3; }
1002
- .span-4 { grid-column: span 4; }
1003
- .span-5 { grid-column: span 5; }
1004
- .span-6 { grid-column: span 6; }
1005
- .span-7 { grid-column: span 7; }
1006
- .span-8 { grid-column: span 8; }
1007
- .span-12 { grid-column: span 12; }
1008
- h2 { margin: 0 0 12px; font-size: 1rem; color: var(--aqua); letter-spacing: 0; }
1009
- h3 { margin: 0 0 6px; font-size: .86rem; color: var(--muted); font-weight: 650; text-transform: uppercase; letter-spacing: 0; }
1010
- .metric { margin-top: 4px; font-size: clamp(2rem, 4vw, 3.35rem); line-height: 1; font-weight: 800; letter-spacing: 0; }
1011
- .metric.small { font-size: clamp(1.55rem, 3vw, 2.3rem); }
1012
- .caption { margin-top: 10px; color: var(--subtle); font-size: .94rem; }
1013
- .status { color: var(--success); }
1014
- .warning { color: var(--warning); }
1015
- .list { padding-left: 18px; margin: 0; color: var(--muted); }
1016
- .list li + li { margin-top: 8px; }
1017
- .command { margin-top: 12px; border: 1px solid rgba(255,255,255,.10); border-radius: 8px; background: rgba(0,0,0,.28); padding: 13px; }
1018
- .download-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 14px; }
1019
- .download-link {
1020
- display: grid;
1021
- min-height: 74px;
1022
- align-content: center;
1023
- gap: 4px;
1024
- border: 1px solid var(--line);
1025
- border-radius: 8px;
1026
- background: rgba(0,0,0,.18);
1027
- padding: 12px;
1028
- color: var(--ink);
1029
- text-decoration: none;
1030
- }
1031
- .download-link strong { color: var(--aqua); }
1032
- .download-link span { color: var(--muted); font-size: .88rem; }
1033
- .endpoint-list { display: grid; gap: 10px; margin-top: 10px; }
1034
- .endpoint-row { display: grid; grid-template-columns: 72px 1fr; gap: 12px; align-items: baseline; }
1035
- .method { color: var(--success); font: 700 .78rem/1 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
1036
- .footer { margin-top: 26px; color: var(--muted); font-size: .94rem; }
1037
- @media (max-width: 860px) {
1038
- header { grid-template-columns: 1fr; }
1039
- img.logo { width: 76px; height: 76px; border-radius: 20px; }
1040
- .span-3, .span-4, .span-5, .span-6, .span-7, .span-8 { grid-column: span 12; }
1041
- .download-grid { grid-template-columns: 1fr; }
1042
- }
1043
- @media (min-width: 861px) and (max-width: 1120px) {
1044
- .span-3, .span-4 { grid-column: span 6; }
1045
- .span-5, .span-6, .span-7, .span-8 { grid-column: span 12; }
1046
- }
1047
- @media (prefers-reduced-motion: reduce) {
1048
- html { scroll-behavior: auto; }
1049
- }
1050
- </style>
1051
- </head>
1052
- <body>
1053
- <main>
1054
- <header>
1055
- <img class="logo" src="/favicon.png" alt="SAP MCP logo" width="92" height="92">
1056
- <div>
1057
- <span class="badge">OOBE Protocol • ${escapeHtml(endpointLabel)}</span>
1058
- <h1>${escapeHtml(title)}</h1>
1059
- <p>${escapeHtml(info.description)}</p>
1060
- <div class="hero-actions">
1061
- <a class="button" href="/wizard/install.sh">Install wizard</a>
1062
- <a class="button secondary" href="/wizard/downloads.json">Downloads</a>
1063
- <a class="button secondary" href="/docs">Docs</a>
1064
- <a class="button secondary" href="/server.json">Server JSON</a>
1065
- <a class="button secondary" href="/openapi.json">OpenAPI</a>
1066
- <a class="button secondary" href="/pay/provider.yml">pay.sh YAML</a>
1067
- <a class="button secondary" href="/.well-known/agent-card.json">Agent card</a>
1068
- </div>
1069
- </div>
1070
- </header>
1071
-
1072
- <div class="bento" aria-label="SAP MCP hosted server overview">
1073
- <section class="card strong span-4">
1074
- <h3>Facilitator Volume</h3>
1075
- <div class="metric">${escapeHtml(formatUsd(paymentStats.totalVolumeUsd))}</div>
1076
- <p class="caption">Estimated settled x402/pay.sh tool volume recorded by the hosted payment ledger.</p>
1077
- </section>
1078
-
1079
- <section class="card strong span-4">
1080
- <h3>Total Settlements</h3>
1081
- <div class="metric">${escapeHtml(formatInteger(paymentStats.totalSettlements))}</div>
1082
- <p class="caption">${escapeHtml(formatLastSettlement(paymentStats))}</p>
1083
- </section>
1084
-
1085
- <section class="card span-4">
1086
- <h3>Payment Requests</h3>
1087
- <div class="metric">${escapeHtml(formatInteger(paymentStats.totalPaymentRequests))}</div>
1088
- <p class="caption"><span class="status">${escapeHtml(formatInteger(paymentStats.totalVerifiedPayments))}</span> verified, <span class="warning">${escapeHtml(formatInteger(paymentStats.totalFailedSettlements))}</span> failed settlements.</p>
1089
- </section>
1090
-
1091
- <section class="card span-7">
1092
- <h2>Install Wizard</h2>
1093
- <p>Create a local SAP MCP profile, signer, policy limits, and optional client injection. Hosted users still sign x402/pay.sh and value-moving operations from their own machine or external signer.</p>
1094
- <div class="command"><code>${escapeHtml(installScriptCommand)}</code></div>
1095
- <div class="command"><code>${escapeHtml(wizardCommand)}</code></div>
1096
- </section>
1097
-
1098
- <section class="card span-5">
1099
- <h2>Native Downloads</h2>
1100
- <p>Use the desktop wizard when you want a guided setup without touching config files by hand.</p>
1101
- <div class="download-grid">
1102
- <a class="download-link" href="${escapeHtml(info.downloads.desktopWizard.windowsX64Setup)}"><strong>Windows</strong><span>x64 setup .exe</span></a>
1103
- <a class="download-link" href="${escapeHtml(info.downloads.desktopWizard.macosArm64Dmg)}"><strong>macOS</strong><span>Apple Silicon .dmg</span></a>
1104
- <a class="download-link" href="${escapeHtml(info.downloads.desktopWizard.linuxX64TarGz)}"><strong>Linux</strong><span>x64 tar.gz</span></a>
1105
- </div>
1106
- <p class="caption">Machine-readable links: <a href="${escapeHtml(info.endpoints.wizardDownloads)}">/wizard/downloads.json</a>.</p>
1107
- </section>
1108
-
1109
- <section class="card span-5">
1110
- <h2>Payments</h2>
1111
- <ul class="list">
1112
- <li><strong>x402:</strong> paid MCP tool calls return HTTP 402 with payment requirements, then settle through the OOBE facilitator.</li>
1113
- <li><strong>pay.sh:</strong> public provider YAML is available at <a href="${escapeHtml(info.endpoints.payShProvider)}">/pay/provider.yml</a> for catalog and proxy workflows.</li>
1114
- <li><strong>Local stdio:</strong> free by default; monetization applies to hosted remote usage.</li>
1115
- </ul>
1116
- </section>
1117
-
1118
- <section class="card strong span-12">
1119
- <h2>x402 Paid-Call Plugin For Agents</h2>
1120
- <p>Agents that can reach hosted MCP but cannot replay x402 challenges natively can install the wizard addon or invoke the local paid-call helper directly. The helper signs the payment payload locally and retries the hosted tool call without sending keypair bytes to OOBE.</p>
1121
- <div class="command"><code>Addon path: ${escapeHtml(X402_PAID_CALL_ADDON_PATH)}</code></div>
1122
- <div class="command"><code>${escapeHtml(x402PaidCallCommand)}</code></div>
1123
- </section>
1124
-
1125
- <section class="card span-8">
1126
- <h2>Endpoints</h2>
1127
- <div class="endpoint-list">
1128
- <div class="endpoint-row"><span class="method">POST</span><code>${escapeHtml(info.endpoints.mcp)}</code></div>
1129
- <div class="endpoint-row"><span class="method">GET</span><code>${escapeHtml(info.endpoints.docs)}</code></div>
1130
- <div class="endpoint-row"><span class="method">GET</span><code>${escapeHtml(info.endpoints.health)}</code></div>
1131
- <div class="endpoint-row"><span class="method">GET</span><code>${escapeHtml(info.endpoints.serverInfo)}</code></div>
1132
- <div class="endpoint-row"><span class="method">GET</span><code>${escapeHtml(info.endpoints.openApi)}</code></div>
1133
- <div class="endpoint-row"><span class="method">GET</span><code>${escapeHtml(info.endpoints.x402Discovery)}</code></div>
1134
- <div class="endpoint-row"><span class="method">GET</span><code>${escapeHtml(info.endpoints.payShProvider)}</code></div>
1135
- <div class="endpoint-row"><span class="method">GET</span><code>${escapeHtml(info.endpoints.agentCard)}</code></div>
1136
- <div class="endpoint-row"><span class="method">GET</span><code>${escapeHtml(info.endpoints.wizardDescriptor)}</code></div>
1137
- </div>
1138
- </section>
1139
-
1140
- <section class="card span-4">
1141
- <h2>Security Boundary</h2>
1142
- <ul class="list">
1143
- <li>Keypair bytes are never exposed by public endpoints.</li>
1144
- <li>RPC secrets are redacted from public metadata and UI.</li>
1145
- <li>Paid tools require payment before execution.</li>
1146
- </ul>
1147
- </section>
1148
- </div>
1149
- <p class="footer">Use <a href="/server.json">/server.json</a> for machine-readable public metadata. MCP clients should connect to <a href="/mcp">/mcp</a> with <code>Accept: application/json, text/event-stream</code>.</p>
1150
- </main>
1151
- </body>
1152
- </html>`;
983
+ return renderLandingPage({
984
+ endpoint,
985
+ info,
986
+ paymentStats,
987
+ pageUrl,
988
+ title,
989
+ installScriptCommand: 'curl -fsSL https://mcp.sap.oobeprotocol.ai/wizard/install.sh | sh',
990
+ wizardCommand: WIZARD_NPM_COMMAND,
991
+ paidCallCommand: X402_PAID_CALL_NPX_COMMAND,
992
+ paidCallAddonPath: X402_PAID_CALL_ADDON_PATH,
993
+ });
1153
994
  }
1154
995
  /**
1155
996
  * @name buildDocsHtml
@@ -1268,30 +1109,6 @@ function acceptsHtmlPreview(req) {
1268
1109
  const accept = String(req.headers.accept ?? '');
1269
1110
  return accept.includes('text/html') && !accept.includes('text/event-stream') && !accept.includes('application/json');
1270
1111
  }
1271
- function formatUsd(value) {
1272
- if (!Number.isFinite(value) || value <= 0) {
1273
- return '$0.00';
1274
- }
1275
- return `$${value.toLocaleString('en-US', {
1276
- minimumFractionDigits: value < 1 ? 3 : 2,
1277
- maximumFractionDigits: value < 1 ? 3 : 2,
1278
- })}`;
1279
- }
1280
- function formatInteger(value) {
1281
- if (!Number.isFinite(value) || value <= 0) {
1282
- return '0';
1283
- }
1284
- return Math.trunc(value).toLocaleString('en-US');
1285
- }
1286
- function formatLastSettlement(stats) {
1287
- if (!stats.ledgerAvailable) {
1288
- return 'Ledger not initialized yet. Metrics appear after the first paid hosted call.';
1289
- }
1290
- if (!stats.lastSettlementAt) {
1291
- return 'No settlements yet. Paid requests will update this card after successful settlement.';
1292
- }
1293
- return `Last settlement: ${stats.lastSettlementAt}`;
1294
- }
1295
1112
  /**
1296
1113
  * @name escapeHtml
1297
1114
  * @description Escapes dynamic strings before embedding them in public HTML metadata.