concrete typing from pydantic
This commit is contained in:
@@ -495,3 +495,35 @@ If the client is iterating with an LLM, prefer a draft workspace:
|
|||||||
|
|
||||||
This avoids resending the whole draft object every turn. The saved artifact is
|
This avoids resending the whole draft object every turn. The saved artifact is
|
||||||
still immutable and should be deployed through the normal deployment path.
|
still immutable and should be deployed through the normal deployment path.
|
||||||
|
|
||||||
|
Concrete MCP sequence:
|
||||||
|
|
||||||
|
1. `wf.workflow.list_capabilities` with a query such as `echo`.
|
||||||
|
2. `wf.workflow.call_capability` with a small payload to verify the selected
|
||||||
|
capability behaves as expected.
|
||||||
|
3. `wf.workflow.create_minimal_draft_workspace` with a `request` object that
|
||||||
|
contains schemas, `input_map`, and `output_map`.
|
||||||
|
4. `wf.workflow.get_draft_workspace` with `include_draft=true` if the client
|
||||||
|
needs to inspect the full current draft.
|
||||||
|
5. `wf.workflow.patch_draft_workspace` with the current `revision`.
|
||||||
|
6. `wf.workflow.create_artifact_from_workspace` after validation is clean.
|
||||||
|
7. `wf.workflow.save_deployment`, then `validate_deployment`, then
|
||||||
|
`run_deployment`.
|
||||||
|
|
||||||
|
`create_artifact_from_workspace` also uses a `request` object:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"workspace_id": "echo_draft",
|
||||||
|
"artifact_id": "echo",
|
||||||
|
"version": 1,
|
||||||
|
"title": "Echo",
|
||||||
|
"outcomes": ["completed"],
|
||||||
|
"source_bindings": {
|
||||||
|
"demo": "demo.personal",
|
||||||
|
"wf.std": "wf.std"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -356,3 +356,68 @@ resending the full draft each turn.
|
|||||||
Workspace patches are optimistic-concurrency guarded. Pass the current
|
Workspace patches are optimistic-concurrency guarded. Pass the current
|
||||||
`revision` from `get_draft_workspace`; a stale revision returns
|
`revision` from `get_draft_workspace`; a stale revision returns
|
||||||
`revision_conflict` and leaves the stored draft unchanged.
|
`revision_conflict` and leaves the stored draft unchanged.
|
||||||
|
|
||||||
|
Workspace mutation tools use a single `request` object in MCP Inspector. That
|
||||||
|
keeps the form grouped and lets the schema describe fields like
|
||||||
|
`input_schema`, `output_map`, and `error_message_source`.
|
||||||
|
|
||||||
|
Minimal example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"workspace_id": "echo_draft",
|
||||||
|
"name": "echo",
|
||||||
|
"capability_name": "demo.personal.echo_tool",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["text"]
|
||||||
|
},
|
||||||
|
"state_schema": {
|
||||||
|
"fields": {
|
||||||
|
"echoed": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"echoed": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["echoed"]
|
||||||
|
},
|
||||||
|
"input_map": {
|
||||||
|
"input.text": "text"
|
||||||
|
},
|
||||||
|
"output_map": {
|
||||||
|
"echoed": "state.echoed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Patch example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"workspace_id": "echo_draft",
|
||||||
|
"revision": 1,
|
||||||
|
"patch": [
|
||||||
|
{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/name",
|
||||||
|
"value": "echo_v2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -220,6 +220,10 @@ an `error` outcome for naive MCP wrappers only when `error_message_source` is
|
|||||||
provided or a state path can be derived from `output_map`. Provider-specific
|
provided or a state path can be derived from `output_map`. Provider-specific
|
||||||
error envelopes still belong in saved wrapper artifacts or follow-up patches.
|
error envelopes still belong in saved wrapper artifacts or follow-up patches.
|
||||||
|
|
||||||
|
In MCP Inspector, workspace mutation tools accept a single `request` object.
|
||||||
|
This is deliberate: the request object carries descriptions and validation for
|
||||||
|
the authoring envelope while raw JSON Schema fields remain plain JSON objects.
|
||||||
|
|
||||||
## Patching Drafts
|
## Patching Drafts
|
||||||
|
|
||||||
`patch_draft` accepts JSON Patch operations.
|
`patch_draft` accepts JSON Patch operations.
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
"""Shared workflow-surface literals used by generated draft helpers."""
|
||||||
|
|
||||||
|
DEFAULT_CALL_STEP_ID = "call"
|
||||||
|
DEFAULT_ERROR_STEP_ID = "tool_error"
|
||||||
|
DEFAULT_OK_OUTCOME = "ok"
|
||||||
|
DEFAULT_ERROR_OUTCOME = "error"
|
||||||
|
RUNTIME_ERROR_CAPABILITY = "wf.std.runtime_error"
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"DEFAULT_CALL_STEP_ID",
|
||||||
|
"DEFAULT_ERROR_OUTCOME",
|
||||||
|
"DEFAULT_ERROR_STEP_ID",
|
||||||
|
"DEFAULT_OK_OUTCOME",
|
||||||
|
"RUNTIME_ERROR_CAPABILITY",
|
||||||
|
]
|
||||||
@@ -35,6 +35,13 @@ from wf_core import RuntimeContext
|
|||||||
|
|
||||||
from ..events import make_event
|
from ..events import make_event
|
||||||
from ..models import RawWorkflowPlan
|
from ..models import RawWorkflowPlan
|
||||||
|
from .constants import (
|
||||||
|
DEFAULT_CALL_STEP_ID,
|
||||||
|
DEFAULT_ERROR_OUTCOME,
|
||||||
|
DEFAULT_ERROR_STEP_ID,
|
||||||
|
DEFAULT_OK_OUTCOME,
|
||||||
|
RUNTIME_ERROR_CAPABILITY,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..broker.service import WfMcpService
|
from ..broker.service import WfMcpService
|
||||||
@@ -423,33 +430,39 @@ class WorkflowSurfaceHandlers:
|
|||||||
title: str | None = None,
|
title: str | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Bootstrap the smallest patchable draft around one workflow capability."""
|
"""Bootstrap the smallest patchable draft around one workflow capability."""
|
||||||
outcomes = self._outcomes_for_capability(capability_name) or ("ok",)
|
outcomes = self._outcomes_for_capability(capability_name) or (
|
||||||
|
DEFAULT_OK_OUTCOME,
|
||||||
|
)
|
||||||
steps: dict[str, Any] = {
|
steps: dict[str, Any] = {
|
||||||
"call": {
|
DEFAULT_CALL_STEP_ID: {
|
||||||
"use": capability_name,
|
"use": capability_name,
|
||||||
"in": input_map,
|
"in": input_map,
|
||||||
"out": output_map,
|
"out": output_map,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
routes: dict[str, dict[str, str]] = {"call": {"ok": "__end__"}}
|
routes: dict[str, dict[str, str]] = {
|
||||||
|
DEFAULT_CALL_STEP_ID: {DEFAULT_OK_OUTCOME: "__end__"}
|
||||||
|
}
|
||||||
error_source = error_message_source or _first_state_path(output_map)
|
error_source = error_message_source or _first_state_path(output_map)
|
||||||
if "error" in outcomes and error_source is not None:
|
if DEFAULT_ERROR_OUTCOME in outcomes and error_source is not None:
|
||||||
# The bootstrapper cannot infer provider-specific error envelopes.
|
# The bootstrapper cannot infer provider-specific error envelopes.
|
||||||
# It only wires an error route when the caller gave, or output_map
|
# It only wires an error route when the caller gave, or output_map
|
||||||
# exposes, a concrete state path that can become a runtime message.
|
# exposes, a concrete state path that can become a runtime message.
|
||||||
steps["tool_error"] = {
|
steps[DEFAULT_ERROR_STEP_ID] = {
|
||||||
"use": "wf.std.runtime_error",
|
"use": RUNTIME_ERROR_CAPABILITY,
|
||||||
"in": {error_source: "message"},
|
"in": {error_source: "message"},
|
||||||
"out": {},
|
"out": {},
|
||||||
}
|
}
|
||||||
routes["call"]["error"] = "tool_error"
|
routes[DEFAULT_CALL_STEP_ID][DEFAULT_ERROR_OUTCOME] = (
|
||||||
routes["tool_error"] = {"ok": "__end__"}
|
DEFAULT_ERROR_STEP_ID
|
||||||
|
)
|
||||||
|
routes[DEFAULT_ERROR_STEP_ID] = {DEFAULT_OK_OUTCOME: "__end__"}
|
||||||
draft = {
|
draft = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"input_schema": input_schema,
|
"input_schema": input_schema,
|
||||||
"state_schema": state_schema,
|
"state_schema": state_schema,
|
||||||
"output_schema": output_schema,
|
"output_schema": output_schema,
|
||||||
"start": "call",
|
"start": DEFAULT_CALL_STEP_ID,
|
||||||
"steps": steps,
|
"steps": steps,
|
||||||
"routes": routes,
|
"routes": routes,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,46 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Literal
|
from typing import Annotated, Any, Literal
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from wf_artifacts import ArtifactKind
|
||||||
|
from wf_artifacts.draft_workspaces.models import WORKSPACE_ID_PATTERN
|
||||||
|
|
||||||
|
WorkspaceId = Annotated[
|
||||||
|
str,
|
||||||
|
Field(
|
||||||
|
pattern=WORKSPACE_ID_PATTERN,
|
||||||
|
description="Draft workspace id. Use letters, numbers, underscore, dot, or dash.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
JsonSchemaObject = Annotated[
|
||||||
|
dict[str, Any],
|
||||||
|
Field(
|
||||||
|
description=(
|
||||||
|
"JSON Schema object. Keep this as ordinary JSON; nested schema fields "
|
||||||
|
"are passed through unchanged."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
DraftPathMap = Annotated[
|
||||||
|
dict[str, str],
|
||||||
|
Field(
|
||||||
|
description=(
|
||||||
|
"Map local draft paths to workflow paths, for example "
|
||||||
|
"{'input.text': 'text'} or {'echoed': 'state.echoed'}."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
JsonPatchOperations = Annotated[
|
||||||
|
list[dict[str, Any]],
|
||||||
|
Field(description="RFC 6902 JSON Patch operations."),
|
||||||
|
]
|
||||||
|
SourceBindings = Annotated[
|
||||||
|
dict[str, str],
|
||||||
|
Field(description="Map logical source ids to concrete source ids."),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class CallCapabilityResult(BaseModel):
|
class CallCapabilityResult(BaseModel):
|
||||||
"""Inspector-visible response contract for testing one workflow capability."""
|
"""Inspector-visible response contract for testing one workflow capability."""
|
||||||
@@ -52,3 +89,66 @@ class DraftWorkspaceResult(BaseModel):
|
|||||||
default=None,
|
default=None,
|
||||||
description="Full draft document, only returned when requested.",
|
description="Full draft document, only returned when requested.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateDraftWorkspaceRequest(BaseModel):
|
||||||
|
"""Typed MCP request payload for creating a stored draft workspace."""
|
||||||
|
|
||||||
|
workspace_id: WorkspaceId
|
||||||
|
draft: dict[str, Any] = Field(description="WorkflowDraft JSON document.")
|
||||||
|
title: str | None = Field(default=None, description="Optional workspace title.")
|
||||||
|
|
||||||
|
|
||||||
|
class PatchDraftWorkspaceRequest(BaseModel):
|
||||||
|
"""Typed MCP request payload for revision-checked draft workspace patching."""
|
||||||
|
|
||||||
|
workspace_id: WorkspaceId
|
||||||
|
revision: int = Field(ge=1, description="Expected current workspace revision.")
|
||||||
|
patch: JsonPatchOperations
|
||||||
|
|
||||||
|
|
||||||
|
class CreateMinimalDraftWorkspaceRequest(BaseModel):
|
||||||
|
"""Typed MCP request payload for bootstrapping one-capability drafts."""
|
||||||
|
|
||||||
|
workspace_id: WorkspaceId
|
||||||
|
name: str = Field(description="Workflow draft name.")
|
||||||
|
capability_name: str = Field(
|
||||||
|
description="Workflow capability to call, such as demo.default.echo_tool."
|
||||||
|
)
|
||||||
|
input_schema: JsonSchemaObject
|
||||||
|
state_schema: JsonSchemaObject
|
||||||
|
output_schema: JsonSchemaObject
|
||||||
|
input_map: DraftPathMap
|
||||||
|
output_map: DraftPathMap
|
||||||
|
error_message_source: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description=(
|
||||||
|
"Optional state path used as runtime_error.message when the capability "
|
||||||
|
"has an error outcome, for example state.error_message."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
title: str | None = Field(default=None, description="Optional workspace title.")
|
||||||
|
|
||||||
|
|
||||||
|
class CreateArtifactFromWorkspaceRequest(BaseModel):
|
||||||
|
"""Typed MCP request payload for saving a draft workspace as an artifact."""
|
||||||
|
|
||||||
|
workspace_id: WorkspaceId
|
||||||
|
artifact_id: str = Field(description="Immutable artifact id to write.")
|
||||||
|
version: int = Field(ge=1, description="Artifact version to write.")
|
||||||
|
title: str = Field(description="Human-readable artifact title.")
|
||||||
|
outcomes: list[str] = Field(description="Artifact-level outcomes.")
|
||||||
|
kind: ArtifactKind = Field(default="workflow", description="Artifact kind.")
|
||||||
|
description: str | None = Field(default=None, description="Optional description.")
|
||||||
|
required_capabilities: dict[str, dict[str, Any]] | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="Optional explicit dependency contract override.",
|
||||||
|
)
|
||||||
|
source_bindings: SourceBindings | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="Optional logical-to-concrete source bindings.",
|
||||||
|
)
|
||||||
|
created_from_catalog_version: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="Optional catalog version used while authoring.",
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,7 +9,14 @@ from wf_artifacts.models import RequiredCapability
|
|||||||
from wf_mcp.broker.service import WfMcpService
|
from wf_mcp.broker.service import WfMcpService
|
||||||
|
|
||||||
from .handlers import WorkflowSurfaceHandlers
|
from .handlers import WorkflowSurfaceHandlers
|
||||||
from .models import CallCapabilityResult, DraftWorkspaceResult
|
from .models import (
|
||||||
|
CallCapabilityResult,
|
||||||
|
CreateArtifactFromWorkspaceRequest,
|
||||||
|
CreateDraftWorkspaceRequest,
|
||||||
|
CreateMinimalDraftWorkspaceRequest,
|
||||||
|
DraftWorkspaceResult,
|
||||||
|
PatchDraftWorkspaceRequest,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None:
|
def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None:
|
||||||
@@ -196,15 +203,13 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
|
|||||||
description="Store a mutable workflow draft workspace for iterative patching.",
|
description="Store a mutable workflow draft workspace for iterative patching.",
|
||||||
)
|
)
|
||||||
async def create_draft_workspace(
|
async def create_draft_workspace(
|
||||||
workspace_id: str,
|
request: CreateDraftWorkspaceRequest,
|
||||||
draft: dict[str, Any],
|
|
||||||
title: str | None = None,
|
|
||||||
) -> DraftWorkspaceResult:
|
) -> DraftWorkspaceResult:
|
||||||
return DraftWorkspaceResult.model_validate(
|
return DraftWorkspaceResult.model_validate(
|
||||||
await handlers.create_draft_workspace(
|
await handlers.create_draft_workspace(
|
||||||
workspace_id=workspace_id,
|
workspace_id=request.workspace_id,
|
||||||
draft=draft,
|
draft=request.draft,
|
||||||
title=title,
|
title=request.title,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -233,15 +238,13 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def patch_draft_workspace(
|
async def patch_draft_workspace(
|
||||||
workspace_id: str,
|
request: PatchDraftWorkspaceRequest,
|
||||||
revision: int,
|
|
||||||
patch: list[dict[str, Any]],
|
|
||||||
) -> DraftWorkspaceResult:
|
) -> DraftWorkspaceResult:
|
||||||
return DraftWorkspaceResult.model_validate(
|
return DraftWorkspaceResult.model_validate(
|
||||||
await handlers.patch_draft_workspace(
|
await handlers.patch_draft_workspace(
|
||||||
workspace_id=workspace_id,
|
workspace_id=request.workspace_id,
|
||||||
revision=revision,
|
revision=request.revision,
|
||||||
patch=patch,
|
patch=request.patch,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -251,29 +254,20 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
|
|||||||
description="Bootstrap a patchable draft workspace around one capability.",
|
description="Bootstrap a patchable draft workspace around one capability.",
|
||||||
)
|
)
|
||||||
async def create_minimal_draft_workspace(
|
async def create_minimal_draft_workspace(
|
||||||
workspace_id: str,
|
request: CreateMinimalDraftWorkspaceRequest,
|
||||||
name: str,
|
|
||||||
capability_name: str,
|
|
||||||
input_schema: dict[str, Any],
|
|
||||||
state_schema: dict[str, Any],
|
|
||||||
output_schema: dict[str, Any],
|
|
||||||
input_map: dict[str, str],
|
|
||||||
output_map: dict[str, str],
|
|
||||||
error_message_source: str | None = None,
|
|
||||||
title: str | None = None,
|
|
||||||
) -> DraftWorkspaceResult:
|
) -> DraftWorkspaceResult:
|
||||||
return DraftWorkspaceResult.model_validate(
|
return DraftWorkspaceResult.model_validate(
|
||||||
await handlers.create_minimal_draft_workspace(
|
await handlers.create_minimal_draft_workspace(
|
||||||
workspace_id=workspace_id,
|
workspace_id=request.workspace_id,
|
||||||
name=name,
|
name=request.name,
|
||||||
capability_name=capability_name,
|
capability_name=request.capability_name,
|
||||||
input_schema=input_schema,
|
input_schema=request.input_schema,
|
||||||
state_schema=state_schema,
|
state_schema=request.state_schema,
|
||||||
output_schema=output_schema,
|
output_schema=request.output_schema,
|
||||||
input_map=input_map,
|
input_map=request.input_map,
|
||||||
output_map=output_map,
|
output_map=request.output_map,
|
||||||
error_message_source=error_message_source,
|
error_message_source=request.error_message_source,
|
||||||
title=title,
|
title=request.title,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -286,38 +280,29 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def create_artifact_from_workspace(
|
async def create_artifact_from_workspace(
|
||||||
workspace_id: str,
|
request: CreateArtifactFromWorkspaceRequest,
|
||||||
artifact_id: str,
|
|
||||||
version: int,
|
|
||||||
title: str,
|
|
||||||
outcomes: list[str],
|
|
||||||
kind: ArtifactKind = "workflow",
|
|
||||||
description: str | None = None,
|
|
||||||
required_capabilities: (
|
|
||||||
Mapping[str, RequiredCapability | dict[str, Any]] | None
|
|
||||||
) = None,
|
|
||||||
source_bindings: Mapping[str, str] | None = None,
|
|
||||||
created_from_catalog_version: str | None = None,
|
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
return await handlers.create_artifact_from_workspace(
|
return await handlers.create_artifact_from_workspace(
|
||||||
workspace_id=workspace_id,
|
workspace_id=request.workspace_id,
|
||||||
artifact_id=artifact_id,
|
artifact_id=request.artifact_id,
|
||||||
version=version,
|
version=request.version,
|
||||||
title=title,
|
title=request.title,
|
||||||
kind=kind,
|
kind=request.kind,
|
||||||
description=description,
|
description=request.description,
|
||||||
outcomes=outcomes,
|
outcomes=request.outcomes,
|
||||||
required_capabilities={
|
required_capabilities={
|
||||||
name: (
|
name: (
|
||||||
capability.model_dump()
|
capability.model_dump()
|
||||||
if isinstance(capability, RequiredCapability)
|
if isinstance(capability, RequiredCapability)
|
||||||
else capability
|
else capability
|
||||||
)
|
)
|
||||||
for name, capability in (required_capabilities or {}).items()
|
for name, capability in (
|
||||||
|
request.required_capabilities or {}
|
||||||
|
).items()
|
||||||
}
|
}
|
||||||
or None,
|
or None,
|
||||||
source_bindings=dict(source_bindings or {}),
|
source_bindings=dict(request.source_bindings or {}),
|
||||||
created_from_catalog_version=created_from_catalog_version,
|
created_from_catalog_version=request.created_from_catalog_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
@server.tool(
|
@server.tool(
|
||||||
|
|||||||
@@ -83,6 +83,17 @@ def test_server_exposes_upstream_admin_and_workflow_tools() -> None:
|
|||||||
assert create_workspace_schema is not None
|
assert create_workspace_schema is not None
|
||||||
assert "workspace_id" in create_workspace_schema["properties"]
|
assert "workspace_id" in create_workspace_schema["properties"]
|
||||||
assert "revision" in create_workspace_schema["properties"]
|
assert "revision" in create_workspace_schema["properties"]
|
||||||
|
minimal_workspace_input = tools_by_name[
|
||||||
|
"wf.workflow.create_minimal_draft_workspace"
|
||||||
|
].inputSchema
|
||||||
|
minimal_request = minimal_workspace_input["properties"]["request"]
|
||||||
|
assert minimal_request["properties"]["workspace_id"]["pattern"]
|
||||||
|
assert "error_message_source" in minimal_request["properties"]
|
||||||
|
assert (
|
||||||
|
minimal_request["properties"]["input_schema"]["description"]
|
||||||
|
== "JSON Schema object. Keep this as ordinary JSON; "
|
||||||
|
"nested schema fields are passed through unchanged."
|
||||||
|
)
|
||||||
|
|
||||||
echo_result = await client.call_tool(
|
echo_result = await client.call_tool(
|
||||||
"fixture.personal.echo_tool",
|
"fixture.personal.echo_tool",
|
||||||
|
|||||||
Reference in New Issue
Block a user