diff --git a/.mcp.json b/.mcp.json
new file mode 100644
index 0000000..07dde52
--- /dev/null
+++ b/.mcp.json
@@ -0,0 +1,7 @@
+{
+ "mcpServers": {
+ "minecraft": {
+ "url": "http://localhost:3002/mcp"
+ }
+ }
+}
diff --git a/src/schematics-browser.js b/src/schematics-browser.js
index e30d365..6cac02e 100644
--- a/src/schematics-browser.js
+++ b/src/schematics-browser.js
@@ -190,43 +190,75 @@ export async function downloadSchematic(projectUrl, timeoutMs = 60000) {
await page.goto(projectUrl, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
await waitForCloudflare(page, timeoutMs);
- // Step 2: Navigate to download confirmation page
- const downloadPageUrl = projectUrl.replace(/\/?$/, '/download/schematic/');
+ // Step 2: Navigate to download page (worldmap has the Schemagic embed with the real URL)
+ const downloadPageUrl = projectUrl.replace(/\/?$/, '/download/worldmap/');
log(TAG, `Navigating to download page: ${downloadPageUrl}`);
await page.goto(downloadPageUrl, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
await waitForCloudflare(page, timeoutMs);
- // Step 3: Try to extract the static file URL from element
- let staticUrl = null;
- try {
- staticUrl = await page.getAttribute('a[download]', 'href', { timeout: 5000 });
- if (staticUrl) {
- log(TAG, `Found static download URL: ${staticUrl}`);
- }
- } catch {
- log(TAG, 'No element found, will try countdown fallback');
+ // Step 3: Extract schematic URL from Schemagic.load() JS call or element
+ let schematicUrl = null;
+
+ // 3a: Look for Schemagic.load({ schematic: "https://...resource_media/schematic/..." })
+ const html = await page.content();
+ const schemagicMatch = html.match(/Schemagic\.load\s*\(\s*\{[^}]*schematic:\s*"([^"]+)"/);
+ if (schemagicMatch) {
+ schematicUrl = schemagicMatch[1];
+ log(TAG, `Found Schemagic schematic URL: ${schematicUrl}`);
}
- // Also try broader selectors if the first didn't work
- if (!staticUrl) {
+ // 3b: Look for direct resource_media URLs in the page (signed S3 links)
+ if (!schematicUrl) {
+ const resourceMatch = html.match(/https?:\/\/[^"'\s]*static\.planetminecraft\.com\/files\/resource_media\/schematic[^"'\s]*/);
+ if (resourceMatch) {
+ schematicUrl = resourceMatch[0];
+ log(TAG, `Found resource_media schematic URL: ${schematicUrl}`);
+ }
+ }
+
+ // 3c: Try element
+ if (!schematicUrl) {
try {
- staticUrl = await page.getAttribute('a[href*="static.planetminecraft.com"]', 'href', { timeout: 3000 });
- if (staticUrl) {
- log(TAG, `Found static PMC URL: ${staticUrl}`);
+ schematicUrl = await page.getAttribute('a[download]', 'href', { timeout: 5000 });
+ if (schematicUrl) {
+ log(TAG, `Found URL: ${schematicUrl}`);
}
} catch {
- // Will use fallback
+ log(TAG, 'No element found');
+ }
+ }
+
+ // 3d: Also try /download/schematic/ page as fallback
+ if (!schematicUrl) {
+ const schematicPageUrl = projectUrl.replace(/\/?$/, '/download/schematic/');
+ log(TAG, `Trying schematic download page: ${schematicPageUrl}`);
+ await page.goto(schematicPageUrl, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
+ await waitForCloudflare(page, timeoutMs);
+
+ const schematicHtml = await page.content();
+ const schematicPageMatch = schematicHtml.match(/Schemagic\.load\s*\(\s*\{[^}]*schematic:\s*"([^"]+)"/);
+ if (schematicPageMatch) {
+ schematicUrl = schematicPageMatch[1];
+ log(TAG, `Found Schemagic URL on schematic page: ${schematicUrl}`);
+ }
+
+ if (!schematicUrl) {
+ const resourceMatch2 = schematicHtml.match(/https?:\/\/[^"'\s]*static\.planetminecraft\.com\/files\/resource_media\/schematic[^"'\s]*/);
+ if (resourceMatch2) {
+ schematicUrl = resourceMatch2[0];
+ log(TAG, `Found resource_media URL on schematic page: ${schematicUrl}`);
+ }
}
}
let downloadBuffer;
- if (staticUrl) {
- // Step 4a: Download via navigating to the static URL
- log(TAG, 'Downloading via static URL...');
+ if (schematicUrl) {
+ // Step 4a: Download via navigating to the S3 URL (triggers download event)
+ log(TAG, `Downloading schematic from: ${schematicUrl.slice(0, 100)}...`);
const [download] = await Promise.all([
page.waitForEvent('download', { timeout: timeoutMs }),
- page.evaluate((url) => { window.location.href = url; }, staticUrl),
+ page.evaluate((url) => { window.location.href = url; }, schematicUrl),
]);
const path = await download.path();
if (!path) throw new Error('Download failed — no file path returned');
@@ -234,10 +266,9 @@ export async function downloadSchematic(projectUrl, timeoutMs = 60000) {
downloadBuffer = readFileSync(path);
} else {
// Step 4b: Fallback — wait for countdown and click download button
- log(TAG, 'Waiting for countdown timer to complete...');
- // Wait for the download button/link to become active (countdown is typically 5s)
+ log(TAG, 'No schematic URL found, trying countdown fallback...');
try {
- await page.waitForSelector('a.download-action:not([disabled]), a[href*="static.planetminecraft.com"], .confirm-download a', {
+ await page.waitForSelector('a.download-action:not([disabled]), .confirm-download a', {
timeout: 15000,
state: 'visible',
});
@@ -247,7 +278,7 @@ export async function downloadSchematic(projectUrl, timeoutMs = 60000) {
const [download] = await Promise.all([
page.waitForEvent('download', { timeout: timeoutMs }),
- page.click('a.download-action, a[href*="static.planetminecraft.com"], .confirm-download a'),
+ page.click('a.download-action, .confirm-download a'),
]);
const path = await download.path();
if (!path) throw new Error('Download failed — no file path returned');
diff --git a/src/schematics.js b/src/schematics.js
index b871569..24d42f8 100644
--- a/src/schematics.js
+++ b/src/schematics.js
@@ -207,13 +207,173 @@ function extractFromZipIfNeeded(buffer) {
}
/**
- * Parse a raw .schematic/.schem buffer into our blueprint format.
+ * Try to parse a buffer as Litematica format (.litematic).
+ * Returns null if not a litematic file.
+ * @param {Buffer} buffer
+ * @returns {Promise