@redocly/realm 0.138.0-next.12 → 0.138.0-next.13

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 (71) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/client/app/pages/Invite/Invite.js +1 -1
  3. package/dist/constants/l10n/langs/ar.js +1 -1
  4. package/dist/constants/l10n/langs/de.js +1 -1
  5. package/dist/constants/l10n/langs/en.js +1 -1
  6. package/dist/constants/l10n/langs/es.js +1 -1
  7. package/dist/constants/l10n/langs/fr.js +1 -1
  8. package/dist/constants/l10n/langs/hi.js +1 -1
  9. package/dist/constants/l10n/langs/it.js +1 -1
  10. package/dist/constants/l10n/langs/ja.js +1 -1
  11. package/dist/constants/l10n/langs/ko.js +1 -1
  12. package/dist/constants/l10n/langs/pl.js +1 -1
  13. package/dist/constants/l10n/langs/pt-BR.js +1 -1
  14. package/dist/constants/l10n/langs/pt.js +1 -1
  15. package/dist/constants/l10n/langs/ru.js +1 -1
  16. package/dist/constants/l10n/langs/uk.js +1 -1
  17. package/dist/constants/l10n/langs/zh.js +1 -1
  18. package/dist/server/plugins/markdown/attribute-resolvers/code-walkthrough/file-chunk-parser.d.ts +1 -1
  19. package/dist/server/plugins/markdown/attribute-resolvers/resolve-link.js +1 -1
  20. package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +1 -0
  21. package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +5 -5
  22. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +1 -1
  23. package/dist/server/plugins/mcp/gateway-mcp/secret-store.d.ts +9 -3
  24. package/dist/server/plugins/mcp/gateway-mcp/secret-store.js +1 -1
  25. package/dist/server/plugins/mcp/gateway-mcp/subject.js +1 -1
  26. package/dist/server/plugins/mcp/handlers/credentials-page/constants.d.ts +7 -0
  27. package/dist/server/plugins/mcp/handlers/credentials-page/constants.js +1 -0
  28. package/dist/server/plugins/mcp/handlers/credentials-page/form-contract.d.ts +5 -0
  29. package/dist/server/plugins/mcp/handlers/credentials-page/form-contract.js +1 -0
  30. package/dist/server/plugins/mcp/handlers/credentials-page/form-page.d.ts +14 -0
  31. package/dist/server/plugins/mcp/handlers/credentials-page/form-page.js +62 -0
  32. package/dist/server/plugins/mcp/handlers/credentials-page/layout.d.ts +17 -0
  33. package/dist/server/plugins/mcp/handlers/credentials-page/layout.js +11 -0
  34. package/dist/server/plugins/mcp/handlers/credentials-page/outcome-pages.d.ts +10 -0
  35. package/dist/server/plugins/mcp/handlers/credentials-page/outcome-pages.js +11 -0
  36. package/dist/server/plugins/mcp/handlers/credentials-page/site-branding.d.ts +10 -0
  37. package/dist/server/plugins/mcp/handlers/credentials-page/site-branding.js +1 -0
  38. package/dist/server/plugins/mcp/handlers/credentials-page/styles.d.ts +4 -0
  39. package/dist/server/plugins/mcp/handlers/credentials-page/styles.js +186 -0
  40. package/dist/server/plugins/mcp/handlers/credentials-page/types.d.ts +32 -0
  41. package/dist/server/plugins/mcp/handlers/credentials-page/types.js +0 -0
  42. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.d.ts +0 -1
  43. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +1 -1
  44. package/dist/server/plugins/mcp/index.js +1 -1
  45. package/dist/server/plugins/mcp/servers/mcp-server.js +2 -2
  46. package/dist/server/plugins/mcp/types.d.ts +6 -0
  47. package/dist/server/plugins/mcp/utils/mode.d.ts +4 -1
  48. package/dist/server/plugins/mcp/utils/mode.js +1 -1
  49. package/dist/server/plugins/scorecard-classic/template/components.js +1 -1
  50. package/dist/server/providers/database/client.d.ts +5 -0
  51. package/dist/server/providers/database/client.js +1 -1
  52. package/dist/server/providers/database/copy-migrations.js +1 -1
  53. package/dist/server/providers/database/database-connections-manager.js +1 -1
  54. package/dist/server/providers/database/has-project-database.d.ts +3 -0
  55. package/dist/server/providers/database/has-project-database.js +1 -0
  56. package/dist/server/web-server/auth.d.ts +0 -1
  57. package/dist/server/web-server/auth.js +4 -4
  58. package/dist/server/web-server/jwt/token-types.d.ts +1 -0
  59. package/dist/server/web-server/jwt/token-types.js +1 -1
  60. package/dist/server/web-server/routes/catalog/helpers/create-entity-schema.d.ts +1756 -1756
  61. package/dist/server/web-server/routes/catalog/helpers/create-entity-update-schema.d.ts +295 -295
  62. package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.d.ts +24 -0
  63. package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.js +35 -0
  64. package/dist/server/web-server/routes/mcp-routes/mcp-oauth-store.d.ts +15 -0
  65. package/dist/server/web-server/routes/mcp-routes/mcp-oauth-store.js +1 -0
  66. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.d.ts +3 -1
  67. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  68. package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -1
  69. package/package.json +5 -5
  70. package/dist/server/plugins/mcp/handlers/mcp-credentials-page.d.ts +0 -12
  71. package/dist/server/plugins/mcp/handlers/mcp-credentials-page.js +0 -225
@@ -1 +1 @@
1
- import o from"node:path";import{ASYNC_API_DOCS_TEMPLATE_ID as T,GRAPHQL_TEMPLATE_ID as v,OPENAPI_DOCS_TEMPLATE_ID as y}from"../../../../constants/common.js";import{isLocalLink as D}from"../../../../utils/path/is-local-link.js";import{normalizeRouteSlug as h}from"../../../../utils/path/normalize-route-slug.js";import{removeFragment as F}from"../../../../utils/path/remove-fragment.js";import{getInnerText as M}from"../../../../markdoc/helpers/get-inner-text.js";import{getNodeAttribute as U}from"../../../../markdoc/helpers/get-node-attribute.js";import{setNodeAttributeValue as p}from"../../../../markdoc/helpers/set-node-attribute-value.js";import{isAuthRoutePath as W}from"../../../../utils/auth/is-auth-route-path.js";import{isApiDownloadLink as j}from"../../../utils/is-api-download-link.js";import{MdResolveError as g}from"./md-resolve-error.js";import{getLinkOriginalAttrName as B}from"./utils.js";import{isOpenApiURL as $}from"../../api-docs/utils/is-api-doc.js";import{parseBaseName as C}from"../../utils.js";import{isMarkdownPage as V}from"../is-markdown-page.js";import{copyStaticFile as b}from"../../../utils/fs.js";async function at(i,s,E,{actions:k,context:f}){const{contentDir:d,outdir:S,getRouteByFsPath:w,hasRouteOrRedirectBySlug:A}=k,L=B(s),e=i[L]||U(i,s);if(i[L]=e,!D(e)||j(e)||W(e))return;const[z,t="",R,P]=/^([^?#]+)?([^#]+)?(.+)?/.exec(e)||[],c=i.type==="image"?"IMAGE":"LINK",u=String(i.attributes.title||i.attributes.alt||M([i])||""),n=F(E);if(i.type!=="image"){const{isOpenapiDetected:r,isOpenapiValid:m}=await $(t,n,k,f);if(r&&!m)throw new g(`OpenAPI route ${e} does not exist`,{rawLink:e,link:t,title:u,brokenLinkType:c})}if(o.extname(t)===""){if(t.startsWith("/")){if(!A(h(t)))throw new g(`Route ${t} does not exist`,{rawLink:e,link:t,title:u,brokenLinkType:c});p(i,s,t+(R||"")+(P||""))}if(!e.startsWith("#")&&!t.startsWith("/")&&V(n)){const r=w(n)?.slug;if(!r)return;const{isIndexFile:m}=C(n),x=m?h(o.posix.join(r,t)):h(o.posix.join(r,"../",t));if(!A(h(x)))throw new g(`Route ${t} does not exist`,{rawLink:e,link:x,title:u,brokenLinkType:c});p(i,s,x+(R||"")+(P||""))}return}const a=t.startsWith("/")?t.substring(1):o.posix.join(o.posix.dirname(n),decodeURI(t)),_=t.startsWith("/")?o.posix.join("static",t.substring(1)):"",I=f.fs.exists(a),O=f.fs.exists(_),l=w(a);if(!I&&!l&&!O)throw p(i,s,"#"),new g(`File ${a} does not exist`,{rawLink:e,link:a,title:u,brokenLinkType:c});if(l){const r=[y,v,T].includes(l.templateId);p(i,s,(r?l.baseSlug:l.slug)+(P||""))}else if(I){const r=f.fs.getFileInfo(a);if(!r||r.isVirtual)return;const m=await b(d,r.realRelativePath,S);p(i,s,m)}}export{at as resolveLink};
1
+ import n from"node:path";import{API_DOCS_TEMPLATE_ID as y,ASYNC_API_DOCS_TEMPLATE_ID as F,GRAPHQL_TEMPLATE_ID as M,OPENAPI_DOCS_TEMPLATE_ID as U}from"../../../../constants/common.js";import{isLocalLink as C}from"../../../../utils/path/is-local-link.js";import{normalizeRouteSlug as P}from"../../../../utils/path/normalize-route-slug.js";import{removeFragment as B}from"../../../../utils/path/remove-fragment.js";import{getInnerText as W}from"../../../../markdoc/helpers/get-inner-text.js";import{getNodeAttribute as j}from"../../../../markdoc/helpers/get-node-attribute.js";import{setNodeAttributeValue as c}from"../../../../markdoc/helpers/set-node-attribute-value.js";import{isAuthRoutePath as $}from"../../../../utils/auth/is-auth-route-path.js";import{isApiDownloadLink as V}from"../../../utils/is-api-download-link.js";import{MdResolveError as A}from"./md-resolve-error.js";import{getLinkOriginalAttrName as b}from"./utils.js";import{isOpenApiURL as z}from"../../api-docs/utils/is-api-doc.js";import{parseBaseName as G}from"../../utils.js";import{isMarkdownPage as N}from"../is-markdown-page.js";import{copyStaticFile as q}from"../../../utils/fs.js";async function pt(i,r,p,{actions:w,context:u}){const{contentDir:D,outdir:T,getRouteByFsPath:x,getRouteBySlug:I,hasRouteOrRedirectBySlug:d}=w,k=b(r),o=i[k]||j(i,r);if(i[k]=o,!C(o)||V(o)||$(o))return;const[H,t="",R,L]=/^([^?#]+)?([^#]+)?(.+)?/.exec(o)||[],f=i.type==="image"?"IMAGE":"LINK",g=String(i.attributes.title||i.attributes.alt||W([i])||""),a=B(p);if(i.type!=="image"){const{isOpenapiDetected:e,isOpenapiValid:s}=await z(t,a,w,u);if(e&&!s)throw new A(`OpenAPI route ${o} does not exist`,{rawLink:o,link:t,title:g,brokenLinkType:f})}if(n.extname(t)===""){if(t.startsWith("/")){const e=P(t),s=d(e)?t:_(e,I);if(!s)throw new A(`Route ${t} does not exist`,{rawLink:o,link:t,title:g,brokenLinkType:f});c(i,r,s+(R||"")+(L||""))}if(!o.startsWith("#")&&!t.startsWith("/")&&N(a)){const e=x(a)?.slug;if(!e)return;const{isIndexFile:s}=G(a),h=s?P(n.posix.join(e,t)):P(n.posix.join(e,"../",t)),E=d(P(h))?h:_(h,I);if(!E)throw new A(`Route ${t} does not exist`,{rawLink:o,link:h,title:g,brokenLinkType:f});c(i,r,E+(R||"")+(L||""))}return}const l=t.startsWith("/")?t.substring(1):n.posix.join(n.posix.dirname(a),decodeURI(t)),v=t.startsWith("/")?n.posix.join("static",t.substring(1)):"",S=u.fs.exists(l),O=u.fs.exists(v),m=x(l);if(!S&&!m&&!O)throw c(i,r,"#"),new A(`File ${l} does not exist`,{rawLink:o,link:l,title:g,brokenLinkType:f});if(m){const e=[U,M,F].includes(m.templateId);c(i,r,(e?m.baseSlug:m.slug)+(L||""))}else if(S){const e=u.fs.getFileInfo(l);if(!e||e.isVirtual)return;const s=await q(D,e.realRelativePath,T);c(i,r,s)}}function _(i,r){const p=r(i.toLowerCase());return p?.templateId===y?p.slug:void 0}export{pt as resolveLink};
@@ -8,6 +8,7 @@ export type CredentialTarget = {
8
8
  name: string;
9
9
  scheme: string;
10
10
  schemeLabel?: string;
11
+ acceptedSchemes?: string[];
11
12
  };
12
13
  /**
13
14
  * Why the prompt is being raised. `authFailure` is the automatic path behind an upstream 401;
@@ -1,5 +1,5 @@
1
- import{inputRequired as h,inputResponse as y}from"@modelcontextprotocol/server";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{MCP_CREDENTIALS_ROUTE as A}from"../../../constants/mcp.js";import{MANAGE_API_CREDENTIALS_TOOL_NAME as u}from"../constants.js";import{secretStore as l,ELICITATION_TTL_MS as P}from"./secret-store.js";import{operationsForScheme as E,orderedSchemesOf as g}from"./upstream-auth-scheme.js";import{ANONYMOUS_SUBJECT_PREFIX as p}from"./subject.js";const m="credentials",d=new Map;function F(e,t){const r=d.get(e);if(r)return r;const n=t().finally(()=>{d.delete(e)});return d.set(e,n),n}function D(e){return!!e?.elicitation?.url}function H(e){return e.envelope!==void 0}function q(e){const t=y(e,m);if(t.kind==="elicit")return t.action==="accept"?"accepted":"declined"}function B(e,t){return[`The user accepted the prompt to add credentials for the "${e}" API.`,...t?["The credentials page:",t]:[],"",`Wait until the user says they saved the credentials, then retry the call that needed them. Do not call \`${u}\` again unless the user asks for a new link.`].join(`
2
- `)}async function Y(e,{kv:t,subject:r,baseUrl:n,apis:o}){if(typeof e!="string")return;const i=await l.peekElicitation(t,e);if(!(i?.subject!==r||!o.includes(i.api)))return{apiName:i.name,url:T(n,e)}}function W(e,t){return Object.keys(e).length>1?t:void 0}async function z(e){const{token:t,url:r}=await f(e);return h({inputRequests:{[m]:h.elicitUrl({message:C(e),url:r})},requestState:t})}async function G(e){const{url:t}=await f(e);return _(e,t,S(e.subject))}function J(e,t){const r=b(e);if(e.status===401&&!e.sentScheme){const[i,s]=e.callerHeader?[`Your code set the "${e.callerHeader}" header itself, so the stored credential was not sent.`,"Remove that header from the request and retry. Do not replace the stored credentials."]:["No stored credential was sent with this request, so this result says nothing about the stored credentials.","Do not replace them. Check whether this operation needs a different kind of credential or a different route."];return[`The "${e.name}" API returned ${r}. ${i}`,"",s].join(`
3
- `)}const n=e.status===401?`The "${e.name}" API rejected the stored ${t?`"${t}" `:""}credential (${r}).`:`The "${e.name}" API rejected the request with ${r}.`,o=t?`api: "${e.api}" and scheme: "${t}"`:`api: "${e.api}"`;return[n,"",`Do not retry the same call. Call \`${u}\` with ${o} to get a link the user has to open in a browser to replace or remove the stored credentials, then retry once they confirm they saved them.`].join(`
4
- `)}function b({status:e,detail:t}){return t?`HTTP ${e}: ${t}`:`HTTP ${e}`}async function f({kv:e,subject:t,baseUrl:r,target:n,auth:o,apiAuth:i,hasExistingSecret:s}){const a=i?await k(e,t,n.api,i):[],c=await l.createElicitation(e,{subject:t,api:n.api,name:n.name,scheme:n.scheme,...n.schemeLabel?{schemeLabel:n.schemeLabel}:{},auth:o,hasExistingSecret:s,...a.length>1?{schemes:a}:{}});return{token:c,url:T(r,c)}}async function k(e,t,r,n){const i=(await l.getSecretsForSubject(e,t))[r]??{};return g(n.schemes).map(s=>{const a=E(n,s),c=n.descriptions?.[s];return{name:s,...c?{label:c}:{},auth:n.schemes[s],hasExistingSecret:!!i[s],...a.length>0?{operations:a}:{}}})}function S(e){return e.startsWith(p)?e.slice(p.length):void 0}function C({target:e,hasExistingSecret:t,reason:r}){const n=`The link works for ${$()} minutes.`,o=x(e),i=r.kind==="authFailure"&&r.detail?` The API answered: ${r.detail}.`:"";return t?`Open this page in a browser to replace or remove the stored credentials for ${o}. They go straight to the API and never enter the chat. ${n}`:`${O(o)} needs credentials.${i} Open this page in a browser to paste them - they go straight to the API and never enter the chat. ${n}`}function x({name:e,schemeLabel:t}){return t?`the "${t}" scheme of the "${e}" API`:`the "${e}" API`}function O(e){return e.charAt(0).toUpperCase()+e.slice(1)}function $(){return Math.round(P/6e4)}function T(e,t){const r=I(A);if(!e)return`${r}?token=${t}`;try{const n=new URL(r,e);return n.searchParams.set("token",t),n.toString()}catch{return`${e}${r}?token=${t}`}}function _({target:e,reason:t,hasExistingSecret:r},n,o){const s=[`**Stop and show the user this link. They have to open it in a browser to ${r?`replace or remove the stored credentials for the "${e.name}" API`:`add credentials for the "${e.name}" API`} - nothing continues until they do.**`,"",n,"",`The link is valid ${$()} minutes and opens a form on this site. Credentials go straight to the API from there: never ask for them in the conversation and never pass them as a tool argument.`,"",t.kind==="authFailure"?`The upstream API "${e.name}" returned ${t.status}${t.detail?` (${t.detail})`:""} and needs credentials.`:r?`Credentials are already stored for the "${e.name}" API; the link lets the user replace or remove them.`:`The "${e.name}" API needs credentials.`,...e.schemeLabel?[`The operation authenticates with its "${e.schemeLabel}" security scheme.`]:[],o?`Once the user confirms they saved them, call this tool again with sessionHandle: "${o}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Once the user confirms they saved them, call this tool again - the credentials are applied server-side and never appear in the conversation."],a=t.kind==="authFailure"?t.otherApiNames??[]:[];if(a.length>0){const c=a.map(w=>`"${w}"`).join(", ");s.push("",`The remaining APIs (${c}) will prompt for credentials on subsequent calls.`)}return s.join(`
5
- `)}export{m as CREDENTIALS_INPUT_REQUEST_KEY,Y as acceptedCredentialsLink,B as buildCredentialsAcceptedHint,J as buildCredentialsToolHint,G as buildUpstreamCredentialFallback,z as buildUpstreamCredentialInputRequired,q as credentialElicitationAnswer,F as dedupeElicitation,H as isStatelessRequest,W as schemeLabelOf,D as supportsUrlElicitation};
1
+ import{inputRequired as d,inputResponse as w}from"@modelcontextprotocol/server";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{MCP_CREDENTIALS_ROUTE as E}from"../../../constants/mcp.js";import{MANAGE_API_CREDENTIALS_TOOL_NAME as h}from"../constants.js";import{secretStore as l,ELICITATION_TTL_MINUTES as u}from"./secret-store.js";import{operationsForScheme as A,orderedSchemesOf as P}from"./upstream-auth-scheme.js";import{ANONYMOUS_SUBJECT_PREFIX as p}from"./subject.js";const m="credentials",c=new Map;function D(e,t){const n=c.get(e);if(n)return n;const r=t().finally(()=>{c.delete(e)});return c.set(e,r),r}function F(e){return!!e?.elicitation?.url}function H(e){return e.envelope!==void 0}function M(e){const t=w(e,m);if(t.kind==="elicit")return t.action==="accept"?"accepted":"declined"}function q(e,t){return[`The user accepted the prompt to add credentials for the "${e}" API.`,...t?["The credentials page:",t]:[],"",`Wait until the user says they saved the credentials, then retry the call that needed them. Do not call \`${h}\` again unless the user asks for a new link.`].join(`
2
+ `)}async function Y(e,{kv:t,subject:n,baseUrl:r,apis:i}){if(typeof e!="string")return;const s=await l.peekElicitation(t,e);if(!(s?.subject!==n||!i.includes(s.api)))return{apiName:s.name,url:$(r,e)}}function B(e,t){return Object.keys(e).length>1?t:void 0}async function W(e){const{token:t,url:n}=await f(e);return d({inputRequests:{[m]:d.elicitUrl({message:C(e),url:n})},requestState:t})}async function z(e){const{url:t}=await f(e);return g(e,t,S(e.subject))}function G(e,t){const n=b(e);if(e.status===401&&!e.sentScheme){const[s,a]=e.callerHeader?[`Your code set the "${e.callerHeader}" header itself, so the stored credential was not sent.`,"Remove that header from the request and retry. Do not replace the stored credentials."]:["No stored credential was sent with this request, so this result says nothing about the stored credentials.","Do not replace them. Check whether this operation needs a different kind of credential or a different route."];return[`The "${e.name}" API returned ${n}. ${s}`,"",a].join(`
3
+ `)}const r=e.status===401?`The "${e.name}" API rejected the stored ${t?`"${t}" `:""}credential (${n}).`:`The "${e.name}" API rejected the request with ${n}.`,i=t?`api: "${e.api}" and scheme: "${t}"`:`api: "${e.api}"`;return[r,"",`Do not retry the same call. Call \`${h}\` with ${i} to get a link the user has to open in a browser to replace or remove the stored credentials, then retry once they confirm they saved them.`].join(`
4
+ `)}function b({status:e,detail:t}){return t?`HTTP ${e}: ${t}`:`HTTP ${e}`}async function f({kv:e,subject:t,baseUrl:n,target:r,auth:i,apiAuth:s}){const a=s?k(s):[],o=await l.createElicitation(e,{subject:t,api:r.api,name:r.name,scheme:r.scheme,...r.schemeLabel?{schemeLabel:r.schemeLabel}:{},auth:i,...a.length>1?{schemes:a}:{},...a.length>1&&r.acceptedSchemes?{acceptedSchemes:r.acceptedSchemes}:{}});return{token:o,url:$(n,o)}}function k(e){return P(e.schemes).map(t=>{const n=A(e,t),r=e.descriptions?.[t];return{name:t,...r?{label:r}:{},auth:e.schemes[t],...n.length>0?{operations:n}:{}}})}function S(e){return e.startsWith(p)?e.slice(p.length):void 0}function C({target:e,hasExistingSecret:t,reason:n}){const r=`The link works for ${u} minutes.`,i=x(e),s=n.kind==="authFailure"&&n.detail?` The API answered: ${n.detail}.`:"";return t?`Open this page in a browser to replace or remove the stored credentials for ${i}. They stay out of the chat. ${r}`:`${O(i)} needs credentials.${s} Open this page in a browser to paste them - they stay out of the chat. ${r}`}function x({name:e,schemeLabel:t}){return t?`the "${t}" scheme of the "${e}" API`:`the "${e}" API`}function O(e){return e.charAt(0).toUpperCase()+e.slice(1)}function $(e,t){const n=I(E);if(!e)return`${n}?token=${t}`;try{const r=new URL(n,e);return r.searchParams.set("token",t),r.toString()}catch{return`${e}${n}?token=${t}`}}function g({target:e,reason:t,hasExistingSecret:n},r,i){const a=[`**Stop and show the user this link. They have to open it in a browser to ${n?`replace or remove the stored credentials for the "${e.name}" API`:`add credentials for the "${e.name}" API`} - nothing continues until they do.**`,"",r,"",`The link is valid ${u} minutes and opens a form on this site. The site stores the credentials and adds them to API calls: never ask for them in the conversation and never pass them as a tool argument.`,"",t.kind==="authFailure"?`The upstream API "${e.name}" returned ${t.status}${t.detail?` (${t.detail})`:""} and needs credentials.`:n?`Credentials are already stored for the "${e.name}" API; the link lets the user replace or remove them.`:`The "${e.name}" API needs credentials.`,...e.schemeLabel?[`The operation authenticates with its "${e.schemeLabel}" security scheme.`]:[],i?`Once the user confirms they saved them, retry the call that needed them with sessionHandle: "${i}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Once the user confirms they saved them, retry the call that needed them - the credentials are applied server-side and never appear in the conversation."],o=t.kind==="authFailure"?t.otherApiNames??[]:[];if(o.length>0){const T=o.map(y=>`"${y}"`).join(", ");a.push("",`The remaining APIs (${T}) will prompt for credentials on subsequent calls.`)}return a.join(`
5
+ `)}export{m as CREDENTIALS_INPUT_REQUEST_KEY,Y as acceptedCredentialsLink,q as buildCredentialsAcceptedHint,G as buildCredentialsToolHint,z as buildUpstreamCredentialFallback,W as buildUpstreamCredentialInputRequired,M as credentialElicitationAnswer,D as dedupeElicitation,H as isStatelessRequest,B as schemeLabelOf,F as supportsUrlElicitation};
@@ -1 +1 @@
1
- import{envConfig as o}from"../../../../config/env-config.js";import{KvRepository as s}from"../../../../persistence/kv/repositories/kv-repository.js";import{logger as m}from"../../../../tools/notifiers/logger.js";import{ipRateLimitKey as p}from"./ip-key.js";import{checkEdgeRateLimit as c}from"./edge-limiter.js";import{resolveGatewayFetchLimits as d}from"./limits.js";import{reportMcpRateLimited as f}from"./rate-limit-telemetry.js";async function S(n,a){const i=p(n);if(!i)return;let e,t;try{const r=await s.create({baseDbDir:a.serverOutDir,sqldRemoteDatabaseUrl:o.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:o.SQLD_REMOTE_DATABASE_AUTH_TOKEN});t=d(),e=await c({kv:r,ip:i,limits:t,now:Date.now()})}catch(r){m.warn(`gateway rate-limit init error, failing open: ${String(r)}`);return}if(!e.allowed)return f({scope:e.scope,retryAfterSeconds:e.retryAfterSeconds,limit:e.scope==="rate"?t.ratePerMin:t.quotaPerDay}),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32029,message:`Rate limit exceeded (${e.scope}); retry after ${e.retryAfterSeconds}s`},id:null}),{status:429,headers:{"Content-Type":"application/json","Retry-After":String(e.retryAfterSeconds)}})}export{S as applyEdgeRateLimit};
1
+ import{envConfig as o}from"../../../../config/env-config.js";import{isRealmOrReef as s}from"../../../../utils/is-realm-or-reef.js";import{KvRepository as m}from"../../../../persistence/kv/repositories/kv-repository.js";import{logger as f}from"../../../../tools/notifiers/logger.js";import{ipRateLimitKey as p}from"./ip-key.js";import{checkEdgeRateLimit as c}from"./edge-limiter.js";import{resolveGatewayFetchLimits as d}from"./limits.js";import{reportMcpRateLimited as l}from"./rate-limit-telemetry.js";async function E(n,a){if(!s())return;const i=p(n);if(!i)return;let e,r;try{const t=await m.create({baseDbDir:a.serverOutDir,sqldRemoteDatabaseUrl:o.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:o.SQLD_REMOTE_DATABASE_AUTH_TOKEN});r=d(),e=await c({kv:t,ip:i,limits:r,now:Date.now()})}catch(t){f.warn(`gateway rate-limit init error, failing open: ${String(t)}`);return}if(!e.allowed)return l({scope:e.scope,retryAfterSeconds:e.retryAfterSeconds,limit:e.scope==="rate"?r.ratePerMin:r.quotaPerDay}),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32029,message:`Rate limit exceeded (${e.scope}); retry after ${e.retryAfterSeconds}s`},id:null}),{status:429,headers:{"Content-Type":"application/json","Retry-After":String(e.retryAfterSeconds)}})}export{E as applyEdgeRateLimit};
@@ -1,5 +1,6 @@
1
1
  import type { KvService } from '@redocly/config';
2
2
  import type { UpstreamAuthScheme } from './upstream-auth-scheme.js';
3
+ export declare const ELICITATION_TTL_MINUTES = 15;
3
4
  export declare const ELICITATION_TTL_MS: number;
4
5
  export type StoredSecret = {
5
6
  header: string;
@@ -11,7 +12,6 @@ export type ElicitationScheme = {
11
12
  name: string;
12
13
  label?: string;
13
14
  auth: UpstreamAuthScheme;
14
- hasExistingSecret: boolean;
15
15
  operations?: string[];
16
16
  };
17
17
  export type ElicitationTarget = {
@@ -21,17 +21,23 @@ export type ElicitationTarget = {
21
21
  scheme: string;
22
22
  schemeLabel?: string;
23
23
  auth: UpstreamAuthScheme;
24
- hasExistingSecret?: boolean;
25
24
  schemes?: ElicitationScheme[];
25
+ /** The schemes the failing operation accepts, when it accepts more than one. */
26
+ acceptedSchemes?: string[];
27
+ expiresAt: number;
26
28
  };
29
+ export type StoredSchemes = Record<string, {
30
+ savedAt?: number;
31
+ }>;
27
32
  export declare class SecretStore {
28
33
  #private;
29
34
  setSecret(kv: KvService, subject: string, api: string, scheme: string, secret: StoredSecret): Promise<void>;
30
35
  getSecretsForSubject(kv: KvService, subject: string): Promise<UpstreamSecrets>;
36
+ getStoredSchemes(kv: KvService, subject: string, api: string): Promise<StoredSchemes>;
31
37
  hasSecret(kv: KvService, subject: string, api: string, scheme: string): Promise<boolean>;
32
38
  deleteSecret(kv: KvService, subject: string, api: string, scheme: string): Promise<void>;
33
39
  deleteSubject(kv: KvService, subject: string): Promise<void>;
34
- createElicitation(kv: KvService, target: ElicitationTarget): Promise<string>;
40
+ createElicitation(kv: KvService, target: Omit<ElicitationTarget, 'expiresAt'>): Promise<string>;
35
41
  peekElicitation(kv: KvService, token: string): Promise<ElicitationTarget | undefined>;
36
42
  consumeElicitation(kv: KvService, token: string): Promise<ElicitationTarget | undefined>;
37
43
  }
@@ -1 +1 @@
1
- import{createCipheriv as h,createDecipheriv as m,createHash as d,hkdfSync as o,randomBytes as l,randomUUID as p}from"node:crypto";import{JWT_SECRET_KEY as f}from"../../../constants/common.js";import{assertHeaderSafe as y}from"./credential-validation.js";import{secretTtlSeconds as S}from"./subject.js";const g=900*1e3,I="mcp-upstream-secrets",T="mcp-elicitation",w="mcp-elicitation";class A{async setSecret(t,e,r,s,i){y(i.header,i.value);const n={...i,savedAt:Date.now()},a={api:r,scheme:s,...this.#o(this.#c(e),n,this.#n(r,s))};await t.set(this.#t(e,r,s),a,{ttlInSeconds:S(e)})}async getSecretsForSubject(t,e){const r=this.#c(e),{items:s}=await t.list({prefix:this.#i(e)}),i={};for(const n of s){if(!n.value?.scheme)continue;const{api:a,scheme:c}=n.value;try{const u=this.#s(r,n.value,this.#n(a,c));i[a]={...i[a],[c]:u}}catch{continue}}return i}async hasSecret(t,e,r,s){return!!await t.get(this.#t(e,r,s))}async deleteSecret(t,e,r,s){await t.delete(this.#t(e,r,s))}async deleteSubject(t,e){const{items:r}=await t.list({prefix:this.#i(e)}),s=r.flatMap(i=>i.value?[i.key]:[]);s.length!==0&&await t.transaction(async i=>{for(const n of s)await i.delete(n)})}async createElicitation(t,e){const r=p();return await t.set(this.#e(r),this.#o(this.#r(),e),{ttlInSeconds:g/1e3}),r}async peekElicitation(t,e){const r=await t.get(this.#e(e));if(r)try{return this.#s(this.#r(),r)}catch{return}}async consumeElicitation(t,e){const r=this.#e(e),s=await t.transaction(async i=>{const n=await i.get(r);return n&&await i.delete(r),n});if(s)try{return this.#s(this.#r(),s)}catch{return}}#i(t){return["mcp","secret",this.#a(t)]}#t(t,e,r){return["mcp","secret",this.#a(t),e,r]}#n(t,e){return`${t}\0${e}`}#e(t){return["mcp","elicitation",t]}#a(t){return d("sha256").update(t).digest("hex")}#c(t){return Buffer.from(o("sha256",f,t,I,32))}#r(){return Buffer.from(o("sha256",f,w,T,32))}#o(t,e,r){const s=l(12),i=h("aes-256-gcm",t,s);r&&i.setAAD(Buffer.from(r,"utf8"));const n=Buffer.concat([i.update(JSON.stringify(e),"utf8"),i.final()]);return{iv:s.toString("base64"),tag:i.getAuthTag().toString("base64"),data:n.toString("base64")}}#s(t,e,r){const s=m("aes-256-gcm",t,Buffer.from(e.iv,"base64"));r&&s.setAAD(Buffer.from(r,"utf8")),s.setAuthTag(Buffer.from(e.tag,"base64"));const i=Buffer.concat([s.update(Buffer.from(e.data,"base64")),s.final()]).toString("utf8");return JSON.parse(i)}}const N=new A;export{g as ELICITATION_TTL_MS,A as SecretStore,N as secretStore};
1
+ import{createCipheriv as d,createDecipheriv as l,createHash as p,hkdfSync as f,randomBytes as y,randomUUID as S}from"node:crypto";import{JWT_SECRET_KEY as u}from"../../../constants/common.js";import{assertHeaderSafe as I}from"./credential-validation.js";import{secretTtlSeconds as T}from"./subject.js";const g=15,h=g*60*1e3,w="mcp-upstream-secrets",A="mcp-elicitation",E="mcp-elicitation";class v{async setSecret(t,e,r,s,n){I(n.header,n.value);const i={...n,savedAt:Date.now()},a={api:r,scheme:s,...this.#f(this.#o(e),i,this.#a(r,s))};await t.set(this.#e(e,r,s),a,{ttlInSeconds:T(e)})}async getSecretsForSubject(t,e){return this.#i(t,e,this.#t(e))}async getStoredSchemes(t,e,r){const s=[...this.#t(e),r],n=(await this.#i(t,e,s))[r]??{};return Object.fromEntries(Object.entries(n).map(([i,{savedAt:a}])=>[i,{savedAt:a}]))}async#i(t,e,r){const s=this.#o(e),{items:n}=await t.list({prefix:r}),i={};for(const a of n){if(!a.value?.scheme)continue;const{api:c,scheme:o}=a.value;try{const m=this.#n(s,a.value,this.#a(c,o));i[c]={...i[c],[o]:m}}catch{continue}}return i}async hasSecret(t,e,r,s){return!!await t.get(this.#e(e,r,s))}async deleteSecret(t,e,r,s){await t.delete(this.#e(e,r,s))}async deleteSubject(t,e){const{items:r}=await t.list({prefix:this.#t(e)}),s=r.flatMap(n=>n.value?[n.key]:[]);s.length!==0&&await t.transaction(async n=>{for(const i of s)await n.delete(i)})}async createElicitation(t,e){const r=S(),s={...e,expiresAt:Date.now()+h};return await t.set(this.#r(r),this.#f(this.#s(),s),{ttlInSeconds:h/1e3}),r}async peekElicitation(t,e){const r=await t.get(this.#r(e));if(r)try{return this.#n(this.#s(),r)}catch{return}}async consumeElicitation(t,e){const r=this.#r(e),s=await t.transaction(async n=>{const i=await n.get(r);return i&&await n.delete(r),i});if(s)try{return this.#n(this.#s(),s)}catch{return}}#t(t){return["mcp","secret",this.#c(t)]}#e(t,e,r){return["mcp","secret",this.#c(t),e,r]}#a(t,e){return`${t}\0${e}`}#r(t){return["mcp","elicitation",t]}#c(t){return p("sha256").update(t).digest("hex")}#o(t){return Buffer.from(f("sha256",u,t,w,32))}#s(){return Buffer.from(f("sha256",u,E,A,32))}#f(t,e,r){const s=y(12),n=d("aes-256-gcm",t,s);r&&n.setAAD(Buffer.from(r,"utf8"));const i=Buffer.concat([n.update(JSON.stringify(e),"utf8"),n.final()]);return{iv:s.toString("base64"),tag:n.getAuthTag().toString("base64"),data:i.toString("base64")}}#n(t,e,r){const s=l("aes-256-gcm",t,Buffer.from(e.iv,"base64"));r&&s.setAAD(Buffer.from(r,"utf8")),s.setAuthTag(Buffer.from(e.tag,"base64"));const n=Buffer.concat([s.update(Buffer.from(e.data,"base64")),s.final()]).toString("utf8");return JSON.parse(n)}}const K=new v;export{g as ELICITATION_TTL_MINUTES,h as ELICITATION_TTL_MS,v as SecretStore,K as secretStore};
@@ -1 +1 @@
1
- import{randomBytes as r}from"node:crypto";const i=32,S=/^[A-Za-z0-9_-]{43}$/,c=1440*60,u=720*60*60,s="anon:",d="user:",f="This API requires credentials and the caller is anonymous; call createAnonymousSession first, then retry with sessionHandle",a="The sessionHandle is not a valid handle; call createAnonymousSession to obtain one";function N(){return r(i).toString("base64url")}function E(e){return S.test(e)}function l(e){if(!e?.isAuthenticated)return;const n=e.claims,o=(typeof n?.sub=="string"&&n.sub?n.sub:void 0)??(e.email||void 0);if(o)return`${d}${e.idpId??"local"}:${o}`}function A(e,n){const t=l(e);return t?{kind:"resolved",subject:t}:n===void 0?{kind:"anonymous"}:E(n)?{kind:"resolved",subject:`${s}${n}`}:{kind:"invalid",message:a}}function T(e){return e.startsWith(s)?c:u}export{s as ANONYMOUS_SUBJECT_PREFIX,a as MALFORMED_SESSION_HANDLE_MESSAGE,f as MISSING_SESSION_HANDLE_MESSAGE,d as USER_SUBJECT_PREFIX,N as createSessionHandle,E as isValidSessionHandle,A as resolveSecretSubject,T as secretTtlSeconds,l as userSubject};
1
+ import{randomBytes as r}from"node:crypto";const i=32,S=/^[A-Za-z0-9_-]{43}$/,c=1440*60,u=10080*60,s="anon:",d="user:",f="This API requires credentials and the caller is anonymous; call createAnonymousSession first, then retry with sessionHandle",a="The sessionHandle is not a valid handle; call createAnonymousSession to obtain one";function N(){return r(i).toString("base64url")}function E(e){return S.test(e)}function l(e){if(!e?.isAuthenticated)return;const n=e.claims,o=(typeof n?.sub=="string"&&n.sub?n.sub:void 0)??(e.email||void 0);if(o)return`${d}${e.idpId??"local"}:${o}`}function A(e,n){const t=l(e);return t?{kind:"resolved",subject:t}:n===void 0?{kind:"anonymous"}:E(n)?{kind:"resolved",subject:`${s}${n}`}:{kind:"invalid",message:a}}function T(e){return e.startsWith(s)?c:u}export{s as ANONYMOUS_SUBJECT_PREFIX,a as MALFORMED_SESSION_HANDLE_MESSAGE,f as MISSING_SESSION_HANDLE_MESSAGE,d as USER_SUBJECT_PREFIX,N as createSessionHandle,E as isValidSessionHandle,A as resolveSecretSubject,T as secretTtlSeconds,l as userSubject};
@@ -0,0 +1,7 @@
1
+ export declare const FORM_ACTIONS: {
2
+ readonly save: 'save';
3
+ readonly remove: 'remove';
4
+ readonly removeAll: 'remove-all';
5
+ readonly decline: 'decline';
6
+ };
7
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ const e={save:"save",remove:"remove",removeAll:"remove-all",decline:"decline"};export{e as FORM_ACTIONS};
@@ -0,0 +1,5 @@
1
+ import type { ElicitationScheme, ElicitationTarget } from '../../gateway-mcp/secret-store.js';
2
+ import type { SchemeField } from './types.js';
3
+ export declare function schemeFieldName(field: SchemeField, schemeIndex: number): string;
4
+ export declare function credentialSchemesOf(target: ElicitationTarget): ElicitationScheme[];
5
+ //# sourceMappingURL=form-contract.d.ts.map
@@ -0,0 +1 @@
1
+ function c(e,n){return`${e}__${n}`}function h(e){return e.schemes??[{name:e.scheme,auth:e.auth}]}export{h as credentialSchemesOf,c as schemeFieldName};
@@ -0,0 +1,14 @@
1
+ import type { ElicitationTarget, StoredSchemes } from '../../gateway-mcp/secret-store.js';
2
+ import type { FormError, PageContent, PageSite } from './types.js';
3
+ type FormPageInput = {
4
+ site: PageSite;
5
+ target: ElicitationTarget;
6
+ token: string;
7
+ stored: StoredSchemes;
8
+ error?: FormError;
9
+ /** Client IDs to put back into the OAuth2 fields after a rejected submission, by scheme index. */
10
+ clientIds?: readonly string[];
11
+ };
12
+ export declare function formPage(input: FormPageInput): PageContent;
13
+ export {};
14
+ //# sourceMappingURL=form-page.d.ts.map
@@ -0,0 +1,62 @@
1
+ import{withPathPrefix as D}from"@redocly/theme/core/utils";import{MCP_CREDENTIALS_ROUTE as R}from"../../../../constants/mcp.js";import{ELICITATION_TTL_MS as _}from"../../gateway-mcp/secret-store.js";import{secretTtlSeconds as I}from"../../gateway-mcp/subject.js";import{FORM_ACTIONS as $}from"./constants.js";import{credentialSchemesOf as F,schemeFieldName as M}from"./form-contract.js";import{ICONS as v,code as h,escapeHtml as r,siteHeader as N,targetTitle as A}from"./layout.js";const S=10,g="form-error",T=new Intl.DateTimeFormat("en-GB",{day:"numeric",month:"short",year:"numeric",timeZone:"UTC"}),P={bearer:{title:"API token",noun:"API token",placeholder:"Paste your API token"},apiKey:{title:"API key",noun:"API key",placeholder:"Paste your API key"},cookie:{title:"Cookie",noun:"cookie value",placeholder:"Paste the cookie value"},oauth2ClientCredentials:{title:"OAuth2",noun:"access token",placeholder:"Paste an access token"},raw:{title:"Authorization header",noun:"header value",placeholder:"Bearer <token>"}};function te(e){const{site:t,target:n,token:o,error:s}=e,a=D(R),u=F(n),i=u.length>1,c=L(e,u),l=c.some(f=>f.isStored),d=i&&l,m=c.map(f=>i?U(f):B(f)).join(""),p=d?'<button class="text-danger" type="submit" form="remove-all">Remove all credentials</button>':"",O=s?`<div class="banner" role="alert" id="${g}">${v.alert}<p>${r(s.message)}</p></div>`:"";return{title:`Credentials for ${A(n)}`,body:`<div class="page">
2
+ <main class="card">
3
+ ${N(t)}
4
+ <h1>${l?"Update":"Add"} credentials for ${r(A(n))}</h1>
5
+ <p class="lede">${i?"This API accepts more than one kind of credential. Fill in the one that you have.":"This API needs one credential to authenticate your calls."}</p>
6
+ ${O}
7
+ <form class="credentials" method="post" action="${a}" autocomplete="off">
8
+ ${y(o,$.save)}
9
+ <div class="schemes">${m}</div>
10
+ <div class="actions">
11
+ <div class="actions-row">${p}<button class="primary" type="submit">Save credentials</button></div>
12
+ <p class="decline small">Did not ask for this link? <button class="link-button" type="submit" form="decline">Decline it</button></p>
13
+ </div>
14
+ </form>
15
+ ${H(a,o,c,d)}
16
+ </main>
17
+ ${W(n,t.host)}
18
+ </div>`}}function L(e,t){const{target:n,stored:o,error:s,clientIds:a}=e,u=n.acceptedSchemes??[n.scheme],i=s?.schemeIndex??Math.max(0,t.findIndex(({name:l})=>u.includes(l))),c=t.length>1;return t.map((l,d)=>{const m=o[l.name],p=s?.schemeIndex===d?s:void 0;return{scheme:l,index:d,isMulti:c,title:c?l.name:P[l.auth.kind].title,savedAt:m?.savedAt,deletedAt:m?.savedAt===void 0?void 0:m.savedAt+I(n.subject)*1e3,isStored:m!==void 0,isOpen:d===i||u.includes(l.name)||m!==void 0,error:p,clientId:a?.[d]??"",autofocusField:d===i?p?.field??"secret":void 0}})}function B(e){return`<section class="scheme" aria-labelledby="scheme-${e.index}-title">
19
+ <div class="scheme-body">
20
+ <div class="scheme-head">
21
+ <h2 id="scheme-${e.index}-title">${r(e.title)}</h2>
22
+ <p>${E(e.scheme.auth)}</p>
23
+ </div>
24
+ ${C(e)}
25
+ </div>
26
+ </section>`}function U(e){const t=e.isStored?'<span class="tag stored">Stored</span>':'<span class="tag">Not set</span>';return`<details class="scheme" aria-labelledby="scheme-${e.index}-title"${e.isOpen?" open":""}>
27
+ <summary><span class="summary-title" id="scheme-${e.index}-title">${r(e.title)}</span><span class="summary-tag">${t}</span><span class="summary-line">${E(e.scheme.auth)}</span>${e.scheme.label?`<span class="summary-line">${r(e.scheme.label)}</span>`:""}</summary>
28
+ <div class="scheme-body">
29
+ ${C(e)}
30
+ </div>
31
+ </details>`}function C(e){const t=e.isStored?j(e):"",n=e.isMulti?K(e.scheme.operations):"";return e.scheme.auth.kind==="oauth2ClientCredentials"?`${t}${x(e,!1)}${n}${z(e)}`:`${t}${x(e,!e.isMulti&&!e.isStored)}${n}`}function j({savedAt:e,deletedAt:t,index:n,title:o}){return`<div class="stored-box">
32
+ <div>${e===void 0||t===void 0?"<p>A credential is stored for this scheme.</p>":`<p>Saved on ${T.format(e)}</p><p class="muted small">Kept until ${T.format(t)} at ${b(t)} UTC</p>`}</div>
33
+ <button class="text-danger small" type="submit" form="remove-${n}" aria-label="Remove the stored ${r(o)} credential">Remove</button>
34
+ </div>`}function x(e,t){const{auth:n}=e.scheme,{noun:o,placeholder:s}=P[n.kind];return k({view:e,field:"secret",type:"password",label:e.isStored?`Replace the ${o}`:Y(o),placeholder:e.isStored&&e.isMulti?"Leave empty to keep the stored value":s,hint:q(n),isRequired:t})}function z(e){return`<hr class="divider">
35
+ <div class="scheme-head">
36
+ <p class="strong">No token yet?</p>
37
+ <p>We exchange your client ID and secret for a token and store only the token.</p>
38
+ </div>
39
+ <div class="field-row">
40
+ ${k({view:e,field:"client_id",type:"text",label:"Client ID",placeholder:"Enter the client ID",value:e.clientId})}
41
+ ${k({view:e,field:"client_secret",type:"password",label:"Client secret",placeholder:"Enter the client secret"})}
42
+ </div>`}function k({view:e,field:t,type:n,label:o,placeholder:s,hint:a,value:u,isRequired:i}){const c=M(t,e.index),l=e.error!==void 0&&e.error.field===t,d=`${c}-hint`,m=[a?d:"",l?g:""].filter(Boolean).join(" "),p=[`type="${n}"`,`id="${c}"`,`name="${c}"`,`placeholder="${r(s)}"`,'autocomplete="off"','autocapitalize="off"','spellcheck="false"',u?`value="${r(u)}"`:"",m?`aria-describedby="${m}"`:"",l?'aria-invalid="true"':"",i?"required":"",e.autofocusField===t?"autofocus":""].filter(Boolean);return`<div class="field">
43
+ <label for="${c}">${r(o)}</label>
44
+ <input ${p.join(" ")}>
45
+ ${a?`<p class="muted small" id="${d}">${a}</p>`:""}
46
+ </div>`}function K(e){if(!e||e.length===0)return"";const t=e.slice(0,S).map(s=>`<li>${h(s)}</li>`).join(""),n=e.length-S,o=n>0?`<li class="muted">and ${n} more</li>`:"";return`<details class="operations"><summary>Which operations use it</summary><ul>${t}${o}</ul></details>`}function W({name:e,subject:t,expiresAt:n},o){const s=`${v.shield}What happens to your credentials`,a=`<ol class="steps">
47
+ <li>Sent straight to ${r(o)}, never through your AI client</li>
48
+ <li>Stored encrypted and never shown again</li>
49
+ <li>Added only to calls to ${r(e)} from the AI client that asked for this link</li>
50
+ <li>You can delete them anytime</li>
51
+ </ol>`;return`<aside class="panel" aria-labelledby="panel-title">
52
+ <div class="panel-steps"><h2 id="panel-title">${s}</h2>${a}</div>
53
+ <details class="panel-steps-toggle"><summary><h2>${s}</h2></summary>${a}</details>
54
+ <div class="link-facts">
55
+ <p>Link requested at ${b(n-_)} UTC</p>
56
+ <span class="pill">Open until ${b(n)} UTC</span>
57
+ <p>This link works one time.</p>
58
+ <p>Kept for ${V(I(t))} after you save them.</p>
59
+ <p>To delete them, ask your AI client for a new link.</p>
60
+ </div>
61
+ </aside>`}function H(e,t,n,o){const s=n.filter(i=>i.isStored).map(i=>`<form id="remove-${i.index}" method="post" action="${e}">${y(t,$.remove)}<input type="hidden" name="scheme" value="${i.index}"></form>`),a=o?`<form id="remove-all" method="post" action="${e}">${y(t,$.removeAll)}</form>`:"",u=`<form id="decline" method="post" action="${e}">${y(t,$.decline)}</form>`;return[...s,a,u].join(`
62
+ `)}function y(e,t){return`<input type="hidden" name="token" value="${r(e)}"><input type="hidden" name="action" value="${t}">`}function E(e){return e.kind==="apiKey"?`Sent to the API as the ${h(e.header)} header.`:e.kind==="cookie"?`Sent to the API as the ${h(e.name)} cookie.`:`Sent to the API as the ${h("Authorization")} header.`}function q(e){if(e.kind==="cookie")return`Paste only the value. We send it as ${h(`${e.name}=<value>`)}.`;if(e.kind==="raw")return`Paste the full header value, for example ${h("Bearer <token>")}.`}function b(e){return new Date(e).toISOString().slice(11,16)}function V(e){const t=e/86400;return Number.isInteger(t)&&t>1?`${t} days`:`${Math.round(e/3600)} hours`}function Y(e){return e.charAt(0).toUpperCase()+e.slice(1)}export{te as formPage};
@@ -0,0 +1,17 @@
1
+ import type { ElicitationTarget } from '../../gateway-mcp/secret-store.js';
2
+ import type { PageColorScheme, PageContent, PageSite } from './types.js';
3
+ export declare function escapeHtml(value: string): string;
4
+ /** The API name, plus the scheme when the link names one scheme of a multi-scheme API. */
5
+ export declare function targetTitle({ name, schemeLabel, schemes }: ElicitationTarget): string;
6
+ export declare function code(value: string): string;
7
+ export declare function htmlDocument({ title, body }: PageContent, colorScheme: PageColorScheme | undefined): string;
8
+ export declare function siteHeader({ name, logo, host, colorScheme }: PageSite): string;
9
+ export declare const ICONS: {
10
+ readonly shield: '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="M10 2.5 3.75 4.8v4.7c0 3.9 2.6 6.9 6.25 8 3.65-1.1 6.25-4.1 6.25-8V4.8L10 2.5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="m7.3 10 1.9 1.9 3.6-3.8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
11
+ readonly alert: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><circle cx="8" cy="8" r="6.25" stroke="currentColor" stroke-width="1.5"/><path d="m3.8 12.2 8.4-8.4" stroke="currentColor" stroke-width="1.5"/></svg>';
12
+ readonly warning: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 2.2 1.9 13h12.2L8 2.2Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M8 6.5v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="11.3" r=".8" fill="currentColor"/></svg>';
13
+ readonly check: '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><path d="m4.5 9.2 3 3 6-6.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
14
+ readonly cross: '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><path d="m5.5 5.5 7 7m0-7-7 7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>';
15
+ readonly clock: '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><circle cx="9" cy="9" r="5.75" stroke="currentColor" stroke-width="1.6"/><path d="M9 6v3.2l2 1.3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
16
+ };
17
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1,11 @@
1
+ import{pageStyles as l}from"./styles.js";function o(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}function a({name:e,schemeLabel:r,schemes:t}){return t&&t.length>1?e:r?`${e} (${r})`:e}function h(e){return`<code>${o(e)}</code>`}function u({title:e,body:r},t){return`<!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="robots" content="noindex">
7
+ <title>${o(e)}</title>
8
+ <style>${l(t)}</style>
9
+ </head>
10
+ <body>${r}</body>
11
+ </html>`}function k({name:e,logo:r,host:t,colorScheme:i}){const n=r?d(r,e??t,i):`<span class="site-name">${o(e??t)}</span>`,s=r||e?`<span class="site-host">${o(t)}</span>`:"";return`<header class="site">${n}${s}</header>`}function d(e,r,t){const i=n=>`<img class="site-logo" src="${o(n)}" alt="${o(r)}">`;return!e.dark||t==="light"?i(e.light):t==="dark"?i(e.dark):`<picture><source srcset="${o(e.dark)}" media="(prefers-color-scheme: dark)">${i(e.light)}</picture>`}const p={shield:'<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="M10 2.5 3.75 4.8v4.7c0 3.9 2.6 6.9 6.25 8 3.65-1.1 6.25-4.1 6.25-8V4.8L10 2.5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="m7.3 10 1.9 1.9 3.6-3.8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',alert:'<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><circle cx="8" cy="8" r="6.25" stroke="currentColor" stroke-width="1.5"/><path d="m3.8 12.2 8.4-8.4" stroke="currentColor" stroke-width="1.5"/></svg>',warning:'<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 2.2 1.9 13h12.2L8 2.2Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M8 6.5v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="11.3" r=".8" fill="currentColor"/></svg>',check:'<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><path d="m4.5 9.2 3 3 6-6.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',cross:'<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><path d="m5.5 5.5 7 7m0-7-7 7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',clock:'<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><circle cx="9" cy="9" r="5.75" stroke="currentColor" stroke-width="1.6"/><path d="M9 6v3.2l2 1.3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>'};export{p as ICONS,h as code,o as escapeHtml,u as htmlDocument,k as siteHeader,a as targetTitle};
@@ -0,0 +1,10 @@
1
+ import type { ElicitationTarget } from '../../gateway-mcp/secret-store.js';
2
+ import type { CredentialsOutcome, PageContent } from './types.js';
3
+ type Tone = 'success' | 'danger' | 'neutral' | 'warning';
4
+ export declare function outcomePage(target: ElicitationTarget, { action, schemes, stored }: CredentialsOutcome): PageContent;
5
+ export declare function declinedPage(): PageContent;
6
+ export declare function expiredPage(): PageContent;
7
+ /** A centered status card; `lines` are trusted HTML. */
8
+ export declare function statusPage(tone: Tone, title: string, lines: readonly string[]): PageContent;
9
+ export {};
10
+ //# sourceMappingURL=outcome-pages.d.ts.map
@@ -0,0 +1,11 @@
1
+ import{ELICITATION_TTL_MINUTES as p}from"../../gateway-mcp/secret-store.js";import{credentialSchemesOf as $}from"./form-contract.js";import{ICONS as a,code as f,escapeHtml as r,targetTitle as k}from"./layout.js";const v={success:a.check,danger:a.cross,neutral:a.cross,warning:a.clock};function y(n,{action:e,schemes:o,stored:t}){const s=e==="saved",i=$(n).length>1,c=r(n.name),d=t.filter(m=>!o.includes(m)),g=i?`We ${e} ${u(o)} for ${c}.`:`We ${e} your credentials for ${r(k(n))}.`,h=t.length===0?`Calls to ${c} now go without credentials.`:d.length>0?`${s?"Also stored":"Still stored"}: ${u(d)}.`:"";return l(s?"success":"danger",s?"Credentials saved":"Credentials removed",[g,h,s?"Go back to your AI client and tell it that you saved them.":""])}function S(){return l("neutral","Link declined",["We saved nothing, and this link no longer works.","If you did not ask for this link, tell your AI client to stop."])}function A(){return l("warning","This link no longer works",[`The link is not valid, was already used, or is older than ${p} minutes.`,"Ask your AI client for a new link."])}function l(n,e,o){const t=o.filter(Boolean).map(i=>`<p>${i}</p>`).join(`
2
+ `),s=n==="warning"?"":'<p class="muted small">You can close this tab.</p>';return{title:e,body:`<div class="page narrow">
3
+ <main class="card outcome">
4
+ <span class="status-icon ${n}">${v[n]}</span>
5
+ <h1>${r(e)}</h1>
6
+ <div class="message">
7
+ ${t}
8
+ ${s}
9
+ </div>
10
+ </main>
11
+ </div>`}}function u(n){const e=n.map(f);return e.length>1?`${e.slice(0,-1).join(", ")} and ${e.at(-1)}`:e.join("")}export{S as declinedPage,A as expiredPage,y as outcomePage,l as statusPage};
@@ -0,0 +1,10 @@
1
+ import type { RedoclyConfig } from '@redocly/config';
2
+ import type { GlobalData } from '../../../../../types/index.js';
3
+ import type { SiteBranding } from './types.js';
4
+ /** `logo` is the resolved theme logo from global data, whose paths point at served assets. */
5
+ export declare function resolveSiteBranding({ seo, logo, colorMode, }: {
6
+ seo?: RedoclyConfig['seo'];
7
+ logo?: GlobalData['logo'];
8
+ colorMode?: RedoclyConfig['colorMode'];
9
+ }): SiteBranding;
10
+ //# sourceMappingURL=site-branding.d.ts.map
@@ -0,0 +1 @@
1
+ import{parseSrcSet as h}from"../../../../utils/attribute-parser.js";function o({seo:t,logo:e,colorMode:d}){const n=t?.title||t?.projectTitle||e?.altText||void 0,i=e?.srcSet?h(e.srcSet):new Map,r=s(i.get("light")??e?.image),a=s(i.get("dark")),c=f(d);return{...n?{name:n}:{},...r?{logo:{light:r,...a?{dark:a}:{}}}:{},...c?{colorScheme:c}:{}}}function f(t){if(t?.hide)return"light";if(t?.ignoreDetection)return t.modes?.[0]==="dark"?"dark":"light"}function s(t){return t?.startsWith("/")&&!t.startsWith("//")?t:void 0}export{o as resolveSiteBranding};
@@ -0,0 +1,4 @@
1
+ import type { PageColorScheme } from './types.js';
2
+ /** The page CSS. Without a fixed color scheme, the page follows the system setting. */
3
+ export declare function pageStyles(colorScheme: PageColorScheme | undefined): string;
4
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1,186 @@
1
+ const e=`
2
+ --font-family-base:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
3
+ --page-bg:#f7f7f9;
4
+ --page-dot:#e3e4ea;
5
+ --bg-color:#ffffff;
6
+ --border-color-primary:#dcdde5;
7
+ --panel-bg:#f3f3f6;
8
+ --layer-color:#fbfbfc;
9
+ --input-border-color:#dcdde5;
10
+ --border-color-secondary:#ededf2;
11
+ --stored-bg:#ededf2;
12
+ --text-color-primary:#1a1c21;
13
+ --text-color-secondary:#3b3c45;
14
+ --text-color-description:#555761;
15
+ --placeholder:#9b9ca8;
16
+ --input-bg-color:#ffffff;
17
+ --primary-bg:#1a1c21;
18
+ --primary-hover:#3b3c45;
19
+ --primary-text:#ffffff;
20
+ --focus:#4561db;
21
+ --code-bg:#ededf2;
22
+ --code-text:#2a2b33;
23
+ --tag-bg:#ededf2;
24
+ --tag-text:#3b3c45;
25
+ --tag-stored-bg:#f0f4ff;
26
+ --tag-stored-text:#4561db;
27
+ --danger:#c8174f;
28
+ --danger-bg:#fff1f5;
29
+ --danger-border:#f7a8c0;
30
+ --success:#1f9d55;
31
+ --success-glow:rgba(31,157,85,.18);
32
+ --warning:#e08a00;
33
+ --warning-glow:rgba(224,138,0,.18);
34
+ --warning-bg:#fff8eb;
35
+ --warning-border:#f5c97a;
36
+ --danger-icon:#f9316d;
37
+ --danger-glow:rgba(249,49,109,.18);
38
+ --neutral-glow:rgba(85,87,97,.14);
39
+ `,o=`
40
+ --page-bg:#1a1b20;
41
+ --page-dot:#2c2e36;
42
+ --bg-color:#22242b;
43
+ --border-color-primary:#555761;
44
+ --panel-bg:#2a2b33;
45
+ --layer-color:#2a2b33;
46
+ --input-border-color:#3b3c45;
47
+ --border-color-secondary:#3b3c45;
48
+ --stored-bg:#32343e;
49
+ --text-color-primary:#fbfbfc;
50
+ --text-color-secondary:#c4c6d1;
51
+ --text-color-description:#9b9ca8;
52
+ --placeholder:#6e6f7a;
53
+ --input-bg-color:#22242b;
54
+ --primary-bg:#fbfbfc;
55
+ --primary-hover:#dcdde5;
56
+ --primary-text:#1a1c21;
57
+ --focus:#8fa6ff;
58
+ --code-bg:#3b3c45;
59
+ --code-text:#c4c6d1;
60
+ --tag-bg:#3b3c45;
61
+ --tag-text:#c4c6d1;
62
+ --tag-stored-bg:#1f2a4d;
63
+ --tag-stored-text:#8fa6ff;
64
+ --danger:#ff6b95;
65
+ --danger-bg:#3a1822;
66
+ --danger-border:#7a2b44;
67
+ --success:#3ccf74;
68
+ --success-glow:rgba(60,207,116,.2);
69
+ --warning:#f5a524;
70
+ --warning-glow:rgba(245,165,36,.2);
71
+ --warning-bg:#33260f;
72
+ --warning-border:#7a5a1e;
73
+ --danger-icon:#ff6b95;
74
+ --danger-glow:rgba(255,107,149,.2);
75
+ --neutral-glow:rgba(155,156,168,.16);
76
+ `,r=`
77
+ *{box-sizing:border-box}
78
+ body{margin:0;min-height:100vh;padding:48px 24px;font-family:var(--font-family-base);font-size:14px;line-height:22px;color:var(--text-color-primary);background-color:var(--page-bg);background-image:radial-gradient(var(--page-dot) 1px,transparent 1px);background-size:16px 16px}
79
+ .page{display:grid;grid-template-columns:minmax(0,692px) minmax(0,348px);gap:16px;justify-content:center;min-height:calc(100vh - 96px);margin:0 auto}
80
+ .page.narrow{grid-template-columns:minmax(0,864px)}
81
+ .card{display:flex;flex-direction:column;background:var(--bg-color);border:1px solid var(--border-color-primary);border-radius:24px;padding:40px 48px 24px}
82
+ .panel{display:flex;flex-direction:column;gap:16px;background:var(--panel-bg);border:1px solid var(--border-color-primary);border-radius:24px;padding:32px 24px 24px}
83
+ .site{display:flex;align-items:center;justify-content:space-between;gap:16px;padding-bottom:24px;margin-bottom:24px;border-bottom:1px solid var(--border-color-secondary)}
84
+ .site-name{font-size:18px;line-height:24px;font-weight:600;overflow-wrap:anywhere}
85
+ .site-host{font-size:16px;line-height:24px;color:var(--text-color-secondary);overflow-wrap:anywhere;text-align:right}
86
+ .site-logo{display:block;max-height:32px;max-width:200px;width:auto}
87
+ h1{margin:0;font-size:20px;line-height:28px;font-weight:600}
88
+ h2{margin:0;font-size:16px;line-height:24px;font-weight:600}
89
+ p{margin:0}
90
+ .lede{margin-top:12px}
91
+ .muted{color:var(--text-color-description)}
92
+ .small{font-size:12px;line-height:20px}
93
+ .strong{font-weight:600}
94
+ code{font:inherit;background:var(--code-bg);color:var(--code-text);padding:1px 8px;border-radius:6px;overflow-wrap:anywhere}
95
+ .banner{display:flex;gap:8px;align-items:flex-start;margin-top:24px;padding:12px 16px;border:1px solid var(--danger-border);border-radius:8px;background:var(--danger-bg);color:var(--text-color-primary)}
96
+ .banner svg{flex:none;margin-top:3px;color:var(--danger)}
97
+ .banner.warning{border-color:var(--warning-border);background:var(--warning-bg)}
98
+ .banner.warning svg{color:var(--warning)}
99
+ form.credentials{display:flex;flex-direction:column;flex:1}
100
+ .schemes{display:flex;flex-direction:column;gap:12px;margin-top:24px}
101
+ .scheme{background:var(--layer-color);border:1px solid var(--border-color-secondary);border-radius:16px;padding:24px 32px 24px 24px}
102
+ .info-card{margin-top:24px;padding:8px 24px;background:var(--layer-color);border:1px solid var(--border-color-secondary);border-radius:16px}
103
+ .info-card section{display:flex;flex-direction:column;gap:2px;padding:16px 0}
104
+ .info-card section+section{border-top:1px solid var(--border-color-secondary)}
105
+ .info-card h2{font-size:14px;line-height:22px;font-weight:500;color:var(--text-color-secondary)}
106
+ .scheme-body{display:flex;flex-direction:column;gap:16px}
107
+ details.scheme>.scheme-body{margin-top:16px;padding-left:24px}
108
+ details.scheme>summary{display:grid;grid-template-columns:16px minmax(0,1fr) auto;column-gap:8px;row-gap:4px;cursor:pointer;list-style:none}
109
+ details.scheme>summary::-webkit-details-marker{display:none}
110
+ details.scheme>summary::before,details.operations>summary::before{content:"";flex:none;width:6px;height:6px;margin:8px 0 0 4px;border-right:1.5px solid var(--text-color-secondary);border-bottom:1.5px solid var(--text-color-secondary);transform:rotate(-45deg);transition:transform .15s}
111
+ details[open]>summary::before{transform:rotate(45deg);margin-top:6px}
112
+ details.scheme>summary:focus-visible{outline:2px solid var(--focus);outline-offset:4px;border-radius:4px}
113
+ .summary-title{grid-column:2;font-size:16px;line-height:24px;font-weight:600;overflow-wrap:anywhere}
114
+ .summary-tag{grid-column:3;grid-row:1}
115
+ .summary-line{grid-column:2/4}
116
+ .scheme-head{display:flex;flex-direction:column;gap:4px}
117
+ .tag{display:inline-block;padding:1px 8px;border-radius:4px;font-size:14px;line-height:22px;background:var(--tag-bg);color:var(--tag-text);white-space:nowrap}
118
+ .tag.stored{background:var(--tag-stored-bg);color:var(--tag-stored-text)}
119
+ .tags{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px}
120
+ .stored-box{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 16px;border-radius:8px;background:var(--stored-bg)}
121
+ .field{display:flex;flex-direction:column;gap:4px;min-width:0}
122
+ .field-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
123
+ label{font-weight:500;color:var(--text-color-secondary)}
124
+ input[type=text],input[type=password]{width:100%;height:34px;padding:0 12px;font-family:var(--font-family-base);font-size:14px;color:var(--text-color-primary);background:var(--input-bg-color);border:1px solid var(--input-border-color);border-radius:8px}
125
+ input::placeholder{color:var(--placeholder)}
126
+ input:focus{outline:2px solid transparent;border-color:var(--focus);box-shadow:0 0 0 1px var(--focus)}
127
+ input[aria-invalid=true]{border-color:var(--danger)}
128
+ input[aria-invalid=true]:focus{box-shadow:0 0 0 3px var(--danger-border)}
129
+ .divider{border:0;border-top:1px solid var(--border-color-secondary);margin:0}
130
+ details.operations>summary{display:flex;gap:8px;cursor:pointer;list-style:none;width:max-content;max-width:100%}
131
+ details.operations>summary::-webkit-details-marker{display:none}
132
+ details.operations>summary:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:4px}
133
+ details.operations ul{margin:8px 0 0;padding-left:20px;display:flex;flex-direction:column;gap:4px}
134
+ .actions{margin-top:auto;padding-top:40px}
135
+ .actions-row{display:flex;align-items:center;gap:16px;padding-top:24px;border-top:1px solid var(--border-color-secondary)}
136
+ .actions-row .primary{flex:1}
137
+ button{font-family:var(--font-family-base);font-size:16px;line-height:24px;font-weight:400;cursor:pointer;border-radius:12px}
138
+ button:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
139
+ .primary{height:40px;padding:0 20px;border:0;background:var(--primary-bg);color:var(--primary-text)}
140
+ .primary:hover{background:var(--primary-hover)}
141
+ .secondary{height:40px;padding:0 20px;border:0;box-shadow:inset 0 0 0 1px var(--border-color-primary);background:var(--bg-color);color:var(--text-color-primary)}
142
+ .actions-row .secondary{flex:1}
143
+ .secondary:hover{background:var(--panel-bg)}
144
+ .text-danger{padding:0 20px;height:40px;border:0;background:none;color:var(--danger)}
145
+ .text-danger.small{height:auto;padding:0 8px;font-size:14px;line-height:22px}
146
+ .text-danger:hover{text-decoration:underline}
147
+ .link-button{padding:0;border:0;background:none;color:var(--text-color-secondary);font-size:12px;line-height:20px;text-decoration:underline;text-underline-offset:2px}
148
+ .decline{margin-top:12px;text-align:center;color:var(--text-color-secondary)}
149
+ .panel h2{display:flex;align-items:center;gap:12px;font-weight:500}
150
+ .panel-steps{display:flex;flex-direction:column;gap:16px}
151
+ .panel-steps-toggle{display:none}
152
+ .panel-steps-toggle>summary{display:flex;align-items:center;justify-content:space-between;gap:12px;cursor:pointer;list-style:none}
153
+ .panel-steps-toggle>summary::-webkit-details-marker{display:none}
154
+ .panel-steps-toggle>summary::after{content:"";flex:none;width:7px;height:7px;margin:0 4px 4px 0;border-right:1.5px solid var(--text-color-secondary);border-bottom:1.5px solid var(--text-color-secondary);transform:rotate(45deg);transition:transform .15s}
155
+ .panel-steps-toggle[open]>summary::after{transform:rotate(-135deg);margin:4px 4px 0 0}
156
+ .panel-steps-toggle>summary:focus-visible{outline:2px solid var(--focus);outline-offset:4px;border-radius:4px}
157
+ .panel-steps-toggle>.steps{margin-top:16px}
158
+ .steps{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:16px;counter-reset:step}
159
+ .steps li{display:grid;grid-template-columns:20px minmax(0,1fr);gap:12px;counter-increment:step;font-size:16px;line-height:24px}
160
+ .steps li::before{content:counter(step);display:flex;align-items:center;justify-content:center;height:20px;margin-top:2px;border-radius:5px;background:var(--bg-color);font-size:14px;line-height:16px}
161
+ .link-facts{display:flex;flex-direction:column;gap:8px;margin-top:auto;padding-top:32px;border-top:1px solid var(--border-color-primary);color:var(--text-color-secondary)}
162
+ .pill{display:inline-flex;align-items:center;gap:8px;width:max-content;max-width:100%;padding:1px 8px;border-radius:999px;background:var(--bg-color);font-size:12px;line-height:20px}
163
+ .pill::before{content:"";flex:none;width:6px;height:6px;border-radius:50%;background:var(--success)}
164
+ .outcome{display:flex;flex-direction:column;align-items:center;text-align:center;gap:24px;padding:56px 0}
165
+ .status-icon{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;color:#ffffff}
166
+ .status-icon.success{background:var(--success);box-shadow:0 0 16px 10px var(--success-glow)}
167
+ .status-icon.warning{background:var(--warning);box-shadow:0 0 16px 10px var(--warning-glow)}
168
+ .status-icon.danger{background:var(--danger-icon);box-shadow:0 0 16px 10px var(--danger-glow)}
169
+ .status-icon.neutral{background:var(--text-color-description);box-shadow:0 0 16px 10px var(--neutral-glow)}
170
+ .message{display:flex;flex-direction:column;gap:8px;width:100%;max-width:640px;padding:24px 32px;border:1px solid var(--border-color-secondary);border-radius:12px;background:var(--layer-color)}
171
+ @media (max-width:960px){
172
+ body{padding:16px}
173
+ .page{grid-template-columns:minmax(0,1fr);min-height:0}
174
+ .card{padding:24px}
175
+ .site{flex-direction:column;align-items:flex-start;gap:0}
176
+ .site-host{text-align:left}
177
+ .panel-steps{display:none}
178
+ .panel-steps-toggle{display:block}
179
+ .link-facts{padding-top:16px}
180
+ .scheme{padding:16px}
181
+ .info-card{padding:4px 16px}
182
+ .field-row{grid-template-columns:minmax(0,1fr)}
183
+ .outcome{padding:32px 0}
184
+ .message{padding:16px}
185
+ }
186
+ `;function t(a){switch(a){case"light":return`:root{color-scheme:light;${e}}${r}`;case"dark":return`:root{color-scheme:dark;${e}${o}}${r}`;default:return`:root{color-scheme:light dark;${e}}@media (prefers-color-scheme:dark){:root{${o}}}${r}`}}export{t as pageStyles};
@@ -0,0 +1,32 @@
1
+ export type SiteBranding = {
2
+ name?: string;
3
+ logo?: {
4
+ light: string;
5
+ dark?: string;
6
+ };
7
+ /** Set when the portal always shows one color mode. */
8
+ colorScheme?: PageColorScheme;
9
+ };
10
+ export type PageColorScheme = 'light' | 'dark';
11
+ export type PageSite = SiteBranding & {
12
+ host: string;
13
+ };
14
+ /** One page, before the shared document shell wraps it. */
15
+ export type PageContent = {
16
+ title: string;
17
+ body: string;
18
+ };
19
+ export type SchemeField = 'secret' | 'client_id' | 'client_secret';
20
+ /** A rejected submission. `schemeIndex` and `field` name the input to mark, when there is one. */
21
+ export type FormError = {
22
+ message: string;
23
+ schemeIndex?: number;
24
+ field?: SchemeField;
25
+ };
26
+ /** What one submission changed, and the schemes that hold a credential after it. */
27
+ export type CredentialsOutcome = {
28
+ action: 'saved' | 'removed';
29
+ schemes: readonly string[];
30
+ stored: readonly string[];
31
+ };
32
+ //# sourceMappingURL=types.d.ts.map
@@ -1,5 +1,4 @@
1
1
  import type { ApiRoutesHandler } from '@redocly/config';
2
- export declare function setCredentialsFetchImpl(impl: typeof fetch): void;
3
2
  declare const mcpCredentialsHandler: ApiRoutesHandler;
4
3
  export default mcpCredentialsHandler;
5
4
  //# sourceMappingURL=mcp-credentials-handler.d.ts.map