csa-skilljar 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. csa_skilljar-0.8.0/LICENSE +202 -0
  2. csa_skilljar-0.8.0/PKG-INFO +315 -0
  3. csa_skilljar-0.8.0/README.md +275 -0
  4. csa_skilljar-0.8.0/pyproject.toml +76 -0
  5. csa_skilljar-0.8.0/setup.cfg +4 -0
  6. csa_skilljar-0.8.0/src/csa_skilljar/__init__.py +11 -0
  7. csa_skilljar-0.8.0/src/csa_skilljar/auth.py +169 -0
  8. csa_skilljar-0.8.0/src/csa_skilljar/backend.py +2011 -0
  9. csa_skilljar-0.8.0/src/csa_skilljar/client.py +319 -0
  10. csa_skilljar-0.8.0/src/csa_skilljar/exceptions.py +54 -0
  11. csa_skilljar-0.8.0/src/csa_skilljar/mcp/__init__.py +1 -0
  12. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_config.py +142 -0
  13. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_schemas.py +455 -0
  14. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/__init__.py +20 -0
  15. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/_base.py +63 -0
  16. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/access.py +92 -0
  17. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/courses.py +172 -0
  18. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/enrolment.py +372 -0
  19. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/feedback.py +47 -0
  20. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/groups.py +425 -0
  21. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/lessons.py +266 -0
  22. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/publishing.py +362 -0
  23. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/question_banks.py +287 -0
  24. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/questions.py +284 -0
  25. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/quizzes.py +196 -0
  26. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/signup_fields.py +187 -0
  27. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/students.py +299 -0
  28. csa_skilljar-0.8.0/src/csa_skilljar/mcp/_tools/web_packages.py +282 -0
  29. csa_skilljar-0.8.0/src/csa_skilljar/mcp/cli.py +90 -0
  30. csa_skilljar-0.8.0/src/csa_skilljar/mcp/server.py +80 -0
  31. csa_skilljar-0.8.0/src/csa_skilljar/policy.py +201 -0
  32. csa_skilljar-0.8.0/src/csa_skilljar/py.typed +0 -0
  33. csa_skilljar-0.8.0/src/csa_skilljar/scopes.py +105 -0
  34. csa_skilljar-0.8.0/src/csa_skilljar.egg-info/PKG-INFO +315 -0
  35. csa_skilljar-0.8.0/src/csa_skilljar.egg-info/SOURCES.txt +74 -0
  36. csa_skilljar-0.8.0/src/csa_skilljar.egg-info/dependency_links.txt +1 -0
  37. csa_skilljar-0.8.0/src/csa_skilljar.egg-info/entry_points.txt +2 -0
  38. csa_skilljar-0.8.0/src/csa_skilljar.egg-info/requires.txt +16 -0
  39. csa_skilljar-0.8.0/src/csa_skilljar.egg-info/top_level.txt +1 -0
  40. csa_skilljar-0.8.0/tests/test_access_tools.py +50 -0
  41. csa_skilljar-0.8.0/tests/test_auth.py +174 -0
  42. csa_skilljar-0.8.0/tests/test_backend_conformance.py +62 -0
  43. csa_skilljar-0.8.0/tests/test_batch.py +60 -0
  44. csa_skilljar-0.8.0/tests/test_check_upstream.py +104 -0
  45. csa_skilljar-0.8.0/tests/test_cli.py +79 -0
  46. csa_skilljar-0.8.0/tests/test_config.py +112 -0
  47. csa_skilljar-0.8.0/tests/test_courses_read.py +55 -0
  48. csa_skilljar-0.8.0/tests/test_courses_tool.py +64 -0
  49. csa_skilljar-0.8.0/tests/test_courses_write.py +118 -0
  50. csa_skilljar-0.8.0/tests/test_descriptions.py +198 -0
  51. csa_skilljar-0.8.0/tests/test_enrolment.py +188 -0
  52. csa_skilljar-0.8.0/tests/test_error_translation.py +69 -0
  53. csa_skilljar-0.8.0/tests/test_exceptions.py +30 -0
  54. csa_skilljar-0.8.0/tests/test_feedback_tool.py +28 -0
  55. csa_skilljar-0.8.0/tests/test_groups.py +345 -0
  56. csa_skilljar-0.8.0/tests/test_install.py +123 -0
  57. csa_skilljar-0.8.0/tests/test_lessons_read.py +102 -0
  58. csa_skilljar-0.8.0/tests/test_lessons_write.py +148 -0
  59. csa_skilljar-0.8.0/tests/test_pagination.py +162 -0
  60. csa_skilljar-0.8.0/tests/test_parity.py +73 -0
  61. csa_skilljar-0.8.0/tests/test_policy.py +209 -0
  62. csa_skilljar-0.8.0/tests/test_protocol.py +271 -0
  63. csa_skilljar-0.8.0/tests/test_public_api.py +33 -0
  64. csa_skilljar-0.8.0/tests/test_publishing.py +246 -0
  65. csa_skilljar-0.8.0/tests/test_question_banks.py +184 -0
  66. csa_skilljar-0.8.0/tests/test_questions.py +181 -0
  67. csa_skilljar-0.8.0/tests/test_quizzes.py +168 -0
  68. csa_skilljar-0.8.0/tests/test_scopes.py +40 -0
  69. csa_skilljar-0.8.0/tests/test_signup_fields.py +176 -0
  70. csa_skilljar-0.8.0/tests/test_stdout_guard.py +15 -0
  71. csa_skilljar-0.8.0/tests/test_students.py +184 -0
  72. csa_skilljar-0.8.0/tests/test_tool_errors.py +36 -0
  73. csa_skilljar-0.8.0/tests/test_tool_naming.py +45 -0
  74. csa_skilljar-0.8.0/tests/test_v2backend.py +167 -0
  75. csa_skilljar-0.8.0/tests/test_web_packages.py +304 -0
  76. csa_skilljar-0.8.0/tests/test_zero_defect.py +212 -0
@@ -0,0 +1,202 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2025 Kurt Seifried kseifried@cloudsecurityalliance.org
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+
@@ -0,0 +1,315 @@
1
+ Metadata-Version: 2.4
2
+ Name: csa-skilljar
3
+ Version: 0.8.0
4
+ Summary: Python client and local MCP server for both Skilljar REST APIs
5
+ Author: Cloud Security Alliance
6
+ Maintainer-email: Kurt Seifried <kseifried@cloudsecurityalliance.org>
7
+ License-Expression: Apache-2.0
8
+ Project-URL: Homepage, https://github.com/CloudSecurityAlliance/csa-skilljar
9
+ Project-URL: Issues, https://github.com/CloudSecurityAlliance/csa-skilljar/issues
10
+ Keywords: skilljar,lms,training,mcp,elearning,gainsight
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Education
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: httpx>=0.27
26
+ Requires-Dist: typing_extensions>=4.7
27
+ Requires-Dist: mcp>=2.1
28
+ Provides-Extra: mcp
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8; extra == "dev"
31
+ Requires-Dist: pytest-cov>=5; extra == "dev"
32
+ Requires-Dist: ruff>=0.6; extra == "dev"
33
+ Requires-Dist: mypy>=1.11; extra == "dev"
34
+ Requires-Dist: respx>=0.21; extra == "dev"
35
+ Requires-Dist: anyio>=4; extra == "dev"
36
+ Requires-Dist: build>=1.2; extra == "dev"
37
+ Requires-Dist: bandit>=1.7; extra == "dev"
38
+ Requires-Dist: pip-audit>=2.7; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ ```
42
+ project_tracker_base: CINO Project Tracker:appf7fRQUvY9Iy7sL
43
+ project_tracker_table: Projects:tblchmbxSAavvJKaY
44
+ project_tracker_record: csa-skilljar:recbwfx6O30BboQNv
45
+ project_source: github:CloudSecurityAlliance-Internal/CINO-Projects/projects/CloudSecurityAlliance/csa-skilljar
46
+ ```
47
+
48
+ # csa-skilljar
49
+
50
+ A Python library and local MCP server for the [Skilljar](https://www.skilljar.com/) customer
51
+ education platform, covering **both** of Skilljar's REST APIs — v1 and v2 — behind one set of
52
+ tools.
53
+
54
+ > **Status: Blocks 1–9 implemented — full 73-tool parity — not yet released.**
55
+ >
56
+ > **76 tools** over Skilljar's v2 API. Install from source until the first PyPI release:
57
+ > `pipx install git+https://github.com/CloudSecurityAlliance/csa-skilljar`
58
+ > | | |
59
+ > |---|---|
60
+ > | **Server** | `check_access` · `describe_capabilities` · `report_a_problem` |
61
+ > | **Courses** | `list_courses` · `get_course` · `create_courses` · `update_courses` |
62
+ > | **Lessons** | `list_lessons` · `get_lesson` · `create_lessons` · `update_lessons` |
63
+ > | **Quizzes** | `list_quizzes` · `get_quiz` · `create_quizzes` · `update_quizzes` · `delete_quizzes` |
64
+ > | **Questions** | `list_questions` · `get_question` · `create_questions` · `update_questions` · `delete_questions` |
65
+ > | **Question banks** | `list_question_banks` · `get_question_bank` · `create_question_banks` · `update_question_banks` · `delete_question_banks` |
66
+ > | **Bank bindings** | `list_quiz_question_bank_assignments` · `bind_quiz_question_banks` · `update_quiz_question_banks` · `unbind_quiz_question_banks` |
67
+ > | **Enrolment** | `list_enrollments` · `get_enrollment` · `update_enrollments` · `complete_enrollments` · `bulk_enroll_students` |
68
+ > | **Reporting** | `list_certificates` · `get_certificate` · `get_course_analytics` · `list_course_ratings` |
69
+ > | **Students** | `list_students` · `get_student` · `create_students` · `update_students` |
70
+ > | **Groups** | `list_groups` · `get_group` · `create_groups` · `update_groups` · `add_group_memberships` · `remove_group_memberships` |
71
+ > | **Signup fields** | `list_signup_field_values` · `get_signup_field_value` · `create_signup_field_values` · `update_signup_field_values` |
72
+ > | **Publishing** | `list_published_courses` · `get_published_course` · `publish_courses` · `update_published_courses` |
73
+ > | **Catalog** | `list_domains` · `get_domain` |
74
+ > | **Course visibility** | `list_visibility_overrides` · `add_visibility_overrides` · `remove_visibility_overrides` |
75
+ > | **Web packages** | `list_web_packages` · `get_web_package` · `create_web_packages` · `update_web_packages` |
76
+ > | **Students (destructive)** | `anonymize_student` · `deactivate_student` · `set_student_password` · `send_password_reset` — gated on `people.destructive`, which no profile but `full` grants |
77
+ > | **Groups (destructive)** | `delete_groups` — a hard delete that cascades to memberships and course visibility; gated on `groups.delete` |
78
+ > | **Publishing (public-facing)** | `unpublish_published_course` · `republish_published_course` · `delete_published_course` — gated on `publishing.write`, which `authoring` does not grant |
79
+ > | **Web packages (destructive)** | `delete_web_package` — refused while a live lesson uses the package |
80
+ > | **Credentials** | `register_oauth_client` — mints an OAuth client; off unless the `admin` profile is named |
81
+ >
82
+ > All 73 official tools are present — asserted by `tests/test_parity.py`, not claimed. The three extra tools are our own server management. See [ROADMAP.md](ROADMAP.md).
83
+
84
+ ## Start with Skilljar's official MCP server
85
+
86
+ **If you want Skilljar in an AI client, use [Skilljar's own MCP server](https://mcp.skilljar.com/mcp).**
87
+ That is the right default and we recommend it without reservation. It is first-party, hosted and
88
+ maintained by the vendor, needs nothing installed on your machine, covers the whole v2 API in 73
89
+ tools, and authenticates with OAuth and per-operation scopes. Skilljar are actively building v2
90
+ out, so it gets better on their release cadence rather than ours.
91
+
92
+ ```bash
93
+ claude mcp add skilljar --transport http https://mcp.skilljar.com/mcp
94
+ ```
95
+
96
+ Try that first. For most people it is the whole answer.
97
+
98
+ ## …but if you need more
99
+
100
+ Some things are not in the v2 API yet, so no v2 client can reach them. The v1 API is considerably
101
+ larger — 340 operations against v2's 82 — and today it is the only way to get at:
102
+
103
+ - **per-lesson learner progress** — v2 reports course-level completion only
104
+ - **webhooks** — v2 has no event notifications at all
105
+ - **asset upload** — v2 has no file upload
106
+ - **learning paths**, **instructor-led training**, and the **commerce stack**
107
+ (offers, promo codes, purchases, training credits)
108
+
109
+ `csa-skilljar` exists for that gap. It reproduces the official tool surface exactly — same tool
110
+ names, same argument names — and then adds the v1-only capabilities alongside them, so you do not
111
+ have to choose between the two APIs or run two servers.
112
+
113
+ It also runs locally over stdio, which some organisations need: your API credentials stay on your
114
+ own machine.
115
+
116
+ **We expect this project to shrink over time, and that is the intended outcome.** Skilljar has
117
+ publicly reserved OAuth scopes for webhooks, paths, assets, tags and commerce. As those endpoints
118
+ ship, the corresponding v1 support here gets retired in favour of v2 — the tool names stay the
119
+ same and callers notice nothing.
120
+
121
+ | | Official Skilljar MCP | csa-skilljar |
122
+ |---|---|---|
123
+ | APIs | v2 | v1 + v2 |
124
+ | Transport | remote HTTP | local stdio |
125
+ | Credentials | held server-side | stay on your machine |
126
+ | Auth | OAuth authorization code (browser) | OAuth client credentials + v1 API key |
127
+ | Capability control | OAuth scopes at consent | scopes **plus** per-install profiles |
128
+ | Library | — | the library is the product too |
129
+
130
+ ## Checking the state of Skilljar's v2 API
131
+
132
+ Skilljar's v2 API is actively growing, and the official MCP server tracks it closely. That is good
133
+ news for everyone — and it means the coverage map in this project has a shelf life. **Before
134
+ assuming a gap documented here is still a gap, re-check upstream.**
135
+
136
+ ```bash
137
+ # 1. The published v2 surface
138
+ curl -s https://api.skilljar.com/v2/openapi.json | jq '.paths | keys | length'
139
+
140
+ # 2. The declared scope catalogue — a leading indicator of what is coming
141
+ curl -s https://api.skilljar.com/.well-known/oauth-authorization-server \
142
+ | jq -r '.scopes_supported[]'
143
+
144
+ # 3. The official MCP server's live tool list
145
+ # Connect it, then run /mcp in Claude Code.
146
+ ```
147
+
148
+ At the time of writing, the scope catalogue advertises **88 scopes** while the published v2 spec
149
+ uses **28** — areas including webhooks, paths, assets, tags and commerce have scopes reserved but
150
+ no endpoints yet. When those endpoints ship, the v1 fallbacks this project provides for them
151
+ should be retired in favour of v2, and this note updated.
152
+
153
+ `scripts/check_upstream.py` will automate all three and report drift against the snapshots in
154
+ `specs/`. Until then, run the commands above.
155
+
156
+ ## Credentials
157
+
158
+ Two independent credentials, both optional. The server starts with either, both, or neither, and
159
+ tells you what is available.
160
+
161
+ | Variable | For | Obtain from |
162
+ |---|---|---|
163
+ | `CSA_SKILLJAR_V1_API_KEY` | the v1 API | Skilljar Dashboard — see [Skilljar's API guide](https://support.gainsight.com/Skilljar/Develop_and_Customize/API/Getting_started_with_the_Skilljar_API) |
164
+ | `CSA_SKILLJAR_V2_CLIENT_ID` / `CSA_SKILLJAR_V2_CLIENT_SECRET` | the v2 API | Skilljar Dashboard, v2 API clients |
165
+
166
+ We link Skilljar's own documentation rather than transcribing their dashboard navigation, which
167
+ we cannot keep current.
168
+
169
+ ### Connecting it to an MCP client
170
+
171
+ ```bash
172
+ # Recommended: credentials stay in .env, and the client config holds no secret.
173
+ claude mcp add csa-skilljar -- /abs/path/to/csa-skilljar/scripts/mcp-launch.sh
174
+
175
+ # Or pass them directly - note this writes the literal secret into ~/.claude.json,
176
+ # which is not gitignored, and into your shell history.
177
+ claude mcp add csa-skilljar \
178
+ -e CSA_SKILLJAR_V2_CLIENT_ID=... -e CSA_SKILLJAR_V2_CLIENT_SECRET=... \
179
+ -- /abs/path/to/csa-skilljar/.venv/bin/csa-skilljar-mcp
180
+ ```
181
+
182
+ `scripts/mcp-launch.sh` reads `CSA_SKILLJAR_*` variables from the repository's `.env`
183
+ (override with `CSA_SKILLJAR_ENV_FILE`) and execs the server. It parses the file rather
184
+ than sourcing it: `source` executes it, so a stray `echo` would print to stdout and
185
+ corrupt the JSON-RPC stream before the server ever starts.
186
+
187
+ Use an **absolute path** to the script or to `.venv/bin/csa-skilljar-mcp`. A bare
188
+ `csa-skilljar-mcp` resolves through `PATH`, which may find a different install.
189
+
190
+ Then call `check_access` first — it is built to work when nothing else does, and reports
191
+ which credentials resolved and which scopes the token carries.
192
+
193
+ **There is no login step and no browser.** The v2 credential is a machine credential: you create
194
+ an API client in the Skilljar Dashboard, put its id and secret in your MCP client's configuration,
195
+ and the server obtains its own access token on first use (`client_credentials`, ADR-003). No
196
+ redirect URI, no consent screen, no token file on disk. Skilljar's own hosted MCP server does use
197
+ an interactive flow — it is remote and acts for a browser user, which is exactly the constraint
198
+ running locally removes.
199
+
200
+ Scope the v2 client to what you actually need. The API declares a required scope on every
201
+ operation, and the sensitive ones are separable — `students:anonymize` (irreversible),
202
+ `students:deactivate`, and `students:manage-password` can all be withheld from a client used for
203
+ content authoring.
204
+
205
+ ## What it will cover
206
+
207
+ Reproduces all 73 official tools, then adds v1-only families in this order:
208
+
209
+ 1. **Learner progress** — per-lesson detail, which v2 does not report
210
+ 2. **Assets & media** — v2 has no file upload
211
+ 3. **Commerce** — offers, promo codes, purchases, training credits
212
+ 4. **Learning paths** — paths, path items, path enrolments
213
+ 5. **Events & webhooks** — subscriptions and payload previews
214
+ 6. **Instructor-led training** — sessions, instructors, registrations
215
+ 7. **Labels & tags**
216
+
217
+ Deliberately out of scope: catalog page-building, webhook *receiving*, caching, and cross-API
218
+ composite writes. Reasons are in the spec.
219
+
220
+ ## Project documentation
221
+
222
+ | File | What it answers |
223
+ |---|---|
224
+ | [Design spec](docs/superpowers/specs/2026-08-26-csa-skilljar-design.md) | Architecture, routing rule, credential model, auth error taxonomy, phasing. **Start here.** |
225
+ | [ROADMAP.md](ROADMAP.md) | The block sequence — what ships in what order, and what is parked |
226
+ | [GOALS.md](GOALS.md) | What success looks like and how we would know it failed |
227
+ | [BUSINESS-CASE.md](BUSINESS-CASE.md) | Why CSA is investing, and the honest case that this project should shrink |
228
+ | [TODO.md](TODO.md) | Index of all open work |
229
+ | [DECISIONS-ADR.md](DECISIONS-ADR.md) | Technical decisions and why the rejected alternatives lost |
230
+ | [DECISIONS-PRD.md](DECISIONS-PRD.md) | Scope, audience, and what is deliberately out |
231
+ | [SECURITY-RESOURCES.md](SECURITY-RESOURCES.md) | Exposure surface, prompt-injection risk, credential custody |
232
+ | [DATA-RESOURCES.md](DATA-RESOURCES.md) | What data this handles, and what it deliberately never stores |
233
+ | [WAITING-FOR.md](WAITING-FOR.md) | External conditions we are waiting on, each with an observable trigger |
234
+ | [FRICTION.md](FRICTION.md) | Work that is harder than it should be — including how this project works with AI |
235
+ | [RACI.md](RACI.md) | Who decides what |
236
+ | [CLAUDE.md](CLAUDE.md) | Behavioural contract for AI agents working here |
237
+
238
+ ## Development
239
+
240
+ **Always use a virtual environment.** The interpreter is pinned by `.python-version`.
241
+
242
+ ```bash
243
+ python3 -m venv .venv
244
+ .venv/bin/python -m pip install -e ".[dev]"
245
+
246
+ .venv/bin/python -m pytest -q # offline suite: no network, no credentials
247
+ .venv/bin/ruff check src tests scripts
248
+ .venv/bin/mypy
249
+
250
+ ./scripts/verify.sh # or just this: everything CI checks
251
+ ```
252
+
253
+ Commands are written `.venv/bin/...` deliberately — a bare `pytest` resolves to whatever
254
+ is on `PATH`, which is how a suite passes against the wrong dependency versions.
255
+
256
+ Contributions follow [CSA's public repo standards](https://github.com/CloudSecurityAlliance-Internal/CINO-Platform-Engineering/blob/main/PUBLIC-GITHUB-REPO-STANDARDS.md):
257
+ branch and PR for every change, required CI gates, no direct pushes to `main`.
258
+
259
+ ## Releasing
260
+
261
+ Publishing uses **PyPI Trusted Publishing** — GitHub Actions authenticates over OIDC and
262
+ proves its identity with the repository, workflow and environment it is running in.
263
+ **There is no API token anywhere**: not in the repository, not in a GitHub secret, not in
264
+ a `.pypirc`. Nothing to leak, rotate, or accidentally commit.
265
+
266
+ The identity PyPI checks is exactly this:
267
+
268
+ | Field | Value |
269
+ |---|---|
270
+ | PyPI project | `csa-skilljar` |
271
+ | Owner | `CloudSecurityAlliance` |
272
+ | Repository | `csa-skilljar` |
273
+ | Workflow | `release.yml` |
274
+ | Environment | `pypi` |
275
+
276
+ **One-time setup** (a person with the PyPI account, per `RACI.md` — credential and
277
+ publishing identity are not delegated): at
278
+ <https://pypi.org/manage/account/publishing/>, add a **pending publisher** with the five
279
+ values above. "Pending" is the form used when the project does not exist on PyPI yet; it
280
+ becomes a normal trusted publisher on first upload.
281
+
282
+ **Each release:**
283
+
284
+ ```bash
285
+ # 1. Bump the single source of truth and refresh the editable install.
286
+ # src/csa_skilljar/__init__.py __version__ = "X.Y.Z"
287
+ .venv/bin/python -m pip install -e ".[dev]"
288
+ ./scripts/verify.sh
289
+
290
+ # 2. Merge, then tag from main. The tag MUST equal the packaged version - the
291
+ # workflow refuses to publish when they disagree, rather than shipping a
292
+ # mislabelled artifact.
293
+ git tag vX.Y.Z && git push origin vX.Y.Z
294
+ gh release create vX.Y.Z --notes-from-tag
295
+ ```
296
+
297
+ Publishing the GitHub release starts `release.yml`, which reruns the tests, `pip-audit`
298
+ and `bandit`, checks the tag against the packaged version, builds, and refuses to upload
299
+ an artifact containing anything matching `.env`, `token`, `secret`, `credential`,
300
+ `analysis/` or `docs-html/`, or missing `py.typed`.
301
+
302
+ It then waits: the `pypi` environment has a **required reviewer**, so the upload does not
303
+ happen until a human approves it in the Actions run. Worth knowing that GitHub creates a
304
+ missing environment *unprotected* on first use — so `environment: pypi` in a workflow is
305
+ a claim, not a control, until the environment actually exists with rules on it.
306
+
307
+ ## Licence
308
+
309
+ [Apache-2.0](LICENSE).
310
+
311
+ ## Acknowledgements
312
+
313
+ Skilljar is a Gainsight product. This project is not affiliated with or endorsed by Skilljar or
314
+ Gainsight; it is an independent client built against their public APIs. The API snapshots in
315
+ `specs/` are fetched from Skilljar's published, publicly accessible OpenAPI documents.