MCP adapter for 99Freelas with local stdio support, remote Streamable HTTP support, encrypted session storage, and request-scoped operating controls.
Quick Start | Remote HTTP | Render Deploy | VPS Deploy | Tools | Security
This server exposes a focused Model Context Protocol interface for operating a 99Freelas account from an agent runtime. It keeps orchestration policy outside the MCP and provides deterministic tools for account sessions, project discovery, bid context, proposals, inbox workflows, profile updates, prompts, and reference resources.
The server supports two official MCP transport styles:
stdiofor local clients that spawn the process directly.- Streamable HTTP for remote clients.
The HTTP transport is intended for deployments such as Render. The health route is deliberately separate from MCP so the hosting platform can verify liveness without credentials.
- Encrypted cookie/session storage at rest.
- Account-scoped sessions using
accountId. - Agent correlation metadata using
agentId. - Project listing, availability scanning, detail pages, and bid context.
- Proposal sending with duplicate protection and dry-run support.
- Inbox conversation listing, thread reads, replies, directory counts, and notifications.
- Account dashboard, connection balance, and subscription status.
- Profile edit-state inspection, profile updates, public profile reads, and validated skill IDs.
- MCP prompts and resources for agent-oriented workflows.
- Render-ready
/healthzroute andrender.yamlblueprint.
Install dependencies:
npm installCreate your local environment file:
cp .env.example .envSet SESSION_ENCRYPTION_KEY_BASE64 to a base64 value that decodes to exactly 32 bytes.
Generate one with Node:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"Run the local stdio MCP server:
npm run devUse this mode when your MCP client starts the server as a local child process.
Run the Streamable HTTP MCP server locally:
npm run dev:httpMCP endpoint:
POST /mcp
GET /mcp
DELETE /mcp
Health endpoint:
GET /healthz
This repository includes render.yaml for Blueprint-based deploys.
Render settings:
- Runtime: Docker
- Health check path:
/healthz - MCP URL:
https://<your-service>.onrender.com/mcp - Start command: Dockerfile default,
node dist/http.js
Required Render environment variables:
SESSION_ENCRYPTION_KEY_BASE64=...
STATE_DB_FILE=.data/state.sqliteRecommended persistent disk variables:
STATE_DB_FILE=.data/state.sqlite
STATE_DB_JOURNAL_MODE=DELETE
LOG_FILE=.data/server.log
MANUAL_COOKIES_FILE=.data/manual-cookies.jsonThe included Blueprint already defines a /app/.data disk and sets these paths. Render preserves values marked with sync: false, so secrets should be entered in the Render dashboard.
The production path is webhook-driven. GitHub sends a push event to the VPS, the server verifies the signature, and the same deploy script updates the Swarm service in place.
Expected VPS layout:
/srv/99freelas-mcp-server/
.git/
deploy.env
data/
scripts/
src/
One-time setup:
- Clone this repository into
/srv/99freelas-mcp-server. - Copy
deploy.env.exampleto/srv/99freelas-mcp-server/deploy.envand fill the values. - Use Easypanel hostnames that resolve through the existing Traefik wildcard, for example
99freelas-mcp.8osm3q.easypanel.hostand99freelas-mcp-webhook.8osm3q.easypanel.host, then create a GitHub webhook forpushevents targetinghttps://<your-webhook-host>/webhooks/github. - Set
GITHUB_WEBHOOK_SECRETandGITHUB_WEBHOOK_HOSTNAMEindeploy.env. - Start the service with
docker service createor the existing Swarm deploy flow.
Example deploy.env contents:
MCP_HOSTNAME=99freelas-mcp.8osm3q.easypanel.host
SESSION_ENCRYPTION_KEY_BASE64=...
NINETY_NINE_BASE_URL=https://www.99freelas.com.br
ALLOW_MANUAL_COOKIE_FALLBACK=false
GITHUB_WEBHOOK_SECRET=...
GITHUB_WEBHOOK_BRANCH=master
GITHUB_WEBHOOK_REPOSITORY=daviiferrer/99frelaas-mcp-server
GITHUB_WEBHOOK_PATH=/webhooks/github
GITHUB_WEBHOOK_HOSTNAME=99freelas-mcp-webhook.8osm3q.easypanel.hostThe deploy script is scripts/deploy-vps.sh. It fetches master, rebuilds the image, and updates the running Swarm service behind Traefik without relying on GitHub Actions.
Build the image:
npm run docker:buildRun the HTTP container:
npm run docker:runOr use Compose:
docker compose up --buildThe container entrypoint runs the HTTP transport. For local stdio, use npm run dev directly from the repository.
Minimum required variables:
SESSION_ENCRYPTION_KEY_BASE64=...
STATE_DB_FILE=.data/state.sqliteOptional technical variables:
HOST=0.0.0.0
PORT=3000
MCP_HTTP_PATH=/mcp
NINETY_NINE_BASE_URL=https://www.99freelas.com.br
STATE_DB_FILE=.data/state.sqlite
STATE_DB_JOURNAL_MODE=WAL
RATE_LIMIT_REQUESTS_PER_MINUTE=60
ALLOW_MANUAL_COOKIE_FALLBACK=false
MANUAL_COOKIES_FILE=.data/manual-cookies.json
LOG_LEVEL=info
LOG_FILE=.data/server.log
LOG_STDERR=falseLegacy migration variables, only needed if old JSON state files still exist:
SESSION_FILE=.data/sessions.json
CACHE_FILE=.data/cache.jsonBusiness policy should be passed by request, not as global environment. For example, proposalsDailyLimit and operationTimeZone are optional fields on proposals_send.
Authentication is based on imported 99Freelas browser cookies. Raw cookies are never returned by tools.
Recommended flow:
- Export cookies from your browser.
- Call
auth_importCookieswithcookiesJson,cookies, orfilePath. - Call
auth_checkSession. - Use authenticated tools with the same
accountId.
Sessions are isolated by accountId, which lets one MCP process manage multiple account namespaces. Operational state is persisted in SQLite, including session records, dedupe markers, audit records, rate-limit windows, and request-scoped proposal counters when used.
This server is designed for trusted agent runtimes, not anonymous public use.
/healthzis public and returns only liveness metadata.- Session cookies are encrypted with
SESSION_ENCRYPTION_KEY_BASE64. - Logs and audit events redact sensitive values.
- Proposal and message duplicate checks are enforced by the MCP.
- Negotiation policy, budgets, approvals, and campaign rules belong in the calling agent or orchestration layer.
Treat SESSION_ENCRYPTION_KEY_BASE64 as a production secret.
Authentication:
auth_importCookiesauth_checkSessionauth_clearSessionauth_listSessions
Projects and proposals:
projects_listCategoriesprojects_listprojects_listByAvailabilityprojects_getprojects_getBidContextproposals_send
Inbox and notifications:
inbox_listConversationsinbox_getMessagesinbox_getThreadinbox_sendMessageinbox_getDirectoryCountsnotifications_list
Account:
account_getConnectionsaccount_getDashboardSummaryaccount_getSubscriptionStatus
Profile and skills:
profile_getInterestCatalogprofile_getEditStateprofile_updateprofiles_getskills_getCatalogskills_getStacksskills_getSelectionGuide
System:
system_health
screen_projects_for_fitanalyze_projectdraft_proposalreply_inboxmonitor_accountrefine_profile_skillsreview_99freelas_policies
resource://99freelas/server-manifestresource://99freelas/tool-catalogresource://99freelas/prompt-catalogresource://99freelas/operating-playbookresource://99freelas/quickstartresource://99freelas/skills-catalogresource://99freelas/skills-stacksresource://99freelas/skills-selection-guideresource://99freelas/policies-summary
Resource templates:
resource://99freelas/skills-catalog/page/{offset}resource://99freelas/skills-catalog/search/{query}
Recommended project flow:
projects_listorprojects_listByAvailability- Shortlist only high-fit items from list-level fields.
projects_getfor shortlisted projects.profiles_getwhen client context changes the decision.projects_getBidContext.proposals_sendwithdryRun=truewhen uncertain.proposals_sendwithout dry-run only when the bid context is eligible.
Recommended inbox flow:
inbox_getDirectoryCountsinbox_listConversationsinbox_getThreadinbox_sendMessage
The MCP executes scoped operations. The consuming agent should own long-running reasoning, schedules, approvals, campaign memory, and business strategy.
Build:
npm run buildTest:
npm testRun local stdio:
npm run devRun local HTTP:
npm run dev:httpMIT