# Directory Structure

```
├── .github
│   ├── CODEOWNERS
│   ├── dependabot.yml
│   └── workflows
│       ├── ci.yml
│       ├── codeql.yml
│       └── scorecard.yml
├── .gitignore
├── LICENSE
├── package.json
├── README.md
├── SECURITY.md
├── src
│   ├── index.ts
│   ├── tools
│   │   ├── aggregateQuery.ts
│   │   ├── describe.ts
│   │   ├── dml.ts
│   │   ├── executeAnonymous.ts
│   │   ├── manageDebugLogs.ts
│   │   ├── manageField.ts
│   │   ├── manageFieldPermissions.ts
│   │   ├── manageObject.ts
│   │   ├── query.ts
│   │   ├── readApex.ts
│   │   ├── readApexTrigger.ts
│   │   ├── search.ts
│   │   ├── searchAll.ts
│   │   ├── writeApex.ts
│   │   └── writeApexTrigger.ts
│   ├── types
│   │   ├── connection.ts
│   │   ├── metadata.ts
│   │   └── salesforce.ts
│   ├── typings.d.ts
│   └── utils
│       ├── connection.ts
│       └── errorHandler.ts
├── test
│   ├── package.test.js
│   └── repo.guardrails.test.js
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/
package-lock.json

# Build output
dist/

# Environment variables
.env

# IDE and OS files
.DS_Store
.vscode/
.idea/

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Salesforce MCP Server
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/tsmztech/mcp-server-salesforce/badge)](https://securityscorecards.dev/viewer/?uri=github.com/tsmztech/mcp-server-salesforce)


An MCP (Model Context Protocol) server implementation that integrates Claude with Salesforce, enabling natural language interactions with your Salesforce data and metadata. This server allows Claude to query, modify, and manage your Salesforce objects and records using everyday language.

<a href="https://glama.ai/mcp/servers/kqeniawbr6">
  <img width="380" height="200" src="https://glama.ai/mcp/servers/kqeniawbr6/badge" alt="Salesforce Server MCP server" />
</a>

## Features

* **Object and Field Management**: Create and modify custom objects and fields using natural language
* **Smart Object Search**: Find Salesforce objects using partial name matches
* **Detailed Schema Information**: Get comprehensive field and relationship details for any object
* **Flexible Data Queries**: Query records with relationship support and complex filters
* **Data Manipulation**: Insert, update, delete, and upsert records with ease
* **Cross-Object Search**: Search across multiple objects using SOSL
* **Apex Code Management**: Read, create, and update Apex classes and triggers
* **Intuitive Error Handling**: Clear feedback with Salesforce-specific error details
* **Switchable Authentication**: Supports multiple orgs. Easily switch your active Salesforce org based on the default org configured in your VS Code workspace (use Salesforce_CLI authentication for this feature).

## Installation

```bash
npm install -g @tsmztech/mcp-server-salesforce
```

## Tools

### salesforce_search_objects
Search for standard and custom objects:
* Search by partial name matches
* Finds both standard and custom objects
* Example: "Find objects related to Account" will find Account, AccountHistory, etc.

### salesforce_describe_object
Get detailed object schema information:
* Field definitions and properties
* Relationship details
* Picklist values
* Example: "Show me all fields in the Account object"

### salesforce_query_records
Query records with relationship support:
* Parent-to-child relationships
* Child-to-parent relationships
* Complex WHERE conditions
* Example: "Get all Accounts with their related Contacts"
* Note: For queries with GROUP BY or aggregate functions, use salesforce_aggregate_query

### salesforce_aggregate_query
Execute aggregate queries with GROUP BY:
* GROUP BY single or multiple fields
* Aggregate functions: COUNT, COUNT_DISTINCT, SUM, AVG, MIN, MAX
* HAVING clauses for filtering grouped results
* Date/time grouping functions
* Example: "Count opportunities by stage" or "Find accounts with more than 10 opportunities"

### salesforce_dml_records
Perform data operations:
* Insert new records
* Update existing records
* Delete records
* Upsert using external IDs
* Example: "Update status of multiple accounts"

### salesforce_manage_object
Create and modify custom objects:
* Create new custom objects
* Update object properties
* Configure sharing settings
* Example: "Create a Customer Feedback object"

### salesforce_manage_field
Manage object fields:
* Add new custom fields
* Modify field properties
* Create relationships
* Automatically grants Field Level Security to System Administrator by default
* Use `grantAccessTo` parameter to specify different profiles
* Example: "Add a Rating picklist field to Account"

### salesforce_manage_field_permissions
Manage Field Level Security (Field Permissions):
* Grant or revoke read/edit access to fields for specific profiles
* View current field permissions
* Bulk update permissions for multiple profiles
* Useful for managing permissions after field creation or for existing fields
* Example: "Grant System Administrator access to Custom_Field__c on Account"

### salesforce_search_all
Search across multiple objects:
* SOSL-based search
* Multiple object support
* Field snippets
* Example: "Search for 'cloud' across Accounts and Opportunities"

### salesforce_read_apex
Read Apex classes:
* Get full source code of specific classes
* List classes matching name patterns
* View class metadata (API version, status, etc.)
* Support for wildcards (* and ?) in name patterns
* Example: "Show me the AccountController class" or "Find all classes matching Account*Cont*"

### salesforce_write_apex
Create and update Apex classes:
* Create new Apex classes
* Update existing class implementations
* Specify API versions
* Example: "Create a new Apex class for handling account operations"

### salesforce_read_apex_trigger
Read Apex triggers:
* Get full source code of specific triggers
* List triggers matching name patterns
* View trigger metadata (API version, object, status, etc.)
* Support for wildcards (* and ?) in name patterns
* Example: "Show me the AccountTrigger" or "Find all triggers for Contact object"

### salesforce_write_apex_trigger
Create and update Apex triggers:
* Create new Apex triggers for specific objects
* Update existing trigger implementations
* Specify API versions and event operations
* Example: "Create a new trigger for the Account object" or "Update the Lead trigger"

### salesforce_execute_anonymous
Execute anonymous Apex code:
* Run Apex code without creating a permanent class
* View debug logs and execution results
* Useful for data operations not directly supported by other tools
* Example: "Execute Apex code to calculate account metrics" or "Run a script to update related records"

### salesforce_manage_debug_logs
Manage debug logs for Salesforce users:
* Enable debug logs for specific users
* Disable active debug log configurations
* Retrieve and view debug logs
* Configure log levels (NONE, ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST)
* Example: "Enable debug logs for user@example.com" or "Retrieve recent logs for an admin user"

## Setup

### Salesforce Authentication
You can connect to Salesforce using one of three authentication methods:

#### 1. Username/Password Authentication (Default)
1. Set up your Salesforce credentials
2. Get your security token (Reset from Salesforce Settings)

#### 2. OAuth 2.0 Client Credentials Flow
1. Create a Connected App in Salesforce
2. Enable OAuth settings and select "Client Credentials Flow"
3. Set appropriate scopes (typically "api" is sufficient)
4. Save the Client ID and Client Secret
5. **Important**: Note your instance URL (e.g., `https://your-domain.my.salesforce.com`) as it's required for authentication

#### 3. Salesforce CLI Authentication (Recommended for local/dev) (contribution by @andrea9293)
1. Install and authenticate Salesforce CLI (`sf`).
2. Make sure your org is authenticated and accessible via `sf org display --json` in the root of your Salesforce project.
3. The server will automatically retrieve the access token and instance url using the CLI.



### Usage with Claude Desktop


Add to your `claude_desktop_config.json`:


#### For Salesforce CLI Authentication:
```json
{
  "mcpServers": {
    "salesforce": {
      "command": "npx",
      "args": ["-y", "@tsmztech/mcp-server-salesforce"],
      "env": {
        "SALESFORCE_CONNECTION_TYPE": "Salesforce_CLI"
      }
    }
  }
}
```

#### For Username/Password Authentication:
```json
{
  "mcpServers": {
    "salesforce": {
      "command": "npx",
      "args": ["-y", "@tsmztech/mcp-server-salesforce"],
      "env": {
        "SALESFORCE_CONNECTION_TYPE": "User_Password",
        "SALESFORCE_USERNAME": "your_username",
        "SALESFORCE_PASSWORD": "your_password",
        "SALESFORCE_TOKEN": "your_security_token",
        "SALESFORCE_INSTANCE_URL": "org_url"        // Optional. Default value: https://login.salesforce.com
      }
    }
  }
}
```

#### For OAuth 2.0 Client Credentials Flow:
```json
{
  "mcpServers": {
    "salesforce": {
      "command": "npx",
      "args": ["-y", "@tsmztech/mcp-server-salesforce"],
      "env": {
        "SALESFORCE_CONNECTION_TYPE": "OAuth_2.0_Client_Credentials",
        "SALESFORCE_CLIENT_ID": "your_client_id",
        "SALESFORCE_CLIENT_SECRET": "your_client_secret",
        "SALESFORCE_INSTANCE_URL": "https://your-domain.my.salesforce.com"  // REQUIRED: Must be your exact Salesforce instance URL
      }
    }
  }
}
```

> **Note**: For OAuth 2.0 Client Credentials Flow, the `SALESFORCE_INSTANCE_URL` must be your exact Salesforce instance URL (e.g., `https://your-domain.my.salesforce.com`). The token endpoint will be constructed as `<instance_url>/services/oauth2/token`.

## Example Usage

### Searching Objects
```
"Find all objects related to Accounts"
"Show me objects that handle customer service"
"What objects are available for order management?"
```

### Getting Schema Information
```
"What fields are available in the Account object?"
"Show me the picklist values for Case Status"
"Describe the relationship fields in Opportunity"
```

### Querying Records
```
"Get all Accounts created this month"
"Show me high-priority Cases with their related Contacts"
"Find all Opportunities over $100k"
```

### Aggregate Queries
```
"Count opportunities by stage"
"Show me the total revenue by account"
"Find accounts with more than 10 opportunities"
"Calculate average deal size by sales rep and quarter"
"Get the number of cases by priority and status"
```

### Managing Custom Objects
```
"Create a Customer Feedback object"
"Add a Rating field to the Feedback object"
"Update sharing settings for the Service Request object"
```
Examples with Field Level Security:
```
# Default - grants access to System Administrator automatically
"Create a Status picklist field on Custom_Object__c"

# Custom profiles - grants access to specified profiles
"Create a Revenue currency field on Account and grant access to Sales User and Marketing User profiles"
```

### Managing Field Permissions
```
"Grant System Administrator access to Custom_Field__c on Account"
"Give read-only access to Rating__c field for Sales User profile"
"View which profiles have access to the Custom_Field__c"
"Revoke field access for specific profiles"
```

### Searching Across Objects
```
"Search for 'cloud' in Accounts and Opportunities"
"Find mentions of 'network issue' in Cases and Knowledge Articles"
"Search for customer name across all relevant objects"
```

### Managing Apex Code
```
"Show me all Apex classes with 'Controller' in the name"
"Get the full code for the AccountService class"
"Create a new Apex utility class for handling date operations"
"Update the LeadConverter class to add a new method"
```

### Managing Apex Triggers
```
"List all triggers for the Account object"
"Show me the code for the ContactTrigger"
"Create a new trigger for the Opportunity object"
"Update the Case trigger to handle after delete events"
```

### Executing Anonymous Apex Code
```
"Execute Apex code to calculate account metrics"
"Run a script to update related records"
"Execute a batch job to process large datasets"
```

### Managing Debug Logs
```
"Enable debug logs for user@example.com"
"Retrieve recent logs for an admin user"
"Disable debug logs for a specific user"
"Configure log level to DEBUG for a user"
```

## Development

### Building from source
```bash
# Clone the repository
git clone https://github.com/tsmztech/mcp-server-salesforce.git

# Navigate to directory
cd mcp-server-salesforce

# Install dependencies
npm install

# Build the project
npm run build
```

## Contributing
Contributions are welcome! Feel free to submit a Pull Request.

## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Issues and Support
If you encounter any issues or need support, please file an issue on the [GitHub repository](https://github.com/tsmztech/mcp-server-salesforce/issues).
```

--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------

```markdown
# Security Policy

## Reporting Security Vulnerabilities

We take security seriously. If you discover a security vulnerability, please report it responsibly.

### How to Report

Please use GitHub's private vulnerability reporting:
- Go to the [Security tab](https://github.com/tsmztech/mcp-server-salesforce/security/advisories)
- Click "Report a vulnerability"

This ensures the report stays private until a fix is available.

### Response Timeline

- Initial response: Within 72 hours
- Patch/mitigation: Within 14 days for critical issues

## Important Security Notes

⚠️ **For MCP Server Salesforce users:**
- **NEVER** commit credentials or `.env` files
- **ALWAYS** use Salesforce Sandbox environments for testing
- **NEVER** test with production Salesforce data
- All SOQL inputs are sanitized to prevent injection

## Supported Versions

| Version | Supported          |
| ------- | ------------------ |
| main    | :white_check_mark: |
| < 1.0   | :x:                |

---

*Please do not publicly disclose vulnerabilities until we've had a chance to address them.*
```

--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------

```typescript
declare module 'jsforce';

```

--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------

```yaml
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 5

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 5

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
    "compilerOptions": {
      "target": "ES2020",
      "module": "ES2022",
      "moduleResolution": "bundler",
      "outDir": "./dist",
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
    "declaration": true,
    "rootDir": "./src"
    },
    "include": ["src/**/*.ts"],
    "exclude": ["node_modules", "dist"]
  }
  
```

--------------------------------------------------------------------------------
/test/package.test.js:
--------------------------------------------------------------------------------

```javascript
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';

const pkg = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf8'));

test('package.json has name and version', () => {
  assert.ok(pkg.name && typeof pkg.name === 'string');
  assert.ok(pkg.version && typeof pkg.version === 'string');
});

```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
name: ci
on:
  pull_request:
permissions:
  contents: read

jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
      - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
        with:
          node-version: '20'
      - name: Install dependencies
        run: |
          if [ -f package-lock.json ]; then npm ci
          elif [ -f yarn.lock ]; then yarn install --frozen-lockfile
          elif [ -f pnpm-lock.yaml ]; then corepack enable && pnpm install --frozen-lockfile
          else npm install --no-audit --no-fund; fi
      - name: Run tests
        run: npm test

```

--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------

```yaml
name: CodeQL
on:
  pull_request:
    branches: [ "main" ]
  schedule:
    - cron: '0 0 * * 1' # weekly baseline
  workflow_dispatch: {} # optional manual

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v5
      - uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885
        with:
          languages: javascript
      - uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885
      - uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885
        with:
          category: "/language:javascript"

```

--------------------------------------------------------------------------------
/src/types/salesforce.ts:
--------------------------------------------------------------------------------

```typescript
export interface SalesforceObject {
    name: string;
    label: string;
    custom: boolean;
  }
  
  export interface SalesforceField {
    name: string;
    label: string;
    type: string;
    nillable: boolean;
    length?: number;
    picklistValues: Array<{ value: string }>;
    defaultValue: string | null;
    referenceTo: string[];
  }
  
  export interface SalesforceDescribeResponse {
    name: string;
    label: string;
    fields: SalesforceField[];
    custom: boolean;
  }
  
  export interface SalesforceError {
    statusCode: string;
    message: string;
    fields?: string[];
  }
  
  export interface DMLResult {
    success: boolean;
    id?: string;
    errors?: SalesforceError[] | SalesforceError;
  }
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
    "name": "@tsmztech/mcp-server-salesforce",
    "version": "0.0.3",
    "description": "A Salesforce connector MCP Server.",
    "main": "dist/index.js",
    "types": "dist/index.d.ts",
    "type": "module",
    "bin": {
      "salesforce-connector": "dist/index.js"
    },
    "files": [
      "dist"
    ],
    "scripts": {
      "build": "tsc && shx chmod +x dist/*.js",
      "prepare": "npm run build",
      "watch": "tsc --watch",
      "test": "node --test"
    },
    "keywords": [
      "mcp",
      "salesforce",
      "claude",
      "ai"
    ],
    "author": "tsmztech",
    "license": "MIT",
    "dependencies": {
      "@modelcontextprotocol/sdk": "1.19.1",
      "dotenv": "^17.2.1",
      "jsforce": "^3.10.3"
    },
    "devDependencies": {
      "@types/node": "^24.3.0",
      "typescript": "^5.7.2",
      "shx": "^0.4.0"
    }
  }
  
```

--------------------------------------------------------------------------------
/src/types/metadata.ts:
--------------------------------------------------------------------------------

```typescript
export interface MetadataInfo {
  fullName: string;
  label: string;
  pluralLabel?: string;
  nameField?: {
    type: string;
    label: string;
    displayFormat?: string;
  };
  deploymentStatus?: 'Deployed' | 'InDevelopment';
  sharingModel?: 'ReadWrite' | 'Read' | 'Private' | 'ControlledByParent';
  enableActivities?: boolean;
  description?: string;
}

export interface ValueSetDefinition {
  sorted?: boolean;
  value: Array<{
    fullName: string;
    default?: boolean;
    label: string;
  }>;
}

export interface FieldMetadataInfo {
  fullName: string;
  label: string;
  type: string;
  required?: boolean;
  unique?: boolean;
  externalId?: boolean;
  length?: number;
  precision?: number;
  scale?: number;
  visibleLines?: number;
  referenceTo?: string;
  relationshipLabel?: string;
  relationshipName?: string;
  deleteConstraint?: 'Cascade' | 'Restrict' | 'SetNull';
  valueSet?: {
    valueSetDefinition: ValueSetDefinition;
  };
  defaultValue?: string | number | boolean;
  description?: string;
}
```

--------------------------------------------------------------------------------
/src/utils/errorHandler.ts:
--------------------------------------------------------------------------------

```typescript
interface ErrorResult {
    success: boolean;
    fullName?: string;
    errors?: Array<{ message: string; statusCode?: string; fields?: string | string[]; }> | 
            { message: string; statusCode?: string; fields?: string | string[]; };
  }
  
  export function formatMetadataError(result: ErrorResult | ErrorResult[], operation: string): string {
    let errorMessage = `Failed to ${operation}`;
    const saveResult = Array.isArray(result) ? result[0] : result;
    
    if (saveResult && saveResult.errors) {
      if (Array.isArray(saveResult.errors)) {
        errorMessage += ': ' + saveResult.errors.map((e: { message: string }) => e.message).join(', ');
      } else if (typeof saveResult.errors === 'object') {
        const error = saveResult.errors;
        errorMessage += `: ${error.message}`;
        if (error.fields) {
          errorMessage += ` (Field: ${error.fields})`;
        }
        if (error.statusCode) {
          errorMessage += ` [${error.statusCode}]`;
        }
      } else {
        errorMessage += ': ' + String(saveResult.errors);
      }
    }
  
    return errorMessage;
  }
```

--------------------------------------------------------------------------------
/test/repo.guardrails.test.js:
--------------------------------------------------------------------------------

```javascript
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

const root = process.cwd();
const workflowsDir = join(root, '.github', 'workflows');

// helper: does any workflow file look like a scorecard workflow?
function hasScorecardWorkflow() {
  if (!existsSync(workflowsDir)) return false;
  const files = readdirSync(workflowsDir);
  return files.some(f => /scorecard/i.test(f));
}

test('README exists and is non-empty', () => {
  const p = join(root, 'README.md');
  assert.ok(existsSync(p), 'README.md should exist');
  const content = readFileSync(p, 'utf8').trim();
  assert.ok(content.length > 20, 'README.md should not be empty');
});

test('Security + automation guardrails present (soft check)', () => {
  // SECURITY.md is strongly recommended, but don’t hard-fail your first PR if you’re adding it later.
  const hasSecurity = existsSync(join(root, 'SECURITY.md'));
  assert.ok(hasSecurity, 'SECURITY.md should exist (add a minimal policy)');

  // Accept any workflow file that includes "scorecard" in its name
  assert.ok(
    hasScorecardWorkflow(),
    'Expected a Scorecard workflow in .github/workflows (e.g., scorecards.yml)'
  );
});

```

--------------------------------------------------------------------------------
/.github/workflows/scorecard.yml:
--------------------------------------------------------------------------------

```yaml
name: Scorecard supply-chain security
on:
  branch_protection_rule:
  schedule:
    - cron: '24 0 * * 3'
  workflow_dispatch: {}

permissions: read-all

jobs:
  analysis:
    name: Scorecard analysis
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      id-token: write
    steps:
      - name: Harden runner
        uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a
        with:
          egress-policy: audit
      - name: Checkout code
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
        with:
          persist-credentials: false
      - name: Run analysis
        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a
        with:
          results_file: results.sarif
          results_format: sarif
          publish_results: true
          # repo_token: ${{ secrets.SCORECARD_TOKEN }}  # optional PAT
      - name: Upload artifact
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
        with:
          name: SARIF file
          path: results.sarif
          retention-days: 5
      - name: Upload to code-scanning
        uses: github/codeql-action/upload-sarif@64d10c13136e1c5bce3e5fbde8d4906eeaafc885
        with:
          sarif_file: results.sarif

```

--------------------------------------------------------------------------------
/src/types/connection.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Enum representing the available Salesforce connection types
 */
export enum ConnectionType {
  /**
   * Standard username/password authentication with security token
   * Requires SALESFORCE_USERNAME, SALESFORCE_PASSWORD, and optionally SALESFORCE_TOKEN
   */
  User_Password = 'User_Password',
  
  /**
   * OAuth 2.0 Client Credentials Flow using client ID and secret
   * Requires SALESFORCE_CLIENT_ID and SALESFORCE_CLIENT_SECRET
   */
  OAuth_2_0_Client_Credentials = 'OAuth_2.0_Client_Credentials',
  
  /**
   * Salesforce CLI authentication using sf org display command
   * Requires Salesforce CLI to be installed and an authenticated org
   */
  Salesforce_CLI = 'Salesforce_CLI'
}

/**
 * Configuration options for Salesforce connection
 */
export interface ConnectionConfig {
  /**
   * The type of connection to use
   * @default ConnectionType.User_Password
   */
  type?: ConnectionType;
  
  /**
   * The login URL for Salesforce instance
   * @default 'https://login.salesforce.com'
   */
  loginUrl?: string;
}

/**
 * Interface for Salesforce CLI org display response
 */
export interface SalesforceCLIResponse {
  status: number;
  result: {
    id: string;
    apiVersion: string;
    accessToken: string;
    instanceUrl: string;
    username: string;
    clientId: string;
    connectedStatus: string;
    alias?: string;
  };
  warnings?: string[];
}

```

--------------------------------------------------------------------------------
/src/tools/describe.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { SalesforceField, SalesforceDescribeResponse } from "../types/salesforce";

export const DESCRIBE_OBJECT: Tool = {
  name: "salesforce_describe_object",
  description: "Get detailed schema metadata including all fields, relationships, and field properties of any Salesforce object. Examples: 'Account' shows all Account fields including custom fields; 'Case' shows all Case fields including relationships to Account, Contact etc.",
  inputSchema: {
    type: "object",
    properties: {
      objectName: {
        type: "string",
        description: "API name of the object (e.g., 'Account', 'Contact', 'Custom_Object__c')"
      }
    },
    required: ["objectName"]
  }
};

export async function handleDescribeObject(conn: any, objectName: string) {
  const describe = await conn.describe(objectName) as SalesforceDescribeResponse;
  
  // Format the output
  const formattedDescription = `
Object: ${describe.name} (${describe.label})${describe.custom ? ' (Custom Object)' : ''}
Fields:
${describe.fields.map((field: SalesforceField) => `  - ${field.name} (${field.label})
    Type: ${field.type}${field.length ? `, Length: ${field.length}` : ''}
    Required: ${!field.nillable}
    ${field.referenceTo && field.referenceTo.length > 0 ? `References: ${field.referenceTo.join(', ')}` : ''}
    ${field.picklistValues && field.picklistValues.length > 0 ? `Picklist Values: ${field.picklistValues.map((v: { value: string }) => v.value).join(', ')}` : ''}`
  ).join('\n')}`;

  return {
    content: [{
      type: "text",
      text: formattedDescription
    }],
    isError: false,
  };
}
```

--------------------------------------------------------------------------------
/src/tools/search.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { SalesforceObject } from "../types/salesforce";

export const SEARCH_OBJECTS: Tool = {
  name: "salesforce_search_objects",
  description: "Search for Salesforce standard and custom objects by name pattern. Examples: 'Account' will find Account, AccountHistory; 'Order' will find WorkOrder, ServiceOrder__c etc.",
  inputSchema: {
    type: "object",
    properties: {
      searchPattern: {
        type: "string",
        description: "Search pattern to find objects (e.g., 'Account Coverage' will find objects like 'AccountCoverage__c')"
      }
    },
    required: ["searchPattern"]
  }
};

export async function handleSearchObjects(conn: any, searchPattern: string) {
  // Get list of all objects
  const describeGlobal = await conn.describeGlobal();
  
  // Process search pattern to create a more flexible search
  const searchTerms = searchPattern.toLowerCase().split(' ').filter(term => term.length > 0);
  
  // Filter objects based on search pattern
  const matchingObjects = describeGlobal.sobjects.filter((obj: SalesforceObject) => {
    const objectName = obj.name.toLowerCase();
    const objectLabel = obj.label.toLowerCase();
    
    // Check if all search terms are present in either the API name or label
    return searchTerms.every(term => 
      objectName.includes(term) || objectLabel.includes(term)
    );
  });

  if (matchingObjects.length === 0) {
    return {
      content: [{
        type: "text",
        text: `No Salesforce objects found matching "${searchPattern}".`
      }],
      isError: false,
    };
  }

  // Format the output
  const formattedResults = matchingObjects.map((obj: SalesforceObject) => 
    `${obj.name}${obj.custom ? ' (Custom)' : ''}\n  Label: ${obj.label}`
  ).join('\n\n');

  return {
    content: [{
      type: "text",
      text: `Found ${matchingObjects.length} matching objects:\n\n${formattedResults}`
    }],
    isError: false,
  };
}
```

--------------------------------------------------------------------------------
/src/tools/dml.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { DMLResult } from "../types/salesforce";

export const DML_RECORDS: Tool = {
  name: "salesforce_dml_records",
  description: `Perform data manipulation operations on Salesforce records:
  - insert: Create new records
  - update: Modify existing records (requires Id)
  - delete: Remove records (requires Id)
  - upsert: Insert or update based on external ID field
  Examples: Insert new Accounts, Update Case status, Delete old records, Upsert based on custom external ID`,
  inputSchema: {
    type: "object",
    properties: {
      operation: {
        type: "string",
        enum: ["insert", "update", "delete", "upsert"],
        description: "Type of DML operation to perform"
      },
      objectName: {
        type: "string",
        description: "API name of the object"
      },
      records: {
        type: "array",
        items: { type: "object" },
        description: "Array of records to process"
      },
      externalIdField: {
        type: "string",
        description: "External ID field name for upsert operations",
        optional: true
      }
    },
    required: ["operation", "objectName", "records"]
  }
};

export interface DMLArgs {
  operation: 'insert' | 'update' | 'delete' | 'upsert';
  objectName: string;
  records: Record<string, any>[];
  externalIdField?: string;
}

export async function handleDMLRecords(conn: any, args: DMLArgs) {
  const { operation, objectName, records, externalIdField } = args;

  let result: DMLResult | DMLResult[];
  
  switch (operation) {
    case 'insert':
      result = await conn.sobject(objectName).create(records);
      break;
    case 'update':
      result = await conn.sobject(objectName).update(records);
      break;
    case 'delete':
      result = await conn.sobject(objectName).destroy(records.map(r => r.Id));
      break;
    case 'upsert':
      if (!externalIdField) {
        throw new Error('externalIdField is required for upsert operations');
      }
      result = await conn.sobject(objectName).upsert(records, externalIdField);
      break;
    default:
      throw new Error(`Unsupported operation: ${operation}`);
  }

  // Format DML results
  const results = Array.isArray(result) ? result : [result];
  const successCount = results.filter(r => r.success).length;
  const failureCount = results.length - successCount;

  let responseText = `${operation.toUpperCase()} operation completed.\n`;
  responseText += `Processed ${results.length} records:\n`;
  responseText += `- Successful: ${successCount}\n`;
  responseText += `- Failed: ${failureCount}\n\n`;

  if (failureCount > 0) {
    responseText += 'Errors:\n';
    results.forEach((r: DMLResult, idx: number) => {
      if (!r.success && r.errors) {
        responseText += `Record ${idx + 1}:\n`;
        if (Array.isArray(r.errors)) {
          r.errors.forEach((error) => {
            responseText += `  - ${error.message}`;
            if (error.statusCode) {
              responseText += ` [${error.statusCode}]`;
            }
            if (error.fields && error.fields.length > 0) {
              responseText += `\n    Fields: ${error.fields.join(', ')}`;
            }
            responseText += '\n';
          });
        } else {
          // Single error object
          const error = r.errors;
          responseText += `  - ${error.message}`;
          if (error.statusCode) {
            responseText += ` [${error.statusCode}]`;
          }
          if (error.fields) {
            const fields = Array.isArray(error.fields) ? error.fields.join(', ') : error.fields;
            responseText += `\n    Fields: ${fields}`;
          }
          responseText += '\n';
        }
      }
    });
  }

  return {
    content: [{
      type: "text",
      text: responseText
    }],
    isError: false,
  };
}
```

--------------------------------------------------------------------------------
/src/tools/executeAnonymous.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import type { Connection } from "jsforce";

export const EXECUTE_ANONYMOUS: Tool = {
  name: "salesforce_execute_anonymous",
  description: `Execute anonymous Apex code in Salesforce.
  
Examples:
1. Execute simple Apex code:
   {
     "apexCode": "System.debug('Hello World');"
   }

2. Execute Apex code with variables:
   {
     "apexCode": "List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 5]; for(Account a : accounts) { System.debug(a.Name); }"
   }

3. Execute Apex with debug logs:
   {
     "apexCode": "System.debug(LoggingLevel.INFO, 'Processing accounts...'); List<Account> accounts = [SELECT Id FROM Account LIMIT 10]; System.debug(LoggingLevel.INFO, 'Found ' + accounts.size() + ' accounts');",
     "logLevel": "DEBUG"
   }

Notes:
- The apexCode parameter is required and must contain valid Apex code
- The code is executed in an anonymous context and does not persist
- The logLevel parameter is optional (defaults to 'DEBUG')
- Execution results include compilation success/failure, execution success/failure, and debug logs
- For security reasons, some operations may be restricted based on user permissions
- This tool can be used for data operations or updates when there are no other specific tools available
- When users request data queries or updates that aren't directly supported by other tools, this tool can be used if the operation is achievable using Apex code
`,
  inputSchema: {
    type: "object",
    properties: {
      apexCode: {
        type: "string",
        description: "Apex code to execute anonymously"
      },
      logLevel: {
        type: "string",
        enum: ["NONE", "ERROR", "WARN", "INFO", "DEBUG", "FINE", "FINER", "FINEST"],
        description: "Log level for debug logs (optional, defaults to DEBUG)"
      }
    },
    required: ["apexCode"]
  }
};

export interface ExecuteAnonymousArgs {
  apexCode: string;
  logLevel?: 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'FINE' | 'FINER' | 'FINEST';
}

/**
 * Handles executing anonymous Apex code in Salesforce
 * @param conn Active Salesforce connection
 * @param args Arguments for executing anonymous Apex
 * @returns Tool response with execution results and debug logs
 */
export async function handleExecuteAnonymous(conn: any, args: ExecuteAnonymousArgs) {
  try {
    // Validate inputs
    if (!args.apexCode || args.apexCode.trim() === '') {
      throw new Error('apexCode is required and cannot be empty');
    }
    
    console.error(`Executing anonymous Apex code`);
    
    // Set default log level if not provided
    const logLevel = args.logLevel || 'DEBUG';
    
    // Execute the anonymous Apex code
    const result = await conn.tooling.executeAnonymous(args.apexCode);
    
    // Format the response
    let responseText = '';
    
    // Add compilation and execution status
    if (result.compiled) {
      responseText += `**Compilation:** Success\n`;
    } else {
      responseText += `**Compilation:** Failed\n`;
      responseText += `**Line:** ${result.line}\n`;
      responseText += `**Column:** ${result.column}\n`;
      responseText += `**Error:** ${result.compileProblem}\n\n`;
    }
    
    if (result.compiled && result.success) {
      responseText += `**Execution:** Success\n`;
    } else if (result.compiled) {
      responseText += `**Execution:** Failed\n`;
      responseText += `**Error:** ${result.exceptionMessage}\n`;
      if (result.exceptionStackTrace) {
        responseText += `**Stack Trace:**\n\`\`\`\n${result.exceptionStackTrace}\n\`\`\`\n\n`;
      }
    }
    
    // Get debug logs if available
    if (result.compiled) {
      try {
        // Query for the most recent debug log
        const logs = await conn.query(`
          SELECT Id, LogUserId, Operation, Application, Status, LogLength, LastModifiedDate, Request
          FROM ApexLog 
          ORDER BY LastModifiedDate DESC 
          LIMIT 1
        `);
        
        if (logs.records.length > 0) {
          const logId = logs.records[0].Id;
          
          // Retrieve the log body
          const logBody = await conn.tooling.request({
            method: 'GET',
            url: `${conn.instanceUrl}/services/data/v58.0/tooling/sobjects/ApexLog/${logId}/Body`
          });
          
          responseText += `\n**Debug Log:**\n\`\`\`\n${logBody}\n\`\`\``;
        } else {
          responseText += `\n**Debug Log:** No logs available. Ensure debug logs are enabled for your user.`;
        }
      } catch (logError) {
        responseText += `\n**Debug Log:** Unable to retrieve debug logs: ${logError instanceof Error ? logError.message : String(logError)}`;
      }
    }
    
    return {
      content: [{ 
        type: "text", 
        text: responseText
      }]
    };
  } catch (error) {
    console.error('Error executing anonymous Apex:', error);
    return {
      content: [{ 
        type: "text", 
        text: `Error executing anonymous Apex: ${error instanceof Error ? error.message : String(error)}` 
      }],
      isError: true,
    };
  }
}

```

--------------------------------------------------------------------------------
/src/tools/manageObject.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { MetadataInfo } from "../types/metadata";

export const MANAGE_OBJECT: Tool = {
  name: "salesforce_manage_object",
  description: `Create new custom objects or modify existing ones in Salesforce:
  - Create: New custom objects with fields, relationships, and settings
  - Update: Modify existing object settings, labels, sharing model
  Examples: Create Customer_Feedback__c object, Update object sharing settings
  Note: Changes affect metadata and require proper permissions`,
  inputSchema: {
    type: "object",
    properties: {
      operation: {
        type: "string",
        enum: ["create", "update"],
        description: "Whether to create new object or update existing"
      },
      objectName: {
        type: "string",
        description: "API name for the object (without __c suffix)"
      },
      label: {
        type: "string",
        description: "Label for the object"
      },
      pluralLabel: {
        type: "string",
        description: "Plural label for the object"
      },
      description: {
        type: "string",
        description: "Description of the object",
        optional: true
      },
      nameFieldLabel: {
        type: "string",
        description: "Label for the name field",
        optional: true
      },
      nameFieldType: {
        type: "string",
        enum: ["Text", "AutoNumber"],
        description: "Type of the name field",
        optional: true
      },
      nameFieldFormat: {
        type: "string",
        description: "Display format for AutoNumber field (e.g., 'A-{0000}')",
        optional: true
      },
      sharingModel: {
        type: "string",
        enum: ["ReadWrite", "Read", "Private", "ControlledByParent"],
        description: "Sharing model for the object",
        optional: true
      }
    },
    required: ["operation", "objectName"]
  }
};

export interface ManageObjectArgs {
  operation: 'create' | 'update';
  objectName: string;
  label?: string;
  pluralLabel?: string;
  description?: string;
  nameFieldLabel?: string;
  nameFieldType?: 'Text' | 'AutoNumber';
  nameFieldFormat?: string;
  sharingModel?: 'ReadWrite' | 'Read' | 'Private' | 'ControlledByParent';
}

export async function handleManageObject(conn: any, args: ManageObjectArgs) {
  const { operation, objectName, label, pluralLabel, description, nameFieldLabel, nameFieldType, nameFieldFormat, sharingModel } = args;

  try {
    if (operation === 'create') {
      if (!label || !pluralLabel) {
        throw new Error('Label and pluralLabel are required for object creation');
      }

      // Prepare metadata for the new object
      const metadata = {
        fullName: `${objectName}__c`,
        label,
        pluralLabel,
        nameField: {
          label: nameFieldLabel || `${label} Name`,
          type: nameFieldType || 'Text',
          ...(nameFieldType === 'AutoNumber' && nameFieldFormat ? { displayFormat: nameFieldFormat } : {})
        },
        deploymentStatus: 'Deployed',
        sharingModel: sharingModel || 'ReadWrite'
      } as MetadataInfo;

      if (description) {
        metadata.description = description;
      }

      // Create the object using Metadata API
      const result = await conn.metadata.create('CustomObject', metadata);

      if (result && (Array.isArray(result) ? result[0].success : result.success)) {
        return {
          content: [{
            type: "text",
            text: `Successfully created custom object ${objectName}__c`
          }],
          isError: false,
        };
      }
    } else {
      // For update, first get existing metadata
      const existingMetadata = await conn.metadata.read('CustomObject', [`${objectName}__c`]);
      const currentMetadata = Array.isArray(existingMetadata) ? existingMetadata[0] : existingMetadata;

      if (!currentMetadata) {
        throw new Error(`Object ${objectName}__c not found`);
      }

      // Prepare update metadata
      const metadata = {
        ...currentMetadata,
        label: label || currentMetadata.label,
        pluralLabel: pluralLabel || currentMetadata.pluralLabel,
        description: description !== undefined ? description : currentMetadata.description,
        sharingModel: sharingModel || currentMetadata.sharingModel
      } as MetadataInfo;

      // Update the object using Metadata API
      const result = await conn.metadata.update('CustomObject', metadata);

      if (result && (Array.isArray(result) ? result[0].success : result.success)) {
        return {
          content: [{
            type: "text",
            text: `Successfully updated custom object ${objectName}__c`
          }],
          isError: false,
        };
      }
    }

    return {
      content: [{
        type: "text",
        text: `Failed to ${operation} custom object ${objectName}__c`
      }],
      isError: true,
    };

  } catch (error) {
    return {
      content: [{
        type: "text",
        text: `Error ${operation === 'create' ? 'creating' : 'updating'} custom object: ${error instanceof Error ? error.message : String(error)}`
      }],
      isError: true,
    };
  }
}
```

--------------------------------------------------------------------------------
/src/tools/writeApex.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import type { Connection } from "jsforce";

export const WRITE_APEX: Tool = {
  name: "salesforce_write_apex",
  description: `Create or update Apex classes in Salesforce.
  
Examples:
1. Create a new Apex class:
   {
     "operation": "create",
     "className": "AccountService",
     "apiVersion": "58.0",
     "body": "public class AccountService { public static void updateAccounts() { /* implementation */ } }"
   }

2. Update an existing Apex class:
   {
     "operation": "update",
     "className": "AccountService",
     "body": "public class AccountService { public static void updateAccounts() { /* updated implementation */ } }"
   }

Notes:
- The operation must be either 'create' or 'update'
- For 'create' operations, className and body are required
- For 'update' operations, className and body are required
- apiVersion is optional for 'create' (defaults to the latest version)
- The body must be valid Apex code
- The className in the body must match the className parameter
- Status information is returned after successful operations`,
  inputSchema: {
    type: "object",
    properties: {
      operation: {
        type: "string",
        enum: ["create", "update"],
        description: "Whether to create a new class or update an existing one"
      },
      className: {
        type: "string",
        description: "Name of the Apex class to create or update"
      },
      apiVersion: {
        type: "string",
        description: "API version for the Apex class (e.g., '58.0')"
      },
      body: {
        type: "string",
        description: "Full body of the Apex class"
      }
    },
    required: ["operation", "className", "body"]
  }
};

export interface WriteApexArgs {
  operation: 'create' | 'update';
  className: string;
  apiVersion?: string;
  body: string;
}

/**
 * Handles creating or updating Apex classes in Salesforce
 * @param conn Active Salesforce connection
 * @param args Arguments for writing Apex classes
 * @returns Tool response with operation result
 */
export async function handleWriteApex(conn: any, args: WriteApexArgs) {
  try {
    // Validate inputs
    if (!args.className) {
      throw new Error('className is required');
    }
    
    if (!args.body) {
      throw new Error('body is required');
    }
    
    // Check if the class name in the body matches the provided className
    const classNameRegex = new RegExp(`\\b(class|interface|enum)\\s+${args.className}\\b`);
    if (!classNameRegex.test(args.body)) {
      throw new Error(`The class name in the body must match the provided className: ${args.className}`);
    }
    
    // Handle create operation
    if (args.operation === 'create') {
      console.error(`Creating new Apex class: ${args.className}`);
      
      // Check if class already exists
      const existingClass = await conn.query(`
        SELECT Id FROM ApexClass WHERE Name = '${args.className}'
      `);
      
      if (existingClass.records.length > 0) {
        throw new Error(`Apex class with name '${args.className}' already exists. Use 'update' operation instead.`);
      }
      
      // Create the new class using the Tooling API
      const createResult = await conn.tooling.sobject('ApexClass').create({
        Name: args.className,
        Body: args.body,
        ApiVersion: args.apiVersion || '58.0', // Default to latest if not specified
        Status: 'Active'
      });
      
      if (!createResult.success) {
        throw new Error(`Failed to create Apex class: ${createResult.errors.join(', ')}`);
      }
      
      return {
        content: [{ 
          type: "text", 
          text: `Successfully created Apex class: ${args.className}\n\n` +
                `**ID:** ${createResult.id}\n` +
                `**API Version:** ${args.apiVersion || '58.0'}\n` +
                `**Status:** Active`
        }]
      };
    } 
    // Handle update operation
    else if (args.operation === 'update') {
      console.error(`Updating Apex class: ${args.className}`);
      
      // Find the existing class
      const existingClass = await conn.query(`
        SELECT Id FROM ApexClass WHERE Name = '${args.className}'
      `);
      
      if (existingClass.records.length === 0) {
        throw new Error(`No Apex class found with name: ${args.className}. Use 'create' operation instead.`);
      }
      
      const classId = existingClass.records[0].Id;
      
      // Update the class using the Tooling API
      const updateResult = await conn.tooling.sobject('ApexClass').update({
        Id: classId,
        Body: args.body
      });
      
      if (!updateResult.success) {
        throw new Error(`Failed to update Apex class: ${updateResult.errors.join(', ')}`);
      }
      
      // Get the updated class details
      const updatedClass = await conn.query(`
        SELECT Id, Name, ApiVersion, Status, LastModifiedDate
        FROM ApexClass
        WHERE Id = '${classId}'
      `);
      
      const classDetails = updatedClass.records[0];
      
      return {
        content: [{ 
          type: "text", 
          text: `Successfully updated Apex class: ${args.className}\n\n` +
                `**ID:** ${classId}\n` +
                `**API Version:** ${classDetails.ApiVersion}\n` +
                `**Status:** ${classDetails.Status}\n` +
                `**Last Modified:** ${new Date(classDetails.LastModifiedDate).toLocaleString()}`
        }]
      };
    } else {
      throw new Error(`Invalid operation: ${args.operation}. Must be 'create' or 'update'.`);
    }
  } catch (error) {
    console.error('Error writing Apex class:', error);
    return {
      content: [{ 
        type: "text", 
        text: `Error writing Apex class: ${error instanceof Error ? error.message : String(error)}` 
      }],
      isError: true,
    };
  }
}

```

--------------------------------------------------------------------------------
/src/tools/readApex.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";

export const READ_APEX: Tool = {
  name: "salesforce_read_apex",
  description: `Read Apex classes from Salesforce.
  
Examples:
1. Read a specific Apex class by name:
   {
     "className": "AccountController"
   }

2. List all Apex classes with an optional name pattern:
   {
     "namePattern": "Controller"
   }

3. Get metadata about Apex classes:
   {
     "includeMetadata": true,
     "namePattern": "Trigger"
   }

4. Use wildcards in name patterns:
   {
     "namePattern": "Account*Cont*"
   }

Notes:
- When className is provided, the full body of that specific class is returned
- When namePattern is provided, all matching class names are returned (without body)
- Use includeMetadata to get additional information like API version, length, and last modified date
- If neither className nor namePattern is provided, all Apex class names will be listed
- Wildcards are supported in namePattern: * (matches any characters) and ? (matches a single character)`,
  inputSchema: {
    type: "object",
    properties: {
      className: {
        type: "string",
        description: "Name of a specific Apex class to read"
      },
      namePattern: {
        type: "string",
        description: "Pattern to match Apex class names (supports wildcards * and ?)"
      },
      includeMetadata: {
        type: "boolean",
        description: "Whether to include metadata about the Apex classes"
      }
    }
  }
};

export interface ReadApexArgs {
  className?: string;
  namePattern?: string;
  includeMetadata?: boolean;
}

/**
 * Converts a wildcard pattern to a SQL LIKE pattern
 * @param pattern Pattern with * and ? wildcards
 * @returns SQL LIKE compatible pattern
 */
function wildcardToLikePattern(pattern: string): string {
  if (!pattern.includes('*') && !pattern.includes('?')) {
    // If no wildcards, wrap with % for substring match
    return `%${pattern}%`;
  }
  
  // Replace * with % and ? with _ for SQL LIKE
  let likePattern = pattern.replace(/\*/g, '%').replace(/\?/g, '_');
  
  return likePattern;
}

/**
 * Handles reading Apex classes from Salesforce
 * @param conn Active Salesforce connection
 * @param args Arguments for reading Apex classes
 * @returns Tool response with Apex class information
 */
export async function handleReadApex(conn: any, args: ReadApexArgs) {
  try {
    // If a specific class name is provided, get the full class body
    if (args.className) {
      console.error(`Reading Apex class: ${args.className}`);
      
      // Query the ApexClass object to get the class body
      const result = await conn.query(`
        SELECT Id, Name, Body, ApiVersion, LengthWithoutComments, Status, 
               IsValid, LastModifiedDate, LastModifiedById
        FROM ApexClass 
        WHERE Name = '${args.className}'
      `);
      
      if (result.records.length === 0) {
        return {
          content: [{ 
            type: "text", 
            text: `No Apex class found with name: ${args.className}` 
          }],
          isError: true,
        };
      }
      
      const apexClass = result.records[0];
      
      // Format the response with the class body and metadata
      return {
        content: [
          { 
            type: "text", 
            text: `# Apex Class: ${apexClass.Name}\n\n` +
                  (args.includeMetadata ? 
                    `**API Version:** ${apexClass.ApiVersion}\n` +
                    `**Length:** ${apexClass.LengthWithoutComments} characters\n` +
                    `**Status:** ${apexClass.Status}\n` +
                    `**Valid:** ${apexClass.IsValid ? 'Yes' : 'No'}\n` +
                    `**Last Modified:** ${new Date(apexClass.LastModifiedDate).toLocaleString()}\n\n` : '') +
                  "```apex\n" + apexClass.Body + "\n```"
          }
        ]
      };
    } 
    // Otherwise, list classes matching the pattern
    else {
      console.error(`Listing Apex classes${args.namePattern ? ` matching: ${args.namePattern}` : ''}`);
      
      // Build the query
      let query = `
        SELECT Id, Name${args.includeMetadata ? ', ApiVersion, LengthWithoutComments, Status, IsValid, LastModifiedDate' : ''}
        FROM ApexClass
      `;
      
      // Add name pattern filter if provided
      if (args.namePattern) {
        const likePattern = wildcardToLikePattern(args.namePattern);
        query += ` WHERE Name LIKE '${likePattern}'`;
      }
      
      // Order by name
      query += ` ORDER BY Name`;
      
      const result = await conn.query(query);
      
      if (result.records.length === 0) {
        return {
          content: [{ 
            type: "text", 
            text: `No Apex classes found${args.namePattern ? ` matching: ${args.namePattern}` : ''}` 
          }]
        };
      }
      
      // Format the response as a list of classes
      let responseText = `# Found ${result.records.length} Apex Classes\n\n`;
      
      if (args.includeMetadata) {
        // Table format with metadata
        responseText += "| Name | API Version | Length | Status | Valid | Last Modified |\n";
        responseText += "|------|------------|--------|--------|-------|---------------|\n";
        
        for (const cls of result.records) {
          responseText += `| ${cls.Name} | ${cls.ApiVersion} | ${cls.LengthWithoutComments} | ${cls.Status} | ${cls.IsValid ? 'Yes' : 'No'} | ${new Date(cls.LastModifiedDate).toLocaleString()} |\n`;
        }
      } else {
        // Simple list format
        for (const cls of result.records) {
          responseText += `- ${cls.Name}\n`;
        }
      }
      
      return {
        content: [{ type: "text", text: responseText }]
      };
    }
  } catch (error) {
    console.error('Error reading Apex classes:', error);
    return {
      content: [{ 
        type: "text", 
        text: `Error reading Apex classes: ${error instanceof Error ? error.message : String(error)}` 
      }],
      isError: true,
    };
  }
}

```

--------------------------------------------------------------------------------
/src/tools/readApexTrigger.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";

export const READ_APEX_TRIGGER: Tool = {
  name: "salesforce_read_apex_trigger",
  description: `Read Apex triggers from Salesforce.
  
Examples:
1. Read a specific Apex trigger by name:
   {
     "triggerName": "AccountTrigger"
   }

2. List all Apex triggers with an optional name pattern:
   {
     "namePattern": "Account"
   }

3. Get metadata about Apex triggers:
   {
     "includeMetadata": true,
     "namePattern": "Contact"
   }

4. Use wildcards in name patterns:
   {
     "namePattern": "Account*"
   }

Notes:
- When triggerName is provided, the full body of that specific trigger is returned
- When namePattern is provided, all matching trigger names are returned (without body)
- Use includeMetadata to get additional information like API version, object type, and last modified date
- If neither triggerName nor namePattern is provided, all Apex trigger names will be listed
- Wildcards are supported in namePattern: * (matches any characters) and ? (matches a single character)`,
  inputSchema: {
    type: "object",
    properties: {
      triggerName: {
        type: "string",
        description: "Name of a specific Apex trigger to read"
      },
      namePattern: {
        type: "string",
        description: "Pattern to match Apex trigger names (supports wildcards * and ?)"
      },
      includeMetadata: {
        type: "boolean",
        description: "Whether to include metadata about the Apex triggers"
      }
    }
  }
};

export interface ReadApexTriggerArgs {
  triggerName?: string;
  namePattern?: string;
  includeMetadata?: boolean;
}

/**
 * Converts a wildcard pattern to a SQL LIKE pattern
 * @param pattern Pattern with * and ? wildcards
 * @returns SQL LIKE compatible pattern
 */
function wildcardToLikePattern(pattern: string): string {
  if (!pattern.includes('*') && !pattern.includes('?')) {
    // If no wildcards, wrap with % for substring match
    return `%${pattern}%`;
  }
  
  // Replace * with % and ? with _ for SQL LIKE
  let likePattern = pattern.replace(/\*/g, '%').replace(/\?/g, '_');
  
  return likePattern;
}

/**
 * Handles reading Apex triggers from Salesforce
 * @param conn Active Salesforce connection
 * @param args Arguments for reading Apex triggers
 * @returns Tool response with Apex trigger information
 */
export async function handleReadApexTrigger(conn: any, args: ReadApexTriggerArgs) {
  try {
    // If a specific trigger name is provided, get the full trigger body
    if (args.triggerName) {
      console.error(`Reading Apex trigger: ${args.triggerName}`);
      
      // Query the ApexTrigger object to get the trigger body
      const result = await conn.query(`
        SELECT Id, Name, Body, ApiVersion, TableEnumOrId, Status, 
               IsValid, LastModifiedDate, LastModifiedById
        FROM ApexTrigger 
        WHERE Name = '${args.triggerName}'
      `);
      
      if (result.records.length === 0) {
        return {
          content: [{ 
            type: "text", 
            text: `No Apex trigger found with name: ${args.triggerName}` 
          }],
          isError: true,
        };
      }
      
      const apexTrigger = result.records[0];
      
      // Format the response with the trigger body and metadata
      return {
        content: [
          { 
            type: "text", 
            text: `# Apex Trigger: ${apexTrigger.Name}\n\n` +
                  (args.includeMetadata ? 
                    `**API Version:** ${apexTrigger.ApiVersion}\n` +
                    `**Object:** ${apexTrigger.TableEnumOrId}\n` +
                    `**Status:** ${apexTrigger.Status}\n` +
                    `**Valid:** ${apexTrigger.IsValid ? 'Yes' : 'No'}\n` +
                    `**Last Modified:** ${new Date(apexTrigger.LastModifiedDate).toLocaleString()}\n\n` : '') +
                  "```apex\n" + apexTrigger.Body + "\n```"
          }
        ]
      };
    } 
    // Otherwise, list triggers matching the pattern
    else {
      console.error(`Listing Apex triggers${args.namePattern ? ` matching: ${args.namePattern}` : ''}`);
      
      // Build the query
      let query = `
        SELECT Id, Name${args.includeMetadata ? ', ApiVersion, TableEnumOrId, Status, IsValid, LastModifiedDate' : ''}
        FROM ApexTrigger
      `;
      
      // Add name pattern filter if provided
      if (args.namePattern) {
        const likePattern = wildcardToLikePattern(args.namePattern);
        query += ` WHERE Name LIKE '${likePattern}'`;
      }
      
      // Order by name
      query += ` ORDER BY Name`;
      
      const result = await conn.query(query);
      
      if (result.records.length === 0) {
        return {
          content: [{ 
            type: "text", 
            text: `No Apex triggers found${args.namePattern ? ` matching: ${args.namePattern}` : ''}` 
          }]
        };
      }
      
      // Format the response as a list of triggers
      let responseText = `# Found ${result.records.length} Apex Triggers\n\n`;
      
      if (args.includeMetadata) {
        // Table format with metadata
        responseText += "| Name | API Version | Object | Status | Valid | Last Modified |\n";
        responseText += "|------|------------|--------|--------|-------|---------------|\n";
        
        for (const trigger of result.records) {
          responseText += `| ${trigger.Name} | ${trigger.ApiVersion} | ${trigger.TableEnumOrId} | ${trigger.Status} | ${trigger.IsValid ? 'Yes' : 'No'} | ${new Date(trigger.LastModifiedDate).toLocaleString()} |\n`;
        }
      } else {
        // Simple list format
        for (const trigger of result.records) {
          responseText += `- ${trigger.Name}\n`;
        }
      }
      
      return {
        content: [{ type: "text", text: responseText }]
      };
    }
  } catch (error) {
    console.error('Error reading Apex triggers:', error);
    return {
      content: [{ 
        type: "text", 
        text: `Error reading Apex triggers: ${error instanceof Error ? error.message : String(error)}` 
      }],
      isError: true,
    };
  }
}

```

--------------------------------------------------------------------------------
/src/tools/query.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";

export const QUERY_RECORDS: Tool = {
  name: "salesforce_query_records",
  description: `Query records from any Salesforce object using SOQL, including relationship queries.

NOTE: For queries with GROUP BY, aggregate functions (COUNT, SUM, AVG, etc.), or HAVING clauses, use salesforce_aggregate_query instead.

Examples:
1. Parent-to-child query (e.g., Account with Contacts):
   - objectName: "Account"
   - fields: ["Name", "(SELECT Id, FirstName, LastName FROM Contacts)"]

2. Child-to-parent query (e.g., Contact with Account details):
   - objectName: "Contact"
   - fields: ["FirstName", "LastName", "Account.Name", "Account.Industry"]

3. Multiple level query (e.g., Contact -> Account -> Owner):
   - objectName: "Contact"
   - fields: ["Name", "Account.Name", "Account.Owner.Name"]

4. Related object filtering:
   - objectName: "Contact"
   - fields: ["Name", "Account.Name"]
   - whereClause: "Account.Industry = 'Technology'"

Note: When using relationship fields:
- Use dot notation for parent relationships (e.g., "Account.Name")
- Use subqueries in parentheses for child relationships (e.g., "(SELECT Id FROM Contacts)")
- Custom relationship fields end in "__r" (e.g., "CustomObject__r.Name")`,
  inputSchema: {
    type: "object",
    properties: {
      objectName: {
        type: "string",
        description: "API name of the object to query"
      },
      fields: {
        type: "array",
        items: { type: "string" },
        description: "List of fields to retrieve, including relationship fields"
      },
      whereClause: {
        type: "string",
        description: "WHERE clause, can include conditions on related objects",
        optional: true
      },
      orderBy: {
        type: "string",
        description: "ORDER BY clause, can include fields from related objects",
        optional: true
      },
      limit: {
        type: "number",
        description: "Maximum number of records to return",
        optional: true
      }
    },
    required: ["objectName", "fields"]
  }
};

export interface QueryArgs {
  objectName: string;
  fields: string[];
  whereClause?: string;
  orderBy?: string;
  limit?: number;
}

// Helper function to validate relationship field syntax
function validateRelationshipFields(fields: string[]): { isValid: boolean; error?: string } {
  for (const field of fields) {
    // Check for parent relationship syntax (dot notation)
    if (field.includes('.')) {
      const parts = field.split('.');
      // Check for empty parts
      if (parts.some(part => !part)) {
        return {
          isValid: false,
          error: `Invalid relationship field format: "${field}". Relationship fields should use proper dot notation (e.g., "Account.Name")`
        };
      }
      // Check for too many levels (Salesforce typically limits to 5)
      if (parts.length > 5) {
        return {
          isValid: false,
          error: `Relationship field "${field}" exceeds maximum depth of 5 levels`
        };
      }
    }

    // Check for child relationship syntax (subqueries)
    if (field.includes('SELECT') && !field.match(/^\(SELECT.*FROM.*\)$/)) {
      return {
        isValid: false,
        error: `Invalid subquery format: "${field}". Child relationship queries should be wrapped in parentheses`
      };
    }
  }

  return { isValid: true };
}

// Helper function to format relationship query results
function formatRelationshipResults(record: any, field: string, prefix = ''): string {
  if (field.includes('.')) {
    const [relationship, ...rest] = field.split('.');
    const relatedRecord = record[relationship];
    if (relatedRecord === null) {
      return `${prefix}${field}: null`;
    }
    return formatRelationshipResults(relatedRecord, rest.join('.'), `${prefix}${relationship}.`);
  }

  const value = record[field];
  if (Array.isArray(value)) {
    // Handle child relationship arrays
    return `${prefix}${field}: [${value.length} records]`;
  }
  return `${prefix}${field}: ${value !== null && value !== undefined ? value : 'null'}`;
}

export async function handleQueryRecords(conn: any, args: QueryArgs) {
  const { objectName, fields, whereClause, orderBy, limit } = args;

  try {
    // Validate relationship field syntax
    const validation = validateRelationshipFields(fields);
    if (!validation.isValid) {
      return {
        content: [{
          type: "text",
          text: validation.error!
        }],
        isError: true,
      };
    }

    // Construct SOQL query
    let soql = `SELECT ${fields.join(', ')} FROM ${objectName}`;
    if (whereClause) soql += ` WHERE ${whereClause}`;
    if (orderBy) soql += ` ORDER BY ${orderBy}`;
    if (limit) soql += ` LIMIT ${limit}`;

    const result = await conn.query(soql);
    
    // Format the output
    const formattedRecords = result.records.map((record: any, index: number) => {
      const recordStr = fields.map(field => {
        // Handle special case for subqueries (child relationships)
        if (field.startsWith('(SELECT')) {
          const relationshipName = field.match(/FROM\s+(\w+)/)?.[1];
          if (!relationshipName) return `    ${field}: Invalid subquery format`;
          const childRecords = record[relationshipName];
          return `    ${relationshipName}: [${childRecords?.length || 0} records]`;
        }
        return '    ' + formatRelationshipResults(record, field);
      }).join('\n');
      return `Record ${index + 1}:\n${recordStr}`;
    }).join('\n\n');

    return {
      content: [{
        type: "text",
        text: `Query returned ${result.records.length} records:\n\n${formattedRecords}`
      }],
      isError: false,
    };
  } catch (error) {
    // Enhanced error handling for relationship queries
    const errorMessage = error instanceof Error ? error.message : String(error);
    let enhancedError = errorMessage;

    if (errorMessage.includes('INVALID_FIELD')) {
      // Try to identify which relationship field caused the error
      const fieldMatch = errorMessage.match(/(?:No such column |Invalid field: )['"]?([^'")\s]+)/);
      if (fieldMatch) {
        const invalidField = fieldMatch[1];
        if (invalidField.includes('.')) {
          enhancedError = `Invalid relationship field "${invalidField}". Please check:\n` +
            `1. The relationship name is correct\n` +
            `2. The field exists on the related object\n` +
            `3. You have access to the field\n` +
            `4. For custom relationships, ensure you're using '__r' suffix`;
        }
      }
    }

    return {
      content: [{
        type: "text",
        text: `Error executing query: ${enhancedError}`
      }],
      isError: true,
    };
  }
}
```

--------------------------------------------------------------------------------
/src/utils/connection.ts:
--------------------------------------------------------------------------------

```typescript
import jsforce from 'jsforce';
import { ConnectionType, ConnectionConfig, SalesforceCLIResponse } from '../types/connection.js';
import https from 'https';
import querystring from 'querystring';
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

/**
 * Executes the Salesforce CLI command to get org information
 * @returns Parsed response from sf org display --json command
 */
async function getSalesforceOrgInfo(): Promise<SalesforceCLIResponse> {
  try {
    const command = 'sf org display --json';
    const cwdLog = process.cwd();
    console.log(`Executing Salesforce CLI command: ${command} in directory: ${cwdLog}`);

    // Use execAsync and handle both success and error cases
    let stdout = '';
    let stderr = '';
    let error: Error | { stdout?: string; stderr?: string } | null = null;
    try {
      const result = await execAsync(command);
      stdout = result.stdout;
      stderr = result.stderr;
    } catch (err: any) {
      // If the command fails, capture stdout/stderr for diagnostics
      error = err;
      stdout = 'stdout' in err ? err.stdout || '' : '';
      stderr = 'stderr' in err ? err.stderr || '' : '';
    }


    // Log always the output for debug
    console.error('[Salesforce CLI] STDOUT:', stdout);
    if (stderr) {
      console.warn('[Salesforce CLI] STDERR:', stderr);
    }
    // Try to parse stdout as JSON
    let response: SalesforceCLIResponse;
    try {
      response = JSON.parse(stdout);
    } catch (parseErr) {
      throw new Error(`Failed to parse Salesforce CLI JSON output.\nSTDOUT: ${stdout}\nSTDERR: ${stderr}`);
    }

    // If the command failed (non-zero exit code), throw with details
    if (error || response.status !== 0) {
      throw new Error(`Salesforce CLI command failed.\nStatus: ${response.status}\nSTDOUT: ${stdout}\nSTDERR: ${stderr}`);
    }

    // Accept any org that returns accessToken and instanceUrl
    if (!response.result || !response.result.accessToken || !response.result.instanceUrl) {
      throw new Error(`Salesforce CLI did not return accessToken and instanceUrl.\nResult: ${JSON.stringify(response.result)}`);
    }

    return response;
  } catch (error) {
    if (error instanceof Error) {
      if (error.message.includes('sf: command not found') || error.message.includes("'sf' is not recognized")) {
        throw new Error('Salesforce CLI (sf) is not installed or not in PATH. Please install the Salesforce CLI to use this authentication method.');
      }
    }
    throw new Error(`Failed to get Salesforce org info: ${error instanceof Error ? error.message : String(error)}`);
  }
}

/**
 * Creates a Salesforce connection using either username/password or OAuth 2.0 Client Credentials Flow
 * @param config Optional connection configuration
 * @returns Connected jsforce Connection instance
 */
export async function createSalesforceConnection(config?: ConnectionConfig) {
  // Determine connection type from environment variables or config
  const connectionType = config?.type || 
    (process.env.SALESFORCE_CONNECTION_TYPE as ConnectionType) || 
    ConnectionType.User_Password;
  
  // Set login URL from config or environment variable
  const loginUrl = config?.loginUrl || 
    process.env.SALESFORCE_INSTANCE_URL || 
    'https://login.salesforce.com';
  
  try {
    if (connectionType === ConnectionType.OAuth_2_0_Client_Credentials) {
      // OAuth 2.0 Client Credentials Flow
      const clientId = process.env.SALESFORCE_CLIENT_ID;
      const clientSecret = process.env.SALESFORCE_CLIENT_SECRET;
      
      if (!clientId || !clientSecret) {
        throw new Error('SALESFORCE_CLIENT_ID and SALESFORCE_CLIENT_SECRET are required for OAuth 2.0 Client Credentials Flow');
      }
      
      console.error('Connecting to Salesforce using OAuth 2.0 Client Credentials Flow');
      
      // Get the instance URL from environment variable or config
      const instanceUrl = loginUrl;
      
      // Create the token URL
      const tokenUrl = new URL('/services/oauth2/token', instanceUrl);
      
      // Prepare the request body
      const requestBody = querystring.stringify({
        grant_type: 'client_credentials',
        client_id: clientId,
        client_secret: clientSecret
      });
      
      // Make the token request
      const tokenResponse = await new Promise<any>((resolve, reject) => {
        const req = https.request({
          method: 'POST',
          hostname: tokenUrl.hostname,
          path: tokenUrl.pathname,
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': Buffer.byteLength(requestBody)
          }
        }, (res) => {
          let data = '';
          res.on('data', (chunk) => {
            data += chunk;
          });
          res.on('end', () => {
            try {
              const parsedData = JSON.parse(data);
              if (res.statusCode !== 200) {
                reject(new Error(`OAuth token request failed: ${parsedData.error} - ${parsedData.error_description}`));
              } else {
                resolve(parsedData);
              }
            } catch (e: unknown) {
              reject(new Error(`Failed to parse OAuth response: ${e instanceof Error ? e.message : String(e)}`));
            }
          });
        });
        
        req.on('error', (e) => {
          reject(new Error(`OAuth request error: ${e.message}`));
        });
        
        req.write(requestBody);
        req.end();
      });
      
      // Create connection with the access token
      const conn = new jsforce.Connection({
        instanceUrl: tokenResponse.instance_url,
        accessToken: tokenResponse.access_token
      });
      
      return conn;
    } else if (connectionType === ConnectionType.Salesforce_CLI) {
      // Salesforce CLI authentication using sf org display
      console.log('Connecting to Salesforce using Salesforce CLI authentication');
      
      // Execute sf org display --json command
      const orgInfo = await getSalesforceOrgInfo();
      
      // Create connection with the access token from CLI
      const conn = new jsforce.Connection({
        instanceUrl: orgInfo.result.instanceUrl,
        accessToken: orgInfo.result.accessToken
      });
      
      console.log(`Connected to Salesforce org: ${orgInfo.result.username} (${orgInfo.result.alias || 'No alias'})`);
      
      return conn;
    } else {
      // Default: Username/Password Flow with Security Token
      const username = process.env.SALESFORCE_USERNAME;
      const password = process.env.SALESFORCE_PASSWORD;
      const token = process.env.SALESFORCE_TOKEN;
      
      if (!username || !password) {
        throw new Error('SALESFORCE_USERNAME and SALESFORCE_PASSWORD are required for Username/Password authentication');
      }
      
      console.error('Connecting to Salesforce using Username/Password authentication');
      
      // Create connection with login URL
      const conn = new jsforce.Connection({ loginUrl });
      
      await conn.login(
        username,
        password + (token || '')
      );
      
      return conn;
    }
  } catch (error) {
    console.error('Error connecting to Salesforce:', error);
    throw error;
  }
}
```

--------------------------------------------------------------------------------
/src/tools/writeApexTrigger.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";

export const WRITE_APEX_TRIGGER: Tool = {
  name: "salesforce_write_apex_trigger",
  description: `Create or update Apex triggers in Salesforce.
  
Examples:
1. Create a new Apex trigger:
   {
     "operation": "create",
     "triggerName": "AccountTrigger",
     "objectName": "Account",
     "apiVersion": "58.0",
     "body": "trigger AccountTrigger on Account (before insert, before update) { /* implementation */ }"
   }

2. Update an existing Apex trigger:
   {
     "operation": "update",
     "triggerName": "AccountTrigger",
     "body": "trigger AccountTrigger on Account (before insert, before update, after update) { /* updated implementation */ }"
   }

Notes:
- The operation must be either 'create' or 'update'
- For 'create' operations, triggerName, objectName, and body are required
- For 'update' operations, triggerName and body are required
- apiVersion is optional for 'create' (defaults to the latest version)
- The body must be valid Apex trigger code
- The triggerName in the body must match the triggerName parameter
- The objectName in the body must match the objectName parameter (for 'create')
- Status information is returned after successful operations`,
  inputSchema: {
    type: "object",
    properties: {
      operation: {
        type: "string",
        enum: ["create", "update"],
        description: "Whether to create a new trigger or update an existing one"
      },
      triggerName: {
        type: "string",
        description: "Name of the Apex trigger to create or update"
      },
      objectName: {
        type: "string",
        description: "Name of the Salesforce object the trigger is for (required for 'create')"
      },
      apiVersion: {
        type: "string",
        description: "API version for the Apex trigger (e.g., '58.0')"
      },
      body: {
        type: "string",
        description: "Full body of the Apex trigger"
      }
    },
    required: ["operation", "triggerName", "body"]
  }
};

export interface WriteApexTriggerArgs {
  operation: 'create' | 'update';
  triggerName: string;
  objectName?: string;
  apiVersion?: string;
  body: string;
}

/**
 * Handles creating or updating Apex triggers in Salesforce
 * @param conn Active Salesforce connection
 * @param args Arguments for writing Apex triggers
 * @returns Tool response with operation result
 */
export async function handleWriteApexTrigger(conn: any, args: WriteApexTriggerArgs) {
  try {
    // Validate inputs
    if (!args.triggerName) {
      throw new Error('triggerName is required');
    }
    
    if (!args.body) {
      throw new Error('body is required');
    }
    
    // Check if the trigger name in the body matches the provided triggerName
    const triggerNameRegex = new RegExp(`\\btrigger\\s+${args.triggerName}\\b`);
    if (!triggerNameRegex.test(args.body)) {
      throw new Error(`The trigger name in the body must match the provided triggerName: ${args.triggerName}`);
    }
    
    // Handle create operation
    if (args.operation === 'create') {
      console.error(`Creating new Apex trigger: ${args.triggerName}`);
      
      // Validate object name for create operation
      if (!args.objectName) {
        throw new Error('objectName is required for creating a new trigger');
      }
      
      // Check if the object name in the body matches the provided objectName
      const objectNameRegex = new RegExp(`\\bon\\s+${args.objectName}\\b`);
      if (!objectNameRegex.test(args.body)) {
        throw new Error(`The object name in the body must match the provided objectName: ${args.objectName}`);
      }
      
      // Check if trigger already exists
      const existingTrigger = await conn.query(`
        SELECT Id FROM ApexTrigger WHERE Name = '${args.triggerName}'
      `);
      
      if (existingTrigger.records.length > 0) {
        throw new Error(`Apex trigger with name '${args.triggerName}' already exists. Use 'update' operation instead.`);
      }
      
      // Create the new trigger using the Tooling API
      const createResult = await conn.tooling.sobject('ApexTrigger').create({
        Name: args.triggerName,
        TableEnumOrId: args.objectName,
        Body: args.body,
        ApiVersion: args.apiVersion || '58.0', // Default to latest if not specified
        Status: 'Active'
      });
      
      if (!createResult.success) {
        throw new Error(`Failed to create Apex trigger: ${createResult.errors.join(', ')}`);
      }
      
      return {
        content: [{ 
          type: "text", 
          text: `Successfully created Apex trigger: ${args.triggerName}\n\n` +
                `**ID:** ${createResult.id}\n` +
                `**Object:** ${args.objectName}\n` +
                `**API Version:** ${args.apiVersion || '58.0'}\n` +
                `**Status:** Active`
        }]
      };
    } 
    // Handle update operation
    else if (args.operation === 'update') {
      console.error(`Updating Apex trigger: ${args.triggerName}`);
      
      // Find the existing trigger
      const existingTrigger = await conn.query(`
        SELECT Id, TableEnumOrId FROM ApexTrigger WHERE Name = '${args.triggerName}'
      `);
      
      if (existingTrigger.records.length === 0) {
        throw new Error(`No Apex trigger found with name: ${args.triggerName}. Use 'create' operation instead.`);
      }
      
      const triggerId = existingTrigger.records[0].Id;
      const objectName = existingTrigger.records[0].TableEnumOrId;
      
      // Check if the object name in the body matches the existing object
      const objectNameRegex = new RegExp(`\\bon\\s+${objectName}\\b`);
      if (!objectNameRegex.test(args.body)) {
        throw new Error(`The object name in the body must match the existing object: ${objectName}`);
      }
      
      // Update the trigger using the Tooling API
      const updateResult = await conn.tooling.sobject('ApexTrigger').update({
        Id: triggerId,
        Body: args.body
      });
      
      if (!updateResult.success) {
        throw new Error(`Failed to update Apex trigger: ${updateResult.errors.join(', ')}`);
      }
      
      // Get the updated trigger details
      const updatedTrigger = await conn.query(`
        SELECT Id, Name, TableEnumOrId, ApiVersion, Status, LastModifiedDate
        FROM ApexTrigger
        WHERE Id = '${triggerId}'
      `);
      
      const triggerDetails = updatedTrigger.records[0];
      
      return {
        content: [{ 
          type: "text", 
          text: `Successfully updated Apex trigger: ${args.triggerName}\n\n` +
                `**ID:** ${triggerId}\n` +
                `**Object:** ${triggerDetails.TableEnumOrId}\n` +
                `**API Version:** ${triggerDetails.ApiVersion}\n` +
                `**Status:** ${triggerDetails.Status}\n` +
                `**Last Modified:** ${new Date(triggerDetails.LastModifiedDate).toLocaleString()}`
        }]
      };
    } else {
      throw new Error(`Invalid operation: ${args.operation}. Must be 'create' or 'update'.`);
    }
  } catch (error) {
    console.error('Error writing Apex trigger:', error);
    return {
      content: [{ 
        type: "text", 
        text: `Error writing Apex trigger: ${error instanceof Error ? error.message : String(error)}` 
      }],
      isError: true,
    };
  }
}

```

--------------------------------------------------------------------------------
/src/tools/manageFieldPermissions.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";

export const MANAGE_FIELD_PERMISSIONS: Tool = {
  name: "salesforce_manage_field_permissions",
  description: `Manage Field Level Security (Field Permissions) for custom and standard fields.
  - Grant or revoke read/edit access to fields for specific profiles or permission sets
  - View current field permissions
  - Bulk update permissions for multiple profiles
  
  Examples:
  1. Grant System Administrator access to a field
  2. Give read-only access to a field for specific profiles
  3. Check which profiles have access to a field`,
  inputSchema: {
    type: "object",
    properties: {
      operation: {
        type: "string",
        enum: ["grant", "revoke", "view"],
        description: "Operation to perform on field permissions"
      },
      objectName: {
        type: "string",
        description: "API name of the object (e.g., 'Account', 'Custom_Object__c')"
      },
      fieldName: {
        type: "string",
        description: "API name of the field (e.g., 'Custom_Field__c')"
      },
      profileNames: {
        type: "array",
        items: { type: "string" },
        description: "Names of profiles to grant/revoke access (e.g., ['System Administrator', 'Sales User'])",
        optional: true
      },
      readable: {
        type: "boolean",
        description: "Grant/revoke read access (default: true)",
        optional: true
      },
      editable: {
        type: "boolean",
        description: "Grant/revoke edit access (default: true)",
        optional: true
      }
    },
    required: ["operation", "objectName", "fieldName"]
  }
};

export interface ManageFieldPermissionsArgs {
  operation: 'grant' | 'revoke' | 'view';
  objectName: string;
  fieldName: string;
  profileNames?: string[];
  readable?: boolean;
  editable?: boolean;
}

export async function handleManageFieldPermissions(conn: any, args: ManageFieldPermissionsArgs) {
  const { operation, objectName, fieldName, readable = true, editable = true } = args;
  let { profileNames } = args;

  try {
    // Ensure field name has __c suffix if it's a custom field and doesn't already have it
    const fieldApiName = fieldName.endsWith('__c') || fieldName.includes('.') ? fieldName : `${fieldName}__c`;
    const fullFieldName = `${objectName}.${fieldApiName}`;

    if (operation === 'view') {
      // Query existing field permissions
      const permissionsQuery = `
        SELECT Id, Parent.ProfileId, Parent.Profile.Name, Parent.IsOwnedByProfile,
               Parent.PermissionSetId, Parent.PermissionSet.Name,
               Field, PermissionsRead, PermissionsEdit
        FROM FieldPermissions
        WHERE SobjectType = '${objectName}'
        AND Field = '${fullFieldName}'
        ORDER BY Parent.Profile.Name
      `;

      const result = await conn.query(permissionsQuery);
      
      if (result.records.length === 0) {
        return {
          content: [{
            type: "text",
            text: `No field permissions found for ${fullFieldName}. This field might not have any specific permissions set, or it might be universally accessible.`
          }],
          isError: false,
        };
      }

      let responseText = `Field permissions for ${fullFieldName}:\n\n`;
      
      result.records.forEach((perm: any) => {
        const name = perm.Parent.IsOwnedByProfile 
          ? perm.Parent.Profile?.Name 
          : perm.Parent.PermissionSet?.Name;
        const type = perm.Parent.IsOwnedByProfile ? 'Profile' : 'Permission Set';
        
        responseText += `${type}: ${name}\n`;
        responseText += `  - Read Access: ${perm.PermissionsRead ? 'Yes' : 'No'}\n`;
        responseText += `  - Edit Access: ${perm.PermissionsEdit ? 'Yes' : 'No'}\n\n`;
      });

      return {
        content: [{
          type: "text",
          text: responseText
        }],
        isError: false,
      };
    }

    // For grant/revoke operations
    if (!profileNames || profileNames.length === 0) {
      // If no profiles specified, default to System Administrator
      profileNames = ['System Administrator'];
    }

    // Get profile IDs
    const profileQuery = await conn.query(`
      SELECT Id, Name 
      FROM Profile 
      WHERE Name IN (${profileNames.map(name => `'${name}'`).join(', ')})
    `);

    if (profileQuery.records.length === 0) {
      return {
        content: [{
          type: "text",
          text: `No profiles found matching: ${profileNames.join(', ')}`
        }],
        isError: true,
      };
    }

    const results: any[] = [];
    const errors: string[] = [];

    for (const profile of profileQuery.records) {
      try {
        if (operation === 'grant') {
          // First, check if permission already exists
          const existingPerm = await conn.query(`
            SELECT Id, PermissionsRead, PermissionsEdit
            FROM FieldPermissions
            WHERE ParentId IN (
              SELECT Id FROM PermissionSet 
              WHERE IsOwnedByProfile = true 
              AND ProfileId = '${profile.Id}'
            )
            AND Field = '${fullFieldName}'
            AND SobjectType = '${objectName}'
            LIMIT 1
          `);

          if (existingPerm.records.length > 0) {
            // Update existing permission
            const updateResult = await conn.sobject('FieldPermissions').update({
              Id: existingPerm.records[0].Id,
              PermissionsRead: readable,
              PermissionsEdit: editable && readable // Edit requires read
            });
            
            results.push({
              profile: profile.Name,
              action: 'updated',
              success: updateResult.success
            });
          } else {
            // Get the PermissionSet ID for this profile
            const permSetQuery = await conn.query(`
              SELECT Id FROM PermissionSet 
              WHERE IsOwnedByProfile = true 
              AND ProfileId = '${profile.Id}'
              LIMIT 1
            `);

            if (permSetQuery.records.length > 0) {
              // Create new permission
              const createResult = await conn.sobject('FieldPermissions').create({
                ParentId: permSetQuery.records[0].Id,
                SobjectType: objectName,
                Field: fullFieldName,
                PermissionsRead: readable,
                PermissionsEdit: editable && readable // Edit requires read
              });

              results.push({
                profile: profile.Name,
                action: 'created',
                success: createResult.success
              });
            } else {
              errors.push(`Could not find permission set for profile: ${profile.Name}`);
            }
          }
        } else if (operation === 'revoke') {
          // Find and delete the permission
          const existingPerm = await conn.query(`
            SELECT Id
            FROM FieldPermissions
            WHERE ParentId IN (
              SELECT Id FROM PermissionSet 
              WHERE IsOwnedByProfile = true 
              AND ProfileId = '${profile.Id}'
            )
            AND Field = '${fullFieldName}'
            AND SobjectType = '${objectName}'
            LIMIT 1
          `);

          if (existingPerm.records.length > 0) {
            const deleteResult = await conn.sobject('FieldPermissions').delete(existingPerm.records[0].Id);
            results.push({
              profile: profile.Name,
              action: 'revoked',
              success: true
            });
          } else {
            results.push({
              profile: profile.Name,
              action: 'no permission found',
              success: true
            });
          }
        }
      } catch (error) {
        errors.push(`${profile.Name}: ${error instanceof Error ? error.message : String(error)}`);
      }
    }

    // Format response
    let responseText = `Field permission ${operation} operation completed for ${fullFieldName}:\n\n`;
    
    const successful = results.filter(r => r.success);
    const failed = results.filter(r => !r.success);
    
    if (successful.length > 0) {
      responseText += 'Successful:\n';
      successful.forEach(r => {
        responseText += `  - ${r.profile}: ${r.action}\n`;
      });
    }
    
    if (failed.length > 0 || errors.length > 0) {
      responseText += '\nFailed:\n';
      failed.forEach(r => {
        responseText += `  - ${r.profile}: ${r.action}\n`;
      });
      errors.forEach(e => {
        responseText += `  - ${e}\n`;
      });
    }

    if (operation === 'grant') {
      responseText += `\nPermissions granted:\n  - Read: ${readable ? 'Yes' : 'No'}\n  - Edit: ${editable ? 'Yes' : 'No'}`;
    }

    return {
      content: [{
        type: "text",
        text: responseText
      }],
      isError: false,
    };

  } catch (error) {
    return {
      content: [{
        type: "text",
        text: `Error managing field permissions: ${error instanceof Error ? error.message : String(error)}`
      }],
      isError: true,
    };
  }
} 
```

--------------------------------------------------------------------------------
/src/tools/searchAll.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";

export const SEARCH_ALL: Tool = {
  name: "salesforce_search_all",
  description: `Search across multiple Salesforce objects using SOSL (Salesforce Object Search Language).
  
Examples:
1. Basic search across all objects:
   {
     "searchTerm": "John",
     "objects": [
       { "name": "Account", "fields": ["Name"], "limit": 10 },
       { "name": "Contact", "fields": ["FirstName", "LastName", "Email"] }
     ]
   }

2. Advanced search with filters:
   {
     "searchTerm": "Cloud*",
     "searchIn": "NAME FIELDS",
     "objects": [
       { 
         "name": "Account", 
         "fields": ["Name", "Industry"], 
         "orderBy": "Name DESC",
         "where": "Industry = 'Technology'"
       }
     ],
     "withClauses": [
       { "type": "NETWORK", "value": "ALL NETWORKS" },
       { "type": "SNIPPET", "fields": ["Description"] }
     ]
   }

Notes:
- Use * and ? for wildcards in search terms
- Each object can have its own WHERE, ORDER BY, and LIMIT clauses
- Support for WITH clauses: DATA CATEGORY, DIVISION, METADATA, NETWORK, PRICEBOOKID, SNIPPET, SECURITY_ENFORCED
- "updateable" and "viewable" options control record access filtering`,
  inputSchema: {
    type: "object",
    properties: {
      searchTerm: {
        type: "string",
        description: "Text to search for (supports wildcards * and ?)"
      },
      searchIn: {
        type: "string",
        enum: ["ALL FIELDS", "NAME FIELDS", "EMAIL FIELDS", "PHONE FIELDS", "SIDEBAR FIELDS"],
        description: "Which fields to search in",
        optional: true
      },
      objects: {
        type: "array",
        items: {
          type: "object",
          properties: {
            name: { 
              type: "string",
              description: "API name of the object"
            },
            fields: {
              type: "array",
              items: { type: "string" },
              description: "Fields to return for this object"
            },
            where: {
              type: "string",
              description: "WHERE clause for this object",
              optional: true
            },
            orderBy: {
              type: "string",
              description: "ORDER BY clause for this object",
              optional: true
            },
            limit: {
              type: "number",
              description: "Maximum number of records to return for this object",
              optional: true
            }
          },
          required: ["name", "fields"]
        },
        description: "List of objects to search and their return fields"
      },
      withClauses: {
        type: "array",
        items: {
          type: "object",
          properties: {
            type: {
              type: "string",
              enum: ["DATA CATEGORY", "DIVISION", "METADATA", "NETWORK", 
                    "PRICEBOOKID", "SNIPPET", "SECURITY_ENFORCED"]
            },
            value: {
              type: "string",
              description: "Value for the WITH clause",
              optional: true
            },
            fields: {
              type: "array",
              items: { type: "string" },
              description: "Fields for SNIPPET clause",
              optional: true
            }
          },
          required: ["type"]
        },
        description: "Additional WITH clauses for the search",
        optional: true
      },
      updateable: {
        type: "boolean",
        description: "Return only updateable records",
        optional: true
      },
      viewable: {
        type: "boolean",
        description: "Return only viewable records",
        optional: true
      }
    },
    required: ["searchTerm", "objects"]
  }
};

export interface SearchObject {
  name: string;
  fields: string[];
  where?: string;
  orderBy?: string;
  limit?: number;
}

export interface WithClause {
  type: "DATA CATEGORY" | "DIVISION" | "METADATA" | "NETWORK" | 
        "PRICEBOOKID" | "SNIPPET" | "SECURITY_ENFORCED";
  value?: string;
  fields?: string[];
}

export interface SearchAllArgs {
  searchTerm: string;
  searchIn?: "ALL FIELDS" | "NAME FIELDS" | "EMAIL FIELDS" | "PHONE FIELDS" | "SIDEBAR FIELDS";
  objects: SearchObject[];
  withClauses?: WithClause[];
  updateable?: boolean;
  viewable?: boolean;
}

function buildWithClause(withClause: WithClause): string {
  switch (withClause.type) {
    case "SNIPPET":
      return `WITH SNIPPET (${withClause.fields?.join(', ')})`;
    case "DATA CATEGORY":
    case "DIVISION":
    case "NETWORK":
    case "PRICEBOOKID":
      return `WITH ${withClause.type} = ${withClause.value}`;
    case "METADATA":
    case "SECURITY_ENFORCED":
      return `WITH ${withClause.type}`;
    default:
      return '';
  }
}

export async function handleSearchAll(conn: any, args: SearchAllArgs) {
  const { searchTerm, searchIn = "ALL FIELDS", objects, withClauses, updateable, viewable } = args;

  try {
    // Validate the search term
    if (!searchTerm.trim()) {
      throw new Error('Search term cannot be empty');
    }

    // Construct the RETURNING clause with object-specific clauses
    const returningClause = objects
      .map(obj => {
        let clause = `${obj.name}(${obj.fields.join(',')}`
        
        // Add object-specific clauses if present
        if (obj.where) clause += ` WHERE ${obj.where}`;
        if (obj.orderBy) clause += ` ORDER BY ${obj.orderBy}`;
        if (obj.limit) clause += ` LIMIT ${obj.limit}`;
        
        return clause + ')';
      })
      .join(', ');

    // Build WITH clauses if present
    const withClausesStr = withClauses
      ? withClauses.map(buildWithClause).join(' ')
      : '';

    // Add updateable/viewable flags if specified
    const accessFlags = [];
    if (updateable) accessFlags.push('UPDATEABLE');
    if (viewable) accessFlags.push('VIEWABLE');
    const accessClause = accessFlags.length > 0 ? 
      ` RETURNING ${accessFlags.join(',')}` : '';

    // Construct complete SOSL query
    const soslQuery = `FIND {${searchTerm}} IN ${searchIn} 
      ${withClausesStr}
      RETURNING ${returningClause}
      ${accessClause}`.trim();

    // Execute search
    const result = await conn.search(soslQuery);

    // Format results by object
    let formattedResults = '';
    objects.forEach((obj, index) => {
      const objectResults = result.searchRecords.filter((record: any) => 
        record.attributes.type === obj.name
      );

      formattedResults += `\n${obj.name} (${objectResults.length} records found):\n`;
      
      if (objectResults.length > 0) {
        objectResults.forEach((record: any, recordIndex: number) => {
          formattedResults += `  Record ${recordIndex + 1}:\n`;
          obj.fields.forEach(field => {
            const value = record[field];
            formattedResults += `    ${field}: ${value !== null && value !== undefined ? value : 'null'}\n`;
          });
          // Add metadata or snippet info if requested
          if (withClauses?.some(w => w.type === "METADATA")) {
            formattedResults += `    Metadata:\n      Last Modified: ${record.attributes.lastModifiedDate}\n`;
          }
          if (withClauses?.some(w => w.type === "SNIPPET")) {
            formattedResults += `    Snippets:\n${record.snippets?.map((s: any) => 
              `      ${s.field}: ${s.snippet}`).join('\n') || '      None'}\n`;
          }
        });
      }

      if (index < objects.length - 1) {
        formattedResults += '\n';
      }
    });

    return {
      content: [{
        type: "text",
        text: `Search Results:${formattedResults}`
      }],
      isError: false,
    };
  } catch (error) {
    // Enhanced error handling for SOSL queries
    const errorMessage = error instanceof Error ? error.message : String(error);
    let enhancedError = errorMessage;

    if (errorMessage.includes('MALFORMED_SEARCH')) {
      enhancedError = `Invalid search query format. Common issues:\n` +
        `1. Search term contains invalid characters\n` +
        `2. Object or field names are incorrect\n` +
        `3. Missing required SOSL syntax elements\n` +
        `4. Invalid WITH clause combination\n\n` +
        `Original error: ${errorMessage}`;
    } else if (errorMessage.includes('INVALID_FIELD')) {
      enhancedError = `Invalid field specified in RETURNING clause. Please check:\n` +
        `1. Field names are correct\n` +
        `2. Fields exist on the specified objects\n` +
        `3. You have access to all specified fields\n` +
        `4. WITH SNIPPET fields are valid\n\n` +
        `Original error: ${errorMessage}`;
    } else if (errorMessage.includes('WITH_CLAUSE')) {
      enhancedError = `Error in WITH clause. Please check:\n` +
        `1. WITH clause type is supported\n` +
        `2. WITH clause value is valid\n` +
        `3. You have permission to use the specified WITH clause\n\n` +
        `Original error: ${errorMessage}`;
    }

    return {
      content: [{
        type: "text",
        text: `Error executing search: ${enhancedError}`
      }],
      isError: true,
    };
  }
}
```

--------------------------------------------------------------------------------
/src/tools/aggregateQuery.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";

export const AGGREGATE_QUERY: Tool = {
  name: "salesforce_aggregate_query",
  description: `Execute SOQL queries with GROUP BY, aggregate functions, and statistical analysis. Use this tool for queries that summarize and group data rather than returning individual records.

NOTE: For regular queries without GROUP BY or aggregates, use salesforce_query_records instead.

This tool handles:
1. GROUP BY queries (single/multiple fields, related objects, date functions)
2. Aggregate functions: COUNT(), COUNT_DISTINCT(), SUM(), AVG(), MIN(), MAX()
3. HAVING clauses for filtering grouped results
4. Date/time grouping: CALENDAR_YEAR(), CALENDAR_MONTH(), CALENDAR_QUARTER(), FISCAL_YEAR(), FISCAL_QUARTER()

Examples:
1. Count opportunities by stage:
   - objectName: "Opportunity"
   - selectFields: ["StageName", "COUNT(Id) OpportunityCount"]
   - groupByFields: ["StageName"]

2. Analyze cases by priority and status:
   - objectName: "Case"
   - selectFields: ["Priority", "Status", "COUNT(Id) CaseCount", "AVG(Days_Open__c) AvgDaysOpen"]
   - groupByFields: ["Priority", "Status"]

3. Count contacts by account industry:
   - objectName: "Contact"
   - selectFields: ["Account.Industry", "COUNT(Id) ContactCount"]
   - groupByFields: ["Account.Industry"]

4. Quarterly opportunity analysis:
   - objectName: "Opportunity"
   - selectFields: ["CALENDAR_YEAR(CloseDate) Year", "CALENDAR_QUARTER(CloseDate) Quarter", "SUM(Amount) Revenue"]
   - groupByFields: ["CALENDAR_YEAR(CloseDate)", "CALENDAR_QUARTER(CloseDate)"]

5. Find accounts with more than 10 opportunities:
   - objectName: "Opportunity"
   - selectFields: ["Account.Name", "COUNT(Id) OpportunityCount"]
   - groupByFields: ["Account.Name"]
   - havingClause: "COUNT(Id) > 10"

Important Rules:
- All non-aggregate fields in selectFields MUST be included in groupByFields
- Use whereClause to filter rows BEFORE grouping
- Use havingClause to filter AFTER grouping (for aggregate conditions)
- ORDER BY can only use fields from groupByFields or aggregate functions
- OFFSET is not supported with GROUP BY in Salesforce`,
  inputSchema: {
    type: "object",
    properties: {
      objectName: {
        type: "string",
        description: "API name of the object to query"
      },
      selectFields: {
        type: "array",
        items: { type: "string" },
        description: "Fields to select - mix of group fields and aggregates. Format: 'FieldName' or 'COUNT(Id) AliasName'"
      },
      groupByFields: {
        type: "array",
        items: { type: "string" },
        description: "Fields to group by - must include all non-aggregate fields from selectFields"
      },
      whereClause: {
        type: "string",
        description: "WHERE clause to filter rows BEFORE grouping (cannot contain aggregate functions)",
        optional: true
      },
      havingClause: {
        type: "string",
        description: "HAVING clause to filter results AFTER grouping (use for aggregate conditions)",
        optional: true
      },
      orderBy: {
        type: "string",
        description: "ORDER BY clause - can only use grouped fields or aggregate functions",
        optional: true
      },
      limit: {
        type: "number",
        description: "Maximum number of grouped results to return",
        optional: true
      }
    },
    required: ["objectName", "selectFields", "groupByFields"]
  }
};

export interface AggregateQueryArgs {
  objectName: string;
  selectFields: string[];
  groupByFields: string[];
  whereClause?: string;
  havingClause?: string;
  orderBy?: string;
  limit?: number;
}

// Aggregate functions that don't need to be in GROUP BY
const AGGREGATE_FUNCTIONS = ['COUNT', 'COUNT_DISTINCT', 'SUM', 'AVG', 'MIN', 'MAX'];
const DATE_FUNCTIONS = ['CALENDAR_YEAR', 'CALENDAR_MONTH', 'CALENDAR_QUARTER', 'FISCAL_YEAR', 'FISCAL_QUARTER'];

// Helper function to detect if a field contains an aggregate function
function isAggregateField(field: string): boolean {
  const upperField = field.toUpperCase();
  return AGGREGATE_FUNCTIONS.some(func => upperField.includes(`${func}(`));
}

// Helper function to extract the base field from a select field (removing alias)
function extractBaseField(field: string): string {
  // Remove alias if present (e.g., "COUNT(Id) OpportunityCount" -> "COUNT(Id)")
  const parts = field.trim().split(/\s+/);
  return parts[0];
}

// Helper function to extract non-aggregate fields from select fields
function extractNonAggregateFields(selectFields: string[]): string[] {
  return selectFields
    .filter(field => !isAggregateField(field))
    .map(field => extractBaseField(field));
}

// Helper function to validate that all non-aggregate fields are in GROUP BY
function validateGroupByFields(selectFields: string[], groupByFields: string[]): { isValid: boolean; missingFields?: string[] } {
  const nonAggregateFields = extractNonAggregateFields(selectFields);
  const groupBySet = new Set(groupByFields.map(f => f.trim()));
  
  const missingFields = nonAggregateFields.filter(field => !groupBySet.has(field));
  
  return {
    isValid: missingFields.length === 0,
    missingFields
  };
}

// Helper function to validate WHERE clause doesn't contain aggregates
function validateWhereClause(whereClause: string | undefined): { isValid: boolean; error?: string } {
  if (!whereClause) return { isValid: true };
  
  const upperWhere = whereClause.toUpperCase();
  for (const func of AGGREGATE_FUNCTIONS) {
    if (upperWhere.includes(`${func}(`)) {
      return {
        isValid: false,
        error: `WHERE clause cannot contain aggregate functions. Use HAVING clause instead for aggregate conditions like ${func}()`
      };
    }
  }
  
  return { isValid: true };
}

// Helper function to validate ORDER BY fields
function validateOrderBy(orderBy: string | undefined, groupByFields: string[], selectFields: string[]): { isValid: boolean; error?: string } {
  if (!orderBy) return { isValid: true };
  
  // Extract fields from ORDER BY (handling DESC/ASC)
  const orderByParts = orderBy.split(',').map(part => {
    return part.trim().replace(/ (DESC|ASC)$/i, '').trim();
  });
  
  const groupBySet = new Set(groupByFields);
  const aggregateFields = selectFields.filter(field => isAggregateField(field)).map(field => extractBaseField(field));
  
  for (const orderField of orderByParts) {
    // Check if it's in GROUP BY or is an aggregate
    if (!groupBySet.has(orderField) && !aggregateFields.some(agg => agg === orderField) && !isAggregateField(orderField)) {
      return {
        isValid: false,
        error: `ORDER BY field '${orderField}' must be in GROUP BY clause or be an aggregate function`
      };
    }
  }
  
  return { isValid: true };
}

export async function handleAggregateQuery(conn: any, args: AggregateQueryArgs) {
  const { objectName, selectFields, groupByFields, whereClause, havingClause, orderBy, limit } = args;

  try {
    // Validate GROUP BY contains all non-aggregate fields
    const groupByValidation = validateGroupByFields(selectFields, groupByFields);
    if (!groupByValidation.isValid) {
      return {
        content: [{
          type: "text",
          text: `Error: The following non-aggregate fields must be included in GROUP BY clause: ${groupByValidation.missingFields!.join(', ')}\n\n` +
                `All fields in SELECT that are not aggregate functions (COUNT, SUM, AVG, etc.) must be included in GROUP BY.`
        }],
        isError: true,
      };
    }

    // Validate WHERE clause doesn't contain aggregates
    const whereValidation = validateWhereClause(whereClause);
    if (!whereValidation.isValid) {
      return {
        content: [{
          type: "text",
          text: whereValidation.error!
        }],
        isError: true,
      };
    }

    // Validate ORDER BY fields
    const orderByValidation = validateOrderBy(orderBy, groupByFields, selectFields);
    if (!orderByValidation.isValid) {
      return {
        content: [{
          type: "text",
          text: orderByValidation.error!
        }],
        isError: true,
      };
    }

    // Construct SOQL query
    let soql = `SELECT ${selectFields.join(', ')} FROM ${objectName}`;
    if (whereClause) soql += ` WHERE ${whereClause}`;
    soql += ` GROUP BY ${groupByFields.join(', ')}`;
    if (havingClause) soql += ` HAVING ${havingClause}`;
    if (orderBy) soql += ` ORDER BY ${orderBy}`;
    if (limit) soql += ` LIMIT ${limit}`;

    const result = await conn.query(soql);
    
    // Format the output
    const formattedRecords = result.records.map((record: any, index: number) => {
      const recordStr = selectFields.map(field => {
        const baseField = extractBaseField(field);
        const fieldParts = field.trim().split(/\s+/);
        const displayName = fieldParts.length > 1 ? fieldParts[fieldParts.length - 1] : baseField;
        
        // Handle nested fields in results
        if (baseField.includes('.')) {
          const parts = baseField.split('.');
          let value = record;
          for (const part of parts) {
            value = value?.[part];
          }
          return `    ${displayName}: ${value !== null && value !== undefined ? value : 'null'}`;
        }
        
        const value = record[baseField] || record[displayName];
        return `    ${displayName}: ${value !== null && value !== undefined ? value : 'null'}`;
      }).join('\n');
      return `Group ${index + 1}:\n${recordStr}`;
    }).join('\n\n');

    return {
      content: [{
        type: "text",
        text: `Aggregate query returned ${result.records.length} grouped results:\n\n${formattedRecords}`
      }],
      isError: false,
    };
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    
    // Provide more helpful error messages for common issues
    let enhancedError = errorMessage;
    if (errorMessage.includes('MALFORMED_QUERY')) {
      if (errorMessage.includes('GROUP BY')) {
        enhancedError = `Query error: ${errorMessage}\n\nCommon issues:\n` +
          `1. Ensure all non-aggregate fields in SELECT are in GROUP BY\n` +
          `2. Check that date functions match exactly between SELECT and GROUP BY\n` +
          `3. Verify field names and relationships are correct`;
      }
    }

    return {
      content: [{
        type: "text",
        text: `Error executing aggregate query: ${enhancedError}`
      }],
      isError: true,
    };
  }
} 
```

--------------------------------------------------------------------------------
/src/tools/manageField.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { FieldMetadataInfo } from "../types/metadata";

export const MANAGE_FIELD: Tool = {
  name: "salesforce_manage_field",
  description: `Create new custom fields or modify existing fields on any Salesforce object:
  - Field Types: Text, Number, Date, Lookup, Master-Detail, Picklist etc.
  - Properties: Required, Unique, External ID, Length, Scale etc.
  - Relationships: Create lookups and master-detail relationships
  - Automatically grants Field Level Security to System Administrator (or specified profiles)
  Examples: Add Rating__c picklist to Account, Create Account lookup on Custom Object
  Note: Use grantAccessTo parameter to specify profiles, defaults to System Administrator`,
  inputSchema: {
    type: "object",
    properties: {
      operation: {
        type: "string",
        enum: ["create", "update"],
        description: "Whether to create new field or update existing"
      },
      objectName: {
        type: "string",
        description: "API name of the object to add/modify the field"
      },
      fieldName: {
        type: "string",
        description: "API name for the field (without __c suffix)"
      },
      label: {
        type: "string",
        description: "Label for the field",
        optional: true
      },
      type: {
        type: "string",
        enum: ["Checkbox", "Currency", "Date", "DateTime", "Email", "Number", "Percent", 
               "Phone", "Picklist", "MultiselectPicklist", "Text", "TextArea", "LongTextArea", 
               "Html", "Url", "Lookup", "MasterDetail"],
        description: "Field type (required for create)",
        optional: true
      },
      required: {
        type: "boolean",
        description: "Whether the field is required",
        optional: true
      },
      unique: {
        type: "boolean",
        description: "Whether the field value must be unique",
        optional: true
      },
      externalId: {
        type: "boolean",
        description: "Whether the field is an external ID",
        optional: true
      },
      length: {
        type: "number",
        description: "Length for text fields",
        optional: true
      },
      precision: {
        type: "number",
        description: "Precision for numeric fields",
        optional: true
      },
      scale: {
        type: "number",
        description: "Scale for numeric fields",
        optional: true
      },
      referenceTo: {
        type: "string",
        description: "API name of the object to reference (for Lookup/MasterDetail)",
        optional: true
      },
      relationshipLabel: {
        type: "string",
        description: "Label for the relationship (for Lookup/MasterDetail)",
        optional: true
      },
      relationshipName: {
        type: "string",
        description: "API name for the relationship (for Lookup/MasterDetail)",
        optional: true
      },
      deleteConstraint: {
        type: "string",
        enum: ["Cascade", "Restrict", "SetNull"],
        description: "Delete constraint for Lookup fields",
        optional: true
      },
      picklistValues: {
        type: "array",
        items: {
          type: "object",
          properties: {
            label: { type: "string" },
            isDefault: { type: "boolean", optional: true }
          }
        },
        description: "Values for Picklist/MultiselectPicklist fields",
        optional: true
      },
      description: {
        type: "string",
        description: "Description of the field",
        optional: true
      },
      grantAccessTo: {
        type: "array",
        items: { type: "string" },
        description: "Profile names to grant field access to (defaults to ['System Administrator'])",
        optional: true
      }
    },
    required: ["operation", "objectName", "fieldName"]
  }
};

export interface ManageFieldArgs {
  operation: 'create' | 'update';
  objectName: string;
  fieldName: string;
  label?: string;
  type?: string;
  required?: boolean;
  unique?: boolean;
  externalId?: boolean;
  length?: number;
  precision?: number;
  scale?: number;
  referenceTo?: string;
  relationshipLabel?: string;
  relationshipName?: string;
  deleteConstraint?: 'Cascade' | 'Restrict' | 'SetNull';
  picklistValues?: Array<{ label: string; isDefault?: boolean }>;
  description?: string;
  grantAccessTo?: string[];
}

// Helper function to set field permissions (simplified version of the one in manageFieldPermissions.ts)
async function grantFieldPermissions(conn: any, objectName: string, fieldName: string, profileNames: string[]): Promise<{success: boolean; message: string}> {
  try {
    const fieldApiName = fieldName.endsWith('__c') || fieldName.includes('.') ? fieldName : `${fieldName}__c`;
    const fullFieldName = `${objectName}.${fieldApiName}`;
    
    // Get profile IDs
    const profileQuery = await conn.query(`
      SELECT Id, Name 
      FROM Profile 
      WHERE Name IN (${profileNames.map(name => `'${name}'`).join(', ')})
    `);

    if (profileQuery.records.length === 0) {
      return { success: false, message: `No profiles found matching: ${profileNames.join(', ')}` };
    }

    const results: any[] = [];
    const errors: string[] = [];

    for (const profile of profileQuery.records) {
      try {
        // Check if permission already exists
        const existingPerm = await conn.query(`
          SELECT Id, PermissionsRead, PermissionsEdit
          FROM FieldPermissions
          WHERE ParentId IN (
            SELECT Id FROM PermissionSet 
            WHERE IsOwnedByProfile = true 
            AND ProfileId = '${profile.Id}'
          )
          AND Field = '${fullFieldName}'
          AND SobjectType = '${objectName}'
          LIMIT 1
        `);

        if (existingPerm.records.length > 0) {
          // Update existing permission
          await conn.sobject('FieldPermissions').update({
            Id: existingPerm.records[0].Id,
            PermissionsRead: true,
            PermissionsEdit: true
          });
          results.push(profile.Name);
        } else {
          // Get the PermissionSet ID for this profile
          const permSetQuery = await conn.query(`
            SELECT Id FROM PermissionSet 
            WHERE IsOwnedByProfile = true 
            AND ProfileId = '${profile.Id}'
            LIMIT 1
          `);

          if (permSetQuery.records.length > 0) {
            // Create new permission
            await conn.sobject('FieldPermissions').create({
              ParentId: permSetQuery.records[0].Id,
              SobjectType: objectName,
              Field: fullFieldName,
              PermissionsRead: true,
              PermissionsEdit: true
            });
            results.push(profile.Name);
          } else {
            errors.push(profile.Name);
          }
        }
      } catch (error) {
        errors.push(profile.Name);
        console.error(`Error granting permission to ${profile.Name}:`, error);
      }
    }

    if (results.length > 0) {
      return {
        success: true,
        message: `Field Level Security granted to: ${results.join(', ')}${errors.length > 0 ? `. Failed for: ${errors.join(', ')}` : ''}`
      };
    } else {
      return {
        success: false,
        message: `Could not grant Field Level Security to any profiles.`
      };
    }
  } catch (error) {
    console.error('Error granting field permissions:', error);
    return {
      success: false,
      message: `Field Level Security configuration failed.`
    };
  }
}

export async function handleManageField(conn: any, args: ManageFieldArgs) {
  const { operation, objectName, fieldName, type, grantAccessTo, ...fieldProps } = args;

  try {
    if (operation === 'create') {
      if (!type) {
        throw new Error('Field type is required for field creation');
      }

      // Prepare base metadata for the new field
      const metadata: FieldMetadataInfo = {
        fullName: `${objectName}.${fieldName}__c`,
        label: fieldProps.label || fieldName,
        type,
        ...(fieldProps.required && { required: fieldProps.required }),
        ...(fieldProps.unique && { unique: fieldProps.unique }),
        ...(fieldProps.externalId && { externalId: fieldProps.externalId }),
        ...(fieldProps.description && { description: fieldProps.description })
      };

      // Add type-specific properties
      switch (type) {
        case 'MasterDetail':
        case 'Lookup':
          if (fieldProps.referenceTo) {
            metadata.referenceTo = fieldProps.referenceTo;
            metadata.relationshipName = fieldProps.relationshipName;
            metadata.relationshipLabel = fieldProps.relationshipLabel || fieldProps.relationshipName;
            if (type === 'Lookup' && fieldProps.deleteConstraint) {
              metadata.deleteConstraint = fieldProps.deleteConstraint;
            }
          }
          break;

        case 'TextArea':
          metadata.type = 'LongTextArea';
          metadata.length = fieldProps.length || 32768;
          metadata.visibleLines = 3;
          break;

        case 'Text':
          if (fieldProps.length) {
            metadata.length = fieldProps.length;
          }
          break;

        case 'Number':
          if (fieldProps.precision) {
            metadata.precision = fieldProps.precision;
            metadata.scale = fieldProps.scale || 0;
          }
          break;

        case 'Picklist':
        case 'MultiselectPicklist':
          if (fieldProps.picklistValues) {
            metadata.valueSet = {
              valueSetDefinition: {
                sorted: true,
                value: fieldProps.picklistValues.map(val => ({
                  fullName: val.label,
                  default: val.isDefault || false,
                  label: val.label
                }))
              }
            };
          }
          break;
      }

      // Create the field
      const result = await conn.metadata.create('CustomField', metadata);

      if (result && (Array.isArray(result) ? result[0].success : result.success)) {
        let permissionMessage = '';
        
        // Grant Field Level Security (default to System Administrator if not specified)
        const profilesToGrant = grantAccessTo && grantAccessTo.length > 0 ? grantAccessTo : ['System Administrator'];
        
        // Wait a moment for field to be fully created
        await new Promise(resolve => setTimeout(resolve, 2000));
        
        const permissionResult = await grantFieldPermissions(conn, objectName, fieldName, profilesToGrant);
        permissionMessage = `\n${permissionResult.message}`;
        
        return {
          content: [{
            type: "text",
            text: `Successfully created custom field ${fieldName}__c on ${objectName}.${permissionMessage}`
          }],
          isError: false,
        };
      }
    } else {
      // For update, first get existing metadata
      const existingMetadata = await conn.metadata.read('CustomField', [`${objectName}.${fieldName}__c`]);
      const currentMetadata = Array.isArray(existingMetadata) ? existingMetadata[0] : existingMetadata;

      if (!currentMetadata) {
        throw new Error(`Field ${fieldName}__c not found on object ${objectName}`);
      }

      // Prepare update metadata
      const metadata: FieldMetadataInfo = {
        ...currentMetadata,
        ...(fieldProps.label && { label: fieldProps.label }),
        ...(fieldProps.required !== undefined && { required: fieldProps.required }),
        ...(fieldProps.unique !== undefined && { unique: fieldProps.unique }),
        ...(fieldProps.externalId !== undefined && { externalId: fieldProps.externalId }),
        ...(fieldProps.description !== undefined && { description: fieldProps.description }),
        ...(fieldProps.length && { length: fieldProps.length }),
        ...(fieldProps.precision && { precision: fieldProps.precision, scale: fieldProps.scale || 0 })
      };

      // Special handling for picklist values if provided
      if (fieldProps.picklistValues && 
          (currentMetadata.type === 'Picklist' || currentMetadata.type === 'MultiselectPicklist')) {
        metadata.valueSet = {
          valueSetDefinition: {
            sorted: true,
            value: fieldProps.picklistValues.map(val => ({
              fullName: val.label,
              default: val.isDefault || false,
              label: val.label
            }))
          }
        };
      }

      // Update the field
      const result = await conn.metadata.update('CustomField', metadata);

      if (result && (Array.isArray(result) ? result[0].success : result.success)) {
        return {
          content: [{
            type: "text",
            text: `Successfully updated custom field ${fieldName}__c on ${objectName}`
          }],
          isError: false,
        };
      }
    }

    return {
      content: [{
        type: "text",
        text: `Failed to ${operation} custom field ${fieldName}__c`
      }],
      isError: true,
    };

  } catch (error) {
    return {
      content: [{
        type: "text",
        text: `Error ${operation === 'create' ? 'creating' : 'updating'} custom field: ${error instanceof Error ? error.message : String(error)}`
      }],
      isError: true,
    };
  }
}
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import * as dotenv from "dotenv";

import { createSalesforceConnection } from "./utils/connection.js";
import { SEARCH_OBJECTS, handleSearchObjects } from "./tools/search.js";
import { DESCRIBE_OBJECT, handleDescribeObject } from "./tools/describe.js";
import { QUERY_RECORDS, handleQueryRecords, QueryArgs } from "./tools/query.js";
import { AGGREGATE_QUERY, handleAggregateQuery, AggregateQueryArgs } from "./tools/aggregateQuery.js";
import { DML_RECORDS, handleDMLRecords, DMLArgs } from "./tools/dml.js";
import { MANAGE_OBJECT, handleManageObject, ManageObjectArgs } from "./tools/manageObject.js";
import { MANAGE_FIELD, handleManageField, ManageFieldArgs } from "./tools/manageField.js";
import { MANAGE_FIELD_PERMISSIONS, handleManageFieldPermissions, ManageFieldPermissionsArgs } from "./tools/manageFieldPermissions.js";
import { SEARCH_ALL, handleSearchAll, SearchAllArgs, WithClause } from "./tools/searchAll.js";
import { READ_APEX, handleReadApex, ReadApexArgs } from "./tools/readApex.js";
import { WRITE_APEX, handleWriteApex, WriteApexArgs } from "./tools/writeApex.js";
import { READ_APEX_TRIGGER, handleReadApexTrigger, ReadApexTriggerArgs } from "./tools/readApexTrigger.js";
import { WRITE_APEX_TRIGGER, handleWriteApexTrigger, WriteApexTriggerArgs } from "./tools/writeApexTrigger.js";
import { EXECUTE_ANONYMOUS, handleExecuteAnonymous, ExecuteAnonymousArgs } from "./tools/executeAnonymous.js";
import { MANAGE_DEBUG_LOGS, handleManageDebugLogs, ManageDebugLogsArgs } from "./tools/manageDebugLogs.js";

dotenv.config();

const server = new Server(
  {
    name: "salesforce-mcp-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  },
);

// Tool handlers
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    SEARCH_OBJECTS, 
    DESCRIBE_OBJECT, 
    QUERY_RECORDS, 
    AGGREGATE_QUERY,
    DML_RECORDS,
    MANAGE_OBJECT,
    MANAGE_FIELD,
    MANAGE_FIELD_PERMISSIONS,
    SEARCH_ALL,
    READ_APEX,
    WRITE_APEX,
    READ_APEX_TRIGGER,
    WRITE_APEX_TRIGGER,
    EXECUTE_ANONYMOUS,
    MANAGE_DEBUG_LOGS
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    const { name, arguments: args } = request.params;
    if (!args) throw new Error('Arguments are required');

    const conn = await createSalesforceConnection();

    switch (name) {
      case "salesforce_search_objects": {
        const { searchPattern } = args as { searchPattern: string };
        if (!searchPattern) throw new Error('searchPattern is required');
        return await handleSearchObjects(conn, searchPattern);
      }

      case "salesforce_describe_object": {
        const { objectName } = args as { objectName: string };
        if (!objectName) throw new Error('objectName is required');
        return await handleDescribeObject(conn, objectName);
      }

      case "salesforce_query_records": {
        const queryArgs = args as Record<string, unknown>;
        if (!queryArgs.objectName || !Array.isArray(queryArgs.fields)) {
          throw new Error('objectName and fields array are required for query');
        }
        // Type check and conversion
        const validatedArgs: QueryArgs = {
          objectName: queryArgs.objectName as string,
          fields: queryArgs.fields as string[],
          whereClause: queryArgs.whereClause as string | undefined,
          orderBy: queryArgs.orderBy as string | undefined,
          limit: queryArgs.limit as number | undefined
        };
        return await handleQueryRecords(conn, validatedArgs);
      }

      case "salesforce_aggregate_query": {
        const aggregateArgs = args as Record<string, unknown>;
        if (!aggregateArgs.objectName || !Array.isArray(aggregateArgs.selectFields) || !Array.isArray(aggregateArgs.groupByFields)) {
          throw new Error('objectName, selectFields array, and groupByFields array are required for aggregate query');
        }
        // Type check and conversion
        const validatedArgs: AggregateQueryArgs = {
          objectName: aggregateArgs.objectName as string,
          selectFields: aggregateArgs.selectFields as string[],
          groupByFields: aggregateArgs.groupByFields as string[],
          whereClause: aggregateArgs.whereClause as string | undefined,
          havingClause: aggregateArgs.havingClause as string | undefined,
          orderBy: aggregateArgs.orderBy as string | undefined,
          limit: aggregateArgs.limit as number | undefined
        };
        return await handleAggregateQuery(conn, validatedArgs);
      }

      case "salesforce_dml_records": {
        const dmlArgs = args as Record<string, unknown>;
        if (!dmlArgs.operation || !dmlArgs.objectName || !Array.isArray(dmlArgs.records)) {
          throw new Error('operation, objectName, and records array are required for DML');
        }
        const validatedArgs: DMLArgs = {
          operation: dmlArgs.operation as 'insert' | 'update' | 'delete' | 'upsert',
          objectName: dmlArgs.objectName as string,
          records: dmlArgs.records as Record<string, any>[],
          externalIdField: dmlArgs.externalIdField as string | undefined
        };
        return await handleDMLRecords(conn, validatedArgs);
      }

      case "salesforce_manage_object": {
        const objectArgs = args as Record<string, unknown>;
        if (!objectArgs.operation || !objectArgs.objectName) {
          throw new Error('operation and objectName are required for object management');
        }
        const validatedArgs: ManageObjectArgs = {
          operation: objectArgs.operation as 'create' | 'update',
          objectName: objectArgs.objectName as string,
          label: objectArgs.label as string | undefined,
          pluralLabel: objectArgs.pluralLabel as string | undefined,
          description: objectArgs.description as string | undefined,
          nameFieldLabel: objectArgs.nameFieldLabel as string | undefined,
          nameFieldType: objectArgs.nameFieldType as 'Text' | 'AutoNumber' | undefined,
          nameFieldFormat: objectArgs.nameFieldFormat as string | undefined,
          sharingModel: objectArgs.sharingModel as 'ReadWrite' | 'Read' | 'Private' | 'ControlledByParent' | undefined
        };
        return await handleManageObject(conn, validatedArgs);
      }

      case "salesforce_manage_field": {
        const fieldArgs = args as Record<string, unknown>;
        if (!fieldArgs.operation || !fieldArgs.objectName || !fieldArgs.fieldName) {
          throw new Error('operation, objectName, and fieldName are required for field management');
        }
        const validatedArgs: ManageFieldArgs = {
          operation: fieldArgs.operation as 'create' | 'update',
          objectName: fieldArgs.objectName as string,
          fieldName: fieldArgs.fieldName as string,
          label: fieldArgs.label as string | undefined,
          type: fieldArgs.type as string | undefined,
          required: fieldArgs.required as boolean | undefined,
          unique: fieldArgs.unique as boolean | undefined,
          externalId: fieldArgs.externalId as boolean | undefined,
          length: fieldArgs.length as number | undefined,
          precision: fieldArgs.precision as number | undefined,
          scale: fieldArgs.scale as number | undefined,
          referenceTo: fieldArgs.referenceTo as string | undefined,
          relationshipLabel: fieldArgs.relationshipLabel as string | undefined,
          relationshipName: fieldArgs.relationshipName as string | undefined,
          deleteConstraint: fieldArgs.deleteConstraint as 'Cascade' | 'Restrict' | 'SetNull' | undefined,
          picklistValues: fieldArgs.picklistValues as Array<{ label: string; isDefault?: boolean }> | undefined,
          description: fieldArgs.description as string | undefined,
          grantAccessTo: fieldArgs.grantAccessTo as string[] | undefined
        };
        return await handleManageField(conn, validatedArgs);
      }

      case "salesforce_manage_field_permissions": {
        const permArgs = args as Record<string, unknown>;
        if (!permArgs.operation || !permArgs.objectName || !permArgs.fieldName) {
          throw new Error('operation, objectName, and fieldName are required for field permissions management');
        }
        const validatedArgs: ManageFieldPermissionsArgs = {
          operation: permArgs.operation as 'grant' | 'revoke' | 'view',
          objectName: permArgs.objectName as string,
          fieldName: permArgs.fieldName as string,
          profileNames: permArgs.profileNames as string[] | undefined,
          readable: permArgs.readable as boolean | undefined,
          editable: permArgs.editable as boolean | undefined
        };
        return await handleManageFieldPermissions(conn, validatedArgs);
      }

      case "salesforce_search_all": {
        const searchArgs = args as Record<string, unknown>;
        if (!searchArgs.searchTerm || !Array.isArray(searchArgs.objects)) {
          throw new Error('searchTerm and objects array are required for search');
        }

        // Validate objects array
        const objects = searchArgs.objects as Array<Record<string, unknown>>;
        if (!objects.every(obj => obj.name && Array.isArray(obj.fields))) {
          throw new Error('Each object must specify name and fields array');
        }

        // Type check and conversion
        const validatedArgs: SearchAllArgs = {
          searchTerm: searchArgs.searchTerm as string,
          searchIn: searchArgs.searchIn as "ALL FIELDS" | "NAME FIELDS" | "EMAIL FIELDS" | "PHONE FIELDS" | "SIDEBAR FIELDS" | undefined,
          objects: objects.map(obj => ({
            name: obj.name as string,
            fields: obj.fields as string[],
            where: obj.where as string | undefined,
            orderBy: obj.orderBy as string | undefined,
            limit: obj.limit as number | undefined
          })),
          withClauses: searchArgs.withClauses as WithClause[] | undefined,
          updateable: searchArgs.updateable as boolean | undefined,
          viewable: searchArgs.viewable as boolean | undefined
        };

        return await handleSearchAll(conn, validatedArgs);
      }

      case "salesforce_read_apex": {
        const apexArgs = args as Record<string, unknown>;
        
        // Type check and conversion
        const validatedArgs: ReadApexArgs = {
          className: apexArgs.className as string | undefined,
          namePattern: apexArgs.namePattern as string | undefined,
          includeMetadata: apexArgs.includeMetadata as boolean | undefined
        };

        return await handleReadApex(conn, validatedArgs);
      }

      case "salesforce_write_apex": {
        const apexArgs = args as Record<string, unknown>;
        if (!apexArgs.operation || !apexArgs.className || !apexArgs.body) {
          throw new Error('operation, className, and body are required for writing Apex');
        }
        
        // Type check and conversion
        const validatedArgs: WriteApexArgs = {
          operation: apexArgs.operation as 'create' | 'update',
          className: apexArgs.className as string,
          apiVersion: apexArgs.apiVersion as string | undefined,
          body: apexArgs.body as string
        };

        return await handleWriteApex(conn, validatedArgs);
      }

      case "salesforce_read_apex_trigger": {
        const triggerArgs = args as Record<string, unknown>;
        
        // Type check and conversion
        const validatedArgs: ReadApexTriggerArgs = {
          triggerName: triggerArgs.triggerName as string | undefined,
          namePattern: triggerArgs.namePattern as string | undefined,
          includeMetadata: triggerArgs.includeMetadata as boolean | undefined
        };

        return await handleReadApexTrigger(conn, validatedArgs);
      }

      case "salesforce_write_apex_trigger": {
        const triggerArgs = args as Record<string, unknown>;
        if (!triggerArgs.operation || !triggerArgs.triggerName || !triggerArgs.body) {
          throw new Error('operation, triggerName, and body are required for writing Apex trigger');
        }
        
        // Type check and conversion
        const validatedArgs: WriteApexTriggerArgs = {
          operation: triggerArgs.operation as 'create' | 'update',
          triggerName: triggerArgs.triggerName as string,
          objectName: triggerArgs.objectName as string | undefined,
          apiVersion: triggerArgs.apiVersion as string | undefined,
          body: triggerArgs.body as string
        };

        return await handleWriteApexTrigger(conn, validatedArgs);
      }

      case "salesforce_execute_anonymous": {
        const executeArgs = args as Record<string, unknown>;
        if (!executeArgs.apexCode) {
          throw new Error('apexCode is required for executing anonymous Apex');
        }
        
        // Type check and conversion
        const validatedArgs: ExecuteAnonymousArgs = {
          apexCode: executeArgs.apexCode as string,
          logLevel: executeArgs.logLevel as 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'FINE' | 'FINER' | 'FINEST' | undefined
        };

        return await handleExecuteAnonymous(conn, validatedArgs);
      }

      case "salesforce_manage_debug_logs": {
        const debugLogsArgs = args as Record<string, unknown>;
        if (!debugLogsArgs.operation || !debugLogsArgs.username) {
          throw new Error('operation and username are required for managing debug logs');
        }
        
        // Type check and conversion
        const validatedArgs: ManageDebugLogsArgs = {
          operation: debugLogsArgs.operation as 'enable' | 'disable' | 'retrieve',
          username: debugLogsArgs.username as string,
          logLevel: debugLogsArgs.logLevel as 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'FINE' | 'FINER' | 'FINEST' | undefined,
          expirationTime: debugLogsArgs.expirationTime as number | undefined,
          limit: debugLogsArgs.limit as number | undefined,
          logId: debugLogsArgs.logId as string | undefined,
          includeBody: debugLogsArgs.includeBody as boolean | undefined
        };

        return await handleManageDebugLogs(conn, validatedArgs);
      }

      default:
        return {
          content: [{ type: "text", text: `Unknown tool: ${name}` }],
          isError: true,
        };
    }
  } catch (error) {
    return {
      content: [{
        type: "text",
        text: `Error: ${error instanceof Error ? error.message : String(error)}`,
      }],
      isError: true,
    };
  }
});

async function runServer() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Salesforce MCP Server running on stdio");
}

runServer().catch((error) => {
  console.error("Fatal error running server:", error);
  process.exit(1);
});
```

--------------------------------------------------------------------------------
/src/tools/manageDebugLogs.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import type { Connection } from "jsforce";

export const MANAGE_DEBUG_LOGS: Tool = {
  name: "salesforce_manage_debug_logs",
  description: `Manage debug logs for Salesforce users - enable, disable, or retrieve logs.
  
Examples:
1. Enable debug logs for a user:
   {
     "operation": "enable",
     "username": "user@example.com",
     "logLevel": "DEBUG",
     "expirationTime": 30
   }

2. Disable debug logs for a user:
   {
     "operation": "disable",
     "username": "user@example.com"
   }

3. Retrieve debug logs for a user:
   {
     "operation": "retrieve",
     "username": "user@example.com",
     "limit": 5
   }

4. Retrieve a specific log with full content:
   {
     "operation": "retrieve",
     "username": "user@example.com",
     "logId": "07L1g000000XXXXEAA0",
     "includeBody": true
   }

Notes:
- The operation must be one of: 'enable', 'disable', or 'retrieve'
- The username parameter is required for all operations
- For 'enable' operation, logLevel is optional (defaults to 'DEBUG')
- Log levels: NONE, ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST
- expirationTime is optional for 'enable' operation (minutes until expiration, defaults to 30)
- limit is optional for 'retrieve' operation (maximum number of logs to return, defaults to 10)
- logId is optional for 'retrieve' operation (to get a specific log)
- includeBody is optional for 'retrieve' operation (to include the full log content, defaults to false)
- The tool validates that the specified user exists before performing operations
- If logLevel is not specified when enabling logs, the tool will ask for clarification`,
  inputSchema: {
    type: "object",
    properties: {
      operation: {
        type: "string",
        enum: ["enable", "disable", "retrieve"],
        description: "Operation to perform on debug logs"
      },
      username: {
        type: "string",
        description: "Username of the Salesforce user"
      },
      logLevel: {
        type: "string",
        enum: ["NONE", "ERROR", "WARN", "INFO", "DEBUG", "FINE", "FINER", "FINEST"],
        description: "Log level for debug logs (required for 'enable' operation)"
      },
      expirationTime: {
        type: "number",
        description: "Minutes until the debug log configuration expires (optional, defaults to 30)"
      },
      limit: {
        type: "number",
        description: "Maximum number of logs to retrieve (optional, defaults to 10)"
      },
      logId: {
        type: "string",
        description: "ID of a specific log to retrieve (optional)"
      },
      includeBody: {
        type: "boolean",
        description: "Whether to include the full log content (optional, defaults to false)"
      }
    },
    required: ["operation", "username"]
  }
};

export interface ManageDebugLogsArgs {
  operation: 'enable' | 'disable' | 'retrieve';
  username: string;
  logLevel?: 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'FINE' | 'FINER' | 'FINEST';
  expirationTime?: number;
  limit?: number;
  logId?: string;
  includeBody?: boolean;
}

/**
 * Handles managing debug logs for Salesforce users
 * @param conn Active Salesforce connection
 * @param args Arguments for managing debug logs
 * @returns Tool response with operation results
 */
export async function handleManageDebugLogs(conn: any, args: ManageDebugLogsArgs) {
  try {
    // Validate inputs
    if (!args.username) {
      throw new Error('username is required');
    }
    
    // Determine if the input is likely a username or a full name
    const isLikelyUsername = args.username.includes('@') || !args.username.includes(' ');
    
    // Build the query based on whether the input looks like a username or a full name
    let userQuery;
    if (isLikelyUsername) {
      // Query by username
      userQuery = await conn.query(`
        SELECT Id, Username, Name, IsActive 
        FROM User 
        WHERE Username = '${args.username}'
      `);
    } else {
      // Query by full name
      userQuery = await conn.query(`
        SELECT Id, Username, Name, IsActive 
        FROM User 
        WHERE Name LIKE '%${args.username}%'
        ORDER BY LastModifiedDate DESC
        LIMIT 5
      `);
    }
    
    if (userQuery.records.length === 0) {
      // If no results with the initial query, try a more flexible search
      userQuery = await conn.query(`
        SELECT Id, Username, Name, IsActive 
        FROM User 
        WHERE Name LIKE '%${args.username}%' 
        OR Username LIKE '%${args.username}%'
        ORDER BY LastModifiedDate DESC
        LIMIT 5
      `);
      
      if (userQuery.records.length === 0) {
        return {
          content: [{ 
            type: "text", 
            text: `Error: No user found matching '${args.username}'. Please verify the username or full name and try again.` 
          }],
          isError: true,
        };
      }
      
      // If multiple users found, ask for clarification
      if (userQuery.records.length > 1) {
        let responseText = `Multiple users found matching '${args.username}'. Please specify which user by providing the exact username:\n\n`;
        
        userQuery.records.forEach((user: any) => {
          responseText += `- **${user.Name}** (${user.Username})\n`;
        });
        
        return {
          content: [{ 
            type: "text", 
            text: responseText
          }]
        };
      }
    }
    
    const user = userQuery.records[0];
    
    if (!user.IsActive) {
      return {
        content: [{ 
          type: "text", 
          text: `Warning: User '${args.username}' exists but is inactive. Debug logs may not be generated for inactive users.` 
        }]
      };
    }
    
    // Handle operations
    switch (args.operation) {
      case 'enable': {
        // If logLevel is not provided, we need to ask for it
        if (!args.logLevel) {
          return {
            content: [{ 
              type: "text", 
              text: `Please specify a log level for enabling debug logs. Valid options are: NONE, ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST.` 
            }],
            isError: true,
          };
        }
        
        // Set default expiration time if not provided
        const expirationTime = args.expirationTime || 30;
        
        // Check if a trace flag already exists for this user
        const existingTraceFlag = await conn.tooling.query(`
          SELECT Id, DebugLevelId FROM TraceFlag 
          WHERE TracedEntityId = '${user.Id}' 
          AND ExpirationDate > ${new Date().toISOString()}
        `);
        
        let traceFlagId;
        let debugLevelId;
        let operation;
        
        // Calculate expiration date
        const expirationDate = new Date();
        expirationDate.setMinutes(expirationDate.getMinutes() + expirationTime);
        
        if (existingTraceFlag.records.length > 0) {
          // Update existing trace flag
          traceFlagId = existingTraceFlag.records[0].Id;
          debugLevelId = existingTraceFlag.records[0].DebugLevelId;
          
          await conn.tooling.sobject('TraceFlag').update({
            Id: traceFlagId,
            LogType: 'USER_DEBUG',
            StartDate: new Date().toISOString(),
            ExpirationDate: expirationDate.toISOString()
          });
          operation = 'updated';
        } else {
          // Create a new debug level with the correct field names
          const debugLevelResult = await conn.tooling.sobject('DebugLevel').create({
            DeveloperName: `UserDebug_${Date.now()}`,
            MasterLabel: `User Debug ${user.Username}`,
            ApexCode: args.logLevel,
            ApexProfiling: args.logLevel,
            Callout: args.logLevel,
            Database: args.logLevel,
            System: args.logLevel,
            Validation: args.logLevel,
            Visualforce: args.logLevel,
            Workflow: args.logLevel
          });
          
          debugLevelId = debugLevelResult.id;
          
          // Create a new trace flag
          const traceFlagResult = await conn.tooling.sobject('TraceFlag').create({
            TracedEntityId: user.Id,
            DebugLevelId: debugLevelId,
            LogType: 'USER_DEBUG',
            StartDate: new Date().toISOString(),
            ExpirationDate: expirationDate.toISOString()
          });
          
          traceFlagId = traceFlagResult.id;
          operation = 'enabled';
        }
        
        return {
          content: [{ 
            type: "text", 
            text: `Successfully ${operation} debug logs for user '${args.username}'.\n\n` +
                  `**Log Level:** ${args.logLevel}\n` +
                  `**Expiration:** ${expirationDate.toLocaleString()} (${expirationTime} minutes from now)\n` +
                  `**Trace Flag ID:** ${traceFlagId}`
          }]
        };
      }
      
      case 'disable': {
        // Find all active trace flags for this user
        const traceFlags = await conn.tooling.query(`
          SELECT Id FROM TraceFlag WHERE TracedEntityId = '${user.Id}' AND ExpirationDate > ${new Date().toISOString()}
        `);
        
        if (traceFlags.records.length === 0) {
          return {
            content: [{ 
              type: "text", 
              text: `No active debug logs found for user '${args.username}'.` 
            }]
          };
        }
        
        try {
          // Delete trace flags instead of updating expiration date
          const traceFlagIds = traceFlags.records.map((tf: any) => tf.Id);
          const deleteResults = await Promise.all(
            traceFlagIds.map((id: string) => 
              conn.tooling.sobject('TraceFlag').delete(id)
            )
          );
          
          return {
            content: [{ 
              type: "text", 
              text: `Successfully disabled ${traceFlagIds.length} debug log configuration(s) for user '${args.username}' by removing them.` 
            }]
          };
        } catch (deleteError) {
          console.error('Error deleting trace flags:', deleteError);
          
          // Fallback to setting a future expiration date if delete fails
          try {
            // Set expiration date to 5 minutes in the future to satisfy Salesforce's requirement
            const nearFutureExpiration = new Date();
            nearFutureExpiration.setMinutes(nearFutureExpiration.getMinutes() + 5);
            
            const traceFlagIds = traceFlags.records.map((tf: any) => tf.Id);
            const updateResults = await Promise.all(
              traceFlagIds.map((id: string) => 
                conn.tooling.sobject('TraceFlag').update({
                  Id: id,
                  ExpirationDate: nearFutureExpiration.toISOString()
                })
              )
            );
            
            return {
              content: [{ 
                type: "text", 
                text: `Successfully disabled ${traceFlagIds.length} debug log configuration(s) for user '${args.username}'. They will expire in 5 minutes.` 
              }]
            };
          } catch (updateError) {
            console.error('Error updating trace flags:', updateError);
            throw new Error(`Could not disable debug logs: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`);
          }
        }
      }
      
      case 'retrieve': {
        // Set default limit if not provided
        const limit = args.limit || 10;
        
        // If a specific log ID is provided, retrieve that log directly
        if (args.logId) {
          try {
            // First check if the log exists
            const logQuery = await conn.tooling.query(`
              SELECT Id, LogUserId, Operation, Application, Status, LogLength, LastModifiedDate, Request
              FROM ApexLog 
              WHERE Id = '${args.logId}'
            `);
            
            if (logQuery.records.length === 0) {
              return {
                content: [{ 
                  type: "text", 
                  text: `No log found with ID '${args.logId}'.` 
                }]
              };
            }
            
            const log = logQuery.records[0];
            
            // If includeBody is true, retrieve the log body
            if (args.includeBody) {
              try {
                // Retrieve the log body
                const logBody = await conn.tooling.request({
                  method: 'GET',
                  url: `${conn.instanceUrl}/services/data/v58.0/tooling/sobjects/ApexLog/${log.Id}/Body`
                });
                
                let responseText = `**Log Details:**\n\n`;
                responseText += `- **ID:** ${log.Id}\n`;
                responseText += `- **Operation:** ${log.Operation}\n`;
                responseText += `- **Application:** ${log.Application}\n`;
                responseText += `- **Status:** ${log.Status}\n`;
                responseText += `- **Size:** ${log.LogLength} bytes\n`;
                responseText += `- **Date:** ${new Date(log.LastModifiedDate).toLocaleString()}\n\n`;
                responseText += `**Log Body:**\n\`\`\`\n${logBody}\n\`\`\`\n`;
                
                return {
                  content: [{ 
                    type: "text", 
                    text: responseText
                  }]
                };
              } catch (logError) {
                console.error('Error retrieving log body:', logError);
                return {
                  content: [{ 
                    type: "text", 
                    text: `Error retrieving log body: ${logError instanceof Error ? logError.message : String(logError)}` 
                  }],
                  isError: true
                };
              }
            } else {
              // Just return the log metadata
              let responseText = `**Log Details:**\n\n`;
              responseText += `- **ID:** ${log.Id}\n`;
              responseText += `- **Operation:** ${log.Operation}\n`;
              responseText += `- **Application:** ${log.Application}\n`;
              responseText += `- **Status:** ${log.Status}\n`;
              responseText += `- **Size:** ${log.LogLength} bytes\n`;
              responseText += `- **Date:** ${new Date(log.LastModifiedDate).toLocaleString()}\n\n`;
              responseText += `To view the full log content, add "includeBody": true to your request.`;
              
              return {
                content: [{ 
                  type: "text", 
                  text: responseText
                }]
              };
            }
          } catch (error) {
            console.error('Error retrieving log:', error);
            return {
              content: [{ 
                type: "text", 
                text: `Error retrieving log: ${error instanceof Error ? error.message : String(error)}` 
              }],
              isError: true,
            };
          }
        }
        
        // Query for logs
        const logs = await conn.tooling.query(`
          SELECT Id, LogUserId, Operation, Application, Status, LogLength, LastModifiedDate, Request
          FROM ApexLog 
          WHERE LogUserId = '${user.Id}'
          ORDER BY LastModifiedDate DESC 
          LIMIT ${limit}
        `);
        
        if (logs.records.length === 0) {
          return {
            content: [{ 
              type: "text", 
              text: `No debug logs found for user '${args.username}'.` 
            }]
          };
        }
        
        // Format log information
        let responseText = `Found ${logs.records.length} debug logs for user '${args.username}':\n\n`;
        
        for (let i = 0; i < logs.records.length; i++) {
          const log = logs.records[i];
          
          responseText += `**Log ${i + 1}**\n`;
          responseText += `- **ID:** ${log.Id}\n`;
          responseText += `- **Operation:** ${log.Operation}\n`;
          responseText += `- **Application:** ${log.Application}\n`;
          responseText += `- **Status:** ${log.Status}\n`;
          responseText += `- **Size:** ${log.LogLength} bytes\n`;
          responseText += `- **Date:** ${new Date(log.LastModifiedDate).toLocaleString()}\n\n`;
        }
        
        // Add a note about viewing specific logs with full content
        responseText += `To view a specific log with full content, use:\n\`\`\`\n`;
        responseText += `{\n`;
        responseText += `  "operation": "retrieve",\n`;
        responseText += `  "username": "${args.username}",\n`;
        responseText += `  "logId": "<LOG_ID>",\n`;
        responseText += `  "includeBody": true\n`;
        responseText += `}\n\`\`\`\n`;
        
        return {
          content: [{ 
            type: "text", 
            text: responseText
          }]
        };
      }
      
      default:
        throw new Error(`Invalid operation: ${args.operation}. Must be 'enable', 'disable', or 'retrieve'.`);
    }
  } catch (error) {
    console.error('Error managing debug logs:', error);
    return {
      content: [{ 
        type: "text", 
        text: `Error managing debug logs: ${error instanceof Error ? error.message : String(error)}` 
      }],
      isError: true,
    };
  }
}

```

