diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py
index 8aa947da68e484ff135fb1ca84664cc1a839abff..6bc24aa90e923386f0219ced98ea8eb1ac95b960 100644
--- a/lms/djangoapps/ccx/tests/test_field_override_performance.py
+++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py
@@ -245,7 +245,7 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
     __test__ = True
 
     # TODO: decrease query count as part of REVO-28
-    QUERY_COUNT = 31
+    QUERY_COUNT = 33
     TEST_DATA = {
         # (providers, course_width, enable_ccx, view_as_ccx): (
         #     # of sql queries to default,
@@ -274,7 +274,7 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
     __test__ = True
 
     # TODO: decrease query count as part of REVO-28
-    QUERY_COUNT = 31
+    QUERY_COUNT = 33
 
     TEST_DATA = {
         ('no_overrides', 1, True, False): (QUERY_COUNT, 3),
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 6bd3cde360327e1e880c503a6ab944c7d7d723e6..22748d44f6639d21c3a7dbff53bf9734359b4533 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -269,8 +269,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
     NUM_PROBLEMS = 20
 
     @ddt.data(
-        (ModuleStoreEnum.Type.mongo, 10, 173),
-        (ModuleStoreEnum.Type.split, 4, 169),
+        (ModuleStoreEnum.Type.mongo, 10, 175),
+        (ModuleStoreEnum.Type.split, 4, 171),
     )
     @ddt.unpack
     def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count):
@@ -1425,8 +1425,8 @@ class ProgressPageTests(ProgressPageBaseTests):
             self.assertContains(resp, u"Download Your Certificate")
 
     @ddt.data(
-        (True, 53),
-        (False, 52),
+        (True, 55),
+        (False, 54),
     )
     @ddt.unpack
     def test_progress_queries_paced_courses(self, self_paced, query_count):
@@ -1439,8 +1439,8 @@ class ProgressPageTests(ProgressPageBaseTests):
 
     @patch.dict(settings.FEATURES, {'ASSUME_ZERO_GRADE_IF_ABSENT_FOR_ALL_TESTS': False})
     @ddt.data(
-        (False, 61, 42),
-        (True, 52, 37)
+        (False, 63, 44),
+        (True, 54, 39)
     )
     @ddt.unpack
     def test_progress_queries(self, enable_waffle, initial, subsequent):
diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py
index e611adb799a706a96e153cec645081d4a0141739..545d8a420ccbe850686aa3f4b27ff37a15607459 100644
--- a/lms/djangoapps/courseware/views/views.py
+++ b/lms/djangoapps/courseware/views/views.py
@@ -134,6 +134,7 @@ from xmodule.x_module import STUDENT_VIEW
 from ..context_processor import user_timezone_locale_prefs
 from ..entrance_exams import user_can_skip_entrance_exam
 from ..module_render import get_module, get_module_by_usage_id, get_module_for_descriptor
+from ..tabs import _get_dynamic_tabs
 
 log = logging.getLogger("edx.courseware")
 
@@ -602,7 +603,8 @@ class CourseTabView(EdxFragmentView):
             course = get_course_with_access(request.user, 'load', course_key)
             try:
                 # Render the page
-                tab = CourseTabList.get_tab_by_type(course.tabs, tab_type)
+                course_tabs = course.tabs + _get_dynamic_tabs(course, request.user)
+                tab = CourseTabList.get_tab_by_type(course_tabs, tab_type)
                 page_context = self.create_page_context(request, course=course, tab=tab, **kwargs)
 
                 # Show warnings if the user has limited access
diff --git a/lms/djangoapps/discussion/plugins.py b/lms/djangoapps/discussion/plugins.py
index dae5ade9d3967866511ab93f6155c3814422a659..b279827c24a026be027df171c2f670f169137c1c 100644
--- a/lms/djangoapps/discussion/plugins.py
+++ b/lms/djangoapps/discussion/plugins.py
@@ -8,6 +8,7 @@ from django.utils.translation import ugettext_noop
 
 import lms.djangoapps.discussion.django_comment_client.utils as utils
 from lms.djangoapps.courseware.tabs import EnrolledTab
+from openedx.features.lti_course_tab.tab import DiscussionLtiCourseTab
 from xmodule.tabs import TabFragmentViewMixin
 
 
@@ -30,4 +31,7 @@ class DiscussionTab(TabFragmentViewMixin, EnrolledTab):
     def is_enabled(cls, course, user=None):
         if not super(DiscussionTab, cls).is_enabled(course, user):
             return False
+        # Disable the regular discussion tab if LTI-based external Discussion forum is enabled
+        if DiscussionLtiCourseTab.is_enabled(course, user):
+            return False
         return utils.is_discussion_enabled(course.id)
diff --git a/lms/urls.py b/lms/urls.py
index 085e604ad7fc00386b296ca2f96b2b368b3bf685..b416f3d57314a270e59e8b3dbe3d71a6993f52e7 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -746,6 +746,17 @@ urlpatterns += [
     ),
 ]
 
+urlpatterns += [
+    url(
+        r'^courses/{}/lti_tab/(?P<provider_uuid>[^/]+)/$'.format(
+            settings.COURSE_ID_PATTERN,
+        ),
+        CourseTabView.as_view(),
+        name='lti_course_tab',
+        kwargs={'tab_type': 'lti_tab'},
+    ),
+]
+
 urlpatterns += [
     # This MUST be the last view in the courseware--it's a catch-all for custom tabs.
     url(
diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py
index d0581da74724012b65e0b3698885967deaf56727..04f45d079c4fd884158e7041af7c7a989b1fb951 100644
--- a/openedx/features/course_experience/tests/views/test_course_home.py
+++ b/openedx/features/course_experience/tests/views/test_course_home.py
@@ -208,7 +208,7 @@ class TestCourseHomePage(CourseHomePageTestCase):
 
         # Fetch the view and verify the query counts
         # TODO: decrease query count as part of REVO-28
-        with self.assertNumQueries(75, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
+        with self.assertNumQueries(78, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
             with check_mongo_calls(4):
                 url = course_home_url(self.course)
                 self.client.get(url)
diff --git a/openedx/features/course_experience/tests/views/test_course_updates.py b/openedx/features/course_experience/tests/views/test_course_updates.py
index d602a413ed8d245fbc5bb2de4a69c8b05c25752f..0092588dcd4da1a2cdaeecf919ef8012dbe566e2 100644
--- a/openedx/features/course_experience/tests/views/test_course_updates.py
+++ b/openedx/features/course_experience/tests/views/test_course_updates.py
@@ -49,7 +49,7 @@ class TestCourseUpdatesPage(BaseCourseUpdatesTestCase):
 
         # Fetch the view and verify that the query counts haven't changed
         # TODO: decrease query count as part of REVO-28
-        with self.assertNumQueries(49, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
+        with self.assertNumQueries(52, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
             with check_mongo_calls(4):
                 url = course_updates_url(self.course)
                 self.client.get(url)
diff --git a/openedx/features/lti_course_tab/__init__.py b/openedx/features/lti_course_tab/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/openedx/features/lti_course_tab/tab.py b/openedx/features/lti_course_tab/tab.py
new file mode 100644
index 0000000000000000000000000000000000000000..4919a05a8659b33242814ba05887c9154593e131
--- /dev/null
+++ b/openedx/features/lti_course_tab/tab.py
@@ -0,0 +1,247 @@
+"""
+Code related to LTI course tab functionality.
+"""
+from typing import Dict
+from urllib.parse import quote
+
+from django.contrib.auth.models import AbstractBaseUser
+from django.contrib.sites.shortcuts import get_current_site
+from django.http import HttpRequest
+from django.utils.translation import get_language, to_locale, ugettext_lazy
+from lti_consumer.lti_1p1.contrib.django import lti_embed
+from lti_consumer.models import LtiConfiguration
+from opaque_keys.edx.keys import CourseKey
+from web_fragments.fragment import Fragment
+
+from lms.djangoapps.courseware.access import get_user_role
+from lms.djangoapps.courseware.tabs import EnrolledTab
+from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration
+from openedx.core.djangolib.markup import HTML
+from common.djangoapps.student.models import anonymous_id_for_user
+from xmodule.course_module import CourseDescriptor
+from xmodule.tabs import TabFragmentViewMixin, key_checker
+
+
+class LtiCourseLaunchMixin:
+    """
+    Mixin that encapsulates all LTI-related functionality from the View
+    """
+
+    ROLE_MAP = {
+        'student': 'Student',
+        'staff': 'Administrator',
+        'instructor': 'Instructor',
+    }
+    DEFAULT_ROLE = 'Student'
+
+    def _get_additional_lti_parameters(self, course: CourseDescriptor, request: HttpRequest) -> Dict[str, str]:
+        lti_config = self._get_lti_config(course)
+        additional_config = lti_config.lti_config.get('additional_parameters', {})
+        return additional_config
+
+    @staticmethod
+    def _get_user_id(user: AbstractBaseUser, course_key: CourseKey):
+        return anonymous_id_for_user(user, course_key)
+
+    def _get_lti_roles(self, user: AbstractBaseUser, course_key: CourseKey) -> str:
+        return self.ROLE_MAP.get(
+            get_user_role(user, course_key),
+            self.DEFAULT_ROLE,
+        )
+
+    @staticmethod
+    def _get_context_id(course_key: CourseKey) -> str:
+        return quote(str(course_key))
+
+    @staticmethod
+    def _get_resource_link_id(course_key: CourseKey, request: HttpRequest) -> str:
+        site = get_current_site(request)
+        return '{}-{}'.format(
+            site.domain,
+            str(course_key.make_usage_key('course', course_key.run)),
+        )
+
+    @staticmethod
+    def _get_result_sourcedid(context_id: str, resource_link_id: str, user_id: str) -> str:
+        return "{context}:{resource_link}:{user_id}".format(
+            context=context_id,
+            resource_link=resource_link_id,
+            user_id=user_id,
+        )
+
+    @staticmethod
+    def _get_context_title(course: CourseDescriptor) -> str:
+        return "{} - {}".format(
+            course.display_name_with_default,
+            course.display_org_with_default,
+        )
+
+    def _get_lti_config(self, course: CourseDescriptor) -> LtiConfiguration:
+        raise NotImplementedError
+
+    def _get_lti_embed_code(self, course: CourseDescriptor, request: HttpRequest) -> str:
+        """
+        Returns the LTI embed code for embedding in the current course context.
+        Args:
+            course (CourseDescriptor): CourseDescriptor object.
+            request (HttpRequest): Request object for view in which LTI will be embedded.
+        Returns:
+            HTML code to embed LTI in course page.
+        """
+        course_key = course.id
+        lti_config = self._get_lti_config(course)
+        lti_consumer = lti_config.get_lti_consumer()
+        user_id = quote(self._get_user_id(request.user, course_key))
+        context_id = quote(self._get_context_id(course_key))
+        resource_link_id = quote(self._get_resource_link_id(course_key, request))
+        roles = self._get_lti_roles(request.user, course_key)
+        context_title = self._get_context_title(course)
+        result_sourcedid = quote(self._get_result_sourcedid(context_id, resource_link_id, user_id))
+        additional_params = self._get_additional_lti_parameters(course, request)
+        locale = to_locale(get_language())
+
+        return lti_embed(
+            html_element_id='lti-tab-launcher',
+            lti_consumer=lti_consumer,
+            resource_link_id=resource_link_id,
+            user_id=user_id,
+            roles=roles,
+            context_id=context_id,
+            context_title=context_title,
+            context_label=context_id,
+            result_sourcedid=result_sourcedid,
+            launch_presentation_locale=locale,
+            **additional_params,
+        )
+
+    # pylint: disable=unused-argument
+    def render_to_fragment(self, request: HttpRequest, course: CourseDescriptor, **kwargs) -> Fragment:
+        """
+        Returns a fragment view for the LTI launch.
+        Args:
+            request (HttpRequest): request object
+            course (CourseDescriptor): A course object
+        Returns:
+            A Fragment that embeds LTI in a course page.
+        """
+        lti_embed_html = self._get_lti_embed_code(course, request)
+
+        fragment = Fragment(
+            HTML(
+                """
+                <iframe
+                    id='lti-tab-embed'
+                    srcdoc='{srcdoc}'
+                 >
+                </iframe>
+                """
+            ).format(
+                srcdoc=lti_embed_html
+            )
+        )
+        fragment.add_css(
+            """
+            #lti-tab-embed {
+                width: 100%;
+                min-height: 400px;
+                border: none;
+            }
+            """
+        )
+        return fragment
+
+
+class LtiCourseTab(LtiCourseLaunchMixin, EnrolledTab):
+    """
+    A tab to add custom LTI components to a course in a tab.
+    """
+    type = 'lti_tab'
+    is_default = False
+    allow_multiple = True
+
+    def _get_lti_config(self, course: CourseDescriptor) -> LtiConfiguration:
+        return LtiConfiguration.objects.get(config_id=self.lti_config_id)
+
+    def __init__(self, tab_dict=None, name=None, lti_config_id=None):
+        def link_func(course, reverse_func):
+            """ Returns a function that returns the lti tab's URL. """
+            return reverse_func('lti_course_tab', args=[str(course.id), self.lti_config_id])
+
+        self.lti_config_id = tab_dict.get('lti_config_id') if tab_dict else lti_config_id
+
+        if tab_dict is None:
+            tab_dict = dict()
+
+        if name is not None:
+            tab_dict['name'] = name
+
+        tab_dict['link_func'] = link_func
+        tab_dict['tab_id'] = 'lti_tab_{0}'.format(self.lti_config_id)
+
+        super().__init__(tab_dict)
+
+    @classmethod
+    def validate(cls, tab_dict, raise_error=True):
+        """
+        Ensures that the specified tab_dict is valid.
+        """
+        return (
+            super().validate(tab_dict, raise_error)
+            and key_checker(['name', 'lti_config_id'])(tab_dict, raise_error)
+        )
+
+    def __getitem__(self, key):
+        if key == 'lti_config_id':
+            return self.lti_config_id
+        else:
+            return super().__getitem__(key)
+
+    def __setitem__(self, key, value):
+        if key == 'lti_config_id':
+            self.lti_config_id = value
+        else:
+            super().__setitem__(key, value)
+
+    def to_json(self):
+        """
+        Return a dictionary representation of this tab.
+        """
+        to_json_val = super().to_json()
+        to_json_val.update({'lti_config_id': self.lti_config_id})
+        return to_json_val
+
+    def __eq__(self, other):
+        if not super().__eq__(other):
+            return False
+        return self.lti_config_id == other.get('lti_config_id')
+
+    def __hash__(self):
+        """
+        Return a hash representation of Tab Object.
+        """
+        return hash(repr(self))
+
+
+class DiscussionLtiCourseTab(LtiCourseLaunchMixin, TabFragmentViewMixin, EnrolledTab):
+    """
+    Course tab that loads the associated LTI-based discussion provider in a tab.
+    """
+    type = 'lti_discussion'
+    allow_multiple = False
+    is_dynamic = True
+    title = ugettext_lazy("Discussion")
+
+    def _get_lti_config(self, course: CourseDescriptor) -> LtiConfiguration:
+        config = DiscussionsConfiguration.get(course.id)
+        return config.lti_configuration
+
+    @classmethod
+    def is_enabled(cls, course, user=None):
+        if super().is_enabled(course, user):
+            config = DiscussionsConfiguration.get(course.id)
+            return (
+                config.enabled and
+                config.lti_configuration is not None
+            )
+        else:
+            return False
diff --git a/openedx/features/lti_course_tab/tests.py b/openedx/features/lti_course_tab/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..a28401576ca1a8b3f338ce7f3724e33a365c7872
--- /dev/null
+++ b/openedx/features/lti_course_tab/tests.py
@@ -0,0 +1,39 @@
+"""
+Tests for LTI Course tabs.
+"""
+from unittest.mock import Mock, patch
+
+from lms.djangoapps.courseware.tests.test_tabs import TabTestCase
+from openedx.features.lti_course_tab.tab import DiscussionLtiCourseTab
+
+
+class DiscussionLtiCourseTabTestCase(TabTestCase):
+    """Test cases for LTI Discussion Tab."""
+
+    def check_discussion_tab(self):
+        """Helper function for verifying the LTI discussion tab."""
+        return self.check_tab(
+            tab_class=DiscussionLtiCourseTab,
+            dict_tab={'type': DiscussionLtiCourseTab.type, 'name': 'same'},
+            expected_link=self.reverse('course_tab_view', args=[str(self.course.id), DiscussionLtiCourseTab.type]),
+            expected_tab_id=DiscussionLtiCourseTab.type,
+            invalid_dict_tab=None,
+        )
+
+    @patch('openedx.features.lti_course_tab.tab.DiscussionsConfiguration.get')
+    @patch('common.djangoapps.student.models.CourseEnrollment.is_enrolled')
+    def test_discussion_lti_tab(self, is_enrolled, discussion_config_get):
+        is_enrolled.return_value = True
+        mock_config = Mock()
+        mock_config.lti_configuration = {}
+        mock_config.enabled = False
+        discussion_config_get.return_value = mock_config
+        tab = self.check_discussion_tab()
+        self.check_can_display_results(
+            tab, for_staff_only=True, for_enrolled_users_only=True, expected_value=False
+        )
+        mock_config.enabled = True
+        self.check_discussion_tab()
+        self.check_can_display_results(
+            tab, for_staff_only=True, for_enrolled_users_only=True
+        )
diff --git a/requirements/edx-sandbox/py35.txt b/requirements/edx-sandbox/py35.txt
index f4cb5c1aa3d2021c35e1b268fb715f302e4a4d80..3c793990f36eb9a5ef470b246207c5c2221d5f08 100644
--- a/requirements/edx-sandbox/py35.txt
+++ b/requirements/edx-sandbox/py35.txt
@@ -20,7 +20,7 @@ matplotlib==2.2.4         # via -c requirements/edx-sandbox/../constraints.txt,
 mpmath==1.1.0             # via sympy
 networkx==2.2             # via -r requirements/edx-sandbox/py35.in
 nltk==3.5                 # via -r requirements/edx-sandbox/shared.txt, chem
-numpy==1.16.5             # via -c requirements/edx-sandbox/../constraints.txt, -r requirements/edx-sandbox/py35.in, chem, matplotlib, openedx-calc, scipy
+numpy==1.16.5             # via -c requirements/edx-sandbox/../constraints.txt, -r requirements/edx-sandbox/py35.in, chem, matplotlib, openedx-calc
 openedx-calc==1.0.9       # via -r requirements/edx-sandbox/py35.in
 pycparser==2.20           # via -r requirements/edx-sandbox/shared.txt, cffi
 pyparsing==2.2.0          # via -r requirements/edx-sandbox/py35.in, chem, matplotlib, openedx-calc
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index c573d8ec8fd2eb32a8064c02b93b912000b0615d..00a7f1ac176a5a388e5a58acf95f6c01b91bca29 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -67,7 +67,7 @@ django-mysql==3.10.0      # via -r requirements/edx/base.in
 django-oauth-toolkit==1.3.2  # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in
 django-object-actions==3.0.1  # via edx-enterprise
 django-pipeline==1.7.0    # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in
-django-pyfs==2.2          # via -r requirements/edx/base.in
+django-pyfs==3.0          # via -r requirements/edx/base.in
 git+https://github.com/edx/django-ratelimit-backend.git@v2.0.1a5#egg=django-ratelimit-backend==2.0.1a5  # via -r requirements/edx/github.in
 django-ratelimit==3.0.1   # via -r requirements/edx/base.in
 django-require==1.0.11    # via -r requirements/edx/base.in
@@ -126,7 +126,7 @@ future==0.18.2            # via django-ses, edx-celeryutils, edx-enterprise, pyc
 geoip2==3.0.0             # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in
 glob2==0.7                # via -r requirements/edx/base.in
 gunicorn==20.0.4          # via -r requirements/edx/base.in
-help-tokens==1.1.3        # via -r requirements/edx/base.in
+help-tokens==2.0.0        # via -r requirements/edx/base.in
 html5lib==1.1             # via -r requirements/edx/base.in, ora2
 icalendar==4.0.7          # via -r requirements/edx/base.in
 idna==2.10                # via -r requirements/edx/paver.txt, requests
diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt
index 1340e1838ab0eb67e3b82504efe475eafd52fa3d..3973dcd1deb54ca7782610dc8ffa813ecc5fbd7c 100644
--- a/requirements/edx/development.txt
+++ b/requirements/edx/development.txt
@@ -78,7 +78,7 @@ django-mysql==3.10.0      # via -r requirements/edx/testing.txt
 django-oauth-toolkit==1.3.2  # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt
 django-object-actions==3.0.1  # via -r requirements/edx/testing.txt, edx-enterprise
 django-pipeline==1.7.0    # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt
-django-pyfs==2.2          # via -r requirements/edx/testing.txt
+django-pyfs==3.0          # via -r requirements/edx/testing.txt
 git+https://github.com/edx/django-ratelimit-backend.git@v2.0.1a5#egg=django-ratelimit-backend==2.0.1a5  # via -r requirements/edx/testing.txt
 django-ratelimit==3.0.1   # via -r requirements/edx/testing.txt
 django-require==1.0.11    # via -r requirements/edx/testing.txt
@@ -146,7 +146,7 @@ gitdb==4.0.5              # via -r requirements/edx/testing.txt, gitpython
 gitpython==3.1.12         # via -r requirements/edx/testing.txt, transifex-client
 glob2==0.7                # via -r requirements/edx/testing.txt
 gunicorn==20.0.4          # via -r requirements/edx/testing.txt
-help-tokens==1.1.3        # via -r requirements/edx/testing.txt
+help-tokens==2.0.0        # via -r requirements/edx/testing.txt
 html5lib==1.1             # via -r requirements/edx/testing.txt, ora2
 httpretty==0.9.7          # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt
 icalendar==4.0.7          # via -r requirements/edx/testing.txt
diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt
index 05ff0b9856a8624e25581e963aebde0cf52c0b25..8a39ecbb1defb7d93e94b024f7ec5e1b4dcc36f2 100644
--- a/requirements/edx/testing.txt
+++ b/requirements/edx/testing.txt
@@ -76,7 +76,7 @@ django-mysql==3.10.0      # via -r requirements/edx/base.txt
 django-oauth-toolkit==1.3.2  # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt
 django-object-actions==3.0.1  # via -r requirements/edx/base.txt, edx-enterprise
 django-pipeline==1.7.0    # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt
-django-pyfs==2.2          # via -r requirements/edx/base.txt
+django-pyfs==3.0          # via -r requirements/edx/base.txt
 git+https://github.com/edx/django-ratelimit-backend.git@v2.0.1a5#egg=django-ratelimit-backend==2.0.1a5  # via -r requirements/edx/base.txt
 django-ratelimit==3.0.1   # via -r requirements/edx/base.txt
 django-require==1.0.11    # via -r requirements/edx/base.txt
@@ -142,7 +142,7 @@ gitdb==4.0.5              # via gitpython
 gitpython==3.1.12         # via transifex-client
 glob2==0.7                # via -r requirements/edx/base.txt
 gunicorn==20.0.4          # via -r requirements/edx/base.txt
-help-tokens==1.1.3        # via -r requirements/edx/base.txt
+help-tokens==2.0.0        # via -r requirements/edx/base.txt
 html5lib==1.1             # via -r requirements/edx/base.txt, ora2
 httpretty==0.9.7          # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.in
 icalendar==4.0.7          # via -r requirements/edx/base.txt
diff --git a/setup.py b/setup.py
index 59a04229f739732bc3f9ad5dbc87ea23d1eb7e1b..44417e713e6208bbee274a770c86aef7e0737523 100644
--- a/setup.py
+++ b/setup.py
@@ -28,6 +28,8 @@ setup(
             "external_link = lms.djangoapps.courseware.tabs:ExternalLinkCourseTab",
             "html_textbooks = lms.djangoapps.courseware.tabs:HtmlTextbookTabs",
             "instructor = lms.djangoapps.instructor.views.instructor_dashboard:InstructorDashboardTab",
+            "lti_discussion = openedx.features.lti_course_tab.tab:DiscussionLtiCourseTab",
+            "lti_tab = openedx.features.lti_course_tab.tab:LtiCourseTab",
             "pdf_textbooks = lms.djangoapps.courseware.tabs:PDFTextbookTabs",
             "progress = lms.djangoapps.courseware.tabs:ProgressTab",
             "static_tab = xmodule.tabs:StaticTab",