MP-301f · Module 3

ServiceNow, Salesforce & SAP Connectors

4 min read

Enterprise platform connectors are the most complex MCP servers to build because the platforms themselves are complex. Salesforce has its own query language (SOQL), its own API versioning scheme, and object-level security (OLS/FLS) that varies by user profile. ServiceNow has a REST Table API with encoded query syntax, ACL-based access control, and domain separation in multi-tenant instances. SAP has RFC/BAPI function calls, a custom authentication stack (SAP Logon Tickets), and master data governance workflows. Each connector must handle these platform-specific concerns while presenting clean MCP resources.

Authentication varies dramatically across platforms. Salesforce uses OAuth 2.0 with JWT bearer flow for server-to-server communication — the MCP server holds a private key and exchanges it for an access token without user interaction. ServiceNow supports OAuth, basic auth, and mutual TLS depending on the instance configuration. SAP often requires X.509 certificates or SAP Passport tokens. Each connector must handle token refresh, credential rotation, and authentication failures gracefully — a token expiry should not crash the MCP server, it should trigger a re-authentication flow transparently.

Rate limiting is platform-specific and must be handled at the connector level. Salesforce limits API calls per 24-hour rolling window (varies by edition, typically 15,000-100,000). ServiceNow limits concurrent requests and total daily transactions. SAP limits concurrent RFC connections. The MCP server must track its consumption against these limits, queue requests when approaching the ceiling, and return informative errors when limits are exceeded — not raw HTTP 429 responses.

import jsforce from "jsforce";

// Salesforce connector with JWT bearer auth
const conn = new jsforce.Connection({
  loginUrl: process.env.SF_LOGIN_URL,
  instanceUrl: process.env.SF_INSTANCE_URL,
});

// Auto-refresh on token expiry
conn.on("refresh", (accessToken) => {
  console.log("Salesforce token refreshed");
});

// MCP resource — Salesforce Account by ID
server.resource(
  "sf-account",
  new ResourceTemplate("salesforce://accounts/{accountId}", { list: undefined }),
  async (uri, { accountId }) => {
    try {
      const account = await conn.sobject("Account").retrieve(accountId);
      // Strip system fields and apply field-level security
      const safe = filterFields(account, ALLOWED_ACCOUNT_FIELDS);
      return {
        contents: [{
          uri: uri.href,
          mimeType: "application/json",
          text: JSON.stringify(withLineage(safe, {
            source: "salesforce",
            sourceRecordId: accountId,
            readTimestamp: new Date().toISOString(),
            transformations: ["field_filter"],
            classification: "confidential",
          })),
        }],
      };
    } catch (err: unknown) {
      if ((err as { errorCode?: string }).errorCode === "NOT_FOUND") {
        return { contents: [{ uri: uri.href, text: "null" }] };
      }
      throw err;
    }
  }
);

// SOQL-based search resource
server.resource(
  "sf-search",
  new ResourceTemplate("salesforce://search/{sobject}{?where,limit}", {
    list: undefined,
  }),
  async (uri, { sobject, where, limit }) => {
    if (!ALLOWED_SOBJECTS.includes(sobject)) {
      throw new Error(`SObject ${sobject} is not exposed`);
    }
    const fields = ALLOWED_FIELDS[sobject].join(", ");
    const soql = `SELECT ${fields} FROM ${sobject}` +
      (where ? ` WHERE ${where}` : "") +
      ` LIMIT ${Math.min(Number(limit) || 100, 500)}`;
    const result = await conn.query(soql);
    return {
      contents: [{ uri: uri.href, mimeType: "application/json",
        text: JSON.stringify(result.records) }],
    };
  }
);
  1. Choose authentication flow Salesforce: JWT bearer (server-to-server). ServiceNow: OAuth client credentials. SAP: X.509 or SAP Passport. Configure token refresh and handle expiry transparently.
  2. Implement rate limit tracking Track API consumption against platform limits. Queue requests when approaching the ceiling. Return clear error messages when limits are exceeded.
  3. Apply platform-specific field filtering Respect the platform's native security model — Salesforce FLS, ServiceNow ACLs, SAP authorization objects. Never expose fields the service account should not access.