feat(mcp): serve skills via tools - #23
Merged
Merged
Conversation
Replace resources-based skill surface with a single read_skill tool. Skills are model-invoked, not client-gated behind manual selection. - Skill plugins dir (--skill-plugins) with dynamic <namespace>:<skill> discovery; Docker COPY controls which skills ship in the image - ServerConfig.skills_enabled gates tool registration - PurePosixPath file keys (no custom normalization), exact-match only - create_rule/edit_rule docstrings direct agents to load the skill first - Client.list_resources and CLI command retained as protocol tools BREAKING CHANGE: skills are no longer served as MCP resources.
- Add read_skill to Related tools in create_rule/edit_rule docstrings - Warn (not raise) when from_directory discovers no skills - Expand read_skill description with disclosure ladder guidance
There was a problem hiding this comment.
Pull request overview
This PR shifts skill delivery from FastMCP resources to a first-class read_skill MCP tool (gated by config), and introduces a file-backed filesystem.EnapterAPI plus CLI wiring to select adapters by URL scheme (filetree:// vs http(s)://). The goal is to make skill content reliably accessible to clients via tool calls and enable local/dev workflows without hitting the real Enapter Cloud API.
Changes:
- Add
read_skilltool inmcp.Server, delegating tocore.ApplicationServer+ a pluggableSkillProvider, and gate registration viaServerConfig.skills_enabled. - Add
filesystem.SkillProvider(skill discovery/loading) andfilesystem.EnapterAPI(YAML-backed EnapterAPI adapter) with supporting pydantic models + tests. - Update CLI/Docker/Makefile/integration schema snapshots to wire
--skill-plugins, tool counts, andfiletree://adapter selection.
Reviewed changes
Copilot reviewed 48 out of 51 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/mcp/test_server.py | Updates MCP unit tests to cover read_skill tool delegation and registration gating. |
| tests/unit/filesystem/test_skill_provider.py | Adds unit tests for directory-based skill discovery/loading. |
| tests/unit/filesystem/test_enapter_api.py | Adds extensive unit tests for the file-backed filesystem.EnapterAPI. |
| tests/unit/filesystem/models/test_site.py | Adds round-trip tests for filesystem Site/SiteAggregate models. |
| tests/unit/filesystem/models/test_rule.py | Adds round-trip tests for filesystem Rule/RuleScript models. |
| tests/unit/filesystem/models/test_rule_engine.py | Adds round-trip tests for filesystem RuleEngine/aggregate models. |
| tests/unit/filesystem/models/test_device.py | Adds round-trip tests for filesystem Device model conversions. |
| tests/unit/filesystem/models/init.py | Initializes filesystem model test package. |
| tests/unit/filesystem/init.py | Initializes filesystem unit test package. |
| tests/unit/domain/test_skill.py | Adds tests for domain.Skill.read path handling and miss errors. |
| tests/unit/cli/test_serve_command.py | Adds tests for CLI adapter selection based on URL scheme. |
| tests/integration/test_mcp.py | Updates integration expectations for tool counts and read_skill schema/annotations. |
| tests/integration/schemas/read_skill.json | Adds schema snapshot for the new read_skill tool. |
| tests/integration/schemas/edit_rule.json | Updates tool description snapshot to reference read_skill and namespaced skill. |
| tests/integration/schemas/delete_rule.json | Updates formatting/content snapshot for delete_rule description. |
| tests/integration/schemas/create_rule.json | Updates tool description snapshot to reference read_skill and namespaced skill. |
| src/enapter_mcp_server/mcp/server.py | Adds read_skill tool and removes prior FastMCP resource-based skill registration. |
| src/enapter_mcp_server/mcp/server_config.py | Replaces rule_creator_skill_path with skills_enabled flag. |
| src/enapter_mcp_server/filesystem/skill_provider.py | Implements skill discovery/loading from a plugins directory. |
| src/enapter_mcp_server/filesystem/models/site.py | Adds pydantic model for filesystem Site with domain conversions. |
| src/enapter_mcp_server/filesystem/models/site_status.py | Adds pydantic model for filesystem SiteStatus with domain conversions. |
| src/enapter_mcp_server/filesystem/models/site_aggregate.py | Adds pydantic aggregate model for site + devices YAML layout. |
| src/enapter_mcp_server/filesystem/models/rule.py | Adds pydantic model for filesystem Rule with domain conversions. |
| src/enapter_mcp_server/filesystem/models/rule_state.py | Adds literal alias for rule state serialization. |
| src/enapter_mcp_server/filesystem/models/rule_script.py | Adds pydantic model for rule scripts with domain conversions. |
| src/enapter_mcp_server/filesystem/models/rule_runtime_version.py | Adds literal alias for rule runtime version serialization. |
| src/enapter_mcp_server/filesystem/models/rule_engine.py | Adds pydantic model for rule engine with domain conversions. |
| src/enapter_mcp_server/filesystem/models/rule_engine_state.py | Adds literal alias for rule engine state serialization. |
| src/enapter_mcp_server/filesystem/models/rule_engine_aggregate.py | Adds pydantic aggregate model for rule engine + rules YAML layout. |
| src/enapter_mcp_server/filesystem/models/device.py | Adds pydantic model for filesystem Device with domain conversions. |
| src/enapter_mcp_server/filesystem/models/device_type.py | Adds literal alias for device type serialization. |
| src/enapter_mcp_server/filesystem/models/connectivity_status.py | Adds literal alias for connectivity status serialization. |
| src/enapter_mcp_server/filesystem/models/access_role.py | Adds literal alias for access role serialization. |
| src/enapter_mcp_server/filesystem/models/init.py | Exposes filesystem models from a central package module. |
| src/enapter_mcp_server/filesystem/enapter_api.py | Implements file-backed EnapterAPI adapter using YAML aggregates and atomic writes. |
| src/enapter_mcp_server/filesystem/init.py | Exposes filesystem adapters (EnapterAPI, SkillProvider). |
| src/enapter_mcp_server/domain/skill.py | Introduces domain Skill entity with file read invariant. |
| src/enapter_mcp_server/domain/errors.py | Adds SkillFileNotFound and SkillNotFound domain errors. |
| src/enapter_mcp_server/domain/init.py | Updates domain exports to include skill types/errors (and currently re-exports a core error). |
| src/enapter_mcp_server/core/skill_provider.py | Adds core.SkillProvider protocol (port) for skill loading. |
| src/enapter_mcp_server/core/errors.py | Adds core-layer not-found/conflict errors used by adapters. |
| src/enapter_mcp_server/core/application_server.py | Adds read_skill orchestration via optional injected skill provider. |
| src/enapter_mcp_server/core/init.py | Exposes newly added core errors and SkillProvider. |
| src/enapter_mcp_server/cli/serve_command.py | Wires --skill-plugins and URL-scheme adapter selection into server startup. |
| specs/SPEC-012-cli-filetree-wiring.md | Documents the CLI scheme-based adapter selection decision. |
| specs/SPEC-011-filesystem-api-adapter.md | Documents filesystem EnapterAPI adapter design and constraints. |
| specs/SPEC-010-serve-skills-as-tool.md | Documents serving skills via a read_skill tool instead of resources. |
| Pipfile.lock | Updates lockfile (adds types-pyyaml). |
| Pipfile | Adds types-pyyaml to dev dependencies. |
| Makefile | Updates make serve to build/run the Docker image. |
| Dockerfile | Updates skill plugin path and copies vendored skill into image; defines entrypoint/cmd. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…annotation - Remove RuleSlugConflict import and __all__ entry from domain (violates domain→core layering rule; unused re-export) - Replace forward-reference SkillProvider return type with typing.Self in filesystem.SkillProvider.from_directory
Introduce fake.EnapterAPI: a programmable fake whose behavior is ordinary Python and whose data lives in tests, never in the package. Replaces the file-tree approach (SPEC-011/012), which cannot express command-execution behavior without hard-coded mocks. - fake.State: mutable bag of domain objects (data only) - fake.Policy (Protocol) + DefaultPolicy: behavior; reads implemented against State, read-write methods raise NotImplementedError - fake.EnapterAPI: dispatcher delegating every core.EnapterAPI method to policy.method(state, ...); from_url parses fake://?state=&policy= module paths via importlib SPEC-015 (CLI fake:// wiring + filesystem.EnapterAPI removal) and SPEC-016 (agent test migration) are included as design docs for the follow-on work.
…C-015) Replace the filetree:// adapter with fake.EnapterAPI (SPEC-014) in CLI adapter selection. filetree:// now raises ValueError (unknown scheme). - serve_command._make_enapter_api: fake branch + return type updated - delete filesystem/enapter_api.py and filesystem/models/ (dead code that duplicated http.EnapterDataMapper and could not express command behavior) - filesystem/__init__.py exports only SkillProvider (production code, kept) - tests/unit/cli/test_serve_command.py: fake test replaces filetree test Supersedes SPEC-011 and SPEC-012.
The suite moves off the filetree backend — which could not express command-execution behavior without hard-coded mocks — onto the programmable fake. The three layers exist solely for MCP inheritance: the worker reaches the server only through its parent orchestrator, which requires the meta-supervisor to have already started the server, so the meta-supervisor owns the per-scenario server lifecycle while the orchestrator runs one scenario at a time. Verification is logs-only because the worker has no channel to affect the world except tool calls, all logged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR replaces FastMCP resource-based skill delivery with a first-class read_skill MCP tool, and introduces a file-backed API adapter for local/agent-driven testing — all gated behind config flags with no changes to the production HTTP path.