AI agents (MCP)

The same capabilities the REST API exposes are also published as Model Context Protocol tools, so an AI agent can use them directly.

A tool is not a parallel implementation: it dispatches the same handler as its REST counterpart, under the same authentication, the same scopes, the same quota and the same idempotency rules. Anything an agent cannot do over REST, it cannot do over MCP.

tools/list goes one step further than REST discovery: it returns only the tools your token holds the scopes for. Where the OpenAPI document lists every route and refuses only on call, a tool outside your scopes simply does not appear — called by name anyway, it answers naming the scope you are missing.

Connecting

Endpoint /mcp on the Staff & Go API host
Transport Streamable HTTP
Authentication OAuth 2.1 resource server — the same partner credentials as REST

Discovery follows RFC 9728: GET /.well-known/oauth-protected-resource returns the resource identifier, its authorization server and the scopes it supports. A conformant MCP client reads that and knows where to authenticate.

The token must be minted for this API's audience. We never accept a credential passed through from somewhere else, and a token issued for another audience is rejected — that is the whole point of audience binding.

Setting it up

Partner access is machine-to-machine, so the designed grant is client_credentials. Many MCP clients only implement the interactive, redirect-based flow and cannot perform it; pasting a static Authorization header into a config file is not a fix either, since the token expires.

We publish a small stdio proxy for exactly that: it holds your client credentials, mints and refreshes the token, and speaks plain MCP stdio to your client. Node 20 or later is the only prerequisite — the proxy has no dependencies to install.

Download
mcp-stdio-proxy.mjs the proxy — the only file strictly required
install.mjs registers the proxy with Claude Desktop, and writes a settings template if no config.yml is present
install.ps1 · install.sh launchers for the above

Every file is served as plain text, so you can read a script in the browser before you run it. Please do.

Settings

The installer reads its settings from a config.yml beside itself. The easiest way is to download it from Staffngo: Applications (API), in the secret dialog of your application, the Download the MCP configuration (config.yml) button. If no config.yml is present, the installer writes a commented template in its place, to replace with the downloaded file or fill in by hand. Four values are yours to fill in:

Setting
EXTERNAL_MCP_URL https://<your-external-api-host>/mcp
KEYCLOAK_TOKEN_URL the token endpoint of your authorization server — published, with the scopes it supports, at GET /.well-known/oauth-protected-resource on the same host, so you do not have to guess it
PARTNER_CLIENT_ID · PARTNER_CLIENT_SECRET the partner credentials you were issued

There is nothing to declare about scopes: the ones granted to your application are carried by your credentials and enter the token without being asked for. That is also what keeps your configuration working the day an administrator changes what your integration may read.

One setting matters in local development only: NODE_TLS_REJECT_UNAUTHORIZED: "0", carried in the template as a commented-out (#) line that you uncomment only against a self-signed development certificate. Node does not read the operating system certificate store, so trusting a self-signed development certificate at the OS level does not make Node accept it. Against a host with a real certificate, leave it commented out.

The file is flat key: value — the reader refuses nesting and lists rather than misreading them.

Claude Desktop

Four steps:

  1. Node 20 or later. node --version tells you what is installed.

  2. Download your config.yml from Staffngo: Applications (API), in the secret dialog of your application, the Download the MCP configuration (config.yml) button. It already holds the four values.

  3. Download the scripts (table above) into the same folder as config.yml, then run the installer:

    ./install.sh              # macOS / Linux
    .\install.ps1             # Windows (PowerShell)
    

    On Windows, if PowerShell refuses to run install.ps1 (Restricted execution policy, or an "unapproved publisher" prompt), run node install.mjs instead: Node is not subject to that policy, and it is exactly the same installer.

  4. Quit Claude Desktop completely — from the system tray, not just the window — and start it again. The configuration is only read at startup. The connector appears under the name staffngo.

The installer copies the proxy next to Claude's own configuration and merges one entry into claude_desktop_config.json, preserving every other key and every other server. It backs the file up first (.bak-<timestamp>), and --dry-run shows exactly what it would change without writing anything.

Without a config.yml, the installer writes a template beside itself, tells you so and stops without an error: replace it with the downloaded file (or fill in the four values), then run it again. That is a fallback, not a step of the path.

Already installed the MCP before? The installer does not rename the old external entry. Delete it by hand from claude_desktop_config.json (a .bak-<timestamp> backup is made on every write), then run the installer again. To register several environments side by side, give each its own name with --name — for example --name staffngo-staging --config config.staging.yml.

To do it by hand, the file lives here:

Platform Path
Windows — Store build (MSIX) %LOCALAPPDATA%\Packages\Claude_*\LocalCache\Roaming\Claude\
Windows — classic installer %APPDATA%\Claude\
macOS ~/Library/Application Support/Claude/
Linux ~/.config/Claude/

The Store build is an MSIX package, and MSIX redirects %APPDATA% — its configuration is never under %APPDATA%\Claude, which is why looking there finds nothing. The folder appears only after Claude Desktop has been launched once.

Claude Code

Claude Code reads .mcp.json in the project root, under an mcpServers key:

{
  "mcpServers": {
    "staffngo": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-stdio-proxy.mjs"],
      "env": {
        "EXTERNAL_MCP_URL": "https://<your-external-api-host>/mcp",
        "KEYCLOAK_TOKEN_URL": "https://<your-authorization-server>/realms/<realm>/protocol/openid-connect/token",
        "PARTNER_CLIENT_ID": "<your client id>",
        "PARTNER_CLIENT_SECRET": "<your client secret>"
      }
    }
  }
}

.mcp.json is committed by convention, so put a file holding a secret in your untracked configuration instead.

VS Code

VS Code reads .vscode/mcp.json, whose servers live under a servers key rather than mcpServers. The command / args / env triple is identical to the block above. Check your VS Code version's documentation for the exact schema — it has moved more than once.

Any other stdio client

Everything above is the same three values in different wrappers. Whatever your client calls its configuration, it needs:

Use absolute paths. Your client starts the process itself, outside any shell that would resolve a relative path, a symlink or an alias.

If the server does not appear, the proxy writes its diagnostics ([mcp-proxy] …) to stderr, never to stdout, which carries the JSON-RPC stream. Look for them in your client's logs.

The one thing that will otherwise cost you an afternoon

Declare no scopes on the client side. The scopes granted to your application are carried by your credentials and enter the token on their own: there is nothing to list in config.yml or in your client's configuration. A tool whose scopes your token lacks is absent from tools/list altogether — you get no refusal to explain it, you simply never see the tool. If an agent tells you a capability does not exist, first check in Staffngo, under Applications (API), the scopes granted to your application.

Using it in a chat

You do not call tools yourself — you ask in plain language and the agent picks. Start by making it say what it has, which doubles as the check that your scopes came through:

What tools does the staffngo connector give you? List them with what each one does.

Then work by asking for the thing, not the tool:

What you type What it reaches for
What is my partner access entitled to? Which modules are active? the partner profile
List the leave types available. a referential read
Give me the first ten employees, with their job title and manager. the employee list
Take the first one and show me their employment contracts. employments for that employee
Which absences between the 1st and the 30th of September? leave request lines, with a date window
And that person's leave balances? balances for that employee
Show me the audit trail of what you just did. the audit trail

Chaining is where it earns its keep. Identifiers are opaque, so you cannot type one from memory: the natural flow is list → « take that one » → detail, and the agent carries the identifier across for you. That is the same sequence an integration has to perform; the chat just makes it comfortable.

Three things will look like faults and are not:

One caution worth stating plainly: these answers carry personal data, and in a chat they land in the conversation and stay there. To show someone the shape of a response, prefer a test record over a real file.

Tool naming and versions

Tool names are stable and carry their version when more than one exists — get_partner_profile and get_partner_profile_v2 are distinct tools with distinct contracts. Pin the one you mean; a name never silently changes meaning.

Safety hints

Each tool declares what it does, so an agent framework can decide whether to ask a human first:

Hint Meaning
ReadOnly Does not modify anything.
Idempotent Repeating the call has the same effect as making it once.
Destructive Has an effect that is not trivially undone.
OpenWorld Touches state beyond this API.

Use them. A tool marked Destructive is one where an agent's retry loop has real-world consequences — require a human confirmation, and send an Idempotency-Key so a retry cannot double the effect. See Idempotency.

Quota

MCP calls and REST calls share one counter per tenant. An agent that polls in a loop consumes the budget your integration also needs, and the RateLimit headers apply the same way — see Rate limits.