agent-config-kit 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.
- agent_config_kit-0.1.0/.gitignore +35 -0
- agent_config_kit-0.1.0/PKG-INFO +11 -0
- agent_config_kit-0.1.0/README.md +134 -0
- agent_config_kit-0.1.0/agent_config_kit/__init__.py +79 -0
- agent_config_kit-0.1.0/agent_config_kit/adapters/__init__.py +1 -0
- agent_config_kit-0.1.0/agent_config_kit/adapters/_wire/__init__.py +14 -0
- agent_config_kit-0.1.0/agent_config_kit/adapters/_wire/claude_settings.py +313 -0
- agent_config_kit-0.1.0/agent_config_kit/adapters/_wire/copilot_mcp.py +34 -0
- agent_config_kit-0.1.0/agent_config_kit/adapters/_wire/opencode_config.py +120 -0
- agent_config_kit-0.1.0/agent_config_kit/adapters/_wire/pi_mcp.py +36 -0
- agent_config_kit-0.1.0/agent_config_kit/adapters/claude.py +103 -0
- agent_config_kit-0.1.0/agent_config_kit/adapters/copilot.py +28 -0
- agent_config_kit-0.1.0/agent_config_kit/adapters/opencode.py +40 -0
- agent_config_kit-0.1.0/agent_config_kit/adapters/pi.py +28 -0
- agent_config_kit-0.1.0/agent_config_kit/cli.py +242 -0
- agent_config_kit-0.1.0/agent_config_kit/diff.py +138 -0
- agent_config_kit-0.1.0/agent_config_kit/installers.py +105 -0
- agent_config_kit-0.1.0/agent_config_kit/jsonio.py +45 -0
- agent_config_kit-0.1.0/agent_config_kit/manifest.py +152 -0
- agent_config_kit-0.1.0/agent_config_kit/models.py +234 -0
- agent_config_kit-0.1.0/agent_config_kit/paths.py +17 -0
- agent_config_kit-0.1.0/agent_config_kit/plan.py +181 -0
- agent_config_kit-0.1.0/agent_config_kit/prune.py +304 -0
- agent_config_kit-0.1.0/agent_config_kit/registry.py +123 -0
- agent_config_kit-0.1.0/pyproject.toml +32 -0
- agent_config_kit-0.1.0/tests/__init__.py +0 -0
- agent_config_kit-0.1.0/tests/test_cli.py +513 -0
- agent_config_kit-0.1.0/tests/test_diff.py +168 -0
- agent_config_kit-0.1.0/tests/test_installers.py +126 -0
- agent_config_kit-0.1.0/tests/test_jsonio.py +50 -0
- agent_config_kit-0.1.0/tests/test_manifest.py +271 -0
- agent_config_kit-0.1.0/tests/test_models.py +111 -0
- agent_config_kit-0.1.0/tests/test_paths.py +35 -0
- agent_config_kit-0.1.0/tests/test_plan.py +295 -0
- agent_config_kit-0.1.0/tests/test_prune.py +313 -0
- agent_config_kit-0.1.0/tests/test_registry.py +44 -0
- agent_config_kit-0.1.0/tests/test_wire_validation.py +78 -0
- agent_config_kit-0.1.0/uv.lock +327 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
.venv/
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.pyc
|
|
6
|
+
|
|
7
|
+
# Environment / secrets
|
|
8
|
+
.env
|
|
9
|
+
.env.local
|
|
10
|
+
.env.*.local
|
|
11
|
+
*.secret
|
|
12
|
+
|
|
13
|
+
# Editor
|
|
14
|
+
.vscode/settings.json
|
|
15
|
+
.idea/
|
|
16
|
+
*.swp
|
|
17
|
+
*.swo
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
|
|
23
|
+
# Build artifacts
|
|
24
|
+
dist/
|
|
25
|
+
build/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
# omnigraph binary downloaded by hatch_build.py — not committed
|
|
28
|
+
_bin/
|
|
29
|
+
# omnigraph cluster state (populated by `omnigraph cluster import`)
|
|
30
|
+
__cluster/
|
|
31
|
+
|
|
32
|
+
# MCP server logs / caches
|
|
33
|
+
.mcp-cache/
|
|
34
|
+
mcp-server.log
|
|
35
|
+
.ruff_cache/
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-config-kit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unified MCP server, skill, and hook registration across coding-agent platforms
|
|
5
|
+
License-Expression: BSD-3-Clause
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: pydantic<3,>=2
|
|
9
|
+
Provides-Extra: cli
|
|
10
|
+
Requires-Dist: cyclopts<5,>=4; extra == 'cli'
|
|
11
|
+
Requires-Dist: rich>=13; extra == 'cli'
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# agent-config-kit
|
|
2
|
+
|
|
3
|
+
Unified interface for registering MCP servers, skills, and hooks/extensions
|
|
4
|
+
across coding-agent platforms (Claude Code, Pi, GitHub Copilot, OpenCode,
|
|
5
|
+
Kilo Code, ...) without reimplementing every agent's config-file quirks.
|
|
6
|
+
|
|
7
|
+
Canonical, capability-cluster `pydantic` models (`StdioServer`/`RemoteServer`,
|
|
8
|
+
`DeclarativeHook`/`PluginRegistration`, `SkillSource`, ...) describe *what* to
|
|
9
|
+
register; a small per-platform adapter and a shared read-merge-write
|
|
10
|
+
orchestration layer (`apply`/`apply_all`) handle *where* and in what wire
|
|
11
|
+
format each platform expects it.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from agent_config_kit import RegistrationBundle, StdioServer, apply_all
|
|
15
|
+
|
|
16
|
+
bundle = RegistrationBundle(
|
|
17
|
+
mcp_servers={"my-tool": StdioServer(command="uvx", args=["my-tool", "serve"])},
|
|
18
|
+
)
|
|
19
|
+
apply_all(bundle)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
See `docs/design/agent-config-kit-spec.md` in this repo for the full design.
|
|
23
|
+
|
|
24
|
+
## CLI (`ac-kit`)
|
|
25
|
+
|
|
26
|
+
A project without its own Python tooling (a plain Node repo, a
|
|
27
|
+
shell-scripted dotfiles setup, a CI job) can drive the same
|
|
28
|
+
`RegistrationBundle`/`apply` machinery declaratively via a TOML manifest and
|
|
29
|
+
the `ac-kit` console script, gated behind the `cli` extra:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv tool install 'agent-config-kit[cli]'
|
|
33
|
+
# or: pip install 'agent-config-kit[cli]'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Manifest format
|
|
37
|
+
|
|
38
|
+
```toml
|
|
39
|
+
# agent-config.toml
|
|
40
|
+
|
|
41
|
+
instructions = "See AGENTS.md" # optional — must come before any table/
|
|
42
|
+
# array-of-tables header, or TOML parses
|
|
43
|
+
# it as belonging to the preceding one
|
|
44
|
+
|
|
45
|
+
[options]
|
|
46
|
+
scope = "global" # "global" | "project" — default: "global"
|
|
47
|
+
platforms = ["claude", "pi"] # optional allow-list; default: every
|
|
48
|
+
# detected platform
|
|
49
|
+
|
|
50
|
+
[mcp_servers.witan]
|
|
51
|
+
kind = "stdio"
|
|
52
|
+
command = "uvx"
|
|
53
|
+
args = ["witan", "serve"]
|
|
54
|
+
env = { WITAN_AUTHOR = "team" }
|
|
55
|
+
|
|
56
|
+
[mcp_servers.hosted-tool]
|
|
57
|
+
kind = "remote"
|
|
58
|
+
url = "https://example.com/mcp"
|
|
59
|
+
transport = "streamable-http" # "sse" | "http" | "streamable-http"
|
|
60
|
+
|
|
61
|
+
[[hooks]]
|
|
62
|
+
kind = "declarative"
|
|
63
|
+
event = "user_prompt_submit" # see HookEvent for valid values
|
|
64
|
+
command = "witan inject-context"
|
|
65
|
+
|
|
66
|
+
[[hooks]]
|
|
67
|
+
kind = "plugin"
|
|
68
|
+
entry_path = "extensions/pi/witan.ts" # resolved relative to this file
|
|
69
|
+
|
|
70
|
+
[[skills]]
|
|
71
|
+
name = "witan-task"
|
|
72
|
+
skill_md_path = "skills/witan-task/SKILL.md" # resolved relative to this file
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Table/field names mirror the Python model field names exactly
|
|
76
|
+
(`kind`, `command`, `args`, `env`, `event`, `entry_path`, ...) — see
|
|
77
|
+
`docs/design/agent-config-kit-cli-spec.md` for the full schema and rationale.
|
|
78
|
+
|
|
79
|
+
Skills follow the [Agent Skills specification](https://agentskills.io/specification):
|
|
80
|
+
`skill_md_path` must point to a file literally named `SKILL.md`, and its
|
|
81
|
+
parent directory is installed wholesale — `scripts/`, `references/`,
|
|
82
|
+
`assets/`, or any other supporting files alongside it are copied too, not
|
|
83
|
+
just `SKILL.md` itself. `name` must match the spec's frontmatter `name`
|
|
84
|
+
constraints (1-64 characters, lowercase alphanumeric segments separated by
|
|
85
|
+
single hyphens, no leading/trailing/consecutive hyphens) since it becomes
|
|
86
|
+
the installed skill's directory name.
|
|
87
|
+
|
|
88
|
+
### `ac-kit apply`
|
|
89
|
+
|
|
90
|
+
Applies a manifest's MCP servers, hooks, and skills to one or more platforms:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
ac-kit apply agent-config.toml
|
|
94
|
+
ac-kit apply agent-config.toml --platform claude --platform pi
|
|
95
|
+
ac-kit apply agent-config.toml --scope project --dry-run
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- `--platform NAME` (repeatable) overrides the manifest's
|
|
99
|
+
`[options.platforms]`; with neither given, every detected platform is
|
|
100
|
+
targeted.
|
|
101
|
+
- `--scope global|project` overrides the manifest's `[options].scope` for
|
|
102
|
+
this run.
|
|
103
|
+
- `--dry-run` reports what would be written/removed without touching disk.
|
|
104
|
+
- `--prune` also removes entries a *previous* `apply --prune` of this same
|
|
105
|
+
manifest wrote but that have since been dropped from it (e.g. a deleted
|
|
106
|
+
`[mcp_servers.*]` table, a removed skill or hook). This is opt-in and only
|
|
107
|
+
ever removes what it can prove it wrote itself, tracked in a state file
|
|
108
|
+
(`<manifest>.lock.json` by default, override with `--state-file PATH`) — a
|
|
109
|
+
manifest's first-ever `--prune` run removes nothing, since there's no
|
|
110
|
+
recorded state yet to diff against, and a hand-edited key that's absent
|
|
111
|
+
from both the previous and current manifest is never touched.
|
|
112
|
+
|
|
113
|
+
Exit codes: `0` success, `1` a platform's target couldn't be parsed as JSON,
|
|
114
|
+
`2` the manifest failed to load.
|
|
115
|
+
|
|
116
|
+
### `ac-kit validate`
|
|
117
|
+
|
|
118
|
+
Reports drift between a manifest and each platform's on-disk config, without
|
|
119
|
+
writing anything — useful in CI to catch configuration that's fallen out of
|
|
120
|
+
sync:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
ac-kit validate agent-config.toml
|
|
124
|
+
ac-kit validate agent-config.toml --platform claude
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Missing MCP servers/hooks, mismatched MCP server values, and missing skill/
|
|
128
|
+
plugin-hook files are all reported as drift; a target that fails to parse as
|
|
129
|
+
JSON is reported separately (not as drift, since there's nothing to compare).
|
|
130
|
+
`validate` never writes — pair it with `apply --prune` to actually
|
|
131
|
+
reconcile.
|
|
132
|
+
|
|
133
|
+
Exit codes: `0` no drift, `1` drift (or an unreadable target) found, `2` the
|
|
134
|
+
manifest failed to load.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .diff import Drift, diff
|
|
4
|
+
from .jsonio import load_json_object, write_json
|
|
5
|
+
from .manifest import Manifest, ManifestError, ManifestOptions, load_manifest
|
|
6
|
+
from .models import (
|
|
7
|
+
AgentPlatform,
|
|
8
|
+
ApprovalMode,
|
|
9
|
+
ApprovalPolicy,
|
|
10
|
+
CapabilityScope,
|
|
11
|
+
DeclarativeHook,
|
|
12
|
+
FrontmatterRule,
|
|
13
|
+
Hook,
|
|
14
|
+
HookEvent,
|
|
15
|
+
InstructionsConfig,
|
|
16
|
+
LspServer,
|
|
17
|
+
McpServer,
|
|
18
|
+
MergeStrategy,
|
|
19
|
+
PluginRegistration,
|
|
20
|
+
RemoteServer,
|
|
21
|
+
Scope,
|
|
22
|
+
ScopeTarget,
|
|
23
|
+
SearchStrategy,
|
|
24
|
+
SkillSource,
|
|
25
|
+
StdioServer,
|
|
26
|
+
)
|
|
27
|
+
from .plan import InstallResult, RegistrationBundle, apply, apply_all
|
|
28
|
+
from .prune import (
|
|
29
|
+
PlatformState,
|
|
30
|
+
apply_with_prune,
|
|
31
|
+
default_state_path,
|
|
32
|
+
hook_identity,
|
|
33
|
+
load_state,
|
|
34
|
+
write_state,
|
|
35
|
+
)
|
|
36
|
+
from .registry import detect_installed_platforms, get_platform, known_platforms
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"AgentPlatform",
|
|
40
|
+
"ApprovalMode",
|
|
41
|
+
"ApprovalPolicy",
|
|
42
|
+
"CapabilityScope",
|
|
43
|
+
"DeclarativeHook",
|
|
44
|
+
"Drift",
|
|
45
|
+
"FrontmatterRule",
|
|
46
|
+
"Hook",
|
|
47
|
+
"HookEvent",
|
|
48
|
+
"InstallResult",
|
|
49
|
+
"InstructionsConfig",
|
|
50
|
+
"LspServer",
|
|
51
|
+
"Manifest",
|
|
52
|
+
"ManifestError",
|
|
53
|
+
"ManifestOptions",
|
|
54
|
+
"McpServer",
|
|
55
|
+
"MergeStrategy",
|
|
56
|
+
"PlatformState",
|
|
57
|
+
"PluginRegistration",
|
|
58
|
+
"RegistrationBundle",
|
|
59
|
+
"RemoteServer",
|
|
60
|
+
"Scope",
|
|
61
|
+
"ScopeTarget",
|
|
62
|
+
"SearchStrategy",
|
|
63
|
+
"SkillSource",
|
|
64
|
+
"StdioServer",
|
|
65
|
+
"apply",
|
|
66
|
+
"apply_all",
|
|
67
|
+
"apply_with_prune",
|
|
68
|
+
"default_state_path",
|
|
69
|
+
"detect_installed_platforms",
|
|
70
|
+
"diff",
|
|
71
|
+
"get_platform",
|
|
72
|
+
"hook_identity",
|
|
73
|
+
"known_platforms",
|
|
74
|
+
"load_json_object",
|
|
75
|
+
"load_manifest",
|
|
76
|
+
"load_state",
|
|
77
|
+
"write_json",
|
|
78
|
+
"write_state",
|
|
79
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Per-platform wire-format adapters — quirks live here, never in the canonical models."""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Vendored, codegen'd wire-format models — adapter-internal, not public API.
|
|
2
|
+
|
|
3
|
+
Generated with ``datamodel-code-generator`` (per spec D6) from real published
|
|
4
|
+
JSON Schemas (Claude Code's ``settings.json`` hooks section, OpenCode's
|
|
5
|
+
``config.json``) or, where no upstream schema exists (Pi, GitHub Copilot/VS
|
|
6
|
+
Code), a minimal hand-authored one. Regenerate on demand when upstream
|
|
7
|
+
schemas change — deliberately not enforced by CI, since roughly half the
|
|
8
|
+
v1 platforms have no live schema to diff against automatically.
|
|
9
|
+
|
|
10
|
+
Not imported by ``agent_config_kit``'s adapters at runtime; used only to
|
|
11
|
+
validate adapter output against real schema shape (see
|
|
12
|
+
``tests/test_wire_validation.py``) and as a reference for future adapter
|
|
13
|
+
work.
|
|
14
|
+
"""
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# generated by datamodel-codegen:
|
|
2
|
+
# filename: claude-hooks.schema.json
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field, PositiveFloat, RootModel, constr
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Shell(StrEnum):
|
|
13
|
+
bash = "bash"
|
|
14
|
+
powershell = "powershell"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HookCommand1(BaseModel):
|
|
18
|
+
model_config = ConfigDict(
|
|
19
|
+
extra="forbid",
|
|
20
|
+
)
|
|
21
|
+
type: Literal["command"] = Field(..., description="Hook type")
|
|
22
|
+
command: constr(min_length=1) = Field(..., description="Shell command to execute")
|
|
23
|
+
timeout: PositiveFloat | None = Field(
|
|
24
|
+
None, description="Optional timeout in seconds"
|
|
25
|
+
)
|
|
26
|
+
async_: bool | None = Field(
|
|
27
|
+
None,
|
|
28
|
+
alias="async",
|
|
29
|
+
description="Run this hook asynchronously without blocking Claude Code",
|
|
30
|
+
)
|
|
31
|
+
asyncRewake: bool | None = Field(
|
|
32
|
+
None,
|
|
33
|
+
description="When true, the hook runs in the background and wakes the model when it exits with code 2. Implies async.",
|
|
34
|
+
)
|
|
35
|
+
shell: Shell | None = Field(
|
|
36
|
+
None,
|
|
37
|
+
description='Shell interpreter for the command. "bash" uses the login shell (bash/zsh/sh); "powershell" uses pwsh. Defaults to bash.',
|
|
38
|
+
)
|
|
39
|
+
if_: str | None = Field(
|
|
40
|
+
None,
|
|
41
|
+
alias="if",
|
|
42
|
+
description='Optional permission-rule-syntax filter (e.g., "Bash(git *)"). Evaluated only on tool-related events (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied); on other events a hook with `if` set never runs. See https://code.claude.com/docs/en/hooks-guide#filter-hooks-with-matchers',
|
|
43
|
+
)
|
|
44
|
+
statusMessage: str | None = Field(
|
|
45
|
+
None, description="Custom spinner message displayed while the hook runs"
|
|
46
|
+
)
|
|
47
|
+
args: list[str] | None = Field(
|
|
48
|
+
None,
|
|
49
|
+
description="Argument list for exec form. When present, spawns the command directly without shell interpretation — each element is passed as-is, so path placeholders never need quoting. See https://code.claude.com/docs/en/hooks#command-hook-fields",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class HookCommand2(BaseModel):
|
|
54
|
+
model_config = ConfigDict(
|
|
55
|
+
extra="forbid",
|
|
56
|
+
)
|
|
57
|
+
type: Literal["prompt"] = Field(..., description="Hook type")
|
|
58
|
+
prompt: constr(min_length=1) = Field(
|
|
59
|
+
...,
|
|
60
|
+
description="Prompt to evaluate with LLM. Use $ARGUMENTS placeholder for hook input JSON.",
|
|
61
|
+
)
|
|
62
|
+
model: str | None = Field(
|
|
63
|
+
None, description="Model to use for evaluation. Defaults to a fast model"
|
|
64
|
+
)
|
|
65
|
+
timeout: PositiveFloat | None = Field(
|
|
66
|
+
None, description="Optional timeout in seconds (default: 30)"
|
|
67
|
+
)
|
|
68
|
+
if_: str | None = Field(
|
|
69
|
+
None,
|
|
70
|
+
alias="if",
|
|
71
|
+
description="Optional permission-rule-syntax filter. Evaluated only on tool-related events (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied); on other events a hook with `if` set never runs. See https://code.claude.com/docs/en/hooks-guide#filter-hooks-with-matchers",
|
|
72
|
+
)
|
|
73
|
+
statusMessage: str | None = Field(
|
|
74
|
+
None, description="Custom spinner message displayed while the hook runs"
|
|
75
|
+
)
|
|
76
|
+
continueOnBlock: bool | None = Field(
|
|
77
|
+
False,
|
|
78
|
+
description='When the prompt returns ok: false, feed the reason back to Claude and continue the turn instead of stopping. Implemented as continue: true on the resulting decision: "block". See https://code.claude.com/docs/en/hooks#prompt-hook-configuration',
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class HookCommand3(BaseModel):
|
|
83
|
+
model_config = ConfigDict(
|
|
84
|
+
extra="forbid",
|
|
85
|
+
)
|
|
86
|
+
type: Literal["agent"] = Field(..., description="Hook type")
|
|
87
|
+
prompt: constr(min_length=1) = Field(
|
|
88
|
+
...,
|
|
89
|
+
description="Prompt describing what to verify. Use $ARGUMENTS placeholder for hook input JSON.",
|
|
90
|
+
)
|
|
91
|
+
model: str | None = Field(
|
|
92
|
+
None, description="Model to use for evaluation. Defaults to a fast model"
|
|
93
|
+
)
|
|
94
|
+
timeout: PositiveFloat | None = Field(
|
|
95
|
+
None, description="Optional timeout in seconds (default: 60)"
|
|
96
|
+
)
|
|
97
|
+
if_: str | None = Field(
|
|
98
|
+
None,
|
|
99
|
+
alias="if",
|
|
100
|
+
description="Optional permission-rule-syntax filter. Evaluated only on tool-related events (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied); on other events a hook with `if` set never runs. See https://code.claude.com/docs/en/hooks-guide#filter-hooks-with-matchers",
|
|
101
|
+
)
|
|
102
|
+
statusMessage: str | None = Field(
|
|
103
|
+
None, description="Custom spinner message displayed while the hook runs"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class HookCommand4(BaseModel):
|
|
108
|
+
model_config = ConfigDict(
|
|
109
|
+
extra="forbid",
|
|
110
|
+
)
|
|
111
|
+
type: Literal["http"] = Field(..., description="Hook type")
|
|
112
|
+
url: constr(min_length=1) = Field(
|
|
113
|
+
...,
|
|
114
|
+
description="URL to POST hook input JSON to. Endpoint must accept POST requests and return JSON.",
|
|
115
|
+
)
|
|
116
|
+
headers: dict[str, str] | None = Field(
|
|
117
|
+
None,
|
|
118
|
+
description="Custom HTTP headers (e.g., Authorization: Bearer token). Values support $VAR_NAME or ${VAR_NAME} interpolation.",
|
|
119
|
+
)
|
|
120
|
+
allowedEnvVars: list[constr(min_length=1)] | None = Field(
|
|
121
|
+
None,
|
|
122
|
+
description="List of environment variable names permitted for interpolation in headers. If not set, no env var interpolation is allowed.",
|
|
123
|
+
)
|
|
124
|
+
timeout: PositiveFloat | None = Field(
|
|
125
|
+
None, description="Optional timeout in seconds (default: 30)"
|
|
126
|
+
)
|
|
127
|
+
if_: str | None = Field(
|
|
128
|
+
None,
|
|
129
|
+
alias="if",
|
|
130
|
+
description="Optional permission-rule-syntax filter. Evaluated only on tool-related events (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied); on other events a hook with `if` set never runs. See https://code.claude.com/docs/en/hooks-guide#filter-hooks-with-matchers",
|
|
131
|
+
)
|
|
132
|
+
statusMessage: str | None = Field(
|
|
133
|
+
None, description="Custom spinner message displayed while the hook runs"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class HookCommand5(BaseModel):
|
|
138
|
+
model_config = ConfigDict(
|
|
139
|
+
extra="forbid",
|
|
140
|
+
)
|
|
141
|
+
type: Literal["mcp_tool"] = Field(..., description="Hook type")
|
|
142
|
+
server: constr(min_length=1) = Field(
|
|
143
|
+
..., description="Name of a configured MCP server (must already be connected)"
|
|
144
|
+
)
|
|
145
|
+
tool: constr(min_length=1) = Field(
|
|
146
|
+
..., description="Name of the tool to call on that server"
|
|
147
|
+
)
|
|
148
|
+
input: dict[str, Any] | None = Field(
|
|
149
|
+
None,
|
|
150
|
+
description="Arguments passed to the tool. String values support ${path} substitution from hook JSON input (e.g., ${tool_input.file_path}, ${cwd})",
|
|
151
|
+
)
|
|
152
|
+
timeout: PositiveFloat | None = Field(
|
|
153
|
+
None, description="Optional timeout in seconds (default: 60)"
|
|
154
|
+
)
|
|
155
|
+
if_: str | None = Field(
|
|
156
|
+
None,
|
|
157
|
+
alias="if",
|
|
158
|
+
description="Optional permission-rule-syntax filter. Evaluated only on tool-related events (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied); on other events a hook with `if` set never runs. See https://code.claude.com/docs/en/hooks-guide#filter-hooks-with-matchers",
|
|
159
|
+
)
|
|
160
|
+
statusMessage: str | None = Field(
|
|
161
|
+
None, description="Custom spinner message displayed while the hook runs"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class HookCommand(
|
|
166
|
+
RootModel[HookCommand1 | HookCommand2 | HookCommand3 | HookCommand4 | HookCommand5]
|
|
167
|
+
):
|
|
168
|
+
root: HookCommand1 | HookCommand2 | HookCommand3 | HookCommand4 | HookCommand5
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class HookMatcher(BaseModel):
|
|
172
|
+
model_config = ConfigDict(
|
|
173
|
+
extra="forbid",
|
|
174
|
+
)
|
|
175
|
+
matcher: str | None = Field(
|
|
176
|
+
None,
|
|
177
|
+
description="Optional pattern to match event contexts, case-sensitive. Behavior depends on event type. See https://code.claude.com/docs/en/hooks#matcher-patterns for event-specific details and examples",
|
|
178
|
+
)
|
|
179
|
+
hooks: list[HookCommand] = Field(..., description="Array of hooks to execute")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Hooks(BaseModel):
|
|
183
|
+
model_config = ConfigDict(
|
|
184
|
+
extra="forbid",
|
|
185
|
+
)
|
|
186
|
+
PreToolUse: list[HookMatcher] | None = Field(
|
|
187
|
+
None, description="Hooks that run before tool calls"
|
|
188
|
+
)
|
|
189
|
+
PostToolUse: list[HookMatcher] | None = Field(
|
|
190
|
+
None, description="Hooks that run after tool completion"
|
|
191
|
+
)
|
|
192
|
+
PostToolUseFailure: list[HookMatcher] | None = Field(
|
|
193
|
+
None, description="Hooks that run after a tool fails"
|
|
194
|
+
)
|
|
195
|
+
PermissionRequest: list[HookMatcher] | None = Field(
|
|
196
|
+
None, description="Hooks that run when a permission dialog appears"
|
|
197
|
+
)
|
|
198
|
+
Notification: list[HookMatcher] | None = Field(
|
|
199
|
+
None, description="Hooks that trigger on notifications"
|
|
200
|
+
)
|
|
201
|
+
UserPromptSubmit: list[HookMatcher] | None = Field(
|
|
202
|
+
None, description="Hooks that run when a user submits a prompt"
|
|
203
|
+
)
|
|
204
|
+
Stop: list[HookMatcher] | None = Field(
|
|
205
|
+
None,
|
|
206
|
+
description="Hooks that run when agents finish responding. Does not run on user interrupt",
|
|
207
|
+
)
|
|
208
|
+
StopFailure: list[HookMatcher] | None = Field(
|
|
209
|
+
None,
|
|
210
|
+
description="Hooks that run when a turn ends due to an API error (e.g., rate_limit, authentication_failed, billing_error, invalid_request, server_error, max_output_tokens, unknown). Matcher can scope to specific error types. Hook output and exit code are ignored. See https://code.claude.com/docs/en/hooks",
|
|
211
|
+
)
|
|
212
|
+
SubagentStart: list[HookMatcher] | None = Field(
|
|
213
|
+
None, description="Hooks that run when a subagent is spawned"
|
|
214
|
+
)
|
|
215
|
+
SubagentStop: list[HookMatcher] | None = Field(
|
|
216
|
+
None, description="Hooks that run when subagents finish responding"
|
|
217
|
+
)
|
|
218
|
+
PreCompact: list[HookMatcher] | None = Field(
|
|
219
|
+
None, description="Hooks that run before the context is compacted"
|
|
220
|
+
)
|
|
221
|
+
PostCompact: list[HookMatcher] | None = Field(
|
|
222
|
+
None,
|
|
223
|
+
description="Hooks that run after the context is compacted. See https://code.claude.com/docs/en/hooks",
|
|
224
|
+
)
|
|
225
|
+
Elicitation: list[HookMatcher] | None = Field(
|
|
226
|
+
None,
|
|
227
|
+
description="Hooks that run when an MCP server requests user input during a tool call. See https://code.claude.com/docs/en/hooks",
|
|
228
|
+
)
|
|
229
|
+
ElicitationResult: list[HookMatcher] | None = Field(
|
|
230
|
+
None,
|
|
231
|
+
description="Hooks that run after a user responds to an MCP elicitation, before the response is sent back to the server. See https://code.claude.com/docs/en/hooks",
|
|
232
|
+
)
|
|
233
|
+
TeammateIdle: list[HookMatcher] | None = Field(
|
|
234
|
+
None,
|
|
235
|
+
description="Hooks that run when an agent team teammate is about to go idle. Exit code 2 sends feedback and keeps the teammate working. Does not support matchers. Agent teams are experimental. See https://code.claude.com/docs/en/hooks#teammateidle",
|
|
236
|
+
)
|
|
237
|
+
TaskCompleted: list[HookMatcher] | None = Field(
|
|
238
|
+
None,
|
|
239
|
+
description="Hooks that run when a task is being marked as completed. Exit code 2 prevents completion and sends feedback. Does not support matchers. See https://code.claude.com/docs/en/hooks#taskcompleted",
|
|
240
|
+
)
|
|
241
|
+
Setup: list[HookMatcher] | None = Field(
|
|
242
|
+
None,
|
|
243
|
+
description="UNDOCUMENTED. Hooks that run during repository initialization (--init, --init-only) or maintenance (--maintenance)",
|
|
244
|
+
)
|
|
245
|
+
InstructionsLoaded: list[HookMatcher] | None = Field(
|
|
246
|
+
None,
|
|
247
|
+
description="Hooks that run when a CLAUDE.md or .claude/rules/*.md file is loaded into context. Fires at session start and when files are lazily loaded (e.g., nested traversal, path glob match). No decision control; used for audit logging and observability. Does not support matchers. See https://code.claude.com/docs/en/hooks#instructionsloaded",
|
|
248
|
+
)
|
|
249
|
+
CwdChanged: list[HookMatcher] | None = Field(
|
|
250
|
+
None,
|
|
251
|
+
description="Hooks that run when the working directory changes. Provides cwd (new directory) and previous_cwd. Matchers are ignored; fires on every directory change. See https://code.claude.com/docs/en/hooks#cwdchanged",
|
|
252
|
+
)
|
|
253
|
+
FileChanged: list[HookMatcher] | None = Field(
|
|
254
|
+
None,
|
|
255
|
+
description="Hooks that run when a watched file is created, modified, or deleted. Supports filename matchers. Provides file_path and file_event_type (created, modified, deleted). See https://code.claude.com/docs/en/hooks#filechanged",
|
|
256
|
+
)
|
|
257
|
+
ConfigChange: list[HookMatcher] | None = Field(
|
|
258
|
+
None,
|
|
259
|
+
description="Hooks that run when settings, managed settings, or skill files change during a session. Supports matchers: user_settings, project_settings, local_settings, policy_settings, skills. Command handlers only. Exit code 2 blocks the change (except policy_settings which is audit-only). See https://code.claude.com/docs/en/hooks#configchange",
|
|
260
|
+
)
|
|
261
|
+
WorktreeCreate: list[HookMatcher] | None = Field(
|
|
262
|
+
None,
|
|
263
|
+
description='Hooks that run when a worktree is created via --worktree or isolation: "worktree" in subagents. Command handlers only, no matchers. Hook must print absolute path to created worktree on stdout; non-zero exit fails creation. See https://code.claude.com/docs/en/hooks#worktreecreate',
|
|
264
|
+
)
|
|
265
|
+
WorktreeRemove: list[HookMatcher] | None = Field(
|
|
266
|
+
None,
|
|
267
|
+
description="Hooks that run when a worktree is being removed at session exit or when a subagent finishes. Command handlers only, no matchers. Used for cleanup tasks; cannot block removal. See https://code.claude.com/docs/en/hooks#worktreeremove",
|
|
268
|
+
)
|
|
269
|
+
SessionStart: list[HookMatcher] | None = Field(
|
|
270
|
+
None, description="Hooks that run when a new session starts"
|
|
271
|
+
)
|
|
272
|
+
SessionEnd: list[HookMatcher] | None = Field(
|
|
273
|
+
None, description="Hooks that run when a session ends"
|
|
274
|
+
)
|
|
275
|
+
PostToolBatch: list[HookMatcher] | None = Field(
|
|
276
|
+
None,
|
|
277
|
+
description="Hooks that run after a full batch of parallel tool calls resolves, before the next model call. Exit code 2 blocks the agentic loop. Does not support matchers. See https://code.claude.com/docs/en/hooks",
|
|
278
|
+
)
|
|
279
|
+
TaskCreated: list[HookMatcher] | None = Field(
|
|
280
|
+
None,
|
|
281
|
+
description="Hooks that run when a task is being created via TaskCreate. Exit code 2 rolls back task creation. Does not support matchers. See https://code.claude.com/docs/en/hooks#taskcreated",
|
|
282
|
+
)
|
|
283
|
+
PermissionDenied: list[HookMatcher] | None = Field(
|
|
284
|
+
None,
|
|
285
|
+
description="Hooks that run when a tool call is denied by the auto mode classifier. Supports matchers on tool name. See https://code.claude.com/docs/en/hooks",
|
|
286
|
+
)
|
|
287
|
+
UserPromptExpansion: list[HookMatcher] | None = Field(
|
|
288
|
+
None,
|
|
289
|
+
description="Hooks that run when a user-typed command expands into a prompt, before it reaches Claude. Exit code 2 blocks the expansion. Supports matchers on command name. See https://code.claude.com/docs/en/hooks",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class ClaudeCodeHooksConfig(BaseModel):
|
|
294
|
+
hooks: Hooks | None = Field(
|
|
295
|
+
None,
|
|
296
|
+
description="Custom commands to run before/after tool executions. See https://code.claude.com/docs/en/hooks",
|
|
297
|
+
examples=[
|
|
298
|
+
{
|
|
299
|
+
"PostToolUse": [
|
|
300
|
+
{
|
|
301
|
+
"matcher": "Edit|Write",
|
|
302
|
+
"hooks": [
|
|
303
|
+
{
|
|
304
|
+
"type": "command",
|
|
305
|
+
"command": "prettier --write",
|
|
306
|
+
"timeout": 5,
|
|
307
|
+
}
|
|
308
|
+
],
|
|
309
|
+
}
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
],
|
|
313
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# generated by datamodel-codegen:
|
|
2
|
+
# filename: copilot-mcp.schema.json
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Type(StrEnum):
|
|
13
|
+
stdio = "stdio"
|
|
14
|
+
sse = "sse"
|
|
15
|
+
http = "http"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CopilotMcpServer(BaseModel):
|
|
19
|
+
model_config = ConfigDict(
|
|
20
|
+
extra="forbid",
|
|
21
|
+
)
|
|
22
|
+
type: Type
|
|
23
|
+
command: str | None = None
|
|
24
|
+
args: list[str] | None = None
|
|
25
|
+
url: str | None = None
|
|
26
|
+
inputs: list[dict[str, Any]] | None = None
|
|
27
|
+
env: dict[str, str] | None = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CopilotMcpConfig(BaseModel):
|
|
31
|
+
model_config = ConfigDict(
|
|
32
|
+
extra="forbid",
|
|
33
|
+
)
|
|
34
|
+
servers: dict[str, CopilotMcpServer] | None = None
|