netlify-deploy-services-updater
Deploy code from a local directory to a Netlify site by specifying the deployment path and site ID for automated publishing.
Instructions
Select and run one of the following Netlify write operations deploy-site
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| selectSchema | Yes |
Implementation Reference
- src/tools/index.ts:139-198 (registration)Registers the 'netlify-deploy-services-updater' tool (non-verbose mode) as a grouped selector for deploy domain write operations. Dispatches to sub-tools like deploy-site based on selected operation.const paramsSchema = { // @ts-ignore selectSchema: tools.length > 1 ? z.union(tools.map(tool => toSelectorSchema(tool))) : toSelectorSchema(tools[0]) }; const friendlyOperationType = operationType === 'read' ? 'reader' : 'updater'; const toolName = `netlify-${domain}-services-${friendlyOperationType}`; const toolDescription = `Select and run one of the following Netlify ${operationType} operations${readOnlyIndicator} ${toolOperations.join(', ')}`; server.registerTool(toolName, { description: toolDescription, inputSchema: paramsSchema, annotations: { readOnlyHint: operationType === 'read' } }, async (...args) => { checkCompatibility(); try { await getNetlifyAccessToken(remoteMCPRequest); } catch (error: NetlifyUnauthError | any) { if (error instanceof NetlifyUnauthError && remoteMCPRequest) { throw new NetlifyUnauthError(); } return { content: [{ type: "text", text: error?.message || 'Failed to get Netlify token' }], isError: true }; } appendToLog(`${toolName} operation: ${JSON.stringify(args)}`); const selectedSchema = args[0]?.selectSchema as any; if (!selectedSchema) { return { content: [{ type: "text", text: 'Failed to select a valid operation. Retry the MCP operation but select the operation and provide the right inputs.' }] } } const operation = selectedSchema.operation; const subtool = tools.find(subtool => subtool.operation === operation); if (!subtool) { return { content: [{ type: "text", text: 'Agent called the wrong MCP tool for this operation.' }] } } const result = await subtool.cb(selectedSchema.params || {}, {request: remoteMCPRequest, isRemoteMCP: !!remoteMCPRequest}); appendToLog(`${domain} operation result: ${JSON.stringify(result)}`); return { content: [{ type: "text", text: JSON.stringify(result) }] } }); }
- Core handler function (cb) for the deploy-site operation in local MCP mode. Zips the deploy directory, uploads to Netlify builds API, and returns deploy details.export const deploySiteDomainTool: DomainTool<typeof deploySiteParamsSchema> = { domain: 'deploy', operation: 'deploy-site', inputSchema: deploySiteParamsSchema, toolAnnotations: { readOnlyHint: false, }, omitFromRemoteMCP: true, cb: async (params, {request}) => { const { deployDirectory } = params; if (!deployDirectory) { throw new Error("Missing required parameter: deployDirectory"); } let siteId = params.siteId; if (!siteId) { siteId = await getSiteId({ projectDir: deployDirectory }); } if (!siteId) { throw new Error("Missing required parameter: siteId. Get from .netlify/state.json file or use 'netlify link' CLI command to link to an existing site and get a site id."); } const {deployId, buildId} = await zipAndBuild({ deployDirectory, siteId, request }); return JSON.stringify({ deployId, buildId, monitorDeployUrl: `https://app.netlify.com/sites/${siteId}/deploys/${deployId}` }); } }
- Zod input schema for the deploySiteDomainTool, defining parameters for site deployment.const deploySiteParamsSchema = z.object({ deployDirectory: z.string().describe(`absolute file path to the directory containing the code that should be deployed. Must be the root of the project repo unless specified.`), siteId: z.string().optional().describe(`provide the site id of the site of this site. If the agent cannot find the siteId, the user must confirm this is a new site. NEVER assume the user wants a new site. Use 'netlify link' CLI command to link to an existing site and get a site id.`) });
- src/tools/deploy-tools/index.ts:2-6 (helper)Defines the deployDomainTools array containing read and write tools for the deploy domain, used by the grouped updater/reader.import { getDeployByIdDomainTool } from './get-deploy.js'; import { getDeployBySiteIdDomainTool } from './get-deploy-for-site.js'; import { deploySiteDomainTool, deploySiteRemotelyDomainTool } from './deploy-site.js'; export const deployDomainTools = [getDeployByIdDomainTool, getDeployBySiteIdDomainTool, deploySiteDomainTool, deploySiteRemotelyDomainTool];
- Handler (cb) for deploy-site in remote MCP mode, generates CLI command with proxy token for secure remote deployment.export const deploySiteRemotelyDomainTool: DomainTool<typeof deploySiteRemotelyParamsSchema> = { domain: 'deploy', operation: 'deploy-site', inputSchema: deploySiteRemotelyParamsSchema, omitFromLocalMCP: true, toolAnnotations: { readOnlyHint: false, }, cb: async (params, {request}) => { const proxyToken = await createJWE({ accessToken: await getNetlifyAccessToken(request), siteId: params.siteId, // TODO: in the future, lock this down even further apisAllowed: [ { path: `/api/v1/sites/${params.siteId}/builds`, method: 'POST' }, // allow reads of deploys { path: '/api/v1/deploys/:deploy_id', method: 'GET' } ] }, '30m'); const proxyPath = `/proxy/${proxyToken}`; return ` To deploy this to Netlify, run the following command within the source/repo directory: \`\`\`shell npx -y @netlify/mcp@latest --site-id ${params.siteId} --proxy-path "${getOAuthIssuer()}${proxyPath}" \`\`\` This command will upload the code repo and run a build in Netlify's build system. By default, the command will wait for the deployment to completely finish (which can take time). If you want to skip waiting for the deployment to finish, you can add the \`--no-wait\` flag to the command. ` } };