okta-agent 0.1.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 (60) hide show
  1. okta_agent-0.1.0/LICENSE +21 -0
  2. okta_agent-0.1.0/MANIFEST.in +4 -0
  3. okta_agent-0.1.0/PKG-INFO +243 -0
  4. okta_agent-0.1.0/README.md +213 -0
  5. okta_agent-0.1.0/okta_agent/__init__.py +68 -0
  6. okta_agent-0.1.0/okta_agent/__main__.py +4 -0
  7. okta_agent-0.1.0/okta_agent/agent_server.py +71 -0
  8. okta_agent-0.1.0/okta_agent/api/__init__.py +1 -0
  9. okta_agent-0.1.0/okta_agent/api/api_client_apps.py +250 -0
  10. okta_agent-0.1.0/okta_agent/api/api_client_base.py +301 -0
  11. okta_agent-0.1.0/okta_agent/api/api_client_groups.py +184 -0
  12. okta_agent-0.1.0/okta_agent/api/api_client_policies.py +112 -0
  13. okta_agent-0.1.0/okta_agent/api/api_client_system.py +95 -0
  14. okta_agent-0.1.0/okta_agent/api/api_client_users.py +250 -0
  15. okta_agent-0.1.0/okta_agent/api/credentials.py +161 -0
  16. okta_agent-0.1.0/okta_agent/api/filters.py +71 -0
  17. okta_agent-0.1.0/okta_agent/api_client.py +17 -0
  18. okta_agent-0.1.0/okta_agent/auth.py +94 -0
  19. okta_agent-0.1.0/okta_agent/main_agent.json +17 -0
  20. okta_agent-0.1.0/okta_agent/mcp/__init__.py +19 -0
  21. okta_agent-0.1.0/okta_agent/mcp/common.py +67 -0
  22. okta_agent-0.1.0/okta_agent/mcp/mcp_apps.py +127 -0
  23. okta_agent-0.1.0/okta_agent/mcp/mcp_groups.py +128 -0
  24. okta_agent-0.1.0/okta_agent/mcp/mcp_policies.py +124 -0
  25. okta_agent-0.1.0/okta_agent/mcp/mcp_system.py +81 -0
  26. okta_agent-0.1.0/okta_agent/mcp/mcp_users.py +162 -0
  27. okta_agent-0.1.0/okta_agent/mcp_config.json +3 -0
  28. okta_agent-0.1.0/okta_agent/mcp_server.py +70 -0
  29. okta_agent-0.1.0/okta_agent/okta_input_models.py +145 -0
  30. okta_agent-0.1.0/okta_agent/okta_response_models.py +72 -0
  31. okta_agent-0.1.0/okta_agent.egg-info/PKG-INFO +243 -0
  32. okta_agent-0.1.0/okta_agent.egg-info/SOURCES.txt +58 -0
  33. okta_agent-0.1.0/okta_agent.egg-info/dependency_links.txt +1 -0
  34. okta_agent-0.1.0/okta_agent.egg-info/entry_points.txt +3 -0
  35. okta_agent-0.1.0/okta_agent.egg-info/requires.txt +18 -0
  36. okta_agent-0.1.0/okta_agent.egg-info/top_level.txt +6 -0
  37. okta_agent-0.1.0/pyproject.toml +56 -0
  38. okta_agent-0.1.0/requirements.txt +3 -0
  39. okta_agent-0.1.0/scripts/security_sanitizer.py +160 -0
  40. okta_agent-0.1.0/scripts/validate_a2a_agent.py +139 -0
  41. okta_agent-0.1.0/scripts/validate_agent.py +31 -0
  42. okta_agent-0.1.0/scripts/verify_api_integration.py +281 -0
  43. okta_agent-0.1.0/setup.cfg +4 -0
  44. okta_agent-0.1.0/tests/__init__.py +0 -0
  45. okta_agent-0.1.0/tests/conftest.py +93 -0
  46. okta_agent-0.1.0/tests/test_api_wrapper.py +203 -0
  47. okta_agent-0.1.0/tests/test_apps_client.py +109 -0
  48. okta_agent-0.1.0/tests/test_auth.py +71 -0
  49. okta_agent-0.1.0/tests/test_concept_parity.py +33 -0
  50. okta_agent-0.1.0/tests/test_credentials.py +176 -0
  51. okta_agent-0.1.0/tests/test_filters.py +84 -0
  52. okta_agent-0.1.0/tests/test_groups_client.py +86 -0
  53. okta_agent-0.1.0/tests/test_init_dynamics.py +19 -0
  54. okta_agent-0.1.0/tests/test_okta_mcp_validation.py +201 -0
  55. okta_agent-0.1.0/tests/test_okta_models.py +94 -0
  56. okta_agent-0.1.0/tests/test_pagination.py +67 -0
  57. okta_agent-0.1.0/tests/test_policies_client.py +67 -0
  58. okta_agent-0.1.0/tests/test_startup.py +25 -0
  59. okta_agent-0.1.0/tests/test_system_client.py +66 -0
  60. okta_agent-0.1.0/tests/test_users_client.py +126 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Advanced Agentic Coding
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include LICENSE
2
+ include README.md
3
+ include requirements.txt
4
+ recursive-include okta_agent *.py *.json
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: okta-agent
3
+ Version: 0.1.0
4
+ Summary: Okta CIAM/SSO MCP Server and Agent for Agentic AI!
5
+ Author-email: Audel Rouhi <knucklessg1@gmail.com>
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Environment :: Console
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: <3.15,>=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: agent-utilities>=0.47.0
16
+ Requires-Dist: httpx>=0.27.0
17
+ Requires-Dist: PyJWT[crypto]>=2.8.0
18
+ Provides-Extra: mcp
19
+ Requires-Dist: agent-utilities[mcp]>=0.47.0; extra == "mcp"
20
+ Provides-Extra: agent
21
+ Requires-Dist: agent-utilities[agent,logfire]>=0.47.0; extra == "agent"
22
+ Provides-Extra: all
23
+ Requires-Dist: okta-agent[agent,logfire,mcp]>=0.1.0; extra == "all"
24
+ Provides-Extra: test
25
+ Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
26
+ Requires-Dist: pytest; extra == "test"
27
+ Requires-Dist: pytest-asyncio; extra == "test"
28
+ Requires-Dist: pytest-cov; extra == "test"
29
+ Dynamic: license-file
30
+
31
+ # Okta Agent
32
+ ## CLI or API | MCP | Agent
33
+
34
+ ![PyPI - Version](https://img.shields.io/pypi/v/okta-agent)
35
+ ![MCP Server](https://badge.mcpx.dev?type=server 'MCP Server')
36
+ ![PyPI - Downloads](https://img.shields.io/pypi/dd/okta-agent)
37
+ ![GitHub Repo stars](https://img.shields.io/github/stars/Knuckles-Team/okta-agent)
38
+ ![PyPI - License](https://img.shields.io/pypi/l/okta-agent)
39
+ ![GitHub last commit (by committer)](https://img.shields.io/github/last-commit/Knuckles-Team/okta-agent)
40
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/okta-agent)
41
+
42
+ *Version: 0.1.0*
43
+
44
+ > **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
45
+ > server live on the docs site:
46
+ > <https://knuckles-team.github.io/okta-agent/>
47
+
48
+ ## Table of Contents
49
+
50
+ - [Overview](#overview)
51
+ - [Architecture](#architecture)
52
+ - [Installation](#installation)
53
+ - [Configure](#configure)
54
+ - [Environment Variables](#environment-variables)
55
+ - [Quick Start](#quick-start)
56
+ - [Run](#run)
57
+ - [MCP Tools](#mcp-tools)
58
+ - [Deployment](#deployment)
59
+ - [Keycloak Parity](#keycloak-parity)
60
+ - [Development](#development)
61
+
62
+ ## Overview
63
+
64
+ Enterprise CIAM/SSO connector for the agent fleet: a production-grade MCP
65
+ server and Pydantic AI agent over the **Okta Management API** — users, groups,
66
+ applications, policies, and the system log. Complements `keycloak-agent`
67
+ (open-source IdP) with the commercial-IdP side of the house, mirroring its
68
+ verb taxonomy so agents can switch IdPs with familiar verbs.
69
+
70
+ - Raw `httpx` client — no Okta SDK; every method documents the endpoint it calls.
71
+ - Two auth modes: **SSWS API token** and **OAuth2 private-key-JWT** (org
72
+ authorization server, Okta API scopes).
73
+ - Rate-limit aware: every response envelope carries the latest
74
+ `X-Rate-Limit-*` snapshot; HTTP 429 triggers capped automatic backoff.
75
+ - Cursor pagination via `Link: rel="next"` headers (Okta's `after` cursor),
76
+ with hard item caps and resumable `next_cursor`s.
77
+ - Safety: destructive operations (deactivate / delete / clear sessions /
78
+ password ops) are blocked unless explicitly allowed; credential material is
79
+ redacted from logs and error envelopes.
80
+
81
+ ## Architecture
82
+
83
+ ```mermaid
84
+ graph TD
85
+ User([User/A2A]) --> Server[A2A Server / okta-agent]
86
+ Server --> Agent[Pydantic AI Agent]
87
+ Agent --> MCP[MCP Server / okta-mcp]
88
+ MCP --> Client[Api facade / httpx]
89
+ Client --> ExternalAPI([Okta Management API])
90
+ ```
91
+
92
+ ## Installation
93
+
94
+ ```bash
95
+ pip install okta-agent # core API client
96
+ pip install okta-agent[mcp] # + MCP server
97
+ pip install okta-agent[agent] # + Pydantic AI agent server
98
+ ```
99
+
100
+ ## Configure
101
+
102
+ ```bash
103
+ export OKTA_ORG_URL="https://acme.okta.com"
104
+
105
+ # Mode 1 — SSWS API token (takes precedence)
106
+ export OKTA_API_TOKEN="<api token>"
107
+
108
+ # Mode 2 — OAuth2 private-key-JWT (service app, Okta API scopes)
109
+ export OKTA_CLIENT_ID="0oa..."
110
+ export OKTA_PRIVATE_KEY_FILE="/path/to/key.pem" # or OKTA_PRIVATE_KEY inline
111
+ export OKTA_SCOPES="okta.users.read okta.groups.manage"
112
+ ```
113
+
114
+ See `.env.example` for every knob (`OKTA_SSL_VERIFY`, `OKTA_MAX_RETRIES`,
115
+ `OKTA_BACKOFF_CAP_SECONDS`, `OKTA_ALLOW_DESTRUCTIVE`, per-tool switches).
116
+
117
+ ## Environment Variables
118
+
119
+ | Variable | Default | Purpose |
120
+ |----------|---------|---------|
121
+ | `OKTA_ORG_URL` | — | Okta org URL, no trailing slash (`OKTA_AGENT_BASE_URL` accepted as fallback) |
122
+ | `OKTA_API_TOKEN` | — | SSWS API token (auth mode 1, takes precedence) |
123
+ | `OKTA_CLIENT_ID` | — | Service-app client id (private-key-JWT) |
124
+ | `OKTA_PRIVATE_KEY` / `OKTA_PRIVATE_KEY_FILE` | — | PEM private key inline or path |
125
+ | `OKTA_KEY_ID` | — | Optional JWKS key id for the client assertion |
126
+ | `OKTA_SCOPES` | read scopes | Space-separated Okta API scopes |
127
+ | `OKTA_SSL_VERIFY` | `True` | TLS verification toward the org |
128
+ | `OKTA_MAX_RETRIES` | `2` | 429 retry attempts |
129
+ | `OKTA_BACKOFF_CAP_SECONDS` | `60` | 429 backoff cap |
130
+ | `OKTA_ALLOW_DESTRUCTIVE` | `False` | Org-wide default for destructive tool actions |
131
+ | `HOST` / `PORT` / `TRANSPORT` | `0.0.0.0` / `8000` / `stdio` | MCP server bind + transport |
132
+ | `USERSTOOL` / `GROUPSTOOL` / `APPSTOOL` / `POLICIESTOOL` / `SYSTEMTOOL` | `True` | Per-domain tool toggles |
133
+ | `ENABLE_OTEL` / `OTEL_EXPORTER_OTLP_*` | — | Telemetry (OTEL / Langfuse) |
134
+ | `EUNOMIA_TYPE` / `EUNOMIA_POLICY_FILE` / `EUNOMIA_REMOTE_URL` | `none` | MCP authorization middleware |
135
+ | `AUTH_TYPE` | `none` | MCP server auth mode (Docker) |
136
+ | `DEFAULT_AGENT_NAME` / `AGENT_DESCRIPTION` / `AGENT_SYSTEM_PROMPT` | identity files | A2A agent identity overrides |
137
+
138
+ ## Quick Start
139
+
140
+ ```python
141
+ from okta_agent import Api
142
+ from okta_agent.api.credentials import SswsToken
143
+ from okta_agent.okta_input_models import SearchInput, FilterCondition
144
+
145
+ api = Api(org_url="https://acme.okta.com", credential=SswsToken("<token>"))
146
+
147
+ active = api.search_users(
148
+ conditions=[{"field": "status", "op": "eq", "value": "ACTIVE"}],
149
+ )
150
+ print(active["data"], active["rate_limit"])
151
+
152
+ # Or build typed tool params for the MCP surface:
153
+ params = SearchInput(
154
+ conditions=[FilterCondition(field="status", op="eq", value="ACTIVE")]
155
+ ).model_dump_json(exclude_none=True)
156
+ ```
157
+
158
+ ## Run
159
+
160
+ ```bash
161
+ okta-mcp # stdio MCP server
162
+ okta-mcp --transport streamable-http --host 0.0.0.0 --port 8000
163
+ okta-agent # A2A agent server
164
+ ```
165
+
166
+ ## MCP Tools
167
+
168
+ Five consolidated, action-routed tools. Each takes `action`, `params_json`,
169
+ and (where applicable) `allow_destructive`.
170
+
171
+ | Tool | Actions | Destructive (gated) |
172
+ |------|---------|---------------------|
173
+ | `okta_users` | list, search, get, create, update, activate, deactivate, suspend, unsuspend, unlock, expire_password, reset_password, list_groups, list_apps, list_factors, clear_sessions | deactivate, suspend, expire_password, reset_password, clear_sessions |
174
+ | `okta_groups` | list, search, get, create, update, delete, list_members, add_member, remove_member, list_rules, create_rule, activate_rule, deactivate_rule | delete, remove_member, deactivate_rule |
175
+ | `okta_apps` | list, get, create (oidc/saml/bookmark templates), update, activate, deactivate, list_users, assign_user, unassign_user, list_groups, assign_group, unassign_group | deactivate, unassign_user, unassign_group |
176
+ | `okta_policies` | list (okta_sign_on/password/mfa_enroll/access_policy), get, list_rules, activate, deactivate, activate_rule, deactivate_rule — *policy/rule create+update intentionally out of scope in this release* | deactivate, deactivate_rule |
177
+ | `okta_system` | org, list_authorization_servers, logs (since/until/filter/q, capped at 1000 events, resumable cursor), list_authenticators, list_factors, list_zones | — |
178
+
179
+ ### Examples
180
+
181
+ ```json
182
+ {"action": "search", "params_json": "{\"conditions\": [{\"field\": \"status\", \"op\": \"eq\", \"value\": \"ACTIVE\"}, {\"field\": \"profile.department\", \"op\": \"eq\", \"value\": \"Engineering\"}]}"}
183
+ ```
184
+
185
+ ```json
186
+ {"action": "create", "params_json": "{\"template\": \"oidc\", \"label\": \"My App\", \"settings\": {\"redirect_uris\": [\"https://app.example.com/cb\"]}}"}
187
+ ```
188
+
189
+ ```json
190
+ {"action": "deactivate", "params_json": "{\"user_id\": \"00u1\"}", "allow_destructive": true}
191
+ ```
192
+
193
+ Every successful response is an envelope:
194
+
195
+ ```json
196
+ {"data": [...], "rate_limit": {"limit": 600, "remaining": 599, "reset": 1700000000}, "count": 5, "truncated": false, "next_cursor": null}
197
+ ```
198
+
199
+ Errors map Okta's envelope: `{"error": {"status", "error_code", "error_summary", "error_id", "error_causes", "rate_limit"}}`.
200
+
201
+ ## Deployment
202
+
203
+ ```bash
204
+ # MCP server only (port 8000, streamable-http, /health)
205
+ docker compose -f docker/mcp.compose.yml up -d
206
+
207
+ # MCP server + A2A agent server (agent on port 9021, AG-UI web interface)
208
+ docker compose -f docker/agent.compose.yml up -d
209
+ ```
210
+
211
+ The A2A agent server (`okta-agent` console script, `agent_server.py`) reads
212
+ `MCP_URL`, `PROVIDER`, and `MODEL_ID` from the environment. Prebuilt image:
213
+ `knucklessg1/okta-agent:latest`. See [docs/deployment.md](docs/deployment.md)
214
+ for transports, reverse proxy, and DNS guidance.
215
+
216
+ ## Keycloak parity
217
+
218
+ Verbs intentionally mirror `keycloak-agent` where the concepts overlap, so an
219
+ agent fluent in one IdP connector can drive the other:
220
+
221
+ | Concept | keycloak-agent | okta-agent |
222
+ |---------|----------------|------------|
223
+ | Users CRUD | `keycloak_agent_users` list/get/create/update | `okta_users` list/get/create/update |
224
+ | Password reset | `reset_password` | `reset_password` (plus expire_password) |
225
+ | User's groups | `list_users_by_user_id_groups` | `list_groups` |
226
+ | Groups CRUD + members | `keycloak_agent_groups` | `okta_groups` (+ Okta group rules) |
227
+ | OAuth/SAML clients | `keycloak_agent_clients` | `okta_apps` (Keycloak "clients" = Okta "apps") |
228
+ | Auth policies/flows | `keycloak_agent_authentication` | `okta_policies` (read + lifecycle) |
229
+ | Server/org info & events | `keycloak_agent_info` / attack detection | `okta_system` (org + system log) |
230
+
231
+ Okta-only surface: lifecycle states (suspend/unlock), group rules, app
232
+ assignment profiles, network zones, Okta API scopes via private-key-JWT.
233
+
234
+ ## Development
235
+
236
+ ```bash
237
+ pip install -e .[mcp,test]
238
+ pytest -v
239
+ pre-commit run --all-files
240
+ ```
241
+
242
+ API references are cited in every client docstring
243
+ (https://developer.okta.com/docs/api/).
@@ -0,0 +1,213 @@
1
+ # Okta Agent
2
+ ## CLI or API | MCP | Agent
3
+
4
+ ![PyPI - Version](https://img.shields.io/pypi/v/okta-agent)
5
+ ![MCP Server](https://badge.mcpx.dev?type=server 'MCP Server')
6
+ ![PyPI - Downloads](https://img.shields.io/pypi/dd/okta-agent)
7
+ ![GitHub Repo stars](https://img.shields.io/github/stars/Knuckles-Team/okta-agent)
8
+ ![PyPI - License](https://img.shields.io/pypi/l/okta-agent)
9
+ ![GitHub last commit (by committer)](https://img.shields.io/github/last-commit/Knuckles-Team/okta-agent)
10
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/okta-agent)
11
+
12
+ *Version: 0.1.0*
13
+
14
+ > **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
15
+ > server live on the docs site:
16
+ > <https://knuckles-team.github.io/okta-agent/>
17
+
18
+ ## Table of Contents
19
+
20
+ - [Overview](#overview)
21
+ - [Architecture](#architecture)
22
+ - [Installation](#installation)
23
+ - [Configure](#configure)
24
+ - [Environment Variables](#environment-variables)
25
+ - [Quick Start](#quick-start)
26
+ - [Run](#run)
27
+ - [MCP Tools](#mcp-tools)
28
+ - [Deployment](#deployment)
29
+ - [Keycloak Parity](#keycloak-parity)
30
+ - [Development](#development)
31
+
32
+ ## Overview
33
+
34
+ Enterprise CIAM/SSO connector for the agent fleet: a production-grade MCP
35
+ server and Pydantic AI agent over the **Okta Management API** — users, groups,
36
+ applications, policies, and the system log. Complements `keycloak-agent`
37
+ (open-source IdP) with the commercial-IdP side of the house, mirroring its
38
+ verb taxonomy so agents can switch IdPs with familiar verbs.
39
+
40
+ - Raw `httpx` client — no Okta SDK; every method documents the endpoint it calls.
41
+ - Two auth modes: **SSWS API token** and **OAuth2 private-key-JWT** (org
42
+ authorization server, Okta API scopes).
43
+ - Rate-limit aware: every response envelope carries the latest
44
+ `X-Rate-Limit-*` snapshot; HTTP 429 triggers capped automatic backoff.
45
+ - Cursor pagination via `Link: rel="next"` headers (Okta's `after` cursor),
46
+ with hard item caps and resumable `next_cursor`s.
47
+ - Safety: destructive operations (deactivate / delete / clear sessions /
48
+ password ops) are blocked unless explicitly allowed; credential material is
49
+ redacted from logs and error envelopes.
50
+
51
+ ## Architecture
52
+
53
+ ```mermaid
54
+ graph TD
55
+ User([User/A2A]) --> Server[A2A Server / okta-agent]
56
+ Server --> Agent[Pydantic AI Agent]
57
+ Agent --> MCP[MCP Server / okta-mcp]
58
+ MCP --> Client[Api facade / httpx]
59
+ Client --> ExternalAPI([Okta Management API])
60
+ ```
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ pip install okta-agent # core API client
66
+ pip install okta-agent[mcp] # + MCP server
67
+ pip install okta-agent[agent] # + Pydantic AI agent server
68
+ ```
69
+
70
+ ## Configure
71
+
72
+ ```bash
73
+ export OKTA_ORG_URL="https://acme.okta.com"
74
+
75
+ # Mode 1 — SSWS API token (takes precedence)
76
+ export OKTA_API_TOKEN="<api token>"
77
+
78
+ # Mode 2 — OAuth2 private-key-JWT (service app, Okta API scopes)
79
+ export OKTA_CLIENT_ID="0oa..."
80
+ export OKTA_PRIVATE_KEY_FILE="/path/to/key.pem" # or OKTA_PRIVATE_KEY inline
81
+ export OKTA_SCOPES="okta.users.read okta.groups.manage"
82
+ ```
83
+
84
+ See `.env.example` for every knob (`OKTA_SSL_VERIFY`, `OKTA_MAX_RETRIES`,
85
+ `OKTA_BACKOFF_CAP_SECONDS`, `OKTA_ALLOW_DESTRUCTIVE`, per-tool switches).
86
+
87
+ ## Environment Variables
88
+
89
+ | Variable | Default | Purpose |
90
+ |----------|---------|---------|
91
+ | `OKTA_ORG_URL` | — | Okta org URL, no trailing slash (`OKTA_AGENT_BASE_URL` accepted as fallback) |
92
+ | `OKTA_API_TOKEN` | — | SSWS API token (auth mode 1, takes precedence) |
93
+ | `OKTA_CLIENT_ID` | — | Service-app client id (private-key-JWT) |
94
+ | `OKTA_PRIVATE_KEY` / `OKTA_PRIVATE_KEY_FILE` | — | PEM private key inline or path |
95
+ | `OKTA_KEY_ID` | — | Optional JWKS key id for the client assertion |
96
+ | `OKTA_SCOPES` | read scopes | Space-separated Okta API scopes |
97
+ | `OKTA_SSL_VERIFY` | `True` | TLS verification toward the org |
98
+ | `OKTA_MAX_RETRIES` | `2` | 429 retry attempts |
99
+ | `OKTA_BACKOFF_CAP_SECONDS` | `60` | 429 backoff cap |
100
+ | `OKTA_ALLOW_DESTRUCTIVE` | `False` | Org-wide default for destructive tool actions |
101
+ | `HOST` / `PORT` / `TRANSPORT` | `0.0.0.0` / `8000` / `stdio` | MCP server bind + transport |
102
+ | `USERSTOOL` / `GROUPSTOOL` / `APPSTOOL` / `POLICIESTOOL` / `SYSTEMTOOL` | `True` | Per-domain tool toggles |
103
+ | `ENABLE_OTEL` / `OTEL_EXPORTER_OTLP_*` | — | Telemetry (OTEL / Langfuse) |
104
+ | `EUNOMIA_TYPE` / `EUNOMIA_POLICY_FILE` / `EUNOMIA_REMOTE_URL` | `none` | MCP authorization middleware |
105
+ | `AUTH_TYPE` | `none` | MCP server auth mode (Docker) |
106
+ | `DEFAULT_AGENT_NAME` / `AGENT_DESCRIPTION` / `AGENT_SYSTEM_PROMPT` | identity files | A2A agent identity overrides |
107
+
108
+ ## Quick Start
109
+
110
+ ```python
111
+ from okta_agent import Api
112
+ from okta_agent.api.credentials import SswsToken
113
+ from okta_agent.okta_input_models import SearchInput, FilterCondition
114
+
115
+ api = Api(org_url="https://acme.okta.com", credential=SswsToken("<token>"))
116
+
117
+ active = api.search_users(
118
+ conditions=[{"field": "status", "op": "eq", "value": "ACTIVE"}],
119
+ )
120
+ print(active["data"], active["rate_limit"])
121
+
122
+ # Or build typed tool params for the MCP surface:
123
+ params = SearchInput(
124
+ conditions=[FilterCondition(field="status", op="eq", value="ACTIVE")]
125
+ ).model_dump_json(exclude_none=True)
126
+ ```
127
+
128
+ ## Run
129
+
130
+ ```bash
131
+ okta-mcp # stdio MCP server
132
+ okta-mcp --transport streamable-http --host 0.0.0.0 --port 8000
133
+ okta-agent # A2A agent server
134
+ ```
135
+
136
+ ## MCP Tools
137
+
138
+ Five consolidated, action-routed tools. Each takes `action`, `params_json`,
139
+ and (where applicable) `allow_destructive`.
140
+
141
+ | Tool | Actions | Destructive (gated) |
142
+ |------|---------|---------------------|
143
+ | `okta_users` | list, search, get, create, update, activate, deactivate, suspend, unsuspend, unlock, expire_password, reset_password, list_groups, list_apps, list_factors, clear_sessions | deactivate, suspend, expire_password, reset_password, clear_sessions |
144
+ | `okta_groups` | list, search, get, create, update, delete, list_members, add_member, remove_member, list_rules, create_rule, activate_rule, deactivate_rule | delete, remove_member, deactivate_rule |
145
+ | `okta_apps` | list, get, create (oidc/saml/bookmark templates), update, activate, deactivate, list_users, assign_user, unassign_user, list_groups, assign_group, unassign_group | deactivate, unassign_user, unassign_group |
146
+ | `okta_policies` | list (okta_sign_on/password/mfa_enroll/access_policy), get, list_rules, activate, deactivate, activate_rule, deactivate_rule — *policy/rule create+update intentionally out of scope in this release* | deactivate, deactivate_rule |
147
+ | `okta_system` | org, list_authorization_servers, logs (since/until/filter/q, capped at 1000 events, resumable cursor), list_authenticators, list_factors, list_zones | — |
148
+
149
+ ### Examples
150
+
151
+ ```json
152
+ {"action": "search", "params_json": "{\"conditions\": [{\"field\": \"status\", \"op\": \"eq\", \"value\": \"ACTIVE\"}, {\"field\": \"profile.department\", \"op\": \"eq\", \"value\": \"Engineering\"}]}"}
153
+ ```
154
+
155
+ ```json
156
+ {"action": "create", "params_json": "{\"template\": \"oidc\", \"label\": \"My App\", \"settings\": {\"redirect_uris\": [\"https://app.example.com/cb\"]}}"}
157
+ ```
158
+
159
+ ```json
160
+ {"action": "deactivate", "params_json": "{\"user_id\": \"00u1\"}", "allow_destructive": true}
161
+ ```
162
+
163
+ Every successful response is an envelope:
164
+
165
+ ```json
166
+ {"data": [...], "rate_limit": {"limit": 600, "remaining": 599, "reset": 1700000000}, "count": 5, "truncated": false, "next_cursor": null}
167
+ ```
168
+
169
+ Errors map Okta's envelope: `{"error": {"status", "error_code", "error_summary", "error_id", "error_causes", "rate_limit"}}`.
170
+
171
+ ## Deployment
172
+
173
+ ```bash
174
+ # MCP server only (port 8000, streamable-http, /health)
175
+ docker compose -f docker/mcp.compose.yml up -d
176
+
177
+ # MCP server + A2A agent server (agent on port 9021, AG-UI web interface)
178
+ docker compose -f docker/agent.compose.yml up -d
179
+ ```
180
+
181
+ The A2A agent server (`okta-agent` console script, `agent_server.py`) reads
182
+ `MCP_URL`, `PROVIDER`, and `MODEL_ID` from the environment. Prebuilt image:
183
+ `knucklessg1/okta-agent:latest`. See [docs/deployment.md](docs/deployment.md)
184
+ for transports, reverse proxy, and DNS guidance.
185
+
186
+ ## Keycloak parity
187
+
188
+ Verbs intentionally mirror `keycloak-agent` where the concepts overlap, so an
189
+ agent fluent in one IdP connector can drive the other:
190
+
191
+ | Concept | keycloak-agent | okta-agent |
192
+ |---------|----------------|------------|
193
+ | Users CRUD | `keycloak_agent_users` list/get/create/update | `okta_users` list/get/create/update |
194
+ | Password reset | `reset_password` | `reset_password` (plus expire_password) |
195
+ | User's groups | `list_users_by_user_id_groups` | `list_groups` |
196
+ | Groups CRUD + members | `keycloak_agent_groups` | `okta_groups` (+ Okta group rules) |
197
+ | OAuth/SAML clients | `keycloak_agent_clients` | `okta_apps` (Keycloak "clients" = Okta "apps") |
198
+ | Auth policies/flows | `keycloak_agent_authentication` | `okta_policies` (read + lifecycle) |
199
+ | Server/org info & events | `keycloak_agent_info` / attack detection | `okta_system` (org + system log) |
200
+
201
+ Okta-only surface: lifecycle states (suspend/unlock), group rules, app
202
+ assignment profiles, network zones, Okta API scopes via private-key-JWT.
203
+
204
+ ## Development
205
+
206
+ ```bash
207
+ pip install -e .[mcp,test]
208
+ pytest -v
209
+ pre-commit run --all-files
210
+ ```
211
+
212
+ API references are cited in every client docstring
213
+ (https://developer.okta.com/docs/api/).
@@ -0,0 +1,68 @@
1
+ """CONCEPT:ECO-4.0 okta-agent: Okta Management API + MCP Server + A2A Server."""
2
+
3
+ import importlib
4
+ import inspect
5
+ from typing import Any
6
+
7
+ __version__ = "0.1.0"
8
+ __all__: list[str] = []
9
+
10
+ CORE_MODULES = [
11
+ "okta_agent.okta_input_models",
12
+ "okta_agent.okta_response_models",
13
+ "okta_agent.api_client",
14
+ ]
15
+ OPTIONAL_MODULES = {
16
+ "okta_agent.agent_server": "agent",
17
+ "okta_agent.mcp_server": "mcp",
18
+ }
19
+
20
+
21
+ def _expose_members(module):
22
+ for name, obj in inspect.getmembers(module):
23
+ if (inspect.isclass(obj) or inspect.isfunction(obj)) and not name.startswith(
24
+ "_"
25
+ ):
26
+ globals()[name] = obj
27
+ if name not in __all__:
28
+ __all__.append(name)
29
+
30
+
31
+ for module_name in CORE_MODULES:
32
+ module = importlib.import_module(module_name)
33
+ _expose_members(module)
34
+
35
+ _loaded_optional_modules: dict[str, Any] = {}
36
+
37
+
38
+ def _import_module_safely(module_name: str):
39
+ try:
40
+ return importlib.import_module(module_name)
41
+ except ImportError:
42
+ return None
43
+
44
+
45
+ def __getattr__(name: str) -> Any:
46
+ if name == "_MCP_AVAILABLE":
47
+ mcp_key = next((k for k in OPTIONAL_MODULES if "mcp_server" in k), None)
48
+ return _import_module_safely(mcp_key) is not None if mcp_key else False
49
+ if name == "_AGENT_AVAILABLE":
50
+ agent_key = next((k for k in OPTIONAL_MODULES if "agent_server" in k), None)
51
+ return _import_module_safely(agent_key) is not None if agent_key else False
52
+
53
+ for module_name in OPTIONAL_MODULES:
54
+ if module_name not in _loaded_optional_modules:
55
+ module = _import_module_safely(module_name)
56
+ if module is not None:
57
+ _loaded_optional_modules[module_name] = module
58
+ _expose_members(module)
59
+
60
+ module = _loaded_optional_modules.get(module_name)
61
+ if module is not None and hasattr(module, name):
62
+ return getattr(module, name)
63
+
64
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
65
+
66
+
67
+ def __dir__() -> list[str]:
68
+ return sorted(list(globals().keys()) + __all__)
@@ -0,0 +1,4 @@
1
+ from okta_agent.agent_server import agent_server
2
+
3
+ if __name__ == "__main__":
4
+ agent_server()
@@ -0,0 +1,71 @@
1
+ """Pydantic AI agent server entry point for the Okta connector."""
2
+
3
+ import logging
4
+ import os
5
+ import sys
6
+ import warnings
7
+
8
+ __version__ = "0.1.0"
9
+
10
+ logging.basicConfig(
11
+ level=logging.INFO,
12
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
13
+ )
14
+ logger = logging.getLogger(__name__)
15
+
16
+ DEFAULT_AGENT_NAME = None
17
+ DEFAULT_AGENT_DESCRIPTION = None
18
+ DEFAULT_AGENT_SYSTEM_PROMPT = None
19
+
20
+
21
+ def agent_server():
22
+ """Start the graph-based Pydantic AI agent server."""
23
+ from agent_utilities import (
24
+ build_system_prompt_from_workspace,
25
+ create_agent_parser,
26
+ create_agent_server,
27
+ initialize_workspace,
28
+ load_identity,
29
+ )
30
+
31
+ global DEFAULT_AGENT_NAME, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_SYSTEM_PROMPT
32
+ initialize_workspace()
33
+ meta = load_identity()
34
+ DEFAULT_AGENT_NAME = os.getenv("DEFAULT_AGENT_NAME", meta.get("name", "Okta Agent"))
35
+ DEFAULT_AGENT_DESCRIPTION = os.getenv(
36
+ "AGENT_DESCRIPTION",
37
+ meta.get("description", "AI agent for Okta CIAM/SSO operations."),
38
+ )
39
+ DEFAULT_AGENT_SYSTEM_PROMPT = os.getenv(
40
+ "AGENT_SYSTEM_PROMPT",
41
+ meta.get("content") or build_system_prompt_from_workspace(),
42
+ )
43
+
44
+ warnings.filterwarnings("ignore", message=".*urllib3.*")
45
+ warnings.filterwarnings("ignore", category=DeprecationWarning, module="fastmcp")
46
+
47
+ print(f"{DEFAULT_AGENT_NAME} v{__version__}", file=sys.stderr)
48
+ parser = create_agent_parser()
49
+ args = parser.parse_args()
50
+
51
+ create_agent_server(
52
+ mcp_url=args.mcp_url,
53
+ mcp_config=args.mcp_config or "mcp_config.json",
54
+ host=args.host,
55
+ port=args.port,
56
+ provider=args.provider,
57
+ model_id=args.model_id,
58
+ router_model=args.model_id,
59
+ agent_model=args.model_id,
60
+ base_url=args.base_url,
61
+ api_key=args.api_key,
62
+ custom_skills_directory=args.custom_skills_directory,
63
+ enable_web_ui=args.web,
64
+ enable_otel=args.otel,
65
+ otel_endpoint=args.otel_endpoint,
66
+ otel_headers=args.otel_headers,
67
+ otel_public_key=args.otel_public_key,
68
+ otel_secret_key=args.otel_secret_key,
69
+ otel_protocol=args.otel_protocol,
70
+ debug=args.debug,
71
+ )
@@ -0,0 +1 @@
1
+ """Okta Management API client modules (raw httpx, no Okta SDK)."""