Skip to content

Commit 9a96c7c

Browse files
authored
Merge pull request #11 from AnCarsenat/10-feature-using-pack_format-instead-of-version
10 feature using pack format instead of version & Removing tool clutter & Debug environnment
2 parents 4cdd340 + cde7f2d commit 9a96c7c

9 files changed

Lines changed: 237 additions & 77 deletions

File tree

.mcp.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "minecode-debug",
3+
"transport": "stdio",
4+
"command": "${command:python.interpreterPath}",
5+
"args": ["-m", "minecode.server"],
6+
"cwd": ".",
7+
"env": {},
8+
"description": "Configuration for launching the MineCode MCP server via stdio. Use an MCP-capable client or VS Code extension to start/connect."
9+
}

.vscode/mcp.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"servers": [
3+
{
4+
"name": "minecode-debug",
5+
"command": "${command:python.interpreterPath}",
6+
"args": ["-m", "minecode.server"],
7+
"transport": "stdio",
8+
"cwd": "${workspaceFolder}",
9+
"autoStart": false,
10+
"description": "Start MineCode MCP server for workspace (stdio transport)."
11+
}
12+
]
13+
}

minecode/config/config.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
{
3+
"preprompt_path": "minecode/preprompts/assistant_preprompt.txt",
4+
"preprompt_enabled": true,
5+
"description": "Central config for MineCode MCP. Contains path to assistant pre-prompt and feature flags."
6+
}

minecode/config/prompt_config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"default_preprompt": "minecode/preprompts/assistant_preprompt.txt",
3+
"enabled": true,
4+
"description": "Path to the default assistant pre-prompt used by the MCP server."
5+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
You are an AI agent built to help minecraft datapack/texture pack creators on java.
2+
When you need to search for information, use the tools provided to you if possible and target the right versions.
3+
4+
Proceed with the following method :
5+
Get the pack_format of the datapack (generally in /pack.mcmeta)
6+
Get the versions that the pack has to support.
7+
Sometimes pack have multi-version support. In those cases It will be generally harder to work with.
8+
Carefully examine and understand what the user reports.
9+
If it is an error report:
10+
- Use tools to diagnose the problem (ex: get_minecraft_logs)
11+
- Explain the problem to the user
12+
- Try and fix
13+
- Explain changes made
14+
If it is a feature request :
15+
- Make sure you know what version you are on and what tools you can use
16+
- If you know of methods and workarounds to minecraft's limitations
17+
- Explain limitations and workarounds
18+
- Add the feature
19+
When you're done TRY TO FIND PROBLEMS WHICH MIGHT ARISE
20+
21+
Tools which are available to you :
22+
scrappers contains scrappers and api wrappers for interacting with websites which are up to date and generally more accurate.
23+
- misode
24+
misode is a website containing UI generators : you can give the user a generator if you think he will be more able then you to add a feature.
25+
- mojira
26+
mojira is a bug tracking website. You generally won't have to use this.
27+
- spyglass
28+
spyglass is an api which has per version version information.
29+
commands
30+
blocks
31+
etc
32+
- minecraft_wiki
33+
the minecraft wiki is an official wiki containing information.
34+
the problem when searching this site is that it only contains the latest versions' information. (back for backporting or older versions)

minecode/prompts/main.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
You are an AI agent built to help minecraft datapack/texture pack creators on java.
2+
When you need to search for information, use the tools provided to you if possible and target the right versions.
3+
4+
Proceed with the following method :
5+
Get the pack_format of the datapack (generally in /pack.mcmeta)
6+
Get the versions that the pack has to support.
7+
Sometimes pack have multi-version support. In those cases It will be generally harder to work with.
8+
Carefully examine and understand what the user reports.
9+
If it is an error report:
10+
- Use tools to diagnose the problem (ex: get_minecraft_logs)
11+
- Explain the problem to the user
12+
- Try and fix
13+
- Explain changes made
14+
If it is a feature request :
15+
- Make sure you know what version you are on and what tools you can use
16+
- If you know of methods and workarounds to minecraft's limitations
17+
- Explain limitations and workarounds
18+
- Add the feature
19+
When you're done TRY TO FIND PROBLEMS WHICH MIGHT ARISE
20+
21+
Tools which are available to you :
22+
scrappers contains scrappers and api wrappers for interacting with websites which are up to date and generally more accurate.
23+
- misode
24+
misode is a website containing UI generators : you can give the user a generator if you think he will be more able then you to add a feature.
25+
- mojira
26+
mojira is a bug tracking website. You generally won't have to use this.

minecode/scrappers/minecraftwiki.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ def get_block_info(block: str) -> Optional[PageContent]:
368368
Args:
369369
block: Block name (e.g., "Stone", "Diamond Ore")
370370
371-
Returns:
371+
Returns:
372372
PageContent object with block information
373373
"""
374374
return get_page_content(block)

minecode/scrappers/spyglass.py

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -69,51 +69,6 @@ def get_versions() -> List[Dict[str, Any]]:
6969
return _make_request("/mcje/versions")
7070

7171

72-
def get_latest_release() -> Optional[Dict[str, Any]]:
73-
"""
74-
Get the latest stable release version.
75-
76-
Returns:
77-
Version object for the latest release, or None if not found
78-
"""
79-
versions = get_versions()
80-
for version in versions:
81-
if version.get("type") == "release" and version.get("stable"):
82-
return version
83-
return None
84-
85-
86-
def get_latest_snapshot() -> Optional[Dict[str, Any]]:
87-
"""
88-
Get the latest snapshot version.
89-
90-
Returns:
91-
Version object for the latest snapshot, or None if not found
92-
"""
93-
versions = get_versions()
94-
for version in versions:
95-
if version.get("type") == "snapshot":
96-
return version
97-
return None
98-
99-
100-
def get_version_info(version_id: str) -> Optional[Dict[str, Any]]:
101-
"""
102-
Get information about a specific version.
103-
104-
Args:
105-
version_id: Version ID (e.g., "1.21", "24w14a")
106-
107-
Returns:
108-
Version object or None if not found
109-
"""
110-
versions = get_versions()
111-
for version in versions:
112-
if version.get("id") == version_id:
113-
return version
114-
return None
115-
116-
11772
# ============================================================================
11873
# Block States Endpoint
11974
# ============================================================================

minecode/server.py

Lines changed: 143 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import asyncio
88
import json
9+
import logging
910

1011
from mcp.server import Server
1112
from mcp.server.stdio import stdio_server
@@ -21,6 +22,74 @@
2122
# Initialize MCP Server
2223
server = Server("minecode-server")
2324

25+
# logging
26+
logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")
27+
logger = logging.getLogger("minecode.server")
28+
29+
# Load central configuration and assistant preprompt (if enabled)
30+
from pathlib import Path
31+
32+
_pkg_dir = Path(__file__).resolve().parent
33+
_config_file = _pkg_dir / "config" / "config.json"
34+
35+
# attach default_preprompt to server so other modules can access it
36+
server.default_preprompt = None
37+
38+
def _load_preprompt_from_config():
39+
try:
40+
if _config_file.exists():
41+
cfg = json.loads(_config_file.read_text(encoding="utf-8"))
42+
preprompt_enabled = cfg.get("preprompt_enabled", False)
43+
preprompt_path = cfg.get("preprompt_path")
44+
if preprompt_enabled and preprompt_path:
45+
# Try multiple candidate locations for the preprompt path:
46+
candidates = []
47+
p = Path(preprompt_path)
48+
# Absolute path as-given
49+
if p.is_absolute():
50+
candidates.append(p)
51+
52+
# Package-relative (e.g. "preprompts/assistant_preprompt.txt")
53+
candidates.append((_pkg_dir / preprompt_path))
54+
55+
# Workspace/root-relative if the config used "minecode/..." or similar
56+
repo_root = _pkg_dir.parent
57+
candidates.append(repo_root / preprompt_path)
58+
59+
# If path contains a nested "minecode/", try the suffix relative to package
60+
if "minecode/" in preprompt_path:
61+
suffix = preprompt_path.split("minecode/", 1)[1]
62+
candidates.append(_pkg_dir / suffix)
63+
64+
found = None
65+
for c in candidates:
66+
try:
67+
cc = c.resolve()
68+
except Exception:
69+
cc = c
70+
logger.debug(f"Checking preprompt candidate: {cc}")
71+
if cc.exists():
72+
found = cc
73+
break
74+
75+
if found:
76+
server.default_preprompt = found.read_text(encoding="utf-8")
77+
logger.info(f"Loaded assistant preprompt from {found}")
78+
else:
79+
logger.info(f"Assistant preprompt not found; tried {len(candidates)} locations")
80+
except Exception as e:
81+
logger.exception("Failed loading preprompt from config")
82+
83+
84+
_load_preprompt_from_config()
85+
86+
def get_preprompt_messages():
87+
if server.default_preprompt:
88+
return [{"role": "system", "content": server.default_preprompt}]
89+
return []
90+
91+
server.get_preprompt_messages = get_preprompt_messages
92+
2493

2594
# Tool definitions
2695
TOOLS = [
@@ -47,9 +116,13 @@
47116
"version": {
48117
"type": "string",
49118
"description": "Minecraft version (e.g., 1.20.1, latest)"
119+
},
120+
"datapack_path": {
121+
"type": "string",
122+
"description": "Path to a datapack folder containing pack.mcmeta to infer version"
50123
}
51124
},
52-
"required": ["version"]
125+
"required": []
53126
}
54127
),
55128
Tool(
@@ -482,30 +555,62 @@ def handle_hello_world(name: str = None) -> str:
482555
return "Hello, World! Welcome to MineCode - Your Minecraft Datapack Development Assistant"
483556

484557

485-
def handle_get_minecraft_version(version: str) -> dict:
486-
"""Handle get_minecraft_version tool"""
487-
# Simulated version info
488-
versions = {
489-
"1.20.1": {
490-
"release_date": "2023-12-07",
491-
"snapshot": False,
492-
"features": ["Decorated pots", "Armor trims", "Smithing templates"]
493-
},
494-
"1.20": {
495-
"release_date": "2023-06-07",
496-
"snapshot": False,
497-
"features": ["Cherry logs", "Painting variants", "Camel mob"]
498-
},
499-
"latest": {
500-
"release_date": "2024-01-18",
501-
"snapshot": False,
502-
"version": "1.20.4"
503-
}
504-
}
505-
506-
if version in versions:
507-
return {"success": True, "data": versions[version]}
508-
return {"success": False, "error": f"Version {version} not found in database"}
558+
def handle_get_minecraft_version(version: str = None, datapack_path: str = None) -> dict:
559+
"""Handle get_minecraft_version tool.
560+
561+
If `datapack_path` is provided, attempt to read `pack.mcmeta` and infer
562+
the pack_format, then use `misode` metadata to find matching Minecraft
563+
versions. If `version` is provided, return the version info from `misode`.
564+
"""
565+
# If a datapack path is provided, try to read pack.mcmeta and infer versions
566+
if datapack_path:
567+
from pathlib import Path
568+
try:
569+
p = Path(datapack_path)
570+
# allow passing either the folder or the direct path to pack.mcmeta
571+
pp = p / "pack.mcmeta" if p.is_dir() else p
572+
if not pp.exists():
573+
return {"success": False, "error": f"pack.mcmeta not found at {pp}"}
574+
575+
import json as _json
576+
content = _json.loads(pp.read_text(encoding="utf-8"))
577+
pack = content.get("pack") or {}
578+
pack_format = pack.get("pack_format")
579+
if pack_format is None:
580+
return {"success": False, "error": "pack_format not found in pack.mcmeta"}
581+
582+
# Find versions in misode that match this data_pack_version
583+
try:
584+
candidates = []
585+
for vid in misode.list_versions():
586+
info = misode.get_version_info(vid) or {}
587+
dpv = info.get("data_pack_version") or info.get("dataPackVersion") or info.get("pack_format")
588+
if dpv is None:
589+
continue
590+
# compare as ints/strings
591+
try:
592+
if int(dpv) == int(pack_format):
593+
candidates.append({"version": vid, "data_pack_version": dpv})
594+
except Exception:
595+
if str(dpv) == str(pack_format):
596+
candidates.append({"version": vid, "data_pack_version": dpv})
597+
598+
if candidates:
599+
return {"success": True, "pack_format": pack_format, "matches": candidates}
600+
return {"success": False, "pack_format": pack_format, "matches": [], "note": "No matching versions found in misode metadata"}
601+
except Exception as e:
602+
return {"success": False, "error": f"Error querying misode metadata: {e}"}
603+
except Exception as e:
604+
return {"success": False, "error": f"Failed reading pack.mcmeta: {e}"}
605+
606+
# Fallback: if a version string is provided, try to get info from misode
607+
if version:
608+
info = misode.get_version_info(version)
609+
if info:
610+
return {"success": True, "version": version, "info": info}
611+
return {"success": False, "error": f"Version {version} not found in misode database"}
612+
613+
return {"success": False, "error": "Either `version` or `datapack_path` must be provided"}
509614

510615

511616
def handle_validate_datapack(datapack_path: str, mc_version: str) -> dict:
@@ -965,7 +1070,7 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
9651070
if name == "hello_world":
9661071
result = handle_hello_world(arguments.get("name"))
9671072
elif name == "get_minecraft_version":
968-
result = handle_get_minecraft_version(arguments["version"])
1073+
result = handle_get_minecraft_version(arguments.get("version"), arguments.get("datapack_path"))
9691074
elif name == "validate_datapack":
9701075
result = handle_validate_datapack(arguments["datapack_path"], arguments["mc_version"])
9711076
elif name == "search_wiki":
@@ -1085,15 +1190,22 @@ async def list_tools():
10851190
async def _run():
10861191
"""Run the MCP server"""
10871192
async with stdio_server() as (read_stream, write_stream):
1088-
await server.run(
1089-
read_stream,
1090-
write_stream,
1091-
server.create_initialization_options()
1092-
)
1193+
logger.info("MineCode MCP server starting (stdio mode)")
1194+
logger.info(f"Registering {len(TOOLS)} tools")
1195+
try:
1196+
await server.run(
1197+
read_stream,
1198+
write_stream,
1199+
server.create_initialization_options()
1200+
)
1201+
finally:
1202+
logger.info("MineCode MCP server stopped")
10931203

10941204

10951205
def main():
10961206
"""Entry point for the MCP server"""
1207+
logger.info("Starting MineCode MCP server (main entry)")
1208+
logger.info(f"Config file: {_config_file}")
10971209
asyncio.run(_run())
10981210

10991211

0 commit comments

Comments
 (0)