create-solvapay 0.8.1 → 0.8.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.
- package/package.json +1 -1
- package/scripts/mcp/scaffold.mjs +35 -5
package/package.json
CHANGED
package/scripts/mcp/scaffold.mjs
CHANGED
|
@@ -625,7 +625,8 @@ function deriveServerNameFromWorkerName(workerName) {
|
|
|
625
625
|
return cleaned || 'solvapay-mcp-server'
|
|
626
626
|
}
|
|
627
627
|
|
|
628
|
-
function renderToolFile({ operation, schemes, tier, auth, serverBaseUrl, freeLimit }) {
|
|
628
|
+
function renderToolFile({ operation: specOperation, schemes, tier, auth, serverBaseUrl, freeLimit }) {
|
|
629
|
+
const operation = withoutReservedHeaderParams(specOperation, auth)
|
|
629
630
|
const fnName = `register${toPascalIdentifier(operation.operationId)}`
|
|
630
631
|
const urlTemplate = renderUrlTemplate(serverBaseUrl, operation)
|
|
631
632
|
const headerLines = renderHeaderLines(auth, schemes, operation)
|
|
@@ -765,7 +766,7 @@ function shortenDescription(text) {
|
|
|
765
766
|
function renderUrlTemplate(baseUrl, operation) {
|
|
766
767
|
let path = operation.path
|
|
767
768
|
for (const param of operation.parameters.filter(p => p.in === 'path')) {
|
|
768
|
-
path = path.replace(`{${param.name}}`, `\${
|
|
769
|
+
path = path.replace(`{${param.name}}`, `\${${inputAccess(param.name)}}`)
|
|
769
770
|
}
|
|
770
771
|
return `\`${joinUrl(baseUrl, path)}\``
|
|
771
772
|
}
|
|
@@ -783,7 +784,7 @@ function renderFormAssign(operation) {
|
|
|
783
784
|
const ctor = operation.formEncoding === 'multipart' ? 'FormData' : 'URLSearchParams'
|
|
784
785
|
const lines = [` const form = new ${ctor}()`]
|
|
785
786
|
for (const field of fields) {
|
|
786
|
-
const access =
|
|
787
|
+
const access = inputAccess(field.name)
|
|
787
788
|
lines.push(` if (${access} !== undefined) form.set('${field.name}', String(${access}))`)
|
|
788
789
|
}
|
|
789
790
|
return lines.join('\n')
|
|
@@ -795,7 +796,7 @@ function renderFetchInit(operation, headerLines) {
|
|
|
795
796
|
const queryAssign = operation.parameters
|
|
796
797
|
.filter(p => p.in === 'query')
|
|
797
798
|
.map(p => {
|
|
798
|
-
const access =
|
|
799
|
+
const access = inputAccess(p.name)
|
|
799
800
|
return ` if (${access} !== undefined) url.searchParams.set('${p.name}', String(${access}))`
|
|
800
801
|
})
|
|
801
802
|
.concat(renderFormAssign(operation) || [])
|
|
@@ -821,6 +822,22 @@ function renderFetchInit(operation, headerLines) {
|
|
|
821
822
|
return { method, queryAssign, headersBlock, bodyBlock, methodBlock, usesStringifyLossless }
|
|
822
823
|
}
|
|
823
824
|
|
|
825
|
+
// OpenAPI ignores `in: header` params named Accept, Content-Type or
|
|
826
|
+
// Authorization, and a header param that names the upstream credential
|
|
827
|
+
// header would let the LLM supply (or overwrite) the secret. Drop both
|
|
828
|
+
// from the tool input.
|
|
829
|
+
function withoutReservedHeaderParams(operation, auth) {
|
|
830
|
+
const reserved = new Set(['accept', 'content-type', 'authorization'])
|
|
831
|
+
if (auth.kind === 'apiKey' && auth.name) reserved.add(auth.name.toLowerCase())
|
|
832
|
+
if (auth.kind === 'apiKey-multi') {
|
|
833
|
+
for (const header of auth.headers ?? []) reserved.add(String(header.name).toLowerCase())
|
|
834
|
+
}
|
|
835
|
+
const parameters = operation.parameters.filter(
|
|
836
|
+
p => p.in !== 'header' || !reserved.has(String(p.name).toLowerCase()),
|
|
837
|
+
)
|
|
838
|
+
return parameters.length === operation.parameters.length ? operation : { ...operation, parameters }
|
|
839
|
+
}
|
|
840
|
+
|
|
824
841
|
function renderHeaderLines(auth, schemes, operation) {
|
|
825
842
|
// Pick the first supported scheme this operation requires; fall back
|
|
826
843
|
// to the top-level auth kind when the spec has no operation-level
|
|
@@ -838,7 +855,14 @@ function renderHeaderLines(auth, schemes, operation) {
|
|
|
838
855
|
// handler body (see renderPayableBody / renderFreeCappedBody /
|
|
839
856
|
// renderFreeBody) via
|
|
840
857
|
// `const token = await getAccessToken(env)`.
|
|
841
|
-
|
|
858
|
+
// `in: header` params go first so the upstream credential (added below)
|
|
859
|
+
// always wins over a caller-supplied value.
|
|
860
|
+
const headerEntries = operation.parameters
|
|
861
|
+
.filter(p => p.in === 'header')
|
|
862
|
+
.map(p => {
|
|
863
|
+
const access = inputAccess(p.name)
|
|
864
|
+
return `...(${access} !== undefined ? { ${JSON.stringify(p.name.toLowerCase())}: String(${access}) } : {})`
|
|
865
|
+
})
|
|
842
866
|
if (auth.kind === 'bearer') {
|
|
843
867
|
headerEntries.push("authorization: `Bearer ${env.UPSTREAM_API_KEY}`")
|
|
844
868
|
} else if (auth.kind === 'apiKey') {
|
|
@@ -1126,6 +1150,12 @@ function jsAccessor(name) {
|
|
|
1126
1150
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : `[${JSON.stringify(name)}]`
|
|
1127
1151
|
}
|
|
1128
1152
|
|
|
1153
|
+
// `input.foo` for identifier-safe names, `input["page-size"]` otherwise.
|
|
1154
|
+
function inputAccess(name) {
|
|
1155
|
+
const accessor = jsAccessor(name)
|
|
1156
|
+
return accessor.startsWith('[') ? `input${accessor}` : `input.${accessor}`
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1129
1159
|
function capitalize(s) {
|
|
1130
1160
|
return s.charAt(0).toUpperCase() + s.slice(1)
|
|
1131
1161
|
}
|