From 7a5c5837feafcb6424abf420149791f2108c7bb2 Mon Sep 17 00:00:00 2001 From: bhuvesku Date: Tue, 17 Mar 2026 19:27:08 +0800 Subject: [PATCH 01/11] feat(jms): added OCI JMS MCP server, models, and e2e tests Added the new `oci-jms-mcp-server` package with FastMCP server bootstrap, typed JMS response models, and 9 read-only JMS tools for fleets, plugins, installation sites, fleet configuration, and inventory/usage summaries. Also added unit tests, JMS-specific e2e mocks and feature coverage, package metadata, `uv` workflow files, and README/tool documentation. --- src/oci-jms-mcp-server/AGENT.md | 256 +++ src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md | 560 +++++++ .../JMS_TOOL_TEST_REPORT.md | 756 +++++++++ .../JMS_TOOL_TEST_RESULTS.json | 641 ++++++++ src/oci-jms-mcp-server/README.md | 183 +++ .../oracle/oci_jms_mcp_server/__init__.py | 8 + .../oracle/oci_jms_mcp_server/models.py | 556 +++++++ .../oracle/oci_jms_mcp_server/server.py | 627 ++++++++ .../tests/test_jms_models.py | 143 ++ .../tests/test_jms_tools.py | 765 +++++++++ .../oracle/oci_jms_mcp_server/util.py | 72 + src/oci-jms-mcp-server/pyproject.toml | 55 + src/oci-jms-mcp-server/uv.lock | 1391 +++++++++++++++++ tests/e2e/features/mcphost-jms.json | 29 + tests/e2e/features/mocks/services/jms_data.py | 152 ++ .../e2e/features/mocks/services/jms_routes.py | 189 +++ tests/e2e/features/oci-jms-mcp-server.feature | 40 + .../steps/oci-jms-mcp-server-steps.py | 103 ++ 18 files changed, 6526 insertions(+) create mode 100644 src/oci-jms-mcp-server/AGENT.md create mode 100644 src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md create mode 100644 src/oci-jms-mcp-server/JMS_TOOL_TEST_REPORT.md create mode 100644 src/oci-jms-mcp-server/JMS_TOOL_TEST_RESULTS.json create mode 100644 src/oci-jms-mcp-server/README.md create mode 100644 src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/__init__.py create mode 100644 src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py create mode 100644 src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py create mode 100644 src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py create mode 100644 src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py create mode 100644 src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/util.py create mode 100644 src/oci-jms-mcp-server/pyproject.toml create mode 100644 src/oci-jms-mcp-server/uv.lock create mode 100644 tests/e2e/features/mcphost-jms.json create mode 100644 tests/e2e/features/mocks/services/jms_data.py create mode 100644 tests/e2e/features/mocks/services/jms_routes.py create mode 100644 tests/e2e/features/oci-jms-mcp-server.feature create mode 100644 tests/e2e/features/steps/oci-jms-mcp-server-steps.py diff --git a/src/oci-jms-mcp-server/AGENT.md b/src/oci-jms-mcp-server/AGENT.md new file mode 100644 index 00000000..aef2b98b --- /dev/null +++ b/src/oci-jms-mcp-server/AGENT.md @@ -0,0 +1,256 @@ +# OCI JMS MCP Server Implementation Guide + +Implement `src/oci-jms-mcp-server` as a standard, service-specific OCI Python MCP server. + +Do not invent a new architecture for JMS. Match the structure and coding style already used by the mature Python OCI MCP servers in this repository. + +## Goal + +Build `oci-jms-mcp-server` as a read-only v1 focused on fleet inventory and discovery in OCI Java Management Service (JMS). + +The implementation must follow the same patterns used by: + +- `src/oci-compute-mcp-server` +- `src/oci-networking-mcp-server` +- `src/oci-monitoring-mcp-server` +- `src/oci-usage-mcp-server` + +Use those servers as the source of truth for packaging, client bootstrap, tool layout, model mapping, tests, and README style. + +## Required Files + +Create and maintain this exact package-local file set: + +- `src/oci-jms-mcp-server/pyproject.toml` +- `src/oci-jms-mcp-server/README.md` +- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/__init__.py` +- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py` +- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py` +- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py` +- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py` + +Do not add repo-wide shared abstractions just to support JMS. + +## Package Structure Pattern + +Follow the same package envelope used by the other Python OCI servers: + +1. `pyproject.toml` + - Python package name: `oracle.oci-jms-mcp-server` + - entrypoint: `oracle.oci-jms-mcp-server = "oracle.oci_jms_mcp_server.server:main"` + - use `hatchling` + - depend on `fastmcp`, `oci`, and `pydantic` + - include the usual dev dependencies for `pytest`, `pytest-asyncio`, and `pytest-cov` + +2. `README.md` + - short overview + - stdio run command + - HTTP transport run command using `ORACLE_MCP_HOST` and `ORACLE_MCP_PORT` + - a tool table listing the JMS tools implemented + - the same OCI credential/safety note used in peer servers + +3. `__init__.py` + - preserve the standard Oracle copyright header + - define `__project__` and `__version__` + +## Server Implementation Pattern + +`server.py` must look and behave like the service-specific servers, not like `oci-cloud-mcp-server`. + +### Required bootstrap + +- Add the standard Oracle license header. +- Import `__project__` and `__version__` from the package. +- Create `logger = Logger(__name__, level="INFO")`. +- Create `mcp = FastMCP(name=__project__)`. + +### Required client helper + +Implement `get_jms_client()` using the same pattern as compute, networking, monitoring, usage, registry, and resource-search: + +1. load OCI config with: + - `file_location=os.getenv("OCI_CONFIG_FILE", oci.config.DEFAULT_LOCATION)` + - `profile_name=os.getenv("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE)` +2. derive the user agent name from `__project__` +3. set `config["additional_user_agent"] = f"{user_agent_name}/{__version__}"` +4. load the private key from `config["key_file"]` +5. read the security token from `config["security_token_file"]` +6. create `oci.auth.signers.SecurityTokenSigner` +7. return `oci.jms.JavaManagementServiceClient(config, signer=signer)` + +Do not replace this with a generic helper module or a dynamic client loader. + +### Required `main()` + +Implement: + +- stdio mode by default +- HTTP mode only when both `ORACLE_MCP_HOST` and `ORACLE_MCP_PORT` are set +- `mcp.run(transport="http", host=host, port=int(port))` when HTTP is enabled +- `mcp.run()` otherwise + +Keep the entrypoint in `server.py`. Do not create `main.py`. + +## Tool Design Pattern + +Use hand-written `@mcp.tool` functions exactly like the other service servers. + +### Tool rules + +- Each tool must have a clear description. +- Use `pydantic.Field` metadata for required and optional parameters where it adds clarity. +- Use `Literal` for constrained enum-like values when practical. +- Return typed Pydantic models, not raw OCI SDK objects. +- Catch exceptions only for lightweight logging and then re-raise. +- Paginate list-style APIs manually using `has_next_page` and `next_page`. +- Stop pagination when the server-provided page stream ends or the requested `limit` has been satisfied. + +### V1 JMS tool set + +Implement this exact read-only tool set first: + +- `list_fleets` +- `get_fleet` +- `list_jms_plugins` +- `get_jms_plugin` +- `list_installation_sites` +- `get_fleet_agent_configuration` +- `get_fleet_advanced_feature_configuration` +- `summarize_resource_inventory` +- `summarize_managed_instance_usage` + +Use the OCI SDK call shapes that are already available in `oci.jms.JavaManagementServiceClient`. + +### Scope boundaries for v1 + +Do not add these in v1: + +- create/update/delete fleet operations +- create/update/delete JMS plugin operations +- analysis request submission APIs +- DRS file mutation APIs +- task schedule mutation APIs +- generic OCI SDK invocation + +The first cut must be read-only and inventory-focused. + +## Model Design Pattern + +`models.py` must mirror the approach used by compute, networking, registry, and other typed servers. + +### Required model rules + +- Use Pydantic `BaseModel`. +- Add field descriptions. +- Keep field names aligned with OCI SDK attribute names. +- Add nested Pydantic models only when needed for stable serialization. +- Implement explicit `map_*` functions for every top-level returned OCI SDK model. +- Return `None` from mappers when the input is `None`. +- Preserve datetimes as datetimes. +- Preserve optional fields as optional. + +### V1 model set + +Implement only the models required for the v1 tools: + +- `FleetSummary` +- `Fleet` +- `JmsPluginSummary` +- `JmsPlugin` +- `InstallationSiteSummary` +- `FleetAgentConfiguration` +- `FleetAdvancedFeatureConfiguration` +- `ManagedInstanceUsageSummary` +- `ResourceInventory` + +Add nested models only when they are actually present in those responses and would otherwise serialize poorly. + +Do not attempt to mirror the entire JMS SDK in v1. + +## Testing Pattern + +Follow the package-local test layout used by the other Python OCI servers. + +### `test_jms_tools.py` + +Use `fastmcp.Client` and patch `get_jms_client()`. + +Required coverage: + +- happy-path test for each tool +- at least one paginated list test +- at least one paginated summarize/list-style usage test +- exception propagation test for a list tool +- exception propagation test for a get tool +- `main()` transport selection tests +- `get_jms_client()` test verifying: + - config loading + - token file read + - signer creation + - `additional_user_agent` assignment + - `JavaManagementServiceClient` instantiation + +### `test_jms_models.py` + +Required coverage: + +- mapper test for each top-level model returned by the tools +- nested object mapping where applicable +- datetime preservation +- optional and missing field handling + +## Reference Patterns To Copy + +Use these files as implementation references: + +- `src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/server.py` + - classic service-specific tool layout +- `src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/models.py` + - typed Pydantic models plus explicit `map_*` helpers +- `src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/server.py` + - straightforward client bootstrap and paginated list tools +- `src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/server.py` + - richer read-only tool descriptions and optional filtering +- `src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/server.py` + - minimal `main()` and client bootstrap pattern +- `src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/tests/test_usage_tools.py` + - server bootstrap and client helper tests + +## Anti-Patterns To Avoid + +Do not do any of the following: + +- do not build JMS as a generic OCI API wrapper +- do not use the `oci-cloud-mcp-server` dynamic invocation design +- do not return raw OCI SDK model objects from tools +- do not skip Pydantic mapping because `oci.util.to_dict` is easier +- do not add mutating JMS tools in v1 +- do not create repo-wide helpers just to reduce duplication +- do not omit `main()` +- do not omit tests +- do not leave the README or `pyproject.toml` out of sync with the tool surface + +## Verification Checklist + +Before considering the JMS server complete, verify all of the following: + +- `src/oci-jms-mcp-server/pyproject.toml` exists and defines the correct package script +- the package imports successfully +- `server.py` exposes `mcp`, `get_jms_client()`, and `main()` +- stdio and HTTP transport behavior are both tested +- all v1 tools are registered and callable through `fastmcp.Client` +- list-style tools paginate correctly +- outputs are typed and serialized through Pydantic models +- `get_jms_client()` sets `additional_user_agent` +- README documents the final v1 tool list +- package-local tests pass + +## Implementation Defaults + +If a decision is not explicitly documented elsewhere, use these defaults: + +- prefer read-only behavior +- prefer service-specific typed tools +- prefer explicit mappers over clever abstractions +- prefer matching peer server patterns over reducing line count +- prefer the smallest model set that fully supports the v1 JMS tools diff --git a/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md b/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md new file mode 100644 index 00000000..b6083bab --- /dev/null +++ b/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md @@ -0,0 +1,560 @@ +# JMS Tool Details + +This file documents the 9 tools exposed by `oracle.oci-jms-mcp-server`. + +Use this as a quick reference for: + +- tool input parameters +- expected output shape +- demo MCP/agent queries + +The source of truth is: + +- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py` +- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py` + +## General Notes + +- Do not send extra keys such as `task_progress` to the tool call payload. +- Enum-like inputs are accepted in flexible form by the wrapper, but the safest values are the OCI SDK values shown below. +- `sort_order` values should be `ASC` or `DESC`. +- Timestamp inputs must be RFC3339 strings. +- For `get_fleet`, prefer using a fleet OCID returned by `list_fleets`. + +--- + +## 1. `list_fleets` + +Lists JMS fleets in a compartment. + +### Inputs + +```json +{ + "compartment_id": "string", + "id": "string", + "lifecycle_state": "ACTIVE|CREATING|DELETED|DELETING|FAILED|NEEDS_ATTENTION|UPDATING", + "display_name": "string", + "display_name_contains": "string", + "limit": 50, + "sort_order": "ASC|DESC", + "sort_by": "displayName|timeCreated" +} +``` + +### Output + +Returns a list of `FleetSummary` objects: + +```json +[ + { + "id": "ocid1.jmsfleet...", + "display_name": "Test Fleet", + "description": "Fleet description", + "compartment_id": "ocid1.compartment...", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": 0, + "approximate_library_vulnerability_count": 0, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup...", + "log_id": "ocid1.log..." + }, + "operation_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup...", + "log_id": "ocid1.log..." + }, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2026-02-10T09:27:25.338000Z", + "lifecycle_state": "FAILED", + "defined_tags": {}, + "freeform_tags": {}, + "system_tags": {} + } +] +``` + +### Demo Query + +```text +Call the `list_fleets` tool with: +{ + "compartment_id": "ocid1.compartment.oc1..example", + "sort_order": "ASC", + "sort_by": "displayName", + "limit": 50 +} +``` + +### Demo Query For Failed Fleets + +```text +Call the `list_fleets` tool with: +{ + "compartment_id": "ocid1.compartment.oc1..example", + "lifecycle_state": "FAILED", + "sort_order": "DESC", + "sort_by": "timeCreated", + "limit": 20 +} +``` + +--- + +## 2. `get_fleet` + +Gets one JMS fleet by OCID. + +### Inputs + +```json +{ + "fleet_id": "ocid1.jmsfleet..." +} +``` + +### Output + +Returns a `Fleet` object. It has the same shape as `FleetSummary` in this server. + +### Demo Query + +```text +Call the `get_fleet` tool with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example" +} +``` + +--- + +## 3. `list_jms_plugins` + +Lists JMS plugins for a compartment or fleet. + +### Inputs + +```json +{ + "compartment_id": "string", + "compartment_id_in_subtree": false, + "id": "string", + "fleet_id": "string", + "agent_id": "string", + "lifecycle_state": "ACTIVE|INACTIVE|NEEDS_ATTENTION|DELETED", + "availability_status": "ACTIVE|SILENT|NOT_AVAILABLE", + "agent_type": "OMA|OCA", + "time_registered_less_than_or_equal_to": "2026-03-01T00:00:00Z", + "time_last_seen_less_than_or_equal_to": "2026-03-01T00:00:00Z", + "hostname_contains": "host", + "limit": 50, + "sort_order": "ASC|DESC", + "sort_by": "id|timeLastSeen|timeRegistered|hostname|agentId|agentType|lifecycleState|availabilityStatus|fleetId|compartmentId|osFamily|osArchitecture|osDistribution|pluginVersion" +} +``` + +### Output + +Returns a list of `JmsPluginSummary` objects: + +```json +[ + { + "id": "ocid1.jmsplugin...", + "agent_id": "ocid1.managementagent...", + "agent_type": "OMA", + "lifecycle_state": "ACTIVE", + "availability_status": "ACTIVE", + "fleet_id": "ocid1.jmsfleet...", + "compartment_id": "ocid1.compartment...", + "hostname": "host1", + "os_family": "LINUX", + "os_architecture": "x86_64", + "os_distribution": "Oracle Linux", + "plugin_version": "string", + "time_registered": "2026-03-01T00:00:00Z", + "time_last_seen": "2026-03-10T00:00:00Z", + "defined_tags": {}, + "freeform_tags": {}, + "system_tags": {} + } +] +``` + +### Demo Query + +```text +Call the `list_jms_plugins` tool with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example", + "sort_order": "DESC", + "sort_by": "timeLastSeen", + "limit": 25 +} +``` + +--- + +## 4. `get_jms_plugin` + +Gets one JMS plugin by OCID. + +### Inputs + +```json +{ + "jms_plugin_id": "ocid1.jmsplugin..." +} +``` + +### Output + +Returns a `JmsPlugin` object. In this server it has the same shape as `JmsPluginSummary`. + +### Demo Query + +```text +Call the `get_jms_plugin` tool with: +{ + "jms_plugin_id": "ocid1.jmsplugin.oc1.iad.example" +} +``` + +--- + +## 5. `list_installation_sites` + +Lists Java installation sites for a fleet. + +### Inputs + +```json +{ + "fleet_id": "ocid1.jmsfleet...", + "jre_vendor": "string", + "jre_distribution": "string", + "jre_version": "string", + "installation_path": "string", + "application_id": "string", + "managed_instance_id": "string", + "os_family": ["LINUX", "WINDOWS", "MACOS", "UNKNOWN"], + "jre_security_status": "EARLY_ACCESS|UNKNOWN|UP_TO_DATE|UPDATE_REQUIRED|UPGRADE_REQUIRED", + "path_contains": "string", + "time_start": "2026-03-01T00:00:00Z", + "time_end": "2026-03-10T00:00:00Z", + "limit": 50, + "sort_order": "ASC|DESC", + "sort_by": "managedInstanceId|jreDistribution|jreVendor|jreVersion|path|approximateApplicationCount|osName|securityStatus" +} +``` + +### Output + +Returns a list of `InstallationSiteSummary` objects: + +```json +[ + { + "installation_key": "string", + "managed_instance_id": "ocid1.instance...", + "jre": { + "version": "17", + "vendor": "Oracle", + "distribution": "JDK", + "jre_key": "string" + }, + "security_status": "UP_TO_DATE", + "path": "/usr/java", + "operating_system": { + "family": "LINUX", + "name": "Oracle Linux", + "distribution": "Oracle Linux", + "version": "9", + "architecture": "x86_64", + "managed_instance_count": null, + "container_count": null + }, + "approximate_application_count": 0, + "time_last_seen": "2026-03-10T00:00:00Z", + "blocklist": [], + "lifecycle_state": "ACTIVE" + } +] +``` + +### Demo Query + +```text +Call the `list_installation_sites` tool with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example", + "os_family": ["LINUX"], + "sort_order": "ASC", + "sort_by": "managedInstanceId", + "limit": 20 +} +``` + +--- + +## 6. `get_fleet_agent_configuration` + +Gets fleet-level agent configuration. + +### Inputs + +```json +{ + "fleet_id": "ocid1.jmsfleet..." +} +``` + +### Output + +Returns a `FleetAgentConfiguration` object: + +```json +{ + "jre_scan_frequency_in_minutes": 60, + "java_usage_tracker_processing_frequency_in_minutes": 15, + "work_request_validity_period_in_days": 7, + "agent_polling_interval_in_minutes": 5, + "is_collecting_managed_instance_metrics_enabled": true, + "is_collecting_usernames_enabled": false, + "is_capturing_ip_address_and_fqdn_enabled": false, + "is_libraries_scan_enabled": false, + "linux_configuration": { + "include_paths": ["/usr/java"], + "exclude_paths": ["/tmp"] + }, + "windows_configuration": null, + "mac_os_configuration": null, + "time_last_modified": "2026-03-01T00:00:00Z" +} +``` + +### Demo Query + +```text +Call the `get_fleet_agent_configuration` tool with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example" +} +``` + +--- + +## 7. `get_fleet_advanced_feature_configuration` + +Gets advanced feature configuration for a fleet. + +### Inputs + +```json +{ + "fleet_id": "ocid1.jmsfleet..." +} +``` + +### Output + +Returns a `FleetAdvancedFeatureConfiguration` object: + +```json +{ + "analytic_namespace": "string", + "analytic_bucket_name": "string", + "lcm": {}, + "crypto_event_analysis": {}, + "advanced_usage_tracking": {}, + "jfr_recording": {}, + "performance_tuning_analysis": {}, + "java_migration_analysis": {}, + "time_last_modified": "2026-03-01T00:00:00Z" +} +``` + +### Demo Query + +```text +Call the `get_fleet_advanced_feature_configuration` tool with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example" +} +``` + +--- + +## 8. `summarize_resource_inventory` + +Summarizes high-level JMS inventory counts for a compartment. + +### Inputs + +```json +{ + "compartment_id": "ocid1.compartment...", + "compartment_id_in_subtree": false, + "time_start": "2026-03-01T00:00:00Z", + "time_end": "2026-03-10T00:00:00Z" +} +``` + +### Output + +Returns a `ResourceInventory` object: + +```json +{ + "active_fleet_count": 1, + "managed_instance_count": 2, + "jre_count": 3, + "installation_count": 4, + "application_count": 5 +} +``` + +### Demo Query + +```text +Call the `summarize_resource_inventory` tool with: +{ + "compartment_id": "ocid1.compartment.oc1..example", + "compartment_id_in_subtree": false +} +``` + +--- + +## 9. `summarize_managed_instance_usage` + +Summarizes managed instance usage records for a fleet. + +### Inputs + +```json +{ + "fleet_id": "ocid1.jmsfleet...", + "managed_instance_id": "string", + "managed_instance_type": "ORACLE_MANAGEMENT_AGENT|ORACLE_CLOUD_AGENT", + "jre_vendor": "string", + "jre_distribution": "string", + "jre_version": "string", + "installation_path": "string", + "application_id": "string", + "fields": ["approximateJreCount", "approximateInstallationCount", "approximateApplicationCount"], + "time_start": "2026-03-01T00:00:00Z", + "time_end": "2026-03-10T00:00:00Z", + "limit": 50, + "sort_order": "ASC|DESC", + "sort_by": "timeFirstSeen|timeLastSeen|approximateJreCount|approximateInstallationCount|approximateApplicationCount|osName", + "os_family": ["LINUX", "WINDOWS", "MACOS", "UNKNOWN"], + "hostname_contains": "string", + "library_key": "string" +} +``` + +### Output + +Returns a list of `ManagedInstanceUsage` objects: + +```json +[ + { + "managed_instance_id": "ocid1.instance...", + "managed_instance_type": "ORACLE_MANAGEMENT_AGENT", + "hostname": "host1", + "host_id": "string", + "ip_addresses": ["10.0.0.10"], + "hostnames": ["host1"], + "fqdns": ["host1.example.internal"], + "operating_system": { + "family": "LINUX", + "name": "Oracle Linux", + "distribution": "Oracle Linux", + "version": "9", + "architecture": "x86_64", + "managed_instance_count": null, + "container_count": null + }, + "agent": {}, + "cluster_details": {}, + "approximate_application_count": 0, + "approximate_installation_count": 0, + "approximate_jre_count": 0, + "drs_file_status": "PRESENT", + "application_invoked_by": "oracle", + "time_start": "2026-03-01T00:00:00Z", + "time_end": "2026-03-10T00:00:00Z", + "time_first_seen": "2026-03-01T00:00:00Z", + "time_last_seen": "2026-03-10T00:00:00Z" + } +] +``` + +### Demo Query + +```text +Call the `summarize_managed_instance_usage` tool with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example", + "fields": ["approximateJreCount"], + "sort_order": "DESC", + "sort_by": "timeLastSeen", + "limit": 25 +} +``` + +--- + +## Suggested Agent Workflow + +### Find a fleet, then inspect it + +```text +1. Call `list_fleets` with: +{ + "compartment_id": "ocid1.compartment.oc1..example", + "sort_order": "ASC", + "sort_by": "displayName", + "limit": 50 +} +2. Take one returned fleet id. +3. Call `get_fleet` with that fleet id. +``` + +### Find plugins for a fleet + +```text +1. Call `list_jms_plugins` with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example", + "sort_order": "DESC", + "sort_by": "timeLastSeen", + "limit": 25 +} +``` + +### Find installation sites for a fleet + +```text +1. Call `list_installation_sites` with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example", + "os_family": ["LINUX"], + "sort_order": "ASC", + "sort_by": "managedInstanceId", + "limit": 20 +} +``` diff --git a/src/oci-jms-mcp-server/JMS_TOOL_TEST_REPORT.md b/src/oci-jms-mcp-server/JMS_TOOL_TEST_REPORT.md new file mode 100644 index 00000000..e0ae1d5b --- /dev/null +++ b/src/oci-jms-mcp-server/JMS_TOOL_TEST_REPORT.md @@ -0,0 +1,756 @@ +# JMS Tool Test Report + +Date: 2026-03-17 + +Target fleet: + +```text +ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq +``` + +Target compartment: + +```text +ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq +``` + +This report was produced from a live rerun of all 9 JMS tools through `fastmcp.Client(mcp)`. + +## 1. `list_fleets` + +Status: `ok` + +Input: + +```json +{ + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "sort_order": "ASC", + "sort_by": "displayName", + "limit": 10 +} +``` + +Output: + +```json +[ + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguia7iry2zw5zygfmqejmaessuuzbll2im7kdqxgp5rwgzna", + "display_name": "Test2", + "description": "Test2", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiavs2et3qpq2klmgurnigpbqya3sbr5m3otqntlzgupk6a", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaj3zfojeftftvgji2mw4fu7smsfz2g2d43fyx2lmu66da" + }, + "operation_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiavs2et3qpq2klmgurnigpbqya3sbr5m3otqntlzgupk6a", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiajoc4aj43veuw6fpvqymbi5fxiki62ekpwmwy3ldp3ada" + }, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2026-02-10T09:27:25.338000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "kwickram", + "CreatedOn": "2026-02-10T09:27:24.860Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguia3txn7ennnkfbdzlceq4j5nyj5s4z7piy7c4icbn6prfa", + "display_name": "auto-move-managed-instance-A", + "description": "autotest fleet", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7uexc2le75lji7xl26qjrpkqnzqv7sszlmouktxcbi7q", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiazf652yp5zdpmbr4s7narowmw3nyap2jykdxy4z4lx2gq" + }, + "operation_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7uexc2le75lji7xl26qjrpkqnzqv7sszlmouktxcbi7q", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiath6unzwkgzlgqarrguaborwbam6husaexgfhfh4gip3a" + }, + "is_advanced_features_enabled": false, + "is_export_setting_enabled": false, + "time_created": "2023-06-08T16:12:37.330000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "klevytsk", + "CreatedOn": "2023-06-08T16:12:36.173Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguialuyracd2pi6zqjxg2z5n2i7lv5ywkrjjwkfhavpi6twa", + "display_name": "fleet-20250613-1315", + "description": "Test fleet created by SQE", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaunp4y53xiohb5hqvasykcqdjroukjnhvfaexyqqixwfq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiavnauazecrpuf7s5o4vzfxygzy2e2uefp2ll5kkiimgpq" + }, + "operation_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaunp4y53xiohb5hqvasykcqdjroukjnhvfaexyqqixwfq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaejedobtl2mmcx4biucxr46lqiehtbshjqnnt46zmsifa" + }, + "is_advanced_features_enabled": false, + "is_export_setting_enabled": false, + "time_created": "2025-06-13T05:15:46.987000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "kwickram", + "CreatedOn": "2025-06-13T05:15:46.402Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiaovou2nqghvburhlmoy42gzk62vvtzak4zzz2i5nhlmxa", + "display_name": "fleet-20251014-0910_test-incubator_canary", + "description": "Testing fleet-20251014-0910_test-incubator_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiabmgw3bckpm63xiujmh6fkmkdwqiu4s23mytbx767j6eq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiad4ltos3e6vgjwu7imnqiyv46li7i6ekgbjxys4ki2z2a" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-10-14T09:12:34.940000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-10-14T09:12:34.660Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiatzxyg3gkpuwcqffkpaiwminrjxtxdyduwbhnrlwsi3hq", + "display_name": "fleet-20251016-1325_test-null_canary", + "description": "Testing fleet-20251016-1325_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiayml34lgbdsqxadrtei5hzxkmkg3sxcannmjytx6e4dla", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia55hr5yvk4zfxcremzz6q24grrsjocsj2vcs6lkr3toka" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-10-16T13:25:52.653000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-10-16T13:25:52.110Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiaxjjqturjxfb7ffgiwe7ozx2qomqftw55swvv6e2nvllq", + "display_name": "fleet-20251017-0834_test-null_canary", + "description": "Testing fleet-20251017-0834_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7yo6e5f6pw5hpylhfsiro5lwh7ps5rj3ue7jclwlkm2a", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia7olcl6ppvivnqtpilylgtaemtszmsmgppozf5t7qapfq" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-10-17T08:34:59.591000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-10-17T08:34:59.345Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiame4obzlb2r4micfk7vr5qhjdjaxb5s62a4ehmdqrneaq", + "display_name": "fleet-20251216-0835_test-null_canary", + "description": "Testing fleet-20251216-0835_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiao2v4e5xec4yeu5zo46bfwwgbqm7ntfnwmj7mdyio2t3a", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia3g2ogwthju5jsvef2qipkl7dv2ufym63eyhkzhhtugxq" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-12-16T08:35:37.365000Z", + "lifecycle_state": "DELETED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-12-16T08:35:37.100Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiarvjb6ikdnmwgy27wvx6gyzszcpp2qn23k4mnhnjyzk6a", + "display_name": "fleet-20251216-1434_test-null_canary", + "description": "Testing fleet-20251216-1434_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaked7r3fqtylyru2zosqdmigywgajqcwqv4j7o2ehpofq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiawezvurunu63y534hjza5e6ebcy56kfe2z4rgx3cb4yfq" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-12-16T14:34:38.887000Z", + "lifecycle_state": "DELETED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-12-16T14:34:38.639Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguial2nqjd7hahr5qz2laytp2qcnjfcjmcjsylhrh4egwaia", + "display_name": "fleet-20251216-2034_test-null_canary", + "description": "Testing fleet-20251216-2034_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiacugfanyfx7rnaq6wcngvdkp7t7lqjxjlfdfkrwqvrn2a", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiatep563wid7ucmpjavtsmi4v35uv6myiu4bu2dlcol2jq" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-12-16T20:34:51.004000Z", + "lifecycle_state": "DELETED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-12-16T20:34:50.735Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiahxduzprvifslhei6lczre6axawn7pou5hx6h5l7d2pva", + "display_name": "fleet-20251217-0234_test-null_canary", + "description": "Testing fleet-20251217-0234_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiay2c6dklzrc4gg3epv4v4vjedcug4t5b3kvx23yzsayba", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiak4g3fucg7xvgun63tlu44bubw22rzx3ycxdn3zykx3sa" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-12-17T02:34:31.877000Z", + "lifecycle_state": "DELETED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-12-17T02:34:31.584Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + } +] +``` + +## 2. `get_fleet` + +Status: `ok` + +Input: + +```json +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" +} +``` + +Output: + +```json +{ + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "display_name": "jms10-bpn-k8s", + "description": "JMS 10 Test Fleet", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguian5ex7u5otwik5adoq6n65fbbwdu6yfarknbzo4uyyxma" + }, + "operation_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaxmmcot7l6pynst3mc3d6abrvc3ol6qd7fv3rkp4f66fq" + }, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-09-25T09:42:20.123000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "bbanathu", + "CreatedOn": "2025-09-25T09:42:19.695Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } +} +``` + +## 3. `list_jms_plugins` + +Status: `ok` + +Input: + +```json +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "sort_order": "DESC", + "sort_by": "timeLastSeen", + "limit": 10 +} +``` + +Output: + +```json +[ + { + "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea", + "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguia6q6c7ces7dz3tw7nf3unz5ofmhccxcpcp7lchjpccraq", + "agent_type": "OMA", + "lifecycle_state": "DELETED", + "availability_status": "NOT_AVAILABLE", + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "hostname": "192.168.29.249", + "os_family": "MACOS", + "os_architecture": "x86_64", + "os_distribution": "UNKNOWN", + "plugin_version": "10.0.476", + "time_registered": "2025-09-25T15:18:39.681000Z", + "time_last_seen": null, + "defined_tags": {}, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaaebraeu2tvhcqh6jlxh6hymw24wsk3acyl2t7l6p2kjdlhd4le3oa", + "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguiaqdlpx5c77d5cdmpcjko6w7s453dydft7ht4ovv53jcea", + "agent_type": "UNKNOWN_ENUM_VALUE", + "lifecycle_state": "DELETED", + "availability_status": "NOT_AVAILABLE", + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "hostname": "jms-k8s-cluster", + "os_family": "LINUX", + "os_architecture": "amd64", + "os_distribution": "OL8", + "plugin_version": "10.0.476.7", + "time_registered": "2025-09-25T10:17:09.383000Z", + "time_last_seen": null, + "defined_tags": {}, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + } +] +``` + +## 4. `get_jms_plugin` + +Status: `ok` + +Input: + +```json +{ + "jms_plugin_id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea" +} +``` + +Output: + +```json +{ + "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea", + "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguia6q6c7ces7dz3tw7nf3unz5ofmhccxcpcp7lchjpccraq", + "agent_type": "OMA", + "lifecycle_state": "DELETED", + "availability_status": "NOT_AVAILABLE", + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "hostname": "192.168.29.249", + "os_family": "MACOS", + "os_architecture": "x86_64", + "os_distribution": "UNKNOWN", + "plugin_version": "10.0.476", + "time_registered": "2025-09-25T15:18:39.681000Z", + "time_last_seen": null, + "defined_tags": {}, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } +} +``` + +## 5. `list_installation_sites` + +Status: `ok` + +Input: + +```json +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "limit": 10 +} +``` + +Output: + +```json +[] +``` + +## 6. `get_fleet_agent_configuration` + +Status: `ok` + +Input: + +```json +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" +} +``` + +Output: + +```json +{ + "jre_scan_frequency_in_minutes": 1440, + "java_usage_tracker_processing_frequency_in_minutes": 60, + "work_request_validity_period_in_days": 14, + "agent_polling_interval_in_minutes": 10, + "is_collecting_managed_instance_metrics_enabled": true, + "is_collecting_usernames_enabled": true, + "is_capturing_ip_address_and_fqdn_enabled": null, + "is_libraries_scan_enabled": null, + "linux_configuration": { + "include_paths": [ + "/usr/java", + "/usr/lib/jvm", + "/usr/lib64/graalvm", + "/opt" + ], + "exclude_paths": [] + }, + "windows_configuration": { + "include_paths": [ + "${ProgramFiles}\\Java", + "${ProgramFiles(x86)}\\Java", + "C:\\Oracle" + ], + "exclude_paths": [] + }, + "mac_os_configuration": { + "include_paths": [ + "/Library/Java", + "/Library/Internet Plug-Ins/", + "/Library/Oracle" + ], + "exclude_paths": [] + }, + "time_last_modified": "2025-09-26T06:26:12.058000Z" +} +``` + +## 7. `get_fleet_advanced_feature_configuration` + +Status: `ok` + +Input: + +```json +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" +} +``` + +Output: + +```json +{ + "analytic_namespace": "ideq4gu7gs6j", + "analytic_bucket_name": "jms_ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "lcm": { + "is_enabled": true, + "post_installation_actions": null + }, + "crypto_event_analysis": { + "is_enabled": true, + "summarized_events_log": { + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia75rkcwb43ccw2ne6q32p44x26tav3d5ncnytdcr4zwva" + } + }, + "advanced_usage_tracking": { + "is_enabled": true + }, + "jfr_recording": { + "is_enabled": true + }, + "performance_tuning_analysis": { + "is_enabled": true + }, + "java_migration_analysis": { + "is_enabled": true + }, + "time_last_modified": "2025-09-26T06:14:05.652000Z" +} +``` + +## 8. `summarize_resource_inventory` + +Status: `ok` + +Input: + +```json +{ + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq" +} +``` + +Output: + +```json +{ + "active_fleet_count": 0, + "managed_instance_count": 0, + "jre_count": 0, + "installation_count": 0, + "application_count": 0 +} +``` + +## 9. `summarize_managed_instance_usage` + +Status: `ok` + +Input: + +```json +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "limit": 10 +} +``` + +Output: + +```json +[] +``` + diff --git a/src/oci-jms-mcp-server/JMS_TOOL_TEST_RESULTS.json b/src/oci-jms-mcp-server/JMS_TOOL_TEST_RESULTS.json new file mode 100644 index 00000000..1c4fdf9e --- /dev/null +++ b/src/oci-jms-mcp-server/JMS_TOOL_TEST_RESULTS.json @@ -0,0 +1,641 @@ +{ + "list_fleets": { + "status": "ok", + "input": { + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "sort_order": "ASC", + "sort_by": "displayName", + "limit": 10 + }, + "output": [ + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguia7iry2zw5zygfmqejmaessuuzbll2im7kdqxgp5rwgzna", + "display_name": "Test2", + "description": "Test2", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiavs2et3qpq2klmgurnigpbqya3sbr5m3otqntlzgupk6a", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaj3zfojeftftvgji2mw4fu7smsfz2g2d43fyx2lmu66da" + }, + "operation_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiavs2et3qpq2klmgurnigpbqya3sbr5m3otqntlzgupk6a", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiajoc4aj43veuw6fpvqymbi5fxiki62ekpwmwy3ldp3ada" + }, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2026-02-10T09:27:25.338000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "kwickram", + "CreatedOn": "2026-02-10T09:27:24.860Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguia3txn7ennnkfbdzlceq4j5nyj5s4z7piy7c4icbn6prfa", + "display_name": "auto-move-managed-instance-A", + "description": "autotest fleet", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7uexc2le75lji7xl26qjrpkqnzqv7sszlmouktxcbi7q", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiazf652yp5zdpmbr4s7narowmw3nyap2jykdxy4z4lx2gq" + }, + "operation_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7uexc2le75lji7xl26qjrpkqnzqv7sszlmouktxcbi7q", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiath6unzwkgzlgqarrguaborwbam6husaexgfhfh4gip3a" + }, + "is_advanced_features_enabled": false, + "is_export_setting_enabled": false, + "time_created": "2023-06-08T16:12:37.330000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "klevytsk", + "CreatedOn": "2023-06-08T16:12:36.173Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguialuyracd2pi6zqjxg2z5n2i7lv5ywkrjjwkfhavpi6twa", + "display_name": "fleet-20250613-1315", + "description": "Test fleet created by SQE", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaunp4y53xiohb5hqvasykcqdjroukjnhvfaexyqqixwfq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiavnauazecrpuf7s5o4vzfxygzy2e2uefp2ll5kkiimgpq" + }, + "operation_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaunp4y53xiohb5hqvasykcqdjroukjnhvfaexyqqixwfq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaejedobtl2mmcx4biucxr46lqiehtbshjqnnt46zmsifa" + }, + "is_advanced_features_enabled": false, + "is_export_setting_enabled": false, + "time_created": "2025-06-13T05:15:46.987000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "kwickram", + "CreatedOn": "2025-06-13T05:15:46.402Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiaovou2nqghvburhlmoy42gzk62vvtzak4zzz2i5nhlmxa", + "display_name": "fleet-20251014-0910_test-incubator_canary", + "description": "Testing fleet-20251014-0910_test-incubator_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiabmgw3bckpm63xiujmh6fkmkdwqiu4s23mytbx767j6eq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiad4ltos3e6vgjwu7imnqiyv46li7i6ekgbjxys4ki2z2a" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-10-14T09:12:34.940000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-10-14T09:12:34.660Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiatzxyg3gkpuwcqffkpaiwminrjxtxdyduwbhnrlwsi3hq", + "display_name": "fleet-20251016-1325_test-null_canary", + "description": "Testing fleet-20251016-1325_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiayml34lgbdsqxadrtei5hzxkmkg3sxcannmjytx6e4dla", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia55hr5yvk4zfxcremzz6q24grrsjocsj2vcs6lkr3toka" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-10-16T13:25:52.653000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-10-16T13:25:52.110Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiaxjjqturjxfb7ffgiwe7ozx2qomqftw55swvv6e2nvllq", + "display_name": "fleet-20251017-0834_test-null_canary", + "description": "Testing fleet-20251017-0834_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7yo6e5f6pw5hpylhfsiro5lwh7ps5rj3ue7jclwlkm2a", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia7olcl6ppvivnqtpilylgtaemtszmsmgppozf5t7qapfq" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-10-17T08:34:59.591000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-10-17T08:34:59.345Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiame4obzlb2r4micfk7vr5qhjdjaxb5s62a4ehmdqrneaq", + "display_name": "fleet-20251216-0835_test-null_canary", + "description": "Testing fleet-20251216-0835_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiao2v4e5xec4yeu5zo46bfwwgbqm7ntfnwmj7mdyio2t3a", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia3g2ogwthju5jsvef2qipkl7dv2ufym63eyhkzhhtugxq" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-12-16T08:35:37.365000Z", + "lifecycle_state": "DELETED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-12-16T08:35:37.100Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiarvjb6ikdnmwgy27wvx6gyzszcpp2qn23k4mnhnjyzk6a", + "display_name": "fleet-20251216-1434_test-null_canary", + "description": "Testing fleet-20251216-1434_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaked7r3fqtylyru2zosqdmigywgajqcwqv4j7o2ehpofq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiawezvurunu63y534hjza5e6ebcy56kfe2z4rgx3cb4yfq" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-12-16T14:34:38.887000Z", + "lifecycle_state": "DELETED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-12-16T14:34:38.639Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguial2nqjd7hahr5qz2laytp2qcnjfcjmcjsylhrh4egwaia", + "display_name": "fleet-20251216-2034_test-null_canary", + "description": "Testing fleet-20251216-2034_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiacugfanyfx7rnaq6wcngvdkp7t7lqjxjlfdfkrwqvrn2a", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiatep563wid7ucmpjavtsmi4v35uv6myiu4bu2dlcol2jq" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-12-16T20:34:51.004000Z", + "lifecycle_state": "DELETED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-12-16T20:34:50.735Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiahxduzprvifslhei6lczre6axawn7pou5hx6h5l7d2pva", + "display_name": "fleet-20251217-0234_test-null_canary", + "description": "Testing fleet-20251217-0234_test-null_canary", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiay2c6dklzrc4gg3epv4v4vjedcug4t5b3kvx23yzsayba", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiak4g3fucg7xvgun63tlu44bubw22rzx3ycxdn3zykx3sa" + }, + "operation_log": null, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-12-17T02:34:31.877000Z", + "lifecycle_state": "DELETED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "jmstest-canaryuser-iad", + "CreatedOn": "2025-12-17T02:34:31.584Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + } + ] + }, + "get_fleet": { + "status": "ok", + "input": { + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" + }, + "output": { + "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "display_name": "jms10-bpn-k8s", + "description": "JMS 10 Test Fleet", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "approximate_jre_count": 0, + "approximate_installation_count": 0, + "approximate_application_count": 0, + "approximate_managed_instance_count": 0, + "approximate_java_server_count": 0, + "approximate_library_count": null, + "approximate_library_vulnerability_count": null, + "inventory_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguian5ex7u5otwik5adoq6n65fbbwdu6yfarknbzo4uyyxma" + }, + "operation_log": { + "namespace": null, + "bucket_name": null, + "object_name": null, + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaxmmcot7l6pynst3mc3d6abrvc3ol6qd7fv3rkp4f66fq" + }, + "is_advanced_features_enabled": true, + "is_export_setting_enabled": false, + "time_created": "2025-09-25T09:42:20.123000Z", + "lifecycle_state": "FAILED", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "bbanathu", + "CreatedOn": "2025-09-25T09:42:19.695Z" + } + }, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + } + }, + "list_jms_plugins": { + "status": "ok", + "input": { + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "sort_order": "DESC", + "sort_by": "timeLastSeen", + "limit": 10 + }, + "output": [ + { + "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea", + "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguia6q6c7ces7dz3tw7nf3unz5ofmhccxcpcp7lchjpccraq", + "agent_type": "OMA", + "lifecycle_state": "DELETED", + "availability_status": "NOT_AVAILABLE", + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "hostname": "192.168.29.249", + "os_family": "MACOS", + "os_architecture": "x86_64", + "os_distribution": "UNKNOWN", + "plugin_version": "10.0.476", + "time_registered": "2025-09-25T15:18:39.681000Z", + "time_last_seen": null, + "defined_tags": {}, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + }, + { + "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaaebraeu2tvhcqh6jlxh6hymw24wsk3acyl2t7l6p2kjdlhd4le3oa", + "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguiaqdlpx5c77d5cdmpcjko6w7s453dydft7ht4ovv53jcea", + "agent_type": "UNKNOWN_ENUM_VALUE", + "lifecycle_state": "DELETED", + "availability_status": "NOT_AVAILABLE", + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "hostname": "jms-k8s-cluster", + "os_family": "LINUX", + "os_architecture": "amd64", + "os_distribution": "OL8", + "plugin_version": "10.0.476.7", + "time_registered": "2025-09-25T10:17:09.383000Z", + "time_last_seen": null, + "defined_tags": {}, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + } + ] + }, + "list_installation_sites": { + "status": "ok", + "input": { + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "limit": 10 + }, + "output": [] + }, + "get_fleet_agent_configuration": { + "status": "ok", + "input": { + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" + }, + "output": { + "jre_scan_frequency_in_minutes": 1440, + "java_usage_tracker_processing_frequency_in_minutes": 60, + "work_request_validity_period_in_days": 14, + "agent_polling_interval_in_minutes": 10, + "is_collecting_managed_instance_metrics_enabled": true, + "is_collecting_usernames_enabled": true, + "is_capturing_ip_address_and_fqdn_enabled": null, + "is_libraries_scan_enabled": null, + "linux_configuration": { + "include_paths": [ + "/usr/java", + "/usr/lib/jvm", + "/usr/lib64/graalvm", + "/opt" + ], + "exclude_paths": [] + }, + "windows_configuration": { + "include_paths": [ + "${ProgramFiles}\\Java", + "${ProgramFiles(x86)}\\Java", + "C:\\Oracle" + ], + "exclude_paths": [] + }, + "mac_os_configuration": { + "include_paths": [ + "/Library/Java", + "/Library/Internet Plug-Ins/", + "/Library/Oracle" + ], + "exclude_paths": [] + }, + "time_last_modified": "2025-09-26T06:26:12.058000Z" + } + }, + "get_fleet_advanced_feature_configuration": { + "status": "ok", + "input": { + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" + }, + "output": { + "analytic_namespace": "ideq4gu7gs6j", + "analytic_bucket_name": "jms_ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "lcm": { + "is_enabled": true, + "post_installation_actions": null + }, + "crypto_event_analysis": { + "is_enabled": true, + "summarized_events_log": { + "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", + "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia75rkcwb43ccw2ne6q32p44x26tav3d5ncnytdcr4zwva" + } + }, + "advanced_usage_tracking": { + "is_enabled": true + }, + "jfr_recording": { + "is_enabled": true + }, + "performance_tuning_analysis": { + "is_enabled": true + }, + "java_migration_analysis": { + "is_enabled": true + }, + "time_last_modified": "2025-09-26T06:14:05.652000Z" + } + }, + "summarize_resource_inventory": { + "status": "ok", + "input": { + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq" + }, + "output": { + "active_fleet_count": 0, + "managed_instance_count": 0, + "jre_count": 0, + "installation_count": 0, + "application_count": 0 + } + }, + "summarize_managed_instance_usage": { + "status": "ok", + "input": { + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "limit": 10 + }, + "output": [] + }, + "get_jms_plugin": { + "status": "ok", + "input": { + "jms_plugin_id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea" + }, + "output": { + "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea", + "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguia6q6c7ces7dz3tw7nf3unz5ofmhccxcpcp7lchjpccraq", + "agent_type": "OMA", + "lifecycle_state": "DELETED", + "availability_status": "NOT_AVAILABLE", + "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", + "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", + "hostname": "192.168.29.249", + "os_family": "MACOS", + "os_architecture": "x86_64", + "os_distribution": "UNKNOWN", + "plugin_version": "10.0.476", + "time_registered": "2025-09-25T15:18:39.681000Z", + "time_last_seen": null, + "defined_tags": {}, + "freeform_tags": {}, + "system_tags": { + "orcl-cloud": { + "free-tier-retained": "false" + } + } + } + } +} diff --git a/src/oci-jms-mcp-server/README.md b/src/oci-jms-mcp-server/README.md new file mode 100644 index 00000000..c1761513 --- /dev/null +++ b/src/oci-jms-mcp-server/README.md @@ -0,0 +1,183 @@ +# OCI JMS MCP Server + +## Overview + +This server provides tools to interact with Oracle Cloud Infrastructure Java Management Service (JMS). +It focuses on fleet inventory and discovery workflows. + +## Authentication + +This server expects OCI session-token auth through a normal OCI CLI config file. + +- Use `OCI_CONFIG_FILE` to point at the OCI config file +- Use `OCI_CONFIG_PROFILE` to select the profile +- The configured profile must include a valid `security_token_file` +- Refresh expired sessions with `oci session authenticate ...` + +This server does **not** use the env names from the separate `oci_api_mcp` project. + + +- Use `OCI_CONFIG_FILE`, not `OCI_CONFIG` +- Use `OCI_CONFIG_PROFILE`, not `OCI_PROFILE` + +## JMS Environment Endpoint Selection + +By default, this server lets the OCI Python SDK derive the production JMS endpoint from the OCI config region. + +For dev and other non-prod JMS environments, set `JMS_TEST_ENVIRONMENT` to derive the service endpoint using the same environment naming pattern used by JMS Java tooling. + +Supported base values: + +- `PROD` +- `HERDS` +- `DEV` +- `DEV2` +- `DEV3` +- `TEST` +- `TEST2` +- `TEST3` +- `STAGE` +- `VANILLA` + +Optional suffixes are allowed and ignored for endpoint derivation, for example `DEV-canary` or `DEV_canary`. + +Examples: + +- `JMS_TEST_ENVIRONMENT=DEV` -> `https://javamanagement-dev..oci.oc-test.com` +- `JMS_TEST_ENVIRONMENT=TEST` -> `https://javamanagement-test..oci.oc-test.com` +- `JMS_TEST_ENVIRONMENT=TEST2` -> `https://javamanagement-test2..oci.oc-test.com` +- `JMS_TEST_ENVIRONMENT=STAGE` -> `https://javamanagement-stage..oci.oc-test.com` +- `JMS_TEST_ENVIRONMENT=HERDS` -> `https://javamanagement-herds..oci.rbcloud.oc-test.com` + +When `JMS_TEST_ENVIRONMENT=PROD` or the variable is unset, the server keeps the default SDK-derived production endpoint. + +## Running the server + +From the JMS package directory: + +```sh +cd /Users/bhuvesku/Documents/projects/oracle-jms-mcp-server/src/oci-jms-mcp-server +mkdir -p /tmp/uv-cache +UV_CACHE_DIR=/tmp/uv-cache uv sync +``` + +### STDIO transport mode + +```sh +OCI_CONFIG_FILE=~/.oci/config OCI_CONFIG_PROFILE=DEFAULT UV_CACHE_DIR=/tmp/uv-cache uv run oracle.oci-jms-mcp-server +``` + +Non-prod example: + +```sh +OCI_CONFIG_FILE=~/.oci/config OCI_CONFIG_PROFILE=DEFAULT JMS_TEST_ENVIRONMENT=DEV UV_CACHE_DIR=/tmp/uv-cache uv run oracle.oci-jms-mcp-server +``` + +### HTTP streaming transport mode + +```sh +OCI_CONFIG_FILE=~/.oci/config OCI_CONFIG_PROFILE=DEFAULT UV_CACHE_DIR=/tmp/uv-cache ORACLE_MCP_HOST=127.0.0.1 ORACLE_MCP_PORT=8888 uv run oracle.oci-jms-mcp-server +``` + +Non-prod example: + +```sh +OCI_CONFIG_FILE=~/.oci/config OCI_CONFIG_PROFILE=DEFAULT JMS_TEST_ENVIRONMENT=TEST UV_CACHE_DIR=/tmp/uv-cache ORACLE_MCP_HOST=127.0.0.1 ORACLE_MCP_PORT=8888 uv run oracle.oci-jms-mcp-server +``` + +The HTTP endpoint is: + +```text +http://127.0.0.1:8888/mcp +``` + +## MCP Client Configuration + +### Cline / stdio + +```json +{ + "mcpServers": { + "oracle-oci-jms-mcp-server": { + "type": "stdio", + "command": "/bin/zsh", + "args": [ + "-lc", + "cd /Users/bhuvesku/Documents/projects/oracle-jms-mcp-server/src/oci-jms-mcp-server && OCI_CONFIG_FILE=/Users/bhuvesku/.oci/config OCI_CONFIG_PROFILE=DEFAULT UV_CACHE_DIR=/tmp/uv-cache uv run oracle.oci-jms-mcp-server" + ], + "env": { + "FASTMCP_LOG_LEVEL": "ERROR" + } + } + } +} +``` + +### Cline / HTTP + +Run the server manually, then configure: + +```json +{ + "mcpServers": { + "oracle-oci-jms-mcp-server": { + "type": "streamableHttp", + "url": "http://127.0.0.1:8888/mcp" + } + } +} +``` + +## Verification + +Verify the server through an MCP agent in this order: + +1. List the tools from `oracle-oci-jms-mcp-server` +2. Call `list_fleets` for a known compartment +3. Use one returned fleet OCID in `get_fleet` + +Prefer verifying `get_fleet` only with an OCID returned by `list_fleets`. If `get_fleet` fails with `NotAuthorizedOrNotFound`, first confirm the fleet is visible to the active profile. + +## Troubleshooting + +- If startup falls back to stdio, make sure both `ORACLE_MCP_HOST` and `ORACLE_MCP_PORT` are set. +- If `uv` fails with `Failed to initialize cache`, create a writable cache directory and run with `UV_CACHE_DIR=/tmp/uv-cache`. +- If `get_fleet` returns `NotAuthorizedOrNotFound`, verify: + - the active `OCI_CONFIG_PROFILE` + - the region/tenancy for the fleet OCID + - IAM permission to read JMS fleets + - the fleet exists by first calling `list_fleets` +- If your client config was copied from `oci_api_mcp`, replace `OCI_CONFIG`/`OCI_PROFILE` with `OCI_CONFIG_FILE`/`OCI_CONFIG_PROFILE`. +- If you set `JMS_TEST_ENVIRONMENT`, make sure the OCI config contains a `region`. +- If you need a dev endpoint, set `JMS_TEST_ENVIRONMENT`; tool inputs do not accept a per-call endpoint override. + +## Tools + +| Tool Name | Description | +| --- | --- | +| list_fleets | List JMS fleets in a compartment | +| get_fleet | Get a JMS fleet by OCID | +| list_jms_plugins | List JMS plugins in a compartment or fleet | +| get_jms_plugin | Get a JMS plugin by OCID | +| list_installation_sites | List Java installation sites in a JMS fleet | +| get_fleet_agent_configuration | Get fleet agent configuration | +| get_fleet_advanced_feature_configuration | Get fleet advanced feature configuration | +| summarize_resource_inventory | Summarize JMS resource inventory | +| summarize_managed_instance_usage | Summarize managed instance usage in a fleet | + +⚠️ **NOTE**: All actions are performed with the permissions of the configured OCI CLI profile. We advise least-privilege IAM setup, secure credential management, safe network practices, secure logging, and warn against exposing secrets. + +## Third-Party APIs + +Developers choosing to distribute a binary implementation of this project are responsible for obtaining and providing all required licenses and copyright notices for the third-party code used in order to ensure compliance with their respective open source licenses. + +## Disclaimer + +Users are responsible for their local environment and credential safety. Different language model selections may yield different results and performance. + +## License + +Copyright (c) 2025 Oracle and/or its affiliates. + +Released under the Universal Permissive License v1.0 as shown at +. diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/__init__.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/__init__.py new file mode 100644 index 00000000..35ca2659 --- /dev/null +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/__init__.py @@ -0,0 +1,8 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +__project__ = "oracle.oci-jms-mcp-server" +__version__ = "1.0.0" diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py new file mode 100644 index 00000000..63199f45 --- /dev/null +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py @@ -0,0 +1,556 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any, Dict, List, Literal, Optional + +import oci +from pydantic import BaseModel, Field + + +def _oci_to_dict(obj): + """Best-effort conversion of OCI SDK objects into plain dictionaries.""" + if obj is None: + return None + try: + from oci.util import to_dict as oci_to_dict + + return oci_to_dict(obj) + except Exception: + pass + if isinstance(obj, dict): + return obj + if hasattr(obj, "__dict__"): + return {k: v for k, v in obj.__dict__.items() if not k.startswith("_")} + return None + + +class CustomLog(BaseModel): + """Structured log destination metadata returned by JMS for fleet logging settings.""" + namespace: Optional[str] = Field(None, description="OCI Logging namespace.") + bucket_name: Optional[str] = Field(None, description="Bucket name associated with the log.") + object_name: Optional[str] = Field(None, description="Object name associated with the log.") + log_group_id: Optional[str] = Field(None, description="OCI Logging log group OCID.") + log_id: Optional[str] = Field(None, description="OCI Logging log OCID.") + + +def map_custom_log(custom_log) -> CustomLog | None: + """Convert an OCI JMS custom log object into the local `CustomLog` model.""" + if custom_log is None: + return None + return CustomLog( + namespace=getattr(custom_log, "namespace", None), + bucket_name=getattr(custom_log, "bucket_name", None), + object_name=getattr(custom_log, "object_name", None), + log_group_id=getattr(custom_log, "log_group_id", None), + log_id=getattr(custom_log, "log_id", None), + ) + + +class FleetSummary(BaseModel): + """Summary view of a JMS fleet as returned by list-style fleet APIs.""" + id: Optional[str] = Field(None, description="The OCID of the fleet.") + display_name: Optional[str] = Field(None, description="The name of the fleet.") + description: Optional[str] = Field(None, description="Description of the fleet.") + compartment_id: Optional[str] = Field(None, description="The OCID of the compartment.") + approximate_jre_count: Optional[int] = Field(None, description="Approximate number of Java runtimes.") + approximate_installation_count: Optional[int] = Field( + None, description="Approximate number of Java installations." + ) + approximate_application_count: Optional[int] = Field( + None, description="Approximate number of applications." + ) + approximate_managed_instance_count: Optional[int] = Field( + None, description="Approximate number of managed instances." + ) + approximate_java_server_count: Optional[int] = Field( + None, description="Approximate number of Java servers." + ) + approximate_library_count: Optional[int] = Field( + None, description="Approximate number of libraries." + ) + approximate_library_vulnerability_count: Optional[int] = Field( + None, + description="Approximate number of library vulnerabilities.", + ) + inventory_log: Optional[CustomLog] = Field(None, description="Inventory log configuration.") + operation_log: Optional[CustomLog] = Field(None, description="Operation log configuration.") + is_advanced_features_enabled: Optional[bool] = Field( + None, description="Whether advanced features are enabled." + ) + is_export_setting_enabled: Optional[bool] = Field( + None, description="Whether export setting is enabled." + ) + time_created: Optional[datetime] = Field(None, description="Fleet creation time.") + lifecycle_state: Optional[ + Literal[ + "ACTIVE", + "CREATING", + "DELETED", + "DELETING", + "FAILED", + "NEEDS_ATTENTION", + "UPDATING", + "UNKNOWN_ENUM_VALUE", + ] + ] = Field(None, description="Lifecycle state of the fleet.") + defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field( + None, description="Defined tags for this fleet." + ) + freeform_tags: Optional[Dict[str, str]] = Field(None, description="Free-form tags for this fleet.") + system_tags: Optional[Dict[str, Dict[str, Any]]] = Field( + None, description="System tags for this fleet." + ) + + +def map_fleet_summary(data: oci.jms.models.FleetSummary) -> FleetSummary | None: + """Convert `oci.jms.models.FleetSummary` into the local `FleetSummary` model.""" + if data is None: + return None + return FleetSummary( + id=getattr(data, "id", None), + display_name=getattr(data, "display_name", None), + description=getattr(data, "description", None), + compartment_id=getattr(data, "compartment_id", None), + approximate_jre_count=getattr(data, "approximate_jre_count", None), + approximate_installation_count=getattr(data, "approximate_installation_count", None), + approximate_application_count=getattr(data, "approximate_application_count", None), + approximate_managed_instance_count=getattr(data, "approximate_managed_instance_count", None), + approximate_java_server_count=getattr(data, "approximate_java_server_count", None), + approximate_library_count=getattr(data, "approximate_library_count", None), + approximate_library_vulnerability_count=getattr( + data, "approximate_library_vulnerability_count", None + ), + inventory_log=map_custom_log(getattr(data, "inventory_log", None)), + operation_log=map_custom_log(getattr(data, "operation_log", None)), + is_advanced_features_enabled=getattr(data, "is_advanced_features_enabled", None), + is_export_setting_enabled=getattr(data, "is_export_setting_enabled", None), + time_created=getattr(data, "time_created", None), + lifecycle_state=getattr(data, "lifecycle_state", None), + defined_tags=getattr(data, "defined_tags", None), + freeform_tags=getattr(data, "freeform_tags", None), + system_tags=getattr(data, "system_tags", None), + ) + + +class Fleet(FleetSummary): + """Detailed JMS fleet model for single-fleet reads.""" + pass + + +def map_fleet(data: oci.jms.models.Fleet) -> Fleet | None: + """Convert `oci.jms.models.Fleet` into the local `Fleet` model.""" + if data is None: + return None + return Fleet.model_validate(map_fleet_summary(data).model_dump()) + + +class JmsPluginSummary(BaseModel): + """Summary view of a JMS plugin returned by list APIs.""" + id: Optional[str] = Field(None, description="The OCID of the JMS plugin.") + agent_id: Optional[str] = Field(None, description="The agent OCID.") + agent_type: Optional[Literal["OMA", "OCA", "OCMA", "UNKNOWN_ENUM_VALUE"]] = Field( + None, description="Type of agent reporting the plugin." + ) + lifecycle_state: Optional[ + Literal["ACTIVE", "INACTIVE", "NEEDS_ATTENTION", "DELETED", "UNKNOWN_ENUM_VALUE"] + ] = Field(None, description="Lifecycle state of the plugin.") + availability_status: Optional[ + Literal["ACTIVE", "SILENT", "NOT_AVAILABLE", "UNKNOWN_ENUM_VALUE"] + ] = Field(None, description="Availability status of the plugin.") + fleet_id: Optional[str] = Field(None, description="The associated fleet OCID.") + compartment_id: Optional[str] = Field(None, description="The compartment OCID.") + hostname: Optional[str] = Field(None, description="Hostname of the plugin host.") + os_family: Optional[Literal["LINUX", "WINDOWS", "MACOS", "UNKNOWN", "UNKNOWN_ENUM_VALUE"]] = ( + Field(None, description="Operating system family.") + ) + os_architecture: Optional[str] = Field(None, description="Operating system architecture.") + os_distribution: Optional[str] = Field(None, description="Operating system distribution.") + plugin_version: Optional[str] = Field(None, description="Plugin version.") + time_registered: Optional[datetime] = Field(None, description="Registration time.") + time_last_seen: Optional[datetime] = Field(None, description="Last seen time.") + defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field( + None, description="Defined tags for this plugin." + ) + freeform_tags: Optional[Dict[str, str]] = Field(None, description="Free-form tags for this plugin.") + system_tags: Optional[Dict[str, Dict[str, Any]]] = Field( + None, description="System tags for this plugin." + ) + + +def map_jms_plugin_summary(data: oci.jms.models.JmsPluginSummary) -> JmsPluginSummary | None: + """Convert `oci.jms.models.JmsPluginSummary` into `JmsPluginSummary`.""" + if data is None: + return None + return JmsPluginSummary( + id=getattr(data, "id", None), + agent_id=getattr(data, "agent_id", None), + agent_type=getattr(data, "agent_type", None), + lifecycle_state=getattr(data, "lifecycle_state", None), + availability_status=getattr(data, "availability_status", None), + fleet_id=getattr(data, "fleet_id", None), + compartment_id=getattr(data, "compartment_id", None), + hostname=getattr(data, "hostname", None), + os_family=getattr(data, "os_family", None), + os_architecture=getattr(data, "os_architecture", None), + os_distribution=getattr(data, "os_distribution", None), + plugin_version=getattr(data, "plugin_version", None), + time_registered=getattr(data, "time_registered", None), + time_last_seen=getattr(data, "time_last_seen", None), + defined_tags=getattr(data, "defined_tags", None), + freeform_tags=getattr(data, "freeform_tags", None), + system_tags=getattr(data, "system_tags", None), + ) + + +class JmsPlugin(JmsPluginSummary): + """Detailed JMS plugin model for single-plugin reads.""" + pass + + +def map_jms_plugin(data: oci.jms.models.JmsPlugin) -> JmsPlugin | None: + """Convert `oci.jms.models.JmsPlugin` into the local `JmsPlugin` model.""" + if data is None: + return None + return JmsPlugin.model_validate(map_jms_plugin_summary(data).model_dump()) + + +class JavaRuntimeId(BaseModel): + """Minimal Java runtime identity used in installation-site responses.""" + version: Optional[str] = Field(None, description="Java runtime version.") + vendor: Optional[str] = Field(None, description="Java runtime vendor.") + distribution: Optional[str] = Field(None, description="Java runtime distribution.") + jre_key: Optional[str] = Field(None, description="Unique runtime key.") + + +def map_java_runtime_id(data) -> JavaRuntimeId | None: + """Convert an OCI JMS Java runtime identity object into `JavaRuntimeId`.""" + if data is None: + return None + return JavaRuntimeId( + version=getattr(data, "version", None), + vendor=getattr(data, "vendor", None), + distribution=getattr(data, "distribution", None), + jre_key=getattr(data, "jre_key", None), + ) + + +class OperatingSystem(BaseModel): + """Operating system metadata attached to JMS resources and usage records.""" + family: Optional[Literal["LINUX", "WINDOWS", "MACOS", "UNKNOWN", "UNKNOWN_ENUM_VALUE"]] = ( + Field(None, description="Operating system family.") + ) + name: Optional[str] = Field(None, description="Operating system name.") + distribution: Optional[str] = Field(None, description="Operating system distribution.") + version: Optional[str] = Field(None, description="Operating system version.") + architecture: Optional[str] = Field(None, description="Operating system architecture.") + managed_instance_count: Optional[int] = Field( + None, description="Number of managed instances for this operating system." + ) + container_count: Optional[int] = Field( + None, description="Number of containers for this operating system." + ) + + +def map_operating_system(data) -> OperatingSystem | None: + """Convert an OCI JMS operating system object into the local `OperatingSystem` model.""" + if data is None: + return None + return OperatingSystem( + family=getattr(data, "family", None), + name=getattr(data, "name", None), + distribution=getattr(data, "distribution", None), + version=getattr(data, "version", None), + architecture=getattr(data, "architecture", None), + managed_instance_count=getattr(data, "managed_instance_count", None), + container_count=getattr(data, "container_count", None), + ) + + +class InstallationSiteSummary(BaseModel): + """Summary of a Java installation site discovered within a JMS fleet.""" + installation_key: Optional[str] = Field(None, description="Unique installation identifier.") + managed_instance_id: Optional[str] = Field(None, description="Managed instance OCID.") + jre: Optional[JavaRuntimeId] = Field(None, description="Associated Java runtime identifier.") + security_status: Optional[ + Literal[ + "EARLY_ACCESS", + "UNKNOWN", + "UP_TO_DATE", + "UPDATE_REQUIRED", + "UPGRADE_REQUIRED", + "UNKNOWN_ENUM_VALUE", + ] + ] = Field(None, description="Security status of the Java runtime.") + path: Optional[str] = Field(None, description="Installation path.") + operating_system: Optional[OperatingSystem] = Field( + None, description="Operating system for the installation site." + ) + approximate_application_count: Optional[int] = Field( + None, description="Approximate number of applications using this installation." + ) + time_last_seen: Optional[datetime] = Field(None, description="Last seen time.") + blocklist: Optional[List[Dict[str, Any]]] = Field( + None, description="Blocklist entries associated with the installation." + ) + lifecycle_state: Optional[ + Literal[ + "ACTIVE", + "CREATING", + "DELETED", + "DELETING", + "FAILED", + "NEEDS_ATTENTION", + "UPDATING", + "UNKNOWN_ENUM_VALUE", + ] + ] = Field(None, description="Lifecycle state of the installation site.") + + +def map_installation_site_summary( + data: oci.jms.models.InstallationSiteSummary, +) -> InstallationSiteSummary | None: + """Convert `oci.jms.models.InstallationSiteSummary` into `InstallationSiteSummary`.""" + if data is None: + return None + blocklist = getattr(data, "blocklist", None) + return InstallationSiteSummary( + installation_key=getattr(data, "installation_key", None), + managed_instance_id=getattr(data, "managed_instance_id", None), + jre=map_java_runtime_id(getattr(data, "jre", None)), + security_status=getattr(data, "security_status", None), + path=getattr(data, "path", None), + operating_system=map_operating_system(getattr(data, "operating_system", None)), + approximate_application_count=getattr(data, "approximate_application_count", None), + time_last_seen=getattr(data, "time_last_seen", None), + blocklist=[_oci_to_dict(item) for item in blocklist] if blocklist else None, + lifecycle_state=getattr(data, "lifecycle_state", None), + ) + + +class FleetAgentOsConfiguration(BaseModel): + """OS-specific include and exclude path configuration for JMS fleet agents.""" + include_paths: Optional[List[str]] = Field(None, description="Included filesystem paths.") + exclude_paths: Optional[List[str]] = Field(None, description="Excluded filesystem paths.") + + +def map_fleet_agent_os_configuration(data) -> FleetAgentOsConfiguration | None: + """Convert an OCI JMS fleet-agent OS configuration into the local model.""" + if data is None: + return None + return FleetAgentOsConfiguration( + include_paths=getattr(data, "include_paths", None), + exclude_paths=getattr(data, "exclude_paths", None), + ) + + +class FleetAgentConfiguration(BaseModel): + """Fleet-wide JMS agent configuration returned for a fleet.""" + jre_scan_frequency_in_minutes: Optional[int] = Field( + None, description="JRE scanning frequency in minutes." + ) + java_usage_tracker_processing_frequency_in_minutes: Optional[int] = Field( + None, description="Java usage tracker processing frequency in minutes." + ) + work_request_validity_period_in_days: Optional[int] = Field( + None, description="Validity period for work requests in days." + ) + agent_polling_interval_in_minutes: Optional[int] = Field( + None, description="Agent polling interval in minutes." + ) + is_collecting_managed_instance_metrics_enabled: Optional[bool] = Field( + None, description="Whether managed instance metrics collection is enabled." + ) + is_collecting_usernames_enabled: Optional[bool] = Field( + None, description="Whether username collection is enabled." + ) + is_capturing_ip_address_and_fqdn_enabled: Optional[bool] = Field( + None, description="Whether IP address and FQDN capture is enabled." + ) + is_libraries_scan_enabled: Optional[bool] = Field( + None, description="Whether library scanning is enabled." + ) + linux_configuration: Optional[FleetAgentOsConfiguration] = Field( + None, description="Linux-specific agent configuration." + ) + windows_configuration: Optional[FleetAgentOsConfiguration] = Field( + None, description="Windows-specific agent configuration." + ) + mac_os_configuration: Optional[FleetAgentOsConfiguration] = Field( + None, description="macOS-specific agent configuration." + ) + time_last_modified: Optional[datetime] = Field(None, description="Last modified time.") + + +def map_fleet_agent_configuration( + data: oci.jms.models.FleetAgentConfiguration, +) -> FleetAgentConfiguration | None: + """Convert `oci.jms.models.FleetAgentConfiguration` into `FleetAgentConfiguration`.""" + if data is None: + return None + return FleetAgentConfiguration( + jre_scan_frequency_in_minutes=getattr(data, "jre_scan_frequency_in_minutes", None), + java_usage_tracker_processing_frequency_in_minutes=getattr( + data, "java_usage_tracker_processing_frequency_in_minutes", None + ), + work_request_validity_period_in_days=getattr( + data, "work_request_validity_period_in_days", None + ), + agent_polling_interval_in_minutes=getattr(data, "agent_polling_interval_in_minutes", None), + is_collecting_managed_instance_metrics_enabled=getattr( + data, "is_collecting_managed_instance_metrics_enabled", None + ), + is_collecting_usernames_enabled=getattr(data, "is_collecting_usernames_enabled", None), + is_capturing_ip_address_and_fqdn_enabled=getattr( + data, "is_capturing_ip_address_and_fqdn_enabled", None + ), + is_libraries_scan_enabled=getattr(data, "is_libraries_scan_enabled", None), + linux_configuration=map_fleet_agent_os_configuration( + getattr(data, "linux_configuration", None) + ), + windows_configuration=map_fleet_agent_os_configuration( + getattr(data, "windows_configuration", None) + ), + mac_os_configuration=map_fleet_agent_os_configuration( + getattr(data, "mac_os_configuration", None) + ), + time_last_modified=getattr(data, "time_last_modified", None), + ) + + +class FleetAdvancedFeatureConfiguration(BaseModel): + """Advanced feature configuration attached to a JMS fleet.""" + analytic_namespace: Optional[str] = Field(None, description="Analytics namespace.") + analytic_bucket_name: Optional[str] = Field(None, description="Analytics bucket name.") + lcm: Optional[Dict[str, Any]] = Field(None, description="LCM configuration.") + crypto_event_analysis: Optional[Dict[str, Any]] = Field( + None, description="Crypto event analysis configuration." + ) + advanced_usage_tracking: Optional[Dict[str, Any]] = Field( + None, description="Advanced usage tracking configuration." + ) + jfr_recording: Optional[Dict[str, Any]] = Field(None, description="JFR recording configuration.") + performance_tuning_analysis: Optional[Dict[str, Any]] = Field( + None, description="Performance tuning analysis configuration." + ) + java_migration_analysis: Optional[Dict[str, Any]] = Field( + None, description="Java migration analysis configuration." + ) + time_last_modified: Optional[datetime] = Field(None, description="Last modified time.") + + +def map_fleet_advanced_feature_configuration( + data: oci.jms.models.FleetAdvancedFeatureConfiguration, +) -> FleetAdvancedFeatureConfiguration | None: + """Convert fleet advanced feature configuration into a serializable local model.""" + if data is None: + return None + return FleetAdvancedFeatureConfiguration( + analytic_namespace=getattr(data, "analytic_namespace", None), + analytic_bucket_name=getattr(data, "analytic_bucket_name", None), + lcm=_oci_to_dict(getattr(data, "lcm", None)), + crypto_event_analysis=_oci_to_dict(getattr(data, "crypto_event_analysis", None)), + advanced_usage_tracking=_oci_to_dict(getattr(data, "advanced_usage_tracking", None)), + jfr_recording=_oci_to_dict(getattr(data, "jfr_recording", None)), + performance_tuning_analysis=_oci_to_dict( + getattr(data, "performance_tuning_analysis", None) + ), + java_migration_analysis=_oci_to_dict(getattr(data, "java_migration_analysis", None)), + time_last_modified=getattr(data, "time_last_modified", None), + ) + + +class ManagedInstanceUsage(BaseModel): + """Managed instance usage summary returned by JMS usage aggregation APIs.""" + managed_instance_id: Optional[str] = Field(None, description="Managed instance OCID.") + managed_instance_type: Optional[ + Literal[ + "ORACLE_MANAGEMENT_AGENT", + "ORACLE_CLOUD_AGENT", + "ORACLE_CONTAINER_MANAGEMENT_AGENT", + "UNKNOWN_ENUM_VALUE", + ] + ] = Field(None, description="Managed instance type.") + hostname: Optional[str] = Field(None, description="Hostname of the managed instance.") + host_id: Optional[str] = Field(None, description="Host OCID or host identifier.") + ip_addresses: Optional[List[str]] = Field(None, description="IP addresses of the managed instance.") + hostnames: Optional[List[str]] = Field(None, description="Hostnames associated with the instance.") + fqdns: Optional[List[str]] = Field(None, description="FQDNs associated with the instance.") + operating_system: Optional[OperatingSystem] = Field( + None, description="Operating system information." + ) + agent: Optional[Dict[str, Any]] = Field(None, description="Agent details.") + cluster_details: Optional[Dict[str, Any]] = Field(None, description="Cluster details.") + approximate_application_count: Optional[int] = Field( + None, description="Approximate application count." + ) + approximate_installation_count: Optional[int] = Field( + None, description="Approximate installation count." + ) + approximate_jre_count: Optional[int] = Field(None, description="Approximate JRE count.") + drs_file_status: Optional[ + Literal["PRESENT", "ABSENT", "MISMATCH", "NOT_CONFIGURED", "UNKNOWN_ENUM_VALUE"] + ] = Field(None, description="DRS file status.") + application_invoked_by: Optional[str] = Field( + None, description="Username or principal that invoked the application." + ) + time_start: Optional[datetime] = Field(None, description="Usage summary start time.") + time_end: Optional[datetime] = Field(None, description="Usage summary end time.") + time_first_seen: Optional[datetime] = Field(None, description="First seen time.") + time_last_seen: Optional[datetime] = Field(None, description="Last seen time.") + + +def map_managed_instance_usage( + data: oci.jms.models.ManagedInstanceUsage, +) -> ManagedInstanceUsage | None: + """Convert `oci.jms.models.ManagedInstanceUsage` into `ManagedInstanceUsage`.""" + if data is None: + return None + return ManagedInstanceUsage( + managed_instance_id=getattr(data, "managed_instance_id", None), + managed_instance_type=getattr(data, "managed_instance_type", None), + hostname=getattr(data, "hostname", None), + host_id=getattr(data, "host_id", None), + ip_addresses=getattr(data, "ip_addresses", None), + hostnames=getattr(data, "hostnames", None), + fqdns=getattr(data, "fqdns", None), + operating_system=map_operating_system(getattr(data, "operating_system", None)), + agent=_oci_to_dict(getattr(data, "agent", None)), + cluster_details=_oci_to_dict(getattr(data, "cluster_details", None)), + approximate_application_count=getattr(data, "approximate_application_count", None), + approximate_installation_count=getattr(data, "approximate_installation_count", None), + approximate_jre_count=getattr(data, "approximate_jre_count", None), + drs_file_status=getattr(data, "drs_file_status", None), + application_invoked_by=getattr(data, "application_invoked_by", None), + time_start=getattr(data, "time_start", None), + time_end=getattr(data, "time_end", None), + time_first_seen=getattr(data, "time_first_seen", None), + time_last_seen=getattr(data, "time_last_seen", None), + ) + + +class ResourceInventory(BaseModel): + """High-level inventory counts for JMS resources in a compartment.""" + active_fleet_count: Optional[int] = Field(None, description="Number of active fleets.") + managed_instance_count: Optional[int] = Field(None, description="Number of managed instances.") + jre_count: Optional[int] = Field(None, description="Number of Java runtimes.") + installation_count: Optional[int] = Field(None, description="Number of Java installations.") + application_count: Optional[int] = Field(None, description="Number of applications.") + + +def map_resource_inventory(data: oci.jms.models.ResourceInventory) -> ResourceInventory | None: + """Convert `oci.jms.models.ResourceInventory` into `ResourceInventory`.""" + if data is None: + return None + return ResourceInventory( + active_fleet_count=getattr(data, "active_fleet_count", None), + managed_instance_count=getattr(data, "managed_instance_count", None), + jre_count=getattr(data, "jre_count", None), + installation_count=getattr(data, "installation_count", None), + application_count=getattr(data, "application_count", None), + ) diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py new file mode 100644 index 00000000..d197cbd7 --- /dev/null +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py @@ -0,0 +1,627 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +import os +from datetime import datetime +from logging import Logger +from typing import Literal, Optional + +import oci +from fastmcp import FastMCP +from oracle.oci_jms_mcp_server.models import ( + Fleet, + FleetAdvancedFeatureConfiguration, + FleetAgentConfiguration, + FleetSummary, + InstallationSiteSummary, + JmsPlugin, + JmsPluginSummary, + ManagedInstanceUsage, + ResourceInventory, + map_fleet, + map_fleet_advanced_feature_configuration, + map_fleet_agent_configuration, + map_fleet_summary, + map_installation_site_summary, + map_jms_plugin, + map_jms_plugin_summary, + map_managed_instance_usage, + map_resource_inventory, +) +from pydantic import Field + +from . import __project__, __version__ +from .util import get_jms_service_endpoint + +logger = Logger(__name__, level="INFO") + +mcp = FastMCP(name=__project__) + +_UNSUPPORTED_ENV_ALIASES = { + "OCI_CONFIG": "OCI_CONFIG_FILE", + "OCI_PROFILE": "OCI_CONFIG_PROFILE", +} + + +def _normalize_enum(value: Optional[str]) -> Optional[str]: + """Normalize flexible enum input into OCI SDK-style uppercase values.""" + if value is None: + return None + normalized = value.strip() + if not normalized: + return None + return normalized.upper().replace("-", "_").replace(" ", "_") + + +def _normalize_enum_list(values: Optional[list[str]]) -> Optional[list[str]]: + """Normalize lists of enum-like values.""" + if values is None: + return None + return [_normalize_enum(value) for value in values] + + +def _normalized_key(value: str) -> str: + """Canonicalize user input for case-insensitive matching of mixed-case SDK values.""" + return "".join(ch for ch in value.strip().lower() if ch.isalnum()) + + +def _normalize_choice(value: Optional[str], allowed_values: list[str]) -> Optional[str]: + """Map flexible user input to one of the SDK's exact allowed string values.""" + if value is None: + return None + normalized = value.strip() + if not normalized: + return None + + by_key = {_normalized_key(item): item for item in allowed_values} + return by_key.get(_normalized_key(normalized), value) + + +def _normalize_choice_list( + values: Optional[list[str]], allowed_values: list[str] +) -> Optional[list[str]]: + """Map a list of flexible user inputs to exact SDK values where possible.""" + if values is None: + return None + normalized = [_normalize_choice(value, allowed_values) for value in values] + normalized = [value for value in normalized if value is not None] + return normalized or None + + +def _omit_none(**kwargs): + """Drop only None values so optional OCI SDK kwargs are not sent spuriously.""" + return {key: value for key, value in kwargs.items() if value is not None} + + +def _parse_rfc3339(value: Optional[str]) -> Optional[datetime]: + """Convert an optional RFC3339 timestamp string into a datetime.""" + if value is None: + return None + return datetime.fromisoformat(value.replace("Z", "+00:00")) + + +def _warn_on_unsupported_env_aliases(): + """Warn when env vars from the separate generic OCI API MCP project are used.""" + for alias, canonical in _UNSUPPORTED_ENV_ALIASES.items(): + if os.getenv(alias) and not os.getenv(canonical): + logger.warning( + f"{alias} is not used by oracle.oci-jms-mcp-server; use {canonical} instead." + ) + + +def _load_oci_config() -> dict: + """Load OCI SDK config using the JMS server's supported env var names.""" + # Warn early because users often copy client config from the generic OCI API MCP server. + _warn_on_unsupported_env_aliases() + return oci.config.from_file( + file_location=os.getenv("OCI_CONFIG_FILE", oci.config.DEFAULT_LOCATION), + profile_name=os.getenv("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE), + ) + + +def _build_security_token_signer(config: dict) -> oci.auth.signers.SecurityTokenSigner: + """Create a security-token signer from the OCI config file referenced key and token.""" + private_key = oci.signer.load_private_key_from_file(config["key_file"]) + token_file = os.path.expanduser(config["security_token_file"]) + with open(token_file, "r") as f: + token = f.read().strip() + return oci.auth.signers.SecurityTokenSigner(token, private_key) + + +def get_jms_client(): + """Construct a fresh OCI Java Management Service client for the active profile.""" + logger.info("entering get_jms_client") + config = _load_oci_config() + user_agent_name = __project__.split("oracle.", 1)[1].split("-server", 1)[0] + config["additional_user_agent"] = f"{user_agent_name}/{__version__}" + signer = _build_security_token_signer(config) + service_endpoint = get_jms_service_endpoint(config) + if service_endpoint: + logger.info(f"Using JMS endpoint override from JMS_TEST_ENVIRONMENT: {service_endpoint}") + client_kwargs = _omit_none(signer=signer, service_endpoint=service_endpoint) + return oci.jms.JavaManagementServiceClient(config, **client_kwargs) + + +@mcp.tool(description="List Java Management Service fleets in a compartment.") +def list_fleets( + compartment_id: Optional[str] = Field( + None, + description="The OCID of the compartment in which to list fleets. Required unless `id` is provided.", + ), + id: Optional[str] = Field(None, description="The OCID of a specific fleet to filter for."), + lifecycle_state: Optional[str] = Field( + None, + description=( + "Filter fleets by lifecycle state. Accepted values include ACTIVE, CREATING, " + "DELETED, DELETING, FAILED, NEEDS_ATTENTION, and UPDATING." + ), + ), + display_name: Optional[str] = Field(None, description="Filter fleets by exact display name."), + display_name_contains: Optional[str] = Field( + None, + description="Filter fleets whose display name contains this value.", + ), + limit: Optional[int] = Field(None, description="Maximum number of fleets to return.", ge=1), + sort_order: Optional[str] = Field( + None, description="Sort order for the fleet results: ASC or DESC." + ), + sort_by: Optional[str] = Field( + None, + description="Field to sort fleets by.", + ), +) -> list[FleetSummary]: + """List fleets visible in the requested compartment, handling pagination transparently.""" + fleets: list[FleetSummary] = [] + + try: + client = get_jms_client() + + response: oci.response.Response | None = None + has_next_page = True + next_page: Optional[str] = None + + while has_next_page and (limit is None or len(fleets) < limit): + # Continue paging until OCI stops returning pages or the caller-supplied limit is met. + response = client.list_fleets(**_omit_none( + compartment_id=compartment_id, + id=id, + lifecycle_state=_normalize_enum(lifecycle_state), + display_name=display_name, + display_name_contains=display_name_contains, + limit=limit, + sort_order=_normalize_enum(sort_order), + sort_by=_normalize_choice(sort_by, ["displayName", "timeCreated"]), + page=next_page, + )) + has_next_page = response.has_next_page + next_page = response.next_page if hasattr(response, "next_page") else None + + for item in response.data.items: + fleets.append(map_fleet_summary(item)) + if limit is not None and len(fleets) >= limit: + break + + logger.info(f"Found {len(fleets)} fleets") + return fleets + except Exception as e: + logger.error(f"Error in list_fleets tool: {str(e)}") + raise + + +@mcp.tool(description="Get a JMS fleet by its OCID.") +def get_fleet(fleet_id: str = Field(..., description="The OCID of the fleet.")) -> Fleet: + """Fetch a single fleet by OCID and convert the OCI SDK model into the local Pydantic model.""" + try: + client = get_jms_client() + response: oci.response.Response = client.get_fleet(fleet_id=fleet_id) + logger.info("Found fleet") + return map_fleet(response.data) + except Exception as e: + logger.error(f"Error in get_fleet tool: {str(e)}") + raise + + +@mcp.tool(description="List JMS plugins in a compartment or fleet.") +def list_jms_plugins( + compartment_id: Optional[str] = Field( + None, + description="The OCID of the compartment in which to list plugins.", + ), + compartment_id_in_subtree: bool = Field( + False, + description="Whether to gather plugin information from the compartment subtree.", + ), + id: Optional[str] = Field(None, description="The OCID of a specific JMS plugin."), + fleet_id: Optional[str] = Field(None, description="Filter by fleet OCID."), + agent_id: Optional[str] = Field(None, description="Filter by agent OCID."), + lifecycle_state: Optional[str] = Field( + None, + description=( + "Filter plugins by lifecycle state. Accepted values include ACTIVE, INACTIVE, " + "NEEDS_ATTENTION, and DELETED." + ), + ), + availability_status: Optional[str] = Field( + None, + description="Filter plugins by availability status: ACTIVE, SILENT, or NOT_AVAILABLE.", + ), + agent_type: Optional[str] = Field( + None, + description="Filter plugins by agent type: OMA or OCA.", + ), + time_registered_less_than_or_equal_to: Optional[str] = Field( + None, + description="Only return plugins registered at or before this RFC3339 timestamp.", + ), + time_last_seen_less_than_or_equal_to: Optional[str] = Field( + None, + description="Only return plugins last seen at or before this RFC3339 timestamp.", + ), + hostname_contains: Optional[str] = Field( + None, + description="Filter the list with hostname contains this value.", + ), + limit: Optional[int] = Field(None, description="Maximum number of plugins to return.", ge=1), + sort_order: Optional[str] = Field( + None, description="Sort order for the plugin results: ASC or DESC." + ), + sort_by: Optional[str] = Field(None, description="Field to sort plugins by."), +) -> list[JmsPluginSummary]: + """List JMS plugins with optional fleet, agent, lifecycle, and time-based filtering.""" + plugins: list[JmsPluginSummary] = [] + + try: + client = get_jms_client() + + response: oci.response.Response | None = None + has_next_page = True + next_page: Optional[str] = None + + while has_next_page and (limit is None or len(plugins) < limit): + # Parse timestamp filters up front so the SDK receives native datetimes. + response = client.list_jms_plugins(**_omit_none( + compartment_id=compartment_id, + compartment_id_in_subtree=compartment_id_in_subtree, + id=id, + fleet_id=fleet_id, + agent_id=agent_id, + lifecycle_state=_normalize_enum(lifecycle_state), + availability_status=_normalize_enum(availability_status), + agent_type=_normalize_enum(agent_type), + time_registered_less_than_or_equal_to=_parse_rfc3339( + time_registered_less_than_or_equal_to + ), + time_last_seen_less_than_or_equal_to=_parse_rfc3339( + time_last_seen_less_than_or_equal_to + ), + hostname_contains=hostname_contains, + limit=limit, + sort_order=_normalize_enum(sort_order), + sort_by=_normalize_choice( + sort_by, + [ + "id", + "timeLastSeen", + "timeRegistered", + "hostname", + "agentId", + "agentType", + "lifecycleState", + "availabilityStatus", + "fleetId", + "compartmentId", + "osFamily", + "osArchitecture", + "osDistribution", + "pluginVersion", + ], + ), + page=next_page, + )) + has_next_page = response.has_next_page + next_page = response.next_page if hasattr(response, "next_page") else None + + for item in response.data.items: + plugins.append(map_jms_plugin_summary(item)) + if limit is not None and len(plugins) >= limit: + break + + logger.info(f"Found {len(plugins)} JMS plugins") + return plugins + except Exception as e: + logger.error(f"Error in list_jms_plugins tool: {str(e)}") + raise + + +@mcp.tool(description="Get a JMS plugin by its OCID.") +def get_jms_plugin( + jms_plugin_id: str = Field(..., description="The OCID of the JMS plugin.") +) -> JmsPlugin: + """Fetch a single JMS plugin by OCID.""" + try: + client = get_jms_client() + response: oci.response.Response = client.get_jms_plugin(jms_plugin_id=jms_plugin_id) + logger.info("Found JMS plugin") + return map_jms_plugin(response.data) + except Exception as e: + logger.error(f"Error in get_jms_plugin tool: {str(e)}") + raise + + +@mcp.tool(description="List Java installation sites in a JMS fleet.") +def list_installation_sites( + fleet_id: str = Field(..., description="The OCID of the fleet."), + jre_vendor: Optional[str] = Field(None, description="Filter by JRE vendor."), + jre_distribution: Optional[str] = Field(None, description="Filter by JRE distribution."), + jre_version: Optional[str] = Field(None, description="Filter by JRE version."), + installation_path: Optional[str] = Field(None, description="Filter by installation path."), + application_id: Optional[str] = Field(None, description="Filter by application identifier."), + managed_instance_id: Optional[str] = Field( + None, description="Filter by managed instance identifier." + ), + os_family: Optional[list[str]] = Field(None, description="Filter by operating system family."), + jre_security_status: Optional[str] = Field( + None, + description=( + "Filter by JRE security status. Accepted values include EARLY_ACCESS, UNKNOWN, " + "UP_TO_DATE, UPDATE_REQUIRED, and UPGRADE_REQUIRED." + ), + ), + path_contains: Optional[str] = Field( + None, + description="Filter installation sites where the path contains this value.", + ), + time_start: Optional[str] = Field( + None, + description="Search start time in RFC3339 format.", + ), + time_end: Optional[str] = Field( + None, + description="Search end time in RFC3339 format.", + ), + limit: Optional[int] = Field(None, description="Maximum number of installation sites to return.", ge=1), + sort_order: Optional[str] = Field( + None, description="Sort order for installation sites: ASC or DESC." + ), + sort_by: Optional[str] = Field(None, description="Field to sort installation sites by."), +) -> list[InstallationSiteSummary]: + """List Java installation sites in a fleet with optional runtime, host, and time filters.""" + installation_sites: list[InstallationSiteSummary] = [] + + try: + client = get_jms_client() + + response: oci.response.Response | None = None + has_next_page = True + next_page: Optional[str] = None + + while has_next_page and (limit is None or len(installation_sites) < limit): + # Convert RFC3339 strings before handing them to the OCI SDK search parameters. + response = client.list_installation_sites(**_omit_none( + fleet_id=fleet_id, + jre_vendor=jre_vendor, + jre_distribution=jre_distribution, + jre_version=jre_version, + installation_path=installation_path, + application_id=application_id, + managed_instance_id=managed_instance_id, + os_family=_normalize_enum_list(os_family), + jre_security_status=_normalize_enum(jre_security_status), + path_contains=path_contains, + time_start=_parse_rfc3339(time_start), + time_end=_parse_rfc3339(time_end), + limit=limit, + sort_order=_normalize_enum(sort_order), + sort_by=_normalize_choice( + sort_by, + [ + "managedInstanceId", + "jreDistribution", + "jreVendor", + "jreVersion", + "path", + "approximateApplicationCount", + "osName", + "securityStatus", + ], + ), + page=next_page, + )) + has_next_page = response.has_next_page + next_page = response.next_page if hasattr(response, "next_page") else None + + for item in response.data.items: + installation_sites.append(map_installation_site_summary(item)) + if limit is not None and len(installation_sites) >= limit: + break + + logger.info(f"Found {len(installation_sites)} installation sites") + return installation_sites + except Exception as e: + logger.error(f"Error in list_installation_sites tool: {str(e)}") + raise + + +@mcp.tool(description="Get fleet agent configuration for a JMS fleet.") +def get_fleet_agent_configuration( + fleet_id: str = Field(..., description="The OCID of the fleet.") +) -> FleetAgentConfiguration: + """Return the fleet-wide agent configuration for a JMS fleet.""" + try: + client = get_jms_client() + response: oci.response.Response = client.get_fleet_agent_configuration(fleet_id=fleet_id) + logger.info("Found fleet agent configuration") + return map_fleet_agent_configuration(response.data) + except Exception as e: + logger.error(f"Error in get_fleet_agent_configuration tool: {str(e)}") + raise + + +@mcp.tool(description="Get advanced feature configuration for a JMS fleet.") +def get_fleet_advanced_feature_configuration( + fleet_id: str = Field(..., description="The OCID of the fleet.") +) -> FleetAdvancedFeatureConfiguration: + """Return the advanced feature configuration for a JMS fleet.""" + try: + client = get_jms_client() + response: oci.response.Response = client.get_fleet_advanced_feature_configuration( + fleet_id=fleet_id + ) + logger.info("Found fleet advanced feature configuration") + return map_fleet_advanced_feature_configuration(response.data) + except Exception as e: + logger.error(f"Error in get_fleet_advanced_feature_configuration tool: {str(e)}") + raise + + +@mcp.tool(description="Summarize JMS resource inventory in a compartment.") +def summarize_resource_inventory( + compartment_id: Optional[str] = Field( + None, + description="The OCID of the compartment in which to summarize inventory.", + ), + compartment_id_in_subtree: bool = Field( + False, + description="Whether to include the compartment subtree in the summary.", + ), + time_start: Optional[str] = Field( + None, + description="Summary start time in RFC3339 format.", + ), + time_end: Optional[str] = Field( + None, + description="Summary end time in RFC3339 format.", + ), +) -> ResourceInventory: + """Summarize high-level JMS resource counts for a compartment and optional time range.""" + try: + client = get_jms_client() + response: oci.response.Response = client.summarize_resource_inventory(**_omit_none( + compartment_id=compartment_id, + compartment_id_in_subtree=compartment_id_in_subtree, + time_start=_parse_rfc3339(time_start), + time_end=_parse_rfc3339(time_end), + )) + logger.info("Summarized resource inventory") + return map_resource_inventory(response.data) + except Exception as e: + logger.error(f"Error in summarize_resource_inventory tool: {str(e)}") + raise + + +@mcp.tool(description="Summarize managed instance usage within a JMS fleet.") +def summarize_managed_instance_usage( + fleet_id: str = Field(..., description="The OCID of the fleet."), + managed_instance_id: Optional[str] = Field( + None, + description="Filter by managed instance OCID.", + ), + managed_instance_type: Optional[str] = Field( + None, + description=( + "Filter by managed instance type. Accepted values include " + "ORACLE_MANAGEMENT_AGENT and ORACLE_CLOUD_AGENT." + ), + ), + jre_vendor: Optional[str] = Field(None, description="Filter by JRE vendor."), + jre_distribution: Optional[str] = Field(None, description="Filter by JRE distribution."), + jre_version: Optional[str] = Field(None, description="Filter by JRE version."), + installation_path: Optional[str] = Field(None, description="Filter by installation path."), + application_id: Optional[str] = Field(None, description="Filter by application identifier."), + fields: Optional[list[str]] = Field( + None, description="Additional fields to include in each usage record." + ), + time_start: Optional[str] = Field(None, description="Summary start time in RFC3339 format."), + time_end: Optional[str] = Field(None, description="Summary end time in RFC3339 format."), + limit: Optional[int] = Field(None, description="Maximum number of usage records to return.", ge=1), + sort_order: Optional[str] = Field( + None, description="Sort order for the usage results: ASC or DESC." + ), + sort_by: Optional[str] = Field(None, description="Field to sort usage results by."), + os_family: Optional[list[str]] = Field(None, description="Filter by operating system family."), + hostname_contains: Optional[str] = Field( + None, + description="Filter the list with hostname contains this value.", + ), + library_key: Optional[str] = Field(None, description="Filter by library key."), +) -> list[ManagedInstanceUsage]: + """Summarize managed instance usage records for a fleet with optional filters.""" + usage: list[ManagedInstanceUsage] = [] + + try: + client = get_jms_client() + + response: oci.response.Response | None = None + has_next_page = True + next_page: Optional[str] = None + + while has_next_page and (limit is None or len(usage) < limit): + # The summarize API is paginated even though it returns aggregate-style records. + response = client.summarize_managed_instance_usage(**_omit_none( + fleet_id=fleet_id, + managed_instance_id=managed_instance_id, + managed_instance_type=_normalize_enum(managed_instance_type), + jre_vendor=jre_vendor, + jre_distribution=jre_distribution, + jre_version=jre_version, + installation_path=installation_path, + application_id=application_id, + fields=_normalize_choice_list( + fields, + [ + "approximateJreCount", + "approximateInstallationCount", + "approximateApplicationCount", + ], + ), + time_start=_parse_rfc3339(time_start), + time_end=_parse_rfc3339(time_end), + limit=limit, + sort_order=_normalize_enum(sort_order), + sort_by=_normalize_choice( + sort_by, + [ + "timeFirstSeen", + "timeLastSeen", + "approximateJreCount", + "approximateInstallationCount", + "approximateApplicationCount", + "osName", + ], + ), + os_family=_normalize_enum_list(os_family), + hostname_contains=hostname_contains, + library_key=library_key, + page=next_page, + )) + has_next_page = response.has_next_page + next_page = response.next_page if hasattr(response, "next_page") else None + + for item in response.data.items: + usage.append(map_managed_instance_usage(item)) + if limit is not None and len(usage) >= limit: + break + + logger.info(f"Found {len(usage)} managed instance usage records") + return usage + except Exception as e: + logger.error(f"Error in summarize_managed_instance_usage tool: {str(e)}") + raise + + +def main(): + """Run the JMS MCP server over stdio by default or HTTP when host and port are provided.""" + host = os.getenv("ORACLE_MCP_HOST") + port = os.getenv("ORACLE_MCP_PORT") + + if host and port: + mcp.run(transport="http", host=host, port=int(port)) + else: + mcp.run() + + +if __name__ == "__main__": + main() diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py new file mode 100644 index 00000000..ccccf7f1 --- /dev/null +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py @@ -0,0 +1,143 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from datetime import datetime, UTC + +import oci + +from oracle.oci_jms_mcp_server.models import ( + map_fleet, + map_fleet_advanced_feature_configuration, + map_fleet_agent_configuration, + map_fleet_summary, + map_installation_site_summary, + map_jms_plugin, + map_jms_plugin_summary, + map_managed_instance_usage, + map_resource_inventory, +) + + +def test_map_fleet_summary(): + now = datetime.now(UTC) + fleet = oci.jms.models.FleetSummary( + id="fleet1", + display_name="Fleet 1", + compartment_id="compartment1", + time_created=now, + ) + + result = map_fleet_summary(fleet) + + assert result.id == "fleet1" + assert result.display_name == "Fleet 1" + assert result.time_created == now + + +def test_map_fleet(): + fleet = oci.jms.models.Fleet(id="fleet1", display_name="Fleet 1") + result = map_fleet(fleet) + assert result.id == "fleet1" + + +def test_map_jms_plugin_summary(): + now = datetime.now(UTC) + plugin = oci.jms.models.JmsPluginSummary( + id="plugin1", + agent_type="OMA", + time_last_seen=now, + ) + + result = map_jms_plugin_summary(plugin) + + assert result.id == "plugin1" + assert result.agent_type == "OMA" + assert result.time_last_seen == now + + +def test_map_jms_plugin(): + plugin = oci.jms.models.JmsPlugin(id="plugin1", hostname="host1") + result = map_jms_plugin(plugin) + assert result.hostname == "host1" + + +def test_map_installation_site_summary_with_nested_values(): + now = datetime.now(UTC) + site = oci.jms.models.InstallationSiteSummary( + installation_key="install1", + managed_instance_id="mi1", + jre=oci.jms.models.JavaRuntimeId(version="17", vendor="Oracle", distribution="JDK"), + operating_system=oci.jms.models.OperatingSystem( + family="LINUX", + name="Linux", + version="9", + architecture="x86_64", + ), + time_last_seen=now, + ) + + result = map_installation_site_summary(site) + + assert result.installation_key == "install1" + assert result.jre.version == "17" + assert result.operating_system.family == "LINUX" + assert result.time_last_seen == now + + +def test_map_fleet_agent_configuration_with_nested_os_configuration(): + config = oci.jms.models.FleetAgentConfiguration( + jre_scan_frequency_in_minutes=60, + java_usage_tracker_processing_frequency_in_minutes=15, + linux_configuration=oci.jms.models.FleetAgentOsConfiguration(include_paths=["/usr/java"]), + ) + + result = map_fleet_agent_configuration(config) + + assert result.jre_scan_frequency_in_minutes == 60 + assert result.linux_configuration.include_paths == ["/usr/java"] + + +def test_map_fleet_advanced_feature_configuration(): + config = oci.jms.models.FleetAdvancedFeatureConfiguration( + analytic_namespace="analytics_ns", + analytic_bucket_name="bucket", + ) + + result = map_fleet_advanced_feature_configuration(config) + + assert result.analytic_namespace == "analytics_ns" + assert result.analytic_bucket_name == "bucket" + + +def test_map_managed_instance_usage(): + now = datetime.now(UTC) + usage = oci.jms.models.ManagedInstanceUsage( + managed_instance_id="mi1", + managed_instance_type="ORACLE_MANAGEMENT_AGENT", + hostname="host1", + time_first_seen=now, + ) + + result = map_managed_instance_usage(usage) + + assert result.managed_instance_id == "mi1" + assert result.hostname == "host1" + assert result.time_first_seen == now + + +def test_map_resource_inventory(): + inventory = oci.jms.models.ResourceInventory( + active_fleet_count=1, + managed_instance_count=2, + jre_count=3, + installation_count=4, + application_count=5, + ) + + result = map_resource_inventory(inventory) + + assert result.active_fleet_count == 1 + assert result.application_count == 5 diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py new file mode 100644 index 00000000..39053a72 --- /dev/null +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py @@ -0,0 +1,765 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +import os +from unittest.mock import MagicMock, create_autospec, mock_open, patch + +import fastmcp.exceptions +import oci +import pytest +from fastmcp import Client +from oracle.oci_jms_mcp_server import server +from oracle.oci_jms_mcp_server.server import mcp + + +class TestJmsTools: + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_fleets_paginates(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response_page_1 = create_autospec(oci.response.Response) + response_page_1.data = oci.jms.models.FleetCollection( + items=[ + oci.jms.models.FleetSummary(id="fleet1", display_name="Fleet 1"), + ] + ) + response_page_1.has_next_page = True + response_page_1.next_page = "token" + + response_page_2 = create_autospec(oci.response.Response) + response_page_2.data = oci.jms.models.FleetCollection( + items=[ + oci.jms.models.FleetSummary(id="fleet2", display_name="Fleet 2"), + ] + ) + response_page_2.has_next_page = False + response_page_2.next_page = None + + mock_client.list_fleets.side_effect = [response_page_1, response_page_2] + + async with Client(mcp) as client: + result = ( + await client.call_tool("list_fleets", {"compartment_id": "ocid1.compartment.oc1..test"}) + ).structured_content["result"] + + assert [item["id"] for item in result] == ["fleet1", "fleet2"] + assert "lifecycle_state" not in mock_client.list_fleets.call_args.kwargs + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_fleets_normalizes_lifecycle_state(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.FleetCollection( + items=[oci.jms.models.FleetSummary(id="fleet1", display_name="Fleet 1")] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_fleets.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "list_fleets", + { + "compartment_id": "ocid1.compartment.oc1..test", + "lifecycle_state": "needs-attention", + }, + ) + ).structured_content["result"] + + assert result[0]["id"] == "fleet1" + assert ( + mock_client.list_fleets.call_args.kwargs["lifecycle_state"] == "NEEDS_ATTENTION" + ) + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_fleets_normalizes_sort_order(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.FleetCollection( + items=[oci.jms.models.FleetSummary(id="fleet1", display_name="Fleet 1")] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_fleets.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "list_fleets", + { + "compartment_id": "ocid1.compartment.oc1..test", + "sort_order": "desc", + }, + ) + ).structured_content["result"] + + assert result[0]["id"] == "fleet1" + assert mock_client.list_fleets.call_args.kwargs["sort_order"] == "DESC" + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_fleets_ignores_blank_lifecycle_state(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.FleetCollection( + items=[oci.jms.models.FleetSummary(id="fleet1", display_name="Fleet 1")] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_fleets.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "list_fleets", + { + "compartment_id": "ocid1.compartment.oc1..test", + "lifecycle_state": " ", + }, + ) + ).structured_content["result"] + + assert result[0]["id"] == "fleet1" + assert "lifecycle_state" not in mock_client.list_fleets.call_args.kwargs + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_fleets_normalizes_sort_by(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.FleetCollection( + items=[oci.jms.models.FleetSummary(id="fleet1", display_name="Fleet 1")] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_fleets.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "list_fleets", + { + "compartment_id": "ocid1.compartment.oc1..test", + "sort_by": "time_created", + }, + ) + ).structured_content["result"] + + assert result[0]["id"] == "fleet1" + assert mock_client.list_fleets.call_args.kwargs["sort_by"] == "timeCreated" + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_fleets_exception(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + mock_client.list_fleets.side_effect = oci.exceptions.ServiceError( + status=500, + code="InternalServerError", + message="Internal server error", + opc_request_id="req", + headers={}, + ) + + async with Client(mcp) as client: + with pytest.raises(fastmcp.exceptions.ToolError): + await client.call_tool("list_fleets", {"compartment_id": "ocid1.compartment.oc1..test"}) + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_get_fleet(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.Fleet(id="fleet1", display_name="Fleet 1") + mock_client.get_fleet.return_value = response + + async with Client(mcp) as client: + result = (await client.call_tool("get_fleet", {"fleet_id": "fleet1"})).structured_content + assert result["id"] == "fleet1" + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_get_fleet_exception(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + mock_client.get_fleet.side_effect = oci.exceptions.ServiceError( + status=404, + code="NotAuthorizedOrNotFound", + message="Not found", + opc_request_id="req", + headers={}, + ) + + async with Client(mcp) as client: + with pytest.raises(fastmcp.exceptions.ToolError): + await client.call_tool("get_fleet", {"fleet_id": "fleet1"}) + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_jms_plugins(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.JmsPluginCollection( + items=[oci.jms.models.JmsPluginSummary(id="plugin1", hostname="host1")] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_jms_plugins.return_value = response + + async with Client(mcp) as client: + result = (await client.call_tool("list_jms_plugins", {"fleet_id": "fleet1"})).structured_content[ + "result" + ] + assert result[0]["id"] == "plugin1" + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_get_jms_plugin(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.JmsPlugin(id="plugin1", hostname="host1") + mock_client.get_jms_plugin.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool("get_jms_plugin", {"jms_plugin_id": "plugin1"}) + ).structured_content + assert result["id"] == "plugin1" + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_installation_sites(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.InstallationSiteCollection( + items=[ + oci.jms.models.InstallationSiteSummary( + installation_key="install1", + managed_instance_id="mi1", + path="/usr/java", + ) + ] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_installation_sites.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool("list_installation_sites", {"fleet_id": "fleet1"}) + ).structured_content["result"] + assert result[0]["installation_key"] == "install1" + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_installation_sites_normalizes_os_family(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.InstallationSiteCollection( + items=[ + oci.jms.models.InstallationSiteSummary( + installation_key="install1", + managed_instance_id="mi1", + path="/usr/java", + ) + ] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_installation_sites.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "list_installation_sites", + {"fleet_id": "fleet1", "os_family": ["linux"]}, + ) + ).structured_content["result"] + + assert result[0]["installation_key"] == "install1" + assert mock_client.list_installation_sites.call_args.kwargs["os_family"] == ["LINUX"] + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_installation_sites_normalizes_sort_by(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.InstallationSiteCollection( + items=[ + oci.jms.models.InstallationSiteSummary( + installation_key="install1", + managed_instance_id="mi1", + path="/usr/java", + ) + ] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_installation_sites.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "list_installation_sites", + {"fleet_id": "fleet1", "sort_by": "managed_instance_id"}, + ) + ).structured_content["result"] + + assert result[0]["installation_key"] == "install1" + assert ( + mock_client.list_installation_sites.call_args.kwargs["sort_by"] + == "managedInstanceId" + ) + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_get_fleet_agent_configuration(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.FleetAgentConfiguration( + jre_scan_frequency_in_minutes=60, + java_usage_tracker_processing_frequency_in_minutes=10, + ) + mock_client.get_fleet_agent_configuration.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool("get_fleet_agent_configuration", {"fleet_id": "fleet1"}) + ).structured_content + assert result["jre_scan_frequency_in_minutes"] == 60 + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_get_fleet_advanced_feature_configuration(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.FleetAdvancedFeatureConfiguration( + analytic_namespace="analytics_ns", + analytic_bucket_name="bucket", + ) + mock_client.get_fleet_advanced_feature_configuration.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "get_fleet_advanced_feature_configuration", {"fleet_id": "fleet1"} + ) + ).structured_content + assert result["analytic_namespace"] == "analytics_ns" + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_summarize_resource_inventory(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.ResourceInventory( + active_fleet_count=1, + managed_instance_count=2, + jre_count=3, + installation_count=4, + application_count=5, + ) + mock_client.summarize_resource_inventory.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "summarize_resource_inventory", + {"compartment_id": "ocid1.compartment.oc1..test"}, + ) + ).structured_content + assert result["active_fleet_count"] == 1 + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_summarize_managed_instance_usage(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.ManagedInstanceUsageCollection( + items=[ + oci.jms.models.ManagedInstanceUsage( + managed_instance_id="mi1", + managed_instance_type="ORACLE_MANAGEMENT_AGENT", + hostname="host1", + ) + ] + ) + response.has_next_page = False + response.next_page = None + mock_client.summarize_managed_instance_usage.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool("summarize_managed_instance_usage", {"fleet_id": "fleet1"}) + ).structured_content["result"] + assert result[0]["managed_instance_id"] == "mi1" + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_summarize_managed_instance_usage_normalizes_fields_and_sort_by( + self, mock_get_client + ): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.ManagedInstanceUsageCollection( + items=[ + oci.jms.models.ManagedInstanceUsage( + managed_instance_id="mi1", + managed_instance_type="ORACLE_MANAGEMENT_AGENT", + hostname="host1", + ) + ] + ) + response.has_next_page = False + response.next_page = None + mock_client.summarize_managed_instance_usage.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "summarize_managed_instance_usage", + { + "fleet_id": "fleet1", + "fields": ["approximate_jre_count"], + "sort_by": "time_first_seen", + }, + ) + ).structured_content["result"] + + assert result[0]["managed_instance_id"] == "mi1" + assert mock_client.summarize_managed_instance_usage.call_args.kwargs["fields"] == [ + "approximateJreCount" + ] + assert ( + mock_client.summarize_managed_instance_usage.call_args.kwargs["sort_by"] + == "timeFirstSeen" + ) + + +class TestServerMain: + @patch("oracle.oci_jms_mcp_server.server.mcp.run") + @patch("os.getenv") + def test_main_with_host_and_port(self, mock_getenv, mock_mcp_run): + mock_env = {"ORACLE_MCP_HOST": "1.2.3.4", "ORACLE_MCP_PORT": "8888"} + mock_getenv.side_effect = lambda key: mock_env.get(key) + server.main() + mock_mcp_run.assert_called_once_with(transport="http", host="1.2.3.4", port=8888) + + @patch("oracle.oci_jms_mcp_server.server.mcp.run") + @patch("os.getenv") + def test_main_without_host_and_port(self, mock_getenv, mock_mcp_run): + mock_getenv.return_value = None + server.main() + mock_mcp_run.assert_called_once_with() + + @patch("oracle.oci_jms_mcp_server.server.mcp.run") + @patch("os.getenv") + def test_main_with_only_host(self, mock_getenv, mock_mcp_run): + mock_env = {"ORACLE_MCP_HOST": "1.2.3.4"} + mock_getenv.side_effect = lambda key: mock_env.get(key) + server.main() + mock_mcp_run.assert_called_once_with() + + @patch("oracle.oci_jms_mcp_server.server.mcp.run") + @patch("os.getenv") + def test_main_with_only_port(self, mock_getenv, mock_mcp_run): + mock_env = {"ORACLE_MCP_PORT": "8888"} + mock_getenv.side_effect = lambda key: mock_env.get(key) + server.main() + mock_mcp_run.assert_called_once_with() + + +class TestGetJmsClient: + @patch("oracle.oci_jms_mcp_server.server.oci.jms.JavaManagementServiceClient") + @patch("oracle.oci_jms_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_jms_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_jms_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_jms_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_jms_mcp_server.server.os.getenv") + def test_get_jms_client( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + mock_client, + ): + mock_getenv.side_effect = lambda key, default=None: ( + "MYPROFILE" if key == "OCI_CONFIG_PROFILE" else default + ) + config = {"key_file": "/abs/key.pem", "security_token_file": "/abs/token"} + mock_from_file.return_value = config + private_key = object() + mock_load_private_key.return_value = private_key + + result = server.get_jms_client() + + mock_from_file.assert_called_once_with( + file_location=oci.config.DEFAULT_LOCATION, + profile_name="MYPROFILE", + ) + mock_open_file.assert_called_once_with("/abs/token", "r") + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key) + passed_config = mock_client.call_args[0][0] + assert passed_config is config + assert "additional_user_agent" in config + assert mock_client.call_args.kwargs == {"signer": mock_security_token_signer.return_value} + assert result == mock_client.return_value + + @patch("oracle.oci_jms_mcp_server.server.oci.jms.JavaManagementServiceClient") + @patch("oracle.oci_jms_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_jms_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_jms_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_jms_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_jms_mcp_server.server.os.getenv") + def test_get_jms_client_with_jms_test_environment_override( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + mock_client, + ): + values = { + "OCI_CONFIG_PROFILE": "MYPROFILE", + "JMS_TEST_ENVIRONMENT": "DEV-canary", + } + mock_getenv.side_effect = lambda key, default=None: values.get(key, default) + config = { + "key_file": "/abs/key.pem", + "security_token_file": "/abs/token", + "region": "us-ashburn-1", + } + mock_from_file.return_value = config + mock_load_private_key.return_value = object() + + server.get_jms_client() + + assert mock_client.call_args.kwargs == { + "signer": mock_security_token_signer.return_value, + "service_endpoint": "https://javamanagement-dev.us-ashburn-1.oci.oc-test.com", + } + + @patch("oracle.oci_jms_mcp_server.server.oci.jms.JavaManagementServiceClient") + @patch("oracle.oci_jms_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_jms_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_jms_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_jms_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_jms_mcp_server.server.os.getenv") + def test_get_jms_client_with_test_endpoint_override( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + mock_client, + ): + values = { + "JMS_TEST_ENVIRONMENT": "TEST", + } + mock_getenv.side_effect = lambda key, default=None: values.get(key, default) + config = { + "key_file": "/abs/key.pem", + "security_token_file": "/abs/token", + "region": "us-ashburn-1", + } + mock_from_file.return_value = config + mock_load_private_key.return_value = object() + + server.get_jms_client() + + assert mock_client.call_args.kwargs == { + "signer": mock_security_token_signer.return_value, + "service_endpoint": "https://javamanagement-test.us-ashburn-1.oci.oc-test.com", + } + + @patch("oracle.oci_jms_mcp_server.server.oci.jms.JavaManagementServiceClient") + @patch("oracle.oci_jms_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_jms_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_jms_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_jms_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_jms_mcp_server.server.os.getenv") + def test_get_jms_client_with_herds_endpoint_override( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + mock_client, + ): + values = { + "JMS_TEST_ENVIRONMENT": "HERDS", + } + mock_getenv.side_effect = lambda key, default=None: values.get(key, default) + config = { + "key_file": "/abs/key.pem", + "security_token_file": "/abs/token", + "region": "eu-frankfurt-1", + } + mock_from_file.return_value = config + mock_load_private_key.return_value = object() + + server.get_jms_client() + + assert mock_client.call_args.kwargs == { + "signer": mock_security_token_signer.return_value, + "service_endpoint": "https://javamanagement-herds.eu-frankfurt-1.oci.rbcloud.oc-test.com", + } + + @patch("oracle.oci_jms_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_jms_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_jms_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_jms_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_jms_mcp_server.server.os.getenv") + def test_get_jms_client_rejects_unknown_jms_test_environment( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + ): + values = { + "JMS_TEST_ENVIRONMENT": "BOGUS", + } + mock_getenv.side_effect = lambda key, default=None: values.get(key, default) + mock_from_file.return_value = { + "key_file": "/abs/key.pem", + "security_token_file": "/abs/token", + "region": "us-ashburn-1", + } + mock_load_private_key.return_value = object() + + with pytest.raises(ValueError, match="Unsupported JMS_TEST_ENVIRONMENT"): + server.get_jms_client() + + @patch("oracle.oci_jms_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_jms_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_jms_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_jms_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_jms_mcp_server.server.os.getenv") + def test_get_jms_client_requires_region_for_jms_test_environment( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + ): + values = { + "JMS_TEST_ENVIRONMENT": "DEV2", + } + mock_getenv.side_effect = lambda key, default=None: values.get(key, default) + mock_from_file.return_value = { + "key_file": "/abs/key.pem", + "security_token_file": "/abs/token", + } + mock_load_private_key.return_value = object() + + with pytest.raises(ValueError, match="OCI config does not contain a region"): + server.get_jms_client() + + @patch("oracle.oci_jms_mcp_server.server.logger") + @patch("oracle.oci_jms_mcp_server.server.os.getenv") + def test_load_oci_config_warns_on_oci_api_mcp_env_aliases(self, mock_getenv, mock_logger): + values = { + "OCI_CONFIG": "/Users/test/.oci/config", + "OCI_PROFILE": "DEFAULT", + } + mock_getenv.side_effect = lambda key, default=None: values.get(key, default) + + with patch("oracle.oci_jms_mcp_server.server.oci.config.from_file") as mock_from_file: + server._load_oci_config() + + mock_from_file.assert_called_once_with( + file_location=oci.config.DEFAULT_LOCATION, + profile_name=oci.config.DEFAULT_PROFILE, + ) + assert mock_logger.warning.call_count == 2 + + @patch("oracle.oci_jms_mcp_server.server.open", new_callable=mock_open, read_data="SECURITY_TOKEN\n") + @patch("oracle.oci_jms_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_jms_mcp_server.server.oci.signer.load_private_key_from_file") + def test_build_security_token_signer_strips_token( + self, + mock_load_private_key, + mock_security_token_signer, + mock_open_file, + ): + config = { + "key_file": "/abs/key.pem", + "security_token_file": "~/.oci/sessions/DEFAULT/token", + } + private_key = object() + mock_load_private_key.return_value = private_key + + server._build_security_token_signer(config) + + mock_open_file.assert_called_once_with(os.path.expanduser(config["security_token_file"]), "r") + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key) diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/util.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/util.py new file mode 100644 index 00000000..e2c474de --- /dev/null +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/util.py @@ -0,0 +1,72 @@ +""" +Copyright (c) 2025, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +import os +import re +from typing import Optional + +_JMS_TEST_ENVIRONMENT = "JMS_TEST_ENVIRONMENT" +_JMS_TEST_ENV_NAME = re.compile(r"^([a-zA-Z0-9]+)([_-]+[\w-]*)?$") +_SUPPORTED_JMS_ENVIRONMENTS = { + "PROD", + "HERDS", + "DEV", + "DEV2", + "DEV3", + "TEST", + "TEST2", + "TEST3", + "STAGE", + "VANILLA", +} + + +def get_jms_environment(value: Optional[str]) -> Optional[str]: + """Parse the JMS environment selector, allowing Java-style optional suffixes.""" + if value is None: + return None + + normalized = value.strip() + if not normalized: + return None + + match = _JMS_TEST_ENV_NAME.match(normalized) + if not match: + raise ValueError( + f"Unable to parse {_JMS_TEST_ENVIRONMENT}={value!r}; expected a base environment name " + "such as PROD, HERDS, DEV, DEV2, DEV3, TEST, TEST2, TEST3, STAGE, or VANILLA." + ) + + environment = match.group(1).upper() + if environment not in _SUPPORTED_JMS_ENVIRONMENTS: + supported = ", ".join(sorted(_SUPPORTED_JMS_ENVIRONMENTS)) + raise ValueError( + f"Unsupported {_JMS_TEST_ENVIRONMENT} base environment {environment!r}; " + f"supported values are: {supported}." + ) + + return environment + + +def get_jms_service_endpoint(config: dict, environment_value: Optional[str] = None) -> Optional[str]: + """Derive a Java-style JMS service endpoint override for non-prod environments.""" + environment = get_jms_environment( + os.getenv(_JMS_TEST_ENVIRONMENT) if environment_value is None else environment_value + ) + if environment is None or environment == "PROD": + return None + + region = config.get("region") + if not region: + raise ValueError( + f"{_JMS_TEST_ENVIRONMENT} is set to {environment!r} but the OCI config does not " + "contain a region." + ) + + if environment == "HERDS": + return f"https://javamanagement-herds.{region}.oci.rbcloud.oc-test.com" + + return f"https://javamanagement-{environment.lower()}.{region}.oci.oc-test.com" diff --git a/src/oci-jms-mcp-server/pyproject.toml b/src/oci-jms-mcp-server/pyproject.toml new file mode 100644 index 00000000..314ff754 --- /dev/null +++ b/src/oci-jms-mcp-server/pyproject.toml @@ -0,0 +1,55 @@ +[project] +name = "oracle.oci-jms-mcp-server" +version = "1.0.0" +description = "OCI Java Management Service MCP server" +readme = "README.md" +requires-python = ">=3.13" +license = "UPL-1.0" +authors = [ + {name = "Oracle MCP", email = "237432095+oracle-mcp@users.noreply.github.com"}, +] +dependencies = [ + "fastmcp==2.14.2", + "oci==2.160.0", + "pydantic==2.12.3", +] + +classifiers = [ + "License :: OSI Approved :: Universal Permissive License (UPL)", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.13", +] + +[project.scripts] +"oracle.oci-jms-mcp-server" = "oracle.oci_jms_mcp_server.server:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["oracle"] + +[dependency-groups] +dev = [ + "pytest>=8.4.2", + "pytest-asyncio>=1.2.0", + "pytest-cov>=7.0.0", +] + +[tool.coverage.run] +omit = [ + "**/__init__.py", + "**/tests/*", + "dist/*", + ".venv/*", +] + +[tool.coverage.report] +omit = [ + "**/__init__.py", + "**/tests/*", +] +precision = 2 +fail_under = 90 diff --git a/src/oci-jms-mcp-server/uv.lock b/src/oci-jms-mcp-server/uv.lock new file mode 100644 index 00000000..0a6f5b95 --- /dev/null +++ b/src/oci-jms-mcp-server/uv.lock @@ -0,0 +1,1391 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "authlib" +version = "1.6.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/98/00d3dd826d46959ad8e32af2dbb2398868fd9fd0683c26e56d0789bd0e68/authlib-1.6.9.tar.gz", hash = "sha256:d8f2421e7e5980cc1ddb4e32d3f5fa659cfaf60d8eaf3281ebed192e4ab74f04", size = 165134, upload-time = "2026-03-02T07:44:01.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/23/b65f568ed0c22f1efacb744d2db1a33c8068f384b8c9b482b52ebdbc3ef6/authlib-1.6.9-py2.py3-none-any.whl", hash = "sha256:f08b4c14e08f0861dc18a32357b33fbcfd2ea86cfe3fe149484b4d764c4a0ac3", size = 244197, upload-time = "2026-03-02T07:44:00.307Z" }, +] + +[[package]] +name = "beartype" +version = "0.22.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, +] + +[[package]] +name = "cachetools" +version = "7.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/dd/57fe3fdb6e65b25a5987fd2cdc7e22db0aef508b91634d2e57d22928d41b/cachetools-7.0.5.tar.gz", hash = "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", size = 37367, upload-time = "2026-03-09T20:51:29.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f3/39cf3367b8107baa44f861dc802cbf16263c945b62d8265d36034fc07bea/cachetools-7.0.5-py3-none-any.whl", hash = "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114", size = 13918, upload-time = "2026-03-09T20:51:27.33Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "circuitbreaker" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/ac/de7a92c4ed39cba31fe5ad9203b76a25ca67c530797f6bb420fff5f65ccb/circuitbreaker-2.1.3.tar.gz", hash = "sha256:1a4baee510f7bea3c91b194dcce7c07805fe96c4423ed5594b75af438531d084", size = 10787, upload-time = "2025-03-31T08:12:08.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/34/15f08edd4628f65217de1fc3c1a27c82e46fe357d60c217fc9881e12ebcc/circuitbreaker-2.1.3-py3-none-any.whl", hash = "sha256:87ba6a3ed03fdc7032bc175561c2b04d52ade9d5faf94ca2b035fbdc5e6b1dd1", size = 7737, upload-time = "2025-03-31T08:12:07.802Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, +] + +[[package]] +name = "cronsim" +version = "2.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/1a/02f105147f7f2e06ed4f734ff5a6439590bb275a53dd91fc73df6312298a/cronsim-2.7-py3-none-any.whl", hash = "sha256:1e1431fa08c51dc7f72e67e571c7c7a09af26420169b607badd4ca9677ffad1e", size = 14213, upload-time = "2025-10-21T16:38:20.431Z" }, +] + +[[package]] +name = "cryptography" +version = "44.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096, upload-time = "2025-05-02T19:36:04.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/53/c776d80e9d26441bb3868457909b4e74dd9ccabd182e10b2b0ae7a07e265/cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88", size = 6670281, upload-time = "2025-05-02T19:34:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305, upload-time = "2025-05-02T19:34:53.042Z" }, + { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040, upload-time = "2025-05-02T19:34:54.675Z" }, + { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411, upload-time = "2025-05-02T19:34:56.61Z" }, + { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263, upload-time = "2025-05-02T19:34:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198, upload-time = "2025-05-02T19:35:00.988Z" }, + { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502, upload-time = "2025-05-02T19:35:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173, upload-time = "2025-05-02T19:35:05.018Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713, upload-time = "2025-05-02T19:35:07.187Z" }, + { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064, upload-time = "2025-05-02T19:35:08.879Z" }, + { url = "https://files.pythonhosted.org/packages/10/a8/8c540a421b44fd267a7d58a1fd5f072a552d72204a3f08194f98889de76d/cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d", size = 2773887, upload-time = "2025-05-02T19:35:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0d/c4b1657c39ead18d76bbd122da86bd95bdc4095413460d09544000a17d56/cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d", size = 3209737, upload-time = "2025-05-02T19:35:12.12Z" }, + { url = "https://files.pythonhosted.org/packages/34/a3/ad08e0bcc34ad436013458d7528e83ac29910943cea42ad7dd4141a27bbb/cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f", size = 6673501, upload-time = "2025-05-02T19:35:13.775Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307, upload-time = "2025-05-02T19:35:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876, upload-time = "2025-05-02T19:35:18.138Z" }, + { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127, upload-time = "2025-05-02T19:35:19.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164, upload-time = "2025-05-02T19:35:21.449Z" }, + { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081, upload-time = "2025-05-02T19:35:23.187Z" }, + { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716, upload-time = "2025-05-02T19:35:25.426Z" }, + { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398, upload-time = "2025-05-02T19:35:27.678Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900, upload-time = "2025-05-02T19:35:29.312Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067, upload-time = "2025-05-02T19:35:31.547Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/d2c48c8137eb39d0c193274db5c04a75dab20d2f7c3f81a7dcc3a8897701/cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028", size = 2775467, upload-time = "2025-05-02T19:35:33.805Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/51f212198681ea7b0deaaf8846ee10af99fba4e894f67b353524eab2bbe5/cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334", size = 3210375, upload-time = "2025-05-02T19:35:35.369Z" }, +] + +[[package]] +name = "cyclopts" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "docstring-parser" }, + { name = "rich" }, + { name = "rich-rst" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/e7/3e26855c046ac527cf94d890f6698e703980337f22ea7097e02b35b910f9/cyclopts-4.10.0.tar.gz", hash = "sha256:0ae04a53274e200ef3477c8b54de63b019bc6cd0162d75c718bf40c9c3fb5268", size = 166394, upload-time = "2026-03-14T14:09:31.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/06/d68a5d5d292c2ad2bc6a02e5ca2cb1bb9c15e941ab02f004a06a342d7f0f/cyclopts-4.10.0-py3-none-any.whl", hash = "sha256:50f333382a60df8d40ec14aa2e627316b361c4f478598ada1f4169d959bf9ea7", size = 204097, upload-time = "2026-03-14T14:09:32.504Z" }, +] + +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "docstring-parser" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "fakeredis" +version = "2.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "redis" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/40/fd09efa66205eb32253d2b2ebc63537281384d2040f0a88bcd2289e120e4/fakeredis-2.34.1.tar.gz", hash = "sha256:4ff55606982972eecce3ab410e03d746c11fe5deda6381d913641fbd8865ea9b", size = 177315, upload-time = "2026-02-25T13:17:51.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/b5/82f89307d0d769cd9bf46a54fb9136be08e4e57c5570ae421db4c9a2ba62/fakeredis-2.34.1-py3-none-any.whl", hash = "sha256:0107ec99d48913e7eec2a5e3e2403d1bd5f8aa6489d1a634571b975289c48f12", size = 122160, upload-time = "2026-02-25T13:17:49.701Z" }, +] + +[package.optional-dependencies] +lua = [ + { name = "lupa" }, +] + +[[package]] +name = "fastmcp" +version = "2.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "authlib" }, + { name = "cyclopts" }, + { name = "exceptiongroup" }, + { name = "httpx" }, + { name = "jsonschema-path" }, + { name = "mcp" }, + { name = "openapi-pydantic" }, + { name = "platformdirs" }, + { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, + { name = "pydantic", extra = ["email"] }, + { name = "pydocket" }, + { name = "pyperclip" }, + { name = "python-dotenv" }, + { name = "rich" }, + { name = "uvicorn" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/1e/e3528227688c248283f6d86869b1e900563ffc223eff00f4f923d2750365/fastmcp-2.14.2.tar.gz", hash = "sha256:bd23d1b808b6f446444f10114dac468b11bfb9153ed78628f5619763d0cf573e", size = 8272966, upload-time = "2025-12-31T15:26:13.433Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/67/8456d39484fcb7afd0defed21918e773ed59a98b39e5b633328527c88367/fastmcp-2.14.2-py3-none-any.whl", hash = "sha256:e33cd622e1ebd5110af6a981804525b6cd41072e3c7d68268ed69ef3be651aca", size = 413279, upload-time = "2025-12-31T15:26:11.178Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/7b/c3081ff1af947915503121c649f26a778e1a2101fd525f74aef997d75b7e/jaraco_context-6.1.1.tar.gz", hash = "sha256:bc046b2dc94f1e5532bd02402684414575cc11f565d929b6563125deb0a6e581", size = 15832, upload-time = "2026-03-07T15:46:04.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl", hash = "sha256:0df6a0287258f3e364072c3e40d5411b20cafa30cb28c4839d24319cecf9f808", size = 7005, upload-time = "2026-03-07T15:46:03.515Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-path" +version = "0.4.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathable" }, + { name = "pyyaml" }, + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/8a/7e6102f2b8bdc6705a9eb5294f8f6f9ccd3a8420e8e8e19671d1dd773251/jsonschema_path-0.4.5.tar.gz", hash = "sha256:c6cd7d577ae290c7defd4f4029e86fdb248ca1bd41a07557795b3c95e5144918", size = 15113, upload-time = "2026-03-03T09:56:46.87Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/d5/4e96c44f6c1ea3d812cf5391d81a4f5abaa540abf8d04ecd7f66e0ed11df/jsonschema_path-0.4.5-py3-none-any.whl", hash = "sha256:7d77a2c3f3ec569a40efe5c5f942c44c1af2a6f96fe0866794c9ef5b8f87fd65", size = 19368, upload-time = "2026-03-03T09:56:45.39Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + +[[package]] +name = "lupa" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/1c/191c3e6ec6502e3dbe25a53e27f69a5daeac3e56de1f73c0138224171ead/lupa-2.6.tar.gz", hash = "sha256:9a770a6e89576be3447668d7ced312cd6fd41d3c13c2462c9dc2c2ab570e45d9", size = 7240282, upload-time = "2025-10-24T07:20:29.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/1d/21176b682ca5469001199d8b95fa1737e29957a3d185186e7a8b55345f2e/lupa-2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:663a6e58a0f60e7d212017d6678639ac8df0119bc13c2145029dcba084391310", size = 947232, upload-time = "2025-10-24T07:18:27.878Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4c/d327befb684660ca13cf79cd1f1d604331808f9f1b6fb6bf57832f8edf80/lupa-2.6-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:d1f5afda5c20b1f3217a80e9bc1b77037f8a6eb11612fd3ada19065303c8f380", size = 1908625, upload-time = "2025-10-24T07:18:29.944Z" }, + { url = "https://files.pythonhosted.org/packages/66/8e/ad22b0a19454dfd08662237a84c792d6d420d36b061f239e084f29d1a4f3/lupa-2.6-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:26f2b3c085fe76e9119e48c1013c1cccdc1f51585d456858290475aa38e7089e", size = 981057, upload-time = "2025-10-24T07:18:31.553Z" }, + { url = "https://files.pythonhosted.org/packages/5c/48/74859073ab276bd0566c719f9ca0108b0cfc1956ca0d68678d117d47d155/lupa-2.6-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:60d2f902c7b96fb8ab98493dcff315e7bb4d0b44dc9dd76eb37de575025d5685", size = 1156227, upload-time = "2025-10-24T07:18:33.981Z" }, + { url = "https://files.pythonhosted.org/packages/09/6c/0e9ded061916877253c2266074060eb71ed99fb21d73c8c114a76725bce2/lupa-2.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a02d25dee3a3250967c36590128d9220ae02f2eda166a24279da0b481519cbff", size = 1035752, upload-time = "2025-10-24T07:18:36.32Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ef/f8c32e454ef9f3fe909f6c7d57a39f950996c37a3deb7b391fec7903dab7/lupa-2.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eae1ee16b886b8914ff292dbefbf2f48abfbdee94b33a88d1d5475e02423203", size = 2069009, upload-time = "2025-10-24T07:18:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/15b80c226a5225815a890ee1c11f07968e0aba7a852df41e8ae6fe285063/lupa-2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0edd5073a4ee74ab36f74fe61450148e6044f3952b8d21248581f3c5d1a58be", size = 1056301, upload-time = "2025-10-24T07:18:40.165Z" }, + { url = "https://files.pythonhosted.org/packages/31/14/2086c1425c985acfb30997a67e90c39457122df41324d3c179d6ee2292c6/lupa-2.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0c53ee9f22a8a17e7d4266ad48e86f43771951797042dd51d1494aaa4f5f3f0a", size = 1170673, upload-time = "2025-10-24T07:18:42.426Z" }, + { url = "https://files.pythonhosted.org/packages/10/e5/b216c054cf86576c0191bf9a9f05de6f7e8e07164897d95eea0078dca9b2/lupa-2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:de7c0f157a9064a400d828789191a96da7f4ce889969a588b87ec80de9b14772", size = 2162227, upload-time = "2025-10-24T07:18:46.112Z" }, + { url = "https://files.pythonhosted.org/packages/59/2f/33ecb5bedf4f3bc297ceacb7f016ff951331d352f58e7e791589609ea306/lupa-2.6-cp313-cp313-win32.whl", hash = "sha256:ee9523941ae0a87b5b703417720c5d78f72d2f5bc23883a2ea80a949a3ed9e75", size = 1419558, upload-time = "2025-10-24T07:18:48.371Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b4/55e885834c847ea610e111d87b9ed4768f0afdaeebc00cd46810f25029f6/lupa-2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b1335a5835b0a25ebdbc75cf0bda195e54d133e4d994877ef025e218c2e59db9", size = 1683424, upload-time = "2025-10-24T07:18:50.976Z" }, + { url = "https://files.pythonhosted.org/packages/66/9d/d9427394e54d22a35d1139ef12e845fd700d4872a67a34db32516170b746/lupa-2.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dcb6d0a3264873e1653bc188499f48c1fb4b41a779e315eba45256cfe7bc33c1", size = 953818, upload-time = "2025-10-24T07:18:53.378Z" }, + { url = "https://files.pythonhosted.org/packages/10/41/27bbe81953fb2f9ecfced5d9c99f85b37964cfaf6aa8453bb11283983721/lupa-2.6-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:a37e01f2128f8c36106726cb9d360bac087d58c54b4522b033cc5691c584db18", size = 1915850, upload-time = "2025-10-24T07:18:55.259Z" }, + { url = "https://files.pythonhosted.org/packages/a3/98/f9ff60db84a75ba8725506bbf448fb085bc77868a021998ed2a66d920568/lupa-2.6-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:458bd7e9ff3c150b245b0fcfbb9bd2593d1152ea7f0a7b91c1d185846da033fe", size = 982344, upload-time = "2025-10-24T07:18:57.05Z" }, + { url = "https://files.pythonhosted.org/packages/41/f7/f39e0f1c055c3b887d86b404aaf0ca197b5edfd235a8b81b45b25bac7fc3/lupa-2.6-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:052ee82cac5206a02df77119c325339acbc09f5ce66967f66a2e12a0f3211cad", size = 1156543, upload-time = "2025-10-24T07:18:59.251Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9c/59e6cffa0d672d662ae17bd7ac8ecd2c89c9449dee499e3eb13ca9cd10d9/lupa-2.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96594eca3c87dd07938009e95e591e43d554c1dbd0385be03c100367141db5a8", size = 1047974, upload-time = "2025-10-24T07:19:01.449Z" }, + { url = "https://files.pythonhosted.org/packages/23/c6/a04e9cef7c052717fcb28fb63b3824802488f688391895b618e39be0f684/lupa-2.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8faddd9d198688c8884091173a088a8e920ecc96cda2ffed576a23574c4b3f6", size = 2073458, upload-time = "2025-10-24T07:19:03.369Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/824173d10f38b51fc77785228f01411b6ca28826ce27404c7c912e0e442c/lupa-2.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:daebb3a6b58095c917e76ba727ab37b27477fb926957c825205fbda431552134", size = 1067683, upload-time = "2025-10-24T07:19:06.2Z" }, + { url = "https://files.pythonhosted.org/packages/b6/dc/9692fbcf3c924d9c4ece2d8d2f724451ac2e09af0bd2a782db1cef34e799/lupa-2.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f3154e68972befe0f81564e37d8142b5d5d79931a18309226a04ec92487d4ea3", size = 1171892, upload-time = "2025-10-24T07:19:08.544Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/e318b628d4643c278c96ab3ddea07fc36b075a57383c837f5b11e537ba9d/lupa-2.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e4dadf77b9fedc0bfa53417cc28dc2278a26d4cbd95c29f8927ad4d8fe0a7ef9", size = 2166641, upload-time = "2025-10-24T07:19:10.485Z" }, + { url = "https://files.pythonhosted.org/packages/12/f7/a6f9ec2806cf2d50826980cdb4b3cffc7691dc6f95e13cc728846d5cb793/lupa-2.6-cp314-cp314-win32.whl", hash = "sha256:cb34169c6fa3bab3e8ac58ca21b8a7102f6a94b6a5d08d3636312f3f02fafd8f", size = 1456857, upload-time = "2025-10-24T07:19:37.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/de/df71896f25bdc18360fdfa3b802cd7d57d7fede41a0e9724a4625b412c85/lupa-2.6-cp314-cp314-win_amd64.whl", hash = "sha256:b74f944fe46c421e25d0f8692aef1e842192f6f7f68034201382ac440ef9ea67", size = 1731191, upload-time = "2025-10-24T07:19:40.281Z" }, + { url = "https://files.pythonhosted.org/packages/47/3c/a1f23b01c54669465f5f4c4083107d496fbe6fb45998771420e9aadcf145/lupa-2.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0e21b716408a21ab65723f8841cf7f2f37a844b7a965eeabb785e27fca4099cf", size = 999343, upload-time = "2025-10-24T07:19:12.519Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6d/501994291cb640bfa2ccf7f554be4e6914afa21c4026bd01bff9ca8aac57/lupa-2.6-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:589db872a141bfff828340079bbdf3e9a31f2689f4ca0d88f97d9e8c2eae6142", size = 2000730, upload-time = "2025-10-24T07:19:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/53/a5/457ffb4f3f20469956c2d4c4842a7675e884efc895b2f23d126d23e126cc/lupa-2.6-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:cd852a91a4a9d4dcbb9a58100f820a75a425703ec3e3f049055f60b8533b7953", size = 1021553, upload-time = "2025-10-24T07:19:17.123Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/36bb5a5d0960f2a5c7c700e0819abb76fd9bf9c1d8a66e5106416d6e9b14/lupa-2.6-cp314-cp314t-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:0334753be028358922415ca97a64a3048e4ed155413fc4eaf87dd0a7e2752983", size = 1133275, upload-time = "2025-10-24T07:19:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/19/86/202ff4429f663013f37d2229f6176ca9f83678a50257d70f61a0a97281bf/lupa-2.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:661d895cd38c87658a34780fac54a690ec036ead743e41b74c3fb81a9e65a6aa", size = 1038441, upload-time = "2025-10-24T07:19:22.509Z" }, + { url = "https://files.pythonhosted.org/packages/a7/42/d8125f8e420714e5b52e9c08d88b5329dfb02dcca731b4f21faaee6cc5b5/lupa-2.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aa58454ccc13878cc177c62529a2056be734da16369e451987ff92784994ca7", size = 2058324, upload-time = "2025-10-24T07:19:24.979Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2c/47bf8b84059876e877a339717ddb595a4a7b0e8740bacae78ba527562e1c/lupa-2.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1425017264e470c98022bba8cff5bd46d054a827f5df6b80274f9cc71dafd24f", size = 1060250, upload-time = "2025-10-24T07:19:27.262Z" }, + { url = "https://files.pythonhosted.org/packages/c2/06/d88add2b6406ca1bdec99d11a429222837ca6d03bea42ca75afa169a78cb/lupa-2.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:224af0532d216e3105f0a127410f12320f7c5f1aa0300bdf9646b8d9afb0048c", size = 1151126, upload-time = "2025-10-24T07:19:29.522Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a0/89e6a024c3b4485b89ef86881c9d55e097e7cb0bdb74efb746f2fa6a9a76/lupa-2.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9abb98d5a8fd27c8285302e82199f0e56e463066f88f619d6594a450bf269d80", size = 2153693, upload-time = "2025-10-24T07:19:31.379Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/a0f007dc58fc1bbf51fb85dcc82fcb1f21b8c4261361de7dab0e3d8521ef/lupa-2.6-cp314-cp314t-win32.whl", hash = "sha256:1849efeba7a8f6fb8aa2c13790bee988fd242ae404bd459509640eeea3d1e291", size = 1590104, upload-time = "2025-10-24T07:19:33.514Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5e/db903ce9cf82c48d6b91bf6d63ae4c8d0d17958939a4e04ba6b9f38b8643/lupa-2.6-cp314-cp314t-win_amd64.whl", hash = "sha256:fc1498d1a4fc028bc521c26d0fad4ca00ed63b952e32fb95949bda76a04bad52", size = 1913818, upload-time = "2025-10-24T07:19:36.039Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "mcp" +version = "1.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "more-itertools" +version = "10.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, +] + +[[package]] +name = "oci" +version = "2.160.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "circuitbreaker" }, + { name = "cryptography" }, + { name = "pyopenssl" }, + { name = "python-dateutil" }, + { name = "pytz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/7b/c9d7fc28f11c25c7875db3584eab5d52ccb2d7df553d07ac47f19a14d075/oci-2.160.0.tar.gz", hash = "sha256:f8e3410204c1405b40247179550cf74f5145a8e17025c4f2a92f2b9ffdc7d26b", size = 15601606, upload-time = "2025-09-09T04:17:43.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/51/752375a4e0d2de371c2788414157eda337417010d2ef7383cd7140388f1e/oci-2.160.0-py3-none-any.whl", hash = "sha256:3dba1ec671ebea23f255fabf836cb0fd08aea0913a8df85610fccaa5a4344ee9", size = 31715365, upload-time = "2025-09-09T04:17:34.998Z" }, +] + +[[package]] +name = "openapi-pydantic" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, +] + +[[package]] +name = "oracle-oci-jms-mcp-server" +version = "1.0.0" +source = { editable = "." } +dependencies = [ + { name = "fastmcp" }, + { name = "oci" }, + { name = "pydantic" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastmcp", specifier = "==2.14.2" }, + { name = "oci", specifier = "==2.160.0" }, + { name = "pydantic", specifier = "==2.12.3" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.4.2" }, + { name = "pytest-asyncio", specifier = ">=1.2.0" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pathable" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/55/b748445cb4ea6b125626f15379be7c96d1035d4fa3e8fee362fa92298abf/pathable-0.5.0.tar.gz", hash = "sha256:d81938348a1cacb525e7c75166270644782c0fb9c8cecc16be033e71427e0ef1", size = 16655, upload-time = "2026-02-20T08:47:00.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/96/5a770e5c461462575474468e5af931cff9de036e7c2b4fea23c1c58d2cbe/pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6", size = 16867, upload-time = "2026-02-20T08:46:59.536Z" }, +] + +[[package]] +name = "pathvalidate" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, +] + +[[package]] +name = "py-key-value-aio" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "py-key-value-shared" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/ce/3136b771dddf5ac905cc193b461eb67967cf3979688c6696e1f2cdcde7ea/py_key_value_aio-0.3.0.tar.gz", hash = "sha256:858e852fcf6d696d231266da66042d3355a7f9871650415feef9fca7a6cd4155", size = 50801, upload-time = "2025-11-17T16:50:04.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl", hash = "sha256:1c781915766078bfd608daa769fefb97e65d1d73746a3dfb640460e322071b64", size = 96342, upload-time = "2025-11-17T16:50:03.801Z" }, +] + +[package.optional-dependencies] +disk = [ + { name = "diskcache" }, + { name = "pathvalidate" }, +] +keyring = [ + { name = "keyring" }, +] +memory = [ + { name = "cachetools" }, +] +redis = [ + { name = "redis" }, +] + +[[package]] +name = "py-key-value-shared" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/e4/1971dfc4620a3a15b4579fe99e024f5edd6e0967a71154771a059daff4db/py_key_value_shared-0.3.0.tar.gz", hash = "sha256:8fdd786cf96c3e900102945f92aa1473138ebe960ef49da1c833790160c28a4b", size = 11666, upload-time = "2025-11-17T16:50:06.849Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl", hash = "sha256:5b0efba7ebca08bb158b1e93afc2f07d30b8f40c2fc12ce24a4c0d84f42f9298", size = 19560, upload-time = "2025-11-17T16:50:05.954Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, +] + +[package.optional-dependencies] +email = [ + { name = "email-validator" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, + { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, + { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, + { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, + { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, + { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, + { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, + { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, + { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, + { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, + { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, +] + +[[package]] +name = "pydocket" +version = "0.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "cronsim" }, + { name = "fakeredis", extra = ["lua"] }, + { name = "opentelemetry-api" }, + { name = "prometheus-client" }, + { name = "py-key-value-aio", extra = ["memory", "redis"] }, + { name = "python-json-logger" }, + { name = "redis" }, + { name = "rich" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, + { name = "uncalled-for" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/5f/82dde9fb6099b960a4203596d3b755d1bd2c0d0210fea104d015d6515d7f/pydocket-0.18.2.tar.gz", hash = "sha256:cc2051d15557f83bb164a83b0743fa9c12c2bfe9a9145cff3a5922b4935ce4f5", size = 354762, upload-time = "2026-03-10T13:09:22.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/cf/8c1b6340baf81d7f6c97fe0181bda7cfd500d5e33bf469fbffbdae07b3c9/pydocket-0.18.2-py3-none-any.whl", hash = "sha256:19e48de15e83370f750e362610b777533ff9c0fa48bf36766ed581f91d266556", size = 99041, upload-time = "2026-03-10T13:09:20.598Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pyopenssl" +version = "24.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/d4/1067b82c4fc674d6f6e9e8d26b3dff978da46d351ca3bac171544693e085/pyopenssl-24.3.0.tar.gz", hash = "sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36", size = 178944, upload-time = "2024-11-27T20:43:12.755Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/22/40f9162e943f86f0fc927ebc648078be87def360d9d8db346619fb97df2b/pyOpenSSL-24.3.0-py3-none-any.whl", hash = "sha256:e474f5a473cd7f92221cc04976e48f4d11502804657a08a989fb3be5514c904a", size = 56111, upload-time = "2024-11-27T20:43:21.112Z" }, +] + +[[package]] +name = "pyperclip" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-json-logger" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, +] + +[[package]] +name = "pytz" +version = "2026.1.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "redis" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/82/4d1a5279f6c1251d3d2a603a798a1137c657de9b12cfc1fba4858232c4d2/redis-7.3.0.tar.gz", hash = "sha256:4d1b768aafcf41b01022410b3cc4f15a07d9b3d6fe0c66fc967da2c88e551034", size = 4928081, upload-time = "2026-03-06T18:18:16.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/28/84e57fce7819e81ec5aa1bd31c42b89607241f4fb1a3ea5b0d2dbeaea26c/redis-7.3.0-py3-none-any.whl", hash = "sha256:9d4fcb002a12a5e3c3fbe005d59c48a2cc231f87fbb2f6b70c2d89bb64fec364", size = 404379, upload-time = "2026-03-06T18:18:14.583Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "rich" +version = "14.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, +] + +[[package]] +name = "rich-rst" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/6d/a506aaa4a9eaa945ed8ab2b7347859f53593864289853c5d6d62b77246e0/rich_rst-1.3.2.tar.gz", hash = "sha256:a1196fdddf1e364b02ec68a05e8ff8f6914fee10fbca2e6b6735f166bb0da8d4", size = 14936, upload-time = "2025-10-14T16:49:45.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl", hash = "sha256:a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a", size = 12567, upload-time = "2025-10-14T16:49:42.953Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, +] + +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/9f/c3695c2d2d4ef70072c3a06992850498b01c6bc9be531950813716b426fa/sse_starlette-3.3.2.tar.gz", hash = "sha256:678fca55a1945c734d8472a6cad186a55ab02840b4f6786f5ee8770970579dcd", size = 32326, upload-time = "2026-02-28T11:24:34.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/28/8cb142d3fe80c4a2d8af54ca0b003f47ce0ba920974e7990fa6e016402d1/sse_starlette-3.3.2-py3-none-any.whl", hash = "sha256:5c3ea3dad425c601236726af2f27689b74494643f57017cafcb6f8c9acfbb862", size = 14270, upload-time = "2026-02-28T11:24:32.984Z" }, +] + +[[package]] +name = "starlette" +version = "0.52.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, +] + +[[package]] +name = "typer" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "uncalled-for" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/7c/b5b7d8136f872e3f13b0584e576886de0489d7213a12de6bebf29ff6ebfc/uncalled_for-0.2.0.tar.gz", hash = "sha256:b4f8fdbcec328c5a113807d653e041c5094473dd4afa7c34599ace69ccb7e69f", size = 49488, upload-time = "2026-02-27T17:40:58.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/7f/4320d9ce3be404e6310b915c3629fe27bf1e2f438a1a7a3cb0396e32e9a9/uncalled_for-0.2.0-py3-none-any.whl", hash = "sha256:2c0bd338faff5f930918f79e7eb9ff48290df2cb05fcc0b40a7f334e55d4d85f", size = 11351, upload-time = "2026-02-27T17:40:56.804Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.42.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0621e62d86c402b4a17ab2be7f7c055d9bd2f638b9e2/uvicorn-0.42.0.tar.gz", hash = "sha256:9b1f190ce15a2dd22e7758651d9b6d12df09a13d51ba5bf4fc33c383a48e1775", size = 85393, upload-time = "2026-03-16T06:19:50.077Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830, upload-time = "2026-03-16T06:19:48.325Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] diff --git a/tests/e2e/features/mcphost-jms.json b/tests/e2e/features/mcphost-jms.json new file mode 100644 index 00000000..e5de848d --- /dev/null +++ b/tests/e2e/features/mcphost-jms.json @@ -0,0 +1,29 @@ +{ + "mcpServers": { + "oracle-oci-jms-mcp-server": { + "disabled": false, + "timeout": 60, + "type": "stdio", + "command": "uv", + "args": [ + "run", + "python", + "-c", + "from oracle.oci_jms_mcp_server.server import main; main()" + ], + "env": { + "PYTHONPATH": "../../../src/oci-jms-mcp-server", + "PYTHONNOUSERSITE": "0", + "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", + "UV_CACHE_DIR": "/tmp/uv-cache", + "HTTP_PROXY": "http://127.0.0.1:5000", + "HTTPS_PROXY": "http://127.0.0.1:5000", + "OCI_SDK_CERT_BUNDLE": "False", + "PYTHONHTTPSVERIFY": "0", + "OCI_SKIP_SSL_VERIFICATION": "True", + "REQUESTS_CA_BUNDLE": "", + "CURL_CA_BUNDLE": "" + } + } + } +} diff --git a/tests/e2e/features/mocks/services/jms_data.py b/tests/e2e/features/mocks/services/jms_data.py new file mode 100644 index 00000000..626bdf54 --- /dev/null +++ b/tests/e2e/features/mocks/services/jms_data.py @@ -0,0 +1,152 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +FLEETS = [ + { + "id": "ocid1.jmsfleet.oc1..mock-fleet-1", + "displayName": "mock-jms-fleet", + "description": "Mock JMS fleet for e2e coverage", + "compartmentId": "ocid1.tenancy.oc1..mock", + "approximateJreCount": 7, + "approximateInstallationCount": 4, + "approximateApplicationCount": 3, + "approximateManagedInstanceCount": 2, + "approximateJavaServerCount": 1, + "inventoryLog": { + "logGroupId": "ocid1.loggroup.oc1..mock-jms-inventory", + "logId": "ocid1.log.oc1..mock-jms-inventory-log", + }, + "operationLog": { + "logGroupId": "ocid1.loggroup.oc1..mock-jms-operation", + "logId": "ocid1.log.oc1..mock-jms-operation-log", + }, + "isAdvancedFeaturesEnabled": True, + "isExportSettingEnabled": True, + "timeCreated": "2026-02-11T10:15:00Z", + "lifecycleState": "ACTIVE", + "freeformTags": {"env": "test"}, + } +] + +JMS_PLUGINS = [ + { + "id": "ocid1.jmsplugin.oc1..mock-plugin-1", + "agentId": "ocid1.managementagent.oc1..mock-agent-1", + "agentType": "OMA", + "lifecycleState": "ACTIVE", + "availabilityStatus": "ACTIVE", + "fleetId": "ocid1.jmsfleet.oc1..mock-fleet-1", + "compartmentId": "ocid1.tenancy.oc1..mock", + "hostname": "plugin-host-1", + "osFamily": "LINUX", + "osArchitecture": "X86_64", + "osDistribution": "Oracle Linux", + "pluginVersion": "1.2.3", + "timeRegistered": "2026-02-11T10:30:00Z", + "timeLastSeen": "2026-02-12T09:45:00Z", + } +] + +INSTALLATION_SITES = { + "ocid1.jmsfleet.oc1..mock-fleet-1": [ + { + "installationKey": "installation-alpha", + "managedInstanceId": "managed-instance-1", + "jre": { + "version": "17.0.12", + "vendor": "Oracle", + "distribution": "JDK", + "jreKey": "jre-17-oracle", + }, + "securityStatus": "UP_TO_DATE", + "path": "/usr/lib/jvm/java-17-oracle", + "operatingSystem": { + "family": "LINUX", + "name": "Oracle Linux", + "distribution": "Oracle Linux", + "version": "9", + "architecture": "X86_64", + "managedInstanceCount": 1, + }, + "approximateApplicationCount": 2, + "timeLastSeen": "2026-02-12T09:45:00Z", + "lifecycleState": "ACTIVE", + } + ] +} + +FLEET_AGENT_CONFIGURATIONS = { + "ocid1.jmsfleet.oc1..mock-fleet-1": { + "jreScanFrequencyInMinutes": 30, + "javaUsageTrackerProcessingFrequencyInMinutes": 15, + "workRequestValidityPeriodInDays": 7, + "agentPollingIntervalInMinutes": 5, + "linuxConfiguration": { + "includePaths": ["/u01/java", "/usr/lib/jvm"], + "excludePaths": ["/tmp/java-cache"], + }, + "windowsConfiguration": { + "includePaths": ["C:\\Java"], + "excludePaths": ["C:\\Temp\\Java"], + }, + "macOsConfiguration": { + "includePaths": ["/Library/Java/JavaVirtualMachines"], + "excludePaths": ["/tmp/java-cache"], + }, + "timeLastModified": "2026-02-12T10:00:00Z", + } +} + +FLEET_ADVANCED_FEATURE_CONFIGURATIONS = { + "ocid1.jmsfleet.oc1..mock-fleet-1": { + "analyticNamespace": "mock_jms_namespace", + "analyticBucketName": "jms_analytics_bucket", + "lcm": {"isEnabled": True}, + "cryptoEventAnalysis": {"isEnabled": True}, + "advancedUsageTracking": {"isEnabled": True}, + "jfrRecording": {"isEnabled": False}, + "performanceTuningAnalysis": {"isEnabled": False}, + "javaMigrationAnalysis": {"isEnabled": True}, + "timeLastModified": "2026-02-12T10:05:00Z", + } +} + +RESOURCE_INVENTORY = { + "activeFleetCount": 1, + "managedInstanceCount": 2, + "jreCount": 7, + "installationCount": 4, + "applicationCount": 42, +} + +MANAGED_INSTANCE_USAGE = { + "ocid1.jmsfleet.oc1..mock-fleet-1": [ + { + "managedInstanceId": "managed-instance-1", + "managedInstanceType": "ORACLE_MANAGEMENT_AGENT", + "hostname": "usage-host-1", + "hostId": "host-1", + "operatingSystem": { + "family": "LINUX", + "name": "Oracle Linux", + "distribution": "Oracle Linux", + "version": "9", + "architecture": "X86_64", + "managedInstanceCount": 1, + }, + "agent": {"version": "1.1.0"}, + "approximateApplicationCount": 2, + "approximateInstallationCount": 1, + "approximateJreCount": 1, + "drsFileStatus": "PRESENT", + "applicationInvokedBy": "opc", + "timeStart": "2026-02-12T08:00:00Z", + "timeEnd": "2026-02-12T09:00:00Z", + "timeFirstSeen": "2026-02-11T09:00:00Z", + "timeLastSeen": "2026-02-12T09:45:00Z", + } + ] +} diff --git a/tests/e2e/features/mocks/services/jms_routes.py b/tests/e2e/features/mocks/services/jms_routes.py new file mode 100644 index 00000000..c17c0f47 --- /dev/null +++ b/tests/e2e/features/mocks/services/jms_routes.py @@ -0,0 +1,189 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from _common import oci_res +from flask import Blueprint, jsonify, request +from jms_data import ( + FLEET_ADVANCED_FEATURE_CONFIGURATIONS, + FLEET_AGENT_CONFIGURATIONS, + FLEETS, + INSTALLATION_SITES, + JMS_PLUGINS, + MANAGED_INSTANCE_USAGE, + RESOURCE_INVENTORY, +) + +jms_bp = Blueprint("jms", __name__, url_prefix="/20210610") + + +def _apply_limit(items): + try: + limit = request.args.get("limit") + if limit is not None: + lim = int(limit) + if lim >= 0: + return items[:lim] + except Exception: + pass + return items + + +def _find_fleet(fleet_id): + return next((fleet for fleet in FLEETS if fleet.get("id") == fleet_id), None) + + +def _find_plugin(plugin_id): + return next((plugin for plugin in JMS_PLUGINS if plugin.get("id") == plugin_id), None) + + +@jms_bp.route("/fleets", methods=["GET"]) +def list_fleets(): + items = FLEETS + + compartment_id = request.args.get("compartmentId") + if compartment_id: + items = [item for item in items if item.get("compartmentId") == compartment_id] + + fleet_id = request.args.get("id") + if fleet_id: + items = [item for item in items if item.get("id") == fleet_id] + + lifecycle_state = request.args.get("lifecycleState") + if lifecycle_state: + items = [item for item in items if item.get("lifecycleState") == lifecycle_state] + + display_name = request.args.get("displayName") + if display_name: + items = [item for item in items if item.get("displayName") == display_name] + + display_name_contains = request.args.get("displayNameContains") + if display_name_contains: + needle = display_name_contains.lower() + items = [item for item in items if needle in item.get("displayName", "").lower()] + + return oci_res({"items": _apply_limit(items)}) + + +@jms_bp.route("/fleets/", methods=["GET"]) +def get_fleet(fleet_id): + fleet = _find_fleet(fleet_id) + if not fleet: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + return oci_res(fleet) + + +@jms_bp.route("/jmsPlugins", methods=["GET"]) +def list_jms_plugins(): + items = JMS_PLUGINS + + compartment_id = request.args.get("compartmentId") + if compartment_id: + items = [item for item in items if item.get("compartmentId") == compartment_id] + + fleet_id = request.args.get("fleetId") + if fleet_id: + items = [item for item in items if item.get("fleetId") == fleet_id] + + plugin_id = request.args.get("id") + if plugin_id: + items = [item for item in items if item.get("id") == plugin_id] + + lifecycle_state = request.args.get("lifecycleState") + if lifecycle_state: + items = [item for item in items if item.get("lifecycleState") == lifecycle_state] + + hostname_contains = request.args.get("hostnameContains") + if hostname_contains: + needle = hostname_contains.lower() + items = [item for item in items if needle in item.get("hostname", "").lower()] + + return oci_res({"items": _apply_limit(items)}) + + +@jms_bp.route("/jmsPlugins/", methods=["GET"]) +def get_jms_plugin(jms_plugin_id): + plugin = _find_plugin(jms_plugin_id) + if not plugin: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + return oci_res(plugin) + + +@jms_bp.route("/fleets//installationSites", methods=["GET"]) +def list_installation_sites(fleet_id): + if not _find_fleet(fleet_id): + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + + items = INSTALLATION_SITES.get(fleet_id, []) + + managed_instance_id = request.args.get("managedInstanceId") + if managed_instance_id: + items = [item for item in items if item.get("managedInstanceId") == managed_instance_id] + + path_contains = request.args.get("pathContains") + if path_contains: + needle = path_contains.lower() + items = [item for item in items if needle in item.get("path", "").lower()] + + jre_version = request.args.get("jreVersion") + if jre_version: + items = [item for item in items if item.get("jre", {}).get("version") == jre_version] + + return oci_res({"items": _apply_limit(items)}) + + +@jms_bp.route("/fleets//agentConfiguration", methods=["GET"]) +def get_fleet_agent_configuration(fleet_id): + if not _find_fleet(fleet_id): + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + config = FLEET_AGENT_CONFIGURATIONS.get(fleet_id) + if not config: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + return oci_res(config) + + +@jms_bp.route("/fleets//advancedFeatureConfiguration", methods=["GET"]) +def get_fleet_advanced_feature_configuration(fleet_id): + if not _find_fleet(fleet_id): + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + config = FLEET_ADVANCED_FEATURE_CONFIGURATIONS.get(fleet_id) + if not config: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + return oci_res(config) + + +@jms_bp.route("/summarizeResourceInventory", methods=["GET"]) +def summarize_resource_inventory(): + compartment_id = request.args.get("compartmentId") + if compartment_id and compartment_id != "ocid1.tenancy.oc1..mock": + return oci_res( + { + "activeFleetCount": 0, + "managedInstanceCount": 0, + "jreCount": 0, + "installationCount": 0, + "applicationCount": 0, + } + ) + return oci_res(RESOURCE_INVENTORY) + + +@jms_bp.route("/fleets//actions/summarizeManagedInstanceUsage", methods=["GET"]) +def summarize_managed_instance_usage(fleet_id): + if not _find_fleet(fleet_id): + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + + items = MANAGED_INSTANCE_USAGE.get(fleet_id, []) + + managed_instance_id = request.args.get("managedInstanceId") + if managed_instance_id: + items = [item for item in items if item.get("managedInstanceId") == managed_instance_id] + + hostname_contains = request.args.get("hostnameContains") + if hostname_contains: + needle = hostname_contains.lower() + items = [item for item in items if needle in item.get("hostname", "").lower()] + + return oci_res({"items": _apply_limit(items)}) diff --git a/tests/e2e/features/oci-jms-mcp-server.feature b/tests/e2e/features/oci-jms-mcp-server.feature new file mode 100644 index 00000000..fe763a4c --- /dev/null +++ b/tests/e2e/features/oci-jms-mcp-server.feature @@ -0,0 +1,40 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +Feature: OCI JMS MCP Server + Scenario: List the JMS tools available in the agent + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "What JMS mcp tools do you have" + Then the response should contain a list of JMS tools available + + Scenario: List JMS fleets + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my JMS fleets" + Then the response should contain a list of JMS fleets + + Scenario: Get fleet details and fleet configuration + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my JMS fleets, then get the details of the first JMS fleet in the list, then get the fleet agent configuration and advanced feature configuration for that fleet" + Then the response should contain fleet configuration details + + Scenario: List JMS plugins and get the first plugin + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my JMS fleets, then list the JMS plugins in the first fleet, then get the details of the first JMS plugin" + Then the response should contain JMS plugin details + + Scenario: List installation sites and summarize managed instance usage + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my JMS fleets, then list the installation sites in the first JMS fleet, then summarize managed instance usage for that same fleet" + Then the response should contain installation site and managed instance usage details + + Scenario: Summarize JMS resource inventory + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "summarize my JMS resource inventory" + Then the response should contain JMS resource inventory details diff --git a/tests/e2e/features/steps/oci-jms-mcp-server-steps.py b/tests/e2e/features/steps/oci-jms-mcp-server-steps.py new file mode 100644 index 00000000..fb55e0f6 --- /dev/null +++ b/tests/e2e/features/steps/oci-jms-mcp-server-steps.py @@ -0,0 +1,103 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from behave import then + + +@then("the response should contain a list of JMS tools available") +def step_impl_jms_tools_available(context): + response_json = context.response.json() + assert "content" in response_json["message"], "Response does not contain a content key." + content = response_json["message"]["content"].lower() + expected_tools = [ + "list_fleets", + "get_fleet", + "list_jms_plugins", + "get_jms_plugin", + "list_installation_sites", + "get_fleet_agent_configuration", + "get_fleet_advanced_feature_configuration", + "summarize_resource_inventory", + "summarize_managed_instance_usage", + ] + assert any(tool in content for tool in expected_tools), "JMS tools could not be queried." + + +@then("the response should contain a list of JMS fleets") +def step_impl_list_jms_fleets(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert ( + "ocid1.jmsfleet" in content or "mock-jms-fleet" in content + ), "List of JMS fleets not found." + + +@then("the response should contain fleet configuration details") +def step_impl_fleet_configuration(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + value in content + for value in [ + "mock-jms-fleet", + "analyticbucketname", + "jms_analytics_bucket", + "/u01/java", + "jrescanfrequencyinminutes", + ] + ), "Fleet configuration details not found." + + +@then("the response should contain JMS plugin details") +def step_impl_jms_plugin_details(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + value in content + for value in [ + "ocid1.jmsplugin", + "plugin-host-1", + "oraclemanagementagent", + "pluginversion", + ] + ), "JMS plugin details not found." + + +@then("the response should contain installation site and managed instance usage details") +def step_impl_installation_site_and_usage(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + value in content + for value in [ + "installation-alpha", + "/usr/lib/jvm/java-17-oracle", + "managed-instance-1", + "approximatejrecount", + "usage-host-1", + ] + ), "Installation site or managed instance usage details not found." + + +@then("the response should contain JMS resource inventory details") +def step_impl_resource_inventory(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + value in content + for value in [ + "activefleetcount", + "managedinstancecount", + "installationcount", + "applicationcount", + "42", + ] + ), "JMS resource inventory details not found." From c981767fc5366aa742b609b2e0ab338eba07c945 Mon Sep 17 00:00:00 2001 From: bhuvesku Date: Tue, 17 Mar 2026 23:19:22 +0800 Subject: [PATCH 02/11] fix(jms): ignore blank enum-list and timestamp filter inputs --- src/oci-jms-mcp-server/README.md | 4 +- .../oracle/oci_jms_mcp_server/server.py | 14 +- .../tests/test_jms_tools.py | 132 ++++++++++++++++++ 3 files changed, 144 insertions(+), 6 deletions(-) diff --git a/src/oci-jms-mcp-server/README.md b/src/oci-jms-mcp-server/README.md index c1761513..7997351f 100644 --- a/src/oci-jms-mcp-server/README.md +++ b/src/oci-jms-mcp-server/README.md @@ -56,7 +56,7 @@ When `JMS_TEST_ENVIRONMENT=PROD` or the variable is unset, the server keeps the From the JMS package directory: ```sh -cd /Users/bhuvesku/Documents/projects/oracle-jms-mcp-server/src/oci-jms-mcp-server +cd src/oci-jms-mcp-server mkdir -p /tmp/uv-cache UV_CACHE_DIR=/tmp/uv-cache uv sync ``` @@ -103,7 +103,7 @@ http://127.0.0.1:8888/mcp "command": "/bin/zsh", "args": [ "-lc", - "cd /Users/bhuvesku/Documents/projects/oracle-jms-mcp-server/src/oci-jms-mcp-server && OCI_CONFIG_FILE=/Users/bhuvesku/.oci/config OCI_CONFIG_PROFILE=DEFAULT UV_CACHE_DIR=/tmp/uv-cache uv run oracle.oci-jms-mcp-server" + "cd src/oci-jms-mcp-server && OCI_CONFIG_FILE=~/.oci/config OCI_CONFIG_PROFILE=DEFAULT UV_CACHE_DIR=/tmp/uv-cache uv run oracle.oci-jms-mcp-server" ], "env": { "FASTMCP_LOG_LEVEL": "ERROR" diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py index d197cbd7..16e1f95e 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py @@ -36,6 +36,7 @@ from . import __project__, __version__ from .util import get_jms_service_endpoint +# Setup logger = Logger(__name__, level="INFO") mcp = FastMCP(name=__project__) @@ -45,7 +46,7 @@ "OCI_PROFILE": "OCI_CONFIG_PROFILE", } - +# Input Normalization Helpers def _normalize_enum(value: Optional[str]) -> Optional[str]: """Normalize flexible enum input into OCI SDK-style uppercase values.""" if value is None: @@ -60,7 +61,9 @@ def _normalize_enum_list(values: Optional[list[str]]) -> Optional[list[str]]: """Normalize lists of enum-like values.""" if values is None: return None - return [_normalize_enum(value) for value in values] + normalized = [_normalize_enum(value) for value in values] + normalized = [value for value in normalized if value is not None] + return normalized or None def _normalized_key(value: str) -> str: @@ -100,7 +103,10 @@ def _parse_rfc3339(value: Optional[str]) -> Optional[datetime]: """Convert an optional RFC3339 timestamp string into a datetime.""" if value is None: return None - return datetime.fromisoformat(value.replace("Z", "+00:00")) + normalized = value.strip() + if not normalized: + return None + return datetime.fromisoformat(normalized.replace("Z", "+00:00")) def _warn_on_unsupported_env_aliases(): @@ -144,7 +150,7 @@ def get_jms_client(): client_kwargs = _omit_none(signer=signer, service_endpoint=service_endpoint) return oci.jms.JavaManagementServiceClient(config, **client_kwargs) - +# mcp tool functions @mcp.tool(description="List Java Management Service fleets in a compartment.") def list_fleets( compartment_id: Optional[str] = Field( diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py index 39053a72..bb307a1a 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py @@ -305,6 +305,68 @@ async def test_list_installation_sites_normalizes_os_family(self, mock_get_clien assert result[0]["installation_key"] == "install1" assert mock_client.list_installation_sites.call_args.kwargs["os_family"] == ["LINUX"] + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_installation_sites_omits_blank_os_family_entries(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.InstallationSiteCollection( + items=[ + oci.jms.models.InstallationSiteSummary( + installation_key="install1", + managed_instance_id="mi1", + path="/usr/java", + ) + ] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_installation_sites.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "list_installation_sites", + {"fleet_id": "fleet1", "os_family": ["linux", " "]}, + ) + ).structured_content["result"] + + assert result[0]["installation_key"] == "install1" + assert mock_client.list_installation_sites.call_args.kwargs["os_family"] == ["LINUX"] + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_installation_sites_omits_os_family_when_only_blank_values(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.InstallationSiteCollection( + items=[ + oci.jms.models.InstallationSiteSummary( + installation_key="install1", + managed_instance_id="mi1", + path="/usr/java", + ) + ] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_installation_sites.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "list_installation_sites", + {"fleet_id": "fleet1", "os_family": [" "]}, + ) + ).structured_content["result"] + + assert result[0]["installation_key"] == "install1" + assert "os_family" not in mock_client.list_installation_sites.call_args.kwargs + @pytest.mark.asyncio @patch("oracle.oci_jms_mcp_server.server.get_jms_client") async def test_list_installation_sites_normalizes_sort_by(self, mock_get_client): @@ -404,6 +466,36 @@ async def test_summarize_resource_inventory(self, mock_get_client): ).structured_content assert result["active_fleet_count"] == 1 + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_summarize_resource_inventory_omits_blank_time_start(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.ResourceInventory( + active_fleet_count=1, + managed_instance_count=2, + jre_count=3, + installation_count=4, + application_count=5, + ) + mock_client.summarize_resource_inventory.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "summarize_resource_inventory", + { + "compartment_id": "ocid1.compartment.oc1..test", + "time_start": "", + }, + ) + ).structured_content + + assert result["active_fleet_count"] == 1 + assert "time_start" not in mock_client.summarize_resource_inventory.call_args.kwargs + @pytest.mark.asyncio @patch("oracle.oci_jms_mcp_server.server.get_jms_client") async def test_summarize_managed_instance_usage(self, mock_get_client): @@ -473,6 +565,46 @@ async def test_summarize_managed_instance_usage_normalizes_fields_and_sort_by( == "timeFirstSeen" ) + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_summarize_managed_instance_usage_omits_blank_os_family_entries_and_time_end( + self, mock_get_client + ): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.ManagedInstanceUsageCollection( + items=[ + oci.jms.models.ManagedInstanceUsage( + managed_instance_id="mi1", + managed_instance_type="ORACLE_MANAGEMENT_AGENT", + hostname="host1", + ) + ] + ) + response.has_next_page = False + response.next_page = None + mock_client.summarize_managed_instance_usage.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "summarize_managed_instance_usage", + { + "fleet_id": "fleet1", + "os_family": ["linux", " "], + "time_end": " ", + }, + ) + ).structured_content["result"] + + assert result[0]["managed_instance_id"] == "mi1" + assert mock_client.summarize_managed_instance_usage.call_args.kwargs["os_family"] == [ + "LINUX" + ] + assert "time_end" not in mock_client.summarize_managed_instance_usage.call_args.kwargs + class TestServerMain: @patch("oracle.oci_jms_mcp_server.server.mcp.run") From cca51d9d4bb24cadbf3f83193b4f5a51c928d0da Mon Sep 17 00:00:00 2001 From: bhuvesku Date: Tue, 17 Mar 2026 23:25:01 +0800 Subject: [PATCH 03/11] fix(jms): added a Temporary manual JMS MCP validation client. --- src/oci-jms-mcp-server/scripts/README.md | 59 ++++ .../scripts/test_jms_mcp_client.py | 271 ++++++++++++++++++ 2 files changed, 330 insertions(+) create mode 100644 src/oci-jms-mcp-server/scripts/README.md create mode 100644 src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py diff --git a/src/oci-jms-mcp-server/scripts/README.md b/src/oci-jms-mcp-server/scripts/README.md new file mode 100644 index 00000000..8ef4151d --- /dev/null +++ b/src/oci-jms-mcp-server/scripts/README.md @@ -0,0 +1,59 @@ +# JMS Script Utilities + +This folder contains temporary local utilities for manual JMS MCP testing. + +## `test_jms_mcp_client.py` + +This is a temporary manual validation client for the JMS MCP server. +It connects to an already-running HTTP MCP server and exercises the 9 JMS tools. + +Remove this script and this README before committing if you do not want local-only test utilities in the final change set. + +### Prerequisites + +- The JMS MCP server must already be running over HTTP. +- Default target URL: `http://127.0.0.1:8888/mcp` +- The repo virtualenv should exist at `../../.venv` when run from `src/oci-jms-mcp-server` + +### Run All 9 Tools + +From the JMS package directory: + +```sh +cd src/oci-jms-mcp-server +../../.venv/bin/python scripts/test_jms_mcp_client.py +``` + +### Run One Tool + +```sh +../../.venv/bin/python scripts/test_jms_mcp_client.py --tool list_fleets +``` + +### Override Inputs + +```sh +../../.venv/bin/python scripts/test_jms_mcp_client.py \ + --url http://127.0.0.1:8888/mcp \ + --compartment-id ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq \ + --fleet-id ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiayfvn2rk5i3ue5cwd67ru4h3gnalej6dqievm233hytla \ + --limit 10 \ + --timeout 20 +``` + +### Output + +The script prints: + +- discovered tool names +- `PASS` / `FAIL` / `TIMEOUT` / `SKIPPED` for each tool +- a final summary line with pass/fail counts + +### Supported Options + +- `--url` +- `--compartment-id` +- `--fleet-id` +- `--limit` +- `--timeout` +- `--tool` diff --git a/src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py b/src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py new file mode 100644 index 00000000..ce9bab49 --- /dev/null +++ b/src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py @@ -0,0 +1,271 @@ +""" +Temporary manual JMS MCP validation client. + +This script connects to an already-running HTTP MCP server and exercises the +JMS tool surface for quick operator testing. Remove it before committing. +""" + +from __future__ import annotations + +import argparse +import asyncio +import sys +from typing import Any + +from fastmcp import Client + +DEFAULT_URL = "http://127.0.0.1:8888/mcp" + +#compartment = JMS-TEST-CANARY +DEFAULT_COMPARTMENT_ID = ( + "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq" +) + +#fleet name = auto-ol8-epp-test +DEFAULT_FLEET_ID = ( + "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiaukoncqzg33zxlz5zcfzw7hsrfrrbceg7vf7fe2dq73nq" +) + +TOOL_ORDER = [ + "list_fleets", + "get_fleet", + "list_jms_plugins", + "get_jms_plugin", + "list_installation_sites", + "get_fleet_agent_configuration", + "get_fleet_advanced_feature_configuration", + "summarize_resource_inventory", + "summarize_managed_instance_usage", +] + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Run manual validation against a running JMS MCP HTTP server." + ) + parser.add_argument("--url", default=DEFAULT_URL, help="MCP HTTP endpoint URL.") + parser.add_argument( + "--compartment-id", + default=DEFAULT_COMPARTMENT_ID, + help="Compartment OCID used by list and summarize calls.", + ) + parser.add_argument( + "--fleet-id", + default=DEFAULT_FLEET_ID, + help="Fleet OCID used by fleet-scoped calls.", + ) + parser.add_argument( + "--limit", + type=int, + default=10, + help="Limit for list-style tool calls.", + ) + parser.add_argument( + "--timeout", + type=int, + default=20, + help="Per-tool timeout in seconds.", + ) + parser.add_argument( + "--tool", + choices=["all", *TOOL_ORDER], + default="all", + help="Run one tool or the full tool suite.", + ) + return parser.parse_args() + + +def summarize_structured_content(structured: Any) -> tuple[str, str | None]: + if isinstance(structured, dict): + if "result" in structured and isinstance(structured["result"], list): + items = structured["result"] + first_id = None + if items and isinstance(items[0], dict): + first_id = items[0].get("id") + summary = f"count={len(items)}" + if first_id: + summary += f" first_id={first_id}" + return summary, first_id + if "id" in structured: + return f"id={structured['id']}", structured["id"] + keys = ",".join(sorted(structured.keys())[:5]) + return f"keys={keys}", None + + if isinstance(structured, list): + first_id = None + if structured and isinstance(structured[0], dict): + first_id = structured[0].get("id") + summary = f"count={len(structured)}" + if first_id: + summary += f" first_id={first_id}" + return summary, first_id + + return f"type={type(structured).__name__}", None + + +async def call_tool( + client: Client, + tool_name: str, + payload: dict[str, Any], + timeout: int, +) -> tuple[str, str | None]: + try: + result = await asyncio.wait_for(client.call_tool(tool_name, payload), timeout=timeout) + summary, first_id = summarize_structured_content(result.structured_content) + return f"PASS {tool_name}: {summary}", first_id + except asyncio.TimeoutError: + return f"TIMEOUT {tool_name}: timed out after {timeout}s", None + except Exception as exc: # noqa: BLE001 + return f"FAIL {tool_name}: {type(exc).__name__}: {exc}", None + + +async def run_suite(args: argparse.Namespace) -> int: + requested_tools = TOOL_ORDER if args.tool == "all" else [args.tool] + results: list[str] = [] + failures = 0 + plugin_id: str | None = None + + async with Client(args.url) as client: + tools = await asyncio.wait_for(client.list_tools(), timeout=args.timeout) + tool_names = [tool.name for tool in tools] + results.append(f"Connected to {args.url}") + results.append(f"Discovered {len(tool_names)} tools: {', '.join(tool_names)}") + + if args.tool in ("all", "list_fleets"): + line, _ = await call_tool( + client, + "list_fleets", + { + "compartment_id": args.compartment_id, + "limit": args.limit, + "sort_order": "ASC", + "sort_by": "displayName", + }, + args.timeout, + ) + results.append(line) + failures += int(not line.startswith("PASS")) + + if args.tool in ("all", "get_fleet"): + line, _ = await call_tool( + client, + "get_fleet", + {"fleet_id": args.fleet_id}, + args.timeout, + ) + results.append(line) + failures += int(not line.startswith("PASS")) + + if args.tool in ("all", "list_jms_plugins"): + line, plugin_id = await call_tool( + client, + "list_jms_plugins", + { + "fleet_id": args.fleet_id, + "limit": args.limit, + "sort_order": "ASC", + }, + args.timeout, + ) + results.append(line) + failures += int(not line.startswith("PASS")) + + if args.tool == "get_jms_plugin": + plugin_id = None + + if args.tool in ("all", "get_jms_plugin"): + if plugin_id is None: + if args.tool == "get_jms_plugin": + results.append( + "FAIL get_jms_plugin: requires a plugin returned by list_jms_plugins in all-tools mode" + ) + failures += 1 + else: + results.append("SKIPPED get_jms_plugin: no plugin returned by list_jms_plugins") + else: + line, _ = await call_tool( + client, + "get_jms_plugin", + {"jms_plugin_id": plugin_id}, + args.timeout, + ) + results.append(line) + failures += int(not line.startswith("PASS")) + + if args.tool in ("all", "list_installation_sites"): + line, _ = await call_tool( + client, + "list_installation_sites", + { + "fleet_id": args.fleet_id, + "limit": args.limit, + "sort_order": "ASC", + }, + args.timeout, + ) + results.append(line) + failures += int(not line.startswith("PASS")) + + if args.tool in ("all", "get_fleet_agent_configuration"): + line, _ = await call_tool( + client, + "get_fleet_agent_configuration", + {"fleet_id": args.fleet_id}, + args.timeout, + ) + results.append(line) + failures += int(not line.startswith("PASS")) + + if args.tool in ("all", "get_fleet_advanced_feature_configuration"): + line, _ = await call_tool( + client, + "get_fleet_advanced_feature_configuration", + {"fleet_id": args.fleet_id}, + args.timeout, + ) + results.append(line) + failures += int(not line.startswith("PASS")) + + if args.tool in ("all", "summarize_resource_inventory"): + line, _ = await call_tool( + client, + "summarize_resource_inventory", + {"compartment_id": args.compartment_id}, + args.timeout, + ) + results.append(line) + failures += int(not line.startswith("PASS")) + + if args.tool in ("all", "summarize_managed_instance_usage"): + line, _ = await call_tool( + client, + "summarize_managed_instance_usage", + { + "fleet_id": args.fleet_id, + "limit": args.limit, + "sort_order": "ASC", + }, + args.timeout, + ) + results.append(line) + failures += int(not line.startswith("PASS")) + + for line in results: + print(line) + + executed = sum( + line.startswith(("PASS", "FAIL", "TIMEOUT", "SKIPPED")) for line in results + ) + passed = sum(line.startswith("PASS") for line in results) + skipped = sum(line.startswith("SKIPPED") for line in results) + print(f"Summary: passed={passed} failed={failures} skipped={skipped} executed={executed}") + return 0 if failures == 0 else 1 + + +def main() -> int: + args = parse_args() + return asyncio.run(run_suite(args)) + + +if __name__ == "__main__": + raise SystemExit(main()) From ce009cde97d7250af6927c66e476567aea65aca3 Mon Sep 17 00:00:00 2001 From: bhuvesku Date: Tue, 17 Mar 2026 23:29:48 +0800 Subject: [PATCH 04/11] chore: remove AGENT.md from repo --- src/oci-jms-mcp-server/AGENT.md | 256 -------------------------------- 1 file changed, 256 deletions(-) delete mode 100644 src/oci-jms-mcp-server/AGENT.md diff --git a/src/oci-jms-mcp-server/AGENT.md b/src/oci-jms-mcp-server/AGENT.md deleted file mode 100644 index aef2b98b..00000000 --- a/src/oci-jms-mcp-server/AGENT.md +++ /dev/null @@ -1,256 +0,0 @@ -# OCI JMS MCP Server Implementation Guide - -Implement `src/oci-jms-mcp-server` as a standard, service-specific OCI Python MCP server. - -Do not invent a new architecture for JMS. Match the structure and coding style already used by the mature Python OCI MCP servers in this repository. - -## Goal - -Build `oci-jms-mcp-server` as a read-only v1 focused on fleet inventory and discovery in OCI Java Management Service (JMS). - -The implementation must follow the same patterns used by: - -- `src/oci-compute-mcp-server` -- `src/oci-networking-mcp-server` -- `src/oci-monitoring-mcp-server` -- `src/oci-usage-mcp-server` - -Use those servers as the source of truth for packaging, client bootstrap, tool layout, model mapping, tests, and README style. - -## Required Files - -Create and maintain this exact package-local file set: - -- `src/oci-jms-mcp-server/pyproject.toml` -- `src/oci-jms-mcp-server/README.md` -- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/__init__.py` -- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py` -- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py` -- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py` -- `src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py` - -Do not add repo-wide shared abstractions just to support JMS. - -## Package Structure Pattern - -Follow the same package envelope used by the other Python OCI servers: - -1. `pyproject.toml` - - Python package name: `oracle.oci-jms-mcp-server` - - entrypoint: `oracle.oci-jms-mcp-server = "oracle.oci_jms_mcp_server.server:main"` - - use `hatchling` - - depend on `fastmcp`, `oci`, and `pydantic` - - include the usual dev dependencies for `pytest`, `pytest-asyncio`, and `pytest-cov` - -2. `README.md` - - short overview - - stdio run command - - HTTP transport run command using `ORACLE_MCP_HOST` and `ORACLE_MCP_PORT` - - a tool table listing the JMS tools implemented - - the same OCI credential/safety note used in peer servers - -3. `__init__.py` - - preserve the standard Oracle copyright header - - define `__project__` and `__version__` - -## Server Implementation Pattern - -`server.py` must look and behave like the service-specific servers, not like `oci-cloud-mcp-server`. - -### Required bootstrap - -- Add the standard Oracle license header. -- Import `__project__` and `__version__` from the package. -- Create `logger = Logger(__name__, level="INFO")`. -- Create `mcp = FastMCP(name=__project__)`. - -### Required client helper - -Implement `get_jms_client()` using the same pattern as compute, networking, monitoring, usage, registry, and resource-search: - -1. load OCI config with: - - `file_location=os.getenv("OCI_CONFIG_FILE", oci.config.DEFAULT_LOCATION)` - - `profile_name=os.getenv("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE)` -2. derive the user agent name from `__project__` -3. set `config["additional_user_agent"] = f"{user_agent_name}/{__version__}"` -4. load the private key from `config["key_file"]` -5. read the security token from `config["security_token_file"]` -6. create `oci.auth.signers.SecurityTokenSigner` -7. return `oci.jms.JavaManagementServiceClient(config, signer=signer)` - -Do not replace this with a generic helper module or a dynamic client loader. - -### Required `main()` - -Implement: - -- stdio mode by default -- HTTP mode only when both `ORACLE_MCP_HOST` and `ORACLE_MCP_PORT` are set -- `mcp.run(transport="http", host=host, port=int(port))` when HTTP is enabled -- `mcp.run()` otherwise - -Keep the entrypoint in `server.py`. Do not create `main.py`. - -## Tool Design Pattern - -Use hand-written `@mcp.tool` functions exactly like the other service servers. - -### Tool rules - -- Each tool must have a clear description. -- Use `pydantic.Field` metadata for required and optional parameters where it adds clarity. -- Use `Literal` for constrained enum-like values when practical. -- Return typed Pydantic models, not raw OCI SDK objects. -- Catch exceptions only for lightweight logging and then re-raise. -- Paginate list-style APIs manually using `has_next_page` and `next_page`. -- Stop pagination when the server-provided page stream ends or the requested `limit` has been satisfied. - -### V1 JMS tool set - -Implement this exact read-only tool set first: - -- `list_fleets` -- `get_fleet` -- `list_jms_plugins` -- `get_jms_plugin` -- `list_installation_sites` -- `get_fleet_agent_configuration` -- `get_fleet_advanced_feature_configuration` -- `summarize_resource_inventory` -- `summarize_managed_instance_usage` - -Use the OCI SDK call shapes that are already available in `oci.jms.JavaManagementServiceClient`. - -### Scope boundaries for v1 - -Do not add these in v1: - -- create/update/delete fleet operations -- create/update/delete JMS plugin operations -- analysis request submission APIs -- DRS file mutation APIs -- task schedule mutation APIs -- generic OCI SDK invocation - -The first cut must be read-only and inventory-focused. - -## Model Design Pattern - -`models.py` must mirror the approach used by compute, networking, registry, and other typed servers. - -### Required model rules - -- Use Pydantic `BaseModel`. -- Add field descriptions. -- Keep field names aligned with OCI SDK attribute names. -- Add nested Pydantic models only when needed for stable serialization. -- Implement explicit `map_*` functions for every top-level returned OCI SDK model. -- Return `None` from mappers when the input is `None`. -- Preserve datetimes as datetimes. -- Preserve optional fields as optional. - -### V1 model set - -Implement only the models required for the v1 tools: - -- `FleetSummary` -- `Fleet` -- `JmsPluginSummary` -- `JmsPlugin` -- `InstallationSiteSummary` -- `FleetAgentConfiguration` -- `FleetAdvancedFeatureConfiguration` -- `ManagedInstanceUsageSummary` -- `ResourceInventory` - -Add nested models only when they are actually present in those responses and would otherwise serialize poorly. - -Do not attempt to mirror the entire JMS SDK in v1. - -## Testing Pattern - -Follow the package-local test layout used by the other Python OCI servers. - -### `test_jms_tools.py` - -Use `fastmcp.Client` and patch `get_jms_client()`. - -Required coverage: - -- happy-path test for each tool -- at least one paginated list test -- at least one paginated summarize/list-style usage test -- exception propagation test for a list tool -- exception propagation test for a get tool -- `main()` transport selection tests -- `get_jms_client()` test verifying: - - config loading - - token file read - - signer creation - - `additional_user_agent` assignment - - `JavaManagementServiceClient` instantiation - -### `test_jms_models.py` - -Required coverage: - -- mapper test for each top-level model returned by the tools -- nested object mapping where applicable -- datetime preservation -- optional and missing field handling - -## Reference Patterns To Copy - -Use these files as implementation references: - -- `src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/server.py` - - classic service-specific tool layout -- `src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/models.py` - - typed Pydantic models plus explicit `map_*` helpers -- `src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/server.py` - - straightforward client bootstrap and paginated list tools -- `src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/server.py` - - richer read-only tool descriptions and optional filtering -- `src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/server.py` - - minimal `main()` and client bootstrap pattern -- `src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/tests/test_usage_tools.py` - - server bootstrap and client helper tests - -## Anti-Patterns To Avoid - -Do not do any of the following: - -- do not build JMS as a generic OCI API wrapper -- do not use the `oci-cloud-mcp-server` dynamic invocation design -- do not return raw OCI SDK model objects from tools -- do not skip Pydantic mapping because `oci.util.to_dict` is easier -- do not add mutating JMS tools in v1 -- do not create repo-wide helpers just to reduce duplication -- do not omit `main()` -- do not omit tests -- do not leave the README or `pyproject.toml` out of sync with the tool surface - -## Verification Checklist - -Before considering the JMS server complete, verify all of the following: - -- `src/oci-jms-mcp-server/pyproject.toml` exists and defines the correct package script -- the package imports successfully -- `server.py` exposes `mcp`, `get_jms_client()`, and `main()` -- stdio and HTTP transport behavior are both tested -- all v1 tools are registered and callable through `fastmcp.Client` -- list-style tools paginate correctly -- outputs are typed and serialized through Pydantic models -- `get_jms_client()` sets `additional_user_agent` -- README documents the final v1 tool list -- package-local tests pass - -## Implementation Defaults - -If a decision is not explicitly documented elsewhere, use these defaults: - -- prefer read-only behavior -- prefer service-specific typed tools -- prefer explicit mappers over clever abstractions -- prefer matching peer server patterns over reducing line count -- prefer the smallest model set that fully supports the v1 JMS tools From c97162273f3e20977573d7b4743dd2dec0557ff4 Mon Sep 17 00:00:00 2001 From: bhuvesku Date: Tue, 17 Mar 2026 23:35:25 +0800 Subject: [PATCH 05/11] chore(jms): remove generated test artifacts --- .../JMS_TOOL_TEST_REPORT.md | 756 ------------------ .../JMS_TOOL_TEST_RESULTS.json | 641 --------------- 2 files changed, 1397 deletions(-) delete mode 100644 src/oci-jms-mcp-server/JMS_TOOL_TEST_REPORT.md delete mode 100644 src/oci-jms-mcp-server/JMS_TOOL_TEST_RESULTS.json diff --git a/src/oci-jms-mcp-server/JMS_TOOL_TEST_REPORT.md b/src/oci-jms-mcp-server/JMS_TOOL_TEST_REPORT.md deleted file mode 100644 index e0ae1d5b..00000000 --- a/src/oci-jms-mcp-server/JMS_TOOL_TEST_REPORT.md +++ /dev/null @@ -1,756 +0,0 @@ -# JMS Tool Test Report - -Date: 2026-03-17 - -Target fleet: - -```text -ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq -``` - -Target compartment: - -```text -ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq -``` - -This report was produced from a live rerun of all 9 JMS tools through `fastmcp.Client(mcp)`. - -## 1. `list_fleets` - -Status: `ok` - -Input: - -```json -{ - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "sort_order": "ASC", - "sort_by": "displayName", - "limit": 10 -} -``` - -Output: - -```json -[ - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguia7iry2zw5zygfmqejmaessuuzbll2im7kdqxgp5rwgzna", - "display_name": "Test2", - "description": "Test2", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiavs2et3qpq2klmgurnigpbqya3sbr5m3otqntlzgupk6a", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaj3zfojeftftvgji2mw4fu7smsfz2g2d43fyx2lmu66da" - }, - "operation_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiavs2et3qpq2klmgurnigpbqya3sbr5m3otqntlzgupk6a", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiajoc4aj43veuw6fpvqymbi5fxiki62ekpwmwy3ldp3ada" - }, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2026-02-10T09:27:25.338000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "kwickram", - "CreatedOn": "2026-02-10T09:27:24.860Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguia3txn7ennnkfbdzlceq4j5nyj5s4z7piy7c4icbn6prfa", - "display_name": "auto-move-managed-instance-A", - "description": "autotest fleet", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7uexc2le75lji7xl26qjrpkqnzqv7sszlmouktxcbi7q", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiazf652yp5zdpmbr4s7narowmw3nyap2jykdxy4z4lx2gq" - }, - "operation_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7uexc2le75lji7xl26qjrpkqnzqv7sszlmouktxcbi7q", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiath6unzwkgzlgqarrguaborwbam6husaexgfhfh4gip3a" - }, - "is_advanced_features_enabled": false, - "is_export_setting_enabled": false, - "time_created": "2023-06-08T16:12:37.330000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "klevytsk", - "CreatedOn": "2023-06-08T16:12:36.173Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguialuyracd2pi6zqjxg2z5n2i7lv5ywkrjjwkfhavpi6twa", - "display_name": "fleet-20250613-1315", - "description": "Test fleet created by SQE", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaunp4y53xiohb5hqvasykcqdjroukjnhvfaexyqqixwfq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiavnauazecrpuf7s5o4vzfxygzy2e2uefp2ll5kkiimgpq" - }, - "operation_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaunp4y53xiohb5hqvasykcqdjroukjnhvfaexyqqixwfq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaejedobtl2mmcx4biucxr46lqiehtbshjqnnt46zmsifa" - }, - "is_advanced_features_enabled": false, - "is_export_setting_enabled": false, - "time_created": "2025-06-13T05:15:46.987000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "kwickram", - "CreatedOn": "2025-06-13T05:15:46.402Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiaovou2nqghvburhlmoy42gzk62vvtzak4zzz2i5nhlmxa", - "display_name": "fleet-20251014-0910_test-incubator_canary", - "description": "Testing fleet-20251014-0910_test-incubator_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiabmgw3bckpm63xiujmh6fkmkdwqiu4s23mytbx767j6eq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiad4ltos3e6vgjwu7imnqiyv46li7i6ekgbjxys4ki2z2a" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-10-14T09:12:34.940000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-10-14T09:12:34.660Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiatzxyg3gkpuwcqffkpaiwminrjxtxdyduwbhnrlwsi3hq", - "display_name": "fleet-20251016-1325_test-null_canary", - "description": "Testing fleet-20251016-1325_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiayml34lgbdsqxadrtei5hzxkmkg3sxcannmjytx6e4dla", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia55hr5yvk4zfxcremzz6q24grrsjocsj2vcs6lkr3toka" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-10-16T13:25:52.653000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-10-16T13:25:52.110Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiaxjjqturjxfb7ffgiwe7ozx2qomqftw55swvv6e2nvllq", - "display_name": "fleet-20251017-0834_test-null_canary", - "description": "Testing fleet-20251017-0834_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7yo6e5f6pw5hpylhfsiro5lwh7ps5rj3ue7jclwlkm2a", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia7olcl6ppvivnqtpilylgtaemtszmsmgppozf5t7qapfq" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-10-17T08:34:59.591000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-10-17T08:34:59.345Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiame4obzlb2r4micfk7vr5qhjdjaxb5s62a4ehmdqrneaq", - "display_name": "fleet-20251216-0835_test-null_canary", - "description": "Testing fleet-20251216-0835_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiao2v4e5xec4yeu5zo46bfwwgbqm7ntfnwmj7mdyio2t3a", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia3g2ogwthju5jsvef2qipkl7dv2ufym63eyhkzhhtugxq" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-12-16T08:35:37.365000Z", - "lifecycle_state": "DELETED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-12-16T08:35:37.100Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiarvjb6ikdnmwgy27wvx6gyzszcpp2qn23k4mnhnjyzk6a", - "display_name": "fleet-20251216-1434_test-null_canary", - "description": "Testing fleet-20251216-1434_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaked7r3fqtylyru2zosqdmigywgajqcwqv4j7o2ehpofq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiawezvurunu63y534hjza5e6ebcy56kfe2z4rgx3cb4yfq" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-12-16T14:34:38.887000Z", - "lifecycle_state": "DELETED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-12-16T14:34:38.639Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguial2nqjd7hahr5qz2laytp2qcnjfcjmcjsylhrh4egwaia", - "display_name": "fleet-20251216-2034_test-null_canary", - "description": "Testing fleet-20251216-2034_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiacugfanyfx7rnaq6wcngvdkp7t7lqjxjlfdfkrwqvrn2a", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiatep563wid7ucmpjavtsmi4v35uv6myiu4bu2dlcol2jq" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-12-16T20:34:51.004000Z", - "lifecycle_state": "DELETED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-12-16T20:34:50.735Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiahxduzprvifslhei6lczre6axawn7pou5hx6h5l7d2pva", - "display_name": "fleet-20251217-0234_test-null_canary", - "description": "Testing fleet-20251217-0234_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiay2c6dklzrc4gg3epv4v4vjedcug4t5b3kvx23yzsayba", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiak4g3fucg7xvgun63tlu44bubw22rzx3ycxdn3zykx3sa" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-12-17T02:34:31.877000Z", - "lifecycle_state": "DELETED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-12-17T02:34:31.584Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - } -] -``` - -## 2. `get_fleet` - -Status: `ok` - -Input: - -```json -{ - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" -} -``` - -Output: - -```json -{ - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "display_name": "jms10-bpn-k8s", - "description": "JMS 10 Test Fleet", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguian5ex7u5otwik5adoq6n65fbbwdu6yfarknbzo4uyyxma" - }, - "operation_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaxmmcot7l6pynst3mc3d6abrvc3ol6qd7fv3rkp4f66fq" - }, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-09-25T09:42:20.123000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "bbanathu", - "CreatedOn": "2025-09-25T09:42:19.695Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } -} -``` - -## 3. `list_jms_plugins` - -Status: `ok` - -Input: - -```json -{ - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "sort_order": "DESC", - "sort_by": "timeLastSeen", - "limit": 10 -} -``` - -Output: - -```json -[ - { - "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea", - "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguia6q6c7ces7dz3tw7nf3unz5ofmhccxcpcp7lchjpccraq", - "agent_type": "OMA", - "lifecycle_state": "DELETED", - "availability_status": "NOT_AVAILABLE", - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "hostname": "192.168.29.249", - "os_family": "MACOS", - "os_architecture": "x86_64", - "os_distribution": "UNKNOWN", - "plugin_version": "10.0.476", - "time_registered": "2025-09-25T15:18:39.681000Z", - "time_last_seen": null, - "defined_tags": {}, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaaebraeu2tvhcqh6jlxh6hymw24wsk3acyl2t7l6p2kjdlhd4le3oa", - "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguiaqdlpx5c77d5cdmpcjko6w7s453dydft7ht4ovv53jcea", - "agent_type": "UNKNOWN_ENUM_VALUE", - "lifecycle_state": "DELETED", - "availability_status": "NOT_AVAILABLE", - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "hostname": "jms-k8s-cluster", - "os_family": "LINUX", - "os_architecture": "amd64", - "os_distribution": "OL8", - "plugin_version": "10.0.476.7", - "time_registered": "2025-09-25T10:17:09.383000Z", - "time_last_seen": null, - "defined_tags": {}, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - } -] -``` - -## 4. `get_jms_plugin` - -Status: `ok` - -Input: - -```json -{ - "jms_plugin_id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea" -} -``` - -Output: - -```json -{ - "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea", - "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguia6q6c7ces7dz3tw7nf3unz5ofmhccxcpcp7lchjpccraq", - "agent_type": "OMA", - "lifecycle_state": "DELETED", - "availability_status": "NOT_AVAILABLE", - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "hostname": "192.168.29.249", - "os_family": "MACOS", - "os_architecture": "x86_64", - "os_distribution": "UNKNOWN", - "plugin_version": "10.0.476", - "time_registered": "2025-09-25T15:18:39.681000Z", - "time_last_seen": null, - "defined_tags": {}, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } -} -``` - -## 5. `list_installation_sites` - -Status: `ok` - -Input: - -```json -{ - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "limit": 10 -} -``` - -Output: - -```json -[] -``` - -## 6. `get_fleet_agent_configuration` - -Status: `ok` - -Input: - -```json -{ - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" -} -``` - -Output: - -```json -{ - "jre_scan_frequency_in_minutes": 1440, - "java_usage_tracker_processing_frequency_in_minutes": 60, - "work_request_validity_period_in_days": 14, - "agent_polling_interval_in_minutes": 10, - "is_collecting_managed_instance_metrics_enabled": true, - "is_collecting_usernames_enabled": true, - "is_capturing_ip_address_and_fqdn_enabled": null, - "is_libraries_scan_enabled": null, - "linux_configuration": { - "include_paths": [ - "/usr/java", - "/usr/lib/jvm", - "/usr/lib64/graalvm", - "/opt" - ], - "exclude_paths": [] - }, - "windows_configuration": { - "include_paths": [ - "${ProgramFiles}\\Java", - "${ProgramFiles(x86)}\\Java", - "C:\\Oracle" - ], - "exclude_paths": [] - }, - "mac_os_configuration": { - "include_paths": [ - "/Library/Java", - "/Library/Internet Plug-Ins/", - "/Library/Oracle" - ], - "exclude_paths": [] - }, - "time_last_modified": "2025-09-26T06:26:12.058000Z" -} -``` - -## 7. `get_fleet_advanced_feature_configuration` - -Status: `ok` - -Input: - -```json -{ - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" -} -``` - -Output: - -```json -{ - "analytic_namespace": "ideq4gu7gs6j", - "analytic_bucket_name": "jms_ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "lcm": { - "is_enabled": true, - "post_installation_actions": null - }, - "crypto_event_analysis": { - "is_enabled": true, - "summarized_events_log": { - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia75rkcwb43ccw2ne6q32p44x26tav3d5ncnytdcr4zwva" - } - }, - "advanced_usage_tracking": { - "is_enabled": true - }, - "jfr_recording": { - "is_enabled": true - }, - "performance_tuning_analysis": { - "is_enabled": true - }, - "java_migration_analysis": { - "is_enabled": true - }, - "time_last_modified": "2025-09-26T06:14:05.652000Z" -} -``` - -## 8. `summarize_resource_inventory` - -Status: `ok` - -Input: - -```json -{ - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq" -} -``` - -Output: - -```json -{ - "active_fleet_count": 0, - "managed_instance_count": 0, - "jre_count": 0, - "installation_count": 0, - "application_count": 0 -} -``` - -## 9. `summarize_managed_instance_usage` - -Status: `ok` - -Input: - -```json -{ - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "limit": 10 -} -``` - -Output: - -```json -[] -``` - diff --git a/src/oci-jms-mcp-server/JMS_TOOL_TEST_RESULTS.json b/src/oci-jms-mcp-server/JMS_TOOL_TEST_RESULTS.json deleted file mode 100644 index 1c4fdf9e..00000000 --- a/src/oci-jms-mcp-server/JMS_TOOL_TEST_RESULTS.json +++ /dev/null @@ -1,641 +0,0 @@ -{ - "list_fleets": { - "status": "ok", - "input": { - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "sort_order": "ASC", - "sort_by": "displayName", - "limit": 10 - }, - "output": [ - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguia7iry2zw5zygfmqejmaessuuzbll2im7kdqxgp5rwgzna", - "display_name": "Test2", - "description": "Test2", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiavs2et3qpq2klmgurnigpbqya3sbr5m3otqntlzgupk6a", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaj3zfojeftftvgji2mw4fu7smsfz2g2d43fyx2lmu66da" - }, - "operation_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiavs2et3qpq2klmgurnigpbqya3sbr5m3otqntlzgupk6a", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiajoc4aj43veuw6fpvqymbi5fxiki62ekpwmwy3ldp3ada" - }, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2026-02-10T09:27:25.338000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "kwickram", - "CreatedOn": "2026-02-10T09:27:24.860Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguia3txn7ennnkfbdzlceq4j5nyj5s4z7piy7c4icbn6prfa", - "display_name": "auto-move-managed-instance-A", - "description": "autotest fleet", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7uexc2le75lji7xl26qjrpkqnzqv7sszlmouktxcbi7q", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiazf652yp5zdpmbr4s7narowmw3nyap2jykdxy4z4lx2gq" - }, - "operation_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7uexc2le75lji7xl26qjrpkqnzqv7sszlmouktxcbi7q", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiath6unzwkgzlgqarrguaborwbam6husaexgfhfh4gip3a" - }, - "is_advanced_features_enabled": false, - "is_export_setting_enabled": false, - "time_created": "2023-06-08T16:12:37.330000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "klevytsk", - "CreatedOn": "2023-06-08T16:12:36.173Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguialuyracd2pi6zqjxg2z5n2i7lv5ywkrjjwkfhavpi6twa", - "display_name": "fleet-20250613-1315", - "description": "Test fleet created by SQE", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaunp4y53xiohb5hqvasykcqdjroukjnhvfaexyqqixwfq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiavnauazecrpuf7s5o4vzfxygzy2e2uefp2ll5kkiimgpq" - }, - "operation_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaunp4y53xiohb5hqvasykcqdjroukjnhvfaexyqqixwfq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaejedobtl2mmcx4biucxr46lqiehtbshjqnnt46zmsifa" - }, - "is_advanced_features_enabled": false, - "is_export_setting_enabled": false, - "time_created": "2025-06-13T05:15:46.987000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "kwickram", - "CreatedOn": "2025-06-13T05:15:46.402Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiaovou2nqghvburhlmoy42gzk62vvtzak4zzz2i5nhlmxa", - "display_name": "fleet-20251014-0910_test-incubator_canary", - "description": "Testing fleet-20251014-0910_test-incubator_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiabmgw3bckpm63xiujmh6fkmkdwqiu4s23mytbx767j6eq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiad4ltos3e6vgjwu7imnqiyv46li7i6ekgbjxys4ki2z2a" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-10-14T09:12:34.940000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-10-14T09:12:34.660Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiatzxyg3gkpuwcqffkpaiwminrjxtxdyduwbhnrlwsi3hq", - "display_name": "fleet-20251016-1325_test-null_canary", - "description": "Testing fleet-20251016-1325_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiayml34lgbdsqxadrtei5hzxkmkg3sxcannmjytx6e4dla", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia55hr5yvk4zfxcremzz6q24grrsjocsj2vcs6lkr3toka" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-10-16T13:25:52.653000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-10-16T13:25:52.110Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiaxjjqturjxfb7ffgiwe7ozx2qomqftw55swvv6e2nvllq", - "display_name": "fleet-20251017-0834_test-null_canary", - "description": "Testing fleet-20251017-0834_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia7yo6e5f6pw5hpylhfsiro5lwh7ps5rj3ue7jclwlkm2a", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia7olcl6ppvivnqtpilylgtaemtszmsmgppozf5t7qapfq" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-10-17T08:34:59.591000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-10-17T08:34:59.345Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiame4obzlb2r4micfk7vr5qhjdjaxb5s62a4ehmdqrneaq", - "display_name": "fleet-20251216-0835_test-null_canary", - "description": "Testing fleet-20251216-0835_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiao2v4e5xec4yeu5zo46bfwwgbqm7ntfnwmj7mdyio2t3a", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia3g2ogwthju5jsvef2qipkl7dv2ufym63eyhkzhhtugxq" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-12-16T08:35:37.365000Z", - "lifecycle_state": "DELETED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-12-16T08:35:37.100Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiarvjb6ikdnmwgy27wvx6gyzszcpp2qn23k4mnhnjyzk6a", - "display_name": "fleet-20251216-1434_test-null_canary", - "description": "Testing fleet-20251216-1434_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiaked7r3fqtylyru2zosqdmigywgajqcwqv4j7o2ehpofq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiawezvurunu63y534hjza5e6ebcy56kfe2z4rgx3cb4yfq" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-12-16T14:34:38.887000Z", - "lifecycle_state": "DELETED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-12-16T14:34:38.639Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguial2nqjd7hahr5qz2laytp2qcnjfcjmcjsylhrh4egwaia", - "display_name": "fleet-20251216-2034_test-null_canary", - "description": "Testing fleet-20251216-2034_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiacugfanyfx7rnaq6wcngvdkp7t7lqjxjlfdfkrwqvrn2a", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiatep563wid7ucmpjavtsmi4v35uv6myiu4bu2dlcol2jq" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-12-16T20:34:51.004000Z", - "lifecycle_state": "DELETED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-12-16T20:34:50.735Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiahxduzprvifslhei6lczre6axawn7pou5hx6h5l7d2pva", - "display_name": "fleet-20251217-0234_test-null_canary", - "description": "Testing fleet-20251217-0234_test-null_canary", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguiay2c6dklzrc4gg3epv4v4vjedcug4t5b3kvx23yzsayba", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiak4g3fucg7xvgun63tlu44bubw22rzx3ycxdn3zykx3sa" - }, - "operation_log": null, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-12-17T02:34:31.877000Z", - "lifecycle_state": "DELETED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "jmstest-canaryuser-iad", - "CreatedOn": "2025-12-17T02:34:31.584Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - } - ] - }, - "get_fleet": { - "status": "ok", - "input": { - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" - }, - "output": { - "id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "display_name": "jms10-bpn-k8s", - "description": "JMS 10 Test Fleet", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "approximate_jre_count": 0, - "approximate_installation_count": 0, - "approximate_application_count": 0, - "approximate_managed_instance_count": 0, - "approximate_java_server_count": 0, - "approximate_library_count": null, - "approximate_library_vulnerability_count": null, - "inventory_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguian5ex7u5otwik5adoq6n65fbbwdu6yfarknbzo4uyyxma" - }, - "operation_log": { - "namespace": null, - "bucket_name": null, - "object_name": null, - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguiaxmmcot7l6pynst3mc3d6abrvc3ol6qd7fv3rkp4f66fq" - }, - "is_advanced_features_enabled": true, - "is_export_setting_enabled": false, - "time_created": "2025-09-25T09:42:20.123000Z", - "lifecycle_state": "FAILED", - "defined_tags": { - "Oracle-Tags": { - "CreatedBy": "bbanathu", - "CreatedOn": "2025-09-25T09:42:19.695Z" - } - }, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - } - }, - "list_jms_plugins": { - "status": "ok", - "input": { - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "sort_order": "DESC", - "sort_by": "timeLastSeen", - "limit": 10 - }, - "output": [ - { - "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea", - "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguia6q6c7ces7dz3tw7nf3unz5ofmhccxcpcp7lchjpccraq", - "agent_type": "OMA", - "lifecycle_state": "DELETED", - "availability_status": "NOT_AVAILABLE", - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "hostname": "192.168.29.249", - "os_family": "MACOS", - "os_architecture": "x86_64", - "os_distribution": "UNKNOWN", - "plugin_version": "10.0.476", - "time_registered": "2025-09-25T15:18:39.681000Z", - "time_last_seen": null, - "defined_tags": {}, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - }, - { - "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaaebraeu2tvhcqh6jlxh6hymw24wsk3acyl2t7l6p2kjdlhd4le3oa", - "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguiaqdlpx5c77d5cdmpcjko6w7s453dydft7ht4ovv53jcea", - "agent_type": "UNKNOWN_ENUM_VALUE", - "lifecycle_state": "DELETED", - "availability_status": "NOT_AVAILABLE", - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "hostname": "jms-k8s-cluster", - "os_family": "LINUX", - "os_architecture": "amd64", - "os_distribution": "OL8", - "plugin_version": "10.0.476.7", - "time_registered": "2025-09-25T10:17:09.383000Z", - "time_last_seen": null, - "defined_tags": {}, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - } - ] - }, - "list_installation_sites": { - "status": "ok", - "input": { - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "limit": 10 - }, - "output": [] - }, - "get_fleet_agent_configuration": { - "status": "ok", - "input": { - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" - }, - "output": { - "jre_scan_frequency_in_minutes": 1440, - "java_usage_tracker_processing_frequency_in_minutes": 60, - "work_request_validity_period_in_days": 14, - "agent_polling_interval_in_minutes": 10, - "is_collecting_managed_instance_metrics_enabled": true, - "is_collecting_usernames_enabled": true, - "is_capturing_ip_address_and_fqdn_enabled": null, - "is_libraries_scan_enabled": null, - "linux_configuration": { - "include_paths": [ - "/usr/java", - "/usr/lib/jvm", - "/usr/lib64/graalvm", - "/opt" - ], - "exclude_paths": [] - }, - "windows_configuration": { - "include_paths": [ - "${ProgramFiles}\\Java", - "${ProgramFiles(x86)}\\Java", - "C:\\Oracle" - ], - "exclude_paths": [] - }, - "mac_os_configuration": { - "include_paths": [ - "/Library/Java", - "/Library/Internet Plug-Ins/", - "/Library/Oracle" - ], - "exclude_paths": [] - }, - "time_last_modified": "2025-09-26T06:26:12.058000Z" - } - }, - "get_fleet_advanced_feature_configuration": { - "status": "ok", - "input": { - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq" - }, - "output": { - "analytic_namespace": "ideq4gu7gs6j", - "analytic_bucket_name": "jms_ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "lcm": { - "is_enabled": true, - "post_installation_actions": null - }, - "crypto_event_analysis": { - "is_enabled": true, - "summarized_events_log": { - "log_group_id": "ocid1.loggroup.oc1.iad.amaaaaaa6cijguia2k7mytdyr4gmci4pklhmsm4ftb5uwecr3q2ep5jzfiuq", - "log_id": "ocid1.log.oc1.iad.amaaaaaa6cijguia75rkcwb43ccw2ne6q32p44x26tav3d5ncnytdcr4zwva" - } - }, - "advanced_usage_tracking": { - "is_enabled": true - }, - "jfr_recording": { - "is_enabled": true - }, - "performance_tuning_analysis": { - "is_enabled": true - }, - "java_migration_analysis": { - "is_enabled": true - }, - "time_last_modified": "2025-09-26T06:14:05.652000Z" - } - }, - "summarize_resource_inventory": { - "status": "ok", - "input": { - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq" - }, - "output": { - "active_fleet_count": 0, - "managed_instance_count": 0, - "jre_count": 0, - "installation_count": 0, - "application_count": 0 - } - }, - "summarize_managed_instance_usage": { - "status": "ok", - "input": { - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "limit": 10 - }, - "output": [] - }, - "get_jms_plugin": { - "status": "ok", - "input": { - "jms_plugin_id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea" - }, - "output": { - "id": "ocid1.jmsplugin.oc1.iad.aaaaaaaasu4rloal4nryx2u4cqkukktit7d527infdokv4fmax3cqda6kqea", - "agent_id": "ocid1.managementagent.oc1.iad.amaaaaaa6cijguia6q6c7ces7dz3tw7nf3unz5ofmhccxcpcp7lchjpccraq", - "agent_type": "OMA", - "lifecycle_state": "DELETED", - "availability_status": "NOT_AVAILABLE", - "fleet_id": "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiafufahiluczeesffxgqqkwiz6odgjmzudesgosz2cn7uq", - "compartment_id": "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq", - "hostname": "192.168.29.249", - "os_family": "MACOS", - "os_architecture": "x86_64", - "os_distribution": "UNKNOWN", - "plugin_version": "10.0.476", - "time_registered": "2025-09-25T15:18:39.681000Z", - "time_last_seen": null, - "defined_tags": {}, - "freeform_tags": {}, - "system_tags": { - "orcl-cloud": { - "free-tier-retained": "false" - } - } - } - } -} From f56b03219a36614ca5e788657514525ca074f143 Mon Sep 17 00:00:00 2001 From: bhuveshsharma09 Date: Tue, 17 Mar 2026 23:42:25 +0800 Subject: [PATCH 06/11] fix(jms): clarify blank optional filter handling --- src/oci-jms-mcp-server/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/oci-jms-mcp-server/README.md b/src/oci-jms-mcp-server/README.md index 7997351f..f7bc3e18 100644 --- a/src/oci-jms-mcp-server/README.md +++ b/src/oci-jms-mcp-server/README.md @@ -150,6 +150,7 @@ Prefer verifying `get_fleet` only with an OCID returned by `list_fleets`. If `ge - If your client config was copied from `oci_api_mcp`, replace `OCI_CONFIG`/`OCI_PROFILE` with `OCI_CONFIG_FILE`/`OCI_CONFIG_PROFILE`. - If you set `JMS_TEST_ENVIRONMENT`, make sure the OCI config contains a `region`. - If you need a dev endpoint, set `JMS_TEST_ENVIRONMENT`; tool inputs do not accept a per-call endpoint override. +- Blank optional filter values are ignored. For example, empty `time_start`, `time_end`, or blank entries in `os_family` are treated as unset inputs instead of being sent to the OCI SDK. ## Tools From cbb64b74cd442acb7b34696c52c49f50dceb85c7 Mon Sep 17 00:00:00 2001 From: bhuveshsharma09 Date: Thu, 19 Mar 2026 11:07:04 +0800 Subject: [PATCH 07/11] feat(jms): add summarize_fleet_health, get_fleet_health_diagnostics, and list_jms_notices mcp tools --- src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md | 157 ++++++++++- src/oci-jms-mcp-server/README.md | 5 +- .../oracle/oci_jms_mcp_server/models.py | 117 ++++++++ .../oracle/oci_jms_mcp_server/server.py | 266 ++++++++++++++++++ .../tests/test_jms_models.py | 58 ++++ .../tests/test_jms_tools.py | 217 ++++++++++++++ .../scripts/test_jms_mcp_client.py | 4 +- 7 files changed, 820 insertions(+), 4 deletions(-) diff --git a/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md b/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md index b6083bab..3eb0dc77 100644 --- a/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md +++ b/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md @@ -1,6 +1,6 @@ # JMS Tool Details -This file documents the 9 tools exposed by `oracle.oci-jms-mcp-server`. +This file documents the 12 tools exposed by `oracle.oci-jms-mcp-server`. Use this as a quick reference for: @@ -518,6 +518,161 @@ Call the `summarize_managed_instance_usage` tool with: --- +## 10. `summarize_fleet_health` + +Summarizes fleet health using fleet diagnoses and fleet errors. + +### Inputs + +```json +{ + "fleet_id": "ocid1.jmsfleet..." +} +``` + +### Output + +Returns a `FleetHealthSummary` object: + +```json +{ + "fleet_id": "ocid1.jmsfleet...", + "diagnosis_count": 2, + "fleet_errors": [ + { + "fleet_id": "ocid1.jmsfleet...", + "fleet_name": "Test Fleet", + "errors": [ + { + "reason": "Agent connectivity failure", + "details": "Critical agent reporting failure", + "time_last_seen": "2026-03-18T12:00:00Z" + } + ] + } + ], + "top_issue_categories": ["Inventory scan issue", "Agent connectivity failure"], + "overall_health_status": "CRITICAL", + "recommended_next_checks": [ + "Review fleet agent configuration and inventory collection settings.", + "Check JMS notices for any known service-side issues or advisories." + ] +} +``` + +### Demo Query + +```text +Call the `summarize_fleet_health` tool with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example" +} +``` + +--- + +## 11. `get_fleet_health_diagnostics` + +Gets detailed fleet diagnoses and fleet errors for drill-down troubleshooting. + +### Inputs + +```json +{ + "fleet_id": "ocid1.jmsfleet..." +} +``` + +### Output + +Returns a `FleetHealthDiagnostics` object: + +```json +{ + "fleet_id": "ocid1.jmsfleet...", + "diagnoses": [ + { + "resource_id": "ocid1.jmsfleet...", + "resource_diagnosis": "Inventory scan issue", + "resource_state": "FAILED", + "resource_type": "JMS_FLEET" + } + ], + "fleet_errors": [ + { + "fleet_id": "ocid1.jmsfleet...", + "fleet_name": "Test Fleet", + "errors": [ + { + "reason": "Agent connectivity failure", + "details": "Critical agent reporting failure", + "time_last_seen": "2026-03-18T12:00:00Z" + } + ] + } + ], + "diagnosis_count": 1, + "fleet_error_count": 1 +} +``` + +### Demo Query + +```text +Call the `get_fleet_health_diagnostics` tool with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example" +} +``` + +--- + +## 12. `list_jms_notices` + +Lists JMS announcements and notices. + +### Inputs + +```json +{ + "summary_contains": "maintenance", + "time_start": "2026-03-01T00:00:00Z", + "time_end": "2026-03-10T00:00:00Z", + "limit": 10, + "sort_order": "DESC", + "sort_by": "timeReleased" +} +``` + +### Output + +Returns a list of `JmsNotice` objects: + +```json +[ + { + "key": "announcement-1", + "summary": "Planned JMS maintenance", + "time_released": "2026-03-18T12:00:00Z", + "url": "https://example.com" + } +] +``` + +### Demo Query + +```text +Call the `list_jms_notices` tool with: +{ + "summary_contains": "maintenance", + "sort_order": "DESC", + "sort_by": "timeReleased", + "limit": 10 +} +``` + +--- + ## Suggested Agent Workflow ### Find a fleet, then inspect it diff --git a/src/oci-jms-mcp-server/README.md b/src/oci-jms-mcp-server/README.md index f7bc3e18..4209fb47 100644 --- a/src/oci-jms-mcp-server/README.md +++ b/src/oci-jms-mcp-server/README.md @@ -3,7 +3,7 @@ ## Overview This server provides tools to interact with Oracle Cloud Infrastructure Java Management Service (JMS). -It focuses on fleet inventory and discovery workflows. +It focuses on fleet inventory, discovery, and health troubleshooting workflows. ## Authentication @@ -165,6 +165,9 @@ Prefer verifying `get_fleet` only with an OCID returned by `list_fleets`. If `ge | get_fleet_advanced_feature_configuration | Get fleet advanced feature configuration | | summarize_resource_inventory | Summarize JMS resource inventory | | summarize_managed_instance_usage | Summarize managed instance usage in a fleet | +| summarize_fleet_health | Summarize fleet health using diagnoses and fleet errors | +| get_fleet_health_diagnostics | Get detailed fleet health diagnoses and fleet errors | +| list_jms_notices | List JMS announcements and notices | ⚠️ **NOTE**: All actions are performed with the permissions of the configured OCI CLI profile. We advise least-privilege IAM setup, secure credential management, safe network practices, secure logging, and warn against exposing secrets. diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py index 63199f45..a4ebb7b3 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py @@ -554,3 +554,120 @@ def map_resource_inventory(data: oci.jms.models.ResourceInventory) -> ResourceIn installation_count=getattr(data, "installation_count", None), application_count=getattr(data, "application_count", None), ) + + +class FleetDiagnosisRecord(BaseModel): + """Fleet diagnosis record returned by JMS health APIs.""" + resource_diagnosis: Optional[str] = Field(None, description="Diagnosis message for the resource.") + resource_id: Optional[str] = Field(None, description="Affected resource OCID or identifier.") + resource_state: Optional[str] = Field(None, description="Lifecycle or health state of the resource.") + resource_type: Optional[str] = Field(None, description="Type of the affected resource.") + + +def map_fleet_diagnosis(data: oci.jms.models.FleetDiagnosisSummary) -> FleetDiagnosisRecord | None: + """Convert `oci.jms.models.FleetDiagnosisSummary` into `FleetDiagnosisRecord`.""" + if data is None: + return None + return FleetDiagnosisRecord( + resource_diagnosis=getattr(data, "resource_diagnosis", None), + resource_id=getattr(data, "resource_id", None), + resource_state=getattr(data, "resource_state", None), + resource_type=getattr(data, "resource_type", None), + ) + + +class FleetErrorDetail(BaseModel): + """Detailed fleet error entry nested inside a fleet error summary.""" + details: Optional[str] = Field(None, description="Detailed description of the error.") + reason: Optional[str] = Field(None, description="High-level reason for the error.") + time_last_seen: Optional[datetime] = Field(None, description="Last time the error was seen.") + + +def map_fleet_error_detail(data: oci.jms.models.FleetErrorDetails) -> FleetErrorDetail | None: + """Convert `oci.jms.models.FleetErrorDetails` into `FleetErrorDetail`.""" + if data is None: + return None + return FleetErrorDetail( + details=getattr(data, "details", None), + reason=getattr(data, "reason", None), + time_last_seen=getattr(data, "time_last_seen", None), + ) + + +class FleetErrorRecord(BaseModel): + """Fleet-scoped error summary returned by JMS health APIs.""" + compartment_id: Optional[str] = Field(None, description="Compartment OCID containing the fleet.") + fleet_id: Optional[str] = Field(None, description="Fleet OCID.") + fleet_name: Optional[str] = Field(None, description="Fleet display name.") + errors: Optional[List[FleetErrorDetail]] = Field( + None, description="Error detail entries associated with this fleet." + ) + time_first_seen: Optional[datetime] = Field(None, description="First time the fleet error was seen.") + time_last_seen: Optional[datetime] = Field(None, description="Last time the fleet error was seen.") + + +def map_fleet_error(data: oci.jms.models.FleetErrorSummary) -> FleetErrorRecord | None: + """Convert `oci.jms.models.FleetErrorSummary` into `FleetErrorRecord`.""" + if data is None: + return None + errors = getattr(data, "errors", None) + return FleetErrorRecord( + compartment_id=getattr(data, "compartment_id", None), + fleet_id=getattr(data, "fleet_id", None), + fleet_name=getattr(data, "fleet_name", None), + errors=[map_fleet_error_detail(item) for item in errors] if errors else None, + time_first_seen=getattr(data, "time_first_seen", None), + time_last_seen=getattr(data, "time_last_seen", None), + ) + + +class FleetHealthSummary(BaseModel): + """Chat-friendly summary of the health posture of a JMS fleet.""" + fleet_id: str = Field(..., description="Fleet OCID.") + diagnosis_count: int = Field(..., description="Number of diagnosis records for the fleet.") + fleet_errors: List[FleetErrorRecord] = Field( + default_factory=list, description="Fleet error records returned for the fleet." + ) + top_issue_categories: List[str] = Field( + default_factory=list, description="Deduplicated high-signal issue categories." + ) + overall_health_status: Literal["HEALTHY", "WARNING", "CRITICAL", "UNKNOWN"] = Field( + ..., description="Derived overall health status for the fleet." + ) + recommended_next_checks: List[str] = Field( + default_factory=list, + description="MCP-generated follow-up checks derived from returned diagnoses and errors.", + ) + + +class FleetHealthDiagnostics(BaseModel): + """Detailed fleet health diagnostics for drill-down troubleshooting.""" + fleet_id: str = Field(..., description="Fleet OCID.") + diagnoses: List[FleetDiagnosisRecord] = Field( + default_factory=list, description="Detailed diagnosis records for the fleet." + ) + fleet_errors: List[FleetErrorRecord] = Field( + default_factory=list, description="Detailed fleet error records for the fleet." + ) + diagnosis_count: int = Field(..., description="Number of diagnosis records returned.") + fleet_error_count: int = Field(..., description="Number of fleet error records returned.") + + +class JmsNotice(BaseModel): + """Announcement or notice surfaced by the JMS service.""" + key: Optional[str] = Field(None, description="Announcement key.") + summary: Optional[str] = Field(None, description="Announcement summary.") + time_released: Optional[datetime] = Field(None, description="Announcement release time.") + url: Optional[str] = Field(None, description="Announcement reference URL.") + + +def map_jms_notice(data: oci.jms.models.AnnouncementSummary) -> JmsNotice | None: + """Convert `oci.jms.models.AnnouncementSummary` into `JmsNotice`.""" + if data is None: + return None + return JmsNotice( + key=getattr(data, "key", None), + summary=getattr(data, "summary", None), + time_released=getattr(data, "time_released", None), + url=getattr(data, "url", None), + ) diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py index 16e1f95e..e791a654 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py @@ -15,8 +15,13 @@ Fleet, FleetAdvancedFeatureConfiguration, FleetAgentConfiguration, + FleetDiagnosisRecord, + FleetErrorRecord, + FleetHealthDiagnostics, + FleetHealthSummary, FleetSummary, InstallationSiteSummary, + JmsNotice, JmsPlugin, JmsPluginSummary, ManagedInstanceUsage, @@ -24,8 +29,11 @@ map_fleet, map_fleet_advanced_feature_configuration, map_fleet_agent_configuration, + map_fleet_diagnosis, + map_fleet_error, map_fleet_summary, map_installation_site_summary, + map_jms_notice, map_jms_plugin, map_jms_plugin_summary, map_managed_instance_usage, @@ -109,6 +117,167 @@ def _parse_rfc3339(value: Optional[str]) -> Optional[datetime]: return datetime.fromisoformat(normalized.replace("Z", "+00:00")) +def _collect_paginated_items(method, **kwargs): + """Collect all items from a paginated OCI list operation.""" + items = [] + has_next_page = True + next_page: Optional[str] = None + limit = kwargs.get("limit") + + while has_next_page and (limit is None or len(items) < limit): + response = method(**_omit_none(**kwargs, page=next_page)) + response_items = list(getattr(response.data, "items", [])) + if limit is not None: + remaining = limit - len(items) + response_items = response_items[:remaining] + items.extend(response_items) + has_next_page = getattr(response, "has_next_page", False) + next_page = response.next_page if hasattr(response, "next_page") else None + + return items + + +def _collect_text_fragments(*values) -> list[str]: + """Recursively gather non-empty strings from nested tool data.""" + fragments: list[str] = [] + for value in values: + if value is None: + continue + if isinstance(value, str): + normalized = value.strip() + if normalized and normalized != "UNKNOWN_ENUM_VALUE": + fragments.append(normalized) + continue + if isinstance(value, list): + fragments.extend(_collect_text_fragments(*value)) + continue + if isinstance(value, dict): + fragments.extend(_collect_text_fragments(*value.values())) + continue + if hasattr(value, "model_dump"): + fragments.extend(_collect_text_fragments(value.model_dump())) + return fragments + + +def _normalize_issue_category(value: str) -> str: + """Produce a stable issue category label from diagnosis or error text.""" + normalized = " ".join(value.replace("_", " ").split()) + return normalized[:1].upper() + normalized[1:] if normalized else normalized + + +def _pick_issue_category(*values: Optional[str]) -> Optional[str]: + """Pick the first non-empty, non-sentinel issue text.""" + for value in values: + if value and value.strip() and value != "UNKNOWN_ENUM_VALUE": + return value + return None + + +def _derive_top_issue_categories( + diagnoses: list[FleetDiagnosisRecord], fleet_errors: list[FleetErrorRecord] +) -> list[str]: + """Extract a stable, deduplicated list of high-signal issue categories.""" + categories: list[str] = [] + seen: set[str] = set() + + for diagnosis in diagnoses: + candidate = diagnosis.resource_diagnosis + if candidate: + normalized = _normalize_issue_category(candidate) + key = normalized.casefold() + if key not in seen: + seen.add(key) + categories.append(normalized) + + for fleet_error in fleet_errors: + for error in fleet_error.errors or []: + candidate = _pick_issue_category(error.reason, error.details) + if candidate: + normalized = _normalize_issue_category(candidate) + key = normalized.casefold() + if key not in seen: + seen.add(key) + categories.append(normalized) + + return categories[:5] + + +def _derive_overall_health_status( + diagnoses: list[FleetDiagnosisRecord], fleet_errors: list[FleetErrorRecord] +) -> Literal["HEALTHY", "WARNING", "CRITICAL", "UNKNOWN"]: + """Collapse fleet diagnoses and errors into a single coarse health status.""" + if not diagnoses and not fleet_errors: + return "HEALTHY" + + fragments = _collect_text_fragments(diagnoses, fleet_errors) + if not fragments: + return "UNKNOWN" + + severe_markers = ( + "critical", + "failed", + "failure", + "error", + "severe", + "not available", + "unavailable", + ) + warning_markers = ( + "needs attention", + "warning", + "inventory", + "scan", + "plugin", + "agent", + ) + + lowered = [fragment.casefold() for fragment in fragments] + if any(marker in fragment for fragment in lowered for marker in severe_markers): + return "CRITICAL" + if any(marker in fragment for fragment in lowered for marker in warning_markers): + return "WARNING" + return "WARNING" + + +def _derive_recommended_next_checks( + diagnoses: list[FleetDiagnosisRecord], fleet_errors: list[FleetErrorRecord] +) -> list[str]: + """Generate deterministic next-step hints from returned diagnoses and errors.""" + if not diagnoses and not fleet_errors: + return [] + + fragments = [fragment.casefold() for fragment in _collect_text_fragments(diagnoses, fleet_errors)] + recommendations: list[str] = [] + + def add_once(text: str): + if text not in recommendations: + recommendations.append(text) + + if any( + marker in fragment + for fragment in fragments + for marker in ("inventory", "scan", "discovery", "collection") + ): + add_once("Review fleet agent configuration and inventory collection settings.") + + if any( + marker in fragment + for fragment in fragments + for marker in ("agent", "plugin", "silent", "report", "heartbeat", "connect") + ): + add_once("Inspect detailed fleet health diagnostics and verify recent agent or plugin reporting.") + + if any( + marker in fragment + for fragment in fragments + for marker in ("auth", "permission", "unauthorized", "forbidden", "region") + ): + add_once("Verify fleet visibility, OCI access, and region or compartment alignment.") + + add_once("Check JMS notices for any known service-side issues or advisories.") + return recommendations + + def _warn_on_unsupported_env_aliases(): """Warn when env vars from the separate generic OCI API MCP project are used.""" for alias, canonical in _UNSUPPORTED_ENV_ALIASES.items(): @@ -618,6 +787,103 @@ def summarize_managed_instance_usage( raise +@mcp.tool(description="Summarize fleet health using JMS fleet diagnoses and fleet errors.") +def summarize_fleet_health( + fleet_id: str = Field(..., description="The OCID of the fleet.") +) -> FleetHealthSummary: + """Return a chat-friendly health summary for a JMS fleet.""" + try: + client = get_jms_client() + diagnoses = [ + map_fleet_diagnosis(item) + for item in _collect_paginated_items(client.list_fleet_diagnoses, fleet_id=fleet_id) + ] + fleet_errors = [ + map_fleet_error(item) + for item in _collect_paginated_items(client.list_fleet_errors, fleet_id=fleet_id) + ] + diagnoses = [item for item in diagnoses if item is not None] + fleet_errors = [item for item in fleet_errors if item is not None] + + return FleetHealthSummary( + fleet_id=fleet_id, + diagnosis_count=len(diagnoses), + fleet_errors=fleet_errors, + top_issue_categories=_derive_top_issue_categories(diagnoses, fleet_errors), + overall_health_status=_derive_overall_health_status(diagnoses, fleet_errors), + recommended_next_checks=_derive_recommended_next_checks(diagnoses, fleet_errors), + ) + except Exception as e: + logger.error(f"Error in summarize_fleet_health tool: {str(e)}") + raise + + +@mcp.tool(description="Get detailed fleet health diagnostics using JMS diagnoses and fleet errors.") +def get_fleet_health_diagnostics( + fleet_id: str = Field(..., description="The OCID of the fleet.") +) -> FleetHealthDiagnostics: + """Return detailed fleet diagnoses and fleet errors for drill-down troubleshooting.""" + try: + client = get_jms_client() + diagnoses = [ + map_fleet_diagnosis(item) + for item in _collect_paginated_items(client.list_fleet_diagnoses, fleet_id=fleet_id) + ] + fleet_errors = [ + map_fleet_error(item) + for item in _collect_paginated_items(client.list_fleet_errors, fleet_id=fleet_id) + ] + diagnoses = [item for item in diagnoses if item is not None] + fleet_errors = [item for item in fleet_errors if item is not None] + + return FleetHealthDiagnostics( + fleet_id=fleet_id, + diagnoses=diagnoses, + fleet_errors=fleet_errors, + diagnosis_count=len(diagnoses), + fleet_error_count=len(fleet_errors), + ) + except Exception as e: + logger.error(f"Error in get_fleet_health_diagnostics tool: {str(e)}") + raise + + +@mcp.tool(description="List JMS announcements and notices.") +def list_jms_notices( + summary_contains: Optional[str] = Field( + None, + description="Filter notices whose summary contains this value.", + ), + time_start: Optional[str] = Field(None, description="Search start time in RFC3339 format."), + time_end: Optional[str] = Field(None, description="Search end time in RFC3339 format."), + limit: Optional[int] = Field(None, description="Maximum number of notices to return.", ge=1), + sort_order: Optional[str] = Field(None, description="Sort order for notices: ASC or DESC."), + sort_by: Optional[str] = Field( + None, + description="Field to sort notices by: timeReleased or summary.", + ), +) -> list[JmsNotice]: + """List JMS notices and announcements with optional text, time, and sort filters.""" + try: + client = get_jms_client() + notices = [ + map_jms_notice(item) + for item in _collect_paginated_items( + client.list_announcements, + summary_contains=summary_contains, + time_start=_parse_rfc3339(time_start), + time_end=_parse_rfc3339(time_end), + limit=limit, + sort_order=_normalize_enum(sort_order), + sort_by=_normalize_choice(sort_by, ["timeReleased", "summary"]), + ) + ] + return [item for item in notices if item is not None] + except Exception as e: + logger.error(f"Error in list_jms_notices tool: {str(e)}") + raise + + def main(): """Run the JMS MCP server over stdio by default or HTTP when host and port are provided.""" host = os.getenv("ORACLE_MCP_HOST") diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py index ccccf7f1..38bb342f 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py @@ -9,6 +9,9 @@ import oci from oracle.oci_jms_mcp_server.models import ( + map_fleet_diagnosis, + map_fleet_error, + map_jms_notice, map_fleet, map_fleet_advanced_feature_configuration, map_fleet_agent_configuration, @@ -141,3 +144,58 @@ def test_map_resource_inventory(): assert result.active_fleet_count == 1 assert result.application_count == 5 + + +def test_map_fleet_diagnosis(): + diagnosis = oci.jms.models.FleetDiagnosisSummary( + resource_diagnosis="Inventory scan issue", + resource_id="resource1", + resource_state="FAILED", + resource_type="JMS_FLEET", + ) + + result = map_fleet_diagnosis(diagnosis) + + assert result.resource_diagnosis == "Inventory scan issue" + assert result.resource_id == "resource1" + assert result.resource_state == "UNKNOWN_ENUM_VALUE" + + +def test_map_fleet_error_with_nested_details(): + now = datetime.now(UTC) + fleet_error = oci.jms.models.FleetErrorSummary( + fleet_id="fleet1", + fleet_name="Fleet 1", + time_first_seen=now, + errors=[ + oci.jms.models.FleetErrorDetails( + reason="Agent connectivity failure", + details="Critical reporting failure", + time_last_seen=now, + ) + ], + ) + + result = map_fleet_error(fleet_error) + + assert result.fleet_id == "fleet1" + assert result.fleet_name == "Fleet 1" + assert result.time_first_seen == now + assert result.errors[0].reason == "UNKNOWN_ENUM_VALUE" + assert result.errors[0].details == "Critical reporting failure" + + +def test_map_jms_notice(): + now = datetime.now(UTC) + notice = oci.jms.models.AnnouncementSummary( + key="announcement1", + summary="Planned maintenance", + time_released=now, + url="https://example.com", + ) + + result = map_jms_notice(notice) + + assert result.key == "announcement1" + assert result.summary == "Planned maintenance" + assert result.time_released == now diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py index bb307a1a..d000531b 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py @@ -5,6 +5,7 @@ """ import os +from datetime import UTC, datetime from unittest.mock import MagicMock, create_autospec, mock_open, patch import fastmcp.exceptions @@ -605,6 +606,222 @@ async def test_summarize_managed_instance_usage_omits_blank_os_family_entries_an ] assert "time_end" not in mock_client.summarize_managed_instance_usage.call_args.kwargs + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_summarize_fleet_health_with_issues(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + diagnoses_page = create_autospec(oci.response.Response) + diagnoses_page.data = oci.jms.models.FleetDiagnosisCollection( + items=[ + oci.jms.models.FleetDiagnosisSummary( + resource_id="resource1", + resource_diagnosis="Inventory scan issue", + ) + ] + ) + diagnoses_page.has_next_page = False + diagnoses_page.next_page = None + + error_detail = oci.jms.models.FleetErrorDetails( + details="Critical agent reporting failure", + reason="Agent connectivity failure", + time_last_seen=datetime.now(UTC), + ) + fleet_errors_page = create_autospec(oci.response.Response) + fleet_errors_page.data = oci.jms.models.FleetErrorCollection( + items=[ + oci.jms.models.FleetErrorSummary( + fleet_id="fleet1", + fleet_name="Fleet 1", + errors=[error_detail], + ) + ] + ) + fleet_errors_page.has_next_page = False + fleet_errors_page.next_page = None + + mock_client.list_fleet_diagnoses.return_value = diagnoses_page + mock_client.list_fleet_errors.return_value = fleet_errors_page + + async with Client(mcp) as client: + result = ( + await client.call_tool("summarize_fleet_health", {"fleet_id": "fleet1"}) + ).structured_content + + assert result["fleet_id"] == "fleet1" + assert result["diagnosis_count"] == 1 + assert result["overall_health_status"] == "CRITICAL" + assert "Inventory scan issue" in result["top_issue_categories"] + assert "Critical agent reporting failure" in result["top_issue_categories"] + assert "Check JMS notices for any known service-side issues or advisories." in result[ + "recommended_next_checks" + ] + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_summarize_fleet_health_healthy(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + diagnoses_page = create_autospec(oci.response.Response) + diagnoses_page.data = oci.jms.models.FleetDiagnosisCollection(items=[]) + diagnoses_page.has_next_page = False + diagnoses_page.next_page = None + + fleet_errors_page = create_autospec(oci.response.Response) + fleet_errors_page.data = oci.jms.models.FleetErrorCollection(items=[]) + fleet_errors_page.has_next_page = False + fleet_errors_page.next_page = None + + mock_client.list_fleet_diagnoses.return_value = diagnoses_page + mock_client.list_fleet_errors.return_value = fleet_errors_page + + async with Client(mcp) as client: + result = ( + await client.call_tool("summarize_fleet_health", {"fleet_id": "fleet1"}) + ).structured_content + + assert result["overall_health_status"] == "HEALTHY" + assert result["recommended_next_checks"] == [] + assert result["fleet_errors"] == [] + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_get_fleet_health_diagnostics_paginates(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + diagnoses_page_1 = create_autospec(oci.response.Response) + diagnoses_page_1.data = oci.jms.models.FleetDiagnosisCollection( + items=[oci.jms.models.FleetDiagnosisSummary(resource_id="resource1")] + ) + diagnoses_page_1.has_next_page = True + diagnoses_page_1.next_page = "token" + + diagnoses_page_2 = create_autospec(oci.response.Response) + diagnoses_page_2.data = oci.jms.models.FleetDiagnosisCollection( + items=[oci.jms.models.FleetDiagnosisSummary(resource_id="resource2")] + ) + diagnoses_page_2.has_next_page = False + diagnoses_page_2.next_page = None + + fleet_errors_page = create_autospec(oci.response.Response) + fleet_errors_page.data = oci.jms.models.FleetErrorCollection( + items=[oci.jms.models.FleetErrorSummary(fleet_id="fleet1")] + ) + fleet_errors_page.has_next_page = False + fleet_errors_page.next_page = None + + mock_client.list_fleet_diagnoses.side_effect = [diagnoses_page_1, diagnoses_page_2] + mock_client.list_fleet_errors.return_value = fleet_errors_page + + async with Client(mcp) as client: + result = ( + await client.call_tool("get_fleet_health_diagnostics", {"fleet_id": "fleet1"}) + ).structured_content + + assert result["diagnosis_count"] == 2 + assert result["fleet_error_count"] == 1 + assert [item["resource_id"] for item in result["diagnoses"]] == [ + "resource1", + "resource2", + ] + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_summarize_fleet_health_exception(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + mock_client.list_fleet_diagnoses.side_effect = oci.exceptions.ServiceError( + status=500, + code="InternalServerError", + message="Internal server error", + opc_request_id="req", + headers={}, + ) + + async with Client(mcp) as client: + with pytest.raises(fastmcp.exceptions.ToolError): + await client.call_tool("summarize_fleet_health", {"fleet_id": "fleet1"}) + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_jms_notices_normalizes_sort_by_and_time_filters(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + response = create_autospec(oci.response.Response) + response.data = oci.jms.models.AnnouncementCollection( + items=[ + oci.jms.models.AnnouncementSummary( + key="announcement1", + summary="Planned maintenance", + time_released=datetime.now(UTC), + url="https://example.com", + ) + ] + ) + response.has_next_page = False + response.next_page = None + mock_client.list_announcements.return_value = response + + async with Client(mcp) as client: + result = ( + await client.call_tool( + "list_jms_notices", + { + "summary_contains": "maintenance", + "time_start": "2026-03-01T00:00:00Z", + "time_end": "2026-03-02T00:00:00Z", + "sort_order": "desc", + "sort_by": "time_released", + }, + ) + ).structured_content["result"] + + assert result[0]["key"] == "announcement1" + assert mock_client.list_announcements.call_args.kwargs["sort_order"] == "DESC" + assert mock_client.list_announcements.call_args.kwargs["sort_by"] == "timeReleased" + assert ( + mock_client.list_announcements.call_args.kwargs["time_start"].isoformat() + == "2026-03-01T00:00:00+00:00" + ) + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_list_jms_notices_paginates_with_limit(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + page_1 = create_autospec(oci.response.Response) + page_1.data = oci.jms.models.AnnouncementCollection( + items=[ + oci.jms.models.AnnouncementSummary(key="announcement1"), + oci.jms.models.AnnouncementSummary(key="announcement2"), + ] + ) + page_1.has_next_page = True + page_1.next_page = "token" + + page_2 = create_autospec(oci.response.Response) + page_2.data = oci.jms.models.AnnouncementCollection( + items=[oci.jms.models.AnnouncementSummary(key="announcement3")] + ) + page_2.has_next_page = False + page_2.next_page = None + + mock_client.list_announcements.side_effect = [page_1, page_2] + + async with Client(mcp) as client: + result = ( + await client.call_tool("list_jms_notices", {"limit": 2}) + ).structured_content["result"] + + assert [item["key"] for item in result] == ["announcement1", "announcement2"] + assert mock_client.list_announcements.call_count == 1 + class TestServerMain: @patch("oracle.oci_jms_mcp_server.server.mcp.run") diff --git a/src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py b/src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py index ce9bab49..c788e687 100644 --- a/src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py +++ b/src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py @@ -18,12 +18,12 @@ #compartment = JMS-TEST-CANARY DEFAULT_COMPARTMENT_ID = ( - "ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq" + "" ) #fleet name = auto-ol8-epp-test DEFAULT_FLEET_ID = ( - "ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiaukoncqzg33zxlz5zcfzw7hsrfrrbceg7vf7fe2dq73nq" + "" ) TOOL_ORDER = [ From cdf5f025c1df381b491aed0bc2fd8e6469895f45 Mon Sep 17 00:00:00 2001 From: bhuveshsharma09 Date: Thu, 26 Mar 2026 19:41:18 +0800 Subject: [PATCH 08/11] feat(jms): add runtime compliance tool and expand JMS e2e coverage - add `java_runtime_compliance` to the JMS MCP server - add runtime compliance models and update JMS notice typing - add unit tests for compliance and notice handling - expand JMS e2e mocks, feature scenarios, and step assertions - register JMS in shared `tests/e2e/features/mcphost.json` - remove the separate `mcphost-jms.json` - update JMS README and tool details docs --- src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md | 83 ++++++- src/oci-jms-mcp-server/README.md | 1 + .../oracle/oci_jms_mcp_server/models.py | 64 +++++- .../oracle/oci_jms_mcp_server/server.py | 170 +++++++++++++++ .../tests/test_jms_models.py | 4 +- .../tests/test_jms_tools.py | 203 +++++++++++++++++- tests/e2e/features/mcphost-jms.json | 29 --- tests/e2e/features/mcphost.json | 25 +++ tests/e2e/features/mocks/services/jms_data.py | 136 ++++++++++++ .../e2e/features/mocks/services/jms_routes.py | 102 +++++++++ tests/e2e/features/oci-jms-mcp-server.feature | 28 ++- .../steps/oci-jms-mcp-server-steps.py | 77 ++++++- 12 files changed, 880 insertions(+), 42 deletions(-) delete mode 100644 tests/e2e/features/mcphost-jms.json diff --git a/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md b/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md index 3eb0dc77..7d9e16ff 100644 --- a/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md +++ b/src/oci-jms-mcp-server/DETAILS_OF_TOOLS.md @@ -1,6 +1,6 @@ # JMS Tool Details -This file documents the 12 tools exposed by `oracle.oci-jms-mcp-server`. +This file documents the 13 tools exposed by `oracle.oci-jms-mcp-server`. Use this as a quick reference for: @@ -673,6 +673,87 @@ Call the `list_jms_notices` tool with: --- +## 13. `java_runtime_compliance` + +Summarizes Java runtime compliance for a JMS fleet. + +### Inputs + +```json +{ + "fleet_id": "ocid1.jmsfleet..." +} +``` + +### Output + +Returns a `JavaRuntimeComplianceReport` object: + +```json +{ + "fleet_id": "ocid1.jmsfleet...", + "total_runtimes_in_fleet": 7, + "up_to_date_runtimes": 4, + "runtimes_requiring_update": 2, + "runtimes_requiring_upgrade": 1, + "unknown_runtimes": 0, + "version_breakdown": [ + { + "version": "21.0.2", + "vendor": "Oracle", + "distribution": "JDK", + "security_status": "UP_TO_DATE", + "runtime_count": 4, + "release_type": "CPU", + "license_type": "NFTC" + }, + { + "version": "17.0.10", + "vendor": "Oracle", + "distribution": "JDK", + "security_status": "UPDATE_REQUIRED", + "runtime_count": 3, + "release_type": "CPU", + "license_type": "NFTC" + } + ], + "vendor_breakdown": [ + { + "key": "Oracle", + "runtime_count": 7 + } + ], + "distribution_breakdown": [ + { + "key": "JDK", + "runtime_count": 7 + } + ], + "outdated_installations": [ + { + "installation_key": "install1", + "managed_instance_id": "mi1", + "path": "/usr/java/jdk-17", + "version": "17.0.10", + "vendor": "Oracle", + "distribution": "JDK", + "security_status": "UPDATE_REQUIRED" + } + ] +} +``` + +### Demo Query + +```text +Call the `java_runtime_compliance` tool with: +{ + "fleet_id": "ocid1.jmsfleet.oc1.iad.example" +} +``` + +--- + ## Suggested Agent Workflow ### Find a fleet, then inspect it diff --git a/src/oci-jms-mcp-server/README.md b/src/oci-jms-mcp-server/README.md index 4209fb47..e5649c72 100644 --- a/src/oci-jms-mcp-server/README.md +++ b/src/oci-jms-mcp-server/README.md @@ -168,6 +168,7 @@ Prefer verifying `get_fleet` only with an OCID returned by `list_fleets`. If `ge | summarize_fleet_health | Summarize fleet health using diagnoses and fleet errors | | get_fleet_health_diagnostics | Get detailed fleet health diagnoses and fleet errors | | list_jms_notices | List JMS announcements and notices | +| java_runtime_compliance | Summarize Java runtime compliance for a fleet | ⚠️ **NOTE**: All actions are performed with the permissions of the configured OCI CLI profile. We advise least-privilege IAM setup, secure credential management, safe network practices, secure logging, and warn against exposing secrets. diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py index a4ebb7b3..80ad4b61 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/models.py @@ -655,7 +655,7 @@ class FleetHealthDiagnostics(BaseModel): class JmsNotice(BaseModel): """Announcement or notice surfaced by the JMS service.""" - key: Optional[str] = Field(None, description="Announcement key.") + key: Optional[int] = Field(None, description="Announcement key.") summary: Optional[str] = Field(None, description="Announcement summary.") time_released: Optional[datetime] = Field(None, description="Announcement release time.") url: Optional[str] = Field(None, description="Announcement reference URL.") @@ -671,3 +671,65 @@ def map_jms_notice(data: oci.jms.models.AnnouncementSummary) -> JmsNotice | None time_released=getattr(data, "time_released", None), url=getattr(data, "url", None), ) + + +class JavaRuntimeComplianceBucket(BaseModel): + """Compliance summary for one runtime version or version/vendor/distribution tuple.""" + version: Optional[str] = Field(None, description="Java runtime version.") + vendor: Optional[str] = Field(None, description="Java runtime vendor.") + distribution: Optional[str] = Field(None, description="Java runtime distribution.") + security_status: Optional[str] = Field(None, description="Security status of the runtime.") + runtime_count: int = Field(..., description="Approximate count of runtimes/installations in this bucket.") + approximate_managed_instance_count: Optional[int] = Field( + None, description="Approximate number of managed instances for this runtime." + ) + approximate_application_count: Optional[int] = Field( + None, description="Approximate number of applications for this runtime." + ) + release_date: Optional[datetime] = Field(None, description="Java release date when available.") + days_under_security_baseline: Optional[int] = Field( + None, description="Days the release is under the security baseline when available." + ) + license_type: Optional[str] = Field(None, description="License type for the Java release.") + release_type: Optional[str] = Field(None, description="Release type for the Java release.") + release_notes_url: Optional[str] = Field(None, description="Release notes URL when available.") + + +class JavaRuntimeCountBreakdown(BaseModel): + """Simple count breakdown by one grouping key such as vendor or distribution.""" + key: str = Field(..., description="Grouping key label.") + runtime_count: int = Field(..., description="Approximate count of runtimes/installations in this group.") + + +class OutdatedJavaInstallation(BaseModel): + """Drill-down row for one outdated installation site.""" + installation_key: Optional[str] = Field(None, description="Unique installation identifier.") + managed_instance_id: Optional[str] = Field(None, description="Managed instance OCID.") + path: Optional[str] = Field(None, description="Installation path.") + version: Optional[str] = Field(None, description="Java runtime version.") + vendor: Optional[str] = Field(None, description="Java runtime vendor.") + distribution: Optional[str] = Field(None, description="Java runtime distribution.") + security_status: Optional[str] = Field(None, description="Security status of the runtime.") + time_last_seen: Optional[datetime] = Field(None, description="Last seen time for the installation.") + + +class JavaRuntimeComplianceReport(BaseModel): + """Fleet-level runtime compliance report built from JMS usage and release metadata APIs.""" + fleet_id: str = Field(..., description="Fleet OCID.") + total_runtimes_in_fleet: int = Field(..., description="Approximate total number of runtimes in the fleet.") + up_to_date_runtimes: int = Field(..., description="Approximate count of up-to-date runtimes.") + runtimes_requiring_update: int = Field(..., description="Approximate count of runtimes requiring update.") + runtimes_requiring_upgrade: int = Field(..., description="Approximate count of runtimes requiring upgrade.") + unknown_runtimes: int = Field(..., description="Approximate count of runtimes with unknown status.") + version_breakdown: List[JavaRuntimeComplianceBucket] = Field( + default_factory=list, description="Compliance breakdown by runtime version." + ) + vendor_breakdown: List[JavaRuntimeCountBreakdown] = Field( + default_factory=list, description="Compliance breakdown by vendor." + ) + distribution_breakdown: List[JavaRuntimeCountBreakdown] = Field( + default_factory=list, description="Compliance breakdown by distribution." + ) + outdated_installations: List[OutdatedJavaInstallation] = Field( + default_factory=list, description="Bounded drill-down rows for outdated installations." + ) diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py index e791a654..e3eb4a9d 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py @@ -21,10 +21,14 @@ FleetHealthSummary, FleetSummary, InstallationSiteSummary, + JavaRuntimeComplianceBucket, + JavaRuntimeComplianceReport, + JavaRuntimeCountBreakdown, JmsNotice, JmsPlugin, JmsPluginSummary, ManagedInstanceUsage, + OutdatedJavaInstallation, ResourceInventory, map_fleet, map_fleet_advanced_feature_configuration, @@ -53,6 +57,7 @@ "OCI_CONFIG": "OCI_CONFIG_FILE", "OCI_PROFILE": "OCI_CONFIG_PROFILE", } +_MAX_OUTDATED_INSTALLATIONS = 25 # Input Normalization Helpers def _normalize_enum(value: Optional[str]) -> Optional[str]: @@ -278,6 +283,37 @@ def add_once(text: str): return recommendations +def _normalize_count(value: Optional[int]) -> int: + """Treat missing approximate counts as zero for aggregation purposes.""" + return int(value or 0) + + +def _safe_get_java_release(client, release_version: Optional[str]): + """Best-effort Java release lookup; ignore not-found style misses for enrichment.""" + if not release_version: + return None + try: + response: oci.response.Response = client.get_java_release(release_version=release_version) + return response.data + except oci.exceptions.ServiceError as exc: + if exc.status in (400, 404): + return None + raise + + +def _build_runtime_count_breakdowns(values: list[tuple[Optional[str], int]]) -> list[JavaRuntimeCountBreakdown]: + """Aggregate runtime counts by a single string key such as vendor or distribution.""" + counts: dict[str, int] = {} + for raw_key, count in values: + key = raw_key or "UNKNOWN" + counts[key] = counts.get(key, 0) + count + + return [ + JavaRuntimeCountBreakdown(key=key, runtime_count=runtime_count) + for key, runtime_count in sorted(counts.items(), key=lambda item: (-item[1], item[0])) + ] + + def _warn_on_unsupported_env_aliases(): """Warn when env vars from the separate generic OCI API MCP project are used.""" for alias, canonical in _UNSUPPORTED_ENV_ALIASES.items(): @@ -884,6 +920,140 @@ def list_jms_notices( raise +@mcp.tool(description="Summarize Java runtime compliance for a JMS fleet.") +def java_runtime_compliance( + fleet_id: str = Field(..., description="The OCID of the fleet.") +) -> JavaRuntimeComplianceReport: + """Return a fleet-level Java runtime compliance report enriched with release metadata.""" + try: + client = get_jms_client() + usage_rows = _collect_paginated_items( + client.summarize_jre_usage, + fleet_id=fleet_id, + fields=[ + "approximateInstallationCount", + "approximateApplicationCount", + "approximateManagedInstanceCount", + ], + ) + + release_cache: dict[str, object | None] = {} + version_breakdown: list[JavaRuntimeComplianceBucket] = [] + total_runtimes = 0 + up_to_date_runtimes = 0 + runtimes_requiring_update = 0 + runtimes_requiring_upgrade = 0 + unknown_runtimes = 0 + vendor_values: list[tuple[Optional[str], int]] = [] + distribution_values: list[tuple[Optional[str], int]] = [] + outdated_installations: list[OutdatedJavaInstallation] = [] + + for row in usage_rows: + version = getattr(row, "version", None) + vendor = getattr(row, "vendor", None) + distribution = getattr(row, "distribution", None) + security_status = getattr(row, "security_status", None) + runtime_count = _normalize_count(getattr(row, "approximate_installation_count", None)) + total_runtimes += runtime_count + + if security_status == "UP_TO_DATE": + up_to_date_runtimes += runtime_count + elif security_status == "UPDATE_REQUIRED": + runtimes_requiring_update += runtime_count + elif security_status == "UPGRADE_REQUIRED": + runtimes_requiring_upgrade += runtime_count + else: + unknown_runtimes += runtime_count + + vendor_values.append((vendor, runtime_count)) + distribution_values.append((distribution, runtime_count)) + + if version not in release_cache: + release_cache[version] = _safe_get_java_release(client, version) + release = release_cache[version] + + version_breakdown.append( + JavaRuntimeComplianceBucket( + version=version, + vendor=vendor, + distribution=distribution, + security_status=security_status, + runtime_count=runtime_count, + approximate_managed_instance_count=getattr( + row, "approximate_managed_instance_count", None + ), + approximate_application_count=getattr( + row, "approximate_application_count", None + ), + release_date=getattr(release, "release_date", None) if release else None, + days_under_security_baseline=getattr( + release, "days_under_security_baseline", None + ) + if release + else None, + license_type=getattr(release, "license_type", None) if release else None, + release_type=getattr(release, "release_type", None) if release else None, + release_notes_url=getattr(release, "release_notes_url", None) if release else None, + ) + ) + + if ( + security_status in {"UPDATE_REQUIRED", "UPGRADE_REQUIRED"} + and len(outdated_installations) < _MAX_OUTDATED_INSTALLATIONS + ): + remaining = _MAX_OUTDATED_INSTALLATIONS - len(outdated_installations) + sites = _collect_paginated_items( + client.list_installation_sites, + fleet_id=fleet_id, + jre_version=version, + jre_vendor=vendor, + jre_distribution=distribution, + jre_security_status=security_status, + limit=remaining, + ) + for site in sites: + mapped_site = map_installation_site_summary(site) + if mapped_site is None: + continue + outdated_installations.append( + OutdatedJavaInstallation( + installation_key=mapped_site.installation_key, + managed_instance_id=mapped_site.managed_instance_id, + path=mapped_site.path, + version=mapped_site.jre.version if mapped_site.jre else None, + vendor=mapped_site.jre.vendor if mapped_site.jre else None, + distribution=mapped_site.jre.distribution if mapped_site.jre else None, + security_status=mapped_site.security_status, + time_last_seen=mapped_site.time_last_seen, + ) + ) + + version_breakdown.sort( + key=lambda item: ( + -(item.runtime_count or 0), + item.version or "", + item.vendor or "", + item.distribution or "", + ) + ) + + return JavaRuntimeComplianceReport( + fleet_id=fleet_id, + total_runtimes_in_fleet=total_runtimes, + up_to_date_runtimes=up_to_date_runtimes, + runtimes_requiring_update=runtimes_requiring_update, + runtimes_requiring_upgrade=runtimes_requiring_upgrade, + unknown_runtimes=unknown_runtimes, + version_breakdown=version_breakdown, + vendor_breakdown=_build_runtime_count_breakdowns(vendor_values), + distribution_breakdown=_build_runtime_count_breakdowns(distribution_values), + outdated_installations=outdated_installations, + ) + except Exception as e: + logger.error(f"Error in java_runtime_compliance tool: {str(e)}") + raise + + def main(): """Run the JMS MCP server over stdio by default or HTTP when host and port are provided.""" host = os.getenv("ORACLE_MCP_HOST") diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py index 38bb342f..22f14949 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_models.py @@ -188,7 +188,7 @@ def test_map_fleet_error_with_nested_details(): def test_map_jms_notice(): now = datetime.now(UTC) notice = oci.jms.models.AnnouncementSummary( - key="announcement1", + key=1001, summary="Planned maintenance", time_released=now, url="https://example.com", @@ -196,6 +196,6 @@ def test_map_jms_notice(): result = map_jms_notice(notice) - assert result.key == "announcement1" + assert result.key == 1001 assert result.summary == "Planned maintenance" assert result.time_released == now diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py index d000531b..83776806 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py @@ -756,7 +756,7 @@ async def test_list_jms_notices_normalizes_sort_by_and_time_filters(self, mock_g response.data = oci.jms.models.AnnouncementCollection( items=[ oci.jms.models.AnnouncementSummary( - key="announcement1", + key=1001, summary="Planned maintenance", time_released=datetime.now(UTC), url="https://example.com", @@ -781,7 +781,7 @@ async def test_list_jms_notices_normalizes_sort_by_and_time_filters(self, mock_g ) ).structured_content["result"] - assert result[0]["key"] == "announcement1" + assert result[0]["key"] == 1001 assert mock_client.list_announcements.call_args.kwargs["sort_order"] == "DESC" assert mock_client.list_announcements.call_args.kwargs["sort_by"] == "timeReleased" assert ( @@ -798,8 +798,8 @@ async def test_list_jms_notices_paginates_with_limit(self, mock_get_client): page_1 = create_autospec(oci.response.Response) page_1.data = oci.jms.models.AnnouncementCollection( items=[ - oci.jms.models.AnnouncementSummary(key="announcement1"), - oci.jms.models.AnnouncementSummary(key="announcement2"), + oci.jms.models.AnnouncementSummary(key=1001), + oci.jms.models.AnnouncementSummary(key=1002), ] ) page_1.has_next_page = True @@ -807,7 +807,7 @@ async def test_list_jms_notices_paginates_with_limit(self, mock_get_client): page_2 = create_autospec(oci.response.Response) page_2.data = oci.jms.models.AnnouncementCollection( - items=[oci.jms.models.AnnouncementSummary(key="announcement3")] + items=[oci.jms.models.AnnouncementSummary(key=1003)] ) page_2.has_next_page = False page_2.next_page = None @@ -819,9 +819,200 @@ async def test_list_jms_notices_paginates_with_limit(self, mock_get_client): await client.call_tool("list_jms_notices", {"limit": 2}) ).structured_content["result"] - assert [item["key"] for item in result] == ["announcement1", "announcement2"] + assert [item["key"] for item in result] == [1001, 1002] assert mock_client.list_announcements.call_count == 1 + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_java_runtime_compliance_with_release_enrichment_and_drilldown( + self, mock_get_client + ): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + usage_response = create_autospec(oci.response.Response) + usage_response.data = oci.jms.models.JreUsageCollection( + items=[ + oci.jms.models.JreUsage( + id="jre1", + fleet_id="fleet1", + version="17.0.10", + vendor="Oracle", + distribution="JDK", + security_status="UPDATE_REQUIRED", + approximate_installation_count=3, + approximate_managed_instance_count=2, + approximate_application_count=5, + ), + oci.jms.models.JreUsage( + id="jre2", + fleet_id="fleet1", + version="21.0.2", + vendor="Oracle", + distribution="JDK", + security_status="UP_TO_DATE", + approximate_installation_count=4, + approximate_managed_instance_count=2, + approximate_application_count=6, + ), + ] + ) + usage_response.has_next_page = False + usage_response.next_page = None + mock_client.summarize_jre_usage.return_value = usage_response + + release_update = create_autospec(oci.response.Response) + release_update.data = oci.jms.models.JavaRelease( + release_version="17.0.10", + security_status="UPDATE_REQUIRED", + release_type="CPU", + license_type="NFTC", + ) + release_current = create_autospec(oci.response.Response) + release_current.data = oci.jms.models.JavaRelease( + release_version="21.0.2", + security_status="UP_TO_DATE", + release_type="CPU", + license_type="NFTC", + ) + mock_client.get_java_release.side_effect = [release_update, release_current] + + site_response = create_autospec(oci.response.Response) + site_response.data = oci.jms.models.InstallationSiteCollection( + items=[ + oci.jms.models.InstallationSiteSummary( + installation_key="install1", + managed_instance_id="mi1", + path="/usr/java/jdk-17", + jre=oci.jms.models.JavaRuntimeId( + version="17.0.10", vendor="Oracle", distribution="JDK" + ), + security_status="UPDATE_REQUIRED", + time_last_seen=datetime.now(UTC), + ) + ] + ) + site_response.has_next_page = False + site_response.next_page = None + mock_client.list_installation_sites.return_value = site_response + + async with Client(mcp) as client: + result = ( + await client.call_tool("java_runtime_compliance", {"fleet_id": "fleet1"}) + ).structured_content + + assert result["fleet_id"] == "fleet1" + assert result["total_runtimes_in_fleet"] == 7 + assert result["up_to_date_runtimes"] == 4 + assert result["runtimes_requiring_update"] == 3 + assert result["runtimes_requiring_upgrade"] == 0 + assert result["version_breakdown"][0]["license_type"] == "NFTC" + assert result["outdated_installations"][0]["installation_key"] == "install1" + assert mock_client.list_installation_sites.call_count == 1 + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_java_runtime_compliance_paginates_usage(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + page_1 = create_autospec(oci.response.Response) + page_1.data = oci.jms.models.JreUsageCollection( + items=[ + oci.jms.models.JreUsage( + id="jre1", + fleet_id="fleet1", + version="17.0.10", + vendor="Oracle", + distribution="JDK", + security_status="UPDATE_REQUIRED", + approximate_installation_count=1, + ) + ] + ) + page_1.has_next_page = True + page_1.next_page = "token" + + page_2 = create_autospec(oci.response.Response) + page_2.data = oci.jms.models.JreUsageCollection( + items=[ + oci.jms.models.JreUsage( + id="jre2", + fleet_id="fleet1", + version="21.0.2", + vendor="Oracle", + distribution="JDK", + security_status="UP_TO_DATE", + approximate_installation_count=2, + ) + ] + ) + page_2.has_next_page = False + page_2.next_page = None + + mock_client.summarize_jre_usage.side_effect = [page_1, page_2] + release_1 = create_autospec(oci.response.Response) + release_1.data = oci.jms.models.JavaRelease( + release_version="17.0.10" + ) + release_2 = create_autospec(oci.response.Response) + release_2.data = oci.jms.models.JavaRelease( + release_version="21.0.2" + ) + mock_client.get_java_release.side_effect = [release_1, release_2] + empty_sites = create_autospec(oci.response.Response) + empty_sites.data = oci.jms.models.InstallationSiteCollection(items=[]) + empty_sites.has_next_page = False + empty_sites.next_page = None + mock_client.list_installation_sites.return_value = empty_sites + + async with Client(mcp) as client: + result = ( + await client.call_tool("java_runtime_compliance", {"fleet_id": "fleet1"}) + ).structured_content + + assert result["total_runtimes_in_fleet"] == 3 + assert len(result["version_breakdown"]) == 2 + + @pytest.mark.asyncio + @patch("oracle.oci_jms_mcp_server.server.get_jms_client") + async def test_java_runtime_compliance_ignores_missing_release_metadata(self, mock_get_client): + mock_client = MagicMock() + mock_get_client.return_value = mock_client + + usage_response = create_autospec(oci.response.Response) + usage_response.data = oci.jms.models.JreUsageCollection( + items=[ + oci.jms.models.JreUsage( + id="jre1", + fleet_id="fleet1", + version="11.0.0-custom", + vendor="Oracle", + distribution="JDK", + security_status="UNKNOWN", + approximate_installation_count=1, + ) + ] + ) + usage_response.has_next_page = False + usage_response.next_page = None + mock_client.summarize_jre_usage.return_value = usage_response + mock_client.get_java_release.side_effect = oci.exceptions.ServiceError( + status=404, + code="NotAuthorizedOrNotFound", + message="Not found", + opc_request_id="req", + headers={}, + ) + + async with Client(mcp) as client: + result = ( + await client.call_tool("java_runtime_compliance", {"fleet_id": "fleet1"}) + ).structured_content + + assert result["unknown_runtimes"] == 1 + assert result["version_breakdown"][0]["release_type"] is None + class TestServerMain: @patch("oracle.oci_jms_mcp_server.server.mcp.run") diff --git a/tests/e2e/features/mcphost-jms.json b/tests/e2e/features/mcphost-jms.json deleted file mode 100644 index e5de848d..00000000 --- a/tests/e2e/features/mcphost-jms.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "mcpServers": { - "oracle-oci-jms-mcp-server": { - "disabled": false, - "timeout": 60, - "type": "stdio", - "command": "uv", - "args": [ - "run", - "python", - "-c", - "from oracle.oci_jms_mcp_server.server import main; main()" - ], - "env": { - "PYTHONPATH": "../../../src/oci-jms-mcp-server", - "PYTHONNOUSERSITE": "0", - "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", - "UV_CACHE_DIR": "/tmp/uv-cache", - "HTTP_PROXY": "http://127.0.0.1:5000", - "HTTPS_PROXY": "http://127.0.0.1:5000", - "OCI_SDK_CERT_BUNDLE": "False", - "PYTHONHTTPSVERIFY": "0", - "OCI_SKIP_SSL_VERIFICATION": "True", - "REQUESTS_CA_BUNDLE": "", - "CURL_CA_BUNDLE": "" - } - } - } -} diff --git a/tests/e2e/features/mcphost.json b/tests/e2e/features/mcphost.json index 314c65bb..58b76c6a 100644 --- a/tests/e2e/features/mcphost.json +++ b/tests/e2e/features/mcphost.json @@ -102,6 +102,31 @@ "CURL_CA_BUNDLE": "" } }, + "oracle-oci-jms-mcp-server": { + "disabled": false, + "timeout": 60, + "type": "stdio", + "command": "uv", + "args": [ + "run", + "python", + "-c", + "from oracle.oci_jms_mcp_server.server import main; main()" + ], + "env": { + "PYTHONPATH": "../../../src/oci-jms-mcp-server", + "PYTHONNOUSERSITE": "0", + "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", + "UV_CACHE_DIR": "/tmp/uv-cache", + "HTTP_PROXY": "http://127.0.0.1:5000", + "HTTPS_PROXY": "http://127.0.0.1:5000", + "OCI_SDK_CERT_BUNDLE": "False", + "PYTHONHTTPSVERIFY": "0", + "OCI_SKIP_SSL_VERIFICATION": "True", + "REQUESTS_CA_BUNDLE": "", + "CURL_CA_BUNDLE": "" + } + }, "oracle-oci-logging-mcp-server": { "disabled": false, "timeout": 60, diff --git a/tests/e2e/features/mocks/services/jms_data.py b/tests/e2e/features/mocks/services/jms_data.py index 626bdf54..9f690aa8 100644 --- a/tests/e2e/features/mocks/services/jms_data.py +++ b/tests/e2e/features/mocks/services/jms_data.py @@ -47,6 +47,22 @@ "pluginVersion": "1.2.3", "timeRegistered": "2026-02-11T10:30:00Z", "timeLastSeen": "2026-02-12T09:45:00Z", + }, + { + "id": "ocid1.jmsplugin.oc1..mock-plugin-2", + "agentId": "ocid1.managementagent.oc1..mock-agent-2", + "agentType": "OCA", + "lifecycleState": "ACTIVE", + "availabilityStatus": "SILENT", + "fleetId": "ocid1.jmsfleet.oc1..mock-fleet-1", + "compartmentId": "ocid1.tenancy.oc1..mock", + "hostname": "archive-host-2", + "osFamily": "LINUX", + "osArchitecture": "X86_64", + "osDistribution": "Oracle Linux", + "pluginVersion": "1.1.8", + "timeRegistered": "2026-02-10T08:15:00Z", + "timeLastSeen": "2026-02-10T08:45:00Z", } ] @@ -74,6 +90,29 @@ "approximateApplicationCount": 2, "timeLastSeen": "2026-02-12T09:45:00Z", "lifecycleState": "ACTIVE", + }, + { + "installationKey": "installation-beta", + "managedInstanceId": "managed-instance-2", + "jre": { + "version": "17.0.10", + "vendor": "Oracle", + "distribution": "JDK", + "jreKey": "jre-17-10-oracle", + }, + "securityStatus": "UPDATE_REQUIRED", + "path": "/opt/java/jdk-17.0.10", + "operatingSystem": { + "family": "LINUX", + "name": "Oracle Linux", + "distribution": "Oracle Linux", + "version": "8", + "architecture": "X86_64", + "managedInstanceCount": 1, + }, + "approximateApplicationCount": 1, + "timeLastSeen": "2026-02-13T08:45:00Z", + "lifecycleState": "ACTIVE", } ] } @@ -150,3 +189,100 @@ } ] } + +FLEET_DIAGNOSES = { + "ocid1.jmsfleet.oc1..mock-fleet-1": [ + { + "resourceDiagnosis": "Inventory scan issue", + "resourceId": "ocid1.jmsfleet.oc1..mock-fleet-1", + "resourceState": "FAILED", + "resourceType": "JMS_FLEET", + }, + { + "resourceDiagnosis": "Plugin heartbeat warning", + "resourceId": "ocid1.jmsplugin.oc1..mock-plugin-1", + "resourceState": "NEEDS_ATTENTION", + "resourceType": "JMS_PLUGIN", + }, + ] +} + +FLEET_ERRORS = [ + { + "compartmentId": "ocid1.tenancy.oc1..mock", + "fleetId": "ocid1.jmsfleet.oc1..mock-fleet-1", + "fleetName": "mock-jms-fleet", + "errors": [ + { + "reason": "Agent connectivity failure", + "details": "Critical agent reporting failure for plugin-host-1", + "timeLastSeen": "2026-02-13T10:30:00Z", + } + ], + "timeFirstSeen": "2026-02-13T09:00:00Z", + "timeLastSeen": "2026-02-13T10:30:00Z", + } +] + +JMS_NOTICES = [ + { + "key": 1001, + "summary": "Planned JMS maintenance window", + "timeReleased": "2026-02-14T11:00:00Z", + "url": "https://example.oracle.test/jms/maintenance", + }, + { + "key": 1002, + "summary": "JMS advisory for plugin telemetry delays", + "timeReleased": "2026-02-12T06:00:00Z", + "url": "https://example.oracle.test/jms/advisory", + }, +] + +JRE_USAGE = { + "ocid1.jmsfleet.oc1..mock-fleet-1": [ + { + "id": "jre-usage-1", + "fleetId": "ocid1.jmsfleet.oc1..mock-fleet-1", + "version": "21.0.2", + "vendor": "Oracle", + "distribution": "JDK", + "securityStatus": "UP_TO_DATE", + "approximateInstallationCount": 4, + "approximateManagedInstanceCount": 2, + "approximateApplicationCount": 3, + }, + { + "id": "jre-usage-2", + "fleetId": "ocid1.jmsfleet.oc1..mock-fleet-1", + "version": "17.0.10", + "vendor": "Oracle", + "distribution": "JDK", + "securityStatus": "UPDATE_REQUIRED", + "approximateInstallationCount": 3, + "approximateManagedInstanceCount": 1, + "approximateApplicationCount": 1, + }, + ] +} + +JAVA_RELEASES = { + "21.0.2": { + "releaseVersion": "21.0.2", + "releaseDate": "2026-01-16T00:00:00Z", + "daysUnderSecurityBaseline": 0, + "licenseType": "NFTC", + "releaseType": "CPU", + "releaseNotesUrl": "https://example.oracle.test/jms/releases/21.0.2", + "securityStatus": "UP_TO_DATE", + }, + "17.0.10": { + "releaseVersion": "17.0.10", + "releaseDate": "2025-10-15T00:00:00Z", + "daysUnderSecurityBaseline": 30, + "licenseType": "NFTC", + "releaseType": "CPU", + "releaseNotesUrl": "https://example.oracle.test/jms/releases/17.0.10", + "securityStatus": "UPDATE_REQUIRED", + }, +} diff --git a/tests/e2e/features/mocks/services/jms_routes.py b/tests/e2e/features/mocks/services/jms_routes.py index c17c0f47..b81ec468 100644 --- a/tests/e2e/features/mocks/services/jms_routes.py +++ b/tests/e2e/features/mocks/services/jms_routes.py @@ -7,11 +7,16 @@ from _common import oci_res from flask import Blueprint, jsonify, request from jms_data import ( + FLEET_DIAGNOSES, FLEET_ADVANCED_FEATURE_CONFIGURATIONS, FLEET_AGENT_CONFIGURATIONS, + FLEET_ERRORS, FLEETS, INSTALLATION_SITES, + JAVA_RELEASES, + JMS_NOTICES, JMS_PLUGINS, + JRE_USAGE, MANAGED_INSTANCE_USAGE, RESOURCE_INVENTORY, ) @@ -39,6 +44,14 @@ def _find_plugin(plugin_id): return next((plugin for plugin in JMS_PLUGINS if plugin.get("id") == plugin_id), None) +def _find_java_release(release_version): + return JAVA_RELEASES.get(release_version) + + +def _sort_items(items, sort_key, descending=False): + return sorted(items, key=lambda item: item.get(sort_key) or "", reverse=descending) + + @jms_bp.route("/fleets", methods=["GET"]) def list_fleets(): items = FLEETS @@ -131,6 +144,20 @@ def list_installation_sites(fleet_id): if jre_version: items = [item for item in items if item.get("jre", {}).get("version") == jre_version] + jre_vendor = request.args.get("jreVendor") + if jre_vendor: + items = [item for item in items if item.get("jre", {}).get("vendor") == jre_vendor] + + jre_distribution = request.args.get("jreDistribution") + if jre_distribution: + items = [ + item for item in items if item.get("jre", {}).get("distribution") == jre_distribution + ] + + jre_security_status = request.args.get("jreSecurityStatus") + if jre_security_status: + items = [item for item in items if item.get("securityStatus") == jre_security_status] + return oci_res({"items": _apply_limit(items)}) @@ -187,3 +214,78 @@ def summarize_managed_instance_usage(fleet_id): items = [item for item in items if needle in item.get("hostname", "").lower()] return oci_res({"items": _apply_limit(items)}) + + +@jms_bp.route("/fleets//diagnoses", methods=["GET"]) +def list_fleet_diagnoses(fleet_id): + if not _find_fleet(fleet_id): + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + items = FLEET_DIAGNOSES.get(fleet_id, []) + return oci_res({"items": _apply_limit(items)}) + + +@jms_bp.route("/fleetErrors", methods=["GET"]) +def list_fleet_errors(): + items = FLEET_ERRORS + + compartment_id = request.args.get("compartmentId") + if compartment_id: + items = [item for item in items if item.get("compartmentId") == compartment_id] + + fleet_id = request.args.get("fleetId") + if fleet_id: + items = [item for item in items if item.get("fleetId") == fleet_id] + + return oci_res({"items": _apply_limit(items)}) + + +@jms_bp.route("/announcements", methods=["GET"]) +def list_announcements(): + items = JMS_NOTICES + + summary_contains = request.args.get("summaryContains") + if summary_contains: + needle = summary_contains.lower() + items = [item for item in items if needle in item.get("summary", "").lower()] + + sort_by = request.args.get("sortBy") + if sort_by == "summary": + items = _sort_items(items, "summary", request.args.get("sortOrder") == "DESC") + else: + items = _sort_items(items, "timeReleased", request.args.get("sortOrder") == "DESC") + + return oci_res({"items": _apply_limit(items)}) + + +@jms_bp.route("/fleets//actions/summarizeJreUsage", methods=["GET"]) +def summarize_jre_usage(fleet_id): + if not _find_fleet(fleet_id): + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + + items = JRE_USAGE.get(fleet_id, []) + + jre_version = request.args.get("jreVersion") + if jre_version: + items = [item for item in items if item.get("version") == jre_version] + + jre_vendor = request.args.get("jreVendor") + if jre_vendor: + items = [item for item in items if item.get("vendor") == jre_vendor] + + jre_distribution = request.args.get("jreDistribution") + if jre_distribution: + items = [item for item in items if item.get("distribution") == jre_distribution] + + jre_security_status = request.args.get("jreSecurityStatus") + if jre_security_status: + items = [item for item in items if item.get("securityStatus") == jre_security_status] + + return oci_res({"items": _apply_limit(items)}) + + +@jms_bp.route("/javaReleases/", methods=["GET"]) +def get_java_release(release_version): + release = _find_java_release(release_version) + if not release: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + return oci_res(release) diff --git a/tests/e2e/features/oci-jms-mcp-server.feature b/tests/e2e/features/oci-jms-mcp-server.feature index fe763a4c..db22fd0b 100644 --- a/tests/e2e/features/oci-jms-mcp-server.feature +++ b/tests/e2e/features/oci-jms-mcp-server.feature @@ -24,13 +24,13 @@ Feature: OCI JMS MCP Server Scenario: List JMS plugins and get the first plugin Given the MCP server is running with OCI tools And the ollama model with the tools is properly working - When I send a request with the prompt "list my JMS fleets, then list the JMS plugins in the first fleet, then get the details of the first JMS plugin" + When I send a request with the prompt "list my JMS fleets, then list the JMS plugins in the first fleet with hostname containing plugin, then get the details of the first JMS plugin" Then the response should contain JMS plugin details Scenario: List installation sites and summarize managed instance usage Given the MCP server is running with OCI tools And the ollama model with the tools is properly working - When I send a request with the prompt "list my JMS fleets, then list the installation sites in the first JMS fleet, then summarize managed instance usage for that same fleet" + When I send a request with the prompt "list my JMS fleets, then list the installation sites in the first JMS fleet with path containing /usr/lib/jvm, then summarize managed instance usage for that same fleet with hostname containing usage" Then the response should contain installation site and managed instance usage details Scenario: Summarize JMS resource inventory @@ -38,3 +38,27 @@ Feature: OCI JMS MCP Server And the ollama model with the tools is properly working When I send a request with the prompt "summarize my JMS resource inventory" Then the response should contain JMS resource inventory details + + Scenario: Summarize fleet health + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my JMS fleets, then summarize fleet health for the first JMS fleet in the list" + Then the response should contain JMS fleet health summary details + + Scenario: Get fleet health diagnostics + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my JMS fleets, then get the detailed fleet health diagnostics for the first JMS fleet in the list" + Then the response should contain JMS fleet health diagnostics details + + Scenario: List JMS notices with filter + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my JMS notices whose summary contains maintenance and sort by time released descending" + Then the response should contain JMS notice details + + Scenario: Summarize Java runtime compliance + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my JMS fleets, then summarize Java runtime compliance for the first JMS fleet in the list" + Then the response should contain JMS runtime compliance details diff --git a/tests/e2e/features/steps/oci-jms-mcp-server-steps.py b/tests/e2e/features/steps/oci-jms-mcp-server-steps.py index fb55e0f6..41cc3ec4 100644 --- a/tests/e2e/features/steps/oci-jms-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-jms-mcp-server-steps.py @@ -22,8 +22,13 @@ def step_impl_jms_tools_available(context): "get_fleet_advanced_feature_configuration", "summarize_resource_inventory", "summarize_managed_instance_usage", + "summarize_fleet_health", + "get_fleet_health_diagnostics", + "list_jms_notices", + "java_runtime_compliance", ] - assert any(tool in content for tool in expected_tools), "JMS tools could not be queried." + missing_tools = [tool for tool in expected_tools if tool not in content] + assert not missing_tools, f"JMS tools could not be queried: missing {missing_tools}" @then("the response should contain a list of JMS fleets") @@ -63,6 +68,7 @@ def step_impl_jms_plugin_details(context): for value in [ "ocid1.jmsplugin", "plugin-host-1", + "hostname", "oraclemanagementagent", "pluginversion", ] @@ -80,6 +86,7 @@ def step_impl_installation_site_and_usage(context): "installation-alpha", "/usr/lib/jvm/java-17-oracle", "managed-instance-1", + "hostname", "approximatejrecount", "usage-host-1", ] @@ -101,3 +108,71 @@ def step_impl_resource_inventory(context): "42", ] ), "JMS resource inventory details not found." + + +@then("the response should contain JMS fleet health summary details") +def step_impl_fleet_health_summary(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + value in content + for value in [ + "critical", + "warning", + "agent connectivity failure", + "inventory scan issue", + "recommended_next_checks", + ] + ), "JMS fleet health summary details not found." + + +@then("the response should contain JMS fleet health diagnostics details") +def step_impl_fleet_health_diagnostics(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + value in content + for value in [ + "diagnosis_count", + "fleet_error_count", + "plugin heartbeat warning", + "critical agent reporting failure", + "jms_fleet", + ] + ), "JMS fleet health diagnostics details not found." + + +@then("the response should contain JMS notice details") +def step_impl_jms_notice_details(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + value in content + for value in [ + "planned jms maintenance window", + "maintenance", + "1001", + "time_released", + "example.oracle.test/jms/maintenance", + ] + ), "JMS notice details not found." + + +@then("the response should contain JMS runtime compliance details") +def step_impl_jms_runtime_compliance(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + value in content + for value in [ + "total_runtimes_in_fleet", + "17.0.10", + "update_required", + "nftc", + "/opt/java/jdk-17.0.10", + ] + ), "JMS runtime compliance details not found." From bd5124df0f592843b9e5a7465dcab12024440ca2 Mon Sep 17 00:00:00 2001 From: bhuveshsharma09 Date: Tue, 31 Mar 2026 10:23:13 +0800 Subject: [PATCH 09/11] feat(jms): add detailed comments for complex functions --- .../oracle/oci_jms_mcp_server/server.py | 7 + src/oci-jms-mcp-server/scripts/README.md | 59 ---- .../scripts/test_jms_mcp_client.py | 271 ------------------ 3 files changed, 7 insertions(+), 330 deletions(-) delete mode 100644 src/oci-jms-mcp-server/scripts/README.md delete mode 100644 src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py index e3eb4a9d..4d906a1c 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py @@ -237,6 +237,7 @@ def _derive_overall_health_status( ) lowered = [fragment.casefold() for fragment in fragments] + # Prefer a stable coarse status over surfacing raw provider-specific states. if any(marker in fragment for fragment in lowered for marker in severe_markers): return "CRITICAL" if any(marker in fragment for fragment in lowered for marker in warning_markers): @@ -258,6 +259,8 @@ def add_once(text: str): if text not in recommendations: recommendations.append(text) + # These recommendations intentionally trade completeness for deterministic, + # chat-friendly next steps derived from recurring diagnosis/error keywords. if any( marker in fragment for fragment in fragments @@ -969,6 +972,8 @@ def java_runtime_compliance( distribution_values.append((distribution, runtime_count)) if version not in release_cache: + # Reuse release lookups across identical versions so the report + # stays bounded even when multiple usage rows share a release. release_cache[version] = _safe_get_java_release(client, version) release = release_cache[version] @@ -1001,6 +1006,8 @@ def java_runtime_compliance( security_status in {"UPDATE_REQUIRED", "UPGRADE_REQUIRED"} and len(outdated_installations) < _MAX_OUTDATED_INSTALLATIONS ): + # Drill down only for outdated buckets and cap the sample so the + # tool remains readable while still surfacing concrete fixes. remaining = _MAX_OUTDATED_INSTALLATIONS - len(outdated_installations) sites = _collect_paginated_items( client.list_installation_sites, diff --git a/src/oci-jms-mcp-server/scripts/README.md b/src/oci-jms-mcp-server/scripts/README.md deleted file mode 100644 index 8ef4151d..00000000 --- a/src/oci-jms-mcp-server/scripts/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# JMS Script Utilities - -This folder contains temporary local utilities for manual JMS MCP testing. - -## `test_jms_mcp_client.py` - -This is a temporary manual validation client for the JMS MCP server. -It connects to an already-running HTTP MCP server and exercises the 9 JMS tools. - -Remove this script and this README before committing if you do not want local-only test utilities in the final change set. - -### Prerequisites - -- The JMS MCP server must already be running over HTTP. -- Default target URL: `http://127.0.0.1:8888/mcp` -- The repo virtualenv should exist at `../../.venv` when run from `src/oci-jms-mcp-server` - -### Run All 9 Tools - -From the JMS package directory: - -```sh -cd src/oci-jms-mcp-server -../../.venv/bin/python scripts/test_jms_mcp_client.py -``` - -### Run One Tool - -```sh -../../.venv/bin/python scripts/test_jms_mcp_client.py --tool list_fleets -``` - -### Override Inputs - -```sh -../../.venv/bin/python scripts/test_jms_mcp_client.py \ - --url http://127.0.0.1:8888/mcp \ - --compartment-id ocid1.compartment.oc1..aaaaaaaamfgnvfzwcbbhn7ktlulig2whera32rwrvvmfkkx3svic4geffgxq \ - --fleet-id ocid1.jmsfleet.oc1.iad.amaaaaaa6cijguiayfvn2rk5i3ue5cwd67ru4h3gnalej6dqievm233hytla \ - --limit 10 \ - --timeout 20 -``` - -### Output - -The script prints: - -- discovered tool names -- `PASS` / `FAIL` / `TIMEOUT` / `SKIPPED` for each tool -- a final summary line with pass/fail counts - -### Supported Options - -- `--url` -- `--compartment-id` -- `--fleet-id` -- `--limit` -- `--timeout` -- `--tool` diff --git a/src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py b/src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py deleted file mode 100644 index c788e687..00000000 --- a/src/oci-jms-mcp-server/scripts/test_jms_mcp_client.py +++ /dev/null @@ -1,271 +0,0 @@ -""" -Temporary manual JMS MCP validation client. - -This script connects to an already-running HTTP MCP server and exercises the -JMS tool surface for quick operator testing. Remove it before committing. -""" - -from __future__ import annotations - -import argparse -import asyncio -import sys -from typing import Any - -from fastmcp import Client - -DEFAULT_URL = "http://127.0.0.1:8888/mcp" - -#compartment = JMS-TEST-CANARY -DEFAULT_COMPARTMENT_ID = ( - "" -) - -#fleet name = auto-ol8-epp-test -DEFAULT_FLEET_ID = ( - "" -) - -TOOL_ORDER = [ - "list_fleets", - "get_fleet", - "list_jms_plugins", - "get_jms_plugin", - "list_installation_sites", - "get_fleet_agent_configuration", - "get_fleet_advanced_feature_configuration", - "summarize_resource_inventory", - "summarize_managed_instance_usage", -] - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Run manual validation against a running JMS MCP HTTP server." - ) - parser.add_argument("--url", default=DEFAULT_URL, help="MCP HTTP endpoint URL.") - parser.add_argument( - "--compartment-id", - default=DEFAULT_COMPARTMENT_ID, - help="Compartment OCID used by list and summarize calls.", - ) - parser.add_argument( - "--fleet-id", - default=DEFAULT_FLEET_ID, - help="Fleet OCID used by fleet-scoped calls.", - ) - parser.add_argument( - "--limit", - type=int, - default=10, - help="Limit for list-style tool calls.", - ) - parser.add_argument( - "--timeout", - type=int, - default=20, - help="Per-tool timeout in seconds.", - ) - parser.add_argument( - "--tool", - choices=["all", *TOOL_ORDER], - default="all", - help="Run one tool or the full tool suite.", - ) - return parser.parse_args() - - -def summarize_structured_content(structured: Any) -> tuple[str, str | None]: - if isinstance(structured, dict): - if "result" in structured and isinstance(structured["result"], list): - items = structured["result"] - first_id = None - if items and isinstance(items[0], dict): - first_id = items[0].get("id") - summary = f"count={len(items)}" - if first_id: - summary += f" first_id={first_id}" - return summary, first_id - if "id" in structured: - return f"id={structured['id']}", structured["id"] - keys = ",".join(sorted(structured.keys())[:5]) - return f"keys={keys}", None - - if isinstance(structured, list): - first_id = None - if structured and isinstance(structured[0], dict): - first_id = structured[0].get("id") - summary = f"count={len(structured)}" - if first_id: - summary += f" first_id={first_id}" - return summary, first_id - - return f"type={type(structured).__name__}", None - - -async def call_tool( - client: Client, - tool_name: str, - payload: dict[str, Any], - timeout: int, -) -> tuple[str, str | None]: - try: - result = await asyncio.wait_for(client.call_tool(tool_name, payload), timeout=timeout) - summary, first_id = summarize_structured_content(result.structured_content) - return f"PASS {tool_name}: {summary}", first_id - except asyncio.TimeoutError: - return f"TIMEOUT {tool_name}: timed out after {timeout}s", None - except Exception as exc: # noqa: BLE001 - return f"FAIL {tool_name}: {type(exc).__name__}: {exc}", None - - -async def run_suite(args: argparse.Namespace) -> int: - requested_tools = TOOL_ORDER if args.tool == "all" else [args.tool] - results: list[str] = [] - failures = 0 - plugin_id: str | None = None - - async with Client(args.url) as client: - tools = await asyncio.wait_for(client.list_tools(), timeout=args.timeout) - tool_names = [tool.name for tool in tools] - results.append(f"Connected to {args.url}") - results.append(f"Discovered {len(tool_names)} tools: {', '.join(tool_names)}") - - if args.tool in ("all", "list_fleets"): - line, _ = await call_tool( - client, - "list_fleets", - { - "compartment_id": args.compartment_id, - "limit": args.limit, - "sort_order": "ASC", - "sort_by": "displayName", - }, - args.timeout, - ) - results.append(line) - failures += int(not line.startswith("PASS")) - - if args.tool in ("all", "get_fleet"): - line, _ = await call_tool( - client, - "get_fleet", - {"fleet_id": args.fleet_id}, - args.timeout, - ) - results.append(line) - failures += int(not line.startswith("PASS")) - - if args.tool in ("all", "list_jms_plugins"): - line, plugin_id = await call_tool( - client, - "list_jms_plugins", - { - "fleet_id": args.fleet_id, - "limit": args.limit, - "sort_order": "ASC", - }, - args.timeout, - ) - results.append(line) - failures += int(not line.startswith("PASS")) - - if args.tool == "get_jms_plugin": - plugin_id = None - - if args.tool in ("all", "get_jms_plugin"): - if plugin_id is None: - if args.tool == "get_jms_plugin": - results.append( - "FAIL get_jms_plugin: requires a plugin returned by list_jms_plugins in all-tools mode" - ) - failures += 1 - else: - results.append("SKIPPED get_jms_plugin: no plugin returned by list_jms_plugins") - else: - line, _ = await call_tool( - client, - "get_jms_plugin", - {"jms_plugin_id": plugin_id}, - args.timeout, - ) - results.append(line) - failures += int(not line.startswith("PASS")) - - if args.tool in ("all", "list_installation_sites"): - line, _ = await call_tool( - client, - "list_installation_sites", - { - "fleet_id": args.fleet_id, - "limit": args.limit, - "sort_order": "ASC", - }, - args.timeout, - ) - results.append(line) - failures += int(not line.startswith("PASS")) - - if args.tool in ("all", "get_fleet_agent_configuration"): - line, _ = await call_tool( - client, - "get_fleet_agent_configuration", - {"fleet_id": args.fleet_id}, - args.timeout, - ) - results.append(line) - failures += int(not line.startswith("PASS")) - - if args.tool in ("all", "get_fleet_advanced_feature_configuration"): - line, _ = await call_tool( - client, - "get_fleet_advanced_feature_configuration", - {"fleet_id": args.fleet_id}, - args.timeout, - ) - results.append(line) - failures += int(not line.startswith("PASS")) - - if args.tool in ("all", "summarize_resource_inventory"): - line, _ = await call_tool( - client, - "summarize_resource_inventory", - {"compartment_id": args.compartment_id}, - args.timeout, - ) - results.append(line) - failures += int(not line.startswith("PASS")) - - if args.tool in ("all", "summarize_managed_instance_usage"): - line, _ = await call_tool( - client, - "summarize_managed_instance_usage", - { - "fleet_id": args.fleet_id, - "limit": args.limit, - "sort_order": "ASC", - }, - args.timeout, - ) - results.append(line) - failures += int(not line.startswith("PASS")) - - for line in results: - print(line) - - executed = sum( - line.startswith(("PASS", "FAIL", "TIMEOUT", "SKIPPED")) for line in results - ) - passed = sum(line.startswith("PASS") for line in results) - skipped = sum(line.startswith("SKIPPED") for line in results) - print(f"Summary: passed={passed} failed={failures} skipped={skipped} executed={executed}") - return 0 if failures == 0 else 1 - - -def main() -> int: - args = parse_args() - return asyncio.run(run_suite(args)) - - -if __name__ == "__main__": - raise SystemExit(main()) From ff27dca81c0ca13540b759d3ff4dae48ec52c5b9 Mon Sep 17 00:00:00 2001 From: bhuveshsharma09 Date: Tue, 28 Apr 2026 18:52:22 +0800 Subject: [PATCH 10/11] fix(jms): remove HTTP transport support --- src/oci-jms-mcp-server/README.md | 35 +------------------ .../oracle/oci_jms_mcp_server/server.py | 10 ++---- .../tests/test_jms_tools.py | 28 +-------------- 3 files changed, 4 insertions(+), 69 deletions(-) diff --git a/src/oci-jms-mcp-server/README.md b/src/oci-jms-mcp-server/README.md index e5649c72..321968ae 100644 --- a/src/oci-jms-mcp-server/README.md +++ b/src/oci-jms-mcp-server/README.md @@ -4,6 +4,7 @@ This server provides tools to interact with Oracle Cloud Infrastructure Java Management Service (JMS). It focuses on fleet inventory, discovery, and health troubleshooting workflows. +It runs over stdio only. ## Authentication @@ -73,24 +74,6 @@ Non-prod example: OCI_CONFIG_FILE=~/.oci/config OCI_CONFIG_PROFILE=DEFAULT JMS_TEST_ENVIRONMENT=DEV UV_CACHE_DIR=/tmp/uv-cache uv run oracle.oci-jms-mcp-server ``` -### HTTP streaming transport mode - -```sh -OCI_CONFIG_FILE=~/.oci/config OCI_CONFIG_PROFILE=DEFAULT UV_CACHE_DIR=/tmp/uv-cache ORACLE_MCP_HOST=127.0.0.1 ORACLE_MCP_PORT=8888 uv run oracle.oci-jms-mcp-server -``` - -Non-prod example: - -```sh -OCI_CONFIG_FILE=~/.oci/config OCI_CONFIG_PROFILE=DEFAULT JMS_TEST_ENVIRONMENT=TEST UV_CACHE_DIR=/tmp/uv-cache ORACLE_MCP_HOST=127.0.0.1 ORACLE_MCP_PORT=8888 uv run oracle.oci-jms-mcp-server -``` - -The HTTP endpoint is: - -```text -http://127.0.0.1:8888/mcp -``` - ## MCP Client Configuration ### Cline / stdio @@ -113,21 +96,6 @@ http://127.0.0.1:8888/mcp } ``` -### Cline / HTTP - -Run the server manually, then configure: - -```json -{ - "mcpServers": { - "oracle-oci-jms-mcp-server": { - "type": "streamableHttp", - "url": "http://127.0.0.1:8888/mcp" - } - } -} -``` - ## Verification Verify the server through an MCP agent in this order: @@ -140,7 +108,6 @@ Prefer verifying `get_fleet` only with an OCID returned by `list_fleets`. If `ge ## Troubleshooting -- If startup falls back to stdio, make sure both `ORACLE_MCP_HOST` and `ORACLE_MCP_PORT` are set. - If `uv` fails with `Failed to initialize cache`, create a writable cache directory and run with `UV_CACHE_DIR=/tmp/uv-cache`. - If `get_fleet` returns `NotAuthorizedOrNotFound`, verify: - the active `OCI_CONFIG_PROFILE` diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py index 4d906a1c..42597162 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py @@ -1062,14 +1062,8 @@ def java_runtime_compliance( def main(): - """Run the JMS MCP server over stdio by default or HTTP when host and port are provided.""" - host = os.getenv("ORACLE_MCP_HOST") - port = os.getenv("ORACLE_MCP_PORT") - - if host and port: - mcp.run(transport="http", host=host, port=int(port)) - else: - mcp.run() + """Run the JMS MCP server over stdio.""" + mcp.run() if __name__ == "__main__": diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py index 83776806..5b39999d 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py @@ -1016,33 +1016,7 @@ async def test_java_runtime_compliance_ignores_missing_release_metadata(self, mo class TestServerMain: @patch("oracle.oci_jms_mcp_server.server.mcp.run") - @patch("os.getenv") - def test_main_with_host_and_port(self, mock_getenv, mock_mcp_run): - mock_env = {"ORACLE_MCP_HOST": "1.2.3.4", "ORACLE_MCP_PORT": "8888"} - mock_getenv.side_effect = lambda key: mock_env.get(key) - server.main() - mock_mcp_run.assert_called_once_with(transport="http", host="1.2.3.4", port=8888) - - @patch("oracle.oci_jms_mcp_server.server.mcp.run") - @patch("os.getenv") - def test_main_without_host_and_port(self, mock_getenv, mock_mcp_run): - mock_getenv.return_value = None - server.main() - mock_mcp_run.assert_called_once_with() - - @patch("oracle.oci_jms_mcp_server.server.mcp.run") - @patch("os.getenv") - def test_main_with_only_host(self, mock_getenv, mock_mcp_run): - mock_env = {"ORACLE_MCP_HOST": "1.2.3.4"} - mock_getenv.side_effect = lambda key: mock_env.get(key) - server.main() - mock_mcp_run.assert_called_once_with() - - @patch("oracle.oci_jms_mcp_server.server.mcp.run") - @patch("os.getenv") - def test_main_with_only_port(self, mock_getenv, mock_mcp_run): - mock_env = {"ORACLE_MCP_PORT": "8888"} - mock_getenv.side_effect = lambda key: mock_env.get(key) + def test_main_uses_stdio_transport(self, mock_mcp_run): server.main() mock_mcp_run.assert_called_once_with() From 2f75b3d43cbe12bd53dcac9d3637dc54e9b5f8aa Mon Sep 17 00:00:00 2001 From: bhuveshsharma09 Date: Tue, 28 Apr 2026 19:07:38 +0800 Subject: [PATCH 11/11] fix(jms): explicitly enforce stdio transport --- src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py | 2 +- .../oracle/oci_jms_mcp_server/tests/test_jms_tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py index 42597162..96715b74 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/server.py @@ -1063,7 +1063,7 @@ def java_runtime_compliance( def main(): """Run the JMS MCP server over stdio.""" - mcp.run() + mcp.run(transport="stdio") if __name__ == "__main__": diff --git a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py index 5b39999d..baba5edf 100644 --- a/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py +++ b/src/oci-jms-mcp-server/oracle/oci_jms_mcp_server/tests/test_jms_tools.py @@ -1018,7 +1018,7 @@ class TestServerMain: @patch("oracle.oci_jms_mcp_server.server.mcp.run") def test_main_uses_stdio_transport(self, mock_mcp_run): server.main() - mock_mcp_run.assert_called_once_with() + mock_mcp_run.assert_called_once_with(transport="stdio") class TestGetJmsClient: