@yottameta/yotta-dev-mcp-plugin 0.0.0 → 0.1.1

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.
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Deterministic review rules for yotta-dev-mcp.
4
+
5
+ This file is rule data: it contains the literal markers the engine must
6
+ detect, and is exempted by the project publish preflight in the same way as
7
+ other signature rule tables.
8
+ """
9
+
10
+ import re
11
+
12
+ REVIEW_RULES = [
13
+ ("bare-except", "medium", re.compile(r"^\s*except\s*:"),
14
+ "Catch a specific exception instead of a bare except."),
15
+ ("eval-exec", "high", re.compile(r"\b(?:eval|exec)\s*\("),
16
+ "Avoid dynamic evaluation; use explicit parsing or dispatch."),
17
+ ("shell-true", "high", re.compile(r"\bshell\s*=\s*True\b"),
18
+ "Do not invoke a shell with untrusted input; pass an argument list."),
19
+ ("debug-print", "low", re.compile(r"^\s*print\s*\("),
20
+ "Replace debug output with structured logging or remove it."),
21
+ ("todo-comment", "info", re.compile(r"\b(?:TODO|FIXME)\b"),
22
+ "Track the unfinished work or remove the stale marker."),
23
+ ("mutable-default", "medium", re.compile(r"def\s+\w+\s*\([^)]*=\s*(?:\[\]|\{\})"),
24
+ "Use None as the default and create the mutable value inside the function."),
25
+ ]
@@ -0,0 +1,430 @@
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
+ The six tools in this first slice are local, deterministic and read-only:
10
+ repo_map / find_code / compress_output / review_code / review_diff / mcp_doctor.
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", "find_code", "compress_output",
59
+ "review_code", "review_diff", "mcp_doctor",
60
+ "scan_secrets", "scan_dependencies", "check_publish_readiness",
61
+ "run_checks", "scaffold_skill", "workflow_state")
62
+ }
63
+
64
+
65
+ def mcp_tools():
66
+ return [
67
+ {
68
+ "name": "repo_map",
69
+ "description": (
70
+ "Map a local repository's source modules, imports and entrypoints. "
71
+ "Use when starting work in an unfamiliar codebase or when you need a "
72
+ "small structural overview before editing."
73
+ ),
74
+ "inputSchema": {
75
+ "type": "object",
76
+ "properties": {
77
+ "path": {"type": "string", "description": "Repository or source directory"},
78
+ "max_files": {"type": "integer", "minimum": 1,
79
+ "description": "Maximum source files to inspect (default 2000)"},
80
+ },
81
+ "required": ["path"],
82
+ "additionalProperties": False,
83
+ },
84
+ },
85
+ {
86
+ "name": "find_code",
87
+ "description": (
88
+ "Find a symbol, definition or text in local source files with bounded "
89
+ "results. Use when locating where a function, class or string lives."
90
+ ),
91
+ "inputSchema": {
92
+ "type": "object",
93
+ "properties": {
94
+ "path": {"type": "string", "description": "Repository, file or directory"},
95
+ "query": {"type": "string", "description": "Symbol or text to find"},
96
+ "extensions": {"type": "array", "items": {"type": "string"},
97
+ "description": "Optional file extensions such as .py or .ts"},
98
+ "max_results": {"type": "integer", "minimum": 1,
99
+ "description": "Maximum matches (default 100)"},
100
+ "context_lines": {"type": "integer", "minimum": 0, "maximum": 5,
101
+ "description": "Context lines around each match"},
102
+ },
103
+ "required": ["path", "query"],
104
+ "additionalProperties": False,
105
+ },
106
+ },
107
+ {
108
+ "name": "compress_output",
109
+ "description": (
110
+ "Compress long logs or command output while preserving errors, "
111
+ "tracebacks, warnings, head and tail lines. Use before putting long "
112
+ "tool output into the context window."
113
+ ),
114
+ "inputSchema": {
115
+ "type": "object",
116
+ "properties": {
117
+ "text": {"type": "string", "description": "Text to compress"},
118
+ "file": {"type": "string", "description": "Read text from this file instead"},
119
+ "max_chars": {"type": "integer", "minimum": 20,
120
+ "description": "Output character budget (default 4000)"},
121
+ "head_lines": {"type": "integer", "minimum": 0},
122
+ "tail_lines": {"type": "integer", "minimum": 0},
123
+ },
124
+ "additionalProperties": False,
125
+ },
126
+ },
127
+ {
128
+ "name": "review_code",
129
+ "description": (
130
+ "Run deterministic local code review rules and return evidence with "
131
+ "file, line, rule, severity and suggestion. Use for a focused review "
132
+ "before commit or PR."
133
+ ),
134
+ "inputSchema": {
135
+ "type": "object",
136
+ "properties": {
137
+ "path": {"type": "string", "description": "File or repository path"},
138
+ "text": {"type": "string", "description": "Review this text instead"},
139
+ "max_findings": {"type": "integer", "minimum": 1},
140
+ },
141
+ "additionalProperties": False,
142
+ },
143
+ },
144
+ {
145
+ "name": "review_diff",
146
+ "description": (
147
+ "Review only added lines in a git diff or unified diff. Use when "
148
+ "reviewing a patch or before opening a pull request."
149
+ ),
150
+ "inputSchema": {
151
+ "type": "object",
152
+ "properties": {
153
+ "diff_text": {"type": "string", "description": "Unified diff content"},
154
+ "path": {"type": "string", "description": "Git repository path"},
155
+ "base": {"type": "string", "description": "Optional git base revision"},
156
+ "max_findings": {"type": "integer", "minimum": 1},
157
+ },
158
+ "additionalProperties": False,
159
+ },
160
+ },
161
+ {
162
+ "name": "mcp_doctor",
163
+ "description": (
164
+ "Inspect installed skills and MCP JSON configuration files for "
165
+ "versions and obvious configuration issues. Read-only."
166
+ ),
167
+ "inputSchema": {
168
+ "type": "object",
169
+ "properties": {
170
+ "skills_dirs": {"type": "array", "items": {"type": "string"}},
171
+ "config_paths": {"type": "array", "items": {"type": "string"}},
172
+ },
173
+ "additionalProperties": False,
174
+ },
175
+ },
176
+ {
177
+ "name": "scan_secrets",
178
+ "description": (
179
+ "Scan local source, configuration and env files for credentials "
180
+ "and high-entropy tokens, returning redacted evidence. Use before "
181
+ "commit, publishing or sharing a repository."
182
+ ),
183
+ "inputSchema": {
184
+ "type": "object",
185
+ "properties": {
186
+ "path": {"type": "string", "description": "File or repository path"},
187
+ "text": {"type": "string", "description": "Scan this text instead"},
188
+ "max_findings": {"type": "integer", "minimum": 1},
189
+ "include_git_history": {"type": "boolean", "description": "Also scan bounded git history; default false"},
190
+ },
191
+ "additionalProperties": False,
192
+ },
193
+ },
194
+ {
195
+ "name": "scan_dependencies",
196
+ "description": (
197
+ "Inspect local dependency manifests and lockfiles for missing "
198
+ "locks, unpinned ranges, insecure sources and local-only paths. "
199
+ "Offline heuristic only; no package-existence lookup."
200
+ ),
201
+ "inputSchema": {
202
+ "type": "object",
203
+ "properties": {
204
+ "path": {"type": "string", "description": "Repository path"},
205
+ },
206
+ "required": ["path"],
207
+ "additionalProperties": False,
208
+ },
209
+ },
210
+ {
211
+ "name": "check_publish_readiness",
212
+ "description": (
213
+ "Check a local package or skill for version alignment, required "
214
+ "release files, repository metadata and public publish access. "
215
+ "Use before tagging or publishing."
216
+ ),
217
+ "inputSchema": {
218
+ "type": "object",
219
+ "properties": {
220
+ "path": {"type": "string", "description": "Package or skill directory"},
221
+ },
222
+ "required": ["path"],
223
+ "additionalProperties": False,
224
+ },
225
+ },
226
+ {
227
+ "name": "run_checks",
228
+ "description": (
229
+ "Run a whitelisted test, lint or compile check in a local project "
230
+ "and return a bounded structured summary. Execution is explicit: "
231
+ "use only when the user asks to run checks."
232
+ ),
233
+ "inputSchema": {
234
+ "type": "object",
235
+ "properties": {
236
+ "kind": {
237
+ "type": "string",
238
+ "enum": ["python-unittest", "pytest", "python-compile", "npm-test", "npm-lint"],
239
+ },
240
+ "cwd": {"type": "string", "description": "Project directory"},
241
+ "timeout": {"type": "integer", "minimum": 1, "maximum": 600},
242
+ "allow_execute": {"type": "boolean", "description": "Must be true; default false"},
243
+ },
244
+ "required": ["kind", "cwd"],
245
+ "additionalProperties": False,
246
+ },
247
+ },
248
+ {
249
+ "name": "scaffold_skill",
250
+ "description": (
251
+ "Plan or create a minimal skill scaffold with SKILL.md, package.json, "
252
+ "README, changelog and a starter script. Dry-run is the default."
253
+ ),
254
+ "inputSchema": {
255
+ "type": "object",
256
+ "properties": {
257
+ "name": {"type": "string", "description": "Lower-case skill slug"},
258
+ "output_dir": {"type": "string", "description": "Parent output directory"},
259
+ "description": {"type": "string"},
260
+ "apply": {"type": "boolean", "description": "Write files; default false"},
261
+ },
262
+ "required": ["name", "output_dir"],
263
+ "additionalProperties": False,
264
+ },
265
+ },
266
+ {
267
+ "name": "workflow_state",
268
+ "description": (
269
+ "Read .workflow state files and optionally append one log line with "
270
+ "an explicit date. Use for session recovery and handoff checks."
271
+ ),
272
+ "inputSchema": {
273
+ "type": "object",
274
+ "properties": {
275
+ "root": {"type": "string", "description": "Project root"},
276
+ "action": {"type": "string", "enum": ["read", "append-log", "append-file"]},
277
+ "date": {"type": "string", "description": "YYYY-MM-DD for append-log"},
278
+ "text": {"type": "string"},
279
+ "file": {"type": "string", "enum": ["STATE.md", "TASKS.md", "DECISIONS.md", "ROADMAP.md"]},
280
+ "apply": {"type": "boolean", "description": "Write; default false"},
281
+ },
282
+ "required": ["root"],
283
+ "additionalProperties": False,
284
+ },
285
+ },
286
+ ]
287
+
288
+
289
+ def _req_version(params):
290
+ meta = (params or {}).get("_meta") or {}
291
+ return meta.get("io.modelcontextprotocol/protocolVersion")
292
+
293
+
294
+ def _modern_ok(payload, cache=None):
295
+ out = {"resultType": "complete"}
296
+ out.update(payload)
297
+ out["_meta"] = {"io.modelcontextprotocol/serverInfo": dict(SERVER_INFO)}
298
+ if cache:
299
+ out["ttlMs"] = cache[0]
300
+ out["cacheScope"] = cache[1]
301
+ return out
302
+
303
+
304
+ def _unsupported_version(rid, protocol_version):
305
+ return {
306
+ "jsonrpc": "2.0",
307
+ "id": rid,
308
+ "error": {
309
+ "code": -32022,
310
+ "message": "Unsupported protocol version",
311
+ "data": {"supported": [MCP_PROTOCOL_MODERN], "requested": protocol_version},
312
+ },
313
+ }
314
+
315
+
316
+ def handle_message(msg):
317
+ if not isinstance(msg, dict) or msg.get("jsonrpc") != "2.0":
318
+ rid = msg.get("id") if isinstance(msg, dict) else None
319
+ return {"jsonrpc": "2.0", "id": rid,
320
+ "error": {"code": -32600, "message": "invalid request"}}
321
+ method = msg.get("method")
322
+ rid = msg.get("id")
323
+ if rid is None or method is None:
324
+ return None
325
+ params = msg.get("params") or {}
326
+ protocol_version = _req_version(params)
327
+ if protocol_version is not None:
328
+ if protocol_version != MCP_PROTOCOL_MODERN:
329
+ return _unsupported_version(rid, protocol_version)
330
+ if method == "server/discover":
331
+ return {
332
+ "jsonrpc": "2.0",
333
+ "id": rid,
334
+ "result": _modern_ok({
335
+ "supportedVersions": [MCP_PROTOCOL_MODERN],
336
+ "capabilities": {"tools": {}},
337
+ "instructions": (
338
+ "yotta-dev-mcp exposes deterministic local development tools. "
339
+ "Prefer repo_map/find_code before editing, review_diff before PR, "
340
+ "review_code for focused review, compress_output for long logs and "
341
+ "mcp_doctor for local configuration checks. All six tools are offline "
342
+ "and read-only."
343
+ ),
344
+ }, (3600000, "public")),
345
+ }
346
+ if method == "ping":
347
+ return {"jsonrpc": "2.0", "id": rid, "result": _modern_ok({})}
348
+ if method == "tools/list":
349
+ return {
350
+ "jsonrpc": "2.0",
351
+ "id": rid,
352
+ "result": _modern_ok({"tools": mcp_tools()}, (300000, "public")),
353
+ }
354
+ if method == "tools/call":
355
+ name = params.get("name")
356
+ arguments = params.get("arguments") or {}
357
+ handler = TOOL_HANDLERS.get(name)
358
+ if not handler:
359
+ return {
360
+ "jsonrpc": "2.0",
361
+ "id": rid,
362
+ "result": _modern_ok(_tool_error("未知工具: %s" % name)),
363
+ }
364
+ return {"jsonrpc": "2.0", "id": rid, "result": _modern_ok(handler(arguments))}
365
+ if method == "initialize":
366
+ return {
367
+ "jsonrpc": "2.0",
368
+ "id": rid,
369
+ "error": {
370
+ "code": -32601,
371
+ "message": "initialize removed in MCP 2026-07-28; use server/discover",
372
+ },
373
+ }
374
+ return {"jsonrpc": "2.0", "id": rid,
375
+ "error": {"code": -32601, "message": "Method not found: " + str(method)}}
376
+
377
+ if method == "initialize":
378
+ return {
379
+ "jsonrpc": "2.0",
380
+ "id": rid,
381
+ "result": {
382
+ "protocolVersion": MCP_PROTOCOL_LEGACY,
383
+ "capabilities": {"tools": {}},
384
+ "serverInfo": SERVER_INFO,
385
+ },
386
+ }
387
+ if method == "ping":
388
+ return {"jsonrpc": "2.0", "id": rid, "result": {}}
389
+ if method == "tools/list":
390
+ return {"jsonrpc": "2.0", "id": rid, "result": {"tools": mcp_tools()}}
391
+ if method == "tools/call":
392
+ name = params.get("name")
393
+ arguments = params.get("arguments") or {}
394
+ handler = TOOL_HANDLERS.get(name)
395
+ if not handler:
396
+ return {
397
+ "jsonrpc": "2.0",
398
+ "id": rid,
399
+ "result": _tool_error("未知工具: %s" % name),
400
+ }
401
+ return {"jsonrpc": "2.0", "id": rid, "result": handler(arguments)}
402
+ return {"jsonrpc": "2.0", "id": rid,
403
+ "error": {"code": -32601, "message": "Method not found: " + str(method)}}
404
+
405
+
406
+ def main():
407
+ try:
408
+ sys.stdin.reconfigure(encoding="utf-8")
409
+ sys.stdout.reconfigure(encoding="utf-8")
410
+ sys.stderr.reconfigure(encoding="utf-8")
411
+ except Exception: # noqa: BLE001
412
+ pass
413
+ for line in sys.stdin:
414
+ line = line.strip()
415
+ if not line:
416
+ continue
417
+ try:
418
+ message = json.loads(line)
419
+ except json.JSONDecodeError:
420
+ response = {"jsonrpc": "2.0", "id": None,
421
+ "error": {"code": -32700, "message": "parse error"}}
422
+ else:
423
+ response = handle_message(message)
424
+ if response is not None:
425
+ sys.stdout.write(json.dumps(response, ensure_ascii=False) + "\n")
426
+ sys.stdout.flush()
427
+
428
+
429
+ if __name__ == "__main__":
430
+ main()
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.YottaMeta/yotta-dev-mcp",
4
+ "description": "YuanKai (yotta-dev-mcp): deterministic local development tools over stdio MCP, covering code mapping, review, secret/dependency scanning, release checks, whitelisted checks, scaffolding and workflow state.",
5
+ "repository": {
6
+ "url": "https://github.com/YottaMeta/yotta-dev-mcp",
7
+ "source": "github"
8
+ },
9
+ "version": "0.1.1",
10
+ "packages": [
11
+ {
12
+ "registryType": "npm",
13
+ "identifier": "@yottameta/yotta-dev-mcp",
14
+ "version": "0.1.1",
15
+ "transport": {
16
+ "type": "stdio"
17
+ }
18
+ }
19
+ ]
20
+ }