successfactors-mcp
Provides integration with SAP SuccessFactors, enabling API troubleshooting, payload extraction, and integration development through Employee Central SFAPI (Compound Employee SOAP) and OData v2 endpoints, including employee lookups, delta extracts, pagination, and tenant key management.
Click on "Deploy 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., "@successfactors-mcpPull a delta extract of employees since April 1st"
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.
SuccessFactors Toolkit
A self-hosted toolkit for SAP SuccessFactors API troubleshooting, payload extraction, and integration development, exposed as a REST API and as an MCP (Model Context Protocol) server.
API | Protocol | Endpoint prefix |
EC SFAPI — Compound Employee | SOAP 1.1 |
|
OData | REST (v2) |
|
This is an independent project, not affiliated with or endorsed by SAP SE. SAP and SuccessFactors are trademarks of SAP SE.
Before you start: it's fail-closed
The REST API refuses every /api/* request with 503 until you set
API_KEY, and refuses every /api/tenants/* request with 503 until you
also set ADMIN_API_KEY. There is no "works out of the box, insecure"
mode — set both before you can call anything:
cp .env.example .env
# edit .env: set API_KEY and ADMIN_API_KEY to independent random valuesRequests then authenticate with an X-API-Key header (all /api/* routes)
and, for /api/tenants/*, an additional X-Admin-Key header. CORS_ORIGINS
is a JSON list of allowed browser origins and defaults to [] (closed).
The project ships no SuccessFactors credentials. See Connect to SuccessFactors below to generate your own key pair and register it with your tenant.
Related MCP server: @belal-elsabbagh-apex/copilot-mcp
Install
Requires Python 3.12+.
pip install .
# or, for development:
pip install -e ".[dev]"Run the REST API
uvicorn successfactors_toolkit.main:app --host 127.0.0.1 --port 8000Interactive docs (Swagger UI): http://127.0.0.1:8000/docs.
Run with Docker
docker compose up --buildBinds to 127.0.0.1:8000 by default (see docker-compose.yml). Tenant keys
are stored in a named volume mounted at TENANT_KEYS_DIR=/data/tenants
inside the container.
Run the MCP server
successfactors-mcp
# or: python -m successfactors_toolkit.mcp_serverSpeaks MCP over stdio — see MCP server below for client configuration.
Verify it's running
curl http://127.0.0.1:8000/health
# {"status":"ok","version":"0.1.0-rc.1"}Cheat sheet
# Register a tenant's key+cert (one-time per company)
curl -X POST http://127.0.0.1:8000/api/tenants/demo/keypair \
-H "X-API-Key: $API_KEY" -H "X-Admin-Key: $ADMIN_API_KEY" \
-F "private_key=@secrets/keypair_demo/private_key.pem" \
-F "certificate=@secrets/keypair_demo/certificate.crt"
# Single-employee lookup by PERSON_ID_EXTERNAL
curl -X POST http://127.0.0.1:8000/api/sfapi/ce/query-by-person-id \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"person_id_external": ["EMP001"]}'
# Delta extract + auto-pagination
curl -X POST http://127.0.0.1:8000/api/sfapi/ce/query-all \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"last_modified_on": "2026-04-01T00:00:00+0000", "max_rows": 800}'
# OData: pull EmpJob with code -> description in one shot via $expand
curl -X POST http://127.0.0.1:8000/api/odata/execute \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"path": "EmpJob", "params": {
"$top": 5,
"$expand": "jobCodeNav,locationNav",
"$select": "userId,jobCode,jobCodeNav/name,location,locationNav/name"
}}'
# OData: bulk-extract an entity set across all pages
curl -X POST http://127.0.0.1:8000/api/odata/extract \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"path": "EmpJob", "params": {"paging": "cursor", "$top": 1000,
"fromDate": "1900-01-01", "toDate": "9999-12-31"}}'
# List registered tenants (with cert expiry warnings)
curl http://127.0.0.1:8000/api/tenants \
-H "X-API-Key: $API_KEY" -H "X-Admin-Key: $ADMIN_API_KEY"Connect to SuccessFactors
Authentication is OAuth2 SAML Bearer Assertion, which requires an RSA key pair registered as an X.509 certificate in SuccessFactors. The project never ships or generates credentials for you — you provide your own tenant.
1. Generate a key pair
./scripts/generate-keypair.sh <company_id> [technical_user_CN] [validity_days]
# e.g.
./scripts/generate-keypair.sh demo APIUSER 730This writes secrets/keypair_<company_id>/private_key.pem (mode 600) and
secrets/keypair_<company_id>/certificate.crt, both gitignored. Upload
certificate.crt to SF Admin Center → Manage OAuth2 Client Applications →
Register a Client Application, using an X.509 certificate.
Already have a PKCS#12 key pair? Convert it first:
openssl pkcs12 -in your_keypair.p12 -nocerts -nodes -out private_key.pem2. Configure the environment
cp .env.example .envAt minimum set API_KEY, ADMIN_API_KEY, SF_HOST, SF_CLIENT_KEY,
SF_USER_ID (must equal the certificate's CN), SF_COMPANY_ID, and
SF_TOKEN_URL (https://{SF_HOST}/oauth/token).
3. Register the key with the toolkit
Either point SF_PRIVATE_KEY_PATH at the PEM file directly, or register it
through the tenant management API so the toolkit stores and validates it for
you — see Tenant management below.
Environment variables
See .env.example for a filled-in starting point and
successfactors_toolkit/config.py for the authoritative field list.
Variable | Description |
| Required for any |
| Required for any |
| JSON list of allowed browser origins. Default |
| SuccessFactors host, e.g. |
| JSON list of extra hosts a per-request |
| OAuth2 client API key from SF Admin Center. |
| Technical user; must equal the certificate's CN. |
| Default tenant/company ID. |
|
|
| OData REST version, default |
| HTTP timeout in seconds, default |
| Where per-tenant key+cert pairs are stored (see below). Default |
| Where MCP tools and |
Private key resolution order
For each request, the toolkit resolves the RSA private key in this order:
Per-request
connection.private_key_path— must resolve to a path insideTENANT_KEYS_DIR, or the request is rejected with400.{TENANT_KEYS_DIR}/{company_id}/sf_private_key_{company_id}.pem— populated via the tenant management API.SF_PRIVATE_KEY_PEM_<COMPANY_ID>env var (base64-encoded PEM, per company — for CI/CD).SF_PRIVATE_KEY_PEMenv var (base64-encoded PEM, single-tenant fallback).SF_PRIVATE_KEY_PATH, a path template with a{company_id}placeholder.
Tenant management
Per-tenant private keys and certificates are stored on disk under
{TENANT_KEYS_DIR}/{company_id}/, one key+cert pair per company. All
/api/tenants/* routes — including the read-only list and get — require the
X-Admin-Key header in addition to X-API-Key.
# Register (or replace with ?force=true)
curl -X POST http://127.0.0.1:8000/api/tenants/demo/keypair \
-H "X-API-Key: $API_KEY" -H "X-Admin-Key: $ADMIN_API_KEY" \
-F "private_key=@secrets/keypair_demo/private_key.pem" \
-F "certificate=@secrets/keypair_demo/certificate.crt"
# List / inspect
curl http://127.0.0.1:8000/api/tenants -H "X-API-Key: $API_KEY" -H "X-Admin-Key: $ADMIN_API_KEY"
curl http://127.0.0.1:8000/api/tenants/demo -H "X-API-Key: $API_KEY" -H "X-Admin-Key: $ADMIN_API_KEY"
# Delete
curl -X DELETE http://127.0.0.1:8000/api/tenants/demo \
-H "X-API-Key: $API_KEY" -H "X-Admin-Key: $ADMIN_API_KEY"The keypair endpoint validates the key and certificate cryptographically
(matching public key, not expired) before writing anything, returns 409 if
the tenant already exists (bypass with ?force=true), and returns
certificate metadata including a days_until_expiry warning once a cert has
under 90 days left. Installing or deleting a tenant's key invalidates any
cached SFAPI session or OData token for that company_id.
EC SFAPI — Compound Employee (SOAP)
Single-employee lookup
curl -X POST http://127.0.0.1:8000/api/sfapi/ce/query-by-person-id \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"person_id_external": ["EMP001", "EMP002"], "include_contingent_workers": true}'
curl -X POST http://127.0.0.1:8000/api/sfapi/ce/query-by-user-id \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"user_id": ["jsmith"]}'These default to COMMON_SEGMENTS (11 broadly-supported segments); override
with select_segments if your tenant returns INVALID_SFQL for a
module-gated one.
Structured filter query
curl -X POST http://127.0.0.1:8000/api/sfapi/ce/query \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"last_modified_on": "2026-04-01T00:00:00+0000",
"company": "COMP1,COMP2",
"employee_class": "FT",
"max_rows": 200
}'person_id_external and user_id take precedence over the date/org/job
filters when set (in that order); include_contingent_workers may combine
with either. last_modified_on must carry a timezone offset (e.g.
2026-04-01T00:00:00+0000) — a bare timestamp is rejected with 400. SAP
caps look-back on this filter at 3 months.
select_segments overrides the default DEFAULT_SEGMENTS (22 segments,
SELECT * is not supported by this API). max_rows is 1–800, sent as the
maxRows SOAP parameter.
Pagination
# Manual, one page at a time
curl -X POST http://127.0.0.1:8000/api/sfapi/ce/query-more \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"query_session": "<querySessionId from the previous response>"}'
# Automatic, all pages in one call
curl -X POST http://127.0.0.1:8000/api/sfapi/ce/query-all \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"last_modified_on": "2026-04-01T00:00:00+0000", "max_rows": 800, "max_pages": 50}'query-all returns page_count, total_records, truncated,
stopped_reason (exhausted, max_pages, or a parse/fault error), and the
list of raw pages.
OData API
Endpoint | Use case |
| One arbitrary OData call ( |
| Bulk-extract an entity set, auto-following |
| Resolve N codes to records, auto-chunked under SF's |
$format=JSON is auto-injected unless the path is $metadata (served as
EDMX XML only) or the caller already set $format. All three endpoints
accept an optional connection override (host, OData version, credentials,
csrf_protected) — see Per-request connection override.
Effective-dated entities —
EmpJob,Position, allFO*, and MDF generic objects return only today's time slice unless you passasOfDate, orfromDate+toDate. For full history usefromDate=1900-01-01&toDate=9999-12-31.
execute — one request
curl -X POST http://127.0.0.1:8000/api/odata/execute \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"method": "GET", "path": "User", "params": {"$top": 5, "$select": "userId,username,email"}}'extract — bulk pages
curl -X POST http://127.0.0.1:8000/api/odata/extract \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"path": "EmpJob",
"params": {"$top": 1000, "paging": "cursor",
"fromDate": "1900-01-01", "toDate": "9999-12-31"},
"max_pages": 100
}'Response includes pages_fetched, total_records, results (flattened
d.results), stopped_reason (exhausted | max_pages | http_error |
parse_error), and, if max_pages was hit mid-stream, next_skiptoken to
resume via params["$skiptoken"].
Footgun: extract stops at max_pages and reports stopped_reason,
but a truncated response can otherwise look identical to a complete one at a
glance for an entity set without a __next link on its last page — check
stopped_reason, not just the HTTP status.
extract-by-filter-in — N-code lookup
curl -X POST http://127.0.0.1:8000/api/odata/extract-by-filter-in \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"path": "FOJobCode",
"column": "externalCode",
"values": ["500001", "500002", "500003"],
"chunk_size": 1000,
"max_pages_per_chunk": 100
}'Footgun: each chunk is sent as col eq 'a' or col eq 'b' or ... on the
URL query string, not a native in() (SF OData v2 doesn't accept it despite
some docs listing it). A large chunk_size can push the request line past
SuccessFactors' ~8 KB limit, returning HTTP 414. If you see 414, lower
chunk_size.
Per-request connection override
{
"connection": {
"host": "example.invalid",
"odata_version": "v2",
"company_id": "demo",
"private_key_path": "/absolute/path/to/tenants/demo/sf_private_key_demo.pem",
"csrf_protected": false
},
"method": "GET",
"path": "EmpJob",
"params": {"$top": 5}
}host and token_url overrides are only accepted for the configured
SF_HOST, a host listed in SF_ALLOWED_HOSTS, or a SAP SuccessFactors
datacenter domain; anything else is rejected with 400.
private_key_path must resolve inside TENANT_KEYS_DIR, or the request is
rejected with 400. csrf_protected defaults to false (CSRF is a
session-cookie defense, not needed under Bearer auth) — set it per request
for tenants that enforce it.
Response format
All /api/sfapi/* and /api/odata/execute calls return the same shape:
{
"status_code": 200,
"headers": { "content-type": "application/xml" },
"body": "<raw response body as a string — XML for SFAPI, JSON for OData>"
}MCP server (Claude Desktop, Claude Code)
successfactors-mcp exposes five tools over stdio, reusing the same OAuth2
SAML Bearer flow, tenant key store, and pagination logic as the REST API:
Tool | Arguments | Returns |
| — | Registered tenants (cert expiry) plus the |
|
|
|
|
|
|
|
| Counts, field names, file path; |
|
| Counts and one XML file path per page. |
Payloads stay on disk
Records are written under {RESULTS_DIR}/mcp/ (mode 0600) and the tool
returns the file path plus counts, never the records themselves. This is
deliberate, not a limitation: a single Compound Employee payload runs about
80 KB, it is HR data with no business being echoed into a chat transcript,
and comparing two OData metadata documents in-context would burn tens of
thousands of tokens doing a diff a few lines of Python does instantly.
Install
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"successfactors": {
"command": "successfactors-mcp",
"env": {
"SF_HOST": "example.invalid",
"SF_CLIENT_KEY": "...",
"SF_USER_ID": "APIUSER",
"SF_COMPANY_ID": "demo",
"SF_TOKEN_URL": "https://example.invalid/oauth/token",
"TENANT_KEYS_DIR": "/absolute/path/to/tenants",
"RESULTS_DIR": "/absolute/path/to/results"
}
}
}
}Claude Code:
claude mcp add successfactors \
-e SF_HOST=example.invalid \
-e SF_CLIENT_KEY=... \
-e SF_USER_ID=APIUSER \
-e SF_COMPANY_ID=demo \
-e SF_TOKEN_URL=https://example.invalid/oauth/token \
-e TENANT_KEYS_DIR=/absolute/path/to/tenants \
-e RESULTS_DIR=/absolute/path/to/results \
-- successfactors-mcpSettings reads .env from the current working directory, which an MCP
host does not reliably set to the repo root — pass everything needed as an
explicit env block (as above), or set the host's working directory to a
folder containing your .env, rather than relying on ambient state.
Debug tool calls with the MCP Inspector:
npx @modelcontextprotocol/inspector successfactors-mcpKnown SuccessFactors API footguns
Compound Employee
periodDeltamode andisNotFirstQuery. When driving Compound Employee in period-delta mode across repeated calls, theisNotFirstQueryflag must be embedded as one of the<urn:param>entries inside the query'sresultOptionsparameter, not passed as a standalone parameter — SAP's SOAP API silently ignores it in the wrong place rather than erroring.OData bulk extraction can look complete while being truncated. See the
extractandextract-by-filter-infootguns documented above (stopped_reasonand the ~8 KB request-line limit).
Development
git clone https://github.com/wudaoyou/successfactors-toolkit.git
cd successfactors-toolkit
pip install -e ".[dev]"
ruff check .
ruff format --check .
pytest
python3 scripts/check_repository.pySee CONTRIBUTING.md for branch, review, commit, and version rules, and docs/RELEASING.md for the release procedure.
Data handling
Use synthetic examples and test fixtures. Credentials, certificates, tenant
exports, employee payloads, and generated results do not belong in Git —
.gitignore and scripts/check_repository.py are a basic guardrail, not a
complete secret or personal-data scanner. See SECURITY.md for
deployment guidance and how to report a vulnerability.
License
Apache License 2.0. Copyright 2026 Justin Gong. See NOTICE for third-party and migrated-code attribution.
Available Tools
5 toolsce_queryA
Query the EC Compound Employee (SOAP) API and save the payload to disk.
person_id_external / user_id are comma-separated and take precedence over every other filter when set. last_modified_on is an ISO datetime for a delta pull (SAP allows at most 3 months of look-back). With no filter at all this is a full extract, capped by max_pages.
select_segments defaults to the widely supported COMMON_SEGMENTS. If SF answers INVALID_SFQL naming a segment, that module is not enabled on the tenant — pass a narrower list.
Each queryMore page is written as its own XML file. The tool returns counts and paths only: one employee's payload is ~80 KB of HR data.
| Name | Required | Description | Default |
|---|---|---|---|
| user_id | No | ||
| max_rows | No | ||
| max_pages | No | ||
| company_id | No | ||
| select_segments | No | ||
| last_modified_on | No | ||
| person_id_external | No | ||
| include_contingent_workers | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It does well: it discloses that each queryMore page is written as its own XML file, that the tool returns only counts and paths, that payloads are ~80 KB per employee, and that max_pages caps the full extract. It also explains the INVALID_SFQL behavior. The only minor gap is that it doesn't explicitly state whether the tool is read-only or mutating, but the described behavior (querying and saving to disk) makes that reasonably clear.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is four short paragraphs, each earning its place: purpose, filter semantics, segment handling, and output behavior. It front-loads the core purpose and side effect, then layers in usage details. There is no fluff or repetition of schema field names without added meaning. Every sentence adds operational value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (8 parameters, 0% schema coverage, no annotations), the description is remarkably complete. It covers the main query modes, the error-handling pattern, the output format, and the size implications. The gaps are minor: max_rows, company_id, and include_contingent_workers are not explained, and the output schema exists but the description already covers return values (counts and paths). An agent could call this tool correctly for the common cases, though it might need to infer the meaning of the three undocumented parameters.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for the schema's lack of parameter documentation. It does: it explains person_id_external/user_id (comma-separated, precedence), last_modified_on (ISO datetime, 3-month look-back), select_segments (defaults to COMMON_SEGMENTS, narrowing on INVALID_SFQL), and max_pages (caps full extract). However, it doesn't mention max_rows, company_id, or include_contingent_workers, so those parameters remain undocumented. Still, the description covers the most important parameters and adds meaning well beyond the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Query'), a specific resource ('EC Compound Employee (SOAP) API'), and a concrete side effect ('save the payload to disk'). It also distinguishes itself from sibling tools by naming the SOAP API and the disk-saving behavior, which none of the siblings (list_tenants, odata_metadata, compare_metadata, odata_query) imply. This is a clear, specific purpose statement.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit guidance on when to use this tool: for delta pulls with last_modified_on, for full extracts with no filter, and for handling INVALID_SFQL errors by narrowing select_segments. It also explains precedence rules (person_id_external/user_id take precedence over every other filter) and the 3-month look-back limit. This is rich, actionable usage guidance that an agent can act on without further inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
compare_metadataA
Compare the OData configuration of two instances and return the drift.
entity="EmpJob" compares one entity set; entity="" compares the whole service. The comparison runs here, not in the conversation: one instance's EmpJob metadata alone is ~40 KB, so diffing two of them in context is both expensive and easy to get wrong.
Returns in_sync plus, per entity, the fields missing on either side and the
fields whose attributes differ, each as [value_in_a, value_in_b]. The sap:
attributes are the configuration itself — required, visible, upsertable,
picklist, MaxLength — so a changed picklist or a field that never left the
dev instance shows up here.
| Name | Required | Description | Default |
|---|---|---|---|
| entity | No | ||
| company_a | Yes | ||
| company_b | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden and does so well. It states that the comparison runs remotely, not in the conversation, and details the return shape: in_sync, per-entity missing fields, and differing attributes as [value_in_a, value_in_b]. It even explains which attributes matter and why, giving the agent real operational insight.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose, then adds parameter behavior, then returns and edge-case meaning. Every sentence earns its place, including the size rationale and the sap: attribute explanation, without becoming bloated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a comparison tool of this complexity, the description covers what the tool does, how it behaves, what its main parameter controls, and what the output means. An output schema exists to supply formal return-field definitions, so nothing critical is missing for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must clarify the parameters. It fully documents the non-obvious entity parameter with an explicit example and the empty-string whole-service case, and it maps company_a and company_b to the two instances being compared. For three simple parameters, this is sufficient.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening line states a precise operation—compare the OData configuration of two instances—and names the result: drift. It is clearly distinct from siblings like odata_query and odata_metadata, which are single-instance read/query tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear guidance on entity scope: comparing one entity set versus the whole service. It also explains why the tool should run the comparison instead of doing it in-conversation, using a concrete size rationale. It does not explicitly name sibling alternatives or when-not-to-use cases, but the context is clear enough.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_tenantsA
List the SuccessFactors instances this server can reach.
Call this first: the company_id values it returns are what the other tools
take as their company_id argument. An empty company_id always means the
instance configured in the server's own .env, reported here as "default".
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral burden. It discloses that the tool lists only instances the server can reachced, and clarifies the default-instance semantics tied to the server's .env. It stops short of describing output shape, but an output schema exists and 'list' implies a read-only operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each earning its place: the purpose, the critical call-order guidance, and the default-instance semantics. The most actionable instruction ('Call this first') is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a parameterless list tool with an output schema, the description covers purpose, sequencing, relationship to sibling tools, and a subtle default-value meaning. Nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the baseline is 4. The description adds useful context beyond the empty schema by explaining how an empty company_id value maps to the server-configured default instance, which helps agents understand values returned by this tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'List the SuccessFactors instances this server can reach.' This clearly distinguishes it from the sibling tools, which all query or compare metadata rather than enumerate tenants.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says 'Call this first' and explains why: the returned company_id values are the inputs other tools take as their company_id argument. This gives an agent a clear directive on when to use this tool relative to its siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
odata_metadataA
Fetch OData $metadata (EDMX) and reduce it to a compact field map.
entity="" pulls the whole service metadata (large — hundreds of entity types); entity="EmpJob" pulls just that entity set. The full {entity: {field: attributes}} map is written to a JSON file, and a small map is returned inline as well, so two instances can be compared without ever loading raw EDMX into the conversation.
| Name | Required | Description | Default |
|---|---|---|---|
| entity | No | ||
| company_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of disclosure. It transparently reveals that the full map is written to a JSON file, that a 'small map' is returned inline, and that raw EDMX is never loaded into the conversation. This gives an agent a clear model of side effects and output behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded with the primary action, followed by parameter semantics and output behavior. Each sentence adds meaningful information; only minor redundancy in the two-map explanation prevents a perfect score.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return value details are handled. The description covers the entity parameter and the file side-effect, but the unexplained company_id and lack of any guidance about tenant/service context leaves the tool not fully self-contained for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must document parameters. It explains entity behavior well (empty vs specific value), but company_id is completely unexplained, leaving half of the parameters ambiguous. Partial compensation prevents a lower score.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a clear action and resource: 'Fetch OData $metadata (EDMX) and reduce it to a compact field map.' It distinguishes the tool from siblings by stating it produces a reduced map rather than raw EDMX or query results, and the entity-specific example reinforces its purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear internal guidance for when to use entity='' versus entity='EmpJob', and hints at a comparison use case. However, it does not explicitly state when to choose this tool over siblings like odata_query or compare_metadata, leaving the selection partly implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
odata_queryA
Run an OData v2 query, following __next until exhausted or max_pages.
path is the entity set and may carry query options, e.g. "FOCompany" or "EmpJob?$select=userId,jobCode". Effective-dated entities (EmpJob, Position, FO*, MDF) return ONLY today's time slice unless you pass fromDate=1900-01-01 and toDate=9999-12-31 in params.
Records are written to a JSON file; the tool returns counts, the field names of the first record, and the path. preview>0 additionally returns that many records inline — this may be HR data, so ask for it only when the values themselves are needed.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| params | No | ||
| preview | No | ||
| max_pages | No | ||
| company_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure, and it does so well: it reveals that records are written to a JSON file, that pagination follows __next, that effective-dated entities return only today's slice by default, and that preview returns inline records. This is strong transparency, though it leaves some behaviors unstated, such as how company_id affects the query and what happens on errors.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but every sentence earns its place: the opening states the core action, examples clarify path syntax, the effective-dating caveat prevents a common mistake, and the output/preview warning adds important usage constraints. The structure front-loads the main purpose before moving to specific behavioral and output details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no annotations and a low-schema-coverage input schema, the description covers the critical invariants: pagination, date-slice behavior, file output, return contents, and preview caution. It is not fully complete because company_id is left undefined and there is no guidance on how to distinguish this tool from sibling query tools, but the essential calling contract is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It substantially explains path with examples, implies how params work by naming fromDate and toDate, and explains preview and max_pages in context. However, company_id is not mentioned at all, and the relationship between inline query options on path versus params is not fully specified, leaving the parameter semantics incomplete.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Run an OData v2 query,' and immediately clarifies the pagination behavior with 'following __next until exhausted or max_pages.' It gives concrete examples of valid path values, such as 'FOCompany' and 'EmpJob?$select=userId,jobCode', making the tool's operation unambiguous. Although it does not explicitly compare itself to siblings, the focus on entity sets, JSON file output, and record counts clearly separates it from metadata-focused siblings like odata_metadata.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear usage context: path determines the entity set, effective-dated entities need explicit fromDate/toDate params to retrieve more than today's slice, and preview should only be requested when actual values are needed because it may expose HR data. It does not explicitly name alternatives or state when not to use the tool versus ce_query or odata_metadata, so it falls short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.0- First observed
ce_query - First observed
compare_metadata - First observed
list_tenants - First observed
odata_metadata - First observed
odata_query
TDQS
Scored across 5 tools
Each tool targets a distinct concern: tenant discovery, SOAP extract, OData query, metadata fetch, and metadata comparison. There is no functional overlap, and the descriptions make the boundaries clear.
Names are all lowercase snake_case and descriptive, but the pattern is mixed: list_tenants and compare_metadata are verb-first, while ce_query, odata_metadata, and odata_query are object-first. Minor deviation from a uniform verb_noun style.
Five tools is well-scoped for the server's read-only SuccessFactors integration purpose. Each tool earns its place and there is no bloat or thinness.
The toolset covers the full read-only lifecycle: discover instances, extract employee data, query OData, fetch metadata, and compare metadata between environments. No obvious dead ends or missing operations for the stated purpose.
Maintenance
Related MCP Connectors
Query SEC EDGAR filings, XBRL financials, and company data through MCP. STDIO & Streamable HTTP.
Governed data discovery, exact queries, decisions, simulations, and runtime utilities over MCP.
- OneOAuthai.withone
Search, document and execute authenticated API calls across 700+ apps via one MCP server
Search NPPES providers and resolve NUCC specialty codes via MCP over STDIO or Streamable HTTP.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables querying SAP SuccessFactors OData API metadata and managing Role-Based Permission (RBP) configurations. It provides tools for retrieving entity metadata, listing permission roles, and inspecting user-specific access rights through MCP-compatible clients.2911MIT
- FlicenseNot gradedqualityAmaintenanceEnables EHR Copilot operations such as order cloning, queue building, and execution trace analysis over stdio.-
- FlicenseAqualityCmaintenanceRead-only MCP server for searching migrated Papertrail logs via SolarWinds Observability API. Provides tools to list environments and perform bearer-authenticated log queries through stdio.267 npm-
- FlicenseNot gradedqualityCmaintenanceEnables MCP-compatible clients to retrieve invoice and purchase order data live over stdio through tools for listing invoices, getting invoice details, and fetching purchase orders.-