claude-code-capabilities 0.1.2__py3-none-any.whl
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.
- capdisc/__init__.py +69 -0
- capdisc/base.py +56 -0
- capdisc/catalog/__init__.py +71 -0
- capdisc/catalog/entries.py +104 -0
- capdisc/catalog/types.py +227 -0
- capdisc/discovery.py +278 -0
- capdisc/frontmatter.py +49 -0
- capdisc/hooks/__init__.py +61 -0
- capdisc/hooks/schema.py +216 -0
- capdisc/hooks/types.py +156 -0
- capdisc/html.py +75 -0
- capdisc/mcp_catalog.py +87 -0
- capdisc/mcp_harvest/__init__.py +17 -0
- capdisc/mcp_harvest/auth.py +120 -0
- capdisc/mcp_harvest/cache.py +144 -0
- capdisc/mcp_harvest/config.py +325 -0
- capdisc/mcp_harvest/connect.py +187 -0
- capdisc/mcp_harvest/types.py +29 -0
- capdisc/plugin_catalog.py +308 -0
- capdisc/py.typed +0 -0
- capdisc/report/__init__.py +36 -0
- capdisc/report/__main__.py +6 -0
- capdisc/report/assets.py +138 -0
- capdisc/report/cards.py +109 -0
- capdisc/report/cli.py +35 -0
- capdisc/report/components.py +144 -0
- capdisc/report/harvest.py +150 -0
- capdisc/report/html.py +79 -0
- capdisc/report/models.py +82 -0
- capdisc/report/page.py +129 -0
- capdisc/report/sections.py +154 -0
- capdisc/report/types.py +43 -0
- capdisc/scope/__init__.py +83 -0
- capdisc/scope/inventory/__init__.py +20 -0
- capdisc/scope/inventory/assets.py +54 -0
- capdisc/scope/inventory/capture.py +219 -0
- capdisc/scope/inventory/render.py +149 -0
- capdisc/scope/inventory/roots.py +175 -0
- capdisc/scope/locations.py +330 -0
- capdisc/scope/types.py +154 -0
- capdisc/settings.py +230 -0
- capdisc/tokens.py +48 -0
- claude_code_capabilities-0.1.2.dist-info/METADATA +136 -0
- claude_code_capabilities-0.1.2.dist-info/RECORD +47 -0
- claude_code_capabilities-0.1.2.dist-info/WHEEL +4 -0
- claude_code_capabilities-0.1.2.dist-info/entry_points.txt +2 -0
- claude_code_capabilities-0.1.2.dist-info/licenses/LICENSE +21 -0
capdisc/__init__.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Discovery of Claude Code capabilities: skills, agents, plugins, MCP servers, hooks.
|
|
2
|
+
|
|
3
|
+
Scans the environment's scope roots into a typed inventory, harvests plugin and MCP
|
|
4
|
+
metadata, and assembles capability catalogs and the environment report.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from .catalog import (
|
|
10
|
+
Catalog,
|
|
11
|
+
CatalogEntry,
|
|
12
|
+
CatalogMcpServer,
|
|
13
|
+
CatalogPlugin,
|
|
14
|
+
CatalogSkill,
|
|
15
|
+
CatalogTool,
|
|
16
|
+
McpTool,
|
|
17
|
+
)
|
|
18
|
+
from .discovery import BUILTIN_TOOLS, scan_environment, scan_indexed_skills, scan_skills
|
|
19
|
+
from .mcp_catalog import enumerate_mcp_servers
|
|
20
|
+
from .mcp_harvest import (
|
|
21
|
+
cache_is_stale,
|
|
22
|
+
read_mcp_cache,
|
|
23
|
+
refresh_in_background,
|
|
24
|
+
refresh_mcp_cache,
|
|
25
|
+
write_mcp_cache,
|
|
26
|
+
)
|
|
27
|
+
from .plugin_catalog import (
|
|
28
|
+
enumerate_plugins,
|
|
29
|
+
enumerate_plugins_with_paths,
|
|
30
|
+
installed_plugin_dirs,
|
|
31
|
+
installed_plugins,
|
|
32
|
+
)
|
|
33
|
+
from .report import EnvironmentReport, build_report, write_report, write_report_on_start
|
|
34
|
+
from .scope import ScopeInventory, ScopeRoots, default_managed_dir
|
|
35
|
+
from .settings import DiscoverySettings, ExtraSourceDir, get_settings
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"BUILTIN_TOOLS",
|
|
39
|
+
"Catalog",
|
|
40
|
+
"CatalogEntry",
|
|
41
|
+
"CatalogMcpServer",
|
|
42
|
+
"CatalogPlugin",
|
|
43
|
+
"CatalogSkill",
|
|
44
|
+
"CatalogTool",
|
|
45
|
+
"DiscoverySettings",
|
|
46
|
+
"EnvironmentReport",
|
|
47
|
+
"ExtraSourceDir",
|
|
48
|
+
"McpTool",
|
|
49
|
+
"ScopeInventory",
|
|
50
|
+
"ScopeRoots",
|
|
51
|
+
"build_report",
|
|
52
|
+
"cache_is_stale",
|
|
53
|
+
"default_managed_dir",
|
|
54
|
+
"enumerate_mcp_servers",
|
|
55
|
+
"enumerate_plugins",
|
|
56
|
+
"enumerate_plugins_with_paths",
|
|
57
|
+
"get_settings",
|
|
58
|
+
"installed_plugin_dirs",
|
|
59
|
+
"installed_plugins",
|
|
60
|
+
"read_mcp_cache",
|
|
61
|
+
"refresh_in_background",
|
|
62
|
+
"refresh_mcp_cache",
|
|
63
|
+
"scan_environment",
|
|
64
|
+
"scan_indexed_skills",
|
|
65
|
+
"scan_skills",
|
|
66
|
+
"write_mcp_cache",
|
|
67
|
+
"write_report",
|
|
68
|
+
"write_report_on_start",
|
|
69
|
+
]
|
capdisc/base.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
from pydantic.alias_generators import to_camel
|
|
7
|
+
from pydantic.fields import ComputedFieldInfo, FieldInfo
|
|
8
|
+
|
|
9
|
+
_CAMEL_BOUNDARY = re.compile(r"(?<=[a-z0-9])(?=[A-Z])")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def humanize_field_title(field_name: str, _info: FieldInfo | ComputedFieldInfo) -> str:
|
|
13
|
+
"""A clean, alias-independent JSON-Schema title from a field's Python name.
|
|
14
|
+
|
|
15
|
+
Splits snake_case, kebab-case, and camelCase into words and renders them sentence-cased
|
|
16
|
+
(`status_message` -> `Status message`). Avoids the mangled titles Pydantic derives from a
|
|
17
|
+
camelCase/hyphenated alias when a schema is exported `by_alias` (`statusMessage` -> the broken
|
|
18
|
+
`Statusmessage`). Set as `field_title_generator`, so it drives the field-level `title` only;
|
|
19
|
+
a domain-primitive alias's own `title` on the inner type is untouched.
|
|
20
|
+
"""
|
|
21
|
+
spaced = _CAMEL_BOUNDARY.sub(" ", field_name.replace("_", " ").replace("-", " "))
|
|
22
|
+
return " ".join(spaced.split()).capitalize()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FrozenModel(BaseModel):
|
|
26
|
+
"""Immutable, strict domain model — the single config preset for this package."""
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FrozenWireModel(BaseModel):
|
|
32
|
+
"""Immutable model whose snake_case fields bind to the camelCase JSON keys of a Claude Code
|
|
33
|
+
config file — accepts either spelling on load, emits camelCase on dump. Tolerant of unknown
|
|
34
|
+
keys (`extra="ignore"`): this is the ingest boundary for config written by a Claude Code that
|
|
35
|
+
may be newer than us, so a field we don't model yet is dropped, never a parse failure."""
|
|
36
|
+
|
|
37
|
+
model_config = ConfigDict(
|
|
38
|
+
extra="ignore",
|
|
39
|
+
frozen=True,
|
|
40
|
+
alias_generator=to_camel,
|
|
41
|
+
populate_by_name=True,
|
|
42
|
+
serialize_by_alias=True,
|
|
43
|
+
field_title_generator=humanize_field_title,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MutableModel(BaseModel):
|
|
48
|
+
"""Strict but mutable domain model — for in-place accumulators that fill across a loop."""
|
|
49
|
+
|
|
50
|
+
model_config = ConfigDict(extra="forbid")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class InputModel(BaseModel):
|
|
54
|
+
"""Lenient boundary model — ignores unknown keys from external sources."""
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(extra="ignore")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .entries import (
|
|
4
|
+
Catalog,
|
|
5
|
+
CatalogEntry,
|
|
6
|
+
CatalogMcpServer,
|
|
7
|
+
CatalogPlugin,
|
|
8
|
+
CatalogSkill,
|
|
9
|
+
CatalogTool,
|
|
10
|
+
McpTool,
|
|
11
|
+
)
|
|
12
|
+
from .types import (
|
|
13
|
+
CATALOG_ID_MAX,
|
|
14
|
+
DEFAULT_RECALL_LIMIT,
|
|
15
|
+
DESCRIPTION_TOKEN_MAX,
|
|
16
|
+
MCP_INPUT_SCHEMA_MAX_BYTES,
|
|
17
|
+
MCP_TOOL_DESCRIPTION_MAX,
|
|
18
|
+
MCP_TOOL_PARAMS_MAX,
|
|
19
|
+
RELEVANCE_THRESHOLD,
|
|
20
|
+
BuiltinTool,
|
|
21
|
+
CatalogEntryId,
|
|
22
|
+
CatalogIdPrefix,
|
|
23
|
+
EntryDescription,
|
|
24
|
+
McpInputSchema,
|
|
25
|
+
McpServerRef,
|
|
26
|
+
McpToolCount,
|
|
27
|
+
McpToolName,
|
|
28
|
+
NeedsNetworkFlag,
|
|
29
|
+
PluginRef,
|
|
30
|
+
ReadOnlyFlag,
|
|
31
|
+
RecallLimit,
|
|
32
|
+
RelevanceScore,
|
|
33
|
+
SkillRef,
|
|
34
|
+
Tag,
|
|
35
|
+
ToolRef,
|
|
36
|
+
catalog_id,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"CATALOG_ID_MAX",
|
|
41
|
+
"DEFAULT_RECALL_LIMIT",
|
|
42
|
+
"DESCRIPTION_TOKEN_MAX",
|
|
43
|
+
"MCP_INPUT_SCHEMA_MAX_BYTES",
|
|
44
|
+
"MCP_TOOL_DESCRIPTION_MAX",
|
|
45
|
+
"MCP_TOOL_PARAMS_MAX",
|
|
46
|
+
"RELEVANCE_THRESHOLD",
|
|
47
|
+
"BuiltinTool",
|
|
48
|
+
"Catalog",
|
|
49
|
+
"CatalogEntry",
|
|
50
|
+
"CatalogEntryId",
|
|
51
|
+
"CatalogIdPrefix",
|
|
52
|
+
"CatalogMcpServer",
|
|
53
|
+
"CatalogPlugin",
|
|
54
|
+
"CatalogSkill",
|
|
55
|
+
"CatalogTool",
|
|
56
|
+
"EntryDescription",
|
|
57
|
+
"McpInputSchema",
|
|
58
|
+
"McpServerRef",
|
|
59
|
+
"McpTool",
|
|
60
|
+
"McpToolCount",
|
|
61
|
+
"McpToolName",
|
|
62
|
+
"NeedsNetworkFlag",
|
|
63
|
+
"PluginRef",
|
|
64
|
+
"ReadOnlyFlag",
|
|
65
|
+
"RecallLimit",
|
|
66
|
+
"RelevanceScore",
|
|
67
|
+
"SkillRef",
|
|
68
|
+
"Tag",
|
|
69
|
+
"ToolRef",
|
|
70
|
+
"catalog_id",
|
|
71
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from ..base import FrozenModel
|
|
8
|
+
from .types import (
|
|
9
|
+
MCP_TOOL_PARAMS_MAX,
|
|
10
|
+
CatalogEntryId,
|
|
11
|
+
EntryDescription,
|
|
12
|
+
McpInputSchema,
|
|
13
|
+
McpServerRef,
|
|
14
|
+
McpToolDescription,
|
|
15
|
+
McpToolName,
|
|
16
|
+
NeedsNetworkFlag,
|
|
17
|
+
PluginRef,
|
|
18
|
+
ReadOnlyFlag,
|
|
19
|
+
SkillRef,
|
|
20
|
+
Tag,
|
|
21
|
+
ToolRef,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _Card(FrozenModel):
|
|
26
|
+
"""Fields every catalog entry shares: its id, description, and recall tags."""
|
|
27
|
+
|
|
28
|
+
id: CatalogEntryId
|
|
29
|
+
description: EntryDescription
|
|
30
|
+
tags: list[Tag] = []
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def search_text(self) -> str:
|
|
34
|
+
"""The text recall ranks this entry on — its description and tags."""
|
|
35
|
+
return " ".join((self.description, *self.tags))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class McpTool(FrozenModel):
|
|
39
|
+
"""One tool a connected MCP server provides, harvested from its `list_tools()`: its name,
|
|
40
|
+
input-parameter names, full input schema, and description — the content that lets recall
|
|
41
|
+
rank a server by what its tools actually do, not just its name."""
|
|
42
|
+
|
|
43
|
+
name: McpToolName
|
|
44
|
+
description: McpToolDescription = ""
|
|
45
|
+
params: list[McpToolName] = Field(default_factory=list, max_length=MCP_TOOL_PARAMS_MAX)
|
|
46
|
+
input_schema: McpInputSchema | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _Tool(_Card):
|
|
50
|
+
"""Shared execution-constraint facets (`read_only`, `needs_network`) for tool-like entries."""
|
|
51
|
+
|
|
52
|
+
ref: ToolRef
|
|
53
|
+
read_only: ReadOnlyFlag = True
|
|
54
|
+
needs_network: NeedsNetworkFlag = False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CatalogSkill(_Card):
|
|
58
|
+
"""A skill an agent can load."""
|
|
59
|
+
|
|
60
|
+
kind: Literal["skill"] = "skill"
|
|
61
|
+
ref: SkillRef
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CatalogTool(_Tool):
|
|
65
|
+
"""A built-in tool an agent can be granted, with its execution-constraint facets."""
|
|
66
|
+
|
|
67
|
+
kind: Literal["tool"] = "tool"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class CatalogMcpServer(_Card):
|
|
71
|
+
"""A connected MCP server an agent can be wired to, with its harvested tools."""
|
|
72
|
+
|
|
73
|
+
kind: Literal["mcp_server"] = "mcp_server"
|
|
74
|
+
ref: McpServerRef
|
|
75
|
+
tools: list[McpTool] = []
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def search_text(self) -> str:
|
|
79
|
+
"""The server's own description/tags plus each harvested tool's name, parameters, and
|
|
80
|
+
description — so a task matches what the server's tools do, fixing name-only ranking."""
|
|
81
|
+
tools = " ".join(f"{t.name} {' '.join(t.params)} {t.description}" for t in self.tools)
|
|
82
|
+
return f"{super().search_text} {tools}".strip()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class CatalogPlugin(_Card):
|
|
86
|
+
"""An installed plugin an agent can draw from, with the skills and MCP servers it bundles."""
|
|
87
|
+
|
|
88
|
+
kind: Literal["plugin"] = "plugin"
|
|
89
|
+
ref: PluginRef
|
|
90
|
+
skills: list[SkillRef] = []
|
|
91
|
+
mcp_servers: list[McpServerRef] = []
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
CatalogEntry = Annotated[
|
|
95
|
+
CatalogSkill | CatalogTool | CatalogMcpServer | CatalogPlugin,
|
|
96
|
+
Field(discriminator="kind"),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Catalog(FrozenModel):
|
|
101
|
+
"""The full set of capabilities — skills, tools, MCP servers, and plugins — the generator
|
|
102
|
+
can draw from when building an agent."""
|
|
103
|
+
|
|
104
|
+
entries: list[CatalogEntry]
|
capdisc/catalog/types.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from typing import Annotated, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import Field, JsonValue, TypeAdapter
|
|
8
|
+
from pydantic.functional_validators import AfterValidator, BeforeValidator
|
|
9
|
+
|
|
10
|
+
from ..tokens import token_bounds
|
|
11
|
+
|
|
12
|
+
DESCRIPTION_TOKEN_MAX = 512
|
|
13
|
+
CATALOG_ID_MAX = 64 # length cap of CatalogEntryId; catalog_id truncates to it
|
|
14
|
+
MCP_TOOL_DESCRIPTION_MAX = 4000 # length cap of McpToolDescription; harvest truncates to it
|
|
15
|
+
MCP_TOOL_PARAMS_MAX = 200 # item cap of McpTool.params
|
|
16
|
+
MCP_INPUT_SCHEMA_MAX_BYTES = 20_000 # serialized size cap of McpInputSchema
|
|
17
|
+
|
|
18
|
+
_INPUT_SCHEMA_ADAPTER: TypeAdapter[dict[str, JsonValue]] = TypeAdapter(dict[str, JsonValue])
|
|
19
|
+
|
|
20
|
+
_SLUG_SKILL = re.compile(r"[^a-z0-9:-]+") # skill refs keep ':' for plugin:skill
|
|
21
|
+
_SLUG_REF = re.compile(r"[^a-z0-9_-]+") # server and plugin refs keep '_'
|
|
22
|
+
_SLUG_ID = re.compile(r"[^a-z0-9._-]+") # catalog ids keep '.' for the 'kind.ref' shape
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _slugify(value: str, disallowed: re.Pattern[str], max_len: int) -> str:
|
|
26
|
+
"""Normalize an arbitrary name into a ref: lowercase, collapse disallowed runs to '-', trim
|
|
27
|
+
edge punctuation so it starts and ends on a word char, and truncate."""
|
|
28
|
+
return disallowed.sub("-", value.strip().lower()).strip("-:_")[:max_len]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _to_skill_ref(value: str) -> str:
|
|
32
|
+
"""Slugify a name into a `SkillRef`, keeping `:` for the `plugin:skill` form (max 128)."""
|
|
33
|
+
return _slugify(value, _SLUG_SKILL, 128)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _to_underscore_ref(value: str) -> str:
|
|
37
|
+
"""Slugify a name into a server/plugin ref, keeping `_` (max 64)."""
|
|
38
|
+
return _slugify(value, _SLUG_REF, 64)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
CatalogIdPrefix = Literal["plugin", "mcp", "skill"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def catalog_id(prefix: CatalogIdPrefix, ref: str) -> CatalogEntryId:
|
|
45
|
+
"""A valid `CatalogEntryId` for `prefix.ref` — folds ':' and overflow away so a valid ref
|
|
46
|
+
never yields an unrepresentable id. `CatalogEntryId` itself stays strict (it is a lookup
|
|
47
|
+
key); this is the one place a kind+ref is composed into an id."""
|
|
48
|
+
return _slugify(f"{prefix}.{ref}", _SLUG_ID, CATALOG_ID_MAX)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class BuiltinTool(StrEnum):
|
|
52
|
+
"""The grant refs of the built-in Claude Code tools — the single source for both the tool
|
|
53
|
+
catalog cards and the default tool set a generated agent receives."""
|
|
54
|
+
|
|
55
|
+
read = "Read"
|
|
56
|
+
write = "Write"
|
|
57
|
+
edit = "Edit"
|
|
58
|
+
glob = "Glob"
|
|
59
|
+
grep = "Grep"
|
|
60
|
+
bash = "Bash"
|
|
61
|
+
web_fetch = "WebFetch"
|
|
62
|
+
web_search = "WebSearch"
|
|
63
|
+
task = "Task"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
EntryDescription = Annotated[
|
|
67
|
+
str,
|
|
68
|
+
Field(
|
|
69
|
+
min_length=8,
|
|
70
|
+
max_length=1536,
|
|
71
|
+
title="Entry description",
|
|
72
|
+
description="What a catalog entry provides and when it applies.",
|
|
73
|
+
examples=["Reads a file from disk."],
|
|
74
|
+
),
|
|
75
|
+
token_bounds(DESCRIPTION_TOKEN_MAX),
|
|
76
|
+
]
|
|
77
|
+
ToolRef = Annotated[
|
|
78
|
+
str,
|
|
79
|
+
Field(
|
|
80
|
+
min_length=1,
|
|
81
|
+
max_length=200,
|
|
82
|
+
title="Tool ref",
|
|
83
|
+
description="A tool grant string, e.g. 'Read', 'Bash(git log:*)', or 'mcp__server__tool'.",
|
|
84
|
+
examples=["Read", "Bash(git log:*)"],
|
|
85
|
+
),
|
|
86
|
+
]
|
|
87
|
+
McpToolName = Annotated[
|
|
88
|
+
str,
|
|
89
|
+
Field(
|
|
90
|
+
pattern=r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$",
|
|
91
|
+
title="MCP tool name",
|
|
92
|
+
description="Name of a tool (or input parameter) provided by an MCP server.",
|
|
93
|
+
examples=["browser_navigate", "file_path"],
|
|
94
|
+
),
|
|
95
|
+
]
|
|
96
|
+
McpToolDescription = Annotated[
|
|
97
|
+
str,
|
|
98
|
+
Field(
|
|
99
|
+
max_length=MCP_TOOL_DESCRIPTION_MAX,
|
|
100
|
+
title="MCP tool description",
|
|
101
|
+
description="What an MCP tool does, as reported by the server's `list_tools()`.",
|
|
102
|
+
examples=["Navigate the browser to a url and wait for load."],
|
|
103
|
+
),
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _check_schema_size(value: dict[str, JsonValue]) -> dict[str, JsonValue]:
|
|
108
|
+
"""Reject a schema whose JSON serialization exceeds `MCP_INPUT_SCHEMA_MAX_BYTES` — an MCP
|
|
109
|
+
server's `list_tools()` response is untrusted input, and one tool's input schema must not be
|
|
110
|
+
able to inflate the on-disk cache without limit."""
|
|
111
|
+
if len(_INPUT_SCHEMA_ADAPTER.dump_json(value)) > MCP_INPUT_SCHEMA_MAX_BYTES:
|
|
112
|
+
raise ValueError(f"input schema exceeds {MCP_INPUT_SCHEMA_MAX_BYTES} bytes serialized")
|
|
113
|
+
return value
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
McpInputSchema = Annotated[
|
|
117
|
+
dict[str, JsonValue],
|
|
118
|
+
AfterValidator(_check_schema_size),
|
|
119
|
+
Field(
|
|
120
|
+
title="MCP tool input schema",
|
|
121
|
+
description=(
|
|
122
|
+
"Full JSON Schema object for an MCP tool's input, as reported by `list_tools()`."
|
|
123
|
+
),
|
|
124
|
+
examples=[
|
|
125
|
+
{
|
|
126
|
+
"type": "object",
|
|
127
|
+
"properties": {"url": {"type": "string"}},
|
|
128
|
+
"required": ["url"],
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
),
|
|
132
|
+
]
|
|
133
|
+
SkillRef = Annotated[
|
|
134
|
+
str,
|
|
135
|
+
BeforeValidator(_to_skill_ref),
|
|
136
|
+
Field(
|
|
137
|
+
pattern=r"^[a-z0-9][a-z0-9:-]{0,127}$",
|
|
138
|
+
title="Skill ref",
|
|
139
|
+
description="A skill name or plugin:skill reference; a raw name is slugified to one.",
|
|
140
|
+
examples=["error-handling", "my-plugin:my-skill"],
|
|
141
|
+
),
|
|
142
|
+
]
|
|
143
|
+
McpServerRef = Annotated[
|
|
144
|
+
str,
|
|
145
|
+
BeforeValidator(_to_underscore_ref),
|
|
146
|
+
Field(
|
|
147
|
+
pattern=r"^[a-z0-9][a-z0-9_-]{0,63}$",
|
|
148
|
+
title="MCP server",
|
|
149
|
+
description="An MCP server name; a raw name is slugified to one.",
|
|
150
|
+
examples=["playwright"],
|
|
151
|
+
),
|
|
152
|
+
]
|
|
153
|
+
PluginRef = Annotated[
|
|
154
|
+
str,
|
|
155
|
+
BeforeValidator(_to_underscore_ref),
|
|
156
|
+
Field(
|
|
157
|
+
pattern=r"^[a-z0-9][a-z0-9_-]{0,63}$",
|
|
158
|
+
title="Plugin",
|
|
159
|
+
description="An installed plugin name (marketplace suffix stripped); slugified.",
|
|
160
|
+
examples=["agentforge"],
|
|
161
|
+
),
|
|
162
|
+
]
|
|
163
|
+
Tag = Annotated[
|
|
164
|
+
str,
|
|
165
|
+
Field(
|
|
166
|
+
pattern=r"^[a-z0-9][a-z0-9-]{0,31}$",
|
|
167
|
+
title="Tag",
|
|
168
|
+
description="A keyword folded into a catalog entry's text for lexical recall.",
|
|
169
|
+
examples=["git"],
|
|
170
|
+
),
|
|
171
|
+
]
|
|
172
|
+
CatalogEntryId = Annotated[
|
|
173
|
+
str,
|
|
174
|
+
Field(
|
|
175
|
+
pattern=r"^[a-z0-9][a-z0-9._-]{0,63}$",
|
|
176
|
+
title="Catalog id",
|
|
177
|
+
description=(
|
|
178
|
+
"Stable id of one item in the catalog of skills, tools, and MCP servers an agent "
|
|
179
|
+
"can be built from. Strict — it is a lookup key; compose ids via catalog_id()."
|
|
180
|
+
),
|
|
181
|
+
examples=["tool.read"],
|
|
182
|
+
),
|
|
183
|
+
]
|
|
184
|
+
RelevanceScore = Annotated[
|
|
185
|
+
float,
|
|
186
|
+
Field(
|
|
187
|
+
ge=0.0,
|
|
188
|
+
le=1.0,
|
|
189
|
+
title="Relevance",
|
|
190
|
+
description="How well a catalog item matches the task, from 0 (no match) to 1 (best).",
|
|
191
|
+
),
|
|
192
|
+
]
|
|
193
|
+
RecallLimit = Annotated[
|
|
194
|
+
int,
|
|
195
|
+
Field(
|
|
196
|
+
ge=1,
|
|
197
|
+
le=200,
|
|
198
|
+
title="Recall limit",
|
|
199
|
+
description="Most matching catalog items the search step may return.",
|
|
200
|
+
),
|
|
201
|
+
]
|
|
202
|
+
McpToolCount = Annotated[
|
|
203
|
+
int,
|
|
204
|
+
Field(
|
|
205
|
+
ge=0,
|
|
206
|
+
title="MCP tool count",
|
|
207
|
+
description="Number of tools a connected MCP server provides.",
|
|
208
|
+
examples=[4],
|
|
209
|
+
),
|
|
210
|
+
]
|
|
211
|
+
ReadOnlyFlag = Annotated[
|
|
212
|
+
bool,
|
|
213
|
+
Field(
|
|
214
|
+
title="Read-only",
|
|
215
|
+
description="Whether the tool only reads state, never writes or executes.",
|
|
216
|
+
),
|
|
217
|
+
]
|
|
218
|
+
NeedsNetworkFlag = Annotated[
|
|
219
|
+
bool,
|
|
220
|
+
Field(
|
|
221
|
+
title="Needs network",
|
|
222
|
+
description="Whether the tool requires outbound network access to function.",
|
|
223
|
+
),
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
DEFAULT_RECALL_LIMIT = 30
|
|
227
|
+
RELEVANCE_THRESHOLD = 0.1
|