Skip to main content
Glama
michaelneale

Goose App Maker MCP

by michaelneale

app_serve

Serve a web application locally on an available HTTP port to test and run applications created with Goose App Maker MCP.

Instructions

Serve an existing web application on a local HTTP server.
The server will automatically find an available port.

Can only serve one app at a time

Args:
    app_name: Name of the application to serve

Returns:
    A dictionary containing the result of the operation

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
app_nameYes

Implementation Reference

  • main.py:268-479 (handler)
    The core handler function for the 'app_serve' tool. It starts a local HTTP server for the specified app directory, includes a custom EnvAwareHandler class for handling goose_api.js requests and environment variable substitution, and manages server lifecycle.
    def app_serve(app_name: str) -> Dict[str, Any]:
        """
        Serve an existing web application on a local HTTP server.
        The server will automatically find an available port.
    
        Can only serve one app at a time
        
        Args:
            app_name: Name of the application to serve
        
        Returns:
            A dictionary containing the result of the operation
        """
        global http_server, server_port, app_response, response_ready
    
        if http_server:
            return "There is already a server running"
    
        # Reset response state
        app_response = None
        response_ready = False
        
        try:
            # Find the app directory
            app_path = os.path.join(APP_DIR, app_name)
            if not os.path.exists(app_path):
                return {
                    "success": False, 
                    "error": f"App '{app_name}' not found at {app_path}"
                }
            
            # Stop any existing server
            if http_server:
                logger.info("Stopping existing HTTP server")
                http_server.shutdown()
                http_server.server_close()
                http_server = None
            
            # Find a free port
            import socket
            def find_free_port():
                with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                    s.bind(('', 0))
                    return s.getsockname()[1]
            
            # Try the default port first, if busy find a free one
            try:
                with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                    s.bind(('', server_port))
            except OSError:
                logger.info(f"Default port {server_port} is busy, finding a free port")
                server_port = find_free_port()
                logger.info(f"Found free port: {server_port}")
            
            # Create a custom handler that serves from the app directory
            # and replaces environment variables in JavaScript files
            class EnvAwareHandler(http.server.SimpleHTTPRequestHandler):
                def __init__(self, *args, **kwargs):
                    super().__init__(*args, directory=app_path, **kwargs)
                
                def end_headers(self):
                    # Add cache control headers to ALL responses
                    self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
                    self.send_header('Pragma', 'no-cache')
                    self.send_header('Expires', '0')
                    super().end_headers()
                
                def do_GET(self):
                    # Check if this is a wait_for_response request
                    if self.path.startswith('/wait_for_response'):
                        global app_response, response_lock, response_ready
                        
                        # Reset response state for a new request
                        if self.path.startswith('/wait_for_response/reset'):
                            with response_lock:
                                app_response = None
                                response_ready = False
                            self.send_response(200)
                            self.send_header('Content-type', 'application/json')
                            self.end_headers()
                            response_data = json.dumps({"success": True, "message": "Response state reset"})
                            self.wfile.write(response_data.encode('utf-8'))
                            return
                        
                        # Check if response already exists
                        if app_response is not None and response_ready:
                            # Return the response immediately
                            self.send_response(200)
                            self.send_header('Content-type', 'application/json')
                            self.end_headers()
                            response_data = json.dumps({"success": True, "data": app_response})
                            self.wfile.write(response_data.encode('utf-8'))
                            
                            # Reset the response state after sending it
                            with response_lock:
                                app_response = None
                                response_ready = False
                            return
                        
                        # Wait for the response with timeout
                        with response_lock:
                            # Wait for up to 180 seconds for the response to be ready
                            start_time = time.time()
                            while not response_ready and time.time() - start_time < 180:
                                response_lock.wait(180 - (time.time() - start_time))
                                
                                # Check if the response is now available
                                if response_ready and app_response is not None:
                                    break
                            
                            # Check if we got the response or timed out
                            if response_ready and app_response is not None:
                                self.send_response(200)
                                self.send_header('Content-type', 'application/json')
                                self.end_headers()
                                response_data = json.dumps({"success": True, "data": app_response})
                                self.wfile.write(response_data.encode('utf-8'))
                            else:
                                # Timeout occurred
                                self.send_response(408)  # Request Timeout
                                self.send_header('Content-type', 'application/json')
                                self.end_headers()
                                response_data = json.dumps({"success": False, "error": "Timeout waiting for response"})
                                self.wfile.write(response_data.encode('utf-8'))
                        return
                    
                    # Get the file path
                    path = self.translate_path(self.path)
                    
                    # Check if the file exists
                    if os.path.isfile(path):
                        # Check if it's a JavaScript file that might need variable replacement
                        if path.endswith('.js'):
                            try:
                                with open(path, 'r') as f:
                                    content = f.read()
                                
                                # Check if the file contains environment variables that need to be replaced
                                if '$GOOSE_PORT' in content or '$GOOSE_SERVER__SECRET_KEY' in content:
                                    # Replace environment variables
                                    goose_port = os.environ.get('GOOSE_PORT', '0')
                                    secret_key = os.environ.get('GOOSE_SERVER__SECRET_KEY', '')
                                    
                                    # Replace variables
                                    content = content.replace('$GOOSE_PORT', goose_port)
                                    content = content.replace('$GOOSE_SERVER__SECRET_KEY', secret_key)
                                    
                                    # Send the modified content
                                    self.send_response(200)
                                    self.send_header('Content-type', 'application/javascript')
                                    self.send_header('Content-Length', str(len(content)))
                                    self.end_headers()
                                    self.wfile.write(content.encode('utf-8'))
                                    return
                            except Exception as e:
                                logger.error(f"Error processing JavaScript file: {e}")
                    
                    # If we didn't handle it specially, use the default handler
                    return super().do_GET()
            
            # Start the server in a separate thread
            import threading
            
            # Use a thread-safe event to signal when the server is ready
            server_ready = threading.Event()
            server_error = [None]  # Use a list to store error from thread
            
            def run_server():
                global http_server
                try:
                    with socketserver.TCPServer(("", server_port), EnvAwareHandler) as server:
                        http_server = server
                        # Signal that server is ready
                        server_ready.set()
                        logger.info(f"Serving app '{app_name}' at http://localhost:{server_port}")
                        logger.info(f"Using GOOSE_PORT={os.environ.get('GOOSE_PORT', '3000')}")
                        logger.info(f"Using GOOSE_SERVER__SECRET_KEY={os.environ.get('GOOSE_SERVER__SECRET_KEY', '')[:5]}...")
                        server.serve_forever()
                except Exception as e:
                    server_error[0] = str(e)
                    server_ready.set()  # Signal even on error
                    logger.error(f"Server error: {e}")
            
            server_thread = threading.Thread(target=run_server)
            server_thread.daemon = True
            server_thread.start()
            
            # Wait for the server to start or fail, with timeout
            if not server_ready.wait(timeout=2.0):
                return {
                    "success": False,
                    "error": "Server failed to start within timeout period"
                }
               
            # Check if there was an error
            if server_error[0]:
                return {
                    "success": False,
                    "error": f"Failed to serve app: {server_error[0]}"
                }
            
            return {
                "success": True,
                "app_name": app_name,
                "port": server_port,
                "url": f"http://localhost:{server_port}",
                "message": f"App '{app_name}' is now being served at http://localhost:{server_port}"
            }
        except Exception as e:
            logger.error(f"Error serving app: {e}")
            return {"success": False, "error": f"Failed to serve app: {str(e)}"}
  • main.py:267-268 (registration)
    The @mcp.tool() decorator registers the app_serve function as an MCP tool.
    @mcp.tool()
    def app_serve(app_name: str) -> Dict[str, Any]:
  • Docstring defining the input (app_name: str) and output (Dict[str, Any]) schema for the tool.
    """
    Serve an existing web application on a local HTTP server.
    The server will automatically find an available port.
    
    Can only serve one app at a time
    
    Args:
        app_name: Name of the application to serve
    
    Returns:
        A dictionary containing the result of the operation
    """

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/michaelneale/goose-app-maker-mcp'

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