Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.4.0] - 2025-08-31

### Added

- **Project Deployment Tool**: New tool for deploying metadata to Salesforce orgs
- `deploy_start` - Deploy metadata components to target org with extensive configuration options
- Supports multiple deployment sources: manifest (package.xml), metadata components, source directories
- Configurable test execution levels (NoTestRun, RunSpecifiedTests, RunLocalTests, RunAllTestsInOrg)
- Dry-run capability for validation without actual deployment
- Support for single package deployments
- Respects READ_ONLY and ALLOWED_ORGS permissions
- Integrated with existing permission system for secure deployments

## [1.3.2] - 2025-08-31

### Added
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,5 @@ The project supports Desktop Extension (.dxt) packaging for one-click installati
- Check that new tools are registered in both the tool file and manifest.json
- Verify permission checks are implemented for destructive operations

- don't forget to add shebang to the build file after creating a new build and before publishing it. it's important
- don't forget to add shebang to the build file after creating a new build and before publishing it. it's important
- when updating documentation, update the @manifest.json as well
10 changes: 9 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"dxt_version": "0.2",
"name": "salesforce-mcp-server",
"display_name": "Salesforce MCP Server",
"version": "1.3.0",
"version": "1.4.0",
"description": "Salesforce MCP Server - Interact with Salesforce orgs through AI assistants",
"icon": "icon.png",
"long_description": "Enables AI assistants to execute Apex code, query Salesforce data, and manage org metadata using your existing Salesforce CLI authentication. Perfect for developers and administrators who want to automate Salesforce tasks through natural language interactions.\n\nSupports environment variables:\n- READ_ONLY=true - Prevents Apex code execution\n- ALLOWED_ORGS=ALL or comma-separated org list - Restricts access to specific orgs (default: ALL)",
Expand Down Expand Up @@ -187,6 +187,14 @@
{
"name": "delete_record",
"description": "Delete a record from a Salesforce org using REST API"
},
{
"name": "generate_component",
"description": "Generate Lightning Web Components (LWC) and Aura components with configurable templates"
},
{
"name": "deploy_start",
"description": "Deploy metadata components to a Salesforce org with test execution options"
}
],
"compatibility": {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@advanced-communities/salesforce-mcp-server",
"version": "1.3.2",
"version": "1.4.0",
"description": "MCP server enabling AI assistants to interact with Salesforce orgs through the Salesforce CLI",
"main": "./src/index.ts",
"scripts": {
Expand Down
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { registerPackageTools } from "./tools/package.js";
import { registerSchemaTools } from "./tools/schema.js";
import { registerSearchTools } from "./tools/search.js";
import { registerLightningTools } from "./tools/lightning.js";
import { registerProjectTools } from "./tools/project.js";
import { permissions } from "./config/permissions.js";

/**
Expand All @@ -24,7 +25,7 @@ function buildServerDescription(): string {
const allowedOrgs = permissions.getAllowedOrgs();
const permissionInfo = [];

let description = `Salesforce MCP Server v1.3.2 - AI-powered Salesforce automation via CLI integration\n`;
let description = `Salesforce MCP Server v1.4.0 - AI-powered Salesforce automation via CLI integration\n`;
description += `Capabilities: Apex execution, SOQL queries, org management, code testing & coverage\n`;

if (readOnlyMode) {
Expand All @@ -41,7 +42,7 @@ function buildServerDescription(): string {
description += `Security: Full access enabled for all authenticated orgs`;
}

description += `\nTools: 29 available (apex, query, search, sobject, org management, records, admin, code analyzer, scanner, package, schema)`;
description += `\nTools: 38 available (apex, query, search, sobject, org management, records, admin, code analyzer, scanner, package, schema, lightning, project deployment)`;

return description;
}
Expand All @@ -67,6 +68,7 @@ registerPackageTools(server);
registerSchemaTools(server);
registerSearchTools(server);
registerLightningTools(server);
registerProjectTools(server);

async function main() {
const transport = new StdioServerTransport();
Expand Down
183 changes: 183 additions & 0 deletions src/tools/project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { permissions } from "../config/permissions.js";
import { executeSfCommand, executeSfCommandRaw } from "../utils/sfCommand.js";

const deployStart = async (
targetOrg: string,
dryRun: boolean,
manifest: string,
metadata: string,
metadataDirectory: string,
singlePackage: boolean,
sourceDirectory: string,
tests: string,
testLevel: string
) => {
let sfCommand = `sf project deploy start --target-org ${targetOrg} --json `;

if (dryRun) {
sfCommand += `--dry-run `;
}

if (manifest && manifest.length > 0) {
sfCommand += `--manifest ${manifest} `;
}

if (metadata && metadata.length > 0) {
sfCommand += `--metadata ${metadata} `;
}

if (metadataDirectory && metadataDirectory.length > 0) {
sfCommand += `--metadata-dir ${metadataDirectory} `;
}

if (singlePackage) {
sfCommand += `--single-package `;
}

if (sourceDirectory && sourceDirectory.length > 0) {
sfCommand += `--source-dir ${sourceDirectory} `;
}

if (tests && tests.length > 0) {
sfCommand += `--tests ${tests} `;
}

if (testLevel && testLevel.length > 0) {
sfCommand += `--test-level ${testLevel} `;
}

try {
const result = await executeSfCommand(sfCommand);
return result;
} catch (error) {
throw error;
}
};

export const registerProjectTools = (server: McpServer) => {
server.tool(
"deploy_start",
"",
{
input: z.object({
targetOrg: z
.string()
.describe(
"Username or alias of the target org. Not required if the 'target-org' configuration variable is already set."
),
dryRun: z
.boolean()
.describe(
"Validate deploy and run Apex tests but don't save to the org."
),
manifest: z
.string()
.optional()
.describe(
"Full file path for manifest (package.xml) of components to deploy. All child components are included. If you specify this flag, don’t specify --metadata or --source-dir."
),
metadata: z
.string()
.optional()
.describe(
"Metadata component names to deploy. Wildcards (`*` ) supported as long as you use quotes, such as `ApexClass:MyClass*`."
),
metadataDirectory: z
.string()
.optional()
.describe(
"Root of directory or zip file of metadata formatted files to deploy."
),
singlePackage: z
.boolean()
.optional()
.describe(
"Indicates that the metadata zip file points to a directory structure for a single package."
),
sourceDirectory: z
.string()
.optional()
.describe(
"Path to the local source files to deploy. The supplied path can be to a single file (in which case the operation is applied to only one file) or to a folder (in which case the operation is applied to all metadata types in the directory and its subdirectories). If you specify this flag, don’t specify --metadata or --manifest."
),
tests: z
.string()
.optional()
.describe(
'Apex tests to run when --test-level is RunSpecifiedTests. If a test name contains a space, enclose it in double quotes. For multiple test names, use one of the following formats: - Repeat the flag for multiple test names: --tests Test1 --tests Test2 --tests "Test With Space" - Separate the test names with spaces: --tests Test1 Test2 "Test With Space"'
),
testLevel: z
.string()
.optional()
.describe(
"Deployment Apex testing level. Valid values are: - NoTestRun — No tests are run. This test level applies only to deployments to development environments, such as sandbox, Developer Edition, or trial orgs. This test level is the default for development environments. - RunSpecifiedTests — Runs only the tests that you specify with the --tests flag. Code coverage requirements differ from the default coverage requirements when using this test level. Executed tests must comprise a minimum of 75% code coverage for each class and trigger in the deployment package. This coverage is computed for each class and trigger individually and is different than the overall coverage percentage. - RunLocalTests — All tests in your org are run, except the ones that originate from installed managed and unlocked packages. This test level is the default for production deployments that include Apex classes or triggers. - RunAllTestsInOrg — All tests in your org are run, including tests of managed packages.If you don’t specify a test level, the default behavior depends on the contents of your deployment package and target org."
),
}),
},
async ({ input }) => {
const {
targetOrg,
dryRun,
manifest,
metadata,
metadataDirectory,
singlePackage,
sourceDirectory,
tests,
testLevel,
} = input;

if (permissions.isReadOnly()) {
return {
content: [
{
type: "text",
text: JSON.stringify({
success: false,
compiled: false,
compileProblem:
"Operation not allowed: Cannot generate components in READ_ONLY mode",
}),
},
],
};
}

if (!permissions.isOrgAllowed(targetOrg)) {
return {
content: [
{
type: "text",
text: JSON.stringify({
success: false,
message: `Access denied: Org '${targetOrg}' is not in the allowed list`,
}),
},
],
};
}

const result = await deployStart(
targetOrg,
dryRun,
manifest || "",
metadata || "",
metadataDirectory || "",
singlePackage || false,
sourceDirectory || "",
tests || "",
testLevel || ""
);
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
}
);
};