Enables retrieval of configuration metadata from SAP SuccessFactors OData APIs, allowing users to query entity metadata, Role-Based Permission (RBP) roles, role permissions, dynamic groups, and effective user permissions.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@SF-MCPList all permission roles in instance 'mycompany'"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
A production-grade Model Context Protocol server that connects Claude (or any MCP client) to SAP SuccessFactors via OData APIs. Query employee data, manage permissions, run compliance reports, and administer HR operations — all through natural language.
You: "Who on the Engineering team has a work anniversary this month?"
Claude: [calls get_anniversary_employees] Found 3 upcoming anniversaries...
- Jane Smith (5 years - milestone!) - March 12
- Bob Johnson (2 years) - March 18
- Alice Chen (10 years - milestone!) - March 25Why SF-MCP?
Challenge | SF-MCP Solution |
SAP SuccessFactors APIs are complex and verbose | 43 purpose-built tools with clean interfaces |
Building OData queries requires deep SF knowledge | Natural language — ask Claude in plain English |
Security concerns with API access | Per-request auth, input validation, audit logging |
Managing multiple SF instances | 21 data centers supported, cross-instance comparison |
API rate limits and performance | Connection pooling, response caching, rate limiting |
Tools
43 tools organized across 13 categories:
Tool | Description |
| Retrieve OData metadata for any entity |
| Discover all available OData entities |
| Compare entity config between two instances |
Tool | Description |
| List all Role-Based Permission roles |
| Get permissions for specific roles |
| Get all permissions for a user |
| Get roles assigned to a user |
| Map UI labels to permission types |
| Check if user has specific permission |
| List permission groups (dynamic groups) |
Tool | Description |
| View modification history for roles |
| View history of role assignments |
Tool | Description |
| Flexible OData queries with filtering, pagination |
| Get dropdown/picklist options |
Tool | Description |
| Complete profile with job info, manager, optional compensation |
| Find by name, department, location, or manager |
| Job history — promotions, transfers, title changes |
| Manager's team with direct/indirect reports |
Tool | Description |
| Vacation, PTO, sick leave balances |
| Team absence calendar for a date range |
| Pending/approved time-off requests |
Tool | Description |
| Job requisitions with status and hiring manager |
| Candidates by stage for a requisition |
| Recent/upcoming hires for onboarding |
Tool | Description |
| Terminated employees for exit processing |
| Incomplete profiles for compliance audits |
| Upcoming work anniversaries for recognition |
Tool | Description |
| Review form completion across the org |
| Pay breakdown with recurring/non-recurring components |
Tool | Description |
| Position with incumbent, department, FTE |
| Open positions for headcount planning |
| Org hierarchy from any position (up or down) |
Tool | Description |
| List custom MDF objects and their fields |
| Query any MDF/generic object ( |
| Query foundation objects (departments, cost centers, etc.) |
Tool | Description |
| Pending workflow items for a user or globally |
| Audit trail of approval steps |
Tool | Description |
| System alerts and notifications |
| Scheduled job run status |
| Integration Center job status |
| Rate limit usage per instance |
| Cache hit rates and entry counts |
| Clear cached responses |
Installation
Prerequisites
Python 3.10+
uv package manager
SAP SuccessFactors account with API access
Setup
git clone https://github.com/aiadiguru2025/sf-mcp.git
cd sf-mcp
uv syncQuick Start
Development mode (MCP Inspector):
uv run mcp dev main.pyStdio mode (Claude Desktop):
uv run main.pyHTTP mode (Cloud Run / remote):
PORT=8080 uv run main.pyClaude Desktop Integration
Step 1 — Find the path to uv
# macOS / Linux
which uv
# Windows (PowerShell)
Get-Command uv | Select-Object -ExpandProperty SourceStep 2 — Edit your Claude Desktop config
OS | Config path |
macOS |
|
Windows |
|
Add the sf-mcp server:
{
"mcpServers": {
"sf-mcp": {
"command": "/path/to/uv",
"args": ["--directory", "/path/to/sf-mcp", "run", "main.py"]
}
}
}Step 3 — Restart Claude Desktop
The MCP tools icon (hammer) will appear in the input area with all 43 tools available.
Note: Credentials (
auth_user_idandauth_password) are provided on each tool call — nothing is stored in the config.
Deployment
Google Cloud Run
# Build and deploy
export PROJECT_ID=your-gcp-project-id
gcloud builds submit --tag gcr.io/$PROJECT_ID/sf-mcp
gcloud run deploy sf-mcp \
--image gcr.io/$PROJECT_ID/sf-mcp \
--platform managed \
--region us-central1Then point Claude Desktop to the remote URL:
{
"mcpServers": {
"sf-mcp": {
"url": "https://sf-mcp-xxxxx-uc.a.run.app/mcp"
}
}
}Docker (local)
docker build -t sf-mcp .
docker run -p 8080:8080 sf-mcpAPI Key Protection (optional)
Set MCP_API_KEY to require authentication on the HTTP endpoint:
MCP_API_KEY=your-secret-key PORT=8080 uv run main.pyClients must then include X-API-Key: your-secret-key in requests.
Configuration
All tool parameters (data_center, environment, auth_user_id, auth_password) are provided per-request. Server-side environment variables are optional:
Rate Limiting
Variable | Default | Description |
|
| Max requests per window per instance |
|
| Window duration in seconds |
|
| Log warning at 80% usage |
|
| Seconds to wait on 429 retry |
|
| Max 429 retry attempts |
Response Caching
Variable | Default | Description |
|
| Metadata cache TTL (1 hour) |
|
| Service doc cache TTL (1 hour) |
|
| Picklist cache TTL (30 min) |
|
| Permission cache TTL (1 hour) |
|
| Default TTL (0 = disabled) |
|
| Max cache entries before eviction |
Endpoint Protection
Variable | Default | Description |
| (none) | API key for HTTP endpoint auth |
Copy .env.example to .env to customize:
cp .env.example .envSupported Data Centers
21 data centers across 6 continents with alias support:
Data Center | Alias | Location | Environments |
DC2 | DC57 | Netherlands | preview, production, sales_demo |
DC4 | DC68 | Virginia, US | preview, production, sales_demo |
DC8 | DC70 | Ashburn, Virginia, US | preview, production, sales_demo |
DC10 | DC66 | Sydney, Australia | preview, production |
DC12 | DC33 | Germany | preview, production |
DC15 | DC30 | Shanghai, China | preview, production |
DC17 | DC60 | Toronto, Canada | preview, production |
DC19 | DC62 | Sao Paulo, Brazil | preview, production |
DC22 | — | Dubai, UAE | preview, production |
DC23 | DC84 | Riyadh, Saudi Arabia | preview, production |
DC40 | — | — | sales_demo |
DC41 | — | Virginia, US | preview, production |
DC44 | DC52 | Singapore | preview, production |
DC47 | — | Canada Central | preview, production |
DC50 | — | Tokyo, Japan | preview, production |
DC55 | — | Frankfurt, Germany | preview, production |
DC74 | — | Zurich, Switzerland | preview, production |
DC80 | — | Mumbai, India | preview, production |
DC82 | — | Riyadh, Saudi Arabia | preview, production |
Architecture
sf-mcp/
├── main.py # Entry point (stdio + HTTP modes)
├── sf_mcp/
│ ├── server.py # FastMCP instance
│ ├── config.py # DC mappings, constants, env vars
│ ├── auth.py # Credential resolution, API key middleware
│ ├── client.py # HTTP client (OData, metadata, service doc, pagination)
│ ├── cache.py # TTL-based response cache with deep-copy safety
│ ├── rate_limiter.py # Sliding-window rate limiter (per-instance)
│ ├── validation.py # 10 input validators with registry pattern
│ ├── decorators.py # sf_tool decorator (cross-cutting concerns)
│ ├── dependencies.py # FastMCP DI for schema exclusion
│ ├── logging_config.py # Cloud Logging JSON formatter, audit_log()
│ ├── xml_utils.py # Safe XML parsing (defusedxml), SAP date parsing
│ └── tools/ # 43 tools across 13 modules
│ ├── configuration.py # get_configuration, compare_configurations, list_entities
│ ├── permissions.py # 7 RBP security tools
│ ├── audit.py # Role history, role assignment history
│ ├── query.py # query_odata, get_picklist_values
│ ├── employee.py # Profile, search, history, team roster
│ ├── time_off.py # Balances, upcoming absences, requests
│ ├── recruiting.py # Requisitions, pipeline, new hires
│ ├── compliance.py # Terminations, missing data, anniversaries, reviews, comp
│ ├── position.py # Position details, vacancies, org chart
│ ├── workflow.py # Pending approvals, workflow history
│ ├── mdf.py # MDF object definitions, queries, foundation objects
│ ├── monitoring.py # Alerts, scheduled jobs, integration jobs
│ ├── admin.py # Rate limit quota, cache status, cache clear
│ └── utils.py # Shared utilities (display_name)
├── tests/ # 110 tests
├── Dockerfile # Cloud Run container
├── .env.example # Configuration template
└── pyproject.toml # Project metadata, dependencies, linter configDesign Principles
Zero boilerplate — The sf_tool decorator handles request ID generation, timing, audit logging, input validation, credential checking, error handling, and $top clamping. Tool functions contain only business logic.
Secure by default — 10 input validators (regex allowlists), OData injection prevention, XXE-safe XML parsing (defusedxml), timing-safe API key comparison (hmac.compare_digest), and automatic credential masking in logs.
Production-ready — Connection pooling (requests.Session), mutation-safe response caching (deep-copy on put/get), sliding-window rate limiting with automatic 429 retry, and Cloud Logging-compatible JSON audit trail.
Schema-clean — Internal parameters (request_id, start_time, api_host) are hidden from the MCP tool schema via FastMCP's Dependency injection, keeping tool interfaces clean for LLM consumers.
Security
Layer | Mechanism |
Input validation | 10 regex-based validators; OData filter blocklist checks raw + URL-decoded + double-decoded input |
Injection prevention | Entity paths, $select, $orderby, $filter, $expand all validated; control characters rejected |
Authentication | Per-request credentials (never stored); timing-safe API key comparison via |
XML safety |
|
Audit logging | Every tool call logged with structured JSON; passwords automatically masked |
Cache safety | Deep-copied on store and retrieval to prevent mutation bugs |
Date handling | All SAP timestamp parsing uses explicit UTC to prevent timezone inconsistencies |
Testing
# Run all 110 tests
uv run pytest tests/ -v
# Run with coverage
uv run pytest tests/ --cov=sf_mcp
# Lint
uv run ruff check .
# Type check
uv run mypy sf_mcp/Test coverage includes:
Config — DC mapping resolution, case insensitivity, aliases, error cases
Validation — All 10 validators with valid/invalid inputs, injection prevention
Client — Mocked HTTP responses (200, 401, 500, empty, connection error)
Rate limiter — Limit enforcement, sliding window, per-instance isolation, thread safety
Cache — Put/get, TTL expiry, category TTLs, invalidation, eviction, deep-copy safety
Pagination — Single/multi page, max_pages limit, error handling, $skip increments
Decorators — Value injection, validation errors, max_top clamping, exception handling
API Reference
Every tool accepts these common parameters:
Parameter | Type | Required | Description |
| string | Yes | SuccessFactors company ID |
| string | Yes | SAP data center code (e.g., |
| string | Yes |
|
| string | Yes | SuccessFactors user ID (without @instance) |
| string | Yes | SuccessFactors password |
get_configuration
Retrieve OData metadata for a SuccessFactors entity.
Parameter | Type | Required | Description |
| string | Yes | OData entity name (e.g., |
list_entities
Discover all available OData entities in an instance.
Parameter | Type | Required | Description |
| string | No |
|
compare_configurations
Compare entity config between two instances (e.g., dev vs prod).
Parameter | Type | Required | Description |
| string | Yes | First instance |
| string | Yes | Second instance |
| string | Yes | Entity to compare |
| string | Yes | Data center for instance1 |
| string | Yes | Environment for instance1 |
| string | Yes | Data center for instance2 |
| string | Yes | Environment for instance2 |
get_rbp_roles
Parameter | Type | Required | Description |
| boolean | No | Include role descriptions (default: false) |
get_role_permissions
Parameter | Type | Required | Description |
| string | Yes | Single or comma-separated: |
| string | No | Locale for labels (default: |
get_user_permissions
Parameter | Type | Required | Description |
| string | Yes | Single or comma-separated: |
| string | No | Locale for labels (default: |
get_user_roles
Parameter | Type | Required | Description |
| string | Yes | User ID to look up roles for |
| boolean | No | Also fetch permissions per role (default: false) |
get_permission_metadata
Parameter | Type | Required | Description |
| string | No | Locale for labels (default: |
check_user_permission
Parameter | Type | Required | Description |
| string | Yes | User whose permission to check |
| string | Yes | Target user of the permission |
| string | Yes | Permission type from metadata |
| string | Yes | Permission string value |
| string | No | Permission long value (default: |
get_dynamic_groups
Parameter | Type | Required | Description |
| string | No | Filter by group type |
get_role_history
Parameter | Type | Required | Description |
| string | No | Filter by role ID |
| string | No | Filter by role name |
| string | No | Start date (YYYY-MM-DD) |
| string | No | End date (YYYY-MM-DD) |
| integer | No | Max records (default: 100, max: 500) |
get_role_assignment_history
Parameter | Type | Required | Description |
| string | No | Filter by role ID |
| string | No | Filter by user ID |
| string | No | Start date (YYYY-MM-DD) |
| string | No | End date (YYYY-MM-DD) |
| integer | No | Max records (default: 100, max: 500) |
At least one of
role_idoruser_idis required.
query_odata
Parameter | Type | Required | Description |
| string | Yes | Entity name or key: |
| string | No | Fields: |
| string | No | OData filter: |
| string | No | Nav properties: |
| integer | No | Max records (default: 100, max: 1000) |
| integer | No | Records to skip |
| string | No | Sort: |
| boolean | No | Auto-fetch all pages (default: false) |
| integer | No | Max pages when paginating (default: 10, max: 50) |
get_picklist_values
Parameter | Type | Required | Description |
| string | Yes | Picklist ID: |
| string | No | Locale for labels (default: |
| boolean | No | Include inactive values (default: false) |
get_employee_profile
Parameter | Type | Required | Description |
| string | Yes | Employee user ID |
| boolean | No | Include compensation (default: false) |
search_employees
Parameter | Type | Required | Description |
| string | No | Partial name search |
| string | No | Filter by department |
| string | No | Filter by location |
| string | No | Filter to manager's reports |
| string | No |
|
| integer | No | Max results (default: 50, max: 200) |
get_employee_history
Parameter | Type | Required | Description |
| string | Yes | Employee user ID |
| boolean | No | Include salary history (default: false) |
get_team_roster
Parameter | Type | Required | Description |
| string | Yes | Manager's user ID |
| boolean | No | Include reports-of-reports (default: false) |
| integer | No | Max direct reports (default: 100, max: 200) |
get_time_off_balances
Parameter | Type | Required | Description |
| string | Yes | Comma-separated user IDs (max 50) |
| string | No | Balance as of date (YYYY-MM-DD) |
get_upcoming_time_off
Parameter | Type | Required | Description |
| string | Yes | Range start (YYYY-MM-DD) |
| string | Yes | Range end (YYYY-MM-DD) |
| string | No | Filter by department |
| string | No | Filter to manager's team |
| string | No |
|
| integer | No | Max results (default: 200, max: 500) |
get_time_off_requests
Parameter | Type | Required | Description |
| string | No | Filter to employee |
| string | No |
|
| string | No | Submitted after date (YYYY-MM-DD) |
| integer | No | Max results (default: 50, max: 200) |
get_open_requisitions
Parameter | Type | Required | Description |
| string | No | Filter by department |
| string | No | Filter by hiring manager |
| string | No | Filter by location |
| string | No |
|
| integer | No | Max results (default: 100, max: 500) |
get_candidate_pipeline
Parameter | Type | Required | Description |
| string | Yes | Job requisition ID |
| boolean | No | Include rejected candidates (default: false) |
| integer | No | Max results (default: 100, max: 500) |
get_new_hires
Parameter | Type | Required | Description |
| string | Yes | Hires on/after date (YYYY-MM-DD) |
| string | Yes | Hires on/before date (YYYY-MM-DD) |
| string | No | Filter by department |
| integer | No | Max results (default: 100, max: 500) |
get_terminations
Parameter | Type | Required | Description |
| string | Yes | Range start (YYYY-MM-DD) |
| string | Yes | Range end (YYYY-MM-DD) |
| string | No | Filter by department |
| integer | No | Max results (default: 100, max: 500) |
get_employees_missing_data
Parameter | Type | Required | Description |
| string | Yes | Comma-separated: |
| string | No | Filter by department |
| integer | No | Max results (default: 100, max: 500) |
get_anniversary_employees
Parameter | Type | Required | Description |
| string | Yes | Range start (YYYY-MM-DD) |
| string | Yes | Range end (YYYY-MM-DD) |
| boolean | No | Only 1, 5, 10, 15, 20, 25+ years (default: false) |
| string | No | Filter by department |
| integer | No | Max results (default: 100, max: 500) |
get_performance_review_status
Parameter | Type | Required | Description |
| string | No | Filter by form template |
| string | No | Filter by department |
| string | No | Filter by manager |
| string | No |
|
| integer | No | Max results (default: 100, max: 500) |
get_compensation_details
Parameter | Type | Required | Description |
| string | Yes | Comma-separated user IDs (max 20) |
| string | No | Compensation as of date (YYYY-MM-DD) |
get_position_details
Parameter | Type | Required | Description |
| string | Yes | Position ID |
get_vacant_positions
Parameter | Type | Required | Description |
| string | No | Filter by department |
| string | No | Filter by location |
| integer | No | Max results (default: 100, max: 500) |
get_org_chart
Parameter | Type | Required | Description |
| string | Yes | Starting position ID |
| string | No |
|
| integer | No | Levels to traverse (default: 2, max: 5) |
get_mdf_object_definitions
Parameter | Type | Required | Description |
| string | No | Specific MDF object (e.g., |
query_mdf_object
Parameter | Type | Required | Description |
| string | Yes | MDF object name (e.g., |
| string | No | Comma-separated fields |
| string | No | OData filter |
| integer | No | Max results (default: 100, max: 500) |
| integer | No | Pagination offset |
| string | No | Sort order |
| string | No | Effective date filter (YYYY-MM-DD) |
get_foundation_objects
Parameter | Type | Required | Description |
| string | Yes |
|
| string | No | Additional OData filter |
| integer | No | Max results (default: 100, max: 500) |
| boolean | No | Include end-dated records (default: false) |
get_pending_approvals
Parameter | Type | Required | Description |
| string | No | Filter to specific approver |
| string | No | Filter to specific workflow request |
| integer | No | Max results (default: 100, max: 500) |
get_workflow_history
Parameter | Type | Required | Description |
| string | Yes | Workflow request ID |
| integer | No | Max results (default: 100, max: 500) |
get_alert_notifications
Parameter | Type | Required | Description |
| string | No | Start date (YYYY-MM-DD) |
| string | No | End date (YYYY-MM-DD) |
| integer | No | Max results (default: 100, max: 500) |
get_scheduled_job_status
Parameter | Type | Required | Description |
| string | No | Filter by job name |
| integer | No | Max results (default: 50, max: 500) |
get_integration_center_jobs
Parameter | Type | Required | Description |
| string | No | Filter by job name |
| string | No | Filter by status |
| integer | No | Max results (default: 50, max: 500) |
get_api_quota_status
Returns current rate limit usage for the specified instance.
get_cache_status
Returns cache hit rates, entry counts by category, and memory usage.
clear_cache
Parameter | Type | Required | Description |
| string | No | Clear specific instance. Empty = clear all. |
Example Queries
Ask Claude in natural language:
Query | Tool Used |
"Show me all users in the Sales department" |
|
"What permissions does jsmith have?" |
|
"Compare User config between dev and prod" |
|
"Who's on vacation next week?" |
|
"List all open job requisitions for Engineering" |
|
"How much PTO does jdoe have left?" |
|
"Show me all new hires starting in March" |
|
"Who has a 10-year anniversary this month?" |
|
"What are the status of performance reviews for my team?" |
|
"Show John's complete job history" |
|
"What custom MDF objects exist in our instance?" |
|
"List all departments with their cost centers" |
|
"Are there any pending workflow approvals?" |
|
"Check the status of our integration jobs" |
|
Common SuccessFactors Entities
Category | Entities |
Employee | User, EmpEmployment, EmpJob, PerPersonal, PerPhone, PerEmail |
Foundation | FOCompany, FODepartment, FOJobCode, FOLocation, FOPayGrade |
Position | Position, PositionEntity, PositionMatrixRelationship |
Talent | Goal, GoalPlan, PerformanceReview, Competency |
Recruiting | JobRequisition, Candidate, JobApplication |
Use list_entities to discover all available entities in your instance.
Troubleshooting
Verify credential format: user ID without
@instanceConfirm the password is correct
Ensure the API user has proper permissions in SuccessFactors Admin Center
All inputs are validated to prevent injection attacks:
Parameter | Rules |
| Alphanumeric, underscores, hyphens only |
| Valid OData entity name pattern |
| No blocked keywords ( |
| Format like |
| Valid field name patterns |
Verify
uvpath in config is correct:which uvCheck logs:
tail -f ~/Library/Logs/Claude/mcp*.logTest manually:
uv run mcp dev main.pyEnsure Python 3.10+ is installed:
python3 --version
The server auto-retries HTTP 429 responses (up to 3 times)
Use
get_api_quota_statusto check current usageIncrease limits via
SF_RATE_LIMITenvironment variableCache responses with
SF_CACHE_TTL_DEFAULTto reduce API calls
Dependencies
Package | Version | Purpose |
>=2.0.0 | Model Context Protocol SDK | |
>=2.31.0 | HTTP client with connection pooling | |
>=0.7.0 | XXE-safe XML parsing | |
>=1.0.0 | Environment variable loading | |
>=0.30.0 | ASGI server for HTTP transport |
Dev dependencies: pytest, ruff, mypy
Contributing
Contributions are welcome! Please:
Fork the repository
Create a feature branch:
git checkout -b feature/my-featureRun tests:
uv run pytest tests/ -vRun linting:
uv run ruff check .Commit your changes
Open a Pull Request
Changelog
See CHANGELOG.md for release history.
License
This project is licensed under the MIT License — see the LICENSE file for details.