Skip to main content
Glama
stringtheoryaccelerator

Maine Burn Permit MCP Server

Apply for Burn Permit

apply_for_burn_permit

Apply for a Maine open burn permit by submitting required information including location, materials, and contact details through the official state system.

Instructions

Automates the process of applying for a Maine open burn permit.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
townYesTown where burning will occur
addressYesPhysical address of the burn (and applicant's home address)
cityYesCity of the applicant's address
stateNoState of the applicant's addressME
zipYesZip code
dobYesDate of Birth (MM/DD/YYYY)
materialYesMaterial to be burned
applicantNameYesFull name of the applicant
phoneYesPhone number (e.g., 207-555-1234)
emailYesEmail address
burnLocationYesDescription of burn location on property

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
messageYes
successYes
permitNumberYes

Implementation Reference

  • The core handler function that executes the tool logic: launches a headless Puppeteer browser, navigates to the Maine burn permit website, fills out the application form with provided input data, submits it, extracts the permit number if successful, and returns structured output.
        async (input) => {
    
        let browser;
        try {
            browser = await puppeteer.launch({
                headless: true, // Start headless, maybe switch to false for debugging if needed
                args: ["--no-sandbox", "--disable-setuid-sandbox"],
            });
            const page = await browser.newPage();
    
            // Step 1: Go to the site
            await page.goto(PERMIT_URL, { waitUntil: "networkidle0" });
    
            // Click "Get Started Now"
            await Promise.all([
                page.waitForNavigation({ waitUntil: "networkidle0" }),
                page.click("#button"),
            ]);
    
            // Step 2: Select Municipality and Burn Type
    
            // Select Burn Type
            const burnTypeSelector = input.material === "Agricultural" ? "#burnType_1" : "#burnType_0";
            await page.click(burnTypeSelector);
    
            // Wait for town dropdown to be visible
            await page.waitForSelector("#town", { visible: true });
    
            // Select Town
            // The site uses Select2, but often we can just set the value on the underlying select and dispatch an event.
            await page.select('#burnTown', input.town);
    
            // Trigger change event to notify Select2 and show the continue button
            await page.evaluate(() => {
                const select = document.querySelector('#burnTown');
                if (select) {
                    select.dispatchEvent(new Event('change', { bubbles: true }));
                    // Also try jQuery trigger if available, as the site uses jQuery
                    // @ts-ignore
                    if (window.jQuery) { window.jQuery('#burnTown').trigger('change'); }
                }
            });
    
            // Wait for Continue button
            await page.waitForSelector("input[name='continue']", { visible: true });
    
            // Click Continue
            await Promise.all([
                page.waitForNavigation({ waitUntil: "networkidle0" }),
                page.click("input[name='continue']"),
            ]);
    
            // Step 3: Applicant Info
            await page.waitForSelector("#firstName", { visible: true });
    
            // Parse Name
            const nameParts = input.applicantName.split(" ");
            const firstName = nameParts[0];
            const lastName = nameParts.slice(1).join(" ");
    
            await page.type("#firstName", firstName);
            await page.type("#lastName", lastName);
            await page.type("#dob", input.dob);
    
            await page.type("#address_address", input.address);
            await page.type("#address_city", input.city);
            await page.select("#address_state", input.state);
            await page.type("#address_zipCode", input.zip);
            await page.select("#address_country", "US");
    
            // Assume burn location is same as home address for now
            await page.select("#burnLocationSameAddress", "true");
            // Trigger change for burnLocationSameAddress just in case
            await page.evaluate(() => {
                const select = document.querySelector('#burnLocationSameAddress');
                if (select) {
                    select.dispatchEvent(new Event('change', { bubbles: true }));
                    // @ts-ignore
                    if (window.jQuery) { window.jQuery('#burnLocationSameAddress').trigger('change'); }
                }
            });
    
            await page.type("#email", input.email);
    
            // Format phone number to (XXX) XXX-XXXX if needed, or just type it and hope the mask handles it.
            // The site uses a mask, so typing digits might work best.
            // Let's strip non-digits.
            const phoneDigits = input.phone.replace(/\D/g, "");
            await page.click("#phoneNumber"); // Focus first
            await page.type("#phoneNumber", phoneDigits);
    
            // Land Owner: Assume Yes for now
            await page.click("#owner_0");
    
            // Burn Location
            await page.type("#burnLocation", input.burnLocation);
    
            // Type of Item to Burn
            // Map material to value. Default to 22 (Brush < 10x10)
            // If Agricultural, we might need to check options.
            // For now, let's try to select "22" if it exists, otherwise the first option.
            const itemValue = "22";
            await page.select("#item", itemValue);
            // Trigger change
            await page.evaluate(() => {
                const select = document.querySelector('#item');
                if (select) {
                    select.dispatchEvent(new Event('change', { bubbles: true }));
                    // @ts-ignore
                    if (window.jQuery) { window.jQuery('#item').trigger('change'); }
                }
            });
    
            // Affirmation
            // Wait for affirmation checkbox to be visible (it might be hidden if 'Other' is selected, but we selected 22)
            await page.waitForSelector("#affirmation", { visible: true });
            await page.click("#affirmation");
    
            // Click Continue
            await Promise.all([
                page.waitForNavigation({ waitUntil: "networkidle0" }),
                page.click("input[name='continue']"),
            ]);
    
            // Step 4: Minimum Requirements
            await page.waitForSelector("#minimumRequirements_agreed", { visible: true });
            await page.click("#minimumRequirements_agreed");
    
            // Click Continue
            // Click Continue
            await Promise.all([
                page.waitForNavigation({ waitUntil: "networkidle0", timeout: 60000 }),
                page.click("input[name='continue']"),
            ]);
    
            // Step 5: Confirmation / Permit Details
            // The permit is issued immediately after step 4.
            // Check for "Burn Permit Details" in the page content
            const bodyText = await page.evaluate(() => document.body.innerText);
    
            if (bodyText.includes("Burn Permit Details") || bodyText.includes("Permission is hereby granted")) {
                // Try to extract permit number
                // It's usually a 6+ digit number
                const match = bodyText.match(/(\d{6,})/);
                const permitNumber = match ? match[0] : "Unknown";
    
                const output = {
                    success: true,
                    permitNumber: permitNumber,
                    message: `Permit Application Successful! Permit Number: ${permitNumber}. Please download the permit from the provided link or save the confirmation page.`,
                };
    
                return {
                    content: [
                        {
                            type: "text",
                            text: output.message,
                        },
                    ],
                    structuredContent: output,
                };
            } else {
                const output = {
                    success: false,
                    permitNumber: "Unknown",
                    message: "Permit application submitted, but confirmation page title mismatch. Please verify manually.",
                };
    
                return {
                    content: [
                        {
                            type: "text",
                            text: output.message,
                        },
                    ],
                    structuredContent: output,
                };
            }
        } catch (error: any) {
            return {
                content: [
                    {
                        type: "text",
                        text: `Error applying for permit: ${error.message}`,
                    },
                ],
                isError: true,
            };
        } finally {
            if (browser) {
                await browser.close();
            }
        }
    });
  • Input and output schemas defining the parameters (town, address, applicant details, etc.) and response format (success, permitNumber, message) for the tool.
    {
        title: "Apply for Burn Permit",
        description: "Automates the process of applying for a Maine open burn permit.",
        inputSchema: {
            town: z.string().describe("Town where burning will occur"),
            address: z.string().describe("Physical address of the burn (and applicant's home address)"),
            city: z.string().describe("City of the applicant's address"),
            state: z.string().default("ME").describe("State of the applicant's address"),
            zip: z.string().describe("Zip code"),
            dob: z.string().describe("Date of Birth (MM/DD/YYYY)"),
            material: z.enum(["Brush", "Wood Debris", "Agricultural", "Campfire"]).describe("Material to be burned"),
            applicantName: z.string().describe("Full name of the applicant"),
            phone: z.string().describe("Phone number (e.g., 207-555-1234)"),
            email: z.string().email().describe("Email address"),
            burnLocation: z.string().describe("Description of burn location on property"),
        },
        outputSchema: {
            success: z.boolean(),
            permitNumber: z.string(),
            message: z.string(),
        },
    },
  • The MCP server.registerTool call that registers the 'apply_for_burn_permit' tool with its schema and handler.
    server.registerTool(
        "apply_for_burn_permit",
        {
            title: "Apply for Burn Permit",
            description: "Automates the process of applying for a Maine open burn permit.",
            inputSchema: {
                town: z.string().describe("Town where burning will occur"),
                address: z.string().describe("Physical address of the burn (and applicant's home address)"),
                city: z.string().describe("City of the applicant's address"),
                state: z.string().default("ME").describe("State of the applicant's address"),
                zip: z.string().describe("Zip code"),
                dob: z.string().describe("Date of Birth (MM/DD/YYYY)"),
                material: z.enum(["Brush", "Wood Debris", "Agricultural", "Campfire"]).describe("Material to be burned"),
                applicantName: z.string().describe("Full name of the applicant"),
                phone: z.string().describe("Phone number (e.g., 207-555-1234)"),
                email: z.string().email().describe("Email address"),
                burnLocation: z.string().describe("Description of burn location on property"),
            },
            outputSchema: {
                success: z.boolean(),
                permitNumber: z.string(),
                message: z.string(),
            },
        },
        async (input) => {
  • src/index.ts:14-15 (registration)
    Top-level registration of the apply permit tool (via registerApplyPermitTool) on the main MCP server instance.
    registerFireDangerTool(server);
    registerApplyPermitTool(server);
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. It states the tool 'automates the process' but doesn't clarify whether this is a read-only check or an actual submission that creates a permit, what permissions are needed, whether it's rate-limited, what happens on success/failure, or what the output contains. For a tool with 11 parameters that likely submits data, this is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that states the core purpose without unnecessary words. It's appropriately sized and front-loaded with the essential information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that there's an output schema (though not shown), the description doesn't need to explain return values. However, for a complex tool with 11 parameters and no annotations, the description should provide more behavioral context about what the tool actually does (submission vs. simulation, authentication needs, etc.). The single sentence is adequate but leaves significant gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, so the schema already documents all 11 parameters thoroughly with descriptions, required status, enums, and formats. The description adds no parameter-specific information beyond what's in the schema, so it meets the baseline for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Automates the process of applying for a Maine open burn permit.' It specifies the action (apply for) and resource (burn permit), though it doesn't explicitly differentiate from the sibling tool 'check_fire_danger' beyond the different action verbs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention the sibling tool 'check_fire_danger' or explain when an application is appropriate versus checking fire danger conditions. No usage context or exclusions are provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/stringtheoryaccelerator/publicmcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server