Merge branch 'main' into Enhancement-web-search

This commit is contained in:
Akanksha256742 2025-09-19 17:26:03 +05:30 committed by GitHub
commit 9b335271e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 542 additions and 98 deletions

View file

@ -44,7 +44,7 @@ async function reinitMCPServer({
const oauthStart =
_oauthStart ??
(async (authURL) => {
logger.info(`[MCP Reinitialize] OAuth URL received: ${authURL}`);
logger.info(`[MCP Reinitialize] OAuth URL received for ${serverName}`);
oauthUrl = authURL;
oauthRequired = true;
});

View file

@ -24,35 +24,45 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: React.Ref<HTMLDivEleme
const inputRef = useRef<HTMLInputElement>(null);
const [showClearIcon, setShowClearIcon] = useState(false);
const { newConversation } = useNewConvo();
const { newConversation: newConvo } = useNewConvo();
const [search, setSearchState] = useRecoilState(store.search);
const clearSearch = useCallback(() => {
if (location.pathname.includes('/search')) {
newConversation({ disableFocus: true });
navigate('/c/new', { replace: true });
}
}, [newConversation, location.pathname, navigate]);
const clearSearch = useCallback(
(pathname?: string) => {
if (pathname?.includes('/search') || pathname === '/c/new') {
queryClient.removeQueries([QueryKeys.messages]);
newConvo({ disableFocus: true });
navigate('/c/new');
}
},
[newConvo, navigate, queryClient],
);
const clearText = useCallback(() => {
setShowClearIcon(false);
setText('');
setSearchState((prev) => ({
...prev,
query: '',
debouncedQuery: '',
isTyping: false,
}));
clearSearch();
inputRef.current?.focus();
}, [setSearchState, clearSearch]);
const clearText = useCallback(
(pathname?: string) => {
setShowClearIcon(false);
setText('');
setSearchState((prev) => ({
...prev,
query: '',
debouncedQuery: '',
isTyping: false,
}));
clearSearch(pathname);
inputRef.current?.focus();
},
[setSearchState, clearSearch],
);
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
const { value } = e.target as HTMLInputElement;
if (e.key === 'Backspace' && value === '') {
clearText();
}
};
const handleKeyUp = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
const { value } = e.target as HTMLInputElement;
if (e.key === 'Backspace' && value === '') {
clearText(location.pathname);
}
},
[clearText, location.pathname],
);
const sendRequest = useCallback(
(value: string) => {
@ -85,8 +95,6 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: React.Ref<HTMLDivEleme
debouncedSetDebouncedQuery(value);
if (value.length > 0 && location.pathname !== '/search') {
navigate('/search', { replace: true });
} else if (value.length === 0 && location.pathname === '/search') {
navigate('/c/new', { replace: true });
}
};
@ -132,7 +140,7 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: React.Ref<HTMLDivEleme
showClearIcon ? 'opacity-100' : 'opacity-0',
isSmallScreen === true ? 'right-[16px]' : '',
)}
onClick={clearText}
onClick={() => clearText(location.pathname)}
tabIndex={showClearIcon ? 0 : -1}
disabled={!showClearIcon}
>

View file

@ -10,9 +10,7 @@
"com_agents_create_error": "حدث خطأ أثناء إنشاء الوكيل الخاص بك",
"com_agents_description_placeholder": "اختياري: اشرح عميلك هنا",
"com_agents_enable_file_search": "تمكين البحث عن الملفات",
"com_agents_file_context": "سياق الملف (قارئ الحروف البصري)",
"com_agents_file_context_disabled": "يحب أولاً إنشاء الوكيل قبل رفع الملف لمحلل سياق الملف",
"com_agents_file_context_info": "الملفات المرفوعة كـ \"سياق\" تتم معالجتها باستخدام قارئ الحروف البصري (OCR) لاستخراج النص، والذي يُضاف بعد ذلك إلى التعليمات الموجِهة للوكيل. مثالية للوثائق والصور التي تحتوي على نص أو ملفات PDF حيث تحتاج إلى المحتوى النصي الكامل للملف.",
"com_agents_file_search_disabled": "يجب إنشاء الوكيل قبل تحميل الملفات للبحث في الملفات.",
"com_agents_file_search_info": "عند التمكين، سيتم إعلام الوكيل بأسماء الملفات المدرجة أدناه بالضبط، مما يتيح له استرجاع السياق ذي الصلة من هذه الملفات.",
"com_agents_instructions_placeholder": "التعليمات النظامية التي يستخدمها الوكيل",

View file

@ -10,9 +10,7 @@
"com_agents_create_error": "S'ha produït un error en crear el teu agent.",
"com_agents_description_placeholder": "Opcional: Descriu el teu Agent aquí",
"com_agents_enable_file_search": "Habilita la Cerca de Fitxers",
"com_agents_file_context": "Context de Fitxer (OCR)",
"com_agents_file_context_disabled": "Cal crear l'agent abans de pujar fitxers per al Context de Fitxer.",
"com_agents_file_context_info": "Els fitxers pujats com a \"Context\" es processen amb OCR per extreure'n el text, que s'afegeix a les instruccions de l'Agent. Ideal per a documents, imatges amb text o PDFs on cal el contingut complet del fitxer.",
"com_agents_file_search_disabled": "Cal crear l'agent abans de pujar fitxers per a la Cerca de Fitxers.",
"com_agents_file_search_info": "Quan està habilitat, l'agent serà informat dels noms exactes dels fitxers llistats a continuació, i podrà recuperar-ne el context rellevant.",
"com_agents_instructions_placeholder": "Les instruccions de sistema que utilitza l'agent",

View file

@ -10,9 +10,7 @@
"com_agents_create_error": "Der opstod en fejl ved oprettelsen af din agent.",
"com_agents_description_placeholder": "Valgfrit: Beskriv din agent her",
"com_agents_enable_file_search": "Aktivér filsøgning",
"com_agents_file_context": "Filkontekst (OCR)",
"com_agents_file_context_disabled": "Agenten skal oprettes, før der uploades filer til File Context.",
"com_agents_file_context_info": "Filer, der uploades som \"Context\", behandles ved hjælp af OCR for at udtrække tekst, som derefter føjes til agentens instruktioner. Ideel til dokumenter, billeder med tekst eller PDF'er, hvor du har brug for det fulde tekstindhold i en fil.",
"com_agents_file_search_disabled": "Agent skal oprettes inden uploading af filer til filsøgning.",
"com_agents_file_search_info": "Når den er aktiveret, får agenten besked om de nøjagtige filnavne, der er anført nedenfor, så den kan hente relevant kontekst fra disse filer.",
"com_agents_instructions_placeholder": "De systeminstruktioner, som agenten bruger",

View file

@ -59,9 +59,7 @@
"com_agents_error_timeout_suggestion": "Bitte überprüfe deine Internetverbindung und versuche es erneut.",
"com_agents_error_timeout_title": "Verbindungs-Timeout",
"com_agents_error_title": "Es ist ein Fehler aufgetreten",
"com_agents_file_context": "Datei-Kontext (OCR)",
"com_agents_file_context_disabled": "Der Agent muss vor dem Hochladen von Dateien für den Datei-Kontext erstellt werden.",
"com_agents_file_context_info": "Als „Kontext“ hochgeladene Dateien werden mit OCR verarbeitet, um Text zu extrahieren, der dann den Anweisungen des Agenten hinzugefügt wird. Ideal für Dokumente, Bilder mit Text oder PDFs, wenn Sie den vollständigen Textinhalt einer Datei benötigen",
"com_agents_file_search_disabled": "Der Agent muss erstellt werden, bevor Dateien für die Dateisuche hochgeladen werden können.",
"com_agents_file_search_info": "Wenn aktiviert, wird der Agent über die unten aufgelisteten exakten Dateinamen informiert und kann dadurch relevante Informationen aus diesen Dateien abrufen",
"com_agents_grid_announcement": "Zeige {{count}} Agenten in der Kategorie {{category}}",

View file

@ -9,7 +9,6 @@
"com_agents_all_category": "All",
"com_agents_all_description": "Browse all shared agents across all categories",
"com_agents_by_librechat": "by LibreChat",
"com_agents_chat_with": "Chat with {{name}}",
"com_agents_category_aftersales": "After Sales",
"com_agents_category_aftersales_description": "Agents specialized in post-sale support, maintenance, and customer service",
"com_agents_category_empty": "No agents found in the {{category}} category",
@ -27,6 +26,7 @@
"com_agents_category_sales_description": "Agents focused on sales processes, customer relations",
"com_agents_category_tab_label": "{{category}} category, {{position}} of {{total}}",
"com_agents_category_tabs_label": "Agent Categories",
"com_agents_chat_with": "Chat with {{name}}",
"com_agents_clear_search": "Clear search",
"com_agents_code_interpreter": "When enabled, allows your agent to leverage the LibreChat Code Interpreter API to run generated code, including file processing, securely. Requires a valid API key.",
"com_agents_code_interpreter_title": "Code Interpreter API",
@ -60,9 +60,9 @@
"com_agents_error_timeout_suggestion": "Please check your internet connection and try again.",
"com_agents_error_timeout_title": "Connection Timeout",
"com_agents_error_title": "Something went wrong",
"com_agents_file_context_label": "File Context",
"com_agents_file_context_disabled": "Agent must be created before uploading files for File Context.",
"com_agents_file_context_description": "Files uploaded as \"Context\" are parsed as text to supplement the Agent's instructions. If OCR is available, or if configured for the uploaded filetype, the process is used to extract text. Ideal for documents, images with text, or PDFs where you need the full text content of a file",
"com_agents_file_context_disabled": "Agent must be created before uploading files for File Context.",
"com_agents_file_context_label": "File Context",
"com_agents_file_search_disabled": "Agent must be created before uploading files for File Search.",
"com_agents_file_search_info": "When enabled, the agent will be informed of the exact filenames listed below, allowing it to retrieve relevant context from these files.",
"com_agents_grid_announcement": "Showing {{count}} agents in {{category}} category",

View file

@ -10,7 +10,6 @@
"com_agents_create_error": "Hubo un error al crear su agente.",
"com_agents_description_placeholder": "Opcional: Describa su Agente aquí",
"com_agents_enable_file_search": "Habilitar búsqueda de archivos",
"com_agents_file_context": "Archivo de contexto (OCR)",
"com_agents_file_context_disabled": "Es necesario crear el Agente antes de subir archivos.",
"com_agents_file_search_disabled": "Es necesario crear el Agente antes de subir archivos para la Búsqueda de Archivos.",
"com_agents_file_search_info": "Cuando está habilitado, se informará al agente sobre los nombres exactos de los archivos listados a continuación, permitiéndole recuperar el contexto relevante de estos archivos.",

View file

@ -10,9 +10,7 @@
"com_agents_create_error": "Agendi loomisel tekkis viga.",
"com_agents_description_placeholder": "Valikuline: Kirjelda oma agenti siin",
"com_agents_enable_file_search": "Luba failiotsing",
"com_agents_file_context": "Faili kontekst (OCR)",
"com_agents_file_context_disabled": "Agent tuleb luua enne failide üleslaadimist failikontekstiks.",
"com_agents_file_context_info": "Failid, mis on laetud \"konteksti\", töödeldakse OCR-iga tekstiks ja lisatakse seejärel agendi juhistesse. See on eriti kasulik dokumentide, tekstiga piltide või PDF-ide puhul, kui vaja läheb kogu faili tekstilist sisu.",
"com_agents_file_search_disabled": "Agent tuleb luua enne failide üleslaadimist failiotsinguks.",
"com_agents_file_search_info": "Kui see on lubatud, teavitatakse agenti täpselt allpool loetletud failinimedest, mis võimaldab tal nendest failidest asjakohast konteksti hankida.",
"com_agents_instructions_placeholder": "Süsteemijuhised, mida agent kasutab",

View file

@ -10,9 +10,7 @@
"com_agents_create_error": "در ایجاد کارگزار شما خطایی روی داد.",
"com_agents_description_placeholder": "اختیاری: کارگزار خود را در اینجا شرح دهید",
"com_agents_enable_file_search": "جستجوی فایل را فعال کنید",
"com_agents_file_context": "زمینه فایل (OCR)",
"com_agents_file_context_disabled": "کارگزار باید قبل از آپلود فایل ها برای File Context ایجاد شود.",
"com_agents_file_context_info": "فایل‌های آپلود شده به‌عنوان «Context» با استفاده از OCR برای استخراج متن پردازش می‌شوند، که سپس به دستورالعمل‌های کارگزار اضافه می‌شود. ایده آل برای اسناد، تصاویر با متن یا PDF که در آن به محتوای متن کامل یک فایل نیاز دارید",
"com_agents_file_search_disabled": "کارگزار باید قبل از آپلود فایل ها برای جستجوی فایل ایجاد شود.",
"com_agents_file_search_info": "وقتی فعال باشد، کارگزار از نام‌های دقیق فایل‌های فهرست‌شده در زیر مطلع می‌شود و به او اجازه می‌دهد متن مربوطه را از این فایل‌ها بازیابی کند.",
"com_agents_instructions_placeholder": "دستورالعمل های سیستمی که کارگزار استفاده می کند",

View file

@ -4,9 +4,7 @@
"com_agents_create_error": "Agentin luonnissa tapahtui virhe.",
"com_agents_description_placeholder": "Valinnainen: Lisää tähän agentin kuvaus",
"com_agents_enable_file_search": "Käytä Tiedostohakua",
"com_agents_file_context": "Tiedostokonteksti (OCR)",
"com_agents_file_context_disabled": "Agentti täytyy luoda ennen tiedostojen lataamista Tiedostokontekstiin",
"com_agents_file_context_info": "\"Kontekstiksi\" ladatuista tiedostoista luetaan sisältö tekstintunnistuksen (OCR) avulla agentin ohjeisiin lisättäväksi. Soveltuu erityisesti asiakirjojen, tekstiä sisältävien kuvien tai PDF-tiedostojen käsittelyyn, kun haluat hyödyntää tiedoston koko tekstisisällön.",
"com_agents_file_search_disabled": "Agentti täytyy luoda ennen tiedostojen lataamista Tiedostohakuun",
"com_agents_file_search_info": "Asetuksen ollessa päällä agentti saa tiedoksi alla luetellut tiedostonimet, jolloin se voi hakea vastausten pohjaksi asiayhteyksiä tiedostojen sisällöistä.",
"com_agents_instructions_placeholder": "Agentin käyttämät järjestelmäohjeet",

View file

@ -10,9 +10,7 @@
"com_agents_create_error": "Une erreur s'est produite lors de la création de votre agent.",
"com_agents_description_placeholder": "Décrivez votre Agent ici (facultatif)",
"com_agents_enable_file_search": "Activer la recherche de fichiers",
"com_agents_file_context": "Contexte du fichier (OCR)",
"com_agents_file_context_disabled": "L'agent doit être créé avant de charger des fichiers pour le contexte de fichiers.",
"com_agents_file_context_info": "Les fichiers téléchargés en tant que \"Contexte\" sont traités à l'aide de l'OCR pour en extraire le texte, qui est ensuite ajouté aux instructions de l'agent. Idéal pour les documents, les images contenant du texte ou les PDF pour lesquels vous avez besoin du contenu textuel complet d'un fichier.",
"com_agents_file_search_disabled": "L'agent doit être créé avant de pouvoir télécharger des fichiers pour la Recherche de Fichiers.",
"com_agents_file_search_info": "Lorsque cette option est activée, l'agent sera informé des noms exacts des fichiers listés ci-dessous, lui permettant d'extraire le contexte pertinent de ces fichiers.",
"com_agents_instructions_placeholder": "Les instructions système que l'agent utilise",

View file

@ -57,9 +57,7 @@
"com_agents_error_timeout_suggestion": "אנא בדוק את חיבור האינטרנט שלך ונסה שוב",
"com_agents_error_timeout_title": "זמן התפוגה של החיבור",
"com_agents_error_title": "משהו השתבש",
"com_agents_file_context": "קבצי הקשר (OCR)",
"com_agents_file_context_disabled": "יש ליצור סוכן לפני שמעלים קבצים עבור הקשר קבצים",
"com_agents_file_context_info": "קבצים שהועלו כ\"הקשר\" מעובדים באמצעות OCR (זיהוי אופטי של תווים) כדי להפיק טקסט אשר לאחר מכן מתווסף להוראות הסוכן. אידיאלי עבור מסמכים, תמונות עם טקסט או קובצי PDF בהם אתה צריך את התוכן הטקסטואלי המלא של הקובץ.",
"com_agents_file_search_disabled": "יש ליצור את הסוכן לפני העלאת קבצים לחיפוש",
"com_agents_file_search_info": "כאשר הסוכן מופעל הוא יקבל מידע על שמות הקבצים המפורטים להלן, כדי שהוא יוכל לאחזר את הקשר רלוונטי.",
"com_agents_grid_announcement": "מציג {{count}} סוכנים מהקטגוריה {{category}}",

View file

@ -10,9 +10,7 @@
"com_agents_create_error": "Hiba történt az ügynök létrehozása során.",
"com_agents_description_placeholder": "Opcionális: Itt írja le az ügynökét",
"com_agents_enable_file_search": "Fájlkeresés engedélyezése",
"com_agents_file_context": "Fájlkontextus (OCR)",
"com_agents_file_context_disabled": "Az ügynököt először létre kell hozni, mielőtt fájlokat tölthet fel a fájlkontextushoz.",
"com_agents_file_context_info": "A „Kontextusként” feltöltött fájlokat OCR-rel dolgozzuk fel a szöveg kinyeréséhez, amelyet aztán az ügynök utasításaihoz adunk. Ideális dokumentumokhoz, szöveges képekhez vagy PDF-ekhez, ahol a teljes szövegtartalomra szükség van.",
"com_agents_file_search_disabled": "Az ügynököt először létre kell hozni, mielőtt fájlokat tölthet fel a fájlkereséshez.",
"com_agents_file_search_info": "Ha engedélyezve van, az ügynök értesül az alább felsorolt pontos fájlnevekről, lehetővé téve számára, hogy releváns kontextust szerezzen ezekből a fájlokból.",
"com_agents_instructions_placeholder": "Az ügynök által használt rendszerutasítások",

View file

@ -10,7 +10,6 @@
"com_agents_create_error": "Ձեր գործակալը ստեղծելիս սխալ է տեղի ունեցել։",
"com_agents_description_placeholder": "Կամընտրական: Այստեղ նկարագրեք ձեր գործակալին",
"com_agents_enable_file_search": "Միացնել ֆայլերի որոնումը",
"com_agents_file_context": "Ֆայլի ճանաչում (OCR)",
"com_agents_file_context_disabled": "Գործակալը պետք է ստեղծվի ֆայլերը վերբեռնելուց առաջ ֆայլերի ճանաչման համար:",
"com_agents_mcp_icon_size": "Նվազագույն չափը՝ 128 x 128 px",
"com_agents_mcp_name_placeholder": "Անհատական գործիք",

View file

@ -8,9 +8,7 @@
"com_agents_create_error": "Si è verificato un errore durante la creazione del tuo agente.",
"com_agents_description_placeholder": "Opzionale: Descrivi qui il tuo Agente",
"com_agents_enable_file_search": "Abilita Ricerca File",
"com_agents_file_context": "Contesto del File (OCR)",
"com_agents_file_context_disabled": "L'agente deve essere creato prima di caricare i file per il Contesto del File.",
"com_agents_file_context_info": "I file caricati come \"Contesto\" vengono elaborati tramite OCR per estrarre il testo, che viene poi aggiunto alle istruzioni dell'Agente. Ideale per documenti, immagini con testo o PDF in cui è necessario il contenuto di testo completo di un file",
"com_agents_file_search_disabled": "L'Agente deve essere creato prima di caricare file per la Ricerca File.",
"com_agents_file_search_info": "Quando abilitato, l'agente verrà informato dei nomi esatti dei file elencati di seguito, permettendogli di recuperare il contesto pertinente da questi file.",
"com_agents_instructions_placeholder": "Le istruzioni di sistema utilizzate dall'agente",

View file

@ -10,9 +10,7 @@
"com_agents_create_error": "エージェントの作成中にエラーが発生しました。",
"com_agents_description_placeholder": "オプション: エージェントの説明を入力してください",
"com_agents_enable_file_search": "ファイル検索を有効にする",
"com_agents_file_context": "ファイルコンテキストOCR",
"com_agents_file_context_disabled": "ファイル検索用のファイルをアップロードする前に、エージェントを作成する必要があります。",
"com_agents_file_context_info": "「コンテキスト」としてアップロードされたファイルは、OCR処理によってテキストが抽出され、エージェントの指示に追加されます。ファイルの全文コンテンツが必要な文書、テキストを含む画像、PDFに最適です。",
"com_agents_file_search_disabled": "ファイル検索用のファイルをアップロードする前に、エージェントを作成する必要があります。",
"com_agents_file_search_info": "有効にすると、エージェントは以下に表示されているファイル名を正確に認識し、それらのファイルから関連する情報を取得することができます。",
"com_agents_instructions_placeholder": "エージェントが使用するシステムの指示",

View file

@ -9,9 +9,7 @@
"com_agents_create_error": "თქვენი აგენტის შექმნისასდაფიქსირდა შეცდომა",
"com_agents_description_placeholder": "არასავალდებულო: აღწერეთ თქვენი აგენტი",
"com_agents_enable_file_search": "ფაილების ძიების ჩართვა",
"com_agents_file_context": "ფაილის კონტექსტი (OCR)",
"com_agents_file_context_disabled": "ფაილის კონტექსტისთვის, ატვირთვამდე უნდა შეიქმნას აგენტი.",
"com_agents_file_context_info": "„კონტექსტის“ სახით ატვირთული ფაილები დამუშავდება OCR-ის გამოყენებით ტექსტის ამოსაღებად, რომელიც შემდეგ დაემატება აგენტის ინსტრუქციებს. იდეალურია დოკუმენტებისთვის, ტექსტიანი სურათებისთვის ან PDF ფაილებისთვის, სადაც გჭირდებათ ფაილის შინაარსი.",
"com_agents_instructions_placeholder": "სისტემის ინსტრუქციები, რომლებსაც გამოიყენებს აგენტი",
"com_agents_mcp_description_placeholder": "რამდენიმე სიტყვით ახსენით დავალება",
"com_agents_mcp_icon_size": "მინიმალური ზომა 128 x 128 პიქსელი",

View file

@ -10,9 +10,7 @@
"com_agents_create_error": "에이전트 생성 중 오류가 발생했습니다",
"com_agents_description_placeholder": "선택 사항: 여기에 에이전트를 설명하세요",
"com_agents_enable_file_search": "파일 검색 활성화",
"com_agents_file_context": "파일 컨텍스트 (OCR)",
"com_agents_file_context_disabled": "파일 컨텍스트를 위해 파일을 업로드하기 전에, 에이전트가 먼저 생성되어야 합니다.",
"com_agents_file_context_info": "컨텍스트(Context)로 업로드 된 파일은 OCR을 사용하여 텍스트를 추출하고, 이 텍스트는 에이전트의 지시사항에 추가됩니다. 문서, 텍스트가 포함된 이미지, 또는 파일의 전체 내용이 필요한 PDF에 이상적입니다.",
"com_agents_file_search_disabled": "파일 검색을 위해 파일을 업로드하기 전에 에이전트를 먼저 생성해야 합니다",
"com_agents_file_search_info": "활성화하면 에이전트가 아래 나열된 파일명들을 정확히 인식하여 해당 파일들에서 관련 내용을 검색할 수 있습니다.",
"com_agents_instructions_placeholder": "에이전트가 사용하는 시스템 지침",

View file

@ -59,9 +59,7 @@
"com_agents_error_timeout_suggestion": "Lūdzu, pārbaudiet interneta savienojumu un mēģiniet vēlreiz.",
"com_agents_error_timeout_title": "Savienojumu neizdevās izveidot",
"com_agents_error_title": "Kaut kas nogāja greizi",
"com_agents_file_context": "Failu konteksts (OCR)",
"com_agents_file_context_disabled": "Pirms failu augšupielādes failu kontekstam ir jāizveido aģents.",
"com_agents_file_context_info": "Faili, kas augšupielādēti kā “Konteksts”, tiek apstrādāti, izmantojot OCR, lai iegūtu tekstu, kas pēc tam tiek pievienots aģenta norādījumiem. Ideāli piemērots dokumentiem, attēliem ar tekstu vai PDF failiem, kuriem nepieciešams pilns faila teksta saturs.",
"com_agents_file_search_disabled": "Lai varētu iespējot faila augšupielādi informācijas iegūšanai no tā ir jāizveido aģents.",
"com_agents_file_search_info": "Kad šī opcija ir iespējota, aģents tiks informēts par precīziem tālāk norādītajiem failu nosaukumiem, ļaujot tam izgūt atbilstošu kontekstu no šiem failiem.",
"com_agents_grid_announcement": "Rādu {{count}} aģentus {{category}} kategorijā",

View file

@ -59,9 +59,7 @@
"com_agents_error_timeout_suggestion": "Sjekk internettforbindelsen din og prøv igjen.",
"com_agents_error_timeout_title": "Tidsavbrudd for tilkobling",
"com_agents_error_title": "Noe gikk galt",
"com_agents_file_context": "Filkontekst (OCR)",
"com_agents_file_context_disabled": "Agenten må være opprettet før du kan laste opp filer for filkontekst.",
"com_agents_file_context_info": "Filer lastet opp som \"kontekst\" behandles med OCR for å trekke ut tekst, som deretter legges til i agentens instruksjoner. Dette er ideelt for dokumenter, bilder med tekst eller PDF-er der du trenger hele tekstinnholdet.",
"com_agents_file_search_disabled": "Agenten må være opprettet før du kan laste opp filer for filsøk.",
"com_agents_file_search_info": "Når dette er aktivert, vil agenten bruke de eksakte filnavnene listet nedenfor for å hente relevant kontekst fra disse filene.",
"com_agents_grid_announcement": "Viser {{count}} agenter i kategorien {{category}}.",

View file

@ -21,9 +21,7 @@
"com_agents_error_bad_request_message": "De aanvraag kon niet worden verwerkt.",
"com_agents_error_bad_request_suggestion": "Controleer uw invoer en probeer het opnieuw.",
"com_agents_error_invalid_request": "Ongeldige aanvraag",
"com_agents_file_context": "File Context (OCR)",
"com_agents_file_context_disabled": "Agent moet worden aangemaakt voordat bestanden worden geüpload voor File Context",
"com_agents_file_context_info": "Bestanden die als \"Context\" worden geüpload, worden verwerkt met OCR voor tekstherkenning. De tekst wordt daarna toegevoegd aan de instructies van de Agent. Ideaal voor documenten, afbeeldingen met tekst of PDF's waarvan je de volledige tekstinhoud nodig hebt.\"",
"com_agents_file_search_disabled": "Maak eerst een Agent aan voordat je bestanden uploadt voor File Search.",
"com_agents_file_search_info": "Als deze functie is ingeschakeld, krijgt de agent informatie over de exacte bestandsnamen die hieronder staan vermeld, zodat deze relevante context uit deze bestanden kan ophalen.",
"com_agents_instructions_placeholder": "De systeeminstructies die de agent gebruikt",

View file

@ -9,9 +9,7 @@
"com_agents_create_error": "Wystąpił błąd podczas tworzenia agenta.",
"com_agents_description_placeholder": "Opcjonalnie: Opisz swojego agenta tutaj",
"com_agents_enable_file_search": "Włącz wyszukiwanie plików",
"com_agents_file_context": "Kontest Pliku (OCR)",
"com_agents_file_context_disabled": "Agent musi zostać utworzony przed przesłaniem plików dla Kontekstu Plików",
"com_agents_file_context_info": "Pliki przesłane jako \"Kontekst\" są przetworzone przez OCR by wydobyć tekst, który potem jest dodany do instrukcji Agenta. Jest to idealne dla dokumentów, obrazów z tekstem oraz plików PDF, gdzie potrzebujesz całego tekstu z pliku.",
"com_agents_file_search_disabled": "Agent musi zostać utworzony przed przesłaniem plików do wyszukiwania.",
"com_agents_file_search_info": "Po włączeniu agent zostanie poinformowany o dokładnych nazwach plików wymienionych poniżej, co pozwoli mu na pobranie odpowiedniego kontekstu z tych plików.",
"com_agents_instructions_placeholder": "Instrukcje systemowe używane przez agenta",

View file

@ -37,9 +37,7 @@
"com_agents_error_server_title": "Erro no servidor",
"com_agents_error_timeout_title": "A conexão expirou",
"com_agents_error_title": "Algo deu errado",
"com_agents_file_context": "Contexto de arquivo (OCR)",
"com_agents_file_context_disabled": "O agente deve ser criado antes de carregar arquivos para o Contexto de Arquivo.",
"com_agents_file_context_info": "Os arquivos carregados como \"Contexto\" são processados usando OCR para extrair texto, que é então adicionado às instruções do Agente. Ideal para documentos, imagens com texto ou PDFs onde você precisa do conteúdo de texto completo de um arquivo",
"com_agents_file_search_disabled": "O agente deve ser criado antes de carregar arquivos para Pesquisa de Arquivos.",
"com_agents_file_search_info": "Quando ativado, o agente será informado dos nomes exatos dos arquivos listados abaixo, permitindo que ele recupere o contexto relevante desses arquivos.",
"com_agents_instructions_placeholder": "As instruções do sistema que o agente usa",

View file

@ -11,7 +11,6 @@
"com_agents_create_error": "Houve um erro ao criar seu agente.",
"com_agents_description_placeholder": "Opcional: Descreva seu Agente aqui",
"com_agents_enable_file_search": "Permitir Pesquisa de Ficheiros.",
"com_agents_file_context": "Contexto de Ficheiro (OCR)",
"com_agents_file_context_disabled": "Um agente deve ser criado antes de tentar fazer upload para contexto.",
"com_agents_file_search_disabled": "O Agente deve ser criado antes carregar ficheiros para Pesquisar.",
"com_agents_file_search_info": "Quando ativo, os agentes serão informados dos nomes de ficheiros listados abaixo, permitindo aos mesmos a extração de contexto relevante.",

View file

@ -9,9 +9,7 @@
"com_agents_create_error": "Произошла ошибка при создании вашего агента",
"com_agents_description_placeholder": "Необязательно: описание вашего агента",
"com_agents_enable_file_search": "Включить поиск файлов",
"com_agents_file_context": "Контекст файла (OCR)",
"com_agents_file_context_disabled": "Агент должен быть создан перед загрузкой файлов для контекста файла",
"com_agents_file_context_info": "Файлы, загруженные как «Контекст», обрабатываются с использованием OCR для извлечения текста, который затем добавляется в инструкции агента. Идеально подходит для документов, изображений с текстом или PDF-файлов, где требуется полный текстовый контент.",
"com_agents_file_search_disabled": "Для загрузки файлов в Поиск необходимо сначала создать агента",
"com_agents_file_search_info": "При включении агент получит доступ к точным названиям файлов, перечисленным ниже, что позволит ему извлекать из них релевантный контекст.",
"com_agents_instructions_placeholder": "Системные инструкции, используемые агентом",

View file

@ -53,9 +53,7 @@
"com_agents_error_suggestion_generic": "Försök att uppdatera sidan eller försök igen senare.",
"com_agents_error_timeout_suggestion": "Kontrollera din internetanslutning och försök igen.",
"com_agents_error_title": "Något gick fel",
"com_agents_file_context": "Filkontext (OCR)",
"com_agents_file_context_disabled": "Agent måste skapas innan filer laddas upp för filkontext.",
"com_agents_file_context_info": "Filer som laddas upp som kontext bearbetas med OCR för att extrahera text, som sedan läggs till i agentens instruktioner. Lämpligt för dokument, bilder med text eller PDF-filer där du behöver hela textinnehållet i en fil",
"com_agents_file_search_disabled": "Agenten måste skapas innan du laddar upp filer.",
"com_agents_file_search_info": "När detta är aktiverat kommer agenten se och hämta relevant information från filnamnen nedan. ",
"com_agents_grid_announcement": "Visar {{count}} agenter i {{category}} kategorin",

View file

@ -10,9 +10,7 @@
"com_agents_create_error": "เกิดข้อผิดพลาดในการสร้างเอเจนต์ของคุณ",
"com_agents_description_placeholder": "ตัวเลือกเพิ่มเติม: อธิบายเอเจนต์ของคุณที่นี่",
"com_agents_enable_file_search": "เปิดใช้งานการค้นหาไฟล์",
"com_agents_file_context": "ข้อความจากไฟล์ (OCR)",
"com_agents_file_context_disabled": "ต้องสร้าง เอเจนท์ ก่อนอัปโหลดไฟล์",
"com_agents_file_context_info": "ไฟล์ที่อัปโหลดเป็น “Context” (บริบท) จะถูกประมวลผลด้วยเทคโนโลยี OCR เพื่อดึงข้อความออกมา แล้วนำไปเพิ่มในคำสั่งของเอเจนต์ เหมาะอย่างยิ่งสำหรับเอกสาร ภาพที่มีข้อความ หรือไฟล์ PDF ที่คุณต้องการข้อความทั้งหมดของไฟล์",
"com_agents_file_search_disabled": "ต้องสร้างเอเจนต์ก่อนที่จะอัปโหลดไฟล์สำหรับใช้ในการค้นหาไฟล์",
"com_agents_file_search_info": "เมื่อเปิดใช้งาน เอเจนต์จะได้รับข้อมูลเกี่ยวกับชื่อไฟล์ที่ระบุไว้ด้านล่างอย่างถูกต้อง ทำให้สามารถดึงข้อมูลที่เกี่ยวข้องจากไฟล์เหล่านี้ได้",
"com_agents_instructions_placeholder": "คำสั่งของระบบที่เอเจนต์ใช้งาน",

View file

@ -59,9 +59,7 @@
"com_agents_error_timeout_suggestion": "Перевірте підключення до інтернету та спробуйте ще раз.",
"com_agents_error_timeout_title": "Час очікування з'єднання вийшов",
"com_agents_error_title": "Щось пішло не так",
"com_agents_file_context": "Контекст файлу (OCR)",
"com_agents_file_context_disabled": "Спочатку потрібно створити агента перед завантаженням файлів для контексту файлу",
"com_agents_file_context_info": "Файли, завантажені як «Контекст», обробляються за допомогою OCR для вилучення тексту, який потім додається до інструкцій агента. Ідеально підходить для документів, зображень з текстом або PDF-файлів, де потрібен повний текстовий вміст.",
"com_agents_file_search_disabled": "Для завантаження файлів у Пошук спочатку потрібно створити агента",
"com_agents_file_search_info": "При ввімкненні агент отримає доступ до точних назв файлів, перелічених нижче, що дозволить йому отримувати з них відповідний контекст.",
"com_agents_grid_announcement": "Показано {{count}} агентів у категорії {{category}}",

View file

@ -8,7 +8,6 @@
"com_agents_description_placeholder": "Tùy chọn: Mô tả Agent của bạn ở đây",
"com_agents_enable_file_search": "Bật Tìm kiếm Tệp",
"com_agents_file_context_disabled": "Phải tạo Agent trước khi tải tệp lên cho File Context.",
"com_agents_file_context_info": "Các tệp được tải lên dưới dạng \"Context\" được xử lý bằng OCR để trích xuất văn bản, sau đó được thêm vào hướng dẫn của Agent. Lý tưởng cho các tài liệu, hình ảnh có văn bản hoặc PDF khi bạn cần nội dung văn bản đầy đủ của tệp",
"com_agents_file_search_disabled": "Phải tạo Agent trước khi tải tệp lên cho Tìm kiếm Tệp.",
"com_agents_file_search_info": "Khi được bật, Agent sẽ được thông báo về tên tệp chính xác được liệt kê bên dưới, cho phép agent truy xuất ngữ cảnh có liên quan từ các tệp này.",
"com_agents_instructions_placeholder": "\nCác hướng dẫn hệ thống mà agent sử dụng",

View file

@ -59,9 +59,7 @@
"com_agents_error_timeout_suggestion": "请检查您的互联网连接并重试。",
"com_agents_error_timeout_title": "连接超时",
"com_agents_error_title": "出了点问题",
"com_agents_file_context": "文件上下文OCR",
"com_agents_file_context_disabled": "必须先创建智能体,才能上传文件用于文件上下文。",
"com_agents_file_context_info": "作为 ”上下文“ 上传的文件会通过 OCR 处理以提取文本,然后将其添加到智能体的指令中。这非常适合文档、带有文本的图片或 PDF 文件等需要文件完整文本内容的场景。",
"com_agents_file_search_disabled": "必须先创建智能体,才能上传文件用于文件搜索。",
"com_agents_file_search_info": "启用后,系统会告知代理以下列出的具体文件名,使其能够从这些文件中检索相关内容。",
"com_agents_grid_announcement": "显示 {{category}} 类别中的 {{count}} 个智能体",

View file

@ -35,9 +35,7 @@
"com_agents_enable_file_search": "啟用檔案搜尋",
"com_agents_error_bad_request_message": "無法處理該請求",
"com_agents_error_bad_request_suggestion": "請檢查您的輸入並再試一次。",
"com_agents_file_context": "文件內容 (OCR)",
"com_agents_file_context_disabled": "在為檔案上下文上傳檔案之前,必須先建立 Agent。",
"com_agents_file_context_info": "以「Context」標記上傳的檔案會使用 OCR 擷取文字,擷取後的文字會被加入到 Agent 的指示中。適合用於文件、含文字的圖片或需要取得檔案完整文字內容的 PDF。",
"com_agents_file_search_disabled": "必須先建立代理才能上傳檔案進行檔案搜尋。",
"com_agents_file_search_info": "啟用後,代理將會被告知以下列出的確切檔案名稱,使其能夠從這些檔案中擷取相關內容。",
"com_agents_instructions_placeholder": "代理程式使用的系統指令",

View file

@ -1,4 +1,5 @@
import type { MCPOptions } from 'librechat-data-provider';
import type { AuthorizationServerMetadata } from '@modelcontextprotocol/sdk/shared/auth.js';
import { MCPOAuthHandler } from '~/mcp/oauth';
jest.mock('@librechat/data-schemas', () => ({
@ -12,11 +13,19 @@ jest.mock('@librechat/data-schemas', () => ({
jest.mock('@modelcontextprotocol/sdk/client/auth.js', () => ({
startAuthorization: jest.fn(),
discoverAuthorizationServerMetadata: jest.fn(),
}));
import { startAuthorization } from '@modelcontextprotocol/sdk/client/auth.js';
import {
startAuthorization,
discoverAuthorizationServerMetadata,
} from '@modelcontextprotocol/sdk/client/auth.js';
const mockStartAuthorization = startAuthorization as jest.MockedFunction<typeof startAuthorization>;
const mockDiscoverAuthorizationServerMetadata =
discoverAuthorizationServerMetadata as jest.MockedFunction<
typeof discoverAuthorizationServerMetadata
>;
describe('MCPOAuthHandler - Configurable OAuth Metadata', () => {
const mockServerName = 'test-server';
@ -188,6 +197,432 @@ describe('MCPOAuthHandler - Configurable OAuth Metadata', () => {
});
});
describe('refreshOAuthTokens', () => {
const mockRefreshToken = 'refresh-token-12345';
const originalFetch = global.fetch;
const mockFetch = jest.fn() as unknown as jest.MockedFunction<typeof fetch>;
beforeEach(() => {
jest.clearAllMocks();
global.fetch = mockFetch;
});
afterEach(() => {
mockFetch.mockClear();
});
afterAll(() => {
global.fetch = originalFetch;
});
describe('with stored metadata', () => {
it('should use client_secret_post when server only supports that method', async () => {
const metadata = {
serverName: 'test-server',
userId: 'user-123',
serverUrl: 'https://auth.example.com',
state: 'state-123',
clientInfo: {
client_id: 'test-client-id',
client_secret: 'test-client-secret',
grant_types: ['authorization_code', 'refresh_token'],
scope: 'read write',
},
};
// Mock OAuth metadata discovery
mockDiscoverAuthorizationServerMetadata.mockResolvedValueOnce({
issuer: 'https://auth.example.com',
authorization_endpoint: 'https://auth.example.com/oauth/authorize',
token_endpoint: 'https://auth.example.com/oauth/token',
token_endpoint_auth_methods_supported: ['client_secret_post'],
response_types_supported: ['code'],
jwks_uri: 'https://auth.example.com/.well-known/jwks.json',
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: ['RS256'],
} as AuthorizationServerMetadata);
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
access_token: 'new-access-token',
refresh_token: 'new-refresh-token',
expires_in: 3600,
}),
} as Response);
const result = await MCPOAuthHandler.refreshOAuthTokens(mockRefreshToken, metadata);
// Verify the call was made without Authorization header
expect(mockFetch).toHaveBeenCalledWith(
'https://auth.example.com/oauth/token',
expect.objectContaining({
method: 'POST',
headers: expect.not.objectContaining({
Authorization: expect.any(String),
}),
}),
);
// Verify the body contains client_id and client_secret
const callArgs = mockFetch.mock.calls[0];
const body = callArgs[1]?.body as URLSearchParams;
expect(body.toString()).toContain('client_id=test-client-id');
expect(body.toString()).toContain('client_secret=test-client-secret');
expect(result).toEqual({
access_token: 'new-access-token',
refresh_token: 'new-refresh-token',
expires_in: 3600,
obtained_at: expect.any(Number),
expires_at: expect.any(Number),
});
});
it('should use client_secret_basic when server only supports that method', async () => {
const metadata = {
serverName: 'test-server',
userId: 'user-123',
serverUrl: 'https://auth.example.com',
state: 'state-123',
clientInfo: {
client_id: 'test-client-id',
client_secret: 'test-client-secret',
grant_types: ['authorization_code', 'refresh_token'],
scope: 'read write',
},
};
// Mock OAuth metadata discovery
mockDiscoverAuthorizationServerMetadata.mockResolvedValueOnce({
issuer: 'https://auth.example.com',
authorization_endpoint: 'https://auth.example.com/oauth/authorize',
token_endpoint: 'https://auth.example.com/oauth/token',
token_endpoint_auth_methods_supported: ['client_secret_basic'],
response_types_supported: ['code'],
jwks_uri: 'https://auth.example.com/.well-known/jwks.json',
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: ['RS256'],
} as AuthorizationServerMetadata);
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
access_token: 'new-access-token',
refresh_token: 'new-refresh-token',
expires_in: 3600,
}),
} as Response);
await MCPOAuthHandler.refreshOAuthTokens(mockRefreshToken, metadata);
const expectedAuth = `Basic ${Buffer.from('test-client-id:test-client-secret').toString('base64')}`;
expect(mockFetch).toHaveBeenCalledWith(
'https://auth.example.com/oauth/token',
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
Authorization: expectedAuth,
}),
body: expect.not.stringContaining('client_id='),
}),
);
});
it('should prefer client_secret_basic when both methods are supported', async () => {
const metadata = {
serverName: 'test-server',
userId: 'user-123',
serverUrl: 'https://auth.example.com',
state: 'state-123',
clientInfo: {
client_id: 'test-client-id',
client_secret: 'test-client-secret',
grant_types: ['authorization_code', 'refresh_token'],
},
};
// Mock OAuth metadata discovery
mockDiscoverAuthorizationServerMetadata.mockResolvedValueOnce({
issuer: 'https://auth.example.com',
authorization_endpoint: 'https://auth.example.com/oauth/authorize',
token_endpoint: 'https://auth.example.com/oauth/token',
token_endpoint_auth_methods_supported: ['client_secret_post', 'client_secret_basic'],
response_types_supported: ['code'],
jwks_uri: 'https://auth.example.com/.well-known/jwks.json',
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: ['RS256'],
} as AuthorizationServerMetadata);
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
access_token: 'new-access-token',
refresh_token: 'new-refresh-token',
expires_in: 3600,
}),
} as Response);
await MCPOAuthHandler.refreshOAuthTokens(mockRefreshToken, metadata);
const expectedAuth = `Basic ${Buffer.from('test-client-id:test-client-secret').toString('base64')}`;
expect(mockFetch).toHaveBeenCalledWith(
'https://auth.example.com/oauth/token',
expect.objectContaining({
headers: expect.objectContaining({
Authorization: expectedAuth,
}),
}),
);
});
it('should default to client_secret_basic when no methods are advertised', async () => {
const metadata = {
serverName: 'test-server',
userId: 'user-123',
serverUrl: 'https://auth.example.com',
state: 'state-123',
clientInfo: {
client_id: 'test-client-id',
client_secret: 'test-client-secret',
grant_types: ['authorization_code', 'refresh_token'],
},
};
// Mock OAuth metadata discovery with no auth methods specified
mockDiscoverAuthorizationServerMetadata.mockResolvedValueOnce({
issuer: 'https://auth.example.com',
authorization_endpoint: 'https://auth.example.com/oauth/authorize',
token_endpoint: 'https://auth.example.com/oauth/token',
// No token_endpoint_auth_methods_supported field
response_types_supported: ['code'],
jwks_uri: 'https://auth.example.com/.well-known/jwks.json',
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: ['RS256'],
} as AuthorizationServerMetadata);
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
access_token: 'new-access-token',
refresh_token: 'new-refresh-token',
expires_in: 3600,
}),
} as Response);
await MCPOAuthHandler.refreshOAuthTokens(mockRefreshToken, metadata);
const expectedAuth = `Basic ${Buffer.from('test-client-id:test-client-secret').toString('base64')}`;
expect(mockFetch).toHaveBeenCalledWith(
'https://auth.example.com/oauth/token',
expect.objectContaining({
headers: expect.objectContaining({
Authorization: expectedAuth,
}),
}),
);
});
it('should include client_id in body for public clients (no secret)', async () => {
const metadata = {
serverName: 'test-server',
userId: 'user-123',
serverUrl: 'https://auth.example.com',
state: 'state-123',
clientInfo: {
client_id: 'test-client-id',
// No client_secret - public client
grant_types: ['authorization_code', 'refresh_token'],
},
};
// Mock OAuth metadata discovery
mockDiscoverAuthorizationServerMetadata.mockResolvedValueOnce({
issuer: 'https://auth.example.com',
authorization_endpoint: 'https://auth.example.com/oauth/authorize',
token_endpoint: 'https://auth.example.com/oauth/token',
token_endpoint_auth_methods_supported: ['none'],
response_types_supported: ['code'],
jwks_uri: 'https://auth.example.com/.well-known/jwks.json',
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: ['RS256'],
} as AuthorizationServerMetadata);
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
access_token: 'new-access-token',
refresh_token: 'new-refresh-token',
expires_in: 3600,
}),
} as Response);
await MCPOAuthHandler.refreshOAuthTokens(mockRefreshToken, metadata);
// Verify the call was made without Authorization header
expect(mockFetch).toHaveBeenCalledWith(
'https://auth.example.com/oauth/token',
expect.objectContaining({
method: 'POST',
headers: expect.not.objectContaining({
Authorization: expect.any(String),
}),
}),
);
// Verify the body contains client_id (public client)
const callArgs = mockFetch.mock.calls[0];
const body = callArgs[1]?.body as URLSearchParams;
expect(body.toString()).toContain('client_id=test-client-id');
});
});
describe('with pre-configured OAuth settings', () => {
it('should use client_secret_post when configured to only support that method', async () => {
const config = {
token_url: 'https://auth.example.com/oauth/token',
client_id: 'test-client-id',
client_secret: 'test-client-secret',
token_endpoint_auth_methods_supported: ['client_secret_post'],
};
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
access_token: 'new-access-token',
refresh_token: 'new-refresh-token',
expires_in: 3600,
}),
} as Response);
await MCPOAuthHandler.refreshOAuthTokens(
mockRefreshToken,
{ serverName: 'test-server' },
config,
);
// Verify the call was made without Authorization header
expect(mockFetch).toHaveBeenCalledWith(
new URL('https://auth.example.com/oauth/token'),
expect.objectContaining({
method: 'POST',
headers: expect.not.objectContaining({
Authorization: expect.any(String),
}),
}),
);
// Verify the body contains client_id and client_secret
const callArgs = mockFetch.mock.calls[0];
const body = callArgs[1]?.body as URLSearchParams;
expect(body.toString()).toContain('client_id=test-client-id');
expect(body.toString()).toContain('client_secret=test-client-secret');
});
it('should use client_secret_basic when configured to support that method', async () => {
const config = {
token_url: 'https://auth.example.com/oauth/token',
client_id: 'test-client-id',
client_secret: 'test-client-secret',
token_endpoint_auth_methods_supported: ['client_secret_basic'],
};
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
access_token: 'new-access-token',
refresh_token: 'new-refresh-token',
expires_in: 3600,
}),
} as Response);
await MCPOAuthHandler.refreshOAuthTokens(
mockRefreshToken,
{ serverName: 'test-server' },
config,
);
const expectedAuth = `Basic ${Buffer.from('test-client-id:test-client-secret').toString('base64')}`;
expect(mockFetch).toHaveBeenCalledWith(
new URL('https://auth.example.com/oauth/token'),
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
Authorization: expectedAuth,
}),
body: expect.not.stringContaining('client_id='),
}),
);
});
it('should default to client_secret_basic when no auth methods configured', async () => {
const config = {
token_url: 'https://auth.example.com/oauth/token',
client_id: 'test-client-id',
client_secret: 'test-client-secret',
// No token_endpoint_auth_methods_supported field
};
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
access_token: 'new-access-token',
refresh_token: 'new-refresh-token',
expires_in: 3600,
}),
} as Response);
await MCPOAuthHandler.refreshOAuthTokens(
mockRefreshToken,
{ serverName: 'test-server' },
config,
);
const expectedAuth = `Basic ${Buffer.from('test-client-id:test-client-secret').toString('base64')}`;
expect(mockFetch).toHaveBeenCalledWith(
new URL('https://auth.example.com/oauth/token'),
expect.objectContaining({
headers: expect.objectContaining({
Authorization: expectedAuth,
}),
}),
);
});
});
it('should throw error when refresh fails', async () => {
const metadata = {
serverName: 'test-server',
userId: 'user-123',
serverUrl: 'https://auth.example.com',
state: 'state-123',
clientInfo: {
client_id: 'test-client-id',
client_secret: 'test-client-secret',
grant_types: ['authorization_code', 'refresh_token'],
},
};
// Mock OAuth metadata discovery
mockDiscoverAuthorizationServerMetadata.mockResolvedValueOnce({
token_endpoint: 'https://auth.example.com/oauth/token',
token_endpoint_auth_methods_supported: ['client_secret_post'],
} as AuthorizationServerMetadata);
mockFetch.mockResolvedValueOnce({
ok: false,
status: 400,
statusText: 'Bad Request',
text: async () =>
'{"error":"invalid_request","error_description":"refresh_token.client_id: Field required"}',
} as Response);
await expect(MCPOAuthHandler.refreshOAuthTokens(mockRefreshToken, metadata)).rejects.toThrow(
'Token refresh failed: 400 Bad Request - {"error":"invalid_request","error_description":"refresh_token.client_id: Field required"}',
);
});
});
describe('revokeOAuthToken', () => {
const mockServerName = 'test-server';
const mockToken = 'test-token-12345';

View file

@ -501,6 +501,7 @@ export class MCPOAuthHandler {
/** Use the stored client information and metadata to determine the token URL */
let tokenUrl: string;
let authMethods: string[] | undefined;
if (config?.token_url) {
tokenUrl = config.token_url;
} else if (!metadata.serverUrl) {
@ -515,6 +516,7 @@ export class MCPOAuthHandler {
throw new Error('No token endpoint found in OAuth metadata');
}
tokenUrl = oauthMetadata.token_endpoint;
authMethods = oauthMetadata.token_endpoint_auth_methods_supported;
}
const body = new URLSearchParams({
@ -532,14 +534,36 @@ export class MCPOAuthHandler {
Accept: 'application/json',
};
/** Use client_secret for authentication if available */
/** Handle authentication based on server's advertised methods */
if (metadata.clientInfo.client_secret) {
const clientAuth = Buffer.from(
`${metadata.clientInfo.client_id}:${metadata.clientInfo.client_secret}`,
).toString('base64');
headers['Authorization'] = `Basic ${clientAuth}`;
/** Default to client_secret_basic if no methods specified (per RFC 8414) */
const tokenAuthMethods = authMethods ?? ['client_secret_basic'];
const usesBasicAuth = tokenAuthMethods.includes('client_secret_basic');
const usesClientSecretPost = tokenAuthMethods.includes('client_secret_post');
if (usesBasicAuth) {
/** Use Basic auth */
logger.debug('[MCPOAuth] Using client_secret_basic authentication method');
const clientAuth = Buffer.from(
`${metadata.clientInfo.client_id}:${metadata.clientInfo.client_secret}`,
).toString('base64');
headers['Authorization'] = `Basic ${clientAuth}`;
} else if (usesClientSecretPost) {
/** Use client_secret_post */
logger.debug('[MCPOAuth] Using client_secret_post authentication method');
body.append('client_id', metadata.clientInfo.client_id);
body.append('client_secret', metadata.clientInfo.client_secret);
} else {
/** No recognized method, default to Basic auth per RFC */
logger.debug('[MCPOAuth] No recognized auth method, defaulting to client_secret_basic');
const clientAuth = Buffer.from(
`${metadata.clientInfo.client_id}:${metadata.clientInfo.client_secret}`,
).toString('base64');
headers['Authorization'] = `Basic ${clientAuth}`;
}
} else {
/** For public clients, client_id must be in the body */
logger.debug('[MCPOAuth] Using public client authentication (no secret)');
body.append('client_id', metadata.clientInfo.client_id);
}
@ -575,9 +599,6 @@ export class MCPOAuthHandler {
logger.debug(`[MCPOAuth] Using pre-configured OAuth settings for token refresh`);
const tokenUrl = new URL(config.token_url);
const clientAuth = config.client_secret
? Buffer.from(`${config.client_id}:${config.client_secret}`).toString('base64')
: null;
const body = new URLSearchParams({
grant_type: 'refresh_token',
@ -593,10 +614,44 @@ export class MCPOAuthHandler {
Accept: 'application/json',
};
if (clientAuth) {
headers['Authorization'] = `Basic ${clientAuth}`;
/** Handle authentication based on configured methods */
if (config.client_secret) {
/** Default to client_secret_basic if no methods specified (per RFC 8414) */
const tokenAuthMethods = config.token_endpoint_auth_methods_supported ?? [
'client_secret_basic',
];
const usesBasicAuth = tokenAuthMethods.includes('client_secret_basic');
const usesClientSecretPost = tokenAuthMethods.includes('client_secret_post');
if (usesBasicAuth) {
/** Use Basic auth */
logger.debug(
'[MCPOAuth] Using client_secret_basic authentication method (pre-configured)',
);
const clientAuth = Buffer.from(`${config.client_id}:${config.client_secret}`).toString(
'base64',
);
headers['Authorization'] = `Basic ${clientAuth}`;
} else if (usesClientSecretPost) {
/** Use client_secret_post */
logger.debug(
'[MCPOAuth] Using client_secret_post authentication method (pre-configured)',
);
body.append('client_id', config.client_id);
body.append('client_secret', config.client_secret);
} else {
/** No recognized method, default to Basic auth per RFC */
logger.debug(
'[MCPOAuth] No recognized auth method, defaulting to client_secret_basic (pre-configured)',
);
const clientAuth = Buffer.from(`${config.client_id}:${config.client_secret}`).toString(
'base64',
);
headers['Authorization'] = `Basic ${clientAuth}`;
}
} else {
// Use client_id in body for public clients
/** For public clients, client_id must be in the body */
logger.debug('[MCPOAuth] Using public client authentication (no secret, pre-configured)');
body.append('client_id', config.client_id);
}