WorkforceGPT for agents
An MCP server that lets any compatible agent score a job description and build a structured role profile (responsibilities, skills with proficiency levels, and qualifications) on behalf of someone with a WorkforceGPT account. Connect it in one step, no API key.
https://workforcegpt.ai/mcp
Quick start
Four steps, and the first three involve no code at all. By the end of step two you will have scored a real job description by asking for it in plain English. Step four is for when you want that to happen the same way every time.
You need two things: a WorkforceGPT account, which is free to create and needs a verified email address, and a copy of Claude (or any other MCP client).
Connect Claude to the server
Everything on this page hangs off the URL in the box above. There is no API key to paste and no client id to request in advance, because your client registers itself the first time it connects.
Claude web, desktop or Cowork
Open Settings, go to Connectors, and choose Add custom connector. Name it whatever you like, paste the server URL, and add it.
That menu belongs to Anthropic and may move. Their guide to custom connectors is the version that stays current, including which plans it is available on.
Claude Code
Add it once, then sign in:
claude mcp add --transport http workforcegpt https://workforcegpt.ai/mcp
Run /mcp inside Claude Code to complete the
sign-in, and claude mcp list to confirm it
says connected.
Any other MCP client
Give it the URL and nothing else. The server publishes the standard discovery documents, so a client that speaks OAuth finds the rest by itself. The detail is in how the connection works.
Whichever route you take, a browser window opens and asks you to sign in to WorkforceGPT and approve the connection. The screen names the application asking, the web address the approval will be sent to, and exactly what it is asking to do. Read that middle one: it is the one thing an application cannot fake about itself.
From then on the agent acts as you, against your account and your allowance. You can disconnect it whenever you like from your settings page, and that takes effect at once.
Ask for something
There is no syntax to learn. Claude reads the tool descriptions and picks one. Try any of these:
"Here is a job description we are about to post. Score it
and tell me what to fix." (then paste it in)
Calls assess_job_description
and answers on the spot, with a score, what the score means,
and what to do about it.
"Build a role profile for a Senior Data Analyst at
Northwind."
Calls generate_role, which
starts the work and hands back an id, then checks back on it
until it is done. See
how generation behaves.
"What have I got left on my WorkforceGPT account?"
Calls
get_account_status. Worth asking before you
plan a batch of work rather than after.
If something is refused, the answer says which limit you hit and what the account has left, in words, rather than making the agent guess from a status code.
Add the two ready-made Skills
A Skill is a Markdown file of instructions that Claude loads when a conversation calls for it. We ship two. They add no capability: everything they do, you just did without them in step two.
What they add is judgment, i.e. the parts a tool description
has no room for. When is a tool worth calling at all? How do
you spend a small allowance well? How do you turn a company's
internal job title into one the labor market will recognize?
And what does importance_to_role: 1 mean? It
means High, which is the opposite of how it reads.
assess-job-description- Scoring an existing job description, and deciding what to do about the score.
build-role-profile- Generating a role profile, and getting the title right so the labor-market match succeeds.
In Claude Code
Each skill is one SKILL.md in a folder named
after it:
SRC=https://workforcegpt.ai/mcp/skills
DEST=~/.claude/skills
for s in assess-job-description build-role-profile; do
mkdir -p $DEST/$s && curl -s $SRC/$s.md -o $DEST/$s/SKILL.md
done
Use .claude/skills/ inside a project instead if
you want it to travel with the repository rather than follow
you around.
In the Claude apps
Download each file from the links above, put it in a folder
named after the skill as SKILL.md, zip that
folder, and upload it in Claude's settings.
Anthropic's Agent Skills
guide has the current path and which plans it needs.
description and inputSchema, which
is where the non-optional parts live.
Write a Skill of your own
You do not need to be a developer for this and you do not need to start from nothing. The two skills above already handle the hard general problems. What they cannot know is how your organization does this: your job families, your leveling, your house format, the approval step you want before anything is written down.
So the skill worth writing is a thin one that wraps ours.
Here is a complete example. Save it as
SKILL.md in a folder named
northwind-role-profiles, in the same place you
put the two above.
---
name: northwind-role-profiles
description: Create a role profile that follows Northwind's job architecture conventions. Use when someone asks for a job profile, role definition, competency model or skills framework for a Northwind position, or wants an existing job description turned into one.
---
# Northwind role profiles
Our job architecture has rules WorkforceGPT does not know about. Apply them
around the tools, not instead of them.
## Before spending anything
Call `get_account_status` and tell the person what the account has left. Do
not guess the allowance, it is configurable and this file will not be updated
when it changes.
## Getting the title right
We title roles internally like "Staff Engineer II, Payments Platform". That
will not match labor-market data and the generation will come back thin. Send
the occupation instead, i.e. "Software Engineer", and keep our internal title
for the heading you write at the end.
If the person insists on the internal title, pass the old job description as
`job_description` and set `use_job_description_if_title_not_found` to true, so
there is something to build from when the title does not match.
## Generating
Call `generate_role` with `role_title` and `company_name`. It returns a
`role_id` rather than a profile. Poll `check_role_status` with that id until
`status` is no longer `pending`.
## Reading the result
`importance_to_role` is 1 for High, 2 for Medium and 3 for Low. It reads like
the opposite of what it means, so print the label and never the number.
## What to hand back
A table of responsibility, skill and proficiency level, then ask whether to
add it to the job architecture sheet. Never add it without being asked.
The four things that decide whether it works
- The description is the trigger. Until Claude decides to open the file it can see only the name and the description, so write the description about the situations somebody will be in, not about what the file contains. "Use when someone asks for a job profile…" beats "Tools for role profiles."
-
Name the tools exactly.
generate_role, not "the generate tool". The names are in the tool list and they are what the agent has to type. -
Do not write the allowance into the file.
It is configurable, and a file sitting on your laptop is
not redeployed when it changes. Point at
get_account_statusinstead, which is always right. - Say what to do with the answer. A score with no interpretation attached is a score an agent will interpret generously. Tell it what good looks like and what to do about bad.
Checking that it loaded
Start a new conversation and say something that should trigger it, without naming the skill. If Claude does not reach for it, the description is the problem rather than the body: it has to sound like the thing the person actually asked for.
Reference
The detail underneath the quick start: every tool and its arguments, what the scopes mean, how the limits work, and the protocol itself.
Tools
10 tools. The scope beside each one is what your client must have been granted to call it; a call without it comes back as a tool error naming the scope you need.
Score a job description from 0-100 on how cleanly it can be turned into a structured role profile, and say what is missing and what to fix. Takes the job description as text. Runs synchronously — the answer comes back on the same request — so allow a generous client timeout. Metered: see get_account_status.
Check whether a role generation has finished, and return the full role profile once it has. Poll this after generate_role.
Start generating a structured role profile — description, responsibilities, skills with proficiency levels, qualifications. Returns immediately with a role_id; generation continues in the background, so poll check_role_status. Spends one of this account's role-generation credits. Only one generation runs at a time.
Report which WorkforceGPT account this credential belongs to, what it is permitted to do, and how much of its quota is left. Call this first: it is how you find out whether a generation will be accepted before you build one.
Fetch one previously run job-description assessment by id.
Fetch one published role profile from the public library in full. Needs no account.
Fetch one generated role profile in full, including its skills.
List this account's recent job-description assessments, newest first, from both this API and the web app.
List the role profiles on this account, newest first.
Search the public WorkforceGPT role library — role profiles their authors chose to publish. Needs no account. Use it for examples of what a generated profile looks like, or for prior art on a title.
Every tool declares its arguments as JSON Schema, served verbatim as
MCP's inputSchema, so your client can read the types
and the required fields rather than guessing them from prose.
Worked examples
These are argument objects, i.e. what goes in the
arguments of a tools/call, or in the body
on the plain HTTP path. If you are driving
Claude rather than writing a client, you will never type these: they
are here so you can see what the agent is actually sending.
Score a job description
{
"job_description": "Senior Data Analyst\n\nNorthwind is hiring an analyst to own reporting for the commercial team...",
"filename": "senior-data-analyst.txt"
}
Start a role profile
{
"role_title": "Data Analyst",
"company_name": "Northwind",
"job_description": "Senior Data Analyst\n\nNorthwind is hiring an analyst to own reporting for the commercial team...",
"use_job_description_if_title_not_found": true,
"include_learning_resources": true,
"learning_resources_source": "Skillsoft"
}
Only role_title and company_name are
required. The rest are shown here because they are the ones worth
knowing about: the job description is what saves a generation when
the title does not match labor-market data, and the learning
resources are the one place this server offers a choice of source.
Check on it
{ "role_id": 4812 }
Browse the public library
{ "query": "project manager", "limit": 5 }
This one and get_public_role read the published corpus
at /roles/. They cost
nothing against an account's allowance, and on the plain HTTP path
they need no credential at all.
Scopes
Three, and they are what the consent screen shows the person
approving your client. Ask for only what you need: a client
requesting generate is asking to spend somebody's
credits, and they can see that.
read- View your generated roles and JD assessments
assess- Score job descriptions on your behalf
generate- Generate role profiles on your behalf, using your credits
How generation behaves
generate_role does not return a role profile. It starts
one and returns a role_id. Poll
check_role_status with that id, around every 30 seconds
rather than continuously, until the status leaves
pending.
How long that takes depends on the role and on load, and we would rather not give you a number we cannot keep. What we can tell you is the ceiling: a generation still running after 60 minutes is marked failed, and a failed generation does not spend a credit.
Only one generation runs per account at a time. Starting a second
while one is in flight comes back as
generation_in_progress rather than queueing.
assess_job_description is the other way round: it runs
synchronously and answers on the same request. It does real work, so
give the call a generous client timeout rather than the default few
seconds.
Limits
Every refusal tells you which limit you hit, what the account has left, and where to get more. You should not have to work that out from a status code.
- Role generations
- 3 per account
- Job description assessments over this API
- 3 per account
- Concurrent generations
- 1
Assessments run from the web app are free and unmetered; only the API ones are counted, because an agent can loop where a person clicking a button does not. Publishing a generated role to the public library earns the account one extra generation, but publishing is a web-app action. See what it will not do.
Call get_account_status before you plan work. It
returns the same quota block that every refusal carries, so you can
find out you have nothing left before you build a request rather
than after.
When an account runs out
There is no self-serve top-up yet. Talk to TalentGuard and we will sort it out.
What this server deliberately will not do
Publish anything. Roles can be published to a public, search-indexed library at /roles/, under the account holder's name if they choose. That is a decision about someone's public footprint, and it is not one an agent can meaningfully take on their behalf, so there is no tool for it and no scope that would allow it. Publishing happens in the web app or not at all.
Touch an account or anyone else's data. No tool deletes, no tool emails, no tool reaches administrative functions. A credential resolves to exactly one account and sees only what that account owns.
Accept files. MCP tool arguments are JSON, so a job description arrives as text. If you are holding a PDF or a DOCX, extract the text first, or use the web app, which does the conversion for you.
How the connection works
Step one of the quick start is the whole configuration. This is what happens underneath it, which matters if you are writing a client rather than using one.
The server implements OAuth 2.1. Your client's first request is
refused with a 401 carrying a
WWW-Authenticate header that points at
https://workforcegpt.ai/.well-known/oauth-protected-resource. From
there it finds the authorization server, registers itself (RFC 7591
dynamic client registration is open, which is why there is no key to
paste), and sends the person using it to a consent screen. PKCE is
required for every client, including confidential ones.
Authentication is required for every message, including
initialize. That is deliberate: the 401 is
how a client given nothing but a URL finds everything else, so it
has to be the first thing a cold client sees.
The person approving the consent screen must have a WorkforceGPT account with a verified email address. The agent then acts as them, against their quota.
If your client cannot do OAuth discovery
Point it at these directly:
Protected resource https://workforcegpt.ai/.well-known/oauth-protected-resource
Authorization server https://workforcegpt.ai/.well-known/oauth-authorization-server
MCP endpoint https://workforcegpt.ai/mcp (POST, JSON-RPC 2.0)
Supported protocol revisions:
2025-11-25, 2025-06-18, 2025-03-26.
If yours is newer, initialize negotiates rather than
refusing: it answers with the newest we speak and you decide
whether to continue. Every other method answers an
MCP-Protocol-Version header we do not know with a
400 naming what we do, which is what the transport
spec asks for. ⚠️ The 401 always comes first, so a
client on a revision ahead of ours can still discover how to sign
in.
The server is stateless, i.e. it issues no
Mcp-Session-Id, and it offers no server-initiated
stream, so a GET asking for
text/event-stream is answered 405. So is
DELETE, since there is no session to terminate.
Methods: initialize, ping,
tools/list, tools/call, and
notifications/*, which are acknowledged and not
answered. Only tools is advertised under capabilities,
with listChanged: false, because the tool set is fixed
when the server starts.
A refused tool call is a result, not a protocol
error. A spent quota, an unknown id, a bad argument or
a missing scope all come back as
isError: true with the reason in
structuredContent, because the protocol worked and
the agent needs to read why. Only an unknown tool name or
malformed params is a JSON-RPC error. Treat the
first kind as a transport failure and you will hide the message
that says what to do next.
Without an MCP client
The same tools are reachable over ordinary HTTP if you are building
something that does not speak MCP. Same credential, same quotas,
same responses:
POST https://workforcegpt.ai/api/v1/mcp/tools/<name>
with a JSON object of arguments.
curl -s https://workforcegpt.ai/api/v1/mcp/tools \
| jq '.tools[] | {name, scope}'
curl -s -X POST https://workforcegpt.ai/api/v1/mcp/tools/get_account_status \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{}'
The two public-library tools (search_public_roles and
get_public_role) need no credential at all on this
path. They read the same corpus search engines already index, so you
can see what a finished role profile looks like before anyone signs
up for anything. This one runs as it stands:
curl -s -X POST https://workforcegpt.ai/api/v1/mcp/tools/search_public_roles \
-H "Content-Type: application/json" \
-d '{"query": "project manager", "limit": 5}'
Over MCP itself every tool needs a token, including those two. That
is what keeps the 401 bootstrap intact, and a client
that has connected has a token anyway.