איך עובדת הזדהות OAuth ב MCP של Langlets

10/07/2026

בוובינר היום ראינו איך לחבר סוכני AI למערכות שלנו ודיברנו על הדוגמה של Langlets ומימוש ה MCP שם. מפאת קוצר זמן דילגתי על הירידה לפרטים התחביריים של הקוד. בפוסט זה אני אתחיל לסגור את הפער ואציג את הפרטים הטכניים סביב הזדהות OAuth.

1. רישום האפליקציה

בשלב הראשון בתהליך ההזדהות סוכן ה AI צריך להירשם בתור "אפליקציה מורשית גישה". בדרך כלל ב OAuth אנחנו רושמים אפליקציות צד שלישי שיוכלו להתחבר למערכות שלנו בצורה ידנית, לדוגמה אם אתם רוצים לכתוב אפליקציה שתיגש בשם המשתמש לגיטהאב עליכם להיכנס למסך הניהול של גיטהאב ולרשום שם "אפליקציית גיטהאב חדשה" בחשבון שלכם. אתם תקבלו מזהה של האפליקציה בדמות Client Id ו Client Secret ותשתמשו במזהה זה כשתשלחו את המשתמשים להזדהות מול גיטהאב עבור האפליקציה שלכם.

בעולם של MCP סוכני AI לא יכולים לרשום את עצמם מראש מול האפליקציה שלי ולכן OAuth מאפשר מנגנון של רישום אוטומטי. ב langlets מנגנון זה ממומש בקבצים:

https://github.com/ynonp/langlets-rails/blob/main/app/controllers/wellknowncontroller.rb

https://github.com/ynonp/langlets-rails/blob/main/app/controllers/oauth/registrations_controller.rb

סוכן ה AI מתחיל עם הנתיב oauth_authorization_server ודרכו מקבל את השמות של כל הנתיבים שהוא צריך כדי לרשום אפליקציה. לאחר מכן הוא ממשיך לנתיב ההרשמה ושם מגיע לפונקציה RegistrationsController#create. פונקציה זו יוצרת אפליקציה חדשה ב Doorkeeper שזו הספריה ברובי שאחראית על הזדהות באמצעות טוקנים. זה הקוד הרלוונטי ממנה:

      application = Doorkeeper::Application.new(
        name: params[:client_name].presence || "Unnamed agent client",
        redirect_uri: redirect_uris.join("\n"),
        scopes: allowed_scopes(params[:scope]),
        confidential: params[:token_endpoint_auth_method] != "none",
        dynamically_registered: true
      )

      unless application.save
        return rfc_error("invalid_client_metadata", application.errors.full_messages.join("; "))
      end

      render status: :created, json: registration_response(application, redirect_uris)

רק אחרי יצירת האפליקציה אפשר לשלוח את המשתמש לתהליך ההזדהות ולקבל טוקן עבורו.

2. הזדהות בשם המשתמש ובקשת קוד גישה

אחרי שסוכן ה AI נרשם בתור אפליקציה הוא מקבל תשובה במבנה הבא (עדיין מתוך הקובץ registrations_controller.rb):

      response = {
        client_id: application.uid,
        client_id_issued_at: application.created_at.to_i,
        client_name: application.name,
        redirect_uris: redirect_uris,
        grant_types: %w[authorization_code refresh_token],
        response_types: %w[code],
        token_endpoint_auth_method: application.confidential? ? "client_secret_basic" : "none",
        scope: application.scopes.to_s
      }

אנחנו מזהים שם את מזהה האפליקציה client_id. בשלב הבא סוכן ה AI שולח את הגולש באמצעות הפניית הדפדפן לנתיב הזדהות על Langlets:

Open /oauth/authorize?client_id=...&code_challenge=...&code_challenge_method=S256

קריאה זו מטופלת בצד של langlets על ידי ספריית Doorkeeper בזכות השורות האלה בקובץ config/routes.rb:

  use_doorkeeper do
    skip_controllers :authorized_applications
  end

דורקיפר מטפלת בפרמטרים שהתקבלו ומציגה טופס הזדהות שמוגדר בקובץ:

https://github.com/ynonp/langlets-rails/blob/main/app/views/doorkeeper/authorizations/new.html.erb

בבסיס הנתונים דורקיפר שומרת את פרמטר ה Code Challenge בטבלת oauth_access_grants. זו הגדרת הטבלה:

  create_table "oauth_access_grants", force: :cascade do |t|
    t.bigint "resource_owner_id", null: false
    t.bigint "application_id", null: false
    t.string "token", null: false
    t.integer "expires_in", null: false
    t.text "redirect_uri", null: false
    t.string "scopes", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "revoked_at"
    t.string "code_challenge"
    t.string "code_challenge_method"
    t.index ["application_id"], name: "index_oauth_access_grants_on_application_id"
    t.index ["resource_owner_id"], name: "index_oauth_access_grants_on_resource_owner_id"
    t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
  end

אנחנו עוד נצטרך את הערך שלו. אחרי שהמשתמש מאשר את הגישה דורקיפר שולחת את המשתמש חזרה לסוכן ה AI עם פרמטר בשם code.

3. החלפת קוד הגישה בטוקן

סוכן ה AI קיבל קוד גישה אבל עדיין אי אפשר לבצע פעולות במערכת עם קוד זה, ל OAuth יש עוד שלב אחד - החלפת קוד הגישה בטוקן. בשביל ההחלפה סוכן ה AI פונה לנתיב:

POST /oauth/token {grant_type, code, code_verifier, client_id}

ומצרף את הקוד שקיבל. דורקיפר בצד השרת מבצעת:

  1. מוצאת את השורה הרלוונטית מ oauth_access_grants בה עמודת token שווה לקוד שהתקבל כפרמטר.

  2. מחשבת Hash של code_verifier ומוודאת שהוא תואם ל code_challenge ששמור בטבלה.

  3. במידה והכל תקין מחזירה Access Token ו Refresh Token. ה Access Token תקף לשעתיים וה Refresh Token לחודש. בעזרת ה Refresh Token אפשר לקבל Access Tokens נוספים בהמשך.

4. ביצוע בקשות מול ה MCP

עכשיו יש לסוכן ה AI אסימון גישה והוא יכול להתחיל לבצע פעולות בשם המשתמש. פניה ראשונה ל /mcp מגיעה לקובץ:

https://github.com/ynonp/langlets-rails/blob/main/app/controllers/mcp_controller.rb

שם אנחנו פוגשים את הפונקציה handle שמגדירה את הכלים הזמינים ומפעילה את הכלי המתאים:

  def handle
    server = MCP::Server.new(
      name: "langlets",
      version: "1.0.0",
      tools: [ Tools::GetVocabulary, Tools::GetCourses ],
      server_context: { user: @current_user, scopes: @token_scopes, base_url: request.base_url }
    )
    transport = MCP::Server::Transports::StreamableHTTPTransport.new(
      server, stateless: true, enable_json_response: true
    )

    status, transport_headers, body = transport.handle_request(request)
    transport_headers.each { |key, value| response.set_header(key, value) }
    render body: body.join, status: status, content_type: transport_headers["Content-Type"] || "application/json"
  end

הכלים עצמם מוגדרים בקבצים בתיקיה:

https://github.com/ynonp/langlets-rails/tree/main/app/mcp/tools

ובתוך כל כלי המשתנה server_context[:user] מחזיק את המשתמש המחובר.

אומנם לנגלטס כתוב ב Ruby אבל העקרונות וזרימת המידע שתוארה כאן יעבדו בכל מערכת שתרצו לחבר לסוכני AI או אפילו באופן כללי יותר למערכות צד שלישי באמצעות OAuth.