tylor-mcp 1.0.0
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.
- package/.aws-setup.sh +25 -0
- package/.claude-plugin/plugin.json +22 -0
- package/.mcp.json +12 -0
- package/AGENTS.md +93 -0
- package/CLAUDE.md +99 -0
- package/CLAUDE_PLATFORM_AWS_SETUP.md +105 -0
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/assets/tylor_logo.png +0 -0
- package/assets/tylor_threads_concept.png +0 -0
- package/bin/tylor.js +23 -0
- package/hooks/kill-thread-trigger.sh +7 -0
- package/hooks/post-tool-use-code-index.sh +7 -0
- package/hooks/session-checkpoint.sh +7 -0
- package/hooks/session-start.sh +7 -0
- package/install.py +401 -0
- package/install.sh +260 -0
- package/package.json +24 -0
- package/pytest.ini +2 -0
- package/registry.json +26 -0
- package/server/.env.example +24 -0
- package/server/__init__.py +0 -0
- package/server/config.py +89 -0
- package/server/main.py +93 -0
- package/server/personas/analyst.md +15 -0
- package/server/personas/ceo.md +14 -0
- package/server/personas/code_agent.md +15 -0
- package/server/personas/cto.md +14 -0
- package/server/provision.py +260 -0
- package/server/provision_opensearch.py +154 -0
- package/server/requirements.txt +26 -0
- package/server/storage/__init__.py +0 -0
- package/server/storage/dynamo.py +399 -0
- package/server/storage/json_store.py +359 -0
- package/server/storage/opensearch.py +194 -0
- package/server/storage/s3.py +96 -0
- package/server/storage/tests/__init__.py +0 -0
- package/server/storage/tests/test_dynamo.py +452 -0
- package/server/storage/tests/test_json_store.py +226 -0
- package/server/storage/tests/test_opensearch.py +270 -0
- package/server/storage/tests/test_s3.py +125 -0
- package/server/tests/__init__.py +0 -0
- package/server/tests/test_install.py +606 -0
- package/server/tests/test_isolation.py +90 -0
- package/server/tests/test_ui_server.py +385 -0
- package/server/tests/test_ui_shader_background.py +52 -0
- package/server/tests/test_ui_story_6_3.py +105 -0
- package/server/tools/__init__.py +0 -0
- package/server/tools/_mcp.py +4 -0
- package/server/tools/agents.py +160 -0
- package/server/tools/ecc/__init__.py +1 -0
- package/server/tools/ecc/data.py +35 -0
- package/server/tools/ecc/diagrams.py +23 -0
- package/server/tools/ecc/pipeline.py +24 -0
- package/server/tools/ecc/presentation.py +24 -0
- package/server/tools/ecc/web.py +23 -0
- package/server/tools/executor.py +880 -0
- package/server/tools/harness.py +330 -0
- package/server/tools/help.py +162 -0
- package/server/tools/hooks.py +357 -0
- package/server/tools/personas.py +110 -0
- package/server/tools/registry.py +195 -0
- package/server/tools/router.py +117 -0
- package/server/tools/skill_installer.py +230 -0
- package/server/tools/summarizer.py +168 -0
- package/server/tools/tests/__init__.py +0 -0
- package/server/tools/tests/test_agents.py +246 -0
- package/server/tools/tests/test_code_index.py +108 -0
- package/server/tools/tests/test_ecc_tools.py +51 -0
- package/server/tools/tests/test_executor.py +584 -0
- package/server/tools/tests/test_help_agent101.py +149 -0
- package/server/tools/tests/test_hooks.py +124 -0
- package/server/tools/tests/test_kill_thread.py +125 -0
- package/server/tools/tests/test_new_thread_list_threads.py +293 -0
- package/server/tools/tests/test_personas.py +52 -0
- package/server/tools/tests/test_recall_memory.py +55 -0
- package/server/tools/tests/test_registry_client.py +308 -0
- package/server/tools/tests/test_router.py +263 -0
- package/server/tools/tests/test_skill_installer.py +174 -0
- package/server/tools/tests/test_switch_thread.py +163 -0
- package/server/tools/tests/test_thread_command_skills.py +54 -0
- package/server/tools/tests/test_thread_resolver.py +165 -0
- package/server/tools/tests/test_tier1_schema.py +296 -0
- package/server/tools/thread_resolver.py +75 -0
- package/server/tools/tylor.py +374 -0
- package/server/tools/ui.py +38 -0
- package/server/ui_server.py +292 -0
- package/server/validate.py +237 -0
- package/skills/add-skill/SKILL.md +37 -0
- package/skills/afk-status/SKILL.md +20 -0
- package/skills/bmad/SKILL.md +14 -0
- package/skills/help-agent101/SKILL.md +48 -0
- package/skills/kill-thread/SKILL.md +35 -0
- package/skills/list-threads/SKILL.md +35 -0
- package/skills/new-thread/SKILL.md +35 -0
- package/skills/recall/SKILL.md +39 -0
- package/skills/run/SKILL.md +33 -0
- package/skills/set-sandbox/SKILL.md +38 -0
- package/skills/switch-thread/SKILL.md +38 -0
- package/ui/claude-logo.png +0 -0
- package/ui/index.html +1314 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""server/tools/agents.py — Tier 1 agent orchestration MCP tools."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import uuid
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from mcp.shared.exceptions import McpError
|
|
7
|
+
from mcp.types import ErrorData, INVALID_PARAMS
|
|
8
|
+
|
|
9
|
+
from .personas import list_persona_summaries, load_persona
|
|
10
|
+
from ._mcp import mcp
|
|
11
|
+
|
|
12
|
+
_AGENT_ID_RE = re.compile(r"^[a-zA-Z0-9_-]+$")
|
|
13
|
+
_THREAD_ID_RE = re.compile(r"^[a-f0-9]{32}$") # uuid4().hex format
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _invalid_params(message: str) -> McpError:
|
|
17
|
+
return McpError(ErrorData(code=INVALID_PARAMS, message=message))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _get_db():
|
|
21
|
+
from .tylor import _get_db as get_thread_db
|
|
22
|
+
|
|
23
|
+
return get_thread_db()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_memory_client():
|
|
27
|
+
from .tylor import _get_memory_client as get_thread_memory_client
|
|
28
|
+
|
|
29
|
+
return get_thread_memory_client()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _ensure_active_thread(db, thread_id: str) -> None:
|
|
33
|
+
meta = db.get_thread_meta(thread_id)
|
|
34
|
+
if not meta:
|
|
35
|
+
raise _invalid_params(f"Thread not found: {thread_id}")
|
|
36
|
+
|
|
37
|
+
status = meta.get("Status", meta.get("status", "active"))
|
|
38
|
+
if status != "active":
|
|
39
|
+
raise _invalid_params(f"Thread is not active: {thread_id}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _available_personas_message() -> str:
|
|
43
|
+
personas = list_persona_summaries()
|
|
44
|
+
formatted = ", ".join(
|
|
45
|
+
f"{p['name']} ({', '.join(p['ecc_tool_categories'])})"
|
|
46
|
+
for p in personas
|
|
47
|
+
)
|
|
48
|
+
return f"Available personas: {formatted}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@mcp.tool()
|
|
52
|
+
def list_personas() -> dict:
|
|
53
|
+
"""
|
|
54
|
+
Return available specialist personas with role summaries and ECC categories.
|
|
55
|
+
"""
|
|
56
|
+
return {"personas": list_persona_summaries()}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@mcp.tool()
|
|
60
|
+
def spawn_agent(persona: str, thread_id: str, task: str) -> dict:
|
|
61
|
+
"""
|
|
62
|
+
Spawn a specialist sub-agent persona within the given thread.
|
|
63
|
+
The sub-agent is scoped to this thread — no cross-thread bleed.
|
|
64
|
+
Available personas: ceo, cto, analyst, code_agent.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
persona: Persona identifier — one of: ceo, cto, analyst, code_agent.
|
|
68
|
+
thread_id: The thread to scope this agent to.
|
|
69
|
+
task: The work the persona should perform inside this thread.
|
|
70
|
+
"""
|
|
71
|
+
if not _THREAD_ID_RE.match(thread_id):
|
|
72
|
+
raise _invalid_params(
|
|
73
|
+
f"Invalid thread_id '{thread_id}' — must be a 32-character hex UUID (e.g. uuid4().hex)"
|
|
74
|
+
)
|
|
75
|
+
definition = load_persona(persona)
|
|
76
|
+
if definition is None:
|
|
77
|
+
raise _invalid_params(
|
|
78
|
+
f"Unknown persona: {persona}. {_available_personas_message()}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
db = _get_db()
|
|
82
|
+
_ensure_active_thread(db, thread_id)
|
|
83
|
+
|
|
84
|
+
agent_id = f"agent_{uuid.uuid4().hex}"
|
|
85
|
+
state_item = db.put_agent_state(
|
|
86
|
+
thread_id=thread_id,
|
|
87
|
+
agent_id=agent_id,
|
|
88
|
+
state={
|
|
89
|
+
"Status": "active",
|
|
90
|
+
"Persona": definition.name,
|
|
91
|
+
"Task": task,
|
|
92
|
+
"ToolsLoaded": list(definition.ecc_tool_categories),
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"agent_id": agent_id,
|
|
98
|
+
"persona": definition.name,
|
|
99
|
+
"thread_id": thread_id,
|
|
100
|
+
"tools_loaded": list(definition.ecc_tool_categories),
|
|
101
|
+
"role_prompt": definition.role_prompt,
|
|
102
|
+
"task": task,
|
|
103
|
+
"state_sk": state_item["SK"],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def persist_agent_output(
|
|
108
|
+
thread_id: str,
|
|
109
|
+
agent_id: str,
|
|
110
|
+
output: str,
|
|
111
|
+
task: str | None = None,
|
|
112
|
+
handoff_state: dict | None = None,
|
|
113
|
+
) -> dict:
|
|
114
|
+
"""
|
|
115
|
+
Persist completed sub-agent output and index it for scoped thread recall.
|
|
116
|
+
|
|
117
|
+
This is intentionally an orchestration helper, not a public Tier 1 MCP tool.
|
|
118
|
+
Future sub-agent execution calls this automatically when an agent completes.
|
|
119
|
+
"""
|
|
120
|
+
if not _AGENT_ID_RE.match(agent_id or ""):
|
|
121
|
+
raise _invalid_params("Invalid agent_id")
|
|
122
|
+
if not output or not output.strip():
|
|
123
|
+
raise _invalid_params("Agent output must not be empty")
|
|
124
|
+
|
|
125
|
+
db = _get_db()
|
|
126
|
+
_ensure_active_thread(db, thread_id)
|
|
127
|
+
|
|
128
|
+
output_item = db.put_agent_output(
|
|
129
|
+
thread_id=thread_id,
|
|
130
|
+
agent_id=agent_id,
|
|
131
|
+
output=output,
|
|
132
|
+
task=task,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
handoff_item = None
|
|
136
|
+
if handoff_state is not None:
|
|
137
|
+
handoff_item = db.put_agent_handoff(
|
|
138
|
+
thread_id=thread_id,
|
|
139
|
+
agent_id=agent_id,
|
|
140
|
+
handoff_state=handoff_state,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
memory = _get_memory_client()
|
|
144
|
+
memory_id = memory.index_memory(
|
|
145
|
+
thread_id=thread_id,
|
|
146
|
+
fact=output,
|
|
147
|
+
metadata={
|
|
148
|
+
"source": "agent_output",
|
|
149
|
+
"agent_id": agent_id,
|
|
150
|
+
"agent_output_sk": output_item["SK"],
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"thread_id": thread_id,
|
|
156
|
+
"agent_id": agent_id,
|
|
157
|
+
"output_sk": output_item["SK"],
|
|
158
|
+
"handoff_sk": handoff_item["SK"] if handoff_item else None,
|
|
159
|
+
"memory_id": memory_id,
|
|
160
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Lazy-loadable ECC tool categories."""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""ECC data tools."""
|
|
2
|
+
from .._mcp import mcp
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@mcp.tool()
|
|
6
|
+
def dataset_manager(action: str, dataset_id: str | None = None) -> dict:
|
|
7
|
+
"""Manage a dataset lifecycle action."""
|
|
8
|
+
return {
|
|
9
|
+
"tool": "dataset_manager",
|
|
10
|
+
"action": action,
|
|
11
|
+
"dataset_id": dataset_id,
|
|
12
|
+
"status": "planned",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@mcp.tool()
|
|
17
|
+
def data_clean(dataset_id: str, rules: list[str] | None = None) -> dict:
|
|
18
|
+
"""Plan data cleaning for a dataset."""
|
|
19
|
+
return {
|
|
20
|
+
"tool": "data_clean",
|
|
21
|
+
"dataset_id": dataset_id,
|
|
22
|
+
"rules": rules or [],
|
|
23
|
+
"status": "planned",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@mcp.tool()
|
|
28
|
+
def data_transform(dataset_id: str, transform: str) -> dict:
|
|
29
|
+
"""Plan a data transformation for a dataset."""
|
|
30
|
+
return {
|
|
31
|
+
"tool": "data_transform",
|
|
32
|
+
"dataset_id": dataset_id,
|
|
33
|
+
"transform": transform,
|
|
34
|
+
"status": "planned",
|
|
35
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""ECC diagram tools."""
|
|
2
|
+
from .._mcp import mcp
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@mcp.tool()
|
|
6
|
+
def diagram_gen(kind: str, description: str) -> dict:
|
|
7
|
+
"""Plan a diagram from a kind and description."""
|
|
8
|
+
return {
|
|
9
|
+
"tool": "diagram_gen",
|
|
10
|
+
"kind": kind,
|
|
11
|
+
"description": description,
|
|
12
|
+
"status": "planned",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@mcp.tool()
|
|
17
|
+
def flowchart_gen(steps: list[str]) -> dict:
|
|
18
|
+
"""Plan a flowchart from ordered steps."""
|
|
19
|
+
return {
|
|
20
|
+
"tool": "flowchart_gen",
|
|
21
|
+
"steps": steps,
|
|
22
|
+
"status": "planned",
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""ECC pipeline tools."""
|
|
2
|
+
from .._mcp import mcp
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@mcp.tool()
|
|
6
|
+
def pipeline_builder(name: str, stages: list[str]) -> dict:
|
|
7
|
+
"""Plan a pipeline definition from named stages."""
|
|
8
|
+
return {
|
|
9
|
+
"tool": "pipeline_builder",
|
|
10
|
+
"name": name,
|
|
11
|
+
"stages": stages,
|
|
12
|
+
"status": "planned",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@mcp.tool()
|
|
17
|
+
def pipeline_run(pipeline_id: str, dry_run: bool = True) -> dict:
|
|
18
|
+
"""Plan or request a pipeline run."""
|
|
19
|
+
return {
|
|
20
|
+
"tool": "pipeline_run",
|
|
21
|
+
"pipeline_id": pipeline_id,
|
|
22
|
+
"dry_run": dry_run,
|
|
23
|
+
"status": "planned",
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""ECC presentation tools."""
|
|
2
|
+
from .._mcp import mcp
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@mcp.tool()
|
|
6
|
+
def build_pptx(title: str, outline: list[str]) -> dict:
|
|
7
|
+
"""Plan a presentation deck from a title and outline."""
|
|
8
|
+
return {
|
|
9
|
+
"tool": "build_pptx",
|
|
10
|
+
"title": title,
|
|
11
|
+
"outline": outline,
|
|
12
|
+
"status": "planned",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@mcp.tool()
|
|
17
|
+
def build_doc(title: str, sections: list[str]) -> dict:
|
|
18
|
+
"""Plan a document from a title and section list."""
|
|
19
|
+
return {
|
|
20
|
+
"tool": "build_doc",
|
|
21
|
+
"title": title,
|
|
22
|
+
"sections": sections,
|
|
23
|
+
"status": "planned",
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""ECC web tools."""
|
|
2
|
+
from .._mcp import mcp
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@mcp.tool()
|
|
6
|
+
def web_scrape(url: str, selector: str | None = None) -> dict:
|
|
7
|
+
"""Plan a structured web scrape for a URL and optional selector."""
|
|
8
|
+
return {
|
|
9
|
+
"tool": "web_scrape",
|
|
10
|
+
"url": url,
|
|
11
|
+
"selector": selector,
|
|
12
|
+
"status": "planned",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@mcp.tool()
|
|
17
|
+
def web_fetch(url: str) -> dict:
|
|
18
|
+
"""Plan a direct web fetch for a URL."""
|
|
19
|
+
return {
|
|
20
|
+
"tool": "web_fetch",
|
|
21
|
+
"url": url,
|
|
22
|
+
"status": "planned",
|
|
23
|
+
}
|