@redocly/realm 0.137.0-next.2 → 0.137.0-next.4
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/CHANGELOG.md +40 -0
- package/dist/cli/stats/collectors/openapi/index.js +1 -1
- package/dist/client/utils/catalog/get-next-page-param.d.ts +1 -1
- package/dist/client/utils/catalog/get-next-page-param.js +1 -1
- package/dist/compiled/svgo/svgo-node.js +32 -32
- package/dist/server/plugins/api-docs/search/search-indexer.js +1 -1
- package/dist/server/plugins/mcp/codemode/tools/manage-credentials.js +2 -2
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +2 -1
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +4 -4
- package/dist/server/plugins/mcp/gateway-mcp/secret-store.d.ts +8 -0
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.d.ts +2 -0
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.js +2 -2
- package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +1 -144
- package/dist/server/plugins/mcp/handlers/mcp-credentials-page.d.ts +6 -0
- package/dist/server/plugins/mcp/handlers/mcp-credentials-page.js +217 -0
- package/dist/server/plugins/mcp/index.js +1 -1
- package/dist/server/plugins/mcp/servers/mcp-server.js +3 -3
- package/dist/server/plugins/mcp/utils/find-gated-tool-names.d.ts +13 -0
- package/dist/server/plugins/mcp/utils/find-gated-tool-names.js +1 -0
- package/dist/server/sandbox/constants/sandbox.d.ts +4 -1
- package/dist/server/sandbox/constants/sandbox.js +1 -1
- package/dist/server/sandbox/sandbox.js +2 -2
- package/dist/server/store.d.ts +1 -0
- package/dist/server/store.js +1 -1
- package/dist/server/types/plugins/common.d.ts +3 -0
- package/dist/server/web-server/routes/ask-ai.d.ts +1 -0
- package/dist/server/web-server/routes/ask-ai.js +1 -1
- package/dist/server/web-server/routes/auth.js +1 -1
- package/dist/server/web-server/routes/index.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-server-card.js +1 -1
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
import{REDOCLY_TEAMS_RBAC as p}from"@redocly/config";import{extractFullText as W,nodeTypes as g,contentType as P,schemaKind as j}from"@redocly/api-docs";import{toMarkdown as H}from"../../markdown/search/to-markdown.js";const L=10,_=200,C="message payload",D="message headers";function E(h){if(typeof h=="string")return h;const e=Array.isArray(h)?h:[h];return H(e).trim()}function N(h){return h.length>_?`${h.slice(0,_)}\u2026`:h}class B{#c=[];#i;#s;#o;#f;constructor(e,t,n){this.#i=e,this.#s=t,this.#o=n}addItem(e){if(!(!e.link||!e.content))try{switch(e.content.contentType){case P.OVERVIEW:this.#a(e,this.#i);break;case P.GROUP:case P.ITEM:this.#a(e,e.link);break}}catch(t){console.error(`Cannot add item to api-docs search indexer [${e.label}, ${e.link}]:`,t.message)}}#a(e,t){if(!e.content)return;const n=t.startsWith(this.#i+"/")?t.slice(this.#i.length+1):t.split("/").slice(-2).join("/"),i=e.content.children,o=this.#p(i),f=this.#y(i,n),r=e,s={id:t,url:t,title:e.label??"",text:o,path:[],deprecated:e.content.meta?.deprecated,..."httpVerb"in e&&e.httpVerb?{httpMethod:e.httpVerb}:{},..."httpPath"in e&&e.httpPath?{httpPath:e.httpPath}:{},..."badges"in e&&Array.isArray(e.badges)&&e.badges.length?{badges:e.badges}:{},..."isAdditionalOperation"in e&&e.isAdditionalOperation?{isAdditionalOperation:!0}:{},[p]:r[p],...f.length&&{parameters:f}};this.#c.push(s)}#p(e){const t=[],n=i=>{for(const o of i)if(o.nodeType===g.MARKDOC){const f=o;if(this.#h(f.content))continue;const r=W(f.content);r&&t.push(r)}else(o.nodeType===g.CONTAINER||o.nodeType===g.OVERVIEW_SECTION_WRAPPER)&&n(o.children)};return n(e),t.join(" ").trim()}#h(e){let t=!1,n=!1;const i=f=>{f.type==="code"&&f.attributes?.content?t=!0:f.type==="text"&&f.attributes?.content&&(n=!0);for(const r of f.children??[])i(r)},o=Array.isArray(e)?e:[e];for(const f of o)i(f);return t&&!n}#y(e,t){const n={},i=new Set,o=f=>{for(const r of f)if(r.nodeType===g.ITEM){const s=r;if(s[p])continue;if(s.parameters?.length)for(const a of s.parameters){if(a[p])continue;const d=this.#O(s.variant),c=this.#$(s.variant),u=a.description?E(a.description):"",l=this.#A(a.schemaId),$={name:a.name,description:u,place:d,mediaType:void 0,type:l,required:a.required??!1,deepLink:this.#d(t,"request",c,void 0,void 0,[a.name]),example:void 0,enum:void 0};n[this.#n($)]=$}this.#g(s,n,i,t)}else if(r.nodeType===g.CONTAINER){const s=r;if(s[p])continue;o(s.children)}else r.nodeType===g.OVERVIEW_SECTION_WRAPPER&&o(r.children)};return o(e),Object.values(n)}#n(e){return[...Array.isArray(e.path)?e.path:[],e.name.toString()].join(".")+e.description+e.place}#d(e,t,n,i,o,f){if(!e)return"";const r=f.join("/"),s=[`t=${t}`];return n&&s.push(`in=${n}`),o&&s.push(`c=${o}`),i&&s.push(`ct=${i}`),r&&s.push(`path=${r}`),`/${e}#${e}/${s.join("&")}`.toLowerCase()}#m(e,t,n,i){if(!e)return"";let o="messages";t&&(o+=`&m=${t}`),o+=`&t=${n}`;const f=i.join("/");return f&&(o+=`&path=${f}`),`/${e}#${e}/${o}`.toLowerCase()}#$(e){switch(e){case"query":return"query";case"path":return"path";case"headers":return"header";case"cookies":return"cookie";default:return}}#O(e){switch(e){case"query":return"query parameters";case"path":return"path parameters";case"headers":return"header parameters";case"cookies":return"cookie parameters";case"parameters":return"channel parameters";default:return e?`${e} parameters`:"parameters"}}#A(e){const t=this.#s?.[e];if(!t||t.kind!==j.JSON_SCHEMA)return"unknown";const n=t.data,i=this.#l(n);return this.#u(i)}#u(e){return Array.isArray(e.type)?e.type.join(" | "):e.type?e.type:e.properties||e.additionalProperties?"object":e.items?"array":e.enum?"string":"any"}#g(e,t,n,i){const o=this.#E(e);if(e.graphqlSchema&&Array.isArray(e.graphqlSchema)){this.#S(e.graphqlSchema,o,t);return}if(!this.#s||!this.#o)return;const f=o.startsWith("response");if(e.mediaTypeSchemas){const r=Object.entries(e.mediaTypeSchemas),s=r.find(([,a])=>!a[p]);if(s){const[a,d]=s;d.schemaId&&this.#r(d.schemaId,o,a,r.length>1,f,t,[],n,i)}}else e.schemaId&&this.#r(e.schemaId,o,void 0,!1,f,t,[],n,i);if(e.responses)for(const r of e.responses){if(r[p])continue;const s=`response ${r.code} fields`,a=new Set;if(r.mediaTypeContent){const d=Object.entries(r.mediaTypeContent),c=d.find(([,u])=>!u[p]);if(c){const[u,l]=c;l.schemaId&&this.#r(l.schemaId,s,u,d.length>1,!0,t,[],a,i)}}else r.schemaId&&this.#r(r.schemaId,s,r.mediaType,!1,!0,t,[],a,i)}if(e.messages)for(const r of e.messages){if(r[p])continue;const s=r.description?E(r.description):"",a=r.summary??"",d={name:r.name,description:(s+a).trim(),place:"message",mediaType:void 0,type:"unknown",required:!1,example:void 0,enum:void 0};t[this.#n(d)]=d,this.#f=r.name;const c=new Set;r.schemaId&&this.#r(r.schemaId,C,r.contentType,!1,!1,t,[],c,i),r.headerSchemaId&&this.#r(r.headerSchemaId,D,void 0,!1,!1,t,[],c,i),this.#f=void 0}}#E(e){switch(e.variant){case"body":return"request fields";case"responses":return"response fields";case"graphql-fields":return"fields";case"graphql-args":return"arguments";case"return-type":return"return type fields";default:return e.variant??"fields"}}#S(e,t,n){for(const i of e){const o=i.description?E(i.description):"",f={name:i.name,description:o,place:t,mediaType:void 0,path:[],type:i.type??"unknown",required:!1,example:void 0,enum:void 0};if(n[this.#n(f)]=f,i.args?.length)for(const r of i.args){const s=r.description?E(r.description):"",a={name:r.name,description:s,place:"arguments",mediaType:void 0,path:[i.name],type:r.type??"unknown",required:!1,example:void 0,enum:void 0};n[this.#n(a)]=a}}}#r(e,t,n,i,o,f,r,s,a=""){const d=this.#s?.[e];if(!d||d.kind!==j.JSON_SCHEMA||d[p])return;const c=d.data,u=i?n:void 0;this.#t(c,t,n,u,o,f,r,s,0,a)}#I(e){const t=e.match(/^response (\d+)/);return t?t[1]:void 0}#l(e){if(e.allOf){const t={};for(const n of e.allOf){const i=n.$ref?this.#e(n.$ref):n;if(i){const o=t.properties,f=t.required;Object.assign(t,i),(o||i.properties)&&(t.properties={...o||{},...i.properties||{}}),(f||i.required)&&(t.required=[...f||[],...i.required||[]])}}return delete t.allOf,e.description&&(t.description=e.description),t}if(e.$ref){const t=this.#e(e.$ref);if(t){const n={...t};return e.description&&(n.description=e.description),n}}return e}#e(e){if(!e.startsWith("#/"))return;const t=e.slice(2).split("/");let n=this.#o;for(const i of t){if(n==null||typeof n!="object")return;n=n[i]}return n!=null&&typeof n=="object"?n:void 0}#t(e,t,n,i,o,f,r,s,a,d=""){if(!(a>L||e[p])){if(e.$ref){if(s.has(e.$ref))return;s.add(e.$ref);const c=this.#e(e.$ref);c&&this.#t(c,t,n,i,o,f,r,s,a,d);return}if(e.allOf){const c={};for(const u of e.allOf){let l=u;if(u.$ref){if(s.has(u.$ref))continue;s.add(u.$ref),l=this.#e(u.$ref)}if(l){const $=c.properties,A=c.required;Object.assign(c,l),($||l.properties)&&(c.properties={...$||{},...l.properties||{}}),(A||l.required)&&(c.required=[...A||[],...l.required||[]])}}delete c.allOf,this.#t(c,t,n,i,o,f,r,s,a,d);return}if(e.discriminator&&e.properties&&e.oneOf){this.#t({...e,oneOf:void 0,discriminator:void 0},t,n,i,o,f,r,s,a,d);return}if(e.oneOf||e.anyOf){for(const c of e.oneOf||e.anyOf||[]){if(c.$ref){if(s.has(c.$ref))continue;s.add(c.$ref)}const u=c.$ref?this.#e(c.$ref):c;u&&this.#t(u,t,n,i,o,f,r,s,a+1,d)}return}if(e.properties){const c=e.required||[],u=this.#I(t),l=o?"response":"request",$=t===C?"payload":t===D?"headers":void 0;for(const[A,O]of Object.entries(e.properties)){if(O[p])continue;if(O.$ref){if(s.has(O.$ref))continue;s.add(O.$ref)}const m=O.$ref?this.#e(O.$ref):O;if(!m)continue;let x=!1;if(m.allOf){let b=!1;for(const q of m.allOf)q.$ref?s.has(q.$ref)||(s.add(q.$ref),b=!0):b=!0;x=!b}const y=this.#l(m);if(m[p]||y[p]||y.readOnly&&!o||y.writeOnly&&o||m.additionalProperties!==void 0&&!m.properties)continue;const T=m.description??y.description,R=T?E(T):"",S=m.example??y.example,V=S!==void 0?N(typeof S=="string"?S:JSON.stringify(S)):void 0,v=Array.isArray(y.enum)&&y.enum.length?y.enum:void 0,I=[...r,A],w={name:A,description:R,place:t,mediaType:n,path:r,type:this.#u(y),required:c.includes(A),deepLink:$?this.#m(d,this.#f,$,I):this.#d(d,l,void 0,i,u,I),example:V,enum:v},k=this.#n(w);f[k]==null&&(f[k]=w,x||this.#t(y,t,n,i,o,f,I,s,a+1,d))}}
|
|
1
|
+
import{REDOCLY_TEAMS_RBAC as p}from"@redocly/config";import{extractFullText as W,nodeTypes as g,contentType as P,schemaKind as j}from"@redocly/api-docs";import{toMarkdown as H}from"../../markdown/search/to-markdown.js";const L=10,_=200,C="message payload",D="message headers";function E(h){if(typeof h=="string")return h;const e=Array.isArray(h)?h:[h];return H(e).trim()}function N(h){return h.length>_?`${h.slice(0,_)}\u2026`:h}class B{#c=[];#i;#s;#o;#f;constructor(e,t,n){this.#i=e,this.#s=t,this.#o=n}addItem(e){if(!(!e.link||!e.content))try{switch(e.content.contentType){case P.OVERVIEW:this.#a(e,this.#i);break;case P.GROUP:case P.ITEM:this.#a(e,e.link);break}}catch(t){console.error(`Cannot add item to api-docs search indexer [${e.label}, ${e.link}]:`,t.message)}}#a(e,t){if(!e.content)return;const n=t.startsWith(this.#i+"/")?t.slice(this.#i.length+1):t.split("/").slice(-2).join("/"),i=e.content.children,o=this.#p(i),f=this.#y(i,n),r=e,s={id:t,url:t,title:e.label??"",text:o,path:[],deprecated:e.content.meta?.deprecated,..."httpVerb"in e&&e.httpVerb?{httpMethod:e.httpVerb}:{},..."httpPath"in e&&e.httpPath?{httpPath:e.httpPath}:{},..."badges"in e&&Array.isArray(e.badges)&&e.badges.length?{badges:e.badges}:{},..."isAdditionalOperation"in e&&e.isAdditionalOperation?{isAdditionalOperation:!0}:{},[p]:r[p],...f.length&&{parameters:f}};this.#c.push(s)}#p(e){const t=[],n=i=>{for(const o of i)if(o.nodeType===g.MARKDOC){const f=o;if(this.#h(f.content))continue;const r=W(f.content);r&&t.push(r)}else(o.nodeType===g.CONTAINER||o.nodeType===g.OVERVIEW_SECTION_WRAPPER)&&n(o.children)};return n(e),t.join(" ").trim()}#h(e){let t=!1,n=!1;const i=f=>{f.type==="code"&&f.attributes?.content?t=!0:f.type==="text"&&f.attributes?.content&&(n=!0);for(const r of f.children??[])i(r)},o=Array.isArray(e)?e:[e];for(const f of o)i(f);return t&&!n}#y(e,t){const n={},i=new Set,o=f=>{for(const r of f)if(r.nodeType===g.ITEM){const s=r;if(s[p])continue;if(s.parameters?.length)for(const a of s.parameters){if(a[p])continue;const d=this.#O(s.variant),c=this.#$(s.variant),u=a.description?E(a.description):"",l=this.#A(a.schemaId),$={name:a.name,description:u,place:d,mediaType:void 0,type:l,required:a.required??!1,deepLink:this.#d(t,"request",c,void 0,void 0,[a.name]),example:void 0,enum:void 0};n[this.#n($)]=$}this.#g(s,n,i,t)}else if(r.nodeType===g.CONTAINER){const s=r;if(s[p])continue;o(s.children)}else r.nodeType===g.OVERVIEW_SECTION_WRAPPER&&o(r.children)};return o(e),Object.values(n)}#n(e){return[...Array.isArray(e.path)?e.path:[],e.name.toString()].join(".")+e.description+e.place}#d(e,t,n,i,o,f){if(!e)return"";const r=f.join("/"),s=[`t=${t}`];return n&&s.push(`in=${n}`),o&&s.push(`c=${o}`),i&&s.push(`ct=${i}`),r&&s.push(`path=${r}`),`/${e}#${e}/${s.join("&")}`.toLowerCase()}#m(e,t,n,i){if(!e)return"";let o="messages";t&&(o+=`&m=${t}`),o+=`&t=${n}`;const f=i.join("/");return f&&(o+=`&path=${f}`),`/${e}#${e}/${o}`.toLowerCase()}#$(e){switch(e){case"query":return"query";case"path":return"path";case"headers":return"header";case"cookies":return"cookie";default:return}}#O(e){switch(e){case"query":return"query parameters";case"path":return"path parameters";case"headers":return"header parameters";case"cookies":return"cookie parameters";case"parameters":return"channel parameters";default:return e?`${e} parameters`:"parameters"}}#A(e){const t=this.#s?.[e];if(!t||t.kind!==j.JSON_SCHEMA)return"unknown";const n=t.data,i=this.#l(n);return this.#u(i)}#u(e){return Array.isArray(e.type)?e.type.join(" | "):e.type?e.type:e.properties||e.additionalProperties?"object":e.items?"array":e.enum?"string":"any"}#g(e,t,n,i){const o=this.#E(e);if(e.graphqlSchema&&Array.isArray(e.graphqlSchema)){this.#S(e.graphqlSchema,o,t);return}if(!this.#s||!this.#o)return;const f=o.startsWith("response");if(e.mediaTypeSchemas){const r=Object.entries(e.mediaTypeSchemas),s=r.find(([,a])=>!a[p]);if(s){const[a,d]=s;d.schemaId&&this.#r(d.schemaId,o,a,r.length>1,f,t,[],n,i)}}else e.schemaId&&this.#r(e.schemaId,o,void 0,!1,f,t,[],n,i);if(e.responses)for(const r of e.responses){if(r[p])continue;const s=`response ${r.code} fields`,a=new Set;if(r.mediaTypeContent){const d=Object.entries(r.mediaTypeContent),c=d.find(([,u])=>!u[p]);if(c){const[u,l]=c;l.schemaId&&this.#r(l.schemaId,s,u,d.length>1,!0,t,[],a,i)}}else r.schemaId&&this.#r(r.schemaId,s,r.mediaType,!1,!0,t,[],a,i)}if(e.messages)for(const r of e.messages){if(r[p])continue;const s=r.description?E(r.description):"",a=r.summary??"",d={name:r.name,description:(s+a).trim(),place:"message",mediaType:void 0,type:"unknown",required:!1,example:void 0,enum:void 0};t[this.#n(d)]=d,this.#f=r.name;const c=new Set;r.schemaId&&this.#r(r.schemaId,C,r.contentType,!1,!1,t,[],c,i),r.headerSchemaId&&this.#r(r.headerSchemaId,D,void 0,!1,!1,t,[],c,i),this.#f=void 0}}#E(e){switch(e.variant){case"body":return"request fields";case"responses":return"response fields";case"graphql-fields":return"fields";case"graphql-args":return"arguments";case"return-type":return"return type fields";default:return e.variant??"fields"}}#S(e,t,n){for(const i of e){const o=i.description?E(i.description):"",f={name:i.name,description:o,place:t,mediaType:void 0,path:[],type:i.type??"unknown",required:!1,example:void 0,enum:void 0};if(n[this.#n(f)]=f,i.args?.length)for(const r of i.args){const s=r.description?E(r.description):"",a={name:r.name,description:s,place:"arguments",mediaType:void 0,path:[i.name],type:r.type??"unknown",required:!1,example:void 0,enum:void 0};n[this.#n(a)]=a}}}#r(e,t,n,i,o,f,r,s,a=""){const d=this.#s?.[e];if(!d||d.kind!==j.JSON_SCHEMA||d[p])return;const c=d.data,u=i?n:void 0;this.#t(c,t,n,u,o,f,r,s,0,a)}#I(e){const t=e.match(/^response (\d+)/);return t?t[1]:void 0}#l(e){if(e.allOf){const t={};for(const n of e.allOf){const i=n.$ref?this.#e(n.$ref):n;if(i){const o=t.properties,f=t.required;Object.assign(t,i),(o||i.properties)&&(t.properties={...o||{},...i.properties||{}}),(f||i.required)&&(t.required=[...f||[],...i.required||[]])}}return delete t.allOf,e.description&&(t.description=e.description),t}if(e.$ref){const t=this.#e(e.$ref);if(t){const n={...t};return e.description&&(n.description=e.description),n}}return e}#e(e){if(!e.startsWith("#/"))return;const t=e.slice(2).split("/");let n=this.#o;for(const i of t){if(n==null||typeof n!="object")return;n=n[i]}return n!=null&&typeof n=="object"?n:void 0}#t(e,t,n,i,o,f,r,s,a,d=""){if(!(a>L||e[p])){if(e.$ref){if(s.has(e.$ref))return;s.add(e.$ref);const c=this.#e(e.$ref);c&&this.#t(c,t,n,i,o,f,r,s,a,d);return}if(e.allOf){const c={};for(const u of e.allOf){let l=u;if(u.$ref){if(s.has(u.$ref))continue;s.add(u.$ref),l=this.#e(u.$ref)}if(l){const $=c.properties,A=c.required;Object.assign(c,l),($||l.properties)&&(c.properties={...$||{},...l.properties||{}}),(A||l.required)&&(c.required=[...A||[],...l.required||[]])}}delete c.allOf,this.#t(c,t,n,i,o,f,r,s,a,d);return}if(e.discriminator&&e.properties&&e.oneOf){this.#t({...e,oneOf:void 0,discriminator:void 0},t,n,i,o,f,r,s,a,d);return}if(e.oneOf||e.anyOf){for(const c of e.oneOf||e.anyOf||[]){if(c.$ref){if(s.has(c.$ref))continue;s.add(c.$ref)}const u=c.$ref?this.#e(c.$ref):c;u&&this.#t(u,t,n,i,o,f,r,s,a+1,d)}return}if(e.properties&&this.#b(e,t,n,i,o,f,r,s,a,d),e.items&&typeof e.items=="object"&&!Array.isArray(e.items)){if(e.items.$ref){if(s.has(e.items.$ref))return;s.add(e.items.$ref)}const c=e.items.$ref?this.#e(e.items.$ref):e.items;c&&this.#t(c,t,n,i,o,f,r,s,a+1,d)}}}#b(e,t,n,i,o,f,r,s,a,d){if(!e.properties)return;const c=e.required||[],u=this.#I(t),l=o?"response":"request",$=t===C?"payload":t===D?"headers":void 0;for(const[A,O]of Object.entries(e.properties)){if(O[p])continue;if(O.$ref){if(s.has(O.$ref))continue;s.add(O.$ref)}const m=O.$ref?this.#e(O.$ref):O;if(!m)continue;let x=!1;if(m.allOf){let b=!1;for(const q of m.allOf)q.$ref?s.has(q.$ref)||(s.add(q.$ref),b=!0):b=!0;x=!b}const y=this.#l(m);if(m[p]||y[p]||y.readOnly&&!o||y.writeOnly&&o||m.additionalProperties!==void 0&&!m.properties)continue;const T=m.description??y.description,R=T?E(T):"",S=m.example??y.example,V=S!==void 0?N(typeof S=="string"?S:JSON.stringify(S)):void 0,v=Array.isArray(y.enum)&&y.enum.length?y.enum:void 0,I=[...r,A],w={name:A,description:R,place:t,mediaType:n,path:r,type:this.#u(y),required:c.includes(A),deepLink:$?this.#m(d,this.#f,$,I):this.#d(d,l,void 0,i,u,I),example:V,enum:v},k=this.#n(w);f[k]==null&&(f[k]=w,x||this.#t(y,t,n,i,o,f,I,s,a+1,d))}}getResult(){return this.#c}}export{B as ApiDocsSearchIndexer};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{MANAGE_API_CREDENTIALS_TOOL_NAME as
|
|
2
|
-
`),annotations:{title:"Manage API credentials",readOnlyHint:!1,openWorldHint:!1},schema:{type:"object",required:["api"],additionalProperties:!1,properties:{api:{type:"string",description:`The API to manage credentials for.${t.length>0?` One of: ${t.join(", ")}.`:""}`,minLength:1,...t.length>0?{enum:t}:{}},scheme:{type:"string",description:"Name of the security scheme to manage the credential for, as named in the API description (a tool result that reports a rejected credential names it). Omit it
|
|
1
|
+
import{MANAGE_API_CREDENTIALS_TOOL_NAME as v}from"../../constants.js";import{buildUpstreamCredentialFallback as y,buildUpstreamCredentialInputRequired as b,schemeLabelOf as P}from"../../gateway-mcp/elicitation.js";import{getCallableApis as m,isFetchCapable as S}from"../../gateway-mcp/fetch-capability.js";import{secretStore as I}from"../../gateway-mcp/secret-store.js";import{MISSING_SESSION_HANDLE_MESSAGE as w,resolveSecretSubject as k}from"../../gateway-mcp/subject.js";import{resolveUpstreamApiAuth as j}from"../../gateway-mcp/upstream-auth-scheme.js";function H(e){return!!e.elicitationEnabled&&S(e)}function U(e){const t=m(e).map(n=>n.relativePath);return{name:v,description:["Get a secure link where the user sets, replaces, or removes the credentials this server sends to a documented API.","Call it when the user wants to change or clear an API key, or when a tool result tells you the stored credentials were rejected.","The link covers every credential the API accepts, so scheme is optional - pass it only when a tool result names the scheme that was rejected.","It returns a link for the user to open - never ask for the secret in the conversation and never pass it as an argument."].join(`
|
|
2
|
+
`),annotations:{title:"Manage API credentials",readOnlyHint:!1,openWorldHint:!1},schema:{type:"object",required:["api"],additionalProperties:!1,properties:{api:{type:"string",description:`The API to manage credentials for.${t.length>0?` One of: ${t.join(", ")}.`:""}`,minLength:1,...t.length>0?{enum:t}:{}},scheme:{type:"string",description:"Name of the security scheme to manage the credential for, as named in the API description (a tool result that reports a rejected credential names it). Omit it to let the link cover every scheme the API accepts.",minLength:1},sessionHandle:{type:"string",description:"Handle returned by createAnonymousSession. Required while the caller is anonymous, so the credentials are stored against the same subject the API calls use.",minLength:1}}}}}function _(e,c){return async t=>{const n=k(e.user,typeof t.sessionHandle=="string"?t.sessionHandle:void 0);if(n.kind==="invalid")return l(n.message);if(n.kind==="anonymous")return l(w);const{subject:u}=n,h=typeof t.api=="string"?t.api:"",s=m(e).find(o=>o.relativePath===h||o.name===h);if(!s){const o=m(e).map(g=>g.relativePath).join(", ");return l(`Unknown api "${h}". Credentials can only be managed for the APIs this server can call: ${o||"none"}.`)}const r=await j(e,s.relativePath),a=typeof t.scheme=="string"?t.scheme:void 0,A=Object.keys(r.schemes);if(a&&!r.schemes[a])return l(`Unknown scheme "${a}" for api "${s.relativePath}". The security schemes this server can store credentials for are: ${A.join(", ")}.`);const i=a??r.defaultScheme,p=P(r.schemes,i),d=await c.getKv(),f={subject:u,baseUrl:e.baseUrl??"",target:{api:s.relativePath,name:s.name,scheme:i,...p?{schemeLabel:p}:{}},auth:r.schemes[i],apiAuth:r,kv:d,hasExistingSecret:await I.hasSecret(d,u,s.relativePath,i),reason:{kind:"requested"}};return c.getClientCapabilities()?.elicitation?.url?await b(f):{content:[{type:"text",text:await y(f)}],isError:!1}}}function l(e){return{content:[{type:"text",text:e}],isError:!0}}export{H as isManageApiCredentialsToolAvailable,_ as manageApiCredentials,U as manageApiCredentialsSchema};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { InputRequiredResult } from '@modelcontextprotocol/server';
|
|
2
2
|
import type { UpstreamAuthRequiredEvent } from './upstream-auth.js';
|
|
3
|
-
import type { SchemeChoice, UpstreamAuthScheme } from './upstream-auth-scheme.js';
|
|
3
|
+
import type { SchemeChoice, UpstreamApiAuth, UpstreamAuthScheme } from './upstream-auth-scheme.js';
|
|
4
4
|
import type { KvService } from '@redocly/config';
|
|
5
5
|
export declare const CREDENTIALS_INPUT_REQUEST_KEY = "credentials";
|
|
6
6
|
export type CredentialTarget = {
|
|
@@ -25,6 +25,7 @@ type CredentialElicitationRequest = {
|
|
|
25
25
|
baseUrl: string;
|
|
26
26
|
target: CredentialTarget;
|
|
27
27
|
auth: UpstreamAuthScheme;
|
|
28
|
+
apiAuth?: UpstreamApiAuth;
|
|
28
29
|
kv: KvService;
|
|
29
30
|
hasExistingSecret: boolean;
|
|
30
31
|
reason: CredentialElicitationReason;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{inputRequired as
|
|
2
|
-
`)}function
|
|
3
|
-
`)}async function
|
|
4
|
-
`)}export{
|
|
1
|
+
import{inputRequired as h,inputResponse as T}from"@modelcontextprotocol/server";import{MANAGE_API_CREDENTIALS_TOOL_NAME as l}from"../constants.js";import{secretStore as d,ELICITATION_TTL_MS as w}from"./secret-store.js";import{operationsForScheme as y,orderedSchemesOf as I}from"./upstream-auth-scheme.js";import{ANONYMOUS_SUBJECT_PREFIX as u}from"./subject.js";const m="credentials";function N(e){const t=T(e,m);return t.kind==="elicit"&&(t.action==="decline"||t.action==="cancel")}function j(e,t){return Object.keys(e).length>1?t:void 0}async function x(e){const{url:t}=await p(e);return h({inputRequests:{[m]:h.elicitUrl({message:P(e.target,e.hasExistingSecret),url:t})}})}async function R(e){const{url:t}=await p(e);return S(e,t,A(e.subject))}function U(e,t){const r=e.status===401?`The stored credentials for "${e.name}" were rejected (HTTP 401).`:`The "${e.name}" API rejected the request with HTTP ${e.status}.`,n=t?`api: "${e.api}" and scheme: "${t}"`:`api: "${e.api}"`;return[r,"",`Do not retry the same call. Call \`${l}\` with ${n} 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(`
|
|
2
|
+
`)}function F(e,t){const r=t.map(({name:n,label:o,description:a})=>`- ${n} \u2014 ${o}${a?`: ${a}`:""}`);return[`The "${e.name}" API rejected the request with HTTP ${e.status} and this operation accepts more than one kind of credential:`,"",...r,"","Ask the user which one of these they have, and wait for the answer. Never guess and never reuse a value meant for another kind.",`Then call \`${l}\` with api: "${e.api}" and scheme set to the name they picked to get a link where they enter it.`].join(`
|
|
3
|
+
`)}async function p({kv:e,subject:t,baseUrl:r,target:n,auth:o,apiAuth:a,hasExistingSecret:i}){const s=a?await b(e,t,n.api,a):[],c=await d.createElicitation(e,{subject:t,api:n.api,name:n.name,scheme:n.scheme,...n.schemeLabel?{schemeLabel:n.schemeLabel}:{},auth:o,hasExistingSecret:i,...s.length>1?{schemes:s}:{}});return{token:c,url:E(r,c)}}async function b(e,t,r,n){const a=(await d.getSecretsForSubject(e,t))[r]??{};return I(n.schemes).map(i=>{const s=y(n,i),c=n.descriptions?.[i];return{name:i,...c?{label:c}:{},auth:n.schemes[i],hasExistingSecret:!!a[i],...s.length>0?{operations:s}:{}}})}function A(e){return e.startsWith(u)?e.slice(u.length):void 0}function P(e,t){const r=`The link works for ${f()} minutes.`,n=g(e);return t?`Open this page in a browser to replace or remove the stored credentials for ${n}. They go straight to the API and never enter the chat. ${r}`:`${k(n)} needs credentials. Open this page in a browser to paste them - they go straight to the API and never enter the chat. ${r}`}function g({name:e,schemeLabel:t}){return t?`the "${t}" scheme of the "${e}" API`:`the "${e}" API`}function k(e){return e.charAt(0).toUpperCase()+e.slice(1)}function f(){return Math.round(w/6e4)}function E(e,t){if(!e)return`/mcp/credentials?token=${t}`;try{const r=new URL("/mcp/credentials",e);return r.searchParams.set("token",t),r.toString()}catch{return`${e}/mcp/credentials?token=${t}`}}function S({target:e,reason:t,hasExistingSecret:r},n,o){const i=[`**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 ${f()} 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} 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."],s=t.kind==="authFailure"?t.otherApiNames??[]:[];if(s.length>0){const c=s.map($=>`"${$}"`).join(", ");i.push("",`The remaining APIs (${c}) will prompt for credentials on subsequent calls.`)}return i.join(`
|
|
4
|
+
`)}export{m as CREDENTIALS_INPUT_REQUEST_KEY,U as buildCredentialsToolHint,F as buildSchemeChoiceHint,R as buildUpstreamCredentialFallback,x as buildUpstreamCredentialInputRequired,N as isCredentialElicitationDeclined,j as schemeLabelOf};
|
|
@@ -7,6 +7,13 @@ export type StoredSecret = {
|
|
|
7
7
|
savedAt?: number;
|
|
8
8
|
};
|
|
9
9
|
export type UpstreamSecrets = Record<string, Record<string, StoredSecret>>;
|
|
10
|
+
export type ElicitationScheme = {
|
|
11
|
+
name: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
auth: UpstreamAuthScheme;
|
|
14
|
+
hasExistingSecret: boolean;
|
|
15
|
+
operations?: string[];
|
|
16
|
+
};
|
|
10
17
|
export type ElicitationTarget = {
|
|
11
18
|
subject: string;
|
|
12
19
|
api: string;
|
|
@@ -15,6 +22,7 @@ export type ElicitationTarget = {
|
|
|
15
22
|
schemeLabel?: string;
|
|
16
23
|
auth: UpstreamAuthScheme;
|
|
17
24
|
hasExistingSecret?: boolean;
|
|
25
|
+
schemes?: ElicitationScheme[];
|
|
18
26
|
};
|
|
19
27
|
export declare class SecretStore {
|
|
20
28
|
#private;
|
|
@@ -17,6 +17,7 @@ export type UpstreamAuthScheme = {
|
|
|
17
17
|
};
|
|
18
18
|
export type UpstreamAuthRoute = {
|
|
19
19
|
pattern: RegExp;
|
|
20
|
+
path: string;
|
|
20
21
|
method: string;
|
|
21
22
|
schemes: string[];
|
|
22
23
|
};
|
|
@@ -37,5 +38,6 @@ export declare function resolveUpstreamApiAuth(context: McpToolContext, apiRelat
|
|
|
37
38
|
export declare function schemeChoicesOf(auth: UpstreamApiAuth, names: readonly string[]): SchemeChoice[];
|
|
38
39
|
export declare function credentialHeadersOf(schemes: Record<string, UpstreamAuthScheme>): string[];
|
|
39
40
|
export declare function orderedSchemesOf(schemes: Record<string, UpstreamAuthScheme>): string[];
|
|
41
|
+
export declare function operationsForScheme(auth: UpstreamApiAuth, scheme: string): string[];
|
|
40
42
|
export {};
|
|
41
43
|
//# sourceMappingURL=upstream-auth-scheme.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getApiDescriptionFromFs as O}from"../docs-mcp/tools/openapi/utils.js";import{OPERATION_METHODS as
|
|
2
|
-
`)[0].trim();s&&(n[r]=s.slice(0,
|
|
1
|
+
import{getApiDescriptionFromFs as O}from"../docs-mcp/tools/openapi/utils.js";import{OPERATION_METHODS as S}from"../docs-mcp/tools/openapi/constants.js";const A=120,l="default",g={kind:"raw"},h=["oauth2ClientCredentials","bearer","apiKey","cookie","raw"],w=new Set(S),C=/\{[^}]*\}/g;function c(){return{schemes:{[l]:g},defaultScheme:l,routes:[]}}async function L(e,t,n=O){const r=Object.values(e.apiDescriptionsMap??{}).find(i=>i.relativePath===t);if(!r)return c();try{const i=await n({relativePath:r.relativePath,outdir:e.outdir||"",user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return i?E(i,r.servers??[]):c()}catch{return c()}}function E(e,t){const n=$(e),r=Object.keys(n);if(r.length===0)return c();const i=m(e.security,n)[0]??a(r,n)[0],o=x(e,r);return{schemes:n,defaultScheme:i,routes:v(e,n,t),...Object.keys(o).length>0?{descriptions:o}:{}}}function N(e,t){return t.flatMap(n=>{const r=e.schemes[n];if(!r)return[];const i=e.descriptions?.[n];return[{name:n,label:j(r),...i?{description:i}:{}}]})}function U(e){return[...new Set(Object.values(e).map(P))]}function q(e){return a(Object.keys(e),e)}function z(e,t){const n=new Set;for(const r of e.routes)r.schemes.includes(t)&&n.add(`${r.method} ${r.path}`);return[...n]}function P(e){return e.kind==="apiKey"?e.header:e.kind==="cookie"?"Cookie":"Authorization"}function j(e){switch(e.kind){case"bearer":return"API key or bearer token";case"cookie":return`browser session cookie (${e.name})`;case"apiKey":return`API key in the ${e.header} header`;case"oauth2ClientCredentials":return"OAuth2 client credentials";default:return"Authorization header value"}}function x(e,t){const n={};for(const r of t){const i=e.components?.securitySchemes?.[r],o=k(i)?i.description:void 0;if(typeof o!="string")continue;const s=o.split(`
|
|
2
|
+
`)[0].trim();s&&(n[r]=s.slice(0,A))}return n}function $(e){const t={};for(const[n,r]of Object.entries(e.components?.securitySchemes??{})){if(!k(r))continue;const i=T(r);i&&(t[n]=i)}return t}function T(e){if(e.type==="oauth2"){const t=e.flows?.clientCredentials;return t?{kind:"oauth2ClientCredentials",tokenUrl:t.tokenUrl,scopes:Object.keys(t.scopes??{})}:{kind:"bearer"}}if(e.type==="http"&&e.scheme==="bearer")return{kind:"bearer"};if(e.type==="apiKey"&&e.in==="header")return{kind:"apiKey",header:e.name||"X-Api-Key"};if(e.type==="apiKey"&&e.in==="cookie"&&e.name)return{kind:"cookie",name:e.name}}function a(e,t){return[...e].sort((n,r)=>h.indexOf(t[n].kind)-h.indexOf(t[r].kind)||e.indexOf(n)-e.indexOf(r))}function m(e,t){const n=[];for(const r of e??[])for(const i of Object.keys(r))t[i]&&!n.includes(i)&&n.push(i);return n}function v(e,t,n){const r=I(n),i=[];for(const[o,s]of Object.entries(e.paths??{})){if(!s||typeof s!="object")continue;const f=H(r,o);if(f)for(const[p,b]of Object.entries(s)){if(!w.has(p.toLowerCase()))continue;const u=b?.security??e.security;if(u===void 0)continue;const d=m(u,t);d.length===0&&u.length>0||i.push({pattern:f,path:o,method:p.toUpperCase(),schemes:a(d,t)})}}return i}function H(e,t){if(t.startsWith("/"))return new RegExp(`^${e}${y(t)}/?$`)}function y(e){return e.split(C).map(_).join("[^/]+").replace(/\/+$/,"")}function I(e){const t=[...new Set(e.map(n=>R(n.url)).filter(Boolean).map(y))];return t.length>0?`(?:${t.join("|")})?`:""}function R(e){if(!e)return"";const t=e.replace(/^[a-z][a-z0-9+.-]*:\/\//i,""),n=t.indexOf("/");return n===-1?"":t.slice(n).split(/[?#]/)[0].replace(/\/+$/,"")}function _(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function k(e){return typeof e=="object"&&e!==null&&"type"in e}export{l as DEFAULT_SCHEME_NAME,U as credentialHeadersOf,z as operationsForScheme,q as orderedSchemesOf,L as resolveUpstreamApiAuth,N as schemeChoicesOf};
|
|
@@ -1,144 +1 @@
|
|
|
1
|
-
import{secretStore as
|
|
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>API credentials</title>
|
|
8
|
-
<style>
|
|
9
|
-
:root{color-scheme:light dark}
|
|
10
|
-
*{box-sizing:border-box}
|
|
11
|
-
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f3f4f6;color:#111827;padding:1.5rem}
|
|
12
|
-
.card{width:100%;max-width:26rem;background:#ffffff;border-radius:.75rem;box-shadow:0 10px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);padding:2rem}
|
|
13
|
-
h1{font-size:1.25rem;margin:0 0 .5rem}
|
|
14
|
-
.section{margin-top:1.5rem;padding-top:1.5rem;border-top:1px solid #e5e7eb}
|
|
15
|
-
.section h2{font-size:1rem;margin:0 0 .5rem}
|
|
16
|
-
p.hint{color:#6b7280;font-size:.875rem;line-height:1.4}
|
|
17
|
-
p.error{margin:1rem 0 0;padding:.75rem;border:1px solid #fecaca;border-radius:.5rem;background:#fef2f2;color:#991b1b;font-size:.875rem;line-height:1.4}
|
|
18
|
-
label{display:block;margin:1rem 0 .25rem;font-weight:600;font-size:.875rem}
|
|
19
|
-
input[type=password],input[type=text]{width:100%;padding:.625rem .75rem;font-size:1rem;border:1px solid #d1d5db;border-radius:.5rem;background:#fff;color:#111827}
|
|
20
|
-
input:focus{outline:none;border-color:#4f46e5;box-shadow:0 0 0 3px rgba(79,70,229,.2)}
|
|
21
|
-
button{margin-top:1.25rem;padding:.625rem 1.5rem;font-size:1rem;font-weight:600;color:#fff;background:#4f46e5;border:none;border-radius:.5rem;cursor:pointer}
|
|
22
|
-
button:hover{background:#4338ca}
|
|
23
|
-
button:focus-visible{outline:2px solid #4f46e5;outline-offset:2px}
|
|
24
|
-
button.danger{background:#b91c1c}
|
|
25
|
-
button.danger:hover{background:#991b1b}
|
|
26
|
-
button.danger:focus-visible{outline-color:#b91c1c}
|
|
27
|
-
code{background:#f3f4f6;padding:.125rem .375rem;border-radius:.25rem;font-size:.85em}
|
|
28
|
-
footer{margin-top:1.5rem;padding-top:1rem;border-top:1px solid #e5e7eb;font-size:.75rem;color:#9ca3af;text-align:center}
|
|
29
|
-
@media (prefers-color-scheme:dark){
|
|
30
|
-
body{background:#0b0f19;color:#e5e7eb}
|
|
31
|
-
.card{background:#111827;box-shadow:0 10px 25px -5px rgba(0,0,0,.4)}
|
|
32
|
-
.section{border-top-color:#1f2937}
|
|
33
|
-
p.hint{color:#9ca3af}
|
|
34
|
-
p.error{border-color:#7f1d1d;background:#2a1215;color:#fca5a5}
|
|
35
|
-
input[type=password],input[type=text]{background:#0b0f19;border-color:#374151;color:#e5e7eb}
|
|
36
|
-
code{background:#1f2937}
|
|
37
|
-
footer{border-top-color:#1f2937;color:#6b7280}
|
|
38
|
-
}
|
|
39
|
-
</style>
|
|
40
|
-
</head>
|
|
41
|
-
<body><div class="card">${e}</div></body>
|
|
42
|
-
</html>`}function l(e){return`<h1>\u{1F512} ${i(e)}</h1>`}function u(){return"<footer>Secured by Redocly · credentials never enter the AI conversation</footer>"}function b(){return`<p class="hint">This link expires ${Math.round(C/6e4)} minutes after it was issued. If it stops working, ask your AI client for a new one.</p>`}function S(e,t,n){const o=e.hasExistingSecret?N(t):"",r=n?`<p class="error">${i(n)}</p>`:"",s=k(e);switch(e.auth.kind){case"bearer":return W(s,t,o,r);case"apiKey":return X(s,t,e.auth.header,o,r);case"cookie":return Y(s,t,e.auth.name,o,r);case"oauth2ClientCredentials":return J(s,t,e.auth.scopes,o,r);default:return Q(s,t,o,r)}}function k({name:e,schemeLabel:t}){return t?`${e} (${t})`:e}function N(e){return`
|
|
43
|
-
<div class="section">
|
|
44
|
-
<h2>Remove stored credentials</h2>
|
|
45
|
-
<p class="hint">This API already has credentials stored. Save above to replace them, or remove them to make calls anonymous again.</p>
|
|
46
|
-
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
47
|
-
<input type="hidden" name="token" value="${i(e)}">
|
|
48
|
-
<input type="hidden" name="mode" value="clear">
|
|
49
|
-
<button class="danger" type="submit">Remove credentials</button>
|
|
50
|
-
</form>
|
|
51
|
-
</div>`}function W(e,t,n,o){return d(`
|
|
52
|
-
${l(`Credentials for ${e}`)}
|
|
53
|
-
${o}
|
|
54
|
-
<p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
|
|
55
|
-
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
56
|
-
<input type="hidden" name="token" value="${i(t)}">
|
|
57
|
-
<input type="hidden" name="mode" value="token">
|
|
58
|
-
<label for="secret">API token</label>
|
|
59
|
-
<input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
|
|
60
|
-
<p class="hint">Paste the raw token or one already prefixed with <code>Bearer</code> — we add the prefix automatically if it is missing.</p>
|
|
61
|
-
<button type="submit">Save credentials</button>
|
|
62
|
-
</form>
|
|
63
|
-
${n}
|
|
64
|
-
${b()}
|
|
65
|
-
${u()}`)}function X(e,t,n,o,r){return d(`
|
|
66
|
-
${l(`Credentials for ${e}`)}
|
|
67
|
-
${r}
|
|
68
|
-
<p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>${i(n)}</code> header. It never passes through the AI conversation.</p>
|
|
69
|
-
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
70
|
-
<input type="hidden" name="token" value="${i(t)}">
|
|
71
|
-
<input type="hidden" name="mode" value="token">
|
|
72
|
-
<label for="secret">${i(n)} value</label>
|
|
73
|
-
<input type="password" id="secret" name="secret" placeholder="value" required autofocus>
|
|
74
|
-
<button type="submit">Save credentials</button>
|
|
75
|
-
</form>
|
|
76
|
-
${o}
|
|
77
|
-
${b()}
|
|
78
|
-
${u()}`)}function Y(e,t,n,o,r){return d(`
|
|
79
|
-
${l(`Credentials for ${e}`)}
|
|
80
|
-
${r}
|
|
81
|
-
<p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>${i(n)}</code> cookie. It never passes through the AI conversation.</p>
|
|
82
|
-
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
83
|
-
<input type="hidden" name="token" value="${i(t)}">
|
|
84
|
-
<input type="hidden" name="mode" value="token">
|
|
85
|
-
<label for="secret">${i(n)} cookie value</label>
|
|
86
|
-
<input type="password" id="secret" name="secret" placeholder="value" required autofocus>
|
|
87
|
-
<p class="hint">Paste only the value — we send it as <code>${i(n)}=<value></code>.</p>
|
|
88
|
-
<button type="submit">Save credentials</button>
|
|
89
|
-
</form>
|
|
90
|
-
${o}
|
|
91
|
-
${b()}
|
|
92
|
-
${u()}`)}function J(e,t,n,o,r){const s=n.length>0?n.map(i).join(", "):"default scopes";return d(`
|
|
93
|
-
${l(`Credentials for ${e}`)}
|
|
94
|
-
${r}
|
|
95
|
-
<p class="hint">Paste a token you already have, or let us exchange client credentials for one via OAuth2 client_credentials.</p>
|
|
96
|
-
<div class="section">
|
|
97
|
-
<h2>I have a token</h2>
|
|
98
|
-
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
99
|
-
<input type="hidden" name="token" value="${i(t)}">
|
|
100
|
-
<input type="hidden" name="mode" value="token">
|
|
101
|
-
<label for="secret">API token</label>
|
|
102
|
-
<input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
|
|
103
|
-
<p class="hint">We add <code>Bearer</code> automatically if it is missing.</p>
|
|
104
|
-
<button type="submit">Save token</button>
|
|
105
|
-
</form>
|
|
106
|
-
</div>
|
|
107
|
-
<div class="section">
|
|
108
|
-
<h2>Exchange client credentials</h2>
|
|
109
|
-
<p class="hint">We request a token from the API on your behalf. Scopes: ${s}.</p>
|
|
110
|
-
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
111
|
-
<input type="hidden" name="token" value="${i(t)}">
|
|
112
|
-
<input type="hidden" name="mode" value="client-credentials">
|
|
113
|
-
<label for="client_id">Client ID</label>
|
|
114
|
-
<input type="text" id="client_id" name="client_id" required>
|
|
115
|
-
<label for="client_secret">Client secret</label>
|
|
116
|
-
<input type="password" id="client_secret" name="client_secret" required>
|
|
117
|
-
<button type="submit">Exchange & save</button>
|
|
118
|
-
</form>
|
|
119
|
-
</div>
|
|
120
|
-
${o}
|
|
121
|
-
${b()}
|
|
122
|
-
${u()}`)}function Q(e,t,n,o){return d(`
|
|
123
|
-
${l(`Credentials for ${e}`)}
|
|
124
|
-
${o}
|
|
125
|
-
<p class="hint">The value is stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and is sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
|
|
126
|
-
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
127
|
-
<input type="hidden" name="token" value="${i(t)}">
|
|
128
|
-
<input type="hidden" name="mode" value="token">
|
|
129
|
-
<label for="secret">Authorization header value</label>
|
|
130
|
-
<input type="password" id="secret" name="secret" placeholder="Bearer <your token>" required autofocus>
|
|
131
|
-
<button type="submit">Save credentials</button>
|
|
132
|
-
</form>
|
|
133
|
-
${n}
|
|
134
|
-
${b()}
|
|
135
|
-
${u()}`)}function V(e){return d(`
|
|
136
|
-
${l("Credentials removed")}
|
|
137
|
-
<p>The stored credentials for ${i(k(e))} are gone. Calls to that API are anonymous again until you add new ones.</p>
|
|
138
|
-
${u()}`)}function E(e){return d(`
|
|
139
|
-
${l("Credentials saved")}
|
|
140
|
-
<p>Credentials for ${i(k(e))} are saved. Go back to your AI client and ask it to retry - or confirm the prompt it left open, if there is one. You can close this tab.</p>
|
|
141
|
-
${u()}`)}function y(e){return new Response(d(`
|
|
142
|
-
${l("Exchange failed")}
|
|
143
|
-
<p>We could not exchange your client credentials for a token for ${i(e)}. This link has been used up - go back to your AI client and request a new link, then try again.</p>
|
|
144
|
-
${u()}`),{status:502,headers:p})}function m(){return new Response(d(`${l("Link expired")}<p>This credentials link is invalid, already used, or older than ${Math.round(C/6e4)} minutes. Retry the request in your AI client to get a fresh link.</p>${u()}`),{status:404,headers:p})}var ae=H;export{ae as default,re as setCredentialsFetchImpl};
|
|
1
|
+
import{secretStore as u}from"../gateway-mcp/secret-store.js";import{rejectCredential as k}from"../gateway-mcp/credential-validation.js";import{buildAllowedHostMatchers as P}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as A}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as R,getDefaultAgent as j}from"../gateway-mcp/fetch/fetch-bridge.js";import{logger as O}from"../../../tools/notifiers/logger.js";import{clearedPage as v,formPage as C,notFoundPage as N,successPage as y}from"./mcp-credentials-page.js";const m={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'"},I=3e4,D=64*1024;let x=fetch;function Q(e){x=e}const F=async(e,r)=>{const t=await r.getKv();if(e.method==="GET"){const n=new URL(e.url).searchParams.get("token")??"",o=await u.peekElicitation(t,n);return o?new Response(C(o,n),{status:200,headers:m}):f()}if(e.method==="POST"){const n=await e.formData(),o=d(n,"token"),s=d(n,"mode")||"token";return s==="multi"?B(t,n,o):s==="client-credentials"?G(t,n,o):s==="clear"?U(t,o):M(t,n,o)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function M(e,r,t){const n=d(r,"secret").trim(),o=await u.peekElicitation(e,t);if(!o)return f();const s=k(o.auth,n);if(s)return p(o,t,s);const l=await u.consumeElicitation(e,t);return l?(await u.setSecret(e,l.subject,l.api,l.scheme,S(l.auth,n)),new Response(y(l),{status:200,headers:m})):f()}async function U(e,r){const t=await u.consumeElicitation(e,r);return t?(await u.deleteSecret(e,t.subject,t.api,t.scheme),new Response(v(t),{status:200,headers:m})):f()}async function B(e,r,t){const n=await u.peekElicitation(e,t);if(!n?.schemes||n.schemes.length<=1)return f();const o=n.schemes,s=[];for(let a=0;a<o.length;a++){const i=o[a];if(i.hasExistingSecret&&d(r,`remove__${a}`)){s.push({kind:"remove",scheme:i.name});continue}const h=d(r,`secret__${a}`).trim();if(i.auth.kind==="oauth2ClientCredentials"){const E=d(r,`client_id__${a}`).trim(),_=d(r,`client_secret__${a}`).trim();if(h){const b=k(i.auth,h);if(b)return p(n,t,`${i.name}: ${b}`);s.push({kind:"set",scheme:i.name,secret:S(i.auth,h)})}else if(E&&_)s.push({kind:"exchange",scheme:i.name,auth:i.auth,clientId:E,clientSecret:_});else if(E||_)return p(n,t,`${i.name}: enter both the client ID and the client secret, or leave both blank.`);continue}if(!h)continue;const w=k(i.auth,h);if(w)return p(n,t,`${i.name}: ${w}`);s.push({kind:"set",scheme:i.name,secret:S(i.auth,h)})}if(s.length===0)return p(n,t,"Fill in at least one credential, or check a box to remove one, before saving.");const l=[];for(const a of s){if(a.kind!=="exchange"){l.push(a);continue}const i=await $(a.auth,a.clientId,a.clientSecret);if(!i.ok)return T(n,t,i.reason,a.scheme);l.push({kind:"set",scheme:a.scheme,secret:i.secret})}const c=await u.consumeElicitation(e,t);if(!c)return f();for(const a of l)a.kind==="remove"?await u.deleteSecret(e,c.subject,c.api,a.scheme):await u.setSecret(e,c.subject,c.api,a.scheme,a.secret);return new Response(y(c),{status:200,headers:m})}async function G(e,r,t){const n=d(r,"client_id").trim(),o=d(r,"client_secret").trim(),s=await u.peekElicitation(e,t);if(!s||s.auth.kind!=="oauth2ClientCredentials")return f();if(!n||!o)return p(s,t,"Enter both the client ID and the client secret before saving.");const l=await $(s.auth,n,o);if(!l.ok)return T(s,t,l.reason);const c=await u.consumeElicitation(e,t);return c?(await u.setSecret(e,c.subject,c.api,c.scheme,l.secret),new Response(y(c),{status:200,headers:m})):f()}async function $(e,r,t){try{const n=P([{url:e.tokenUrl}]),o=A(e.tokenUrl,n),s=new AbortController,l=setTimeout(()=>s.abort(),I);let c;try{c=await x(o,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:r,client_secret:t}),redirect:"manual",signal:s.signal,dispatcher:j()})}finally{clearTimeout(l)}if(!c.ok)return g(`token endpoint returned ${c.status}`);let a;try{a=await R(c,D)}catch{return g("token response exceeded the size cap")}let i;try{i=JSON.parse(a)}catch{return g("token response was not valid JSON")}const h=typeof i=="object"&&i!==null&&"access_token"in i?i.access_token:void 0,w=typeof h=="string"?h.trim():"";return w?{ok:!0,secret:{header:"Authorization",value:`Bearer ${w}`}}:g("token response had no access_token")}catch(n){return g(n instanceof Error?n.message:String(n))}}function g(e){return{ok:!1,reason:e}}function T(e,r,t,n){return O.warn(`gateway oauth2 client_credentials exchange failed for ${e.api}: ${t}`),p(e,r,`${n?`${n}: we`:"We"} could not exchange those client credentials for a token. Check the client ID and secret, then try again.`)}function d(e,r){const t=e.get(r);return typeof t=="string"?t:""}function p(e,r,t){return new Response(C(e,r,t),{status:400,headers:m})}function f(){return new Response(N(),{status:404,headers:m})}function S(e,r){return e.kind==="apiKey"?{header:e.header,value:r}:e.kind==="cookie"?{header:"Cookie",value:`${e.name}=${r}`}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:H(r)}:{header:"Authorization",value:r}}function H(e){const r=e.trim();return r.toLowerCase().startsWith("bearer")?r:`Bearer ${r}`}var V=F;export{V as default,Q as setCredentialsFetchImpl};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ElicitationTarget } from '../gateway-mcp/secret-store.js';
|
|
2
|
+
export declare function formPage(target: ElicitationTarget, token: string, error?: string): string;
|
|
3
|
+
export declare function clearedPage(target: ElicitationTarget): string;
|
|
4
|
+
export declare function successPage(target: ElicitationTarget): string;
|
|
5
|
+
export declare function notFoundPage(): string;
|
|
6
|
+
//# sourceMappingURL=mcp-credentials-page.d.ts.map
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import{ELICITATION_TTL_MS as p}from"../gateway-mcp/secret-store.js";const h=6,f=`<svg class="logo" width="193" height="56" viewBox="0 0 193 56" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Redocly">
|
|
2
|
+
<g clip-path="url(#redocly-logo-clip)">
|
|
3
|
+
<path d="M84.3634 40.7533H80.0357L73.199 30.9849H69.8119V40.8155H66.1738V14.5592H76.3353C78.9067 14.5592 80.9139 15.368 82.2308 16.9857C83.5483 18.5412 84.1756 20.5322 84.1756 22.8343C84.1756 25.0119 83.6107 26.7541 82.4193 28.3095C81.2902 29.865 79.5339 30.7361 77.2134 30.8605L84.3634 40.7533ZM69.8743 17.8568V27.6251H76.2098C79.22 27.6251 80.5999 25.4475 80.5999 22.7098C80.5999 20.0344 79.22 17.7945 76.3353 17.7945H69.8743V17.8568Z" fill="currentColor"/>
|
|
4
|
+
<path d="M103.117 33.225H88.5654C89.0042 36.1492 91.2623 38.078 94.3355 38.078C96.5312 38.078 98.5384 37.0203 99.4789 35.3403C99.8553 35.4648 100.357 35.7137 101.047 35.9626L102.553 36.4603C101.047 39.5091 97.9111 41.1889 94.2731 41.1889C91.701 41.1889 89.506 40.3179 87.6873 38.638C85.931 36.8959 85.1152 34.7182 85.1152 31.9184C85.1152 29.1185 85.9934 26.9409 87.6873 25.2609C89.4436 23.5188 91.6386 22.71 94.2731 22.71C96.8445 22.71 98.9771 23.581 100.608 25.2609C102.302 26.9409 103.18 29.1807 103.18 31.9184C103.117 32.5405 103.117 32.9139 103.117 33.225ZM99.6674 30.4873C99.228 27.563 97.0954 25.7587 94.21 25.7587C91.1999 25.7587 88.9418 27.6252 88.5654 30.4873H99.6674Z" fill="currentColor"/>
|
|
5
|
+
<path d="M120.994 40.7532V38.0155C119.802 40.0066 117.356 41.1265 114.659 41.1265C112.087 41.1265 110.018 40.2555 108.198 38.5755C106.442 36.8334 105.627 34.6557 105.627 31.9181C105.627 29.1183 106.505 26.9406 108.198 25.2607C109.892 23.5808 112.087 22.7098 114.596 22.7098C117.293 22.7098 119.74 23.7675 120.931 25.7585V13.377H124.444V40.8154L120.994 40.7532ZM110.833 27.6873C109.704 28.745 109.202 30.176 109.202 31.9181C109.202 33.6603 109.704 35.0913 110.833 36.2112C111.962 37.3312 113.342 37.8911 114.973 37.8911C116.604 37.8911 117.983 37.3934 119.175 36.3357C120.367 35.2157 120.931 33.7847 120.931 31.9803C120.931 30.1138 120.304 28.745 119.113 27.6873C117.92 26.6295 116.54 26.1318 114.973 26.1318C113.342 26.0696 111.962 26.5673 110.833 27.6873Z" fill="currentColor"/>
|
|
6
|
+
<path d="M129.461 25.1985C131.279 23.4564 133.475 22.6476 136.172 22.6476C138.806 22.6476 141.064 23.5186 142.883 25.1985C144.702 26.8784 145.581 29.1183 145.581 31.8559C145.581 34.5935 144.702 36.8334 142.821 38.5756C141.002 40.3177 138.806 41.1266 136.172 41.1266C133.6 41.1266 131.342 40.2555 129.523 38.5756C127.704 36.8957 126.826 34.6558 126.826 31.8559C126.826 29.0561 127.704 26.8784 129.461 25.1985ZM140.375 27.6251C139.246 26.5673 137.803 26.0696 136.172 26.0696C134.541 26.0696 133.099 26.5673 131.907 27.6251C130.778 28.6828 130.213 30.1138 130.213 31.8559C130.213 33.6603 130.778 35.0913 131.907 36.2112C133.099 37.3312 134.479 37.8289 136.172 37.8289C137.803 37.8289 139.246 37.3312 140.375 36.2112C141.566 35.0913 142.13 33.6603 142.13 31.8559C142.13 30.1138 141.503 28.6828 140.375 27.6251Z" fill="currentColor"/>
|
|
7
|
+
<path d="M163.896 25.4474L161.262 27.6873C160.133 26.5674 158.69 26.0074 156.997 26.0074C155.366 26.0074 153.923 26.5051 152.732 27.6251C151.54 28.6828 150.976 30.1138 150.976 31.856C150.976 33.5981 151.54 35.0913 152.732 36.149C153.923 37.269 155.303 37.829 156.997 37.829C158.753 37.829 160.133 37.269 161.262 36.0869L163.959 38.3267C162.14 40.2555 159.882 41.251 156.997 41.251C154.362 41.251 152.105 40.3799 150.285 38.7C148.404 36.9579 147.525 34.718 147.525 31.9804C147.525 29.2428 148.404 27.0651 150.285 25.323C152.167 23.5808 154.362 22.772 156.997 22.772C159.819 22.6476 162.14 23.5808 163.896 25.4474Z" fill="currentColor"/>
|
|
8
|
+
<path d="M171.987 13.377H168.475V40.8154H171.987V13.377Z" fill="currentColor"/>
|
|
9
|
+
<path d="M181.898 49.4639H178.26L182.024 40.3177L174.873 23.0209H178.574L183.968 35.9624L189.3 23.0209H193L181.898 49.4639Z" fill="currentColor"/>
|
|
10
|
+
<path d="M47.2308 43.491C45.8508 42.5576 44.3455 41.6866 42.8401 41.0644C45.4745 39.9445 47.9207 38.389 49.8651 36.398C53.754 32.5404 56.2002 27.1896 56.2002 21.341C56.2002 15.4303 53.8167 10.1417 49.8651 6.28409C45.9763 2.36431 40.6448 0 34.6861 0H1.63081C1.19174 0 0.752681 0.186656 0.501787 0.497751C0.18817 0.746628 0 1.18215 0 1.61769C0 2.05322 0.18817 2.48875 0.501787 2.73763C0.752681 3.04872 1.19174 3.23538 1.63081 3.23538C6.64866 3.23538 11.2275 5.2886 14.5518 8.52397C17.8134 11.8216 19.8833 16.3635 19.8833 21.341C19.8833 26.3185 17.8134 30.8605 14.5518 34.1581C11.2275 37.3935 6.64866 39.4467 1.63081 39.4467C1.44264 39.4467 1.37992 39.5089 1.19174 39.5089C0.501787 39.6956 0 40.2556 0 41.0022V41.1888C0 42.0599 0.689956 42.6821 1.50536 42.6821H2.57166C6.20962 42.8687 9.59667 44.0509 12.3565 46.0419C15.3672 48.2195 17.688 51.2061 18.9425 54.7526C19.1307 55.3747 19.8206 55.8102 20.4478 55.8102H53.5658C54.0676 55.8102 54.5694 55.5614 54.8203 55.1258C55.1339 54.6903 55.1966 54.1926 55.0085 53.6948C53.5031 49.5884 50.806 46.0419 47.2308 43.491ZM16.8098 36.398C20.6987 32.5404 23.1449 27.1896 23.1449 21.341C23.1449 15.4303 20.7615 10.1417 16.8098 6.28409C15.6181 5.10194 14.3009 4.04422 12.8583 3.17316H34.6861C39.704 3.17316 44.2828 5.22638 47.6071 8.46173C50.8687 11.7594 52.9385 16.3013 52.9385 21.2788C52.9385 26.2563 50.8687 30.7983 47.6071 34.0959C44.2828 37.3313 39.704 39.3845 34.6861 39.3845C34.5606 39.3845 34.4979 39.3845 34.3725 39.4467H12.7956C14.2382 38.7001 15.6181 37.6424 16.8098 36.398ZM14.1755 43.491C13.7992 43.242 13.3601 42.9932 12.9838 42.6821H35.6896C39.3276 42.8687 42.7146 44.0509 45.4745 46.0419C47.858 47.7218 49.8024 49.9617 51.1196 52.6371H21.4514C19.946 48.9662 17.3744 45.8552 14.1755 43.491Z" fill="currentColor"/>
|
|
11
|
+
<path d="M29.668 21.3412C29.668 22.2122 30.3579 22.8966 31.2361 22.8966H47.1679C48.0459 22.8966 48.7359 22.2122 48.7359 21.3412C48.7359 20.4701 48.0459 19.7856 47.1679 19.7856H31.2988C30.3579 19.7856 29.668 20.5323 29.668 21.3412Z" fill="currentColor"/>
|
|
12
|
+
<path d="M28.5385 29.2427H44.4703C45.3484 29.2427 46.0383 28.5583 46.0383 27.6873C46.0383 26.8162 45.3484 26.1318 44.4703 26.1318H28.5385C27.6604 26.1318 26.9705 26.8162 26.9705 27.6873C26.9077 28.5583 27.6604 29.2427 28.5385 29.2427Z" fill="currentColor"/>
|
|
13
|
+
<path d="M28.5385 16.6127H44.4703C45.3484 16.6127 46.0383 15.9282 46.0383 15.0572C46.0383 14.1861 45.3484 13.5017 44.4703 13.5017H28.5385C27.6604 13.5017 26.9705 14.1861 26.9705 15.0572C26.9077 15.8661 27.6604 16.6127 28.5385 16.6127Z" fill="currentColor"/>
|
|
14
|
+
</g>
|
|
15
|
+
<defs><clipPath id="redocly-logo-clip"><rect width="193" height="56" fill="white"/></clipPath></defs>
|
|
16
|
+
</svg>`;function n(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}function s(e){return`<!doctype html>
|
|
17
|
+
<html lang="en">
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="utf-8">
|
|
20
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
21
|
+
<meta name="robots" content="noindex">
|
|
22
|
+
<title>API credentials</title>
|
|
23
|
+
<style>
|
|
24
|
+
:root{
|
|
25
|
+
color-scheme:light dark;
|
|
26
|
+
--font:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
|
27
|
+
--font-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
|
|
28
|
+
--page-bg:#ededf2;
|
|
29
|
+
--card-bg:#fbfbfc;
|
|
30
|
+
--border:#dcdde5;
|
|
31
|
+
--border-strong:#c4c6d1;
|
|
32
|
+
--text:#1a1c21;
|
|
33
|
+
--text-muted:#6e6f7a;
|
|
34
|
+
--brand:#2467f2;
|
|
35
|
+
--action:#1f7cff;
|
|
36
|
+
--action-hover:#3072db;
|
|
37
|
+
--action-ring:rgba(31,124,255,.25);
|
|
38
|
+
--code-bg:#ededf2;
|
|
39
|
+
--danger:#c74b55;
|
|
40
|
+
--danger-hover:#a9434c;
|
|
41
|
+
--error-bg:#fef2f2;
|
|
42
|
+
--error-border:#f5a3aa;
|
|
43
|
+
--error-text:#8b3c42;
|
|
44
|
+
}
|
|
45
|
+
@media (prefers-color-scheme:dark){:root{
|
|
46
|
+
--page-bg:#12141a;
|
|
47
|
+
--card-bg:#22242b;
|
|
48
|
+
--border:#2a2b33;
|
|
49
|
+
--border-strong:#3b3c45;
|
|
50
|
+
--text:#fbfbfc;
|
|
51
|
+
--text-muted:#9b9ca8;
|
|
52
|
+
--brand:#2467f2;
|
|
53
|
+
--action:#1f7cff;
|
|
54
|
+
--action-hover:#3072db;
|
|
55
|
+
--action-ring:rgba(31,124,255,.35);
|
|
56
|
+
--code-bg:#2a2b33;
|
|
57
|
+
--danger:#e5535f;
|
|
58
|
+
--danger-hover:#ef858d;
|
|
59
|
+
--error-bg:#2a1517;
|
|
60
|
+
--error-border:#8b3c42;
|
|
61
|
+
--error-text:#f5a3aa;
|
|
62
|
+
}}
|
|
63
|
+
*{box-sizing:border-box}
|
|
64
|
+
body{font-family:var(--font);margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--page-bg);color:var(--text);padding:1.5rem;font-size:14px;line-height:1.5}
|
|
65
|
+
.card{width:100%;max-width:26rem;background:var(--card-bg);border:1px solid var(--border);border-radius:8px;box-shadow:0 1px 2px rgba(26,28,33,.04),0 8px 24px -12px rgba(26,28,33,.18);padding:2rem}
|
|
66
|
+
.logo{color:var(--brand);height:28px;width:auto;display:block;margin:0 0 1.5rem}
|
|
67
|
+
h1{font-size:1.25rem;line-height:1.3;font-weight:600;margin:0 0 .5rem;letter-spacing:-.01em}
|
|
68
|
+
.section{margin-top:1.5rem;padding-top:1.5rem;border-top:1px solid var(--border)}
|
|
69
|
+
.section h2{font-size:.9375rem;font-weight:600;margin:0 0 .5rem}
|
|
70
|
+
p{margin:.75rem 0 0}
|
|
71
|
+
p.hint{color:var(--text-muted);font-size:.8125rem;line-height:1.45}
|
|
72
|
+
p.error{margin:1rem 0 0;padding:.75rem;border:1px solid var(--error-border);border-radius:6px;background:var(--error-bg);color:var(--error-text);font-size:.8125rem;line-height:1.45}
|
|
73
|
+
label{display:block;margin:1rem 0 .25rem;font-weight:600;font-size:.8125rem}
|
|
74
|
+
input[type=password],input[type=text]{width:100%;padding:.625rem .75rem;font-size:.9375rem;font-family:var(--font);border:1px solid var(--border-strong);border-radius:6px;background:var(--card-bg);color:var(--text)}
|
|
75
|
+
input::placeholder{color:var(--text-muted)}
|
|
76
|
+
input:focus{outline:none;border-color:var(--action);box-shadow:0 0 0 3px var(--action-ring)}
|
|
77
|
+
button{margin-top:1.25rem;padding:.625rem 1.25rem;font-size:.9375rem;font-weight:600;font-family:var(--font);color:#fff;background:var(--action);border:1px solid transparent;border-radius:6px;cursor:pointer}
|
|
78
|
+
button:hover{background:var(--action-hover)}
|
|
79
|
+
button:focus-visible{outline:2px solid var(--action);outline-offset:2px}
|
|
80
|
+
button.danger{background:transparent;color:var(--danger);border-color:var(--border-strong)}
|
|
81
|
+
button.danger:hover{background:var(--danger);color:#fff;border-color:var(--danger)}
|
|
82
|
+
button.danger:focus-visible{outline-color:var(--danger)}
|
|
83
|
+
code{font-family:var(--font-mono);background:var(--code-bg);padding:.125rem .375rem;border-radius:4px;font-size:.8125em}
|
|
84
|
+
label.check{display:flex;align-items:center;gap:.5rem;margin-top:.75rem;font-weight:400;font-size:.8125rem;color:var(--text-muted)}
|
|
85
|
+
label.check input{width:auto;margin:0}
|
|
86
|
+
footer{margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--border);font-size:.75rem;color:var(--text-muted);text-align:center;line-height:1.45}
|
|
87
|
+
</style>
|
|
88
|
+
</head>
|
|
89
|
+
<body><main class="card">${f}${e}</main></body>
|
|
90
|
+
</html>`}function l(e){return`<h1>\u{1F512} ${n(e)}</h1>`}function d(){return"<footer>Secured by Redocly · Credentials never enter the AI conversation</footer>"}function c(){return`<p class="hint">This link expires ${Math.round(p/6e4)} minutes after it was issued. If it stops working, request a new one in your AI client.</p>`}function u({name:e,schemeLabel:t,schemes:o}){return o&&o.length>1?e:t?`${e} (${t})`:e}function b(e){return`
|
|
91
|
+
<div class="section">
|
|
92
|
+
<h2>Remove stored credentials</h2>
|
|
93
|
+
<p class="hint">This API already has credentials stored. Enter new credentials to replace them, or remove them to make calls anonymous again.</p>
|
|
94
|
+
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
95
|
+
<input type="hidden" name="token" value="${n(e)}">
|
|
96
|
+
<input type="hidden" name="mode" value="clear">
|
|
97
|
+
<button class="danger" type="submit">Remove credentials</button>
|
|
98
|
+
</form>
|
|
99
|
+
</div>`}function H(e,t,o){const r=o?`<p class="error">${n(o)}</p>`:"";if(e.schemes&&e.schemes.length>1)return x(e,e.schemes,t,r);const a=e.hasExistingSecret?b(t):"",i=u(e);switch(e.auth.kind){case"bearer":return C(i,t,a,r);case"apiKey":return g(i,t,e.auth.header,a,r);case"cookie":return v(i,t,e.auth.name,a,r);case"oauth2ClientCredentials":return y(i,t,e.auth.scopes,a,r);default:return $(i,t,a,r)}}function C(e,t,o,r){return s(`
|
|
100
|
+
${l(`Credentials for ${e}`)}
|
|
101
|
+
${r}
|
|
102
|
+
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>Authorization header</code>. It never passes through the AI conversation.</p>
|
|
103
|
+
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
104
|
+
<input type="hidden" name="token" value="${n(t)}">
|
|
105
|
+
<input type="hidden" name="mode" value="token">
|
|
106
|
+
<label for="secret">API token</label>
|
|
107
|
+
<input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
|
|
108
|
+
<p class="hint">Paste your token with or without the <code>Bearer prefix</code>. The prefix is added automatically, if missing.</p>
|
|
109
|
+
<button type="submit">Save credentials</button>
|
|
110
|
+
</form>
|
|
111
|
+
${o}
|
|
112
|
+
${c()}
|
|
113
|
+
${d()}`)}function g(e,t,o,r,a){return s(`
|
|
114
|
+
${l(`Credentials for ${e}`)}
|
|
115
|
+
${a}
|
|
116
|
+
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>${n(o)}</code> header. It never passes through the AI conversation.</p>
|
|
117
|
+
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
118
|
+
<input type="hidden" name="token" value="${n(t)}">
|
|
119
|
+
<input type="hidden" name="mode" value="token">
|
|
120
|
+
<label for="secret">${n(o)} value</label>
|
|
121
|
+
<input type="password" id="secret" name="secret" placeholder="value" required autofocus>
|
|
122
|
+
<button type="submit">Save credentials</button>
|
|
123
|
+
</form>
|
|
124
|
+
${r}
|
|
125
|
+
${c()}
|
|
126
|
+
${d()}`)}function v(e,t,o,r,a){return s(`
|
|
127
|
+
${l(`Credentials for ${e}`)}
|
|
128
|
+
${a}
|
|
129
|
+
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>${n(o)}</code> cookie. It never passes through the AI conversation.</p>
|
|
130
|
+
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
131
|
+
<input type="hidden" name="token" value="${n(t)}">
|
|
132
|
+
<input type="hidden" name="mode" value="token">
|
|
133
|
+
<label for="secret">${n(o)} cookie value</label>
|
|
134
|
+
<input type="password" id="secret" name="secret" placeholder="value" required autofocus>
|
|
135
|
+
<p class="hint">Paste only the value. It's sent as <code>${n(o)}=<value></code>.</p>
|
|
136
|
+
<button type="submit">Save credentials</button>
|
|
137
|
+
</form>
|
|
138
|
+
${r}
|
|
139
|
+
${c()}
|
|
140
|
+
${d()}`)}function y(e,t,o,r,a){const i=o.length>0?o.map(n).join(", "):"default scopes";return s(`
|
|
141
|
+
${l(`Credentials for ${e}`)}
|
|
142
|
+
${a}
|
|
143
|
+
<p class="hint">Paste your token, or provide your OAuth2 client credentials.</p>
|
|
144
|
+
<div class="section">
|
|
145
|
+
<h2>I have a token</h2>
|
|
146
|
+
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
147
|
+
<input type="hidden" name="token" value="${n(t)}">
|
|
148
|
+
<input type="hidden" name="mode" value="token">
|
|
149
|
+
<label for="secret">API token</label>
|
|
150
|
+
<input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
|
|
151
|
+
<p class="hint"><code>Bearer</code> prefix is added automatically, if missing.</p>
|
|
152
|
+
<button type="submit">Save token</button>
|
|
153
|
+
</form>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="section">
|
|
156
|
+
<h2>Exchange client credentials</h2>
|
|
157
|
+
<p class="hint">We request a token from the API on your behalf. Scopes: ${i}.</p>
|
|
158
|
+
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
159
|
+
<input type="hidden" name="token" value="${n(t)}">
|
|
160
|
+
<input type="hidden" name="mode" value="client-credentials">
|
|
161
|
+
<label for="client_id">Client ID</label>
|
|
162
|
+
<input type="text" id="client_id" name="client_id" required>
|
|
163
|
+
<label for="client_secret">Client secret</label>
|
|
164
|
+
<input type="password" id="client_secret" name="client_secret" required>
|
|
165
|
+
<button type="submit">Exchange & save</button>
|
|
166
|
+
</form>
|
|
167
|
+
</div>
|
|
168
|
+
${r}
|
|
169
|
+
${c()}
|
|
170
|
+
${d()}`)}function $(e,t,o,r){return s(`
|
|
171
|
+
${l(`Credentials for ${e}`)}
|
|
172
|
+
${r}
|
|
173
|
+
<p class="hint">The value is stored and encrypted only for you for 24 hours (anonymous) or 30 days (signed in).<br />It is sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
|
|
174
|
+
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
175
|
+
<input type="hidden" name="token" value="${n(t)}">
|
|
176
|
+
<input type="hidden" name="mode" value="token">
|
|
177
|
+
<label for="secret">Authorization header value</label>
|
|
178
|
+
<input type="password" id="secret" name="secret" placeholder="Bearer <your token>" required autofocus>
|
|
179
|
+
<button type="submit">Save credentials</button>
|
|
180
|
+
</form>
|
|
181
|
+
${o}
|
|
182
|
+
${c()}
|
|
183
|
+
${d()}`)}function x(e,t,o,r){const a=t.map((i,m)=>k(i,m)).join(`
|
|
184
|
+
`);return s(`
|
|
185
|
+
${l(`Credentials for ${e.name}`)}
|
|
186
|
+
${r}
|
|
187
|
+
<p class="hint">This API accepts more than one kind of credential. Fill in the one(s) you have and leave the rest blank - each value is stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent straight to the API. It never passes through the AI conversation.</p>
|
|
188
|
+
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
189
|
+
<input type="hidden" name="token" value="${n(o)}">
|
|
190
|
+
<input type="hidden" name="mode" value="multi">
|
|
191
|
+
${a}
|
|
192
|
+
<button type="submit">Save credentials</button>
|
|
193
|
+
</form>
|
|
194
|
+
${c()}
|
|
195
|
+
${d()}`)}function k(e,t){const o=A(e.auth),r=e.label?`<p class="hint">${n(e.label)}</p>`:"",a=_(e.operations),i=e.hasExistingSecret?`<label class="check"><input type="checkbox" name="remove__${t}" value="1"> Remove the stored credential</label>`:"";return`
|
|
196
|
+
<div class="section">
|
|
197
|
+
<h2>${n(e.name)}</h2>
|
|
198
|
+
<p class="hint">Sent to the API as the <code>${n(o.code)}</code> ${o.suffix}.</p>
|
|
199
|
+
${r}
|
|
200
|
+
${a}
|
|
201
|
+
${w(e.auth,t)}
|
|
202
|
+
${i}
|
|
203
|
+
</div>`}function w(e,t){const o=`secret__${t}`;if(e.kind==="oauth2ClientCredentials"){const a=e.scopes.length>0?e.scopes.map(n).join(", "):"default scopes";return`<label for="${o}">API token</label>
|
|
204
|
+
<input type="password" id="${o}" name="${o}" placeholder="paste a token you already have">
|
|
205
|
+
<p class="hint">Or exchange client credentials for one (scopes: ${a}). We add <code>Bearer</code> automatically.</p>
|
|
206
|
+
<label for="client_id__${t}">Client ID</label>
|
|
207
|
+
<input type="text" id="client_id__${t}" name="client_id__${t}">
|
|
208
|
+
<label for="client_secret__${t}">Client secret</label>
|
|
209
|
+
<input type="password" id="client_secret__${t}" name="client_secret__${t}">`}const r=I(e);return`<label for="${o}">${n(r)}</label>
|
|
210
|
+
<input type="password" id="${o}" name="${o}" placeholder="value">`}function I(e){switch(e.kind){case"apiKey":return`${e.header} value`;case"cookie":return`${e.name} cookie value`;case"bearer":return"API token";default:return"Authorization header value"}}function A(e){switch(e.kind){case"apiKey":return{code:e.header,suffix:"header"};case"cookie":return{code:e.name,suffix:"cookie"};case"bearer":return{code:"Authorization",suffix:"header (bearer token)"};case"oauth2ClientCredentials":return{code:"Authorization",suffix:"header (OAuth2 bearer token)"};default:return{code:"Authorization",suffix:"header"}}}function _(e){if(!e||e.length===0)return"";const t=e.slice(0,h).map(n).join(", "),o=e.length-h,r=o>0?`, and ${o} more`:"";return`<p class="hint">Used by ${t}${r}.</p>`}function M(e){return s(`
|
|
211
|
+
${l("Credentials removed")}
|
|
212
|
+
<p>Credentials for ${n(u(e))} are removed. Calls to the API are anonymous again until you add new credentials.</p>
|
|
213
|
+
${d()}`)}function S(e){return s(`
|
|
214
|
+
${l("Credentials saved")}
|
|
215
|
+
<p>Credentials for ${n(u(e))} are saved. Retry in your AI client, or confirm any prompt it left open. You can close this tab.</p>
|
|
216
|
+
<p class="hint">This link is single-use and is now spent. To change anything, request a new one in your AI client.</p>
|
|
217
|
+
${d()}`)}function z(){return s(`${l("Link expired")}<p>This credentials link is invalid, already used, or older than ${Math.round(p/6e4)} minutes. Retry in your AI client to get a fresh link.</p>${d()}`)}export{M as clearedPage,H as formPage,z as notFoundPage,S as successPage};
|