Skip to main content
Glama
Clockbook-com

Freelance MCP server

Official

Freelance MCP server

Where this lives. This repository is the distributable copy of the freelance MCP server. It is split out of the Clockbook enterprise monorepo (packages-enterprise/freelance/mcp), which remains the source of truth - changes land there and are mirrored here with git subtree. Two things are monorepo-only and deliberately absent: the live e2e harness, which boots the subgraph against a local Mongo, and the docs-catalogue generator, which writes into the freelance surface package.

npm run build validates all 23 tool documents against the subgraph SDL when it can reach it. Outside the monorepo it says so and skips, rather than failing a build it cannot perform. Point FREELANCE_SCHEMA_DIR at freelance/server/src/graphql/schema (and npm i --no-save graphql) to run that check from a standalone clone.

Point any MCP-capable AI at the Clockbook freelance marketplace: the talent directory, job postings, proposals, contracts with milestones and escrow, the wallet, messages and notifications. Twenty-three tools over the same GraphQL subgraph the freelance surface itself calls.

Works with Claude Desktop, Claude Code, Cursor, or anything else that speaks MCP over stdio.


What you need first

A platform API token. This module issues no credentials of its own. The platform does: mint a Secret API token from your Account page. Such a token already authenticates against the freelance subgraph with no further setup.

An agent identity registered against it — strongly recommended. An unregistered token arrives holding your entire seat, because the platform token has no notion of scopes and defineAbilityFor grants Manage to every org member. Registering the token as an agent identity is how you narrow it: the agent gets only the scopes you list, and you can switch it off later. It can never do more than your seat could; scopes only take away.


Related MCP server: upwork-mcp

Setup

1. Mint a token

Account page → Secret API tokens → create one. Copy it; you will not see it again.

2. Compute its digest

The raw token is never sent to the server during registration. You register the SHA-256 digest, and you compute it yourself:

node -e "console.log(require('crypto').createHash('sha256').update(process.argv[1]).digest('hex'))" <TOKEN>

That prints 64 lowercase hex characters. Nothing is lost by hashing client-side — whoever can register a token already holds it — and it means the bearer is never a mutation argument, never reaches a resolver, and cannot land in a trace or an error message.

3. Register the agent

Run this against your plane's freelance subgraph, signed in as a person — an agent may not register another agent, or the narrowest credential on the system could mint itself a wider sibling:

mutation {
    registerFreelanceAgentIdentity(
        input: {
            label: "Claude Desktop — my laptop"
            tokenDigest: "<the 64 hex characters from step 2>"
            scopes: [READ_DIRECTORY, READ_OWN]
        }
    ) {
        agentId
        label
        tokenHint
        scopes
    }
}

The scopes, and what each actually unlocks:

Scope

What it allows

READ_DIRECTORY

Read the talent directory and job postings.

READ_OWN

Read your own contracts, proposals, wallet and notifications.

DRAFT

Create and edit draft postings and templates. Nothing that reaches a person.

MESSAGE

Send messages in existing threads.

PROPOSE

Submit and withdraw bids.

HIRE

Publish a requisition, invite, accept a bid, end a contract. Commitments, not money.

SPEND

Fund, release, refund, cash out. The one nobody should grant casually.

Omit scopes entirely and you get READ_DIRECTORY and READ_OWN — read-only, which is the right shape to start with. Add more once you have watched it work.

Revoke at any time with revokeFreelanceAgentIdentity(agentId: "agent_…"). It bites on the agent's very next request, because liveness is part of the per-request lookup and not a nightly sweep.

4. Build the server

This package sits outside the yarn workspace, like freelance/surface — its own lockfile, isolated from monorepo hoisting skew:

cd freelance/mcp
npm install
npm run build

5. Wire it into your client

Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
    "mcpServers": {
        "freelance": {
            "command": "node",
            "args": ["/absolute/path/to/packages-enterprise/freelance/mcp/dist/index.js"],
            "env": {
                "FREELANCE_API_TOKEN": "<your platform API token>",
                "FREELANCE_GRAPHQL_URL": "https://freelance-backend.clockbook-app-v10.cdebase.dev/graphql"
            }
        }
    }
}

Restart the client. The absolute path is not optional — the client does not run this from your shell's working directory.

Claude Code — same JSON under mcpServers, or:

claude mcp add freelance \
  --env FREELANCE_API_TOKEN=<your platform API token> \
  --env FREELANCE_GRAPHQL_URL=https://freelance-backend.clockbook-app-v10.cdebase.dev/graphql \
  -- node /absolute/path/to/packages-enterprise/freelance/mcp/dist/index.js

Cursor.cursor/mcp.json in the project, or ~/.cursor/mcp.json globally. Same mcpServers shape as Claude Desktop.

6. First test call

Ask the assistant:

Using the freelance tools, who am I?

It should call freelance_get_my_profile and come back with your name, your email and whether your profile is listed in the directory. That one call proves all three things at once: the server started, the endpoint is right, and the token authenticates.

Then try a read that touches the marketplace:

Find me three people on the freelance marketplace who can do video editing.


Configuration

Setting

Environment variable

Config file key

Default

API token

FREELANCE_API_TOKEN

apiToken

(none — calls refuse)

Endpoint

FREELANCE_GRAPHQL_URL

graphqlUrl

https://freelance-backend.clockbook-app-v10.cdebase.dev/graphql

Environment wins over the file, always. The config file exists for the case the env lane handles badly — driving three clients without pasting the same secret into three JSON files that sync to three different places:

// ~/.freelance-mcp/config.json
{
    "apiToken": "…",
    "graphqlUrl": "https://freelance-backend.clockbook-app-v10.cdebase.dev/graphql"
}

Point FREELANCE_MCP_CONFIG elsewhere if you want the file somewhere else.

Set the endpoint if you are not on clockbook-app-v10. A deployment plane is a whole separate database, so a request that lands on the wrong one does not fail — it silently addresses an organization you do not have. The host is freelance-backend.<your-plane>.


The tools

Reads first, then the writes — which is also the order to use them in, because an id comes from a list and a title is never an id.

Directory and profile: freelance_get_my_profile, freelance_search_talent, freelance_get_profile

Job postings: freelance_search_jobs (the whole marketplace), freelance_list_org_jobs (your organization's own, any status), freelance_get_job, freelance_create_job

Proposals: freelance_list_my_proposals, freelance_list_proposals_for_job, freelance_submit_proposal, freelance_accept_proposal

Contracts and milestones: freelance_list_contracts, freelance_get_contract, freelance_add_milestone, freelance_submit_milestone, freelance_fund_milestone, freelance_refund_milestone, freelance_approve_milestone

Wallet: freelance_get_wallet

Messaging: freelance_list_conversations, freelance_get_conversation, freelance_send_message

Notifications: freelance_list_notifications

Two tools have side effects that are easy to miss: freelance_get_conversation marks the thread read, which clears the other side's unread signal — so do not sweep an inbox to summarise it. And freelance_send_message cannot be undone: there is no edit and no delete.


Money: the acknowledgement gate

Three tools move real money, and all three need the SPEND scope, which the server enforces:

  • freelance_fund_milestone — commit a milestone's amount to escrow. Reversible.

  • freelance_refund_milestone — take it back out. Reversible.

  • freelance_approve_milestone — on a funded milestone, this releases the escrow to the freelancer. Not reversible by anything in this product.

That last one requires acknowledge: true, and the flag means one specific thing: the person the agent is acting for was told the amount and the payee and said yes to that release. Omit it on a funded milestone and the call is refused — usefully:

This will release $1,250.00 USD to Dana Okafor, and it cannot be undone from
here. Re-send approveFreelanceContractMilestone with acknowledge: true to
release the escrow. [FREELANCE_ACKNOWLEDGEMENT_REQUIRED]

That sentence is the one to put in front of a person. The refusal is designed to be read, not logged — which is why the amount and the payee are written into the message itself and not only into extensions.

The gate is per call and never per session. A milestone's amount is editable until it is funded, so an acknowledgement carried over from an earlier call is consent to a different number.


When something is refused

What you see

What it means

[FREELANCE_ACKNOWLEDGEMENT_REQUIRED]

Confirm with the person, then re-send with acknowledge: true.

[FREELANCE_AGENT_SCOPE_REQUIRED]

The agent identity lacks the scope. A human grants it; retrying will not help.

HTTP 401 / HTTP 403

The platform token expired, or its agent identity was revoked. Mint a fresh token, register its digest again, update FREELANCE_API_TOKEN.

Could not reach the freelance backend at …

Wrong plane, or nothing listening. Check FREELANCE_GRAPHQL_URL.

No freelance API token is configured

Neither the env var nor the config file had one. The message names the exact file it looked in.

To see what the server thinks its configuration is, check your client's MCP log. On startup it writes one line to stderr naming the endpoint, where the token came from (env, file or none) and the tool count. It never prints the token or its digest.


Notes for maintainers

The GraphQL documents in src/tools.ts are copies of the ones in freelance/surface/src/api/operations.ts, because the surface is a browser package with its own lockfile and importing it would drag React into a stdio process. Copies drift, and a document that names a field the schema does not have fails whole — "Cannot query field", and that tool stops working.

Two things keep the drift survivable. The field lists here are deliberately slimmer than the surface's: everything these tools return is read by a language model, and the surface's lists exist to paint screens. Fewer fields is both less context burned per row and a smaller drift surface. And every field named here appears in a surface list already known to validate against this schema.

All 23 documents were validated against the subgraph SDL in freelance/server/src/graphql/schema/*.graphql, and every tool inputSchema was checked against the corresponding GraphQL input type — argument names, nested input-object field names and enum vocabularies. Worth redoing after any schema change.

One deliberate divergence: freelance_list_notifications declares side as an enum of FIND_WORK | HIRE_TALENT, while the schema types it as a plain String. A model left to guess sends "find_work" and gets an empty feed, which reads as "no notifications" rather than as an error. If the schema ever gains a real enum, use it and delete the list.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Connects AI agents to Upwork's GraphQL API, enabling job discovery, proposal management, profile tracking, and analytics.
    9 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to interact with any GraphQL API by introspecting the schema and exposing queries and mutations as MCP tools, with built-in pagination, semantic search, and framework adapters.
    14 npm
    MIT