refactor: move broker dto conversions out of wf_sources_mcp
This commit is contained in:
@@ -325,6 +325,9 @@ implementation state.
|
|||||||
models, file store, and conversion helpers now live in
|
models, file store, and conversion helpers now live in
|
||||||
`wf_sources_mcp.source_registry`, with `wf_mcp.source_registry` retained
|
`wf_sources_mcp.source_registry`, with `wf_mcp.source_registry` retained
|
||||||
as a compatibility shim.
|
as a compatibility shim.
|
||||||
|
Broker DTO construction moved out of `wf_sources_mcp`: source-provider
|
||||||
|
modules use structural legacy inputs only, while `wf_mcp.source_registry`
|
||||||
|
owns helpers that construct `ConnectionConfig`.
|
||||||
Third `wf_sources_mcp` slice complete: upstream MCP catalog/discovery DTOs
|
Third `wf_sources_mcp` slice complete: upstream MCP catalog/discovery DTOs
|
||||||
and catalog snapshot dumping now live in `wf_sources_mcp.catalog`, with
|
and catalog snapshot dumping now live in `wf_sources_mcp.catalog`, with
|
||||||
`wf_mcp.capabilities` and `wf_mcp.catalog.models` retained as shims.
|
`wf_mcp.capabilities` and `wf_mcp.catalog.models` retained as shims.
|
||||||
|
|||||||
+486
@@ -0,0 +1,486 @@
|
|||||||
|
# MCP Broker DTO Conversion Boundary Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Remove `wf_mcp.ConnectionConfig` imports from `wf_sources_mcp` by making source-side conversion structural and moving broker DTO construction to `wf_mcp`.
|
||||||
|
|
||||||
|
**Architecture:** `wf_sources_mcp` owns source-provider DTOs and may adapt legacy connection-like inputs by structural protocol. It must not construct broker runtime DTOs. `wf_mcp.source_registry` becomes the compatibility/broker conversion module: it re-exports canonical source registry models and owns helpers that produce `ConnectionConfig`.
|
||||||
|
|
||||||
|
**Tech Stack:** Python 3.14, structural `Protocol`, dataclasses in tests, pytest, ruff, basedpyright, AST import guards.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why This Slice Exists
|
||||||
|
|
||||||
|
After the ID cleanup, the remaining real `wf_sources_mcp -> wf_mcp` dependencies are legacy broker DTO conversions:
|
||||||
|
|
||||||
|
- `wf_sources_mcp.connections` imports `wf_mcp.broker.models.ConnectionConfig` for type checking.
|
||||||
|
- `wf_sources_mcp.source_registry` imports `wf_mcp.models.ConnectionConfig` at runtime to construct broker configs.
|
||||||
|
|
||||||
|
Those conversions are compatibility edges. `wf_sources_mcp` should own MCP source objects, not broker runtime objects.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hard Boundaries
|
||||||
|
|
||||||
|
- Do not move `ConnectionConfig` itself in this slice.
|
||||||
|
- Do not change `ConnectionConfig` fields or behavior.
|
||||||
|
- Do not change source registry JSON shape.
|
||||||
|
- Do not change `McpSourceRegistryEntry` fields.
|
||||||
|
- Do not remove `wf_mcp.source_registry` compatibility imports.
|
||||||
|
- Do not import `wf_mcp` from any `src/wf_sources_mcp/*.py` file.
|
||||||
|
- Keep existing broker call sites working.
|
||||||
|
- Do not commit unless the caller explicitly asks for a commit.
|
||||||
|
|
||||||
|
## File Map
|
||||||
|
|
||||||
|
- Modify `src/wf_sources_mcp/connections.py`: replace `ConnectionConfig` type import with structural protocol.
|
||||||
|
- Modify `src/wf_sources_mcp/source_registry.py`: keep canonical models/store and input-only conversion; remove broker DTO construction helpers.
|
||||||
|
- Modify `src/wf_mcp/source_registry.py`: re-export canonical models/store and define broker DTO construction helpers.
|
||||||
|
- Modify `src/wf_mcp/broker/config.py`: import broker DTO construction helpers from `wf_mcp.source_registry`.
|
||||||
|
- Modify `src/wf_mcp/broker/service/connection_service.py`: import broker DTO construction helpers from `wf_mcp.source_registry`.
|
||||||
|
- Modify tests:
|
||||||
|
- `tests/wf_sources_mcp/test_connections.py`
|
||||||
|
- `tests/wf_sources_mcp/test_source_registry.py`
|
||||||
|
- `tests/wf_mcp/test_source_registry.py`
|
||||||
|
- `tests/wf_sources_mcp/test_import_direction_guard.py`
|
||||||
|
- Modify docs:
|
||||||
|
- `docs/current_roadmap.md`
|
||||||
|
- `docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md`
|
||||||
|
- Move this plan to `docs/historical/superpowers/plans/` after implementation is verified.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Make Legacy Connection Input Structural in `connections.py`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/wf_sources_mcp/connections.py`
|
||||||
|
- Modify: `tests/wf_sources_mcp/test_connections.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace `ConnectionConfig` type import with protocols**
|
||||||
|
|
||||||
|
In `src/wf_sources_mcp/connections.py`, remove:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from wf_mcp.broker.models import ConnectionConfig
|
||||||
|
```
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyConnectionConfigLike(Protocol):
|
||||||
|
"""Structural shape needed from legacy broker connection configs."""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
server: str
|
||||||
|
account: str
|
||||||
|
enabled: bool
|
||||||
|
metadata: Mapping[str, object]
|
||||||
|
```
|
||||||
|
|
||||||
|
Update signatures:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def mcp_source_connection_from_connection_config(
|
||||||
|
connection: LegacyConnectionConfigLike,
|
||||||
|
) -> McpSourceConnection:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def _transport_from_connection_metadata(
|
||||||
|
connection: LegacyConnectionConfigLike,
|
||||||
|
) -> SourceTransport | None:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `LegacyConnectionConfigLike` to `__all__`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Preserve metadata handling**
|
||||||
|
|
||||||
|
Keep the exact current metadata logic:
|
||||||
|
|
||||||
|
- dict `metadata["transport"]` supports `{"kind": "stdio"}` and `{"kind": "http"}`;
|
||||||
|
- flat `"stdio"` metadata supports `command`, `args`, `env`, `cwd`;
|
||||||
|
- flat HTTP aliases support `url`, `headers`;
|
||||||
|
- missing transport returns `None`;
|
||||||
|
- unsupported transport raises `ValueError`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add a no-`wf_mcp` fake test**
|
||||||
|
|
||||||
|
In `tests/wf_sources_mcp/test_connections.py`, add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class _LegacyConnectionLike:
|
||||||
|
id: str
|
||||||
|
server: str
|
||||||
|
account: str
|
||||||
|
enabled: bool = True
|
||||||
|
metadata: dict[str, object] = field(default_factory=dict)
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a test using `_LegacyConnectionLike` with stdio metadata and assert conversion works. Existing tests that import `wf_mcp.broker.models.ConnectionConfig` may remain temporarily, but at least one canonical test must prove the converter does not need the concrete broker class.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run connection tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_sources_mcp/test_connections.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Move Broker DTO Construction Helpers to `wf_mcp.source_registry`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/wf_sources_mcp/source_registry.py`
|
||||||
|
- Modify: `src/wf_mcp/source_registry.py`
|
||||||
|
- Modify: `tests/wf_sources_mcp/test_source_registry.py`
|
||||||
|
- Modify: `tests/wf_mcp/test_source_registry.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Keep input-only seed conversion in `wf_sources_mcp.source_registry`**
|
||||||
|
|
||||||
|
In `src/wf_sources_mcp/source_registry.py`, remove all `wf_mcp.models.ConnectionConfig` imports.
|
||||||
|
|
||||||
|
Add a structural protocol:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyConnectionConfigLike(Protocol):
|
||||||
|
id: str
|
||||||
|
server: str
|
||||||
|
account: str
|
||||||
|
enabled: bool
|
||||||
|
metadata: Mapping[str, object]
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep `connection_config_to_registry_entry(connection: LegacyConnectionConfigLike) -> McpSourceRegistryEntry`.
|
||||||
|
|
||||||
|
This helper is allowed to stay in `wf_sources_mcp` because it converts legacy-shaped input into canonical source registry state and does not construct broker DTOs.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Remove broker-output helpers from canonical `__all__`**
|
||||||
|
|
||||||
|
Remove these functions from `src/wf_sources_mcp/source_registry.py`:
|
||||||
|
|
||||||
|
- `registry_entry_to_connection_config`
|
||||||
|
- `workflow_mcp_source_to_connection_config`
|
||||||
|
|
||||||
|
Remove them from `__all__`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Define broker-output helpers in `src/wf_mcp/source_registry.py`**
|
||||||
|
|
||||||
|
Replace the pure shim with a mixed compatibility module:
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""Compatibility and broker conversion helpers for MCP source registry state.
|
||||||
|
|
||||||
|
Canonical registry models and stores live in `wf_sources_mcp.source_registry`.
|
||||||
|
Helpers that construct `ConnectionConfig` stay here because `ConnectionConfig`
|
||||||
|
is a broker compatibility DTO.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from wf_sources_mcp.source_registry import (
|
||||||
|
FileSourceRegistryStore,
|
||||||
|
HttpSourceTransport,
|
||||||
|
McpSourceRegistryEntry,
|
||||||
|
SourceRegistryFile,
|
||||||
|
SourceRegistryStore,
|
||||||
|
SourceTransport,
|
||||||
|
StdioSourceTransport,
|
||||||
|
connection_config_to_registry_entry,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .models import ConnectionConfig
|
||||||
|
|
||||||
|
|
||||||
|
def registry_entry_to_connection_config(entry: McpSourceRegistryEntry) -> ConnectionConfig:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def workflow_mcp_source_to_connection_config(source: object) -> ConnectionConfig:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Move the current implementations of `registry_entry_to_connection_config` and `workflow_mcp_source_to_connection_config` from `wf_sources_mcp.source_registry` into this module unchanged except for imports.
|
||||||
|
|
||||||
|
Ensure `__all__` includes all re-exported canonical names plus the broker-output helpers.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Move broker-output tests to `wf_mcp`**
|
||||||
|
|
||||||
|
In `tests/wf_sources_mcp/test_source_registry.py`:
|
||||||
|
|
||||||
|
- keep tests for `McpSourceRegistryEntry`;
|
||||||
|
- keep tests for `SourceRegistryFile`;
|
||||||
|
- keep tests for `FileSourceRegistryStore`;
|
||||||
|
- keep tests for `connection_config_to_registry_entry`, but use a local `_LegacyConnectionLike` dataclass instead of importing `wf_mcp.models.ConnectionConfig`;
|
||||||
|
- remove tests for `registry_entry_to_connection_config`;
|
||||||
|
- remove tests for `workflow_mcp_source_to_connection_config` if present.
|
||||||
|
|
||||||
|
In `tests/wf_mcp/test_source_registry.py`:
|
||||||
|
|
||||||
|
- keep or add tests for `registry_entry_to_connection_config`;
|
||||||
|
- keep or add tests for `workflow_mcp_source_to_connection_config`;
|
||||||
|
- assert these helpers return concrete `wf_mcp.models.ConnectionConfig`.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run source registry tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_sources_mcp/test_source_registry.py tests/wf_mcp/test_source_registry.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Update Broker Call Sites to Import Broker Conversions
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/wf_mcp/broker/config.py`
|
||||||
|
- Modify: `src/wf_mcp/broker/service/connection_service.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update imports in broker config**
|
||||||
|
|
||||||
|
In `src/wf_mcp/broker/config.py`, import canonical models/stores from `wf_sources_mcp.source_registry` only when they are pure source registry objects.
|
||||||
|
|
||||||
|
Import broker-output helper from `wf_mcp.source_registry`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_mcp.source_registry import workflow_mcp_source_to_connection_config
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not import `workflow_mcp_source_to_connection_config` from `wf_sources_mcp.source_registry`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update imports in connection service**
|
||||||
|
|
||||||
|
In `src/wf_mcp/broker/service/connection_service.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_mcp.source_registry import (
|
||||||
|
connection_config_to_registry_entry,
|
||||||
|
registry_entry_to_connection_config,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
`connection_config_to_registry_entry` may be re-exported from `wf_mcp.source_registry` for consistency at broker call sites, even though canonical implementation remains in `wf_sources_mcp.source_registry`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run broker source registry tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_mcp/test_workflow_config_bridge.py tests/wf_mcp/service/test_connection_service.py tests/wf_mcp/test_source_registry.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Add Import Guards for Broker DTO Dependencies
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `tests/wf_sources_mcp/test_import_direction_guard.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add forbidden broker DTO import test**
|
||||||
|
|
||||||
|
Append:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_wf_sources_mcp_does_not_import_wf_mcp_broker_dtos() -> None:
|
||||||
|
root = Path(__file__).resolve().parents[2] / "src" / "wf_sources_mcp"
|
||||||
|
forbidden = {"wf_mcp.models", "wf_mcp.broker.models"}
|
||||||
|
violations: list[str] = []
|
||||||
|
|
||||||
|
for py_file in sorted(root.rglob("*.py")):
|
||||||
|
rel = py_file.relative_to(root.parent)
|
||||||
|
module = str(rel.with_suffix("")).replace("/", ".").replace("\\", ".")
|
||||||
|
tree = ast.parse(py_file.read_text(encoding="utf-8"), filename=str(py_file))
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.ImportFrom) and node.module in forbidden:
|
||||||
|
violations.append(f"{module}:{node.lineno}: from {node.module} import ...")
|
||||||
|
elif isinstance(node, ast.Import):
|
||||||
|
for alias in node.names:
|
||||||
|
if alias.name in forbidden:
|
||||||
|
violations.append(f"{module}:{node.lineno}: import {alias.name}")
|
||||||
|
|
||||||
|
assert violations == [], (
|
||||||
|
"wf_sources_mcp still imports wf_mcp broker DTO modules:\n"
|
||||||
|
+ "\n".join(f" {violation}" for violation in violations)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run import guards**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_sources_mcp/test_import_direction_guard.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Update Package Root Exports
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/wf_sources_mcp/__init__.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Remove broker-output helper exports**
|
||||||
|
|
||||||
|
Remove these names from `wf_sources_mcp.__all__` and `__getattr__` routing:
|
||||||
|
|
||||||
|
- `registry_entry_to_connection_config`
|
||||||
|
- `workflow_mcp_source_to_connection_config`
|
||||||
|
|
||||||
|
Keep:
|
||||||
|
|
||||||
|
- `connection_config_to_registry_entry`
|
||||||
|
- `mcp_source_connection_from_connection_config`
|
||||||
|
|
||||||
|
Those remaining helpers must be structural/input-only and must not import `wf_mcp`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run package export tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_sources_mcp tests/wf_mcp/test_compat_imports.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: pass.
|
||||||
|
|
||||||
|
If tests expect broker-output helpers at the `wf_sources_mcp` package root, update them to import from `wf_mcp.source_registry`. Do not keep broker-output helpers at the source-provider package root.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Update Docs and Archive Plan
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/current_roadmap.md`
|
||||||
|
- Modify: `docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md`
|
||||||
|
- Move: `docs/superpowers/plans/2026-06-08-wf-sources-mcp-broker-dto-conversion-boundary.md` to `docs/historical/superpowers/plans/2026-06-08-wf-sources-mcp-broker-dto-conversion-boundary.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update roadmap**
|
||||||
|
|
||||||
|
Under the `wf_sources_mcp` cleanup section, add:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Broker DTO construction moved out of `wf_sources_mcp`: source-provider
|
||||||
|
modules use structural legacy inputs only, while `wf_mcp.source_registry`
|
||||||
|
owns helpers that construct `ConnectionConfig`.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update long-lived boundary spec**
|
||||||
|
|
||||||
|
In `docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md`, add a completed numbered item after the source ID item:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
23. Complete: broker DTO construction removed from `wf_sources_mcp`.
|
||||||
|
`wf_sources_mcp` accepts legacy-shaped inputs structurally, while
|
||||||
|
`wf_mcp.source_registry` owns helpers that construct `ConnectionConfig`.
|
||||||
|
```
|
||||||
|
|
||||||
|
Renumber the pending broad item if needed.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Archive the plan**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git mv docs/superpowers/plans/2026-06-08-wf-sources-mcp-broker-dto-conversion-boundary.md docs/historical/superpowers/plans/2026-06-08-wf-sources-mcp-broker-dto-conversion-boundary.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `git status --short` shows the plan under `docs/historical/...`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7: Final Verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- No code edits unless verification finds a real issue.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run focused tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_sources_mcp tests/wf_mcp/test_source_registry.py tests/wf_mcp/test_workflow_config_bridge.py tests/wf_mcp/service/test_connection_service.py tests/wf_mcp/test_compat_imports.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all selected tests pass.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run import dependency check**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n "^from wf_mcp|^import wf_mcp|wf_mcp\\." src/wf_sources_mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: no production-code imports. A package docstring mention may remain only if it explains a compatibility concern, but prefer updating stale wording if it no longer applies.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run lint**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run ruff check src/wf_sources_mcp src/wf_mcp/source_registry.py src/wf_mcp/broker/config.py src/wf_mcp/broker/service/connection_service.py tests/wf_sources_mcp tests/wf_mcp/test_source_registry.py tests/wf_mcp/test_compat_imports.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `All checks passed!`
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run typecheck**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run basedpyright --level error src/wf_sources_mcp src/wf_mcp/source_registry.py src/wf_mcp/broker/config.py src/wf_mcp/broker/service/connection_service.py tests/wf_sources_mcp tests/wf_mcp/test_source_registry.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `0 errors, 0 warnings, 0 notes`.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Check whitespace**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: no whitespace errors. CRLF warnings on Windows are acceptable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expected Final Report
|
||||||
|
|
||||||
|
The implementer should report:
|
||||||
|
|
||||||
|
- Files created, modified, and moved.
|
||||||
|
- Exact verification commands and pass/fail output.
|
||||||
|
- Confirmation that `src/wf_sources_mcp` has no production imports from `wf_mcp`.
|
||||||
|
- Confirmation that broker DTO construction helpers live in `wf_mcp.source_registry`.
|
||||||
|
- Confirmation that source-provider conversion helpers use structural protocols.
|
||||||
|
- Any deviations from this plan.
|
||||||
|
|
||||||
|
Do not claim "full suite passed" unless the full suite was actually run.
|
||||||
@@ -154,7 +154,10 @@ First slices should move leaf modules only and leave `wf_mcp` re-export shims:
|
|||||||
22. Complete: MCP source ID validation and reserved source IDs are canonical in
|
22. Complete: MCP source ID validation and reserved source IDs are canonical in
|
||||||
`wf_sources_mcp.ids`; legacy `wf_mcp.connections` / `wf_mcp.shared.names`
|
`wf_sources_mcp.ids`; legacy `wf_mcp.connections` / `wf_mcp.shared.names`
|
||||||
remain compatibility consumers.
|
remain compatibility consumers.
|
||||||
23. Upstream transport/discovery/session services.
|
23. Complete: broker DTO construction removed from `wf_sources_mcp`.
|
||||||
|
`wf_sources_mcp` accepts legacy-shaped inputs structurally, while
|
||||||
|
`wf_mcp.source_registry` owns helpers that construct `ConnectionConfig`.
|
||||||
|
24. Upstream transport/discovery/session services.
|
||||||
|
|
||||||
Each slice should add import-direction tests so the new source-provider package
|
Each slice should add import-direction tests so the new source-provider package
|
||||||
does not depend on `wf_mcp.workflow_surface`, `wf_mcp.admin_surface`,
|
does not depend on `wf_mcp.workflow_surface`, `wf_mcp.admin_surface`,
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ from wf_sources_mcp.runtime import McpRuntimePool, PersistentSessionFactory
|
|||||||
from wf_sources_mcp.sdk import McpSdkAdapter
|
from wf_sources_mcp.sdk import McpSdkAdapter
|
||||||
from wf_sources_mcp.source_registry import (
|
from wf_sources_mcp.source_registry import (
|
||||||
FileSourceRegistryStore,
|
FileSourceRegistryStore,
|
||||||
workflow_mcp_source_to_connection_config,
|
|
||||||
)
|
)
|
||||||
from wf_sources_mcp.storage import FileAuthStore, FileCatalogStore, FileStore
|
from wf_sources_mcp.storage import FileAuthStore, FileCatalogStore, FileStore
|
||||||
|
|
||||||
from ..control import BrokerConfigFile, ConnectionConfigFile
|
from ..control import BrokerConfigFile, ConnectionConfigFile
|
||||||
from ..models import BrokerConfig
|
from ..models import BrokerConfig
|
||||||
|
from ..source_registry import workflow_mcp_source_to_connection_config
|
||||||
from .models import BrokerStoreRoots
|
from .models import BrokerStoreRoots
|
||||||
from .service import WfMcpService
|
from .service import WfMcpService
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from wf_mcp.source_registry import (
|
||||||
|
connection_config_to_registry_entry,
|
||||||
|
registry_entry_to_connection_config,
|
||||||
|
)
|
||||||
from wf_sources_mcp.source_registry import (
|
from wf_sources_mcp.source_registry import (
|
||||||
SourceRegistryFile,
|
SourceRegistryFile,
|
||||||
SourceRegistryStore,
|
SourceRegistryStore,
|
||||||
connection_config_to_registry_entry,
|
|
||||||
registry_entry_to_connection_config,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from ...connections import ConnectionRegistry, parse_connection_id
|
from ...connections import ConnectionRegistry, parse_connection_id
|
||||||
@@ -81,7 +83,7 @@ class ConnectionService:
|
|||||||
continue
|
continue
|
||||||
if connection.id in registry_entries:
|
if connection.id in registry_entries:
|
||||||
continue
|
continue
|
||||||
seeded = connection_config_to_registry_entry(connection)
|
seeded = connection_config_to_registry_entry(connection) # type: ignore[arg-type]
|
||||||
registry_entries[seeded.id] = seeded
|
registry_entries[seeded.id] = seeded
|
||||||
registry_changed = True
|
registry_changed = True
|
||||||
self.events.record_kind(
|
self.events.record_kind(
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
"""Compatibility shim for MCP source registry models.
|
"""Compatibility and broker conversion helpers for MCP source registry state.
|
||||||
|
|
||||||
Canonical implementation lives in `wf_sources_mcp.source_registry`.
|
Canonical registry models and stores live in `wf_sources_mcp.source_registry`.
|
||||||
|
Helpers that construct `ConnectionConfig` stay here because `ConnectionConfig`
|
||||||
|
is a broker compatibility DTO.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -8,19 +10,92 @@ from __future__ import annotations
|
|||||||
from wf_sources_mcp.source_registry import (
|
from wf_sources_mcp.source_registry import (
|
||||||
FileSourceRegistryStore,
|
FileSourceRegistryStore,
|
||||||
HttpSourceTransport,
|
HttpSourceTransport,
|
||||||
|
LegacyConnectionConfigLike,
|
||||||
McpSourceRegistryEntry,
|
McpSourceRegistryEntry,
|
||||||
SourceRegistryFile,
|
SourceRegistryFile,
|
||||||
SourceRegistryStore,
|
SourceRegistryStore,
|
||||||
SourceTransport,
|
SourceTransport,
|
||||||
StdioSourceTransport,
|
StdioSourceTransport,
|
||||||
connection_config_to_registry_entry,
|
connection_config_to_registry_entry,
|
||||||
registry_entry_to_connection_config,
|
|
||||||
workflow_mcp_source_to_connection_config,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .models import ConnectionConfig
|
||||||
|
|
||||||
|
|
||||||
|
def registry_entry_to_connection_config(
|
||||||
|
entry: McpSourceRegistryEntry,
|
||||||
|
) -> ConnectionConfig:
|
||||||
|
"""Convert a registry entry to a broker connection config."""
|
||||||
|
return ConnectionConfig(
|
||||||
|
id=entry.id,
|
||||||
|
server=entry.provider,
|
||||||
|
account=entry.account,
|
||||||
|
enabled=entry.enabled,
|
||||||
|
metadata={
|
||||||
|
**entry.metadata,
|
||||||
|
"auth_ref": entry.auth_ref,
|
||||||
|
"profile": entry.profile,
|
||||||
|
"transport": entry.transport.model_dump(mode="json"),
|
||||||
|
"source_registry": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def workflow_mcp_source_to_connection_config(source: object) -> ConnectionConfig:
|
||||||
|
"""Convert neutral wf_config MCP source config into a broker connection.
|
||||||
|
|
||||||
|
This helper stays in the broker compatibility package because its output is
|
||||||
|
the temporary `ConnectionConfig` DTO. The input is intentionally typed as
|
||||||
|
object to avoid making `wf_config` part of this package's import graph.
|
||||||
|
"""
|
||||||
|
if getattr(source, "kind", None) != "mcp":
|
||||||
|
raise ValueError("expected wf_config MCP source")
|
||||||
|
for field in ("id", "provider", "account", "enabled", "ownership", "transport"):
|
||||||
|
if getattr(source, field, None) is None:
|
||||||
|
raise ValueError(f"wf_config MCP source missing required field: {field}")
|
||||||
|
transport = getattr(source, "transport")
|
||||||
|
metadata = dict(getattr(source, "metadata", {}))
|
||||||
|
if transport.kind == "stdio":
|
||||||
|
metadata.update(
|
||||||
|
{
|
||||||
|
"transport": "stdio",
|
||||||
|
"command": transport.command,
|
||||||
|
"args": list(transport.args),
|
||||||
|
"env": dict(transport.env),
|
||||||
|
"source_registry": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif transport.kind == "http":
|
||||||
|
metadata.update(
|
||||||
|
{
|
||||||
|
"transport": "streamable_http",
|
||||||
|
"url": str(transport.url),
|
||||||
|
"headers": dict(transport.headers),
|
||||||
|
"source_registry": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"unsupported wf_config MCP transport {transport.kind!r}")
|
||||||
|
profile = getattr(source, "profile", None)
|
||||||
|
if profile is not None:
|
||||||
|
metadata["profile"] = profile
|
||||||
|
auth_ref = getattr(source, "auth_ref", None)
|
||||||
|
if auth_ref is not None:
|
||||||
|
metadata["auth_ref"] = auth_ref
|
||||||
|
return ConnectionConfig(
|
||||||
|
id=getattr(source, "id"),
|
||||||
|
server=getattr(source, "provider"),
|
||||||
|
account=getattr(source, "account"),
|
||||||
|
enabled=getattr(source, "enabled"),
|
||||||
|
metadata=metadata,
|
||||||
|
source_config_ownership=getattr(source, "ownership"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"FileSourceRegistryStore",
|
"FileSourceRegistryStore",
|
||||||
"HttpSourceTransport",
|
"HttpSourceTransport",
|
||||||
|
"LegacyConnectionConfigLike",
|
||||||
"McpSourceRegistryEntry",
|
"McpSourceRegistryEntry",
|
||||||
"SourceRegistryFile",
|
"SourceRegistryFile",
|
||||||
"SourceRegistryStore",
|
"SourceRegistryStore",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from .auth import (
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .connections import (
|
from .connections import (
|
||||||
|
LegacyConnectionConfigLike,
|
||||||
McpSourceConnection,
|
McpSourceConnection,
|
||||||
mcp_source_connection_from_connection_config,
|
mcp_source_connection_from_connection_config,
|
||||||
mcp_source_connection_from_registry_entry,
|
mcp_source_connection_from_registry_entry,
|
||||||
@@ -32,8 +33,6 @@ if TYPE_CHECKING:
|
|||||||
SourceRegistryFile,
|
SourceRegistryFile,
|
||||||
SourceRegistryStore,
|
SourceRegistryStore,
|
||||||
connection_config_to_registry_entry,
|
connection_config_to_registry_entry,
|
||||||
registry_entry_to_connection_config,
|
|
||||||
workflow_mcp_source_to_connection_config,
|
|
||||||
)
|
)
|
||||||
from .transports import (
|
from .transports import (
|
||||||
HttpSourceTransport,
|
HttpSourceTransport,
|
||||||
@@ -46,6 +45,7 @@ __all__ = [
|
|||||||
"AuthRecord",
|
"AuthRecord",
|
||||||
"DiscoveredConnectionCapabilities",
|
"DiscoveredConnectionCapabilities",
|
||||||
"LegacyAdapterRef",
|
"LegacyAdapterRef",
|
||||||
|
"LegacyConnectionConfigLike",
|
||||||
"SourceAdapterRef",
|
"SourceAdapterRef",
|
||||||
"FileSourceRegistryStore",
|
"FileSourceRegistryStore",
|
||||||
"HttpSourceTransport",
|
"HttpSourceTransport",
|
||||||
@@ -67,7 +67,6 @@ __all__ = [
|
|||||||
"mcp_source_connection_from_registry_entry",
|
"mcp_source_connection_from_registry_entry",
|
||||||
"model_from_schema",
|
"model_from_schema",
|
||||||
"neutral_auth_from_mcp",
|
"neutral_auth_from_mcp",
|
||||||
"registry_entry_to_connection_config",
|
|
||||||
"require_adapter",
|
"require_adapter",
|
||||||
"specs_from_discovered_tools",
|
"specs_from_discovered_tools",
|
||||||
"tool_call_completed_event",
|
"tool_call_completed_event",
|
||||||
@@ -75,7 +74,6 @@ __all__ = [
|
|||||||
"ToolWrapperEvent",
|
"ToolWrapperEvent",
|
||||||
"ToolWrapperEventSink",
|
"ToolWrapperEventSink",
|
||||||
"wrap_discovered_tool",
|
"wrap_discovered_tool",
|
||||||
"workflow_mcp_source_to_connection_config",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -90,6 +88,7 @@ def __getattr__(name: str) -> object:
|
|||||||
|
|
||||||
return getattr(adapters, name)
|
return getattr(adapters, name)
|
||||||
if name in {
|
if name in {
|
||||||
|
"LegacyConnectionConfigLike",
|
||||||
"McpSourceConnection",
|
"McpSourceConnection",
|
||||||
"mcp_source_connection_from_connection_config",
|
"mcp_source_connection_from_connection_config",
|
||||||
"mcp_source_connection_from_registry_entry",
|
"mcp_source_connection_from_registry_entry",
|
||||||
@@ -103,8 +102,6 @@ def __getattr__(name: str) -> object:
|
|||||||
"SourceRegistryFile",
|
"SourceRegistryFile",
|
||||||
"SourceRegistryStore",
|
"SourceRegistryStore",
|
||||||
"connection_config_to_registry_entry",
|
"connection_config_to_registry_entry",
|
||||||
"registry_entry_to_connection_config",
|
|
||||||
"workflow_mcp_source_to_connection_config",
|
|
||||||
}:
|
}:
|
||||||
from . import source_registry
|
from . import source_registry
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import TYPE_CHECKING
|
from typing import cast
|
||||||
|
|
||||||
from wf_sources_mcp.ids import parse_connection_id
|
from wf_sources_mcp.ids import parse_connection_id
|
||||||
from wf_sources_mcp.source_registry import McpSourceRegistryEntry
|
from wf_sources_mcp.source_registry import (
|
||||||
|
LegacyConnectionConfigLike,
|
||||||
|
McpSourceRegistryEntry,
|
||||||
|
)
|
||||||
from wf_sources_mcp.transports import (
|
from wf_sources_mcp.transports import (
|
||||||
HttpSourceTransport,
|
HttpSourceTransport,
|
||||||
SourceTransport,
|
SourceTransport,
|
||||||
StdioSourceTransport,
|
StdioSourceTransport,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from wf_mcp.broker.models import ConnectionConfig
|
|
||||||
|
|
||||||
_FLAT_HTTP_TRANSPORTS = {"http", "streamable-http", "streamable_http", "sse"}
|
_FLAT_HTTP_TRANSPORTS = {"http", "streamable-http", "streamable_http", "sse"}
|
||||||
_CONNECTION_METADATA_KEYS = {
|
_CONNECTION_METADATA_KEYS = {
|
||||||
"transport",
|
"transport",
|
||||||
@@ -85,7 +85,7 @@ def mcp_source_connection_from_registry_entry(
|
|||||||
|
|
||||||
|
|
||||||
def mcp_source_connection_from_connection_config(
|
def mcp_source_connection_from_connection_config(
|
||||||
connection: ConnectionConfig,
|
connection: LegacyConnectionConfigLike,
|
||||||
) -> McpSourceConnection:
|
) -> McpSourceConnection:
|
||||||
"""Adapt legacy broker connection config into typed source shape.
|
"""Adapt legacy broker connection config into typed source shape.
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ def mcp_source_connection_from_connection_config(
|
|||||||
|
|
||||||
|
|
||||||
def _transport_from_connection_metadata(
|
def _transport_from_connection_metadata(
|
||||||
connection: ConnectionConfig,
|
connection: LegacyConnectionConfigLike,
|
||||||
) -> SourceTransport | None:
|
) -> SourceTransport | None:
|
||||||
transport = connection.metadata.get("transport")
|
transport = connection.metadata.get("transport")
|
||||||
if isinstance(transport, dict):
|
if isinstance(transport, dict):
|
||||||
@@ -128,12 +128,16 @@ def _transport_from_connection_metadata(
|
|||||||
)
|
)
|
||||||
if isinstance(transport, str):
|
if isinstance(transport, str):
|
||||||
if transport == "stdio":
|
if transport == "stdio":
|
||||||
|
args_raw = connection.metadata.get("args", ())
|
||||||
|
env_raw = connection.metadata.get("env", {})
|
||||||
return StdioSourceTransport(
|
return StdioSourceTransport(
|
||||||
command=str(connection.metadata.get("command", "")),
|
command=str(connection.metadata.get("command", "")),
|
||||||
args=tuple(str(arg) for arg in connection.metadata.get("args", ())),
|
args=tuple(str(arg) for arg in cast("tuple[object, ...]", args_raw)),
|
||||||
env={
|
env={
|
||||||
str(key): str(value)
|
str(key): str(value)
|
||||||
for key, value in dict(connection.metadata.get("env", {})).items()
|
for key, value in cast(
|
||||||
|
"dict[str, object]", env_raw
|
||||||
|
).items()
|
||||||
},
|
},
|
||||||
cwd=(
|
cwd=(
|
||||||
str(connection.metadata["cwd"])
|
str(connection.metadata["cwd"])
|
||||||
@@ -143,12 +147,13 @@ def _transport_from_connection_metadata(
|
|||||||
)
|
)
|
||||||
if transport in _FLAT_HTTP_TRANSPORTS:
|
if transport in _FLAT_HTTP_TRANSPORTS:
|
||||||
url = connection.metadata.get("url", "")
|
url = connection.metadata.get("url", "")
|
||||||
|
headers_raw = connection.metadata.get("headers", {})
|
||||||
return HttpSourceTransport(
|
return HttpSourceTransport(
|
||||||
url=url if isinstance(url, str) else str(url), # type: ignore[arg-type]
|
url=url if isinstance(url, str) else str(url), # type: ignore[arg-type]
|
||||||
headers={
|
headers={
|
||||||
str(key): str(value)
|
str(key): str(value)
|
||||||
for key, value in dict(
|
for key, value in cast(
|
||||||
connection.metadata.get("headers", {})
|
"dict[str, object]", headers_raw
|
||||||
).items()
|
).items()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -159,6 +164,7 @@ def _transport_from_connection_metadata(
|
|||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"LegacyConnectionConfigLike",
|
||||||
"McpSourceConnection",
|
"McpSourceConnection",
|
||||||
"mcp_source_connection_from_connection_config",
|
"mcp_source_connection_from_connection_config",
|
||||||
"mcp_source_connection_from_registry_entry",
|
"mcp_source_connection_from_registry_entry",
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
"""MCP upstream-source registry models and conversion helpers.
|
"""MCP upstream-source registry models and conversion helpers.
|
||||||
|
|
||||||
This module is canonical for MCP-as-source desired registry state. The temporary
|
This module is canonical for MCP-as-source desired registry state. Legacy
|
||||||
runtime dependency on `wf_mcp.models.ConnectionConfig` remains until broker
|
broker DTO conversions have moved to `wf_mcp.source_registry`. This module
|
||||||
runtime DTOs move out of the compatibility MCP facade.
|
accepts legacy-shaped inputs structurally and must not construct broker
|
||||||
|
runtime DTOs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Literal, Protocol
|
from typing import Literal, Protocol, cast
|
||||||
|
|
||||||
from pydantic import Field, field_validator, model_validator
|
from pydantic import Field, field_validator, model_validator
|
||||||
|
|
||||||
@@ -27,9 +29,6 @@ from wf_sources_mcp.transports import (
|
|||||||
StdioSourceTransport,
|
StdioSourceTransport,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from wf_mcp.models import ConnectionConfig
|
|
||||||
|
|
||||||
_FLAT_HTTP_TRANSPORTS = {"http", "streamable-http", "streamable_http", "sse"}
|
_FLAT_HTTP_TRANSPORTS = {"http", "streamable-http", "streamable_http", "sse"}
|
||||||
_TRANSPORT_METADATA_KEYS = {
|
_TRANSPORT_METADATA_KEYS = {
|
||||||
"transport",
|
"transport",
|
||||||
@@ -45,6 +44,16 @@ _TRANSPORT_METADATA_KEYS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyConnectionConfigLike(Protocol):
|
||||||
|
"""Structural shape needed from legacy broker connection configs."""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
server: str
|
||||||
|
account: str
|
||||||
|
enabled: bool
|
||||||
|
metadata: Mapping[str, object]
|
||||||
|
|
||||||
|
|
||||||
class McpSourceRegistryEntry(SourceRegistryBaseModel):
|
class McpSourceRegistryEntry(SourceRegistryBaseModel):
|
||||||
"""Desired MCP source configuration persisted by server-owned mutation."""
|
"""Desired MCP source configuration persisted by server-owned mutation."""
|
||||||
|
|
||||||
@@ -107,29 +116,8 @@ class FileSourceRegistryStore:
|
|||||||
self._delegate.save_registry(registry)
|
self._delegate.save_registry(registry)
|
||||||
|
|
||||||
|
|
||||||
def registry_entry_to_connection_config(
|
|
||||||
entry: McpSourceRegistryEntry,
|
|
||||||
) -> ConnectionConfig:
|
|
||||||
"""Convert a registry entry to a broker connection config."""
|
|
||||||
from wf_mcp.models import ConnectionConfig
|
|
||||||
|
|
||||||
return ConnectionConfig(
|
|
||||||
id=entry.id,
|
|
||||||
server=entry.provider,
|
|
||||||
account=entry.account,
|
|
||||||
enabled=entry.enabled,
|
|
||||||
metadata={
|
|
||||||
**entry.metadata,
|
|
||||||
"auth_ref": entry.auth_ref,
|
|
||||||
"profile": entry.profile,
|
|
||||||
"transport": entry.transport.model_dump(mode="json"),
|
|
||||||
"source_registry": True,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def connection_config_to_registry_entry(
|
def connection_config_to_registry_entry(
|
||||||
connection: ConnectionConfig,
|
connection: LegacyConnectionConfigLike,
|
||||||
) -> McpSourceRegistryEntry:
|
) -> McpSourceRegistryEntry:
|
||||||
"""Materialize a seed config connection into persisted registry state.
|
"""Materialize a seed config connection into persisted registry state.
|
||||||
|
|
||||||
@@ -142,19 +130,22 @@ def connection_config_to_registry_entry(
|
|||||||
pass
|
pass
|
||||||
elif isinstance(transport, str):
|
elif isinstance(transport, str):
|
||||||
if transport == "stdio":
|
if transport == "stdio":
|
||||||
|
args_raw = connection.metadata.get("args", ())
|
||||||
|
env_raw = connection.metadata.get("env", {})
|
||||||
transport = {
|
transport = {
|
||||||
"kind": "stdio",
|
"kind": "stdio",
|
||||||
"command": connection.metadata.get("command", ""),
|
"command": connection.metadata.get("command", ""),
|
||||||
"args": list(connection.metadata.get("args", [])),
|
"args": list(cast("tuple[object, ...]", args_raw)),
|
||||||
"env": dict(connection.metadata.get("env", {})),
|
"env": dict(cast("dict[str, object]", env_raw)),
|
||||||
"cwd": connection.metadata.get("cwd"),
|
"cwd": connection.metadata.get("cwd"),
|
||||||
}
|
}
|
||||||
elif transport in _FLAT_HTTP_TRANSPORTS:
|
elif transport in _FLAT_HTTP_TRANSPORTS:
|
||||||
legacy_transport_value = transport
|
legacy_transport_value = transport
|
||||||
|
headers_raw = connection.metadata.get("headers", {})
|
||||||
transport = {
|
transport = {
|
||||||
"kind": "http",
|
"kind": "http",
|
||||||
"url": connection.metadata.get("url", ""),
|
"url": connection.metadata.get("url", ""),
|
||||||
"headers": dict(connection.metadata.get("headers", {})),
|
"headers": dict(cast("dict[str, object]", headers_raw)),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -188,68 +179,14 @@ def connection_config_to_registry_entry(
|
|||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
def workflow_mcp_source_to_connection_config(source: object) -> ConnectionConfig:
|
|
||||||
"""Convert neutral wf_config MCP source config into a broker connection.
|
|
||||||
|
|
||||||
This adapter remains source-provider code even though the output is the
|
|
||||||
temporary broker runtime DTO. The input is intentionally typed as object to
|
|
||||||
avoid making `wf_config` part of this package's import graph.
|
|
||||||
"""
|
|
||||||
from wf_mcp.models import ConnectionConfig
|
|
||||||
|
|
||||||
if getattr(source, "kind", None) != "mcp":
|
|
||||||
raise ValueError("expected wf_config MCP source")
|
|
||||||
for field in ("id", "provider", "account", "enabled", "ownership", "transport"):
|
|
||||||
if getattr(source, field, None) is None:
|
|
||||||
raise ValueError(f"wf_config MCP source missing required field: {field}")
|
|
||||||
transport = getattr(source, "transport")
|
|
||||||
metadata = dict(getattr(source, "metadata", {}))
|
|
||||||
if transport.kind == "stdio":
|
|
||||||
metadata.update(
|
|
||||||
{
|
|
||||||
"transport": "stdio",
|
|
||||||
"command": transport.command,
|
|
||||||
"args": list(transport.args),
|
|
||||||
"env": dict(transport.env),
|
|
||||||
"source_registry": False,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
elif transport.kind == "http":
|
|
||||||
metadata.update(
|
|
||||||
{
|
|
||||||
"transport": "streamable_http",
|
|
||||||
"url": str(transport.url),
|
|
||||||
"headers": dict(transport.headers),
|
|
||||||
"source_registry": False,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"unsupported wf_config MCP transport {transport.kind!r}")
|
|
||||||
profile = getattr(source, "profile", None)
|
|
||||||
if profile is not None:
|
|
||||||
metadata["profile"] = profile
|
|
||||||
auth_ref = getattr(source, "auth_ref", None)
|
|
||||||
if auth_ref is not None:
|
|
||||||
metadata["auth_ref"] = auth_ref
|
|
||||||
return ConnectionConfig(
|
|
||||||
id=getattr(source, "id"),
|
|
||||||
server=getattr(source, "provider"),
|
|
||||||
account=getattr(source, "account"),
|
|
||||||
enabled=getattr(source, "enabled"),
|
|
||||||
metadata=metadata,
|
|
||||||
source_config_ownership=getattr(source, "ownership"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"FileSourceRegistryStore",
|
"FileSourceRegistryStore",
|
||||||
"HttpSourceTransport",
|
"HttpSourceTransport",
|
||||||
|
"LegacyConnectionConfigLike",
|
||||||
"McpSourceRegistryEntry",
|
"McpSourceRegistryEntry",
|
||||||
"SourceRegistryFile",
|
"SourceRegistryFile",
|
||||||
"SourceRegistryStore",
|
"SourceRegistryStore",
|
||||||
"SourceTransport",
|
"SourceTransport",
|
||||||
"StdioSourceTransport",
|
"StdioSourceTransport",
|
||||||
"connection_config_to_registry_entry",
|
"connection_config_to_registry_entry",
|
||||||
"registry_entry_to_connection_config",
|
|
||||||
"workflow_mcp_source_to_connection_config",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from wf_mcp.source_registry import (
|
|||||||
StdioSourceTransport,
|
StdioSourceTransport,
|
||||||
connection_config_to_registry_entry,
|
connection_config_to_registry_entry,
|
||||||
registry_entry_to_connection_config,
|
registry_entry_to_connection_config,
|
||||||
|
workflow_mcp_source_to_connection_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -120,6 +121,13 @@ def test_registry_entry_to_connection_config_disabled_entry() -> None:
|
|||||||
assert config.enabled is False
|
assert config.enabled is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_registry_entry_to_connection_config_returns_broker_dto() -> None:
|
||||||
|
entry = _entry()
|
||||||
|
config = registry_entry_to_connection_config(entry)
|
||||||
|
|
||||||
|
assert isinstance(config, ConnectionConfig)
|
||||||
|
|
||||||
|
|
||||||
def test_connection_config_to_registry_entry_preserves_transport_metadata() -> None:
|
def test_connection_config_to_registry_entry_preserves_transport_metadata() -> None:
|
||||||
connection = ConnectionConfig(
|
connection = ConnectionConfig(
|
||||||
id="github.work",
|
id="github.work",
|
||||||
@@ -134,7 +142,7 @@ def test_connection_config_to_registry_entry_preserves_transport_metadata() -> N
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
entry = connection_config_to_registry_entry(connection)
|
entry = connection_config_to_registry_entry(connection) # type: ignore[arg-type]
|
||||||
|
|
||||||
assert entry.id == "github.work"
|
assert entry.id == "github.work"
|
||||||
assert entry.provider == "github"
|
assert entry.provider == "github"
|
||||||
@@ -160,7 +168,7 @@ def test_connection_config_to_registry_entry_accepts_flat_stdio_metadata() -> No
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
entry = connection_config_to_registry_entry(connection)
|
entry = connection_config_to_registry_entry(connection) # type: ignore[arg-type]
|
||||||
|
|
||||||
assert entry.transport.kind == "stdio"
|
assert entry.transport.kind == "stdio"
|
||||||
assert isinstance(entry.transport, StdioSourceTransport)
|
assert isinstance(entry.transport, StdioSourceTransport)
|
||||||
@@ -183,7 +191,7 @@ def test_connection_config_to_registry_entry_accepts_flat_http_metadata() -> Non
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
entry = connection_config_to_registry_entry(connection)
|
entry = connection_config_to_registry_entry(connection) # type: ignore[arg-type]
|
||||||
|
|
||||||
assert entry.transport.kind == "http"
|
assert entry.transport.kind == "http"
|
||||||
assert isinstance(entry.transport, HttpSourceTransport)
|
assert isinstance(entry.transport, HttpSourceTransport)
|
||||||
@@ -196,4 +204,82 @@ def test_connection_config_to_registry_entry_requires_transport_metadata() -> No
|
|||||||
connection = ConnectionConfig(id="github.work", server="github", account="work")
|
connection = ConnectionConfig(id="github.work", server="github", account="work")
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="requires metadata.transport"):
|
with pytest.raises(ValueError, match="requires metadata.transport"):
|
||||||
connection_config_to_registry_entry(connection)
|
connection_config_to_registry_entry(connection) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
|
class _McpSource:
|
||||||
|
"""Minimal mock for wf_config MCP source objects."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.kind = "mcp"
|
||||||
|
self.id = "github.work"
|
||||||
|
self.provider = "github"
|
||||||
|
self.account = "work"
|
||||||
|
self.enabled = True
|
||||||
|
self.ownership = "seed"
|
||||||
|
self.transport = StdioSourceTransport(command="npx", args=("-y", "server"))
|
||||||
|
self.metadata: dict[str, object] = {"region": "us"}
|
||||||
|
self.profile: str | None = "engineering"
|
||||||
|
self.auth_ref: str | None = "github.token"
|
||||||
|
|
||||||
|
|
||||||
|
class _McpSourceHttp:
|
||||||
|
"""Minimal mock for wf_config MCP source with HTTP transport."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.kind = "mcp"
|
||||||
|
self.id = "ctx.default"
|
||||||
|
self.provider = "ctx"
|
||||||
|
self.account = "default"
|
||||||
|
self.enabled = True
|
||||||
|
self.ownership = "locked"
|
||||||
|
self.transport = HttpSourceTransport(url="http://127.0.0.1:3000/sse") # type: ignore[arg-type]
|
||||||
|
self.metadata: dict[str, object] = {}
|
||||||
|
self.profile: str | None = None
|
||||||
|
self.auth_ref: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_mcp_source_to_connection_config_stdio() -> None:
|
||||||
|
source = _McpSource()
|
||||||
|
config = workflow_mcp_source_to_connection_config(source)
|
||||||
|
|
||||||
|
assert isinstance(config, ConnectionConfig)
|
||||||
|
assert config.id == "github.work"
|
||||||
|
assert config.server == "github"
|
||||||
|
assert config.account == "work"
|
||||||
|
assert config.enabled is True
|
||||||
|
assert config.source_config_ownership == "seed"
|
||||||
|
assert config.metadata["transport"] == "stdio"
|
||||||
|
assert config.metadata["command"] == "npx"
|
||||||
|
assert config.metadata["args"] == ["-y", "server"]
|
||||||
|
assert config.metadata["profile"] == "engineering"
|
||||||
|
assert config.metadata["auth_ref"] == "github.token"
|
||||||
|
assert config.metadata["region"] == "us"
|
||||||
|
assert config.metadata["source_registry"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_mcp_source_to_connection_config_http() -> None:
|
||||||
|
source = _McpSourceHttp()
|
||||||
|
config = workflow_mcp_source_to_connection_config(source)
|
||||||
|
|
||||||
|
assert isinstance(config, ConnectionConfig)
|
||||||
|
assert config.id == "ctx.default"
|
||||||
|
assert config.metadata["transport"] == "streamable_http"
|
||||||
|
assert config.metadata["url"] == "http://127.0.0.1:3000/sse"
|
||||||
|
assert config.metadata["source_registry"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_mcp_source_to_connection_config_rejects_non_mcp() -> None:
|
||||||
|
source = _McpSource()
|
||||||
|
source.kind = "stdlib"
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="expected wf_config MCP source"):
|
||||||
|
workflow_mcp_source_to_connection_config(source)
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_mcp_source_to_connection_config_rejects_missing_fields() -> None:
|
||||||
|
source = _McpSource()
|
||||||
|
source.id = None # type: ignore[assignment]
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="missing required field"):
|
||||||
|
workflow_mcp_source_to_connection_config(source)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from collections.abc import Mapping
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -23,6 +25,15 @@ from wf_sources_mcp.transports import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class _LegacyConnectionLike:
|
||||||
|
id: str
|
||||||
|
server: str
|
||||||
|
account: str
|
||||||
|
enabled: bool = True
|
||||||
|
metadata: Mapping[str, object] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
def test_stdio_source_transport_is_typed() -> None:
|
def test_stdio_source_transport_is_typed() -> None:
|
||||||
transport = StdioSourceTransport(
|
transport = StdioSourceTransport(
|
||||||
command="uvx",
|
command="uvx",
|
||||||
@@ -130,7 +141,7 @@ def test_mcp_source_connection_from_legacy_connection_config_stdio() -> None:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
connection = mcp_source_connection_from_connection_config(legacy)
|
connection = mcp_source_connection_from_connection_config(legacy) # type: ignore[arg-type]
|
||||||
|
|
||||||
assert connection.id == "github.work"
|
assert connection.id == "github.work"
|
||||||
assert connection.provider == "github"
|
assert connection.provider == "github"
|
||||||
@@ -159,7 +170,7 @@ def test_mcp_source_connection_from_legacy_connection_config_http() -> None:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
connection = mcp_source_connection_from_connection_config(legacy)
|
connection = mcp_source_connection_from_connection_config(legacy) # type: ignore[arg-type]
|
||||||
|
|
||||||
assert isinstance(connection.transport, HttpSourceTransport)
|
assert isinstance(connection.transport, HttpSourceTransport)
|
||||||
assert str(connection.transport.url) == "http://127.0.0.1:8000/mcp"
|
assert str(connection.transport.url) == "http://127.0.0.1:8000/mcp"
|
||||||
@@ -176,11 +187,45 @@ def test_mcp_source_connection_accepts_missing_legacy_transport_until_open() ->
|
|||||||
metadata={},
|
metadata={},
|
||||||
)
|
)
|
||||||
|
|
||||||
connection = mcp_source_connection_from_connection_config(legacy)
|
connection = mcp_source_connection_from_connection_config(legacy) # type: ignore[arg-type]
|
||||||
|
|
||||||
assert connection.transport is None
|
assert connection.transport is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_structural_legacy_connection_stdio_without_wf_mcp() -> None:
|
||||||
|
legacy = _LegacyConnectionLike(
|
||||||
|
id="github.work",
|
||||||
|
server="github",
|
||||||
|
account="work",
|
||||||
|
enabled=False,
|
||||||
|
metadata={
|
||||||
|
"transport": "stdio",
|
||||||
|
"command": "uvx",
|
||||||
|
"args": ["github-mcp"],
|
||||||
|
"env": {"A": "B"},
|
||||||
|
"cwd": "C:/repo",
|
||||||
|
"auth_ref": "github.token",
|
||||||
|
"profile": "engineering",
|
||||||
|
"source_registry": True,
|
||||||
|
"team": "platform",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
connection = mcp_source_connection_from_connection_config(legacy)
|
||||||
|
|
||||||
|
assert connection.id == "github.work"
|
||||||
|
assert connection.provider == "github"
|
||||||
|
assert connection.account == "work"
|
||||||
|
assert connection.enabled is False
|
||||||
|
assert connection.profile == "engineering"
|
||||||
|
assert connection.auth_ref == "github.token"
|
||||||
|
assert connection.metadata == {"source_registry": True, "team": "platform"}
|
||||||
|
assert isinstance(connection.transport, StdioSourceTransport)
|
||||||
|
assert connection.transport.command == "uvx"
|
||||||
|
assert connection.transport.args == ("github-mcp",)
|
||||||
|
assert connection.transport.cwd == "C:/repo"
|
||||||
|
|
||||||
|
|
||||||
class _ConnectionLike(Protocol):
|
class _ConnectionLike(Protocol):
|
||||||
id: str
|
id: str
|
||||||
auth_ref: str | None
|
auth_ref: str | None
|
||||||
|
|||||||
@@ -270,3 +270,26 @@ def test_wf_sources_mcp_does_not_import_old_wf_mcp_id_modules() -> None:
|
|||||||
"wf_sources_mcp still imports old wf_mcp source ID modules:\n"
|
"wf_sources_mcp still imports old wf_mcp source ID modules:\n"
|
||||||
+ "\n".join(f" {violation}" for violation in violations)
|
+ "\n".join(f" {violation}" for violation in violations)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_wf_sources_mcp_does_not_import_wf_mcp_broker_dtos() -> None:
|
||||||
|
root = Path(__file__).resolve().parents[2] / "src" / "wf_sources_mcp"
|
||||||
|
forbidden = {"wf_mcp.models", "wf_mcp.broker.models"}
|
||||||
|
violations: list[str] = []
|
||||||
|
|
||||||
|
for py_file in sorted(root.rglob("*.py")):
|
||||||
|
rel = py_file.relative_to(root.parent)
|
||||||
|
module = str(rel.with_suffix("")).replace("/", ".").replace("\\", ".")
|
||||||
|
tree = ast.parse(py_file.read_text(encoding="utf-8"), filename=str(py_file))
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.ImportFrom) and node.module in forbidden:
|
||||||
|
violations.append(f"{module}:{node.lineno}: from {node.module} import ...")
|
||||||
|
elif isinstance(node, ast.Import):
|
||||||
|
for alias in node.names:
|
||||||
|
if alias.name in forbidden:
|
||||||
|
violations.append(f"{module}:{node.lineno}: import {alias.name}")
|
||||||
|
|
||||||
|
assert violations == [], (
|
||||||
|
"wf_sources_mcp still imports wf_mcp broker DTO modules:\n"
|
||||||
|
+ "\n".join(f" {violation}" for violation in violations)
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from wf_mcp.models import ConnectionConfig
|
|
||||||
from wf_sources_mcp.source_registry import (
|
from wf_sources_mcp.source_registry import (
|
||||||
FileSourceRegistryStore,
|
FileSourceRegistryStore,
|
||||||
HttpSourceTransport,
|
HttpSourceTransport,
|
||||||
@@ -12,10 +13,18 @@ from wf_sources_mcp.source_registry import (
|
|||||||
SourceRegistryFile,
|
SourceRegistryFile,
|
||||||
StdioSourceTransport,
|
StdioSourceTransport,
|
||||||
connection_config_to_registry_entry,
|
connection_config_to_registry_entry,
|
||||||
registry_entry_to_connection_config,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class _LegacyConnectionLike:
|
||||||
|
id: str
|
||||||
|
server: str
|
||||||
|
account: str
|
||||||
|
enabled: bool = True
|
||||||
|
metadata: Mapping[str, object] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
def _entry(source_id: str = "github.work") -> McpSourceRegistryEntry:
|
def _entry(source_id: str = "github.work") -> McpSourceRegistryEntry:
|
||||||
return McpSourceRegistryEntry(
|
return McpSourceRegistryEntry(
|
||||||
id=source_id,
|
id=source_id,
|
||||||
@@ -83,45 +92,8 @@ def test_file_source_registry_store_validates_loaded_registry(tmp_path: Path) ->
|
|||||||
store.load_registry()
|
store.load_registry()
|
||||||
|
|
||||||
|
|
||||||
def test_registry_entry_to_connection_config_preserves_identity() -> None:
|
|
||||||
entry = _entry()
|
|
||||||
config = registry_entry_to_connection_config(entry)
|
|
||||||
|
|
||||||
assert config.id == "github.work"
|
|
||||||
assert config.server == "github"
|
|
||||||
assert config.account == "work"
|
|
||||||
assert config.enabled is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_registry_entry_to_connection_config_preserves_transport_metadata() -> None:
|
|
||||||
entry = _entry()
|
|
||||||
entry.auth_ref = "github.work.auth"
|
|
||||||
config = registry_entry_to_connection_config(entry)
|
|
||||||
|
|
||||||
assert config.metadata["auth_ref"] == "github.work.auth"
|
|
||||||
assert config.metadata["profile"] is None
|
|
||||||
assert config.metadata["transport"]["kind"] == "stdio"
|
|
||||||
assert config.metadata["transport"]["command"] == "npx"
|
|
||||||
assert config.metadata["source_registry"] is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_registry_entry_to_connection_config_preserves_user_metadata() -> None:
|
|
||||||
entry = _entry()
|
|
||||||
config = registry_entry_to_connection_config(entry)
|
|
||||||
|
|
||||||
assert config.metadata["purpose"] == "tests"
|
|
||||||
|
|
||||||
|
|
||||||
def test_registry_entry_to_connection_config_disabled_entry() -> None:
|
|
||||||
entry = _entry()
|
|
||||||
entry.enabled = False
|
|
||||||
config = registry_entry_to_connection_config(entry)
|
|
||||||
|
|
||||||
assert config.enabled is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_connection_config_to_registry_entry_preserves_transport_metadata() -> None:
|
def test_connection_config_to_registry_entry_preserves_transport_metadata() -> None:
|
||||||
connection = ConnectionConfig(
|
connection = _LegacyConnectionLike(
|
||||||
id="github.work",
|
id="github.work",
|
||||||
server="github",
|
server="github",
|
||||||
account="work",
|
account="work",
|
||||||
@@ -147,7 +119,7 @@ def test_connection_config_to_registry_entry_preserves_transport_metadata() -> N
|
|||||||
|
|
||||||
|
|
||||||
def test_connection_config_to_registry_entry_accepts_flat_stdio_metadata() -> None:
|
def test_connection_config_to_registry_entry_accepts_flat_stdio_metadata() -> None:
|
||||||
connection = ConnectionConfig(
|
connection = _LegacyConnectionLike(
|
||||||
id="github.work",
|
id="github.work",
|
||||||
server="github",
|
server="github",
|
||||||
account="work",
|
account="work",
|
||||||
@@ -171,7 +143,7 @@ def test_connection_config_to_registry_entry_accepts_flat_stdio_metadata() -> No
|
|||||||
|
|
||||||
|
|
||||||
def test_connection_config_to_registry_entry_accepts_flat_http_metadata() -> None:
|
def test_connection_config_to_registry_entry_accepts_flat_http_metadata() -> None:
|
||||||
connection = ConnectionConfig(
|
connection = _LegacyConnectionLike(
|
||||||
id="context7.default",
|
id="context7.default",
|
||||||
server="context7",
|
server="context7",
|
||||||
account="default",
|
account="default",
|
||||||
@@ -193,7 +165,7 @@ def test_connection_config_to_registry_entry_accepts_flat_http_metadata() -> Non
|
|||||||
|
|
||||||
|
|
||||||
def test_connection_config_to_registry_entry_requires_transport_metadata() -> None:
|
def test_connection_config_to_registry_entry_requires_transport_metadata() -> None:
|
||||||
connection = ConnectionConfig(id="github.work", server="github", account="work")
|
connection = _LegacyConnectionLike(id="github.work", server="github", account="work")
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="requires metadata.transport"):
|
with pytest.raises(ValueError, match="requires metadata.transport"):
|
||||||
connection_config_to_registry_entry(connection)
|
connection_config_to_registry_entry(connection)
|
||||||
|
|||||||
Reference in New Issue
Block a user