SF-MCP
<p align="center">
<h1 align="center">SuccessFactors-MCP</h1>
<p align="center">
<strong>SAP SuccessFactors Model Context Protocol Server</strong>
</p>
<p align="center">
<a href="#installation">Installation</a> ·
<a href="#quick-start">Quick Start</a> ·
<a href="#tools">62 Tools</a> ·
<a href="#deployment">Deployment</a> ·
<a href="#api-reference">API Reference</a>
</p>
</p>
<p align="center">
<a href="https://github.com/aiadiguru2025/sf-mcp/releases"><img alt="Version" src="https://img.shields.io/badge/version-0.3.2-blue.svg" /></a>
<a href="https://www.python.org/downloads/"><img alt="Python" src="https://img.shields.io/badge/python-3.10%2B-blue.svg" /></a>
<a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-green.svg" /></a>
<a href="https://modelcontextprotocol.io"><img alt="MCP" src="https://img.shields.io/badge/MCP-compatible-purple.svg" /></a>
</p>
---
A production-grade [Model Context Protocol](https://modelcontextprotocol.io) 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 25
```
## Why SF-MCP?
| Challenge | SF-MCP Solution |
|-----------|-----------------|
| SAP SuccessFactors APIs are complex and verbose | **62 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
62 tools organized across 16 categories:
<details>
<summary><strong>Configuration & Discovery</strong> (3 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_configuration` | Retrieve OData metadata for any entity |
| `list_entities` | Discover all available OData entities |
| `compare_configurations` | Compare entity config between two instances |
</details>
<details>
<summary><strong>RBP Security</strong> (7 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_rbp_roles` | List all Role-Based Permission roles |
| `get_role_permissions` | Get permissions for specific roles |
| `get_user_permissions` | Get all permissions for a user |
| `get_user_roles` | Get roles assigned to a user |
| `get_permission_metadata` | Map UI labels to permission types |
| `check_user_permission` | Check if user has specific permission |
| `get_dynamic_groups` | List permission groups (dynamic groups) |
</details>
<details>
<summary><strong>Security & Audit</strong> (6 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_role_history` | View modification history for roles |
| `get_role_assignment_history` | View history of role assignments |
| `get_login_audit_log` | Login events — who logged in, when, from where |
| `get_admin_audit_log` | Admin-level config/data change history |
| `get_sod_violations` | Detect segregation-of-duty conflicts in RBP assignments |
| `get_dormant_users` | Active accounts with no recent login activity |
</details>
<details>
<summary><strong>Data Query</strong> (2 tools)</summary>
| Tool | Description |
|------|-------------|
| `query_odata` | Flexible OData queries with filtering, pagination |
| `get_picklist_values` | Get dropdown/picklist options |
</details>
<details>
<summary><strong>Employee Lookup</strong> (4 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_employee_profile` | Complete profile with job info, manager, optional compensation |
| `search_employees` | Find by name, department, location, or manager |
| `get_employee_history` | Job history — promotions, transfers, title changes |
| `get_team_roster` | Manager's team with direct/indirect reports |
</details>
<details>
<summary><strong>Time Off</strong> (3 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_time_off_balances` | Vacation, PTO, sick leave balances |
| `get_upcoming_time_off` | Team absence calendar for a date range |
| `get_time_off_requests` | Pending/approved time-off requests |
</details>
<details>
<summary><strong>Hiring & Onboarding</strong> (3 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_open_requisitions` | Job requisitions with status and hiring manager |
| `get_candidate_pipeline` | Candidates by stage for a requisition |
| `get_new_hires` | Recent/upcoming hires for onboarding |
</details>
<details>
<summary><strong>Compliance & Reporting</strong> (3 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_terminations` | Terminated employees for exit processing |
| `get_employees_missing_data` | Incomplete profiles for compliance audits |
| `get_anniversary_employees` | Upcoming work anniversaries for recognition |
</details>
<details>
<summary><strong>Performance & Compensation</strong> (5 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_performance_review_status` | Review form completion across the org |
| `get_compensation_details` | Pay breakdown with recurring/non-recurring components |
| `get_compensation_history` | Full compensation change history (not just latest) |
| `get_compensation_review_status` | Comp planning worksheet completion by manager/department |
| `get_salary_range_analysis` | Compa-ratio: pay vs. grade midpoint |
</details>
<details>
<summary><strong>Performance & Talent</strong> (4 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_goal_summary` | Employee goals — category, weight, completion |
| `get_development_plans` | Development goals and learning activities |
| `get_talent_flags` | Potential, flight risk, impact of loss, key position |
| `get_succession_nominees` | Talent pool nominees for key positions |
</details>
<details>
<summary><strong>Position Management</strong> (3 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_position_details` | Position with incumbent, department, FTE |
| `get_vacant_positions` | Open positions for headcount planning |
| `get_org_chart` | Org hierarchy from any position (up or down) |
</details>
<details>
<summary><strong>MDF Objects</strong> (3 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_mdf_object_definitions` | List custom MDF objects and their fields |
| `query_mdf_object` | Query any MDF/generic object (`cust_*`) |
| `get_foundation_objects` | Query foundation objects (departments, cost centers, etc.) |
</details>
<details>
<summary><strong>Workflow</strong> (2 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_pending_approvals` | Pending workflow items for a user or globally |
| `get_workflow_history` | Audit trail of approval steps |
</details>
<details>
<summary><strong>Monitoring & Admin</strong> (6 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_alert_notifications` | System alerts and notifications |
| `get_scheduled_job_status` | Scheduled job run status |
| `get_integration_center_jobs` | Integration Center job status |
| `get_api_quota_status` | Rate limit usage per instance |
| `get_cache_status` | Cache hit rates and entry counts |
| `clear_cache` | Clear cached responses |
</details>
<details>
<summary><strong>Employee Central</strong> (5 tools)</summary>
| Tool | Description |
|------|-------------|
| `get_global_assignments` | International assignments — home/host details |
| `get_employee_documents` | Documents attached to an employee's record |
| `get_pay_component_groups` | Recurring pay elements (allowances, bonuses) |
| `get_work_permit_expiry` | Work permits/visas expiring soon |
| `get_probation_end_dates` | Employees approaching end of probation |
</details>
<details>
<summary><strong>Data Management</strong> (3 tools)</summary>
| Tool | Description |
|------|-------------|
| `bulk_export_employees` | Paginated full export of active employees |
| `get_picklist_usage` | Which picklists are used on which entity fields |
| `get_country_specific_fields` | Field population rates for a given country |
</details>
## Installation
### Prerequisites
- **Python 3.10+**
- **[uv](https://docs.astral.sh/uv/)** package manager
- SAP SuccessFactors account with API access
### Setup
```bash
git clone https://github.com/aiadiguru2025/sf-mcp.git
cd sf-mcp
uv sync
```
### Quick Start
**Development mode** (MCP Inspector):
```bash
uv run mcp dev main.py
```
**Stdio mode** (Claude Desktop):
```bash
uv run main.py
```
**HTTP mode** (Cloud Run / remote):
```bash
PORT=8080 uv run main.py
```
## Claude Desktop Integration
### Step 1 — Find the path to `uv`
```bash
# macOS / Linux
which uv
# Windows (PowerShell)
Get-Command uv | Select-Object -ExpandProperty Source
```
### Step 2 — Edit your Claude Desktop config
| OS | Config path |
|----|------------|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
Add the sf-mcp server:
```json
{
"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 62 tools available.
> **Note:** Credentials (`auth_user_id` and `auth_password`) are provided on each tool call — nothing is stored in the config.
## Deployment
### Google Cloud Run
```bash
# 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-central1
```
Then point Claude Desktop to the remote URL:
```json
{
"mcpServers": {
"sf-mcp": {
"url": "https://sf-mcp-xxxxx-uc.a.run.app/mcp"
}
}
}
```
### Docker (local)
```bash
docker build -t sf-mcp .
docker run -p 8080:8080 sf-mcp
```
### API Key Protection (optional)
Set `MCP_API_KEY` to require authentication on the HTTP endpoint:
```bash
MCP_API_KEY=your-secret-key PORT=8080 uv run main.py
```
Clients 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 |
|----------|---------|-------------|
| `SF_RATE_LIMIT` | `100` | Max requests per window per instance |
| `SF_RATE_LIMIT_WINDOW` | `60` | Window duration in seconds |
| `SF_RATE_LIMIT_WARN_THRESHOLD` | `0.8` | Log warning at 80% usage |
| `SF_RATE_LIMIT_RETRY_AFTER` | `5` | Seconds to wait on 429 retry |
| `SF_RATE_LIMIT_MAX_RETRIES` | `3` | Max 429 retry attempts |
### Response Caching
| Variable | Default | Description |
|----------|---------|-------------|
| `SF_CACHE_TTL_METADATA` | `3600` | Metadata cache TTL (1 hour) |
| `SF_CACHE_TTL_SERVICE_DOC` | `3600` | Service doc cache TTL (1 hour) |
| `SF_CACHE_TTL_PICKLIST` | `1800` | Picklist cache TTL (30 min) |
| `SF_CACHE_TTL_PERMISSIONS` | `3600` | Permission cache TTL (1 hour) |
| `SF_CACHE_TTL_DEFAULT` | `0` | Default TTL (0 = disabled) |
| `SF_CACHE_MAX_ENTRIES` | `1000` | Max cache entries before eviction |
### Endpoint Protection
| Variable | Default | Description |
|----------|---------|-------------|
| `MCP_API_KEY` | *(none)* | API key for HTTP endpoint auth |
Copy `.env.example` to `.env` to customize:
```bash
cp .env.example .env
```
## Supported 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/ # 62 tools across 16 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 config
```
### Design 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 `hmac.compare_digest` |
| **XML safety** | `defusedxml` prevents XXE, entity expansion, and DTD attacks |
| **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
```bash
# 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 |
|-----------|------|----------|-------------|
| `instance` | string | Yes | SuccessFactors company ID |
| `data_center` | string | Yes | SAP data center code (e.g., `DC55`, `DC10`) |
| `environment` | string | Yes | `preview`, `production`, or `sales_demo` |
| `auth_user_id` | string | Yes | SuccessFactors user ID (without @instance) |
| `auth_password` | string | Yes | SuccessFactors password |
<details>
<summary><strong>Configuration Tools</strong></summary>
#### `get_configuration`
Retrieve OData metadata for a SuccessFactors entity.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `entity` | string | Yes | OData entity name (e.g., `User`, `Position`) |
#### `list_entities`
Discover all available OData entities in an instance.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `category` | string | No | `foundation`, `employee`, `talent`, `platform`, or `all` |
#### `compare_configurations`
Compare entity config between two instances (e.g., dev vs prod).
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `instance1` | string | Yes | First instance |
| `instance2` | string | Yes | Second instance |
| `entity` | string | Yes | Entity to compare |
| `data_center1` | string | Yes | Data center for instance1 |
| `environment1` | string | Yes | Environment for instance1 |
| `data_center2` | string | Yes | Data center for instance2 |
| `environment2` | string | Yes | Environment for instance2 |
</details>
<details>
<summary><strong>RBP Security Tools</strong></summary>
#### `get_rbp_roles`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `include_description` | boolean | No | Include role descriptions (default: false) |
#### `get_role_permissions`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `role_ids` | string | Yes | Single or comma-separated: `10` or `10,20,30` |
| `locale` | string | No | Locale for labels (default: `en-US`) |
#### `get_user_permissions`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_ids` | string | Yes | Single or comma-separated: `admin` or `admin,user2` |
| `locale` | string | No | Locale for labels (default: `en-US`) |
#### `get_user_roles`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | Yes | User ID to look up roles for |
| `include_permissions` | boolean | No | Also fetch permissions per role (default: false) |
#### `get_permission_metadata`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `locale` | string | No | Locale for labels (default: `en-US`) |
#### `check_user_permission`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `access_user_id` | string | Yes | User whose permission to check |
| `target_user_id` | string | Yes | Target user of the permission |
| `perm_type` | string | Yes | Permission type from metadata |
| `perm_string_value` | string | Yes | Permission string value |
| `perm_long_value` | string | No | Permission long value (default: `-1L`) |
#### `get_dynamic_groups`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `group_type` | string | No | Filter by group type |
</details>
<details>
<summary><strong>Security & Audit Tools</strong></summary>
#### `get_role_history`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `role_id` | string | No | Filter by role ID |
| `role_name` | string | No | Filter by role name |
| `from_date` | string | No | Start date (YYYY-MM-DD) |
| `to_date` | string | No | End date (YYYY-MM-DD) |
| `top` | integer | No | Max records (default: 100, max: 500) |
#### `get_role_assignment_history`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `role_id` | string | No | Filter by role ID |
| `user_id` | string | No | Filter by user ID |
| `from_date` | string | No | Start date (YYYY-MM-DD) |
| `to_date` | string | No | End date (YYYY-MM-DD) |
| `top` | integer | No | Max records (default: 100, max: 500) |
> At least one of `role_id` or `user_id` is required.
#### `get_login_audit_log`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | No | Filter to a single user's login history |
| `from_date` | string | No | Start date (YYYY-MM-DD) |
| `to_date` | string | No | End date (YYYY-MM-DD) |
| `top` | integer | No | Max records (default: 100, max: 500) |
> Requires the instance's Audit Trail / Login Tracking feature to be provisioned.
#### `get_admin_audit_log`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `entity_name` | string | No | Filter by entity/object name (e.g., `RBPRole`, `User`) |
| `changed_by` | string | No | Filter by the admin who made the change |
| `from_date` | string | No | Start date (YYYY-MM-DD) |
| `to_date` | string | No | End date (YYYY-MM-DD) |
| `top` | integer | No | Max records (default: 100, max: 500) |
> Requires the instance's Audit Trail feature to be provisioned.
#### `get_sod_violations`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_ids` | string | Yes | Employee user ID(s), comma-separated (max 20) |
| `permission_pairs` | string | No | Custom conflict pairs: `"PermA\|PermB,PermC\|PermD"`. Defaults to a built-in list of common RBP conflicts |
#### `get_dormant_users`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `dormant_days` | integer | No | Flag users with no login in this many days (default: 90) |
| `department` | string | No | Filter by department |
| `top` | integer | No | Max records (default: 100, max: 500) |
</details>
<details>
<summary><strong>Data Query Tools</strong></summary>
#### `query_odata`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `entity` | string | Yes | Entity name or key: `User` or `User('admin')` |
| `select` | string | No | Fields: `userId,firstName,lastName` |
| `filter` | string | No | OData filter: `status eq 'active'` |
| `expand` | string | No | Nav properties: `empInfo,jobInfoNav` |
| `top` | integer | No | Max records (default: 100, max: 1000) |
| `skip` | integer | No | Records to skip |
| `orderby` | string | No | Sort: `lastName asc` |
| `paginate` | boolean | No | Auto-fetch all pages (default: false) |
| `max_pages` | integer | No | Max pages when paginating (default: 10, max: 50) |
#### `get_picklist_values`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `picklist_id` | string | Yes | Picklist ID: `ecJobFunction`, `nationality` |
| `locale` | string | No | Locale for labels (default: `en-US`) |
| `include_inactive` | boolean | No | Include inactive values (default: false) |
</details>
<details>
<summary><strong>Employee Lookup Tools</strong></summary>
#### `get_employee_profile`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | Yes | Employee user ID |
| `include_compensation` | boolean | No | Include compensation (default: false) |
#### `search_employees`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `search_text` | string | No | Partial name search |
| `department` | string | No | Filter by department |
| `location` | string | No | Filter by location |
| `manager_id` | string | No | Filter to manager's reports |
| `status` | string | No | `active`, `inactive`, or `all` (default: `active`) |
| `top` | integer | No | Max results (default: 50, max: 200) |
#### `get_employee_history`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | Yes | Employee user ID |
| `include_compensation_changes` | boolean | No | Include salary history (default: false) |
#### `get_team_roster`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `manager_id` | string | Yes | Manager's user ID |
| `include_indirect_reports` | boolean | No | Include reports-of-reports (default: false) |
| `top` | integer | No | Max direct reports (default: 100, max: 200) |
</details>
<details>
<summary><strong>Time Off Tools</strong></summary>
#### `get_time_off_balances`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_ids` | string | Yes | Comma-separated user IDs (max 50) |
| `as_of_date` | string | No | Balance as of date (YYYY-MM-DD) |
#### `get_upcoming_time_off`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `start_date` | string | Yes | Range start (YYYY-MM-DD) |
| `end_date` | string | Yes | Range end (YYYY-MM-DD) |
| `department` | string | No | Filter by department |
| `manager_id` | string | No | Filter to manager's team |
| `status` | string | No | `approved`, `pending`, or `all` (default: `approved`) |
| `top` | integer | No | Max results (default: 200, max: 500) |
#### `get_time_off_requests`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | No | Filter to employee |
| `status` | string | No | `pending`, `approved`, `rejected`, `cancelled`, or `all` (default: `pending`) |
| `from_date` | string | No | Submitted after date (YYYY-MM-DD) |
| `top` | integer | No | Max results (default: 50, max: 200) |
</details>
<details>
<summary><strong>Hiring & Onboarding Tools</strong></summary>
#### `get_open_requisitions`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `department` | string | No | Filter by department |
| `hiring_manager_id` | string | No | Filter by hiring manager |
| `location` | string | No | Filter by location |
| `status` | string | No | `open`, `filled`, `closed`, or `all` (default: `open`) |
| `top` | integer | No | Max results (default: 100, max: 500) |
#### `get_candidate_pipeline`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `requisition_id` | string | Yes | Job requisition ID |
| `include_rejected` | boolean | No | Include rejected candidates (default: false) |
| `top` | integer | No | Max results (default: 100, max: 500) |
#### `get_new_hires`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `start_date_from` | string | Yes | Hires on/after date (YYYY-MM-DD) |
| `start_date_to` | string | Yes | Hires on/before date (YYYY-MM-DD) |
| `department` | string | No | Filter by department |
| `top` | integer | No | Max results (default: 100, max: 500) |
</details>
<details>
<summary><strong>Compliance & Reporting Tools</strong></summary>
#### `get_terminations`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `from_date` | string | Yes | Range start (YYYY-MM-DD) |
| `to_date` | string | Yes | Range end (YYYY-MM-DD) |
| `department` | string | No | Filter by department |
| `top` | integer | No | Max results (default: 100, max: 500) |
#### `get_employees_missing_data`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `check_fields` | string | Yes | Comma-separated: `email`, `phone`, `address`, `emergency_contact` |
| `department` | string | No | Filter by department |
| `top` | integer | No | Max results (default: 100, max: 500) |
#### `get_anniversary_employees`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `from_date` | string | Yes | Range start (YYYY-MM-DD) |
| `to_date` | string | Yes | Range end (YYYY-MM-DD) |
| `milestone_years_only` | boolean | No | Only 1, 5, 10, 15, 20, 25+ years (default: false) |
| `department` | string | No | Filter by department |
| `top` | integer | No | Max results (default: 100, max: 500) |
</details>
<details>
<summary><strong>Performance & Compensation Tools</strong></summary>
#### `get_performance_review_status`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `form_template_id` | string | No | Filter by form template |
| `department` | string | No | Filter by department |
| `manager_id` | string | No | Filter by manager |
| `status` | string | No | `not_started`, `in_progress`, `completed`, or `""` for all |
| `top` | integer | No | Max results (default: 100, max: 500) |
#### `get_compensation_details`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_ids` | string | Yes | Comma-separated user IDs (max 20) |
| `effective_date` | string | No | Compensation as of date (YYYY-MM-DD) |
#### `get_compensation_history`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | Yes | The employee's user ID |
| `top` | integer | No | Max history records (default: 50, max: 200) |
#### `get_compensation_review_status`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `form_template_id` | string | Yes | The compensation worksheet's form template ID |
| `department` | string | No | Filter by department |
| `manager_id` | string | No | Filter by manager |
| `status` | string | No | `not_started`, `in_progress`, `completed`, or `""` for all |
| `top` | integer | No | Max results (default: 100, max: 500) |
#### `get_salary_range_analysis`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_ids` | string | Yes | Comma-separated user IDs (max 20) |
> Returns compa-ratio (current salary ÷ grade midpoint × 100) per employee.
</details>
<details>
<summary><strong>Performance & Talent Tools</strong></summary>
#### `get_goal_summary`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | Yes | The employee's user ID |
| `status` | string | No | `not_started`, `in_progress`, `completed`, or `""` for all |
| `top` | integer | No | Max results (default: 100, max: 200) |
#### `get_development_plans`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | Yes | The employee's user ID |
| `status` | string | No | `not_started`, `in_progress`, `completed`, or `""` for all |
| `top` | integer | No | Max results (default: 100, max: 200) |
#### `get_talent_flags`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_ids` | string | Yes | Comma-separated user IDs (max 20) |
> Field availability depends on the instance's Succession Data Model configuration.
#### `get_succession_nominees`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `pool_id` | string | No | Filter to nominees for a specific talent pool |
| `user_id` | string | No | Filter to pools an employee is nominated to |
| `top` | integer | No | Max results (default: 100, max: 200) |
</details>
<details>
<summary><strong>Position Management Tools</strong></summary>
#### `get_position_details`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `position_id` | string | Yes | Position ID |
#### `get_vacant_positions`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `department` | string | No | Filter by department |
| `location` | string | No | Filter by location |
| `top` | integer | No | Max results (default: 100, max: 500) |
#### `get_org_chart`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `position_id` | string | Yes | Starting position ID |
| `direction` | string | No | `down` or `up` (default: `down`) |
| `levels` | integer | No | Levels to traverse (default: 2, max: 5) |
</details>
<details>
<summary><strong>MDF Object Tools</strong></summary>
#### `get_mdf_object_definitions`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `object_name` | string | No | Specific MDF object (e.g., `cust_myObject`). Empty = list all. |
#### `query_mdf_object`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `object_name` | string | Yes | MDF object name (e.g., `cust_myObject`) |
| `select` | string | No | Comma-separated fields |
| `filter` | string | No | OData filter |
| `top` | integer | No | Max results (default: 100, max: 500) |
| `skip` | integer | No | Pagination offset |
| `orderby` | string | No | Sort order |
| `effective_date` | string | No | Effective date filter (YYYY-MM-DD) |
#### `get_foundation_objects`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `object_type` | string | Yes | `company`, `department`, `division`, `location`, `cost_center`, `job_code`, `job_function`, `pay_grade`, `pay_group`, `business_unit`, `event_reason`, `legal_entity` |
| `filter` | string | No | Additional OData filter |
| `top` | integer | No | Max results (default: 100, max: 500) |
| `include_inactive` | boolean | No | Include end-dated records (default: false) |
</details>
<details>
<summary><strong>Workflow Tools</strong></summary>
#### `get_pending_approvals`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | No | Filter to specific approver |
| `wf_request_id` | string | No | Filter to specific workflow request |
| `top` | integer | No | Max results (default: 100, max: 500) |
#### `get_workflow_history`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `wf_request_id` | string | Yes | Workflow request ID |
| `top` | integer | No | Max results (default: 100, max: 500) |
</details>
<details>
<summary><strong>Monitoring & Admin Tools</strong></summary>
#### `get_alert_notifications`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `from_date` | string | No | Start date (YYYY-MM-DD) |
| `to_date` | string | No | End date (YYYY-MM-DD) |
| `top` | integer | No | Max results (default: 100, max: 500) |
#### `get_scheduled_job_status`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `job_name` | string | No | Filter by job name |
| `top` | integer | No | Max results (default: 50, max: 500) |
#### `get_integration_center_jobs`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `job_name` | string | No | Filter by job name |
| `status` | string | No | Filter by status |
| `top` | 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 |
|-----------|------|----------|-------------|
| `target_instance` | string | No | Clear specific instance. Empty = clear all. |
</details>
<details>
<summary><strong>Employee Central Tools</strong></summary>
#### `get_global_assignments`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | No | Filter to a single employee's assignment history |
| `active_only` | boolean | No | Only currently active assignments (default: true) |
| `top` | integer | No | Max results (default: 100, max: 200) |
#### `get_employee_documents`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | Yes | The employee's user ID |
| `document_type` | string | No | Filter by document type |
| `top` | integer | No | Max results (default: 50, max: 100) |
#### `get_pay_component_groups`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | string | Yes | The employee's user ID |
| `effective_date` | string | No | Components as of date (YYYY-MM-DD) |
#### `get_work_permit_expiry`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `within_days` | integer | No | Flag permits/visas expiring within N days (default: 90) |
| `country` | string | No | Filter by ISO country code |
| `top` | integer | No | Max results (default: 100, max: 500) |
#### `get_probation_end_dates`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `within_days` | integer | No | Flag probation periods ending within N days (default: 30) |
| `department` | string | No | Filter by department |
| `top` | integer | No | Max results (default: 100, max: 500) |
</details>
<details>
<summary><strong>Data Management Tools</strong></summary>
#### `bulk_export_employees`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `select` | string | No | Comma-separated fields. Defaults to a standard field set |
| `department` | string | No | Filter by department |
| `status` | string | No | `active`, `inactive`, or `all` (default: `active`) |
| `top` | integer | No | Records per page (default: 500, max: 1000) |
| `max_pages` | integer | No | Max pages to fetch (default: 10, max: 50) |
#### `get_picklist_usage`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `entities` | string | No | Comma-separated entity names to scan. Defaults to a common set |
#### `get_country_specific_fields`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `entity` | string | Yes | OData entity to analyze (e.g., `PerPersonal`, `PerNationalId`) |
| `country` | string | Yes | ISO country code to filter the sample |
| `sample_size` | integer | No | Records to sample for population rates (default: 50, max: 200) |
</details>
## Example Queries
Ask Claude in natural language:
| Query | Tool Used |
|-------|-----------|
| "Show me all users in the Sales department" | `search_employees` |
| "What permissions does jsmith have?" | `get_user_permissions` |
| "Compare User config between dev and prod" | `compare_configurations` |
| "Who's on vacation next week?" | `get_upcoming_time_off` |
| "List all open job requisitions for Engineering" | `get_open_requisitions` |
| "How much PTO does jdoe have left?" | `get_time_off_balances` |
| "Show me all new hires starting in March" | `get_new_hires` |
| "Who has a 10-year anniversary this month?" | `get_anniversary_employees` |
| "What are the status of performance reviews for my team?" | `get_performance_review_status` |
| "Show John's complete job history" | `get_employee_history` |
| "What custom MDF objects exist in our instance?" | `get_mdf_object_definitions` |
| "List all departments with their cost centers" | `get_foundation_objects` |
| "Are there any pending workflow approvals?" | `get_pending_approvals` |
| "Check the status of our integration jobs" | `get_integration_center_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
<details>
<summary><strong>Authentication Errors (HTTP 401)</strong></summary>
- Verify credential format: user ID **without** `@instance`
- Confirm the password is correct
- Ensure the API user has proper permissions in SuccessFactors Admin Center
</details>
<details>
<summary><strong>Validation Errors</strong></summary>
All inputs are validated to prevent injection attacks:
| Parameter | Rules |
|-----------|-------|
| `instance` | Alphanumeric, underscores, hyphens only |
| `entity` | Valid OData entity name pattern |
| `filter` | No blocked keywords (`$batch`, `$metadata`, `<script>`, etc.) |
| `locale` | Format like `en-US` or `de` |
| `select` / `orderby` | Valid field name patterns |
</details>
<details>
<summary><strong>Server Disconnected (Claude Desktop)</strong></summary>
1. Verify `uv` path in config is correct: `which uv`
2. Check logs: `tail -f ~/Library/Logs/Claude/mcp*.log`
3. Test manually: `uv run mcp dev main.py`
4. Ensure Python 3.10+ is installed: `python3 --version`
</details>
<details>
<summary><strong>Rate Limit Errors</strong></summary>
- The server auto-retries HTTP 429 responses (up to 3 times)
- Use `get_api_quota_status` to check current usage
- Increase limits via `SF_RATE_LIMIT` environment variable
- Cache responses with `SF_CACHE_TTL_DEFAULT` to reduce API calls
</details>
## Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| [fastmcp](https://gofastmcp.com) | >=2.0.0 | Model Context Protocol SDK |
| [requests](https://requests.readthedocs.io) | >=2.31.0 | HTTP client with connection pooling |
| [defusedxml](https://github.com/tiran/defusedxml) | >=0.7.0 | XXE-safe XML parsing |
| [python-dotenv](https://github.com/theskumar/python-dotenv) | >=1.0.0 | Environment variable loading |
| [uvicorn](https://www.uvicorn.org) | >=0.30.0 | ASGI server for HTTP transport |
**Dev dependencies:** pytest, ruff, mypy
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch: `git checkout -b feature/my-feature`
3. Run tests: `uv run pytest tests/ -v`
4. Run linting: `uv run ruff check .`
5. Commit your changes
6. Open a Pull Request
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for release history.
## License
This project is licensed under the **MIT License** — see the [LICENSE](LICENSE) file for details.
---
<p align="center">
Built with <a href="https://gofastmcp.com">FastMCP</a> ·
Powered by <a href="https://modelcontextprotocol.io">Model Context Protocol</a>
</p>
TDQS
Scored across 29 tools
Most tools have distinct purposes targeting specific HR/employee management functions like permissions, hiring, time-off, and employee data, with clear boundaries. However, some overlap exists between get_user_permissions and check_user_permission, and between get_employee_profile and get_employee_history, which could cause minor confusion.
All tool names follow a consistent verb_noun pattern using snake_case, such as get_employee_profile, check_user_permission, and compare_configurations. This uniformity makes the tool set predictable and easy to navigate.
With 29 tools, the count is borderline high for an HR system server, potentially overwhelming for agents. While it covers many aspects of SuccessFactors, it feels heavy and could benefit from consolidation or better scoping to reduce complexity.
The tool set provides comprehensive coverage for HR operations, including employee data, permissions, hiring, time-off, and configuration. Minor gaps exist, such as lack of create/update/delete tools for CRUD operations, but agents can work around this using the flexible query_odata tool.