@yottameta/yotta-dev-mcp-plugin 0.0.0 → 0.2.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/.agents/plugins/marketplace.json +25 -0
- package/.claude-plugin/marketplace.json +25 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/mcp.json +12 -0
- package/package.json +13 -10
- package/plugin.json +20 -0
- package/skills/yotta-dev-mcp/LICENSE +21 -0
- package/skills/yotta-dev-mcp/NOTICE +7 -0
- package/skills/yotta-dev-mcp/SKILL.md +81 -0
- package/skills/yotta-dev-mcp/assets/banner.png +0 -0
- package/skills/yotta-dev-mcp/bin/yotta-dev-mcp.js +57 -0
- package/skills/yotta-dev-mcp/references/adapters.md +69 -0
- package/skills/yotta-dev-mcp/references/architecture-contract.md +256 -0
- package/skills/yotta-dev-mcp/references/tools.md +257 -0
- package/skills/yotta-dev-mcp/scripts/dev_adapters.py +609 -0
- package/skills/yotta-dev-mcp/scripts/dev_architecture.py +379 -0
- package/skills/yotta-dev-mcp/scripts/dev_common.py +144 -0
- package/skills/yotta-dev-mcp/scripts/dev_contract.py +830 -0
- package/skills/yotta-dev-mcp/scripts/dev_engine.py +1138 -0
- package/skills/yotta-dev-mcp/scripts/dev_impact.py +556 -0
- package/skills/yotta-dev-mcp/scripts/dev_model.py +435 -0
- package/skills/yotta-dev-mcp/scripts/dev_rules.py +25 -0
- package/skills/yotta-dev-mcp/scripts/dev_selftest.py +534 -0
- package/skills/yotta-dev-mcp/scripts/dev_verify.py +450 -0
- package/skills/yotta-dev-mcp/scripts/yotta_dev_mcp.py +659 -0
- package/skills/yotta-dev-mcp/server.json +20 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""yotta-dev-mcp — deterministic development tools exposed over stdio MCP.
|
|
4
|
+
|
|
5
|
+
Protocol support is dual-era:
|
|
6
|
+
* modern: MCP 2026-07-28, server/discover + per-request _meta
|
|
7
|
+
* legacy: initialize handshake with protocolVersion 2025-11-25
|
|
8
|
+
|
|
9
|
+
Tools are local, deterministic and read-only unless a tool explicitly documents
|
|
10
|
+
an opt-in write or an explicit execution flag.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
_HERE = Path(__file__).resolve().parent
|
|
18
|
+
sys.path.insert(0, str(_HERE))
|
|
19
|
+
|
|
20
|
+
import dev_engine as engine # noqa: E402
|
|
21
|
+
|
|
22
|
+
VERSION = engine.VERSION
|
|
23
|
+
TOOL_NAME = "yotta-dev-mcp"
|
|
24
|
+
MCP_PROTOCOL_MODERN = "2026-07-28"
|
|
25
|
+
MCP_PROTOCOL_LEGACY = "2025-11-25"
|
|
26
|
+
SERVER_INFO = {"name": TOOL_NAME, "version": VERSION}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _text_result(payload):
|
|
30
|
+
return {
|
|
31
|
+
"content": [{"type": "text", "text": json.dumps(payload, ensure_ascii=False, indent=2)}],
|
|
32
|
+
"isError": False,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _tool_error(message):
|
|
37
|
+
return {
|
|
38
|
+
"content": [{"type": "text", "text": str(message)}],
|
|
39
|
+
"isError": True,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _tool(name, arguments):
|
|
44
|
+
try:
|
|
45
|
+
result = engine.dispatch(name, arguments)
|
|
46
|
+
except Exception as exc: # noqa: BLE001
|
|
47
|
+
return _tool_error("%s 失败: %s" % (name, exc))
|
|
48
|
+
if name == "compress_output" and isinstance(result, dict):
|
|
49
|
+
return {
|
|
50
|
+
"content": [{"type": "text", "text": result.get("text", "")}],
|
|
51
|
+
"isError": False,
|
|
52
|
+
}
|
|
53
|
+
return _text_result(result)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
TOOL_HANDLERS = {
|
|
57
|
+
name: (lambda arguments, tool_name=name: _tool(tool_name, arguments))
|
|
58
|
+
for name in ("repo_map", "system_model", "architecture_review", "impact_analysis",
|
|
59
|
+
"verify_change", "self_test", "run_adapter", "find_code", "compress_output",
|
|
60
|
+
"review_code", "review_diff", "mcp_doctor",
|
|
61
|
+
"scan_secrets", "scan_dependencies", "check_publish_readiness",
|
|
62
|
+
"run_checks", "scaffold_skill", "workflow_state")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def mcp_tools():
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
"name": "repo_map",
|
|
70
|
+
"description": (
|
|
71
|
+
"Map a local repository's source modules, imports and entrypoints. "
|
|
72
|
+
"Use when starting work in an unfamiliar codebase or when you need a "
|
|
73
|
+
"small structural overview before editing."
|
|
74
|
+
),
|
|
75
|
+
"inputSchema": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"path": {"type": "string", "description": "Repository or source directory"},
|
|
79
|
+
"max_files": {"type": "integer", "minimum": 1,
|
|
80
|
+
"description": "Maximum source files to inspect (default 2000)"},
|
|
81
|
+
},
|
|
82
|
+
"required": ["path"],
|
|
83
|
+
"additionalProperties": False,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "system_model",
|
|
88
|
+
"description": (
|
|
89
|
+
"Build a deterministic system model (modules, imports, entrypoints, "
|
|
90
|
+
"tests, configs, data stores) and attach layer data from the "
|
|
91
|
+
".yotta/architecture.json contract. Returns PASS, FAIL or UNKNOWN "
|
|
92
|
+
"plus the unknowns that still need evidence. Read-only."
|
|
93
|
+
),
|
|
94
|
+
"inputSchema": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"properties": {
|
|
97
|
+
"path": {"type": "string", "description": "Repository or source directory"},
|
|
98
|
+
"max_files": {"type": "integer", "minimum": 1,
|
|
99
|
+
"description": "Maximum source files to inspect (default 2000)"},
|
|
100
|
+
"contract_file": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"description": "Contract path relative to the repository root "
|
|
103
|
+
"(default .yotta/architecture.json)",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
"required": ["path"],
|
|
107
|
+
"additionalProperties": False,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "architecture_review",
|
|
112
|
+
"description": (
|
|
113
|
+
"Review a local repository against the .yotta/architecture.json contract: "
|
|
114
|
+
"dependency rules, boundary visibility and data ownership, each finding "
|
|
115
|
+
"with file, line and severity. critical/high findings fail the review; "
|
|
116
|
+
"medium/low are advisory; unverifiable items become UNKNOWN. Read-only."
|
|
117
|
+
),
|
|
118
|
+
"inputSchema": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"properties": {
|
|
121
|
+
"path": {"type": "string", "description": "Repository or source directory"},
|
|
122
|
+
"max_files": {"type": "integer", "minimum": 1,
|
|
123
|
+
"description": "Maximum source files to inspect (default 2000)"},
|
|
124
|
+
"contract_file": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": "Contract path relative to the repository root "
|
|
127
|
+
"(default .yotta/architecture.json)",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
"required": ["path"],
|
|
131
|
+
"additionalProperties": False,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "impact_analysis",
|
|
136
|
+
"description": (
|
|
137
|
+
"Build the change impact cone for local changes: reverse-dependency "
|
|
138
|
+
"consumers, affected layers, boundaries, data stores and invariants, "
|
|
139
|
+
"mapped tests, an explainable blast radius and rollback probes. Accepts "
|
|
140
|
+
"changed_files, a unified diff or target symbols. Read-only."
|
|
141
|
+
),
|
|
142
|
+
"inputSchema": {
|
|
143
|
+
"type": "object",
|
|
144
|
+
"properties": {
|
|
145
|
+
"path": {"type": "string", "description": "Repository or source directory"},
|
|
146
|
+
"changed_files": {
|
|
147
|
+
"type": "array", "items": {"type": "string"},
|
|
148
|
+
"description": "Repository-relative changed files",
|
|
149
|
+
},
|
|
150
|
+
"diff": {"type": "string",
|
|
151
|
+
"description": "Unified diff text to read changed files from"},
|
|
152
|
+
"symbols": {
|
|
153
|
+
"type": "array", "items": {"type": "string"},
|
|
154
|
+
"description": "Target symbols; their definition sites become the change",
|
|
155
|
+
},
|
|
156
|
+
"depth": {"type": "integer", "minimum": 1, "maximum": 10,
|
|
157
|
+
"description": "Reverse-dependency depth (default 3)"},
|
|
158
|
+
"max_files": {"type": "integer", "minimum": 1,
|
|
159
|
+
"description": "Maximum source files to inspect (default 2000)"},
|
|
160
|
+
"contract_file": {
|
|
161
|
+
"type": "string",
|
|
162
|
+
"description": "Contract path relative to the repository root "
|
|
163
|
+
"(default .yotta/architecture.json)",
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
"required": ["path"],
|
|
167
|
+
"additionalProperties": False,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "verify_change",
|
|
172
|
+
"description": (
|
|
173
|
+
"Run the L0-L5 verification ladder for a local change and return "
|
|
174
|
+
"a deterministic evidence ledger. L0 syntax/contract and L1 "
|
|
175
|
+
"architecture checks run by default; L2-L4 require a whitelisted "
|
|
176
|
+
".yotta/verification.json check plus allow_execute=true. L5 is "
|
|
177
|
+
"always recorded as manual work. Read-only unless execution is "
|
|
178
|
+
"explicitly enabled."
|
|
179
|
+
),
|
|
180
|
+
"inputSchema": {
|
|
181
|
+
"type": "object",
|
|
182
|
+
"properties": {
|
|
183
|
+
"path": {"type": "string", "description": "Repository or source directory"},
|
|
184
|
+
"changed_files": {
|
|
185
|
+
"type": "array", "items": {"type": "string"},
|
|
186
|
+
"description": "Repository-relative changed files",
|
|
187
|
+
},
|
|
188
|
+
"diff": {"type": "string",
|
|
189
|
+
"description": "Unified diff text to read changed files from"},
|
|
190
|
+
"symbols": {
|
|
191
|
+
"type": "array", "items": {"type": "string"},
|
|
192
|
+
"description": "Target symbols; their definition sites become the change",
|
|
193
|
+
},
|
|
194
|
+
"depth": {"type": "integer", "minimum": 1, "maximum": 10,
|
|
195
|
+
"description": "Reverse-dependency depth (default 3)"},
|
|
196
|
+
"levels": {
|
|
197
|
+
"type": "array",
|
|
198
|
+
"items": {"enum": ["L0", "L1", "L2", "L3", "L4", "L5"]},
|
|
199
|
+
"description": "Additional verification levels; L2-L4 require "
|
|
200
|
+
"a policy and explicit execution",
|
|
201
|
+
},
|
|
202
|
+
"allow_execute": {
|
|
203
|
+
"type": "boolean",
|
|
204
|
+
"description": "Must be true to run whitelisted policy checks; default false",
|
|
205
|
+
},
|
|
206
|
+
"timeout": {"type": "integer", "minimum": 1, "maximum": 600,
|
|
207
|
+
"description": "Upper bound for each check in seconds (default 120)"},
|
|
208
|
+
"max_files": {"type": "integer", "minimum": 1,
|
|
209
|
+
"description": "Maximum source files to inspect (default 2000)"},
|
|
210
|
+
"contract_file": {
|
|
211
|
+
"type": "string",
|
|
212
|
+
"description": "Contract path relative to the repository root "
|
|
213
|
+
"(default .yotta/architecture.json)",
|
|
214
|
+
},
|
|
215
|
+
"policy_file": {
|
|
216
|
+
"type": "string",
|
|
217
|
+
"description": "Verification policy path relative to the repository root "
|
|
218
|
+
"(default .yotta/verification.json)",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
"required": ["path"],
|
|
222
|
+
"additionalProperties": False,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"name": "self_test",
|
|
227
|
+
"description": (
|
|
228
|
+
"Run deterministic integrity checks on yotta-dev-mcp itself: "
|
|
229
|
+
"required files, version alignment, protocol/tool schema drift, "
|
|
230
|
+
"fail-closed write gates and seeded-defect counterexamples. Use "
|
|
231
|
+
"installed mode for a skill copy and source mode for the checkout. "
|
|
232
|
+
"Test-suite execution requires allow_execute=true; read-only by default."
|
|
233
|
+
),
|
|
234
|
+
"inputSchema": {
|
|
235
|
+
"type": "object",
|
|
236
|
+
"properties": {
|
|
237
|
+
"path": {"type": "string",
|
|
238
|
+
"description": "Source checkout or installed skill directory"},
|
|
239
|
+
"mode": {
|
|
240
|
+
"type": "string",
|
|
241
|
+
"enum": ["auto", "source", "installed"],
|
|
242
|
+
"description": "auto detects source vs installed layout",
|
|
243
|
+
},
|
|
244
|
+
"allow_execute": {
|
|
245
|
+
"type": "boolean",
|
|
246
|
+
"description": "Run the project test suite; default false",
|
|
247
|
+
},
|
|
248
|
+
"timeout": {"type": "integer", "minimum": 1, "maximum": 600,
|
|
249
|
+
"description": "Test timeout in seconds (default 120)"},
|
|
250
|
+
},
|
|
251
|
+
"required": ["path"],
|
|
252
|
+
"additionalProperties": False,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"name": "run_adapter",
|
|
257
|
+
"description": (
|
|
258
|
+
"Probe or explicitly run an optional local architecture adapter. "
|
|
259
|
+
"action=list only inspects project-local node_modules/.bin, venv and "
|
|
260
|
+
"PATH for import-linter, dependency-cruiser and Repomix; action=run "
|
|
261
|
+
"requires allow_execute=true. The adapter never installs packages, "
|
|
262
|
+
"downloads files or accepts arbitrary argv. Missing tools or configs "
|
|
263
|
+
"return UNKNOWN with the next step."
|
|
264
|
+
),
|
|
265
|
+
"inputSchema": {
|
|
266
|
+
"type": "object",
|
|
267
|
+
"properties": {
|
|
268
|
+
"action": {
|
|
269
|
+
"type": "string",
|
|
270
|
+
"enum": ["list", "run"],
|
|
271
|
+
"description": "list probes capability; run executes one adapter",
|
|
272
|
+
},
|
|
273
|
+
"path": {"type": "string", "description": "Repository root"},
|
|
274
|
+
"adapter": {
|
|
275
|
+
"type": "string",
|
|
276
|
+
"enum": ["import-linter", "dependency-cruiser", "repomix"],
|
|
277
|
+
"description": "Adapter id; required when action=run",
|
|
278
|
+
},
|
|
279
|
+
"allow_execute": {
|
|
280
|
+
"type": "boolean",
|
|
281
|
+
"description": "Must be true to run an adapter; default false",
|
|
282
|
+
},
|
|
283
|
+
"timeout": {
|
|
284
|
+
"type": "integer",
|
|
285
|
+
"minimum": 1,
|
|
286
|
+
"maximum": 600,
|
|
287
|
+
"description": "Adapter timeout in seconds (default 120)",
|
|
288
|
+
},
|
|
289
|
+
"max_chars": {
|
|
290
|
+
"type": "integer",
|
|
291
|
+
"minimum": 1000,
|
|
292
|
+
"maximum": 1000000,
|
|
293
|
+
"description": "Repomix output cap in characters (default 120000)",
|
|
294
|
+
},
|
|
295
|
+
"token_budget": {
|
|
296
|
+
"type": "integer",
|
|
297
|
+
"minimum": 1,
|
|
298
|
+
"maximum": 500000,
|
|
299
|
+
"description": "Optional Repomix token budget",
|
|
300
|
+
},
|
|
301
|
+
"target": {
|
|
302
|
+
"type": "string",
|
|
303
|
+
"description": "Repository-relative adapter target (default .)",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
"required": ["action", "path"],
|
|
307
|
+
"additionalProperties": False,
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"name": "find_code",
|
|
312
|
+
"description": (
|
|
313
|
+
"Find a symbol, definition or text in local source files with bounded "
|
|
314
|
+
"results. Use when locating where a function, class or string lives."
|
|
315
|
+
),
|
|
316
|
+
"inputSchema": {
|
|
317
|
+
"type": "object",
|
|
318
|
+
"properties": {
|
|
319
|
+
"path": {"type": "string", "description": "Repository, file or directory"},
|
|
320
|
+
"query": {"type": "string", "description": "Symbol or text to find"},
|
|
321
|
+
"extensions": {"type": "array", "items": {"type": "string"},
|
|
322
|
+
"description": "Optional file extensions such as .py or .ts"},
|
|
323
|
+
"max_results": {"type": "integer", "minimum": 1,
|
|
324
|
+
"description": "Maximum matches (default 100)"},
|
|
325
|
+
"context_lines": {"type": "integer", "minimum": 0, "maximum": 5,
|
|
326
|
+
"description": "Context lines around each match"},
|
|
327
|
+
},
|
|
328
|
+
"required": ["path", "query"],
|
|
329
|
+
"additionalProperties": False,
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"name": "compress_output",
|
|
334
|
+
"description": (
|
|
335
|
+
"Compress long logs or command output while preserving errors, "
|
|
336
|
+
"tracebacks, warnings, head and tail lines. Use before putting long "
|
|
337
|
+
"tool output into the context window."
|
|
338
|
+
),
|
|
339
|
+
"inputSchema": {
|
|
340
|
+
"type": "object",
|
|
341
|
+
"properties": {
|
|
342
|
+
"text": {"type": "string", "description": "Text to compress"},
|
|
343
|
+
"file": {"type": "string", "description": "Read text from this file instead"},
|
|
344
|
+
"max_chars": {"type": "integer", "minimum": 20,
|
|
345
|
+
"description": "Output character budget (default 4000)"},
|
|
346
|
+
"head_lines": {"type": "integer", "minimum": 0},
|
|
347
|
+
"tail_lines": {"type": "integer", "minimum": 0},
|
|
348
|
+
},
|
|
349
|
+
"additionalProperties": False,
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"name": "review_code",
|
|
354
|
+
"description": (
|
|
355
|
+
"Run deterministic local code review rules and return evidence with "
|
|
356
|
+
"file, line, rule, severity and suggestion. Use for a focused review "
|
|
357
|
+
"before commit or PR."
|
|
358
|
+
),
|
|
359
|
+
"inputSchema": {
|
|
360
|
+
"type": "object",
|
|
361
|
+
"properties": {
|
|
362
|
+
"path": {"type": "string", "description": "File or repository path"},
|
|
363
|
+
"text": {"type": "string", "description": "Review this text instead"},
|
|
364
|
+
"max_findings": {"type": "integer", "minimum": 1},
|
|
365
|
+
},
|
|
366
|
+
"additionalProperties": False,
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"name": "review_diff",
|
|
371
|
+
"description": (
|
|
372
|
+
"Review only added lines in a git diff or unified diff. Use when "
|
|
373
|
+
"reviewing a patch or before opening a pull request."
|
|
374
|
+
),
|
|
375
|
+
"inputSchema": {
|
|
376
|
+
"type": "object",
|
|
377
|
+
"properties": {
|
|
378
|
+
"diff_text": {"type": "string", "description": "Unified diff content"},
|
|
379
|
+
"path": {"type": "string", "description": "Git repository path"},
|
|
380
|
+
"base": {"type": "string", "description": "Optional git base revision"},
|
|
381
|
+
"max_findings": {"type": "integer", "minimum": 1},
|
|
382
|
+
},
|
|
383
|
+
"additionalProperties": False,
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"name": "mcp_doctor",
|
|
388
|
+
"description": (
|
|
389
|
+
"Inspect installed skills and MCP JSON configuration files for "
|
|
390
|
+
"versions and obvious configuration issues. Read-only."
|
|
391
|
+
),
|
|
392
|
+
"inputSchema": {
|
|
393
|
+
"type": "object",
|
|
394
|
+
"properties": {
|
|
395
|
+
"skills_dirs": {"type": "array", "items": {"type": "string"}},
|
|
396
|
+
"config_paths": {"type": "array", "items": {"type": "string"}},
|
|
397
|
+
},
|
|
398
|
+
"additionalProperties": False,
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"name": "scan_secrets",
|
|
403
|
+
"description": (
|
|
404
|
+
"Scan local source, configuration and env files for credentials "
|
|
405
|
+
"and high-entropy tokens, returning redacted evidence. Use before "
|
|
406
|
+
"commit, publishing or sharing a repository."
|
|
407
|
+
),
|
|
408
|
+
"inputSchema": {
|
|
409
|
+
"type": "object",
|
|
410
|
+
"properties": {
|
|
411
|
+
"path": {"type": "string", "description": "File or repository path"},
|
|
412
|
+
"text": {"type": "string", "description": "Scan this text instead"},
|
|
413
|
+
"max_findings": {"type": "integer", "minimum": 1},
|
|
414
|
+
"include_git_history": {"type": "boolean", "description": "Also scan bounded git history; default false"},
|
|
415
|
+
},
|
|
416
|
+
"additionalProperties": False,
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
"name": "scan_dependencies",
|
|
421
|
+
"description": (
|
|
422
|
+
"Inspect local dependency manifests and lockfiles for missing "
|
|
423
|
+
"locks, unpinned ranges, insecure sources and local-only paths. "
|
|
424
|
+
"Offline heuristic only; no package-existence lookup."
|
|
425
|
+
),
|
|
426
|
+
"inputSchema": {
|
|
427
|
+
"type": "object",
|
|
428
|
+
"properties": {
|
|
429
|
+
"path": {"type": "string", "description": "Repository path"},
|
|
430
|
+
},
|
|
431
|
+
"required": ["path"],
|
|
432
|
+
"additionalProperties": False,
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"name": "check_publish_readiness",
|
|
437
|
+
"description": (
|
|
438
|
+
"Check a local package or skill for version alignment, required "
|
|
439
|
+
"release files, repository metadata and public publish access. "
|
|
440
|
+
"Use before tagging or publishing."
|
|
441
|
+
),
|
|
442
|
+
"inputSchema": {
|
|
443
|
+
"type": "object",
|
|
444
|
+
"properties": {
|
|
445
|
+
"path": {"type": "string", "description": "Package or skill directory"},
|
|
446
|
+
},
|
|
447
|
+
"required": ["path"],
|
|
448
|
+
"additionalProperties": False,
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"name": "run_checks",
|
|
453
|
+
"description": (
|
|
454
|
+
"Run a whitelisted test, lint or compile check in a local project "
|
|
455
|
+
"and return a bounded structured summary. Execution is explicit: "
|
|
456
|
+
"use only when the user asks to run checks."
|
|
457
|
+
),
|
|
458
|
+
"inputSchema": {
|
|
459
|
+
"type": "object",
|
|
460
|
+
"properties": {
|
|
461
|
+
"kind": {
|
|
462
|
+
"type": "string",
|
|
463
|
+
"enum": ["python-unittest", "pytest", "python-compile", "npm-test", "npm-lint"],
|
|
464
|
+
},
|
|
465
|
+
"cwd": {"type": "string", "description": "Project directory"},
|
|
466
|
+
"timeout": {"type": "integer", "minimum": 1, "maximum": 600},
|
|
467
|
+
"allow_execute": {"type": "boolean", "description": "Must be true; default false"},
|
|
468
|
+
},
|
|
469
|
+
"required": ["kind", "cwd"],
|
|
470
|
+
"additionalProperties": False,
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"name": "scaffold_skill",
|
|
475
|
+
"description": (
|
|
476
|
+
"Plan or create a minimal skill scaffold with SKILL.md, package.json, "
|
|
477
|
+
"README, changelog and a starter script. Dry-run is the default."
|
|
478
|
+
),
|
|
479
|
+
"inputSchema": {
|
|
480
|
+
"type": "object",
|
|
481
|
+
"properties": {
|
|
482
|
+
"name": {"type": "string", "description": "Lower-case skill slug"},
|
|
483
|
+
"output_dir": {"type": "string", "description": "Parent output directory"},
|
|
484
|
+
"description": {"type": "string"},
|
|
485
|
+
"apply": {"type": "boolean", "description": "Write files; default false"},
|
|
486
|
+
},
|
|
487
|
+
"required": ["name", "output_dir"],
|
|
488
|
+
"additionalProperties": False,
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"name": "workflow_state",
|
|
493
|
+
"description": (
|
|
494
|
+
"Read .workflow state files and optionally append one log line with "
|
|
495
|
+
"an explicit date. Use for session recovery and handoff checks."
|
|
496
|
+
),
|
|
497
|
+
"inputSchema": {
|
|
498
|
+
"type": "object",
|
|
499
|
+
"properties": {
|
|
500
|
+
"root": {"type": "string", "description": "Project root"},
|
|
501
|
+
"action": {"type": "string", "enum": ["read", "append-log", "append-file"]},
|
|
502
|
+
"date": {"type": "string", "description": "YYYY-MM-DD for append-log"},
|
|
503
|
+
"text": {"type": "string"},
|
|
504
|
+
"file": {"type": "string", "enum": ["STATE.md", "TASKS.md", "DECISIONS.md", "ROADMAP.md"]},
|
|
505
|
+
"apply": {"type": "boolean", "description": "Write; default false"},
|
|
506
|
+
},
|
|
507
|
+
"required": ["root"],
|
|
508
|
+
"additionalProperties": False,
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
]
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _req_version(params):
|
|
515
|
+
meta = (params or {}).get("_meta") or {}
|
|
516
|
+
return meta.get("io.modelcontextprotocol/protocolVersion")
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _modern_ok(payload, cache=None):
|
|
520
|
+
out = {"resultType": "complete"}
|
|
521
|
+
out.update(payload)
|
|
522
|
+
out["_meta"] = {"io.modelcontextprotocol/serverInfo": dict(SERVER_INFO)}
|
|
523
|
+
if cache:
|
|
524
|
+
out["ttlMs"] = cache[0]
|
|
525
|
+
out["cacheScope"] = cache[1]
|
|
526
|
+
return out
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _unsupported_version(rid, protocol_version):
|
|
530
|
+
return {
|
|
531
|
+
"jsonrpc": "2.0",
|
|
532
|
+
"id": rid,
|
|
533
|
+
"error": {
|
|
534
|
+
"code": -32022,
|
|
535
|
+
"message": "Unsupported protocol version",
|
|
536
|
+
"data": {"supported": [MCP_PROTOCOL_MODERN], "requested": protocol_version},
|
|
537
|
+
},
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def handle_message(msg):
|
|
542
|
+
if not isinstance(msg, dict) or msg.get("jsonrpc") != "2.0":
|
|
543
|
+
rid = msg.get("id") if isinstance(msg, dict) else None
|
|
544
|
+
return {"jsonrpc": "2.0", "id": rid,
|
|
545
|
+
"error": {"code": -32600, "message": "invalid request"}}
|
|
546
|
+
method = msg.get("method")
|
|
547
|
+
rid = msg.get("id")
|
|
548
|
+
if rid is None or method is None:
|
|
549
|
+
return None
|
|
550
|
+
params = msg.get("params") or {}
|
|
551
|
+
protocol_version = _req_version(params)
|
|
552
|
+
if protocol_version is not None:
|
|
553
|
+
if protocol_version != MCP_PROTOCOL_MODERN:
|
|
554
|
+
return _unsupported_version(rid, protocol_version)
|
|
555
|
+
if method == "server/discover":
|
|
556
|
+
return {
|
|
557
|
+
"jsonrpc": "2.0",
|
|
558
|
+
"id": rid,
|
|
559
|
+
"result": _modern_ok({
|
|
560
|
+
"supportedVersions": [MCP_PROTOCOL_MODERN],
|
|
561
|
+
"capabilities": {"tools": {}},
|
|
562
|
+
"instructions": (
|
|
563
|
+
"yotta-dev-mcp exposes deterministic local development tools. "
|
|
564
|
+
"Prefer repo_map/find_code before editing, review_diff before PR, "
|
|
565
|
+
"review_code for focused review, compress_output for long logs and "
|
|
566
|
+
"mcp_doctor for local configuration checks. Use system_model and "
|
|
567
|
+
"architecture_review for architecture contracts, impact_analysis and "
|
|
568
|
+
"verify_change for change-centered evidence, self_test for integrity "
|
|
569
|
+
"checks, and run_adapter to probe or explicitly run optional local "
|
|
570
|
+
"architecture adapters. Tools are offline and read-only unless an "
|
|
571
|
+
"explicit write or execute flag is set."
|
|
572
|
+
),
|
|
573
|
+
}, (3600000, "public")),
|
|
574
|
+
}
|
|
575
|
+
if method == "ping":
|
|
576
|
+
return {"jsonrpc": "2.0", "id": rid, "result": _modern_ok({})}
|
|
577
|
+
if method == "tools/list":
|
|
578
|
+
return {
|
|
579
|
+
"jsonrpc": "2.0",
|
|
580
|
+
"id": rid,
|
|
581
|
+
"result": _modern_ok({"tools": mcp_tools()}, (300000, "public")),
|
|
582
|
+
}
|
|
583
|
+
if method == "tools/call":
|
|
584
|
+
name = params.get("name")
|
|
585
|
+
arguments = params.get("arguments") or {}
|
|
586
|
+
handler = TOOL_HANDLERS.get(name)
|
|
587
|
+
if not handler:
|
|
588
|
+
return {
|
|
589
|
+
"jsonrpc": "2.0",
|
|
590
|
+
"id": rid,
|
|
591
|
+
"result": _modern_ok(_tool_error("未知工具: %s" % name)),
|
|
592
|
+
}
|
|
593
|
+
return {"jsonrpc": "2.0", "id": rid, "result": _modern_ok(handler(arguments))}
|
|
594
|
+
if method == "initialize":
|
|
595
|
+
return {
|
|
596
|
+
"jsonrpc": "2.0",
|
|
597
|
+
"id": rid,
|
|
598
|
+
"error": {
|
|
599
|
+
"code": -32601,
|
|
600
|
+
"message": "initialize removed in MCP 2026-07-28; use server/discover",
|
|
601
|
+
},
|
|
602
|
+
}
|
|
603
|
+
return {"jsonrpc": "2.0", "id": rid,
|
|
604
|
+
"error": {"code": -32601, "message": "Method not found: " + str(method)}}
|
|
605
|
+
|
|
606
|
+
if method == "initialize":
|
|
607
|
+
return {
|
|
608
|
+
"jsonrpc": "2.0",
|
|
609
|
+
"id": rid,
|
|
610
|
+
"result": {
|
|
611
|
+
"protocolVersion": MCP_PROTOCOL_LEGACY,
|
|
612
|
+
"capabilities": {"tools": {}},
|
|
613
|
+
"serverInfo": SERVER_INFO,
|
|
614
|
+
},
|
|
615
|
+
}
|
|
616
|
+
if method == "ping":
|
|
617
|
+
return {"jsonrpc": "2.0", "id": rid, "result": {}}
|
|
618
|
+
if method == "tools/list":
|
|
619
|
+
return {"jsonrpc": "2.0", "id": rid, "result": {"tools": mcp_tools()}}
|
|
620
|
+
if method == "tools/call":
|
|
621
|
+
name = params.get("name")
|
|
622
|
+
arguments = params.get("arguments") or {}
|
|
623
|
+
handler = TOOL_HANDLERS.get(name)
|
|
624
|
+
if not handler:
|
|
625
|
+
return {
|
|
626
|
+
"jsonrpc": "2.0",
|
|
627
|
+
"id": rid,
|
|
628
|
+
"result": _tool_error("未知工具: %s" % name),
|
|
629
|
+
}
|
|
630
|
+
return {"jsonrpc": "2.0", "id": rid, "result": handler(arguments)}
|
|
631
|
+
return {"jsonrpc": "2.0", "id": rid,
|
|
632
|
+
"error": {"code": -32601, "message": "Method not found: " + str(method)}}
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def main():
|
|
636
|
+
try:
|
|
637
|
+
sys.stdin.reconfigure(encoding="utf-8")
|
|
638
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
639
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
640
|
+
except Exception: # noqa: BLE001
|
|
641
|
+
pass
|
|
642
|
+
for line in sys.stdin:
|
|
643
|
+
line = line.strip()
|
|
644
|
+
if not line:
|
|
645
|
+
continue
|
|
646
|
+
try:
|
|
647
|
+
message = json.loads(line)
|
|
648
|
+
except json.JSONDecodeError:
|
|
649
|
+
response = {"jsonrpc": "2.0", "id": None,
|
|
650
|
+
"error": {"code": -32700, "message": "parse error"}}
|
|
651
|
+
else:
|
|
652
|
+
response = handle_message(message)
|
|
653
|
+
if response is not None:
|
|
654
|
+
sys.stdout.write(json.dumps(response, ensure_ascii=False) + "\n")
|
|
655
|
+
sys.stdout.flush()
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
if __name__ == "__main__":
|
|
659
|
+
main()
|