@payware/mcp-server 1.3.0 → 1.3.2

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 (127) hide show
  1. package/.env.example +0 -0
  2. package/LICENSE +21 -0
  3. package/README.md +2 -0
  4. package/check-keys.js +0 -0
  5. package/keys/README.md +0 -0
  6. package/mcp-config.json +0 -0
  7. package/package.json +8 -4
  8. package/payware-mcp-config.json +0 -0
  9. package/src/auth/rsa-keys.js +0 -0
  10. package/src/auth/sandbox-setup.js +1 -1
  11. package/src/config/env.js +0 -0
  12. package/src/config/partner-types.js +0 -0
  13. package/src/core/auth/jwt-factory.js +0 -0
  14. package/src/core/auth/jwt-token.js +61 -51
  15. package/src/core/utils/json-serializer.js +0 -0
  16. package/src/core/utils/key-utils.js +0 -0
  17. package/src/index.js +0 -0
  18. package/src/isv/data/index.js +0 -0
  19. package/src/isv/deep-links/delete-product-link.js +0 -0
  20. package/src/isv/deep-links/delete-transaction-link.js +0 -0
  21. package/src/isv/deep-links/get-product-link.js +0 -0
  22. package/src/isv/deep-links/get-transaction-link.js +0 -0
  23. package/src/isv/deep-links/index.js +0 -0
  24. package/src/isv/index.js +0 -0
  25. package/src/isv/invitations/index.js +2 -1
  26. package/src/isv/oauth2/create-token-simple.js +0 -0
  27. package/src/isv/oauth2/get-token-info.js +0 -0
  28. package/src/isv/oauth2/obtain-token.js +0 -0
  29. package/src/isv/oauth2/token-helper.js +0 -0
  30. package/src/isv/poi/cancel-price.js +0 -0
  31. package/src/isv/poi/create.js +0 -0
  32. package/src/isv/poi/get-poi.js +0 -0
  33. package/src/isv/poi/get-qrcode.js +0 -0
  34. package/src/isv/poi/get-status.js +0 -0
  35. package/src/isv/poi/index.js +0 -0
  36. package/src/isv/poi/list-pois.js +0 -0
  37. package/src/isv/poi/set-price.js +0 -0
  38. package/src/isv/products/audios.js +0 -0
  39. package/src/isv/products/create-product.js +1 -1
  40. package/src/isv/products/delete-product.js +0 -0
  41. package/src/isv/products/get-product-image.js +0 -0
  42. package/src/isv/products/get-product.js +0 -0
  43. package/src/isv/products/index.js +0 -0
  44. package/src/isv/products/list-products.js +0 -0
  45. package/src/isv/products/schedules.js +0 -0
  46. package/src/isv/products/update-product.js +0 -0
  47. package/src/isv/reference/index.js +0 -0
  48. package/src/isv/shops/index.js +0 -0
  49. package/src/isv/transactions/cancel-transaction.js +0 -0
  50. package/src/isv/transactions/create-transaction.js +0 -0
  51. package/src/isv/transactions/get-status.js +0 -0
  52. package/src/isv/transactions/process-transaction.js +0 -0
  53. package/src/isv/transactions/simulate-callback.js +0 -0
  54. package/src/isv/transactions/transaction-history.js +0 -0
  55. package/src/isv/utils/auth.js +0 -0
  56. package/src/mcp-proxy.js +0 -0
  57. package/src/merchant/data/export-report.js +0 -0
  58. package/src/merchant/data/generate-report.js +0 -0
  59. package/src/merchant/data/get-export-list.js +0 -0
  60. package/src/merchant/data/get-report-requests.js +0 -0
  61. package/src/merchant/data/get-report-status.js +0 -0
  62. package/src/merchant/data/index.js +0 -0
  63. package/src/merchant/data/list-reports.js +0 -0
  64. package/src/merchant/deep-links/delete-product-link.js +0 -0
  65. package/src/merchant/deep-links/delete-transaction-link.js +0 -0
  66. package/src/merchant/deep-links/get-product-link.js +0 -0
  67. package/src/merchant/deep-links/get-transaction-link.js +0 -0
  68. package/src/merchant/deep-links/index.js +0 -0
  69. package/src/merchant/index.js +0 -0
  70. package/src/merchant/logs/index.js +0 -0
  71. package/src/merchant/products/audios.js +0 -0
  72. package/src/merchant/products/create-product.js +0 -0
  73. package/src/merchant/products/delete-product.js +0 -0
  74. package/src/merchant/products/get-product-image.js +0 -0
  75. package/src/merchant/products/get-product.js +0 -0
  76. package/src/merchant/products/index.js +0 -0
  77. package/src/merchant/products/list-products.js +0 -0
  78. package/src/merchant/products/schedules.js +0 -0
  79. package/src/merchant/products/update-product.js +0 -0
  80. package/src/merchant/shops/index.js +0 -0
  81. package/src/merchant/transactions/cancel-transaction.js +0 -0
  82. package/src/merchant/transactions/create-transaction.js +1 -1
  83. package/src/merchant/transactions/get-status.js +0 -0
  84. package/src/merchant/transactions/process-transaction.js +0 -0
  85. package/src/merchant/transactions/simulate-callback.js +0 -0
  86. package/src/merchant/transactions/transaction-history.js +0 -0
  87. package/src/payment-institution/data/index.js +0 -0
  88. package/src/payment-institution/deep-links/delete-transaction-link.js +0 -0
  89. package/src/payment-institution/deep-links/get-transaction-link.js +0 -0
  90. package/src/payment-institution/deep-links/index.js +0 -0
  91. package/src/payment-institution/index.js +0 -0
  92. package/src/payment-institution/transactions/create-pi-transaction.js +0 -0
  93. package/src/payment-institution/transactions/finalize-transaction.js +0 -0
  94. package/src/payment-institution/transactions/get-transaction-history.js +0 -0
  95. package/src/payment-institution/transactions/get-transaction-status.js +0 -0
  96. package/src/payment-institution/transactions/process-pi-transaction.js +0 -0
  97. package/src/payment-institution/transactions/simulate-pi-callback.js +0 -0
  98. package/src/payment-institution/transactions/soundbite-transaction.js +0 -0
  99. package/src/proxy-server.js +25 -4
  100. package/src/shared/api-errors.js +0 -0
  101. package/src/shared/auth-headers.js +0 -0
  102. package/src/shared/data/data-api.js +0 -0
  103. package/src/shared/deep-links/api.js +0 -0
  104. package/src/shared/index.js +0 -0
  105. package/src/shared/logs/logs-api.js +0 -0
  106. package/src/shared/poi/poi-create-api.js +0 -0
  107. package/src/shared/products/products-api.js +0 -0
  108. package/src/shared/shops/shops-api.js +0 -0
  109. package/src/tools/advanced-code-generator.js +0 -0
  110. package/src/utils/code-examples/common/auth-examples.js +0 -0
  111. package/src/utils/code-examples/common/helpers.js +0 -0
  112. package/src/utils/code-examples/common/utils-examples.js +0 -0
  113. package/src/utils/code-examples/frameworks/examples.js +0 -0
  114. package/src/utils/code-examples/index.js +0 -0
  115. package/src/utils/code-examples/isv/oauth2.js +0 -0
  116. package/src/utils/code-examples/isv/products.js +0 -0
  117. package/src/utils/code-examples/isv/transactions.js +0 -0
  118. package/src/utils/code-examples/merchant/products.js +0 -0
  119. package/src/utils/code-examples/merchant/transactions.js +0 -0
  120. package/src/utils/code-examples/payment-institution/transactions.js +0 -0
  121. package/src/utils/code-examples/shared/data.js +0 -0
  122. package/src/utils/code-examples/shared/deep-links.js +0 -0
  123. package/src/utils/code-examples/shared/soundbites.js +0 -0
  124. package/src/utils/code-examples.js +0 -0
  125. package/src/utils/format-request.js +0 -0
  126. package/src/utils/json-formatter.js +0 -0
  127. package/src/utils/server-info.js +0 -0
package/.env.example CHANGED
File without changes
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 payware LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  Official MCP (Model Context Protocol) server for payware payment API integration. This server enables AI assistants to interact with payware APIs, automating integration workflows for all partner types.
6
6
 
7
+ Releases are published to npm as `@payware/mcp-server` and to the official [MCP Registry](https://registry.modelcontextprotocol.io) as `io.github.payware/mcp-server`. MIT licensed, see [LICENSE](LICENSE).
8
+
7
9
  ## Who Is This For?
8
10
 
9
11
  - **Merchants** - Accept payments, manage products, generate transaction reports
package/check-keys.js CHANGED
File without changes
package/keys/README.md CHANGED
File without changes
package/mcp-config.json CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@payware/mcp-server",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
+ "mcpName": "io.github.payware/mcp-server",
4
5
  "description": "Official MCP server for payware payment API integration",
5
6
  "main": "src/index.js",
6
7
  "type": "module",
@@ -36,15 +37,18 @@
36
37
  "model-context-protocol",
37
38
  "payware",
38
39
  "payments",
39
- "payment-gateway",
40
- "payment-processing",
41
40
  "api",
42
41
  "fintech",
43
42
  "pos",
44
43
  "terminal",
45
44
  "merchant",
46
45
  "isv",
47
- "oauth2"
46
+ "oauth2",
47
+ "a2a-payments",
48
+ "account-to-account",
49
+ "instant-payments",
50
+ "transaction-resolution",
51
+ "qr-code-payments"
48
52
  ],
49
53
  "author": "payware <support@payware.com>",
50
54
  "license": "MIT",
File without changes
File without changes
@@ -76,7 +76,7 @@ export function generateSandboxConfig(partnerId, privateKey = null) {
76
76
  type: 'JWT',
77
77
  algorithm: 'RS256',
78
78
  audience: 'https://payware.eu',
79
- requiresContentMd5: 'For POST/PUT/PATCH requests',
79
+ requiresContentSha256: 'For POST/PUT/PATCH requests: Base64(SHA-256(body)) in the JWT header',
80
80
  hasPrivateKey: !!privateKey
81
81
  },
82
82
  generatedAt: new Date().toISOString()
package/src/config/env.js CHANGED
File without changes
File without changes
File without changes
@@ -30,26 +30,6 @@ export function generateContentSha256(body) {
30
30
  return hash.digest('base64');
31
31
  }
32
32
 
33
- /**
34
- * Generate MD5 hash for JWT contentMd5 header (DEPRECATED - use generateContentSha256)
35
- *
36
- * @deprecated Use generateContentSha256 instead. MD5 is still supported by the server
37
- * for backwards compatibility but is deprecated.
38
- *
39
- * @param {Object|string} body - Request body (object will be consistently serialized)
40
- * @returns {string} Base64 encoded MD5 hash for JWT contentMd5 header
41
- */
42
- export function generateContentMd5(body) {
43
- if (!body) return null;
44
-
45
- const bodyString = typeof body === 'string' ? body : createDeterministicJSON(body);
46
-
47
- // Generate MD5 hash as raw binary and encode in base64
48
- const hash = crypto.createHash('md5');
49
- hash.update(bodyString, 'utf8');
50
- return hash.digest('base64');
51
- }
52
-
53
33
  /**
54
34
  * Generate the exact JSON string for both JWT content hash and HTTP request body
55
35
  *
@@ -143,9 +123,16 @@ export function createRequestSignature(method, path, body, privateKey) {
143
123
  /**
144
124
  * Validate and debug JWT token implementation
145
125
  */
126
+ // The rules below mirror the payware API's authentication checks. A check that is looser
127
+ // than the server's reports a token as valid that the API refuses; one that is stricter fails tokens the
128
+ // API accepts - the audience check did both kinds of harm until it learned the on-behalf form.
129
+ const PAYWARE_AUDIENCE = 'https://payware.eu';
130
+ const IAT_MAX_AGE_SECONDS = 300;
131
+ const IAT_MAX_FUTURE_SKEW_SECONDS = 60;
132
+
146
133
  export const validateJWTTokenTool = {
147
134
  name: "payware_authentication_validate_jwt",
148
- description: "Validate and debug JWT token structure, decode payload/headers, verify content hash (SHA-256/MD5) calculation, and check RS256 signature format for payware API compliance",
135
+ description: "Validate and debug a payware JWT: decode header and payload, check the claims against the API's rules (algorithm, audience including the ISV on-behalf form, 5-minute freshness), and verify the contentSha256 hash against the request body when one is given",
149
136
  inputSchema: {
150
137
  type: "object",
151
138
  properties: {
@@ -155,7 +142,7 @@ export const validateJWTTokenTool = {
155
142
  },
156
143
  expectedPayload: {
157
144
  type: "object",
158
- description: "Expected request body (optional - for content hash validation)"
145
+ description: "The request body the token was made for (optional). Give it for POST, PUT and PATCH: contentSha256 is then required and checked against it. Leave it out for GET and DELETE, which carry no hash."
159
146
  }
160
147
  },
161
148
  required: ["jwtToken"],
@@ -179,55 +166,76 @@ export const validateJWTTokenTool = {
179
166
 
180
167
  const { header, payload } = decoded;
181
168
 
182
- // Determine which hash type is used
169
+ // Only contentSha256 counts. The server stopped accepting contentMd5 (SEC-013): a token that
170
+ // carries only contentMd5 is answered 401 ERR_MISSING_CONTENT_HASH, so reporting it here as a
171
+ // present, matching hash told an integrator a token was fine that the API would refuse.
183
172
  const hasContentSha256 = !!header.contentSha256;
184
173
  const hasContentMd5 = !!header.contentMd5;
185
- const hasContentHash = hasContentSha256 || hasContentMd5;
174
+ const hasContentHash = hasContentSha256;
175
+
176
+ // Audience. https://payware.eu for a direct call, and always on /oauth2. An ISV acting on behalf
177
+ // of a merchant sets aud to that merchant's partner ID and must then carry sub = the merchant's
178
+ // OAuth token - the server answers ERR_MISSING_TOKEN without it. Only accepting
179
+ // https://payware.eu failed every on-behalf token, which is most of what an ISV sends.
180
+ const onBehalf = typeof payload.aud === 'string' && payload.aud !== '' && payload.aud !== PAYWARE_AUDIENCE;
181
+ const hasSubject = typeof payload.sub === 'string' && payload.sub.trim() !== '';
182
+
183
+ // Freshness. The server refuses a token issued more than 5 minutes ago, or more than 60 seconds
184
+ // in the future (ERR_STALE_TOKEN); a missing iat is reported by issuedAt.
185
+ const ageSeconds = payload.iat ? Math.floor(Date.now() / 1000) - payload.iat : null;
186
+ const stale = ageSeconds !== null && ageSeconds > IAT_MAX_AGE_SECONDS;
187
+ const fromTheFuture = ageSeconds !== null && ageSeconds < -IAT_MAX_FUTURE_SKEW_SECONDS;
186
188
 
187
189
  // Validation results
188
190
  const validation = {
189
191
  structure: true,
190
192
  algorithm: header.alg === 'RS256',
191
193
  type: header.typ === 'JWT',
192
- audience: payload.aud === 'https://payware.eu',
194
+ audience: payload.aud === PAYWARE_AUDIENCE || (onBehalf && hasSubject),
193
195
  issuer: !!payload.iss,
194
196
  issuedAt: !!payload.iat,
195
- contentHash: hasContentHash
197
+ freshness: !stale && !fromTheFuture,
198
+ // Required only on a request with a body. Without expectedPayload this tool cannot tell a GET
199
+ // from a POST, so an absent hash is not a failure on its own - it used to fail every GET token
200
+ // while the report beside it said "OK for GET requests".
201
+ contentHash: expectedPayload ? hasContentHash : true
196
202
  };
197
203
 
198
204
  // Content hash validation if expected payload provided
199
205
  let hashValidation = null;
200
206
  if (expectedPayload && hasContentHash) {
201
- if (hasContentSha256) {
202
- const calculatedSha256 = generateContentSha256(expectedPayload);
203
- hashValidation = {
204
- type: 'SHA-256 (preferred)',
205
- provided: header.contentSha256,
206
- calculated: calculatedSha256,
207
- matches: header.contentSha256 === calculatedSha256,
208
- deterministicJson: createDeterministicJSON(expectedPayload)
209
- };
210
- } else if (hasContentMd5) {
211
- const calculatedMd5 = generateContentMd5(expectedPayload);
212
- hashValidation = {
213
- type: 'MD5 (deprecated)',
214
- provided: header.contentMd5,
215
- calculated: calculatedMd5,
216
- matches: header.contentMd5 === calculatedMd5,
217
- deterministicJson: createDeterministicJSON(expectedPayload)
218
- };
219
- }
207
+ const calculatedSha256 = generateContentSha256(expectedPayload);
208
+ hashValidation = {
209
+ type: 'SHA-256',
210
+ provided: header.contentSha256,
211
+ calculated: calculatedSha256,
212
+ matches: header.contentSha256 === calculatedSha256,
213
+ deterministicJson: createDeterministicJSON(expectedPayload)
214
+ };
220
215
  }
221
216
 
222
217
  // Check for common issues
223
218
  const issues = [];
224
219
  if (!validation.algorithm) issues.push("Algorithm should be 'RS256'");
225
220
  if (!validation.type) issues.push("Type should be 'JWT'");
226
- if (!validation.audience) issues.push("Audience should be 'https://payware.eu'");
221
+ if (!payload.aud) {
222
+ issues.push("Missing audience (aud) claim");
223
+ } else if (onBehalf && !hasSubject) {
224
+ issues.push(`aud is a merchant partner ID (${payload.aud}), which means acting on its behalf, but sub is missing - set sub to that merchant's OAuth token (ERR_MISSING_TOKEN), or use aud = ${PAYWARE_AUDIENCE} for a direct call`);
225
+ }
227
226
  if (!validation.issuer) issues.push("Missing issuer (iss) claim");
228
227
  if (!validation.issuedAt) issues.push("Missing issued at (iat) claim");
228
+ if (stale) {
229
+ issues.push(`Token is stale: issued ${ageSeconds}s ago, and the API accepts tokens up to ${IAT_MAX_AGE_SECONDS}s old (ERR_STALE_TOKEN)`);
230
+ }
231
+ if (fromTheFuture) {
232
+ issues.push(`iat is ${-ageSeconds}s in the future, beyond the ${IAT_MAX_FUTURE_SKEW_SECONDS}s of clock skew the API tolerates (ERR_STALE_TOKEN)`);
233
+ }
234
+ if (expectedPayload && !hasContentSha256 && !hasContentMd5) {
235
+ issues.push("Missing contentSha256: a request with a body must carry Base64(SHA-256(body)) in the JWT header (ERR_MISSING_CONTENT_HASH)");
236
+ }
229
237
  if (hasContentMd5 && !hasContentSha256) {
230
- issues.push("Using deprecated MD5 hash - consider upgrading to SHA-256 (contentSha256)");
238
+ issues.push("contentMd5 is not accepted: the API answers 401 ERR_MISSING_CONTENT_HASH. Send contentSha256 = Base64(SHA-256(body)) instead");
231
239
  }
232
240
  if (hashValidation && !hashValidation.matches) {
233
241
  issues.push("Content hash mismatch - ensure deterministic JSON serialization with sorted keys");
@@ -259,10 +267,11 @@ ${JSON.stringify(payload, null, 2)}
259
267
  ${validation.structure ? '✅' : '❌'} **Structure**: JWT has valid 3-part structure
260
268
  ${validation.algorithm ? '✅' : '❌'} **Algorithm**: RS256 ${validation.algorithm ? '(correct)' : `(found: ${header.alg})`}
261
269
  ${validation.type ? '✅' : '❌'} **Type**: JWT ${validation.type ? '(correct)' : `(found: ${header.typ})`}
262
- ${validation.audience ? '✅' : '❌'} **Audience**: https://payware.eu ${validation.audience ? '(correct)' : `(found: ${payload.aud})`}
270
+ ${validation.audience ? '✅' : '❌'} **Audience**: ${payload.aud === PAYWARE_AUDIENCE ? `${PAYWARE_AUDIENCE} (direct call)` : onBehalf ? `${payload.aud} (on behalf of this merchant, sub ${hasSubject ? 'present' : 'MISSING'}; not valid on /oauth2 endpoints, which need ${PAYWARE_AUDIENCE})` : 'Missing'}
263
271
  ${validation.issuer ? '✅' : '❌'} **Issuer**: ${payload.iss || 'Missing'}
264
272
  ${validation.issuedAt ? '✅' : '❌'} **Issued At**: ${payload.iat ? new Date(payload.iat * 1000).toISOString() : 'Missing'}
265
- ${validation.contentHash ? '✅' : 'ℹ️'} **Content Hash**: ${header.contentSha256 ? `SHA-256: ${header.contentSha256}` : header.contentMd5 ? `MD5 (deprecated): ${header.contentMd5}` : 'Not present (OK for GET requests)'}
273
+ ${validation.freshness ? '✅' : '❌'} **Freshness**: ${ageSeconds === null ? 'n/a' : stale ? `stale - ${ageSeconds}s old, limit ${IAT_MAX_AGE_SECONDS}s` : fromTheFuture ? `${-ageSeconds}s in the future, limit ${IAT_MAX_FUTURE_SKEW_SECONDS}s` : `${ageSeconds}s old (limit ${IAT_MAX_AGE_SECONDS}s)`}
274
+ ${header.contentSha256 ? '✅' : validation.contentHash ? 'ℹ️' : '❌'} **Content Hash**: ${header.contentSha256 ? `SHA-256: ${header.contentSha256}` : header.contentMd5 ? `only contentMd5, which the API rejects - send contentSha256` : expectedPayload ? 'Missing - required on a request with a body' : 'Not present (fine for GET and DELETE; required on POST, PUT and PATCH with a body)'}
266
275
 
267
276
  ${hashValidation ? `## Content Hash Validation
268
277
 
@@ -305,7 +314,8 @@ ${issues.map(issue => `- ${issue}`).join('\n')}
305
314
 
306
315
  ${issues.includes("Algorithm should be 'RS256'") ? '- Update JWT algorithm to RS256\n' : ''}
307
316
  ${issues.includes("Type should be 'JWT'") ? '- Set JWT type to "JWT"\n' : ''}
308
- ${issues.includes("Audience should be 'https://payware.eu'") ? '- Set audience to "https://payware.eu" (not just "payware")\n' : ''}
317
+ ${!validation.audience ? `- Set aud to "${PAYWARE_AUDIENCE}" for a direct call, or to the merchant's partner ID together with sub = its OAuth token when acting on its behalf\n` : ''}
318
+ ${!validation.freshness ? '- Create the JWT immediately before each request: the API accepts it for 5 minutes, and check the clock of the machine that signs it\n' : ''}
309
319
  ${issues.includes("Missing issuer (iss) claim") ? '- Add your partner ID as the issuer claim\n' : ''}
310
320
  ${issues.includes("Missing issued at (iat) claim") ? '- Add current Unix timestamp as issued at claim\n' : ''}
311
321
  ${issues.some(i => i.includes('hash mismatch')) ? '- Use deterministic JSON serialization with sorted keys\n- Ensure same JSON string for hash calculation and HTTP body\n' : ''}
@@ -550,7 +560,7 @@ ${deterministicJson}
550
560
  ❌ Setting audience to "payware" instead of "https://payware.eu"
551
561
  ❌ Putting contentSha256 in payload instead of header
552
562
  ❌ Using wrong algorithm (HS256 instead of RS256)
553
- ❌ Using deprecated contentMd5 instead of contentSha256
563
+ ❌ Sending contentMd5 - the API no longer accepts it; use contentSha256
554
564
 
555
565
  ---
556
566
  **Execution Info:**
File without changes
File without changes
package/src/index.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/src/isv/index.js CHANGED
File without changes
@@ -350,7 +350,8 @@ ${rows || 'No invitations match this filter.'}
350
350
 
351
351
  **Status meanings:** ⏳ PENDING - link sent, merchant has not acted. ✅ COMPLETED - merchant accepted;
352
352
  obtain a token with \`payware_authorization_oauth2_obtain_token\`. ⌛ EXPIRED - the link lapsed; create
353
- a new invitation. 🚫 CANCELLED - withdrawn by you.
353
+ a new invitation. 🚫 CANCELLED - withdrawn by you, or by payware because the merchant it was addressed
354
+ to was deleted.
354
355
 
355
356
  **Request ID:** ${result.requestId || 'N/A'}
356
357
  **Timestamp:** ${result.timestamp}`
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -70,7 +70,7 @@ export const createProductTool = {
70
70
  },
71
71
  shop: {
72
72
  type: "string",
73
- description: "Shop code (if not supplied, default shop is used)",
73
+ description: "Shop code. If omitted, the merchant's default shop is used - which may be outside your assigned scope (403 ERR_SHOP_NOT_IN_SCOPE). Find valid codes with payware_shops_list.",
74
74
  maxLength: 10
75
75
  },
76
76
  // Product Data Properties
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/src/mcp-proxy.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,5 +1,5 @@
1
1
  import axios from 'axios';
2
- import { createJWTToken, generateContentMd5 } from '../../core/auth/jwt-token.js';
2
+ import { createJWTToken } from '../../core/auth/jwt-token.js';
3
3
  import { createMinimizedJSON } from '../../core/utils/json-serializer.js';
4
4
  import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
5
5
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -25,9 +25,16 @@ class PaywareMCPProxy {
25
25
  }
26
26
 
27
27
  setupMiddleware() {
28
- // Enable CORS for all routes
28
+ // SEC-018: CORS was `origin: true` with credentials - it reflected ANY requesting origin, so any
29
+ // web page the operator visited could drive these routes from their browser. The routes below
30
+ // mint JWTs with the configured partner's private key and call the payware API as that partner,
31
+ // so this is not a cosmetic default. Restricted to explicit local origins; set
32
+ // PAYWARE_MCP_PROXY_ORIGINS (comma-separated) to widen it deliberately.
33
+ const allowedOrigins = (process.env.PAYWARE_MCP_PROXY_ORIGINS
34
+ || `http://localhost:${this.port},http://127.0.0.1:${this.port}`)
35
+ .split(',').map((o) => o.trim()).filter(Boolean);
29
36
  this.app.use(cors({
30
- origin: true,
37
+ origin: allowedOrigins,
31
38
  credentials: true
32
39
  }));
33
40
 
@@ -168,8 +175,22 @@ class PaywareMCPProxy {
168
175
  }
169
176
 
170
177
  async start() {
171
- this.app.listen(this.port, '0.0.0.0', () => {
172
- console.log(`🚀 payware MCP Proxy Server running on http://0.0.0.0:${this.port}`);
178
+ // SEC-018: loopback by default. This bound 0.0.0.0 - every interface - with no authentication on
179
+ // /tools, /tools/:toolName or /mcp, each of which spawns the MCP server with the process env
180
+ // (PAYWARE_PARTNER_ID and the sandbox/production private key paths) and can therefore sign as
181
+ // that partner. On a shared dev host or an office LAN that is partner impersonation over plain
182
+ // HTTP, and with a production key it is total. The stdio transport (mcp-proxy.js) is the normal
183
+ // path and opens no socket at all.
184
+ //
185
+ // A wider bind is now an explicit opt-in via PAYWARE_MCP_PROXY_HOST, so exposing it is a
186
+ // decision someone made rather than the default they inherited.
187
+ const host = process.env.PAYWARE_MCP_PROXY_HOST || '127.0.0.1';
188
+ if (host !== '127.0.0.1' && host !== 'localhost') {
189
+ console.warn(`⚠️ MCP proxy binding ${host} - these routes are UNAUTHENTICATED and can sign as `
190
+ + `the configured partner. Only do this on a network you control.`);
191
+ }
192
+ this.app.listen(this.port, host, () => {
193
+ console.log(`🚀 payware MCP Proxy Server running on http://${host}:${this.port}`);
173
194
  console.log(`📚 API Documentation: http://localhost:${this.port}`);
174
195
  console.log(`🔧 Health Check: http://localhost:${this.port}/health`);
175
196
  console.log(`🛠️ Tools List: http://localhost:${this.port}/tools`);
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes