deploy_local_folder
Deploy a local folder to Google Cloud Run by specifying the folder's absolute path, project ID, and optional region and service name. Simplifies deploying entire folder contents to a Cloud Run service.
Instructions
Deploy a local folder to Cloud Run. Takes an absolute folder path from the local filesystem that will be deployed. Use this tool if the entire folder content needs to be deployed.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| folderPath | Yes | Absolute path to the folder to deploy (e.g. "/home/user/project/src") | |
| project | Yes | Google Cloud project ID. Do not select it yourself, make sure the user provides or confirms the project ID. | |
| region | No | Region to deploy the service to | europe-west1 |
| service | No | Name of the Cloud Run service to deploy to | app |
Implementation Reference
- tools/register-tools.js:369-417 (handler)The handler function for 'deploy_local_folder' tool. Validates inputs, creates progress callback, and invokes the 'deploy' helper with the folder path as files array.async ( { project, region, service, folderPath }, { sendNotification } ) => { if (typeof project !== 'string') { throw new Error( 'Project must be specified, please prompt the user for a valid existing Google Cloud project ID.' ); } if (typeof folderPath !== 'string' || folderPath.trim() === '') { throw new Error( 'Folder path must be specified and be a non-empty string.' ); } const progressCallback = createProgressCallback(sendNotification); // Deploy to Cloud Run try { await progressCallback({ data: `Starting deployment of local folder for service ${service} in project ${project}...`, }); const response = await deploy({ projectId: project, serviceName: service, region: region, files: [folderPath], skipIamCheck: options.skipIamCheck, // Pass the new flag progressCallback, }); return { content: [ { type: 'text', text: `Cloud Run service ${service} deployed from folder ${folderPath} in project ${project}\nCloud Console: https://console.cloud.google.com/run/detail/${region}/${service}?project=${project}\nService URL: ${response.uri}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error deploying folder to Cloud Run: ${error.message || error}`, }, ], }; } }
- tools/register-tools.js:343-365 (schema)Zod input schema defining parameters for the deploy_local_folder tool: project, region, service, folderPath.inputSchema: { project: z .string() .describe( 'Google Cloud project ID. Do not select it yourself, make sure the user provides or confirms the project ID.' ) .default(options.defaultProjectId), region: z .string() .optional() .default(options.defaultRegion) .describe('Region to deploy the service to'), service: z .string() .optional() .default(options.defaultServiceName) .describe('Name of the Cloud Run service to deploy to'), folderPath: z .string() .describe( 'Absolute path to the folder to deploy (e.g. "/home/user/project/src")' ), },
- tools/register-tools.js:337-420 (registration)The registerDeployLocalFolderTool function that registers the 'deploy_local_folder' tool with server, schema, and handler.function registerDeployLocalFolderTool(server, options) { server.registerTool( 'deploy_local_folder', { description: 'Deploy a local folder to Cloud Run. Takes an absolute folder path from the local filesystem that will be deployed. Use this tool if the entire folder content needs to be deployed.', inputSchema: { project: z .string() .describe( 'Google Cloud project ID. Do not select it yourself, make sure the user provides or confirms the project ID.' ) .default(options.defaultProjectId), region: z .string() .optional() .default(options.defaultRegion) .describe('Region to deploy the service to'), service: z .string() .optional() .default(options.defaultServiceName) .describe('Name of the Cloud Run service to deploy to'), folderPath: z .string() .describe( 'Absolute path to the folder to deploy (e.g. "/home/user/project/src")' ), }, }, gcpTool( options.gcpCredentialsAvailable, async ( { project, region, service, folderPath }, { sendNotification } ) => { if (typeof project !== 'string') { throw new Error( 'Project must be specified, please prompt the user for a valid existing Google Cloud project ID.' ); } if (typeof folderPath !== 'string' || folderPath.trim() === '') { throw new Error( 'Folder path must be specified and be a non-empty string.' ); } const progressCallback = createProgressCallback(sendNotification); // Deploy to Cloud Run try { await progressCallback({ data: `Starting deployment of local folder for service ${service} in project ${project}...`, }); const response = await deploy({ projectId: project, serviceName: service, region: region, files: [folderPath], skipIamCheck: options.skipIamCheck, // Pass the new flag progressCallback, }); return { content: [ { type: 'text', text: `Cloud Run service ${service} deployed from folder ${folderPath} in project ${project}\nCloud Console: https://console.cloud.google.com/run/detail/${region}/${service}?project=${project}\nService URL: ${response.uri}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error deploying folder to Cloud Run: ${error.message || error}`, }, ], }; } } ) ); }
- tools/tools.js:36-36 (registration)Invocation of registerDeployLocalFolderTool during tool registration in the main tools module.registerDeployLocalFolderTool(server, options);
- lib/deployment/deployer.js:227-388 (helper)The 'deploy' helper function called by the tool handler. Handles zipping folder/files, Cloud Build, and Cloud Run deployment.export async function deploy({ projectId, serviceName, region, files, progressCallback, skipIamCheck, }) { if (!projectId) { const errorMsg = 'Error: projectId is required in the configuration object.'; await logAndProgress(errorMsg, progressCallback, 'error'); throw new Error(errorMsg); } if (!serviceName) { const errorMsg = 'Error: serviceName is required in the configuration object.'; await logAndProgress(errorMsg, progressCallback, 'error'); throw new Error(errorMsg); } if (!files || !Array.isArray(files) || files.length === 0) { const errorMsg = 'Error: files array is required in the configuration object.'; await logAndProgress(errorMsg, progressCallback, 'error'); if (typeof process !== 'undefined' && process.exit) { process.exit(1); } else { throw new Error(errorMsg); } } const path = await import('path'); const fs = await import('fs'); const { Storage } = await import('@google-cloud/storage'); const { CloudBuildClient } = await import('@google-cloud/cloudbuild'); const { ArtifactRegistryClient } = await import( '@google-cloud/artifact-registry' ); const { v2: CloudRunV2Module } = await import('@google-cloud/run'); const { ServicesClient } = CloudRunV2Module; const { ServiceUsageClient } = await import('@google-cloud/service-usage'); const { Logging } = await import('@google-cloud/logging'); try { const context = { storage: new Storage({ projectId }), cloudBuildClient: new CloudBuildClient({ projectId }), artifactRegistryClient: new ArtifactRegistryClient({ projectId }), runClient: new ServicesClient({ projectId }), serviceUsageClient: new ServiceUsageClient({ projectId }), loggingClient: new Logging({ projectId }), }; await ensureApisEnabled( context, projectId, REQUIRED_APIS_FOR_SOURCE_DEPLOY, progressCallback ); const bucketName = `${projectId}-source-bucket`; const imageUrl = `${region}-docker.pkg.dev/${projectId}/${REPO_NAME}/${serviceName}:${IMAGE_TAG}`; await logAndProgress(`Project: ${projectId}`, progressCallback); await logAndProgress(`Region: ${region}`, progressCallback); await logAndProgress(`Service Name: ${serviceName}`, progressCallback); await logAndProgress(`Files to deploy: ${files.length}`, progressCallback); let hasDockerfile = false; if ( files.length === 1 && typeof files[0] === 'string' && fs.statSync(files[0]).isDirectory() ) { // Handle folder deployment: check for Dockerfile inside the folder const dockerfilePath = path.join(files[0], 'Dockerfile'); const dockerfilePathLowerCase = path.join(files[0], 'dockerfile'); if ( fs.existsSync(dockerfilePath) || fs.existsSync(dockerfilePathLowerCase) ) { hasDockerfile = true; } } else { // Handle file list deployment or file content deployment for (const file of files) { if (typeof file === 'string') { if (path.basename(file).toLowerCase() === 'dockerfile') { hasDockerfile = true; break; } } else if (typeof file === 'object' && file.filename) { if (path.basename(file.filename).toLowerCase() === 'dockerfile') { hasDockerfile = true; break; } } } } await logAndProgress(`Dockerfile: ${hasDockerfile}`, progressCallback); const bucket = await ensureStorageBucketExists( context, bucketName, region, progressCallback ); const zipBuffer = await zipFiles(files, progressCallback); await uploadToStorageBucket( context, bucket, zipBuffer, ZIP_FILE_NAME, progressCallback ); await logAndProgress('Source code uploaded successfully', progressCallback); await ensureArtifactRegistryRepoExists( context, projectId, region, REPO_NAME, 'DOCKER', progressCallback ); const buildResult = await triggerCloudBuild( context, projectId, region, bucketName, ZIP_FILE_NAME, REPO_NAME, imageUrl, hasDockerfile, progressCallback ); const builtImageUrl = buildResult.results.images[0].name; const service = await deployToCloudRun( context, projectId, region, serviceName, builtImageUrl, progressCallback, skipIamCheck ); await logAndProgress(`Deployment Completed Successfully`, progressCallback); return service; } catch (error) { const deployFailedMessage = `Deployment Failed: ${error.message}`; console.error(`Deployment Failed`, error); await logAndProgress(deployFailedMessage, progressCallback, 'error'); throw error; } }