functualize-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,101 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg-info/
7
+ *.egg
8
+ dist/
9
+ build/
10
+ *.whl
11
+
12
+ # Agents
13
+ .spec/archive/
14
+ .spec/features/
15
+ .spec/scrutiny-reports/
16
+ .spec/.agentic-coding
17
+ .spec/STATE.md
18
+ .spec/PROJECT.md
19
+ .spec/REQUIREMENTS.md
20
+ .spec/ROADMAP.md
21
+ .opencode/
22
+
23
+
24
+ # Virtual environments
25
+ .venv/
26
+ venv/
27
+ ENV/
28
+
29
+ # Testing
30
+ .coverage
31
+ .pytest_cache/
32
+ htmlcov/
33
+ .hypothesis/
34
+ snapshot_report.html
35
+ _*_result*.txt
36
+ _debug.txt
37
+ _tui_debug.txt
38
+ _tui_eval_debug.txt
39
+
40
+ # IDE
41
+ .idea/
42
+ *.swp
43
+ *.swo
44
+ *~
45
+ *.code-workspace
46
+
47
+ # Coding-agent tooling state (guards — these dirs are not part of the repo)
48
+ .kiro/
49
+ .moai/
50
+
51
+ # OS
52
+ .DS_Store
53
+ Thumbs.db
54
+
55
+ # Environment / secrets
56
+ .env
57
+ .env.*
58
+ !.env.example
59
+
60
+ # Agent scratch space (test output, temp scripts)
61
+ tmp/
62
+
63
+ # Local-only files (not for the repo)
64
+ *.local.md
65
+ *.local.*
66
+
67
+ # Personal notes
68
+ HUMAN_NOTE.md
69
+
70
+ # Distribution
71
+ dist/
72
+
73
+ # Documentation site build output
74
+ site/
75
+
76
+ # uv
77
+ .python-version
78
+ .functualize/cache.json
79
+ .functualize_cache.json
80
+ .todos/
81
+ .sidecar/
82
+ .sidecar-agent
83
+ .sidecar-task
84
+ .sidecar-pr
85
+ .sidecar-start.sh
86
+ .sidecar-base
87
+ .td-root
88
+ .functualize/
89
+ .import_linter_cache/
90
+ .mypy_cache/
91
+ .pytest_cache/
92
+ .ruff_cache/
93
+
94
+ # OmO / OpenCode agent run-continuation scratch state
95
+ .omo/
96
+ .mcp.json
97
+ .agentsroom/handoff-transcript-*.txt
98
+ .agentsroom/handoff-summary-*.md
99
+
100
+ # Internal pre-release audit reports (contain session IDs / local infra notes)
101
+ .release/
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: functualize-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP delivery adapter plugin for functualize — exposes jobs as MCP tools via FastMCP
5
+ Author-email: Mohammad Hakim Adiprasetya <viltohmyst@gmail.com>
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Typing :: Typed
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: fastmcp>=0.1.0
15
+ Requires-Dist: functualize<1.0.0,>=0.1.0
16
+ Provides-Extra: dev
17
+ Requires-Dist: hypothesis>=6.82.0; extra == 'dev'
18
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
19
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # functualize-mcp
23
+
24
+ > **Status: Published** — Independently installable from PyPI.
25
+
26
+ MCP delivery adapter plugin for functualize — exposes jobs as MCP tools via FastMCP. External AI agents (Goose, Claude, Cursor) can discover and invoke functualize jobs through the standardized [Model Context Protocol](https://modelcontextprotocol.io/) interface without any manual wiring.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install functualize-mcp
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```python
37
+ from functualize_mcp import JobToolTranslator, MCPToolDef, SchemaExporter
38
+
39
+ # Translate a job descriptor into an MCP tool definition
40
+ translator = JobToolTranslator()
41
+ tool_def = translator.translate(my_job_descriptor)
42
+ print(tool_def.name, tool_def.description)
43
+
44
+ # Export all job schemas as OpenAI function calling JSON
45
+ exporter = SchemaExporter(translator)
46
+ openai_json = exporter.export_openai(descriptors)
47
+ ```
48
+
49
+ ## Features
50
+
51
+ - Automatic job-to-tool translation with JSON Schema input generation from config models
52
+ - Dual transport support: stdio (default) and HTTP+SSE for networked agents
53
+ - Job visibility filtering via include/exclude tags and job name exclusion lists
54
+ - Multi-format schema export: JSON (MCP), Markdown, OpenAI function calling, TypeScript
55
+ - Multi-server management: start, list, and stop background HTTP servers
56
+ - Workflow inspection and advancement tools for paused workflow steps
57
+ - Async job execution with polling via `run_job_async` / `get_execution_status`
58
+ - AI outbound gate strategy for workflow checkpoint serialization
59
+
60
+ ## API Reference
61
+
62
+ Public classes and functions exported by this plugin:
63
+
64
+ - `MCPAdapterPlugin` — Plugin entry point implementing the AdapterPlugin protocol; registers MCP tools at boot
65
+ - `MCPConfig` — Pydantic configuration model controlling transport, port, host, tag filters, and management features
66
+ - `MCPServer` — FastMCP wrapper that discovers jobs, registers tools, and serves via stdio or HTTP+SSE
67
+ - `JobToolTranslator` — Translates JobDescriptors into MCPToolDef instances using docstrings and config schemas
68
+ - `MCPToolDef` — Frozen dataclass representing an MCP tool (name, description, input_schema, annotations)
69
+ - `SchemaExporter` — Exports job schemas in JSON, Markdown, OpenAI, and TypeScript formats
70
+ - `MCPToolRegistry` — Registers core MCP tools (discover_jobs, get_job_schema, run_job, run_job_async, get_execution_status)
71
+ - `MCPHistoryToolRegistry` — Registers execution history inspection tools
72
+ - `MCPManagementToolRegistry` — Registers multi-server management meta-tools
73
+ - `MCPTaskToolRegistry` — Registers task-domain MCP tools
74
+ - `WorkflowToolProvider` — Registers workflow state inspection and resume tools
75
+ - `ServerManager` — Manages background MCP HTTP server processes (start, list, stop)
76
+ - `ServerInfo` — Dataclass describing a managed server (name, directory, port, pid, status)
77
+ - `AIOutboundGateResolver` — Gate strategy resolver for AI outbound workflow checkpoints
78
+ - `register_ai_outbound_gate_strategy()` — Registers the ai_outbound gate strategy and preset with an app
79
+
80
+ ## Development
81
+
82
+ Run plugin tests:
83
+
84
+ ```bash
85
+ uv run pytest plugins/functualize-mcp/tests/ -v
86
+ ```
@@ -0,0 +1,65 @@
1
+ # functualize-mcp
2
+
3
+ > **Status: Published** — Independently installable from PyPI.
4
+
5
+ MCP delivery adapter plugin for functualize — exposes jobs as MCP tools via FastMCP. External AI agents (Goose, Claude, Cursor) can discover and invoke functualize jobs through the standardized [Model Context Protocol](https://modelcontextprotocol.io/) interface without any manual wiring.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install functualize-mcp
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from functualize_mcp import JobToolTranslator, MCPToolDef, SchemaExporter
17
+
18
+ # Translate a job descriptor into an MCP tool definition
19
+ translator = JobToolTranslator()
20
+ tool_def = translator.translate(my_job_descriptor)
21
+ print(tool_def.name, tool_def.description)
22
+
23
+ # Export all job schemas as OpenAI function calling JSON
24
+ exporter = SchemaExporter(translator)
25
+ openai_json = exporter.export_openai(descriptors)
26
+ ```
27
+
28
+ ## Features
29
+
30
+ - Automatic job-to-tool translation with JSON Schema input generation from config models
31
+ - Dual transport support: stdio (default) and HTTP+SSE for networked agents
32
+ - Job visibility filtering via include/exclude tags and job name exclusion lists
33
+ - Multi-format schema export: JSON (MCP), Markdown, OpenAI function calling, TypeScript
34
+ - Multi-server management: start, list, and stop background HTTP servers
35
+ - Workflow inspection and advancement tools for paused workflow steps
36
+ - Async job execution with polling via `run_job_async` / `get_execution_status`
37
+ - AI outbound gate strategy for workflow checkpoint serialization
38
+
39
+ ## API Reference
40
+
41
+ Public classes and functions exported by this plugin:
42
+
43
+ - `MCPAdapterPlugin` — Plugin entry point implementing the AdapterPlugin protocol; registers MCP tools at boot
44
+ - `MCPConfig` — Pydantic configuration model controlling transport, port, host, tag filters, and management features
45
+ - `MCPServer` — FastMCP wrapper that discovers jobs, registers tools, and serves via stdio or HTTP+SSE
46
+ - `JobToolTranslator` — Translates JobDescriptors into MCPToolDef instances using docstrings and config schemas
47
+ - `MCPToolDef` — Frozen dataclass representing an MCP tool (name, description, input_schema, annotations)
48
+ - `SchemaExporter` — Exports job schemas in JSON, Markdown, OpenAI, and TypeScript formats
49
+ - `MCPToolRegistry` — Registers core MCP tools (discover_jobs, get_job_schema, run_job, run_job_async, get_execution_status)
50
+ - `MCPHistoryToolRegistry` — Registers execution history inspection tools
51
+ - `MCPManagementToolRegistry` — Registers multi-server management meta-tools
52
+ - `MCPTaskToolRegistry` — Registers task-domain MCP tools
53
+ - `WorkflowToolProvider` — Registers workflow state inspection and resume tools
54
+ - `ServerManager` — Manages background MCP HTTP server processes (start, list, stop)
55
+ - `ServerInfo` — Dataclass describing a managed server (name, directory, port, pid, status)
56
+ - `AIOutboundGateResolver` — Gate strategy resolver for AI outbound workflow checkpoints
57
+ - `register_ai_outbound_gate_strategy()` — Registers the ai_outbound gate strategy and preset with an app
58
+
59
+ ## Development
60
+
61
+ Run plugin tests:
62
+
63
+ ```bash
64
+ uv run pytest plugins/functualize-mcp/tests/ -v
65
+ ```
@@ -0,0 +1,52 @@
1
+ # functualize-mcp Examples
2
+
3
+ The full MCP reference example: exposing jobs as MCP tools that AI agents (Claude, Cursor, Goose) can discover and call. For the minimal narrative version, see [`examples/quickstart/step6_mcp/`](../../../examples/quickstart/step6_mcp/).
4
+
5
+ | Directory | Demonstrates |
6
+ |-----------|--------------|
7
+ | [`weather_tools/`](weather_tools/) | Visibility control, rich `@job_metadata`, and the served tool surface |
8
+
9
+ ## Serving
10
+
11
+ ```bash
12
+ cd plugins/functualize-mcp/examples/weather_tools
13
+ func mcp serve # stdio transport (default)
14
+ ```
15
+
16
+ Wire into Claude Code:
17
+
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "weather": {
22
+ "command": "func",
23
+ "args": ["mcp", "serve"],
24
+ "cwd": "/path/to/weather_tools"
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ ## The tool surface agents see
31
+
32
+ Beyond one tool per `visibility="external"` job, the server exposes framework tools:
33
+
34
+ | Tool | Purpose |
35
+ |------|---------|
36
+ | `discover_jobs` | List external jobs with descriptions and tags |
37
+ | `get_job_schema` | Full input schema (from the job's Pydantic config) |
38
+ | `run_job` / `run_job_async` | Execute synchronously or in the background |
39
+ | `get_execution_status` | Poll an async execution |
40
+ | `get_workflow_state` / `resume_workflow` | Drive gated workflows across turns |
41
+
42
+ Inspect what would be served without starting the server:
43
+
44
+ ```bash
45
+ func mcp tools
46
+ ```
47
+
48
+ ## Tests
49
+
50
+ ```bash
51
+ uv run pytest plugins/functualize-mcp/examples/ -v
52
+ ```
@@ -0,0 +1,62 @@
1
+ """Jobs exposed as MCP tools — visibility and metadata reference.
2
+
3
+ Serve with:
4
+ func mcp serve
5
+
6
+ `forecast` and `travel_advice` become MCP tools; `purge_cache` stays
7
+ hidden (visibility="internal"); `refresh` has no metadata and defaults
8
+ to hidden as well.
9
+ """
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+ from functualize.job import RunContext
14
+ from functualize.job.decorators import job
15
+
16
+
17
+ class ForecastConfig(BaseModel):
18
+ """Configuration for weather jobs — becomes the MCP tool's input schema."""
19
+
20
+ city: str = Field(description="City to check")
21
+ days: int = Field(default=3, ge=1, le=7, description="Days to forecast")
22
+
23
+
24
+ @job(
25
+ extra_description="Get a weather forecast for a city",
26
+ category="weather",
27
+ examples=["forecast --city Tokyo --days 5"],
28
+ tags=["weather", "safe", "read-only"],
29
+ visibility="external",
30
+ )
31
+ def forecast(config: ForecastConfig, rc: RunContext) -> str:
32
+ """Fetch the weather forecast for the configured city."""
33
+ rc.log(f"Fetching {config.days}-day forecast for {config.city}...")
34
+ return f"{config.city}: 24°C, sunny for the next {config.days} days"
35
+
36
+
37
+ @job(
38
+ extra_description="Advise whether the weather suits outdoor travel plans",
39
+ category="weather",
40
+ tags=["weather", "advice"],
41
+ visibility="external",
42
+ )
43
+ def travel_advice(config: ForecastConfig, rc: RunContext) -> str:
44
+ """Turn the forecast into a go/no-go travel recommendation."""
45
+ rc.log(f"Evaluating travel conditions for {config.city}...")
46
+ return f"{config.city} looks great for outdoor plans this week"
47
+
48
+
49
+ @job(
50
+ extra_description="Purge the local forecast cache",
51
+ visibility="internal",
52
+ )
53
+ def purge_cache(rc: RunContext) -> str:
54
+ """Internal maintenance — hidden from MCP agents."""
55
+ rc.log("Purging forecast cache...")
56
+ return "cache purged"
57
+
58
+
59
+ def refresh(rc: RunContext) -> str:
60
+ """No metadata at all — also not exposed as an MCP tool."""
61
+ rc.log("Refreshing data sources...")
62
+ return "refreshed"
@@ -0,0 +1,31 @@
1
+ """Tests for the weather_tools MCP example: the visibility contract."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from unittest.mock import MagicMock
6
+
7
+ sys.path.insert(0, str(Path(__file__).parent))
8
+
9
+ from jobs import ForecastConfig, forecast, purge_cache, refresh, travel_advice
10
+
11
+
12
+ def test_external_jobs_carry_full_metadata():
13
+ for job in (forecast, travel_advice):
14
+ decl = job.__functualize_job__
15
+ assert decl.visibility == "external"
16
+ assert decl.extra_description
17
+ assert "weather" in decl.tags
18
+
19
+
20
+ def test_internal_job_is_marked_hidden():
21
+ assert purge_cache.__functualize_job__.visibility == "internal"
22
+
23
+
24
+ def test_unannotated_job_has_no_declaration():
25
+ assert not hasattr(refresh, "__functualize_job__")
26
+
27
+
28
+ def test_jobs_run_directly():
29
+ rc = MagicMock()
30
+ assert "Tokyo" in forecast(ForecastConfig(city="Tokyo"), rc)
31
+ assert "Tokyo" in travel_advice(ForecastConfig(city="Tokyo"), rc)
@@ -0,0 +1,42 @@
1
+ [project]
2
+ name = "functualize-mcp"
3
+ version = "0.1.0"
4
+ description = "MCP delivery adapter plugin for functualize — exposes jobs as MCP tools via FastMCP"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ authors = [
8
+ { name = "Mohammad Hakim Adiprasetya", email = "viltohmyst@gmail.com" }
9
+ ]
10
+ requires-python = ">=3.11"
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Typing :: Typed",
18
+ ]
19
+ dependencies = [
20
+ "functualize>=0.1.0,<1.0.0",
21
+ "fastmcp>=0.1.0",
22
+ ]
23
+
24
+ [project.entry-points."functualize.plugins"]
25
+ mcp = "functualize_mcp:MCPAdapterPlugin"
26
+
27
+ [project.optional-dependencies]
28
+ dev = [
29
+ "pytest>=7.4.0",
30
+ "pytest-cov>=4.1.0",
31
+ "hypothesis>=6.82.0",
32
+ ]
33
+
34
+ [build-system]
35
+ requires = ["hatchling"]
36
+ build-backend = "hatchling.build"
37
+
38
+ [tool.uv.sources]
39
+ functualize = { workspace = true }
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/functualize_mcp"]
@@ -0,0 +1,86 @@
1
+ """Functualize MCP Plugin — exposes jobs as MCP tools via FastMCP.
2
+
3
+ Provides an MCP delivery adapter that translates functualize jobs into
4
+ MCP tools, allowing external AI agents (Goose, Claude, Cursor) to discover
5
+ and invoke jobs through the Model Context Protocol.
6
+ """
7
+
8
+ from functualize_mcp._plugin import MCPAdapterPlugin
9
+
10
+ __all__ = [
11
+ "AI_OUTBOUND_PRESET_NAME",
12
+ "AI_OUTBOUND_PRESET_STRATEGIES",
13
+ "AI_OUTBOUND_STRATEGY_NAME",
14
+ "AIOutboundGateResolver",
15
+ "MCPAdapterPlugin",
16
+ "MCPConfig",
17
+ "MCPHistoryToolRegistry",
18
+ "MCPManagementToolRegistry",
19
+ "MCPServer",
20
+ "MCPTaskToolRegistry",
21
+ "MCPToolRegistry",
22
+ "JobToolTranslator",
23
+ "MCPToolDef",
24
+ "SchemaExporter",
25
+ "ServerInfo",
26
+ "ServerManager",
27
+ "WorkflowToolProvider",
28
+ "register_ai_outbound_gate_strategy",
29
+ ]
30
+
31
+
32
+ # Lazy loading for heavy symbols (PEP 562)
33
+ _LAZY_IMPORTS: dict[str, tuple[str, str]] = {
34
+ "MCPConfig": ("functualize_mcp._config", "MCPConfig"),
35
+ "AI_OUTBOUND_PRESET_NAME": (
36
+ "functualize_mcp._gate_strategy",
37
+ "AI_OUTBOUND_PRESET_NAME",
38
+ ),
39
+ "AI_OUTBOUND_PRESET_STRATEGIES": (
40
+ "functualize_mcp._gate_strategy",
41
+ "AI_OUTBOUND_PRESET_STRATEGIES",
42
+ ),
43
+ "AI_OUTBOUND_STRATEGY_NAME": (
44
+ "functualize_mcp._gate_strategy",
45
+ "AI_OUTBOUND_STRATEGY_NAME",
46
+ ),
47
+ "AIOutboundGateResolver": (
48
+ "functualize_mcp._gate_strategy",
49
+ "AIOutboundGateResolver",
50
+ ),
51
+ "register_ai_outbound_gate_strategy": (
52
+ "functualize_mcp._gate_strategy",
53
+ "register_ai_outbound_gate_strategy",
54
+ ),
55
+ "MCPHistoryToolRegistry": (
56
+ "functualize_mcp._history_tools",
57
+ "MCPHistoryToolRegistry",
58
+ ),
59
+ "MCPManagementToolRegistry": (
60
+ "functualize_mcp._management_tools",
61
+ "MCPManagementToolRegistry",
62
+ ),
63
+ "SchemaExporter": ("functualize_mcp._schema_export", "SchemaExporter"),
64
+ "MCPServer": ("functualize_mcp._server", "MCPServer"),
65
+ "ServerInfo": ("functualize_mcp._server_manager", "ServerInfo"),
66
+ "ServerManager": ("functualize_mcp._server_manager", "ServerManager"),
67
+ "MCPTaskToolRegistry": ("functualize_mcp._task_tools", "MCPTaskToolRegistry"),
68
+ "MCPToolRegistry": ("functualize_mcp._tools", "MCPToolRegistry"),
69
+ "JobToolTranslator": ("functualize_mcp._translator", "JobToolTranslator"),
70
+ "MCPToolDef": ("functualize_mcp._translator", "MCPToolDef"),
71
+ "WorkflowToolProvider": ("functualize_mcp._workflow_tools", "WorkflowToolProvider"),
72
+ }
73
+
74
+
75
+ def __getattr__(name: str):
76
+ """Lazy-load heavy symbols on first access (PEP 562)."""
77
+ if name in _LAZY_IMPORTS:
78
+ module_path, attr_name = _LAZY_IMPORTS[name]
79
+ import importlib
80
+
81
+ module = importlib.import_module(module_path)
82
+ value = getattr(module, attr_name)
83
+ # Cache it on the module for subsequent fast access
84
+ globals()[name] = value
85
+ return value
86
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,39 @@
1
+ """MCP adapter configuration model.
2
+
3
+ Defines MCPConfig pydantic model with transport, networking, filtering,
4
+ and management settings for the MCP server.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Literal
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+ __all__ = ["MCPConfig"]
14
+
15
+
16
+ class MCPConfig(BaseModel):
17
+ """Configuration for the MCP delivery adapter.
18
+
19
+ Read from the ``[mcp]`` config section. Controls transport mode,
20
+ network binding, job visibility filtering, and management features.
21
+
22
+ Attributes:
23
+ transport: Server transport mode — "stdio" (default) or "http".
24
+ port: HTTP port when using HTTP+SSE transport.
25
+ host: Bind address for HTTP transport.
26
+ include_tags: Only expose jobs tagged with at least one of these tags.
27
+ Empty list means no tag filtering (expose all visible jobs).
28
+ exclude_tags: Hide jobs tagged with any of these tags.
29
+ exclude_jobs: Hide specific jobs by name from MCP tool listings.
30
+ enable_management: When True, expose multi-server management meta-tools.
31
+ """
32
+
33
+ transport: Literal["stdio", "http"] = "stdio"
34
+ port: int = Field(default=8080, ge=1024, le=65535)
35
+ host: str = "127.0.0.1"
36
+ include_tags: list[str] = Field(default_factory=list)
37
+ exclude_tags: list[str] = Field(default_factory=list)
38
+ exclude_jobs: list[str] = Field(default_factory=list)
39
+ enable_management: bool = False