systemlink-cli 1.23.0__tar.gz → 1.25.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/PKG-INFO +2 -2
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/pyproject.toml +4 -3
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/_version.py +1 -1
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/config_click.py +168 -21
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/main.py +46 -1
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/mcp_click.py +20 -22
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/mcp_server.py +264 -56
- systemlink_cli-1.25.0/slcli/pkce.py +613 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/platform.py +122 -52
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/profiles.py +16 -5
- systemlink_cli-1.25.0/slcli/systemlink-logo.svg +20 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/utils.py +182 -18
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/workitem_click.py +14 -2
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/LICENSE +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/dff-editor/editor.js +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/dff-editor/index.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/__init__.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/__main__.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/alarm_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/asset_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/cli_formatters.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/cli_utils.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/comment_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/completion_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/config.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/dataframe_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/dff_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/dff_decorators.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/example_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/example_loader.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/example_provisioner.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/README.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/_schema/schema-v1.0.json +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-1/README.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-1/config.yaml +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-2/README.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-2/config.yaml +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/config.yaml +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/leakage-current-results.json +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/overvoltage-results.json +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/product-xyz-compliance-report.json +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/product-xyz-specification.csv +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/thermal-response-results.json +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/feed_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/file_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/function_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/function_templates.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/mcp_reachability.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/notebook_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/policy_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/policy_utils.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/response_handlers.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/rich_output.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/routine_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skill_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/SKILL.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/commands.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/example-authoring.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/filtering.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/import-specs.min.json +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/job-debugging/overview.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/nipkg/overview.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/notebook/interfaces.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/notebook/notebook-patterns.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/notebook/overview.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/python-test/overview.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/routine-examples.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/troubleshooting.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/angular-ui-packages.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/deployment.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/layout-patterns.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/nimble-angular.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/overview.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/systemlink-services.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/troubleshooting.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/scripts/spec_import_helper.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/spec_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/ssl_trust.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/state_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/system_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/system_query_utils.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/table_utils.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/tag_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/templates_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/testmonitor_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/universal_handlers.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/user_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/web_editor.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_bootstrap.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/.gitignore +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/README.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/angular.json +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/package.json +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/app-routing.module.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/app.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/app.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/app.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/app.module.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/state/app-view-state.service.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/systemlink-context.service.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/theme-sync.service.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/webapp-home-data.service.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/states/view-state.model.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/assets/README.md +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/index.html +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/main.ts +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/styles.scss +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/tsconfig.app.json +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/tsconfig.json +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/workflow_preview.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/workflows_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/workspace_click.py +0 -0
- {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/workspace_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: systemlink-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.25.0
|
|
4
4
|
Summary: SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: Fred Visser
|
|
@@ -14,7 +14,7 @@ Provides-Extra: mcp
|
|
|
14
14
|
Requires-Dist: click (>=7.1.2)
|
|
15
15
|
Requires-Dist: cryptography (>=49.0.0)
|
|
16
16
|
Requires-Dist: keyring (>=25.6.0,<26.0.0)
|
|
17
|
-
Requires-Dist: mcp (>=
|
|
17
|
+
Requires-Dist: mcp (>=2,<3) ; extra == "mcp"
|
|
18
18
|
Requires-Dist: packaging (>=21.0)
|
|
19
19
|
Requires-Dist: pygments (>=2.20.0)
|
|
20
20
|
Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "systemlink-cli"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.25.0"
|
|
4
4
|
description = "SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates."
|
|
5
5
|
authors = ["Fred Visser <fred.visser@emerson.com>"]
|
|
6
6
|
packages = [{ include = "slcli" }]
|
|
7
7
|
include = [
|
|
8
|
+
{ path = "slcli/systemlink-logo.svg", format = ["wheel", "sdist"] },
|
|
8
9
|
{ path = "dff-editor/index.html", format = ["wheel", "sdist"] },
|
|
9
10
|
{ path = "dff-editor/editor.js", format = ["wheel", "sdist"] },
|
|
10
11
|
{ path = "slcli/skills", format = ["wheel", "sdist"] },
|
|
@@ -37,7 +38,7 @@ rich = ">=15,<16"
|
|
|
37
38
|
rich-click = ">=1.8,<2"
|
|
38
39
|
cryptography = ">=49.0.0"
|
|
39
40
|
pygments = ">=2.20.0"
|
|
40
|
-
mcp = { version = ">=
|
|
41
|
+
mcp = { version = ">=2,<3", optional = true }
|
|
41
42
|
|
|
42
43
|
[tool.poetry.extras]
|
|
43
44
|
mcp = ["mcp"]
|
|
@@ -80,7 +81,7 @@ pytest-xdist = "^3.8.0"
|
|
|
80
81
|
openpyxl = "^3.1.5"
|
|
81
82
|
|
|
82
83
|
# MCP server (optional feature, also needed for type checking)
|
|
83
|
-
mcp = ">=
|
|
84
|
+
mcp = ">=2,<3"
|
|
84
85
|
|
|
85
86
|
[tool.pytest.ini_options]
|
|
86
87
|
addopts = "--cov slcli --strict-markers --doctest-modules --ignore=tests/e2e"
|
|
@@ -16,6 +16,7 @@ from .platform import (
|
|
|
16
16
|
PLATFORM_SLE,
|
|
17
17
|
PLATFORM_SLS,
|
|
18
18
|
check_service_status,
|
|
19
|
+
check_web_server_auth,
|
|
19
20
|
)
|
|
20
21
|
from .profiles import ProfileConfig, Profile, check_config_file_permissions
|
|
21
22
|
from .rich_output import render_table
|
|
@@ -134,7 +135,11 @@ def _show_certificate_warning(certificate: dict[str, Any]) -> None:
|
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
def _trust_certificate_if_requested(
|
|
137
|
-
url: str,
|
|
138
|
+
url: str,
|
|
139
|
+
credential: str,
|
|
140
|
+
status: dict[str, Any],
|
|
141
|
+
trust_fingerprint: Optional[str],
|
|
142
|
+
auth_scheme: str = "api-key",
|
|
138
143
|
) -> dict[str, Any]:
|
|
139
144
|
"""Prompt for or apply certificate trust after a verification failure."""
|
|
140
145
|
certificate_details = status.get("certificate")
|
|
@@ -174,7 +179,10 @@ def _trust_certificate_if_requested(
|
|
|
174
179
|
|
|
175
180
|
try:
|
|
176
181
|
save_managed_certificate(certificate)
|
|
177
|
-
|
|
182
|
+
if auth_scheme == "bearer":
|
|
183
|
+
retry_status = check_web_server_auth(url, credential, auth_scheme=auth_scheme)
|
|
184
|
+
else:
|
|
185
|
+
retry_status = check_service_status(url, credential, auth_scheme=auth_scheme)
|
|
178
186
|
except (OSError, ValueError) as exc:
|
|
179
187
|
_exit_with_validation_error(
|
|
180
188
|
f"Could not save the trusted certificate: {exc}. Profile was not saved.",
|
|
@@ -202,6 +210,10 @@ def _add_profile_impl(
|
|
|
202
210
|
set_current: bool,
|
|
203
211
|
readonly: bool,
|
|
204
212
|
trust_fingerprint: Optional[str] = None,
|
|
213
|
+
auth_mode: str = "api-key",
|
|
214
|
+
client_id: Optional[str] = None,
|
|
215
|
+
scopes: tuple[str, ...] = (),
|
|
216
|
+
callback_port: Optional[int] = None,
|
|
205
217
|
) -> None:
|
|
206
218
|
"""Shared implementation for add-profile and login commands.
|
|
207
219
|
|
|
@@ -217,6 +229,10 @@ def _add_profile_impl(
|
|
|
217
229
|
set_current: Whether to set as the current profile
|
|
218
230
|
readonly: Whether to enable readonly mode
|
|
219
231
|
trust_fingerprint: Optional SHA-256 fingerprint for non-interactive trust approval
|
|
232
|
+
auth_mode: Authentication flow to use
|
|
233
|
+
client_id: Public OAuth client ID for the PKCE login
|
|
234
|
+
scopes: OAuth scopes for the PKCE login
|
|
235
|
+
callback_port: Optional loopback callback port; zero selects an ephemeral port
|
|
220
236
|
"""
|
|
221
237
|
# Get profile name
|
|
222
238
|
if not profile:
|
|
@@ -231,11 +247,20 @@ def _add_profile_impl(
|
|
|
231
247
|
assert isinstance(url, str)
|
|
232
248
|
url = _normalize_base_url(url, "SystemLink API URL")
|
|
233
249
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
250
|
+
if auth_mode == "api-key":
|
|
251
|
+
# Get API key - either from flag or prompt
|
|
252
|
+
if not api_key:
|
|
253
|
+
api_key = getpass.getpass("Enter your SystemLink API key: ")
|
|
254
|
+
assert isinstance(api_key, str)
|
|
255
|
+
api_key = _normalize_api_key(api_key)
|
|
256
|
+
elif auth_mode == "pkce":
|
|
257
|
+
if api_key:
|
|
258
|
+
_exit_with_validation_error(
|
|
259
|
+
"--api-key cannot be combined with --auth pkce. "
|
|
260
|
+
"Use --auth api-key for the API-key login flow."
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
_exit_with_validation_error(f"Unsupported authentication flow: {auth_mode}")
|
|
239
264
|
|
|
240
265
|
# Normalize and validate web_url (prompt if not provided)
|
|
241
266
|
if not web_url:
|
|
@@ -244,12 +269,59 @@ def _add_profile_impl(
|
|
|
244
269
|
assert isinstance(web_url, str)
|
|
245
270
|
web_url = _normalize_base_url(web_url, "SystemLink Web UI URL")
|
|
246
271
|
|
|
247
|
-
|
|
272
|
+
pkce_result: Any = None
|
|
273
|
+
pkce_scopes = list(scopes) if scopes else ["openid", "profile", "email", "offline_access"]
|
|
274
|
+
if auth_mode == "pkce":
|
|
275
|
+
certificate_status = check_web_server_auth(web_url, "", auth_scheme="bearer")
|
|
276
|
+
if certificate_status.get("certificate_error") and not certificate_status.get(
|
|
277
|
+
"server_reachable"
|
|
278
|
+
):
|
|
279
|
+
_trust_certificate_if_requested(
|
|
280
|
+
web_url,
|
|
281
|
+
"",
|
|
282
|
+
certificate_status,
|
|
283
|
+
trust_fingerprint,
|
|
284
|
+
auth_scheme="bearer",
|
|
285
|
+
)
|
|
286
|
+
if not client_id:
|
|
287
|
+
client_id = click.prompt("PKCE client ID")
|
|
288
|
+
assert isinstance(client_id, str)
|
|
289
|
+
client_id = client_id.strip()
|
|
290
|
+
if not client_id:
|
|
291
|
+
_exit_with_validation_error("PKCE client ID cannot be empty.")
|
|
292
|
+
|
|
293
|
+
from .pkce import PkceError, perform_pkce_login
|
|
294
|
+
|
|
295
|
+
click.echo("Opening the SystemLink Web UI for authentication...")
|
|
296
|
+
try:
|
|
297
|
+
if callback_port is None:
|
|
298
|
+
pkce_result = perform_pkce_login(web_url, client_id, pkce_scopes)
|
|
299
|
+
else:
|
|
300
|
+
pkce_result = perform_pkce_login(
|
|
301
|
+
web_url, client_id, pkce_scopes, callback_port=callback_port
|
|
302
|
+
)
|
|
303
|
+
except PkceError as exc:
|
|
304
|
+
_exit_with_validation_error(f"PKCE login failed: {exc}")
|
|
305
|
+
api_key = pkce_result.access_token
|
|
306
|
+
|
|
307
|
+
assert isinstance(api_key, str)
|
|
308
|
+
|
|
309
|
+
# Validate PKCE against the Web Server identity route; API-key login retains API probes.
|
|
248
310
|
click.echo("Checking server connectivity and services...")
|
|
249
|
-
|
|
311
|
+
if auth_mode == "pkce":
|
|
312
|
+
status = check_web_server_auth(web_url, api_key, auth_scheme="bearer")
|
|
313
|
+
else:
|
|
314
|
+
status = check_service_status(url, api_key)
|
|
250
315
|
|
|
251
316
|
if status.get("certificate_error") and not status.get("server_reachable"):
|
|
252
|
-
|
|
317
|
+
validation_url = web_url if auth_mode == "pkce" else url
|
|
318
|
+
status = _trust_certificate_if_requested(
|
|
319
|
+
validation_url,
|
|
320
|
+
api_key,
|
|
321
|
+
status,
|
|
322
|
+
trust_fingerprint,
|
|
323
|
+
auth_scheme="bearer" if auth_mode == "pkce" else "api-key",
|
|
324
|
+
)
|
|
253
325
|
|
|
254
326
|
platform = status["platform"]
|
|
255
327
|
services = status.get("services", {})
|
|
@@ -262,16 +334,26 @@ def _add_profile_impl(
|
|
|
262
334
|
)
|
|
263
335
|
|
|
264
336
|
if status["auth_valid"] is False and _all_service_probes_unauthorized(services):
|
|
337
|
+
auth_failure_message = (
|
|
338
|
+
"PKCE bearer token validation failed. The server responded, but the token was not authorized. "
|
|
339
|
+
if auth_mode == "pkce"
|
|
340
|
+
else "API key validation failed. The server responded, but the key was not authorized. "
|
|
341
|
+
)
|
|
265
342
|
_exit_with_validation_error(
|
|
266
|
-
|
|
267
|
-
"Profile was not saved.",
|
|
343
|
+
auth_failure_message + "Profile was not saved.",
|
|
268
344
|
ExitCodes.PERMISSION_DENIED,
|
|
269
345
|
)
|
|
270
346
|
|
|
271
347
|
if status["auth_valid"] is not True:
|
|
272
|
-
|
|
348
|
+
verification_message = (
|
|
273
349
|
"Connected to the server, but profile verification was inconclusive. Check the "
|
|
274
|
-
"
|
|
350
|
+
"Web UI URL, bearer token, and service availability. Profile was not saved."
|
|
351
|
+
if auth_mode == "pkce"
|
|
352
|
+
else "Connected to the server, but profile verification was inconclusive. Check the "
|
|
353
|
+
"API URL, API key, and service availability. Profile was not saved."
|
|
354
|
+
)
|
|
355
|
+
_exit_with_validation_error(
|
|
356
|
+
verification_message,
|
|
275
357
|
ExitCodes.GENERAL_ERROR,
|
|
276
358
|
)
|
|
277
359
|
|
|
@@ -283,7 +365,10 @@ def _add_profile_impl(
|
|
|
283
365
|
else:
|
|
284
366
|
click.echo(" Platform: Unknown (will attempt all features)")
|
|
285
367
|
|
|
286
|
-
|
|
368
|
+
if auth_mode == "pkce":
|
|
369
|
+
click.echo(" PKCE bearer token: ✓ Authorized")
|
|
370
|
+
else:
|
|
371
|
+
click.echo(" API key: ✓ Authorized")
|
|
287
372
|
|
|
288
373
|
if status.get("file_query_endpoint") == "query-files":
|
|
289
374
|
click.echo(" File query: query-files")
|
|
@@ -315,13 +400,40 @@ def _add_profile_impl(
|
|
|
315
400
|
platform=platform,
|
|
316
401
|
workspace=workspace,
|
|
317
402
|
readonly=readonly,
|
|
403
|
+
auth_mode=auth_mode,
|
|
404
|
+
pkce_client_id=client_id if auth_mode == "pkce" else None,
|
|
405
|
+
pkce_scopes=pkce_scopes if auth_mode == "pkce" else None,
|
|
318
406
|
)
|
|
319
407
|
|
|
320
408
|
# Load config and add profile
|
|
321
409
|
cfg = ProfileConfig.load()
|
|
410
|
+
previous_profile = cfg.get_profile(profile)
|
|
411
|
+
previous_current_profile = cfg.current_profile
|
|
322
412
|
cfg.add_profile(new_profile, set_current=set_current)
|
|
323
413
|
cfg.save()
|
|
324
414
|
|
|
415
|
+
if auth_mode == "pkce" and pkce_result is not None:
|
|
416
|
+
from .pkce import save_pkce_credentials
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
save_pkce_credentials(
|
|
420
|
+
profile,
|
|
421
|
+
pkce_result.access_token,
|
|
422
|
+
pkce_result.refresh_token,
|
|
423
|
+
pkce_result.expires_at,
|
|
424
|
+
)
|
|
425
|
+
except Exception as exc:
|
|
426
|
+
if previous_profile is None:
|
|
427
|
+
cfg.profiles.pop(profile, None)
|
|
428
|
+
else:
|
|
429
|
+
cfg.profiles[profile] = previous_profile
|
|
430
|
+
cfg.current_profile = previous_current_profile
|
|
431
|
+
cfg.save()
|
|
432
|
+
_exit_with_validation_error(
|
|
433
|
+
f"Could not store PKCE credentials securely: {exc}.",
|
|
434
|
+
ExitCodes.GENERAL_ERROR,
|
|
435
|
+
)
|
|
436
|
+
|
|
325
437
|
click.echo(f"\n✓ Profile '{profile}' saved successfully.")
|
|
326
438
|
click.echo(f" Server: {url}")
|
|
327
439
|
click.echo(f" Web URL: {web_url}")
|
|
@@ -373,6 +485,7 @@ def register_config_commands(cli: Any) -> None:
|
|
|
373
485
|
item = {
|
|
374
486
|
"name": p.name,
|
|
375
487
|
"server": p.server,
|
|
488
|
+
"auth-mode": p.auth_mode,
|
|
376
489
|
"current": p.name == cfg.current_profile,
|
|
377
490
|
}
|
|
378
491
|
if p.web_url:
|
|
@@ -523,13 +636,16 @@ def register_config_commands(cli: Any) -> None:
|
|
|
523
636
|
if profile.platform:
|
|
524
637
|
rows.append(["Platform", profile.platform or "Unknown"])
|
|
525
638
|
|
|
526
|
-
if
|
|
527
|
-
|
|
639
|
+
if profile.auth_mode == "pkce":
|
|
640
|
+
rows.append(["Authentication", "PKCE (bearer token in keyring)"])
|
|
528
641
|
else:
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
642
|
+
if show_secrets:
|
|
643
|
+
api_key_display = profile.api_key
|
|
644
|
+
else:
|
|
645
|
+
api_key_display = (
|
|
646
|
+
"****" + profile.api_key[-4:] if len(profile.api_key) >= 4 else "****"
|
|
647
|
+
)
|
|
648
|
+
rows.append(["API Key", api_key_display])
|
|
533
649
|
|
|
534
650
|
if profile.workspace:
|
|
535
651
|
rows.append(["Workspace", profile.workspace])
|
|
@@ -735,6 +851,29 @@ def register_config_commands(cli: Any) -> None:
|
|
|
735
851
|
@click.option("--url", help="SystemLink API URL")
|
|
736
852
|
@click.option("--api-key", help="SystemLink API key")
|
|
737
853
|
@click.option("--web-url", help="SystemLink Web UI base URL")
|
|
854
|
+
@click.option(
|
|
855
|
+
"--auth",
|
|
856
|
+
"auth_mode",
|
|
857
|
+
type=click.Choice(["api-key", "pkce"]),
|
|
858
|
+
default="api-key",
|
|
859
|
+
show_default=True,
|
|
860
|
+
help="Authentication flow to use",
|
|
861
|
+
)
|
|
862
|
+
@click.option("--client-id", help="Public OAuth client ID for the PKCE prototype")
|
|
863
|
+
@click.option(
|
|
864
|
+
"--callback-port",
|
|
865
|
+
type=click.IntRange(0, 65535),
|
|
866
|
+
help="Loopback callback port; use 0 for an ephemeral port",
|
|
867
|
+
)
|
|
868
|
+
@click.option(
|
|
869
|
+
"--scope",
|
|
870
|
+
"scopes",
|
|
871
|
+
multiple=True,
|
|
872
|
+
help=(
|
|
873
|
+
"OAuth scope to request; may be repeated "
|
|
874
|
+
"(defaults to openid profile email offline_access)"
|
|
875
|
+
),
|
|
876
|
+
)
|
|
738
877
|
@click.option("--workspace", "-w", help="Default workspace for this profile")
|
|
739
878
|
@click.option(
|
|
740
879
|
"--set-current/--no-set-current",
|
|
@@ -758,6 +897,10 @@ def register_config_commands(cli: Any) -> None:
|
|
|
758
897
|
url: Optional[str],
|
|
759
898
|
api_key: Optional[str],
|
|
760
899
|
web_url: Optional[str],
|
|
900
|
+
auth_mode: str,
|
|
901
|
+
client_id: Optional[str],
|
|
902
|
+
callback_port: Optional[int],
|
|
903
|
+
scopes: tuple[str, ...],
|
|
761
904
|
workspace: Optional[str],
|
|
762
905
|
set_current: bool,
|
|
763
906
|
readonly: bool,
|
|
@@ -781,6 +924,10 @@ def register_config_commands(cli: Any) -> None:
|
|
|
781
924
|
url=url,
|
|
782
925
|
api_key=api_key,
|
|
783
926
|
web_url=web_url,
|
|
927
|
+
auth_mode=auth_mode,
|
|
928
|
+
client_id=client_id,
|
|
929
|
+
callback_port=callback_port,
|
|
930
|
+
scopes=scopes,
|
|
784
931
|
workspace=workspace,
|
|
785
932
|
set_current=set_current,
|
|
786
933
|
readonly=readonly,
|
|
@@ -415,6 +415,29 @@ def ca_info() -> None:
|
|
|
415
415
|
@click.option("--url", help="SystemLink API URL")
|
|
416
416
|
@click.option("--api-key", help="SystemLink API key")
|
|
417
417
|
@click.option("--web-url", help="SystemLink Web UI base URL")
|
|
418
|
+
@click.option(
|
|
419
|
+
"--auth",
|
|
420
|
+
"auth_mode",
|
|
421
|
+
type=click.Choice(["api-key", "pkce"]),
|
|
422
|
+
default="api-key",
|
|
423
|
+
show_default=True,
|
|
424
|
+
help="Authentication flow to use",
|
|
425
|
+
)
|
|
426
|
+
@click.option("--client-id", help="Public OAuth client ID for the PKCE prototype")
|
|
427
|
+
@click.option(
|
|
428
|
+
"--callback-port",
|
|
429
|
+
type=click.IntRange(0, 65535),
|
|
430
|
+
help="Loopback callback port; use 0 for an ephemeral port",
|
|
431
|
+
)
|
|
432
|
+
@click.option(
|
|
433
|
+
"--scope",
|
|
434
|
+
"scopes",
|
|
435
|
+
multiple=True,
|
|
436
|
+
help=(
|
|
437
|
+
"OAuth scope to request; may be repeated "
|
|
438
|
+
"(defaults to openid profile email offline_access)"
|
|
439
|
+
),
|
|
440
|
+
)
|
|
418
441
|
@click.option("--workspace", "-w", help="Default workspace for this profile")
|
|
419
442
|
@click.option(
|
|
420
443
|
"--set-current/--no-set-current",
|
|
@@ -438,6 +461,10 @@ def login(
|
|
|
438
461
|
url: Optional[str],
|
|
439
462
|
api_key: Optional[str],
|
|
440
463
|
web_url: Optional[str],
|
|
464
|
+
auth_mode: str,
|
|
465
|
+
client_id: Optional[str],
|
|
466
|
+
callback_port: Optional[int],
|
|
467
|
+
scopes: tuple[str, ...],
|
|
441
468
|
workspace: Optional[str],
|
|
442
469
|
set_current: bool,
|
|
443
470
|
readonly: bool,
|
|
@@ -464,6 +491,10 @@ def login(
|
|
|
464
491
|
url=url,
|
|
465
492
|
api_key=api_key,
|
|
466
493
|
web_url=web_url,
|
|
494
|
+
auth_mode=auth_mode,
|
|
495
|
+
client_id=client_id,
|
|
496
|
+
callback_port=callback_port,
|
|
497
|
+
scopes=scopes,
|
|
467
498
|
workspace=workspace,
|
|
468
499
|
set_current=set_current,
|
|
469
500
|
readonly=readonly,
|
|
@@ -486,6 +517,7 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
|
486
517
|
from .profiles import ProfileConfig
|
|
487
518
|
|
|
488
519
|
cfg = ProfileConfig.load()
|
|
520
|
+
removed_profiles: list[str] = []
|
|
489
521
|
|
|
490
522
|
if remove_all:
|
|
491
523
|
if not force:
|
|
@@ -497,6 +529,7 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
|
497
529
|
return
|
|
498
530
|
|
|
499
531
|
# Clear all profiles
|
|
532
|
+
removed_profiles = list(cfg.profiles)
|
|
500
533
|
cfg.profiles.clear()
|
|
501
534
|
cfg.current_profile = None
|
|
502
535
|
cfg.save()
|
|
@@ -517,6 +550,7 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
|
517
550
|
return
|
|
518
551
|
|
|
519
552
|
cfg.delete_profile(profile)
|
|
553
|
+
removed_profiles = [profile]
|
|
520
554
|
cfg.save()
|
|
521
555
|
click.echo(f"✓ Profile '{profile}' removed.")
|
|
522
556
|
|
|
@@ -536,12 +570,20 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
|
536
570
|
return
|
|
537
571
|
|
|
538
572
|
cfg.delete_profile(current)
|
|
573
|
+
removed_profiles = [current]
|
|
539
574
|
cfg.save()
|
|
540
575
|
click.echo(f"✓ Profile '{current}' removed.")
|
|
541
576
|
if cfg.current_profile:
|
|
542
577
|
click.echo(f" Current profile is now: {cfg.current_profile}")
|
|
543
578
|
|
|
544
579
|
# Also clean up legacy keyring entries
|
|
580
|
+
try:
|
|
581
|
+
from .pkce import delete_pkce_credentials
|
|
582
|
+
|
|
583
|
+
for removed_profile in removed_profiles:
|
|
584
|
+
delete_pkce_credentials(removed_profile)
|
|
585
|
+
except Exception:
|
|
586
|
+
pass
|
|
545
587
|
try:
|
|
546
588
|
keyring.delete_password("systemlink-cli", "SYSTEMLINK_API_KEY")
|
|
547
589
|
except Exception:
|
|
@@ -606,7 +648,10 @@ def info(format: str, skip_health: bool, debug: bool) -> None:
|
|
|
606
648
|
elif platform_info.get("server_reachable") is False:
|
|
607
649
|
status = "✗ Server unreachable"
|
|
608
650
|
elif platform_info.get("auth_valid") is False:
|
|
609
|
-
|
|
651
|
+
credential_name = (
|
|
652
|
+
"Bearer token" if active_profile and active_profile.auth_mode == "pkce" else "API key"
|
|
653
|
+
)
|
|
654
|
+
status = f"✗ {credential_name} unauthorized"
|
|
610
655
|
else:
|
|
611
656
|
status = "✓ Connected"
|
|
612
657
|
|
|
@@ -14,6 +14,8 @@ import click
|
|
|
14
14
|
|
|
15
15
|
from .utils import ExitCodes
|
|
16
16
|
|
|
17
|
+
DEFAULT_STREAMABLE_HTTP_PORT = 8765
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
def _slcli_exe() -> str:
|
|
19
21
|
"""Return the absolute path to the slcli executable, or 'slcli' as a fallback."""
|
|
@@ -26,7 +28,7 @@ def _vscode_server_entry(slcli_exe: str) -> Dict[str, Any]:
|
|
|
26
28
|
return {
|
|
27
29
|
"type": "stdio",
|
|
28
30
|
"command": slcli_exe,
|
|
29
|
-
"args": ["mcp", "serve"],
|
|
31
|
+
"args": ["mcp", "serve", "--transport", "stdio"],
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
|
|
@@ -34,7 +36,7 @@ def _claude_server_entry(slcli_exe: str) -> Dict[str, Any]:
|
|
|
34
36
|
"""Return the Claude Desktop / Cursor MCP server JSON object for slcli."""
|
|
35
37
|
return {
|
|
36
38
|
"command": slcli_exe,
|
|
37
|
-
"args": ["mcp", "serve"],
|
|
39
|
+
"args": ["mcp", "serve", "--transport", "stdio"],
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
|
|
@@ -150,7 +152,7 @@ def register_mcp_commands(cli: Any) -> None:
|
|
|
150
152
|
"--transport",
|
|
151
153
|
"-T",
|
|
152
154
|
type=click.Choice(["stdio", "streamable-http"]),
|
|
153
|
-
default="
|
|
155
|
+
default="streamable-http",
|
|
154
156
|
show_default=True,
|
|
155
157
|
help=(
|
|
156
158
|
"Transport layer: 'stdio' for AI client integration, "
|
|
@@ -160,7 +162,7 @@ def register_mcp_commands(cli: Any) -> None:
|
|
|
160
162
|
@click.option(
|
|
161
163
|
"--port",
|
|
162
164
|
"-p",
|
|
163
|
-
default=
|
|
165
|
+
default=DEFAULT_STREAMABLE_HTTP_PORT,
|
|
164
166
|
show_default=True,
|
|
165
167
|
help="Port to bind to (streamable HTTP transport only).",
|
|
166
168
|
)
|
|
@@ -173,36 +175,35 @@ def register_mcp_commands(cli: Any) -> None:
|
|
|
173
175
|
def serve(transport: str, port: int, host: str) -> None:
|
|
174
176
|
"""Start the MCP server.
|
|
175
177
|
|
|
176
|
-
Defaults to
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
any client that prefers a persistent HTTP connection.
|
|
178
|
+
Defaults to streamable HTTP on localhost — useful for the MCP Inspector,
|
|
179
|
+
browser-based tooling, or any client that prefers a persistent HTTP connection.
|
|
180
|
+
Use stdio explicitly for AI clients that launch the server as a subprocess.
|
|
180
181
|
|
|
181
182
|
\b
|
|
182
|
-
|
|
183
|
+
Streamable HTTP (default on http://127.0.0.1:8765/mcp):
|
|
183
184
|
slcli mcp serve
|
|
185
|
+
slcli mcp serve --transport streamable-http --host 0.0.0.0 --port 9000
|
|
184
186
|
|
|
185
187
|
\b
|
|
186
|
-
|
|
187
|
-
slcli mcp serve --transport
|
|
188
|
-
slcli mcp serve --transport streamable-http --host 0.0.0.0 --port 9000
|
|
188
|
+
Stdio (for direct AI client integration):
|
|
189
|
+
slcli mcp serve --transport stdio
|
|
189
190
|
|
|
190
191
|
\b
|
|
191
192
|
Test with the MCP Inspector (Streamable HTTP):
|
|
192
|
-
slcli mcp serve
|
|
193
|
+
slcli mcp serve
|
|
193
194
|
npx @modelcontextprotocol/inspector
|
|
194
|
-
# connect to http://127.0.0.1:
|
|
195
|
+
# connect to http://127.0.0.1:8765/mcp (transport: Streamable HTTP)
|
|
195
196
|
"""
|
|
196
197
|
try:
|
|
197
198
|
from .mcp_server import main as run_mcp_server
|
|
198
|
-
from .mcp_server import
|
|
199
|
+
from .mcp_server import run_streamable_http
|
|
199
200
|
except ImportError:
|
|
200
201
|
click.echo(
|
|
201
202
|
"✗ The 'mcp' package is not installed.\n"
|
|
202
|
-
" Install it with: pip install 'mcp>=
|
|
203
|
-
" Or in a Poetry project: poetry add mcp\n"
|
|
203
|
+
" Install it with: pip install 'mcp>=2,<3'\n"
|
|
204
|
+
" Or in a Poetry project: poetry add 'mcp>=2,<3'\n"
|
|
204
205
|
" If slcli was installed with pipx:\n"
|
|
205
|
-
" pipx runpip systemlink-cli install 'mcp>=
|
|
206
|
+
" pipx runpip systemlink-cli install 'mcp>=2,<3'\n",
|
|
206
207
|
err=True,
|
|
207
208
|
)
|
|
208
209
|
sys.exit(ExitCodes.GENERAL_ERROR)
|
|
@@ -210,9 +211,6 @@ def register_mcp_commands(cli: Any) -> None:
|
|
|
210
211
|
if transport == "stdio":
|
|
211
212
|
run_mcp_server()
|
|
212
213
|
else:
|
|
213
|
-
mcp_server.settings.host = host
|
|
214
|
-
mcp_server.settings.port = port
|
|
215
|
-
|
|
216
214
|
click.echo(
|
|
217
215
|
"✓ slcli MCP server starting in streamable HTTP mode on "
|
|
218
216
|
f"http://{host}:{port}/mcp"
|
|
@@ -224,7 +222,7 @@ def register_mcp_commands(cli: Any) -> None:
|
|
|
224
222
|
click.echo(" Press Ctrl+C to stop.\n")
|
|
225
223
|
|
|
226
224
|
try:
|
|
227
|
-
|
|
225
|
+
run_streamable_http(host=host, port=port)
|
|
228
226
|
except KeyboardInterrupt:
|
|
229
227
|
click.echo("\nslcli MCP server stopped")
|
|
230
228
|
sys.exit(0)
|