MCP
Verified request, response and authorization contracts.
The resource origin is configured by MELSO_MCP_PUBLIC_URL (mcpURLFromEnv in router.go); the router itself has no hosted-origin default. /api/mcp and /mcp are mounted. Discover the actual resource from /.well-known/oauth-protected-resource. MCP uses JSON-RPC over HTTP, not a REST endpoint per tool.
Supported actors are an active Run token or an OAuth grant. A personal access token (PAT) is answered 401 here; PATs keep the REST API, including the knowledge (POST /api/knowledge/{operation}) and Artifact (POST /api/artifacts/{operation}) operations the CLI uses with them. Every connection acts in exactly one company: an OAuth grant in the company the person chose at consent, a Run token in its Task's company. Consent asks for one company; to work in another, connect again. Membership is checked on every call. Until the next cleanup release, a workspace argument from a client that cached an older inventory is accepted only when it names the connection's own company (id or slug) and is refused otherwise. Upload routes act in the connection's company. People see each OAuth connection with its company, and disconnect it, in Settings → MCP (GET /api/oauth/mcp/grants, DELETE /api/oauth/mcp/grants/{grantId}). The tool list depends on the credential profile; ask tools/list and use tools_search for advanced tool schemas, then tools_call for discovered tools. Do not infer available tools from this page alone.
The company's kind decides its tools: freelancer (Melso for autónomos), company (an empresa) or agents (Melso Agents), in workspace_status company.kind. tools/list, the action enums, the workspace_status sections and tools_search offer only what the kind allows, and anything else, called directly, as an action or section, or through tools_call, is refused with <tool> is not available in this company before it runs. Areas, GitHub, company_update, profile_update and the execution section are for Melso Agents; the fiscal tools are for autónomos and empresas. An autónomo's or an empresa's tasks_create and Automations take no area_id, execution, harness or model: the work runs with the company's default setup. An autónomo still in setup has only its setup tools: workspace_status (company, usage, connected_apps), fiscal_profile, memories, support tickets, tasks_control actions answer and decline, secrets action request, connected_apps_connect and the apps the member already connected, and, in the Melso chat, account_email.
The Run and OAuth profiles include seven core Task tools: tasks_create, tasks_get, tasks_list, tasks_update, tasks_send, tasks_control, and tasks_wait. Rarely called families are listed as one tool each with a required action, like tasks_control: areas (Melso Agents; list, get, search, create, update, delete, defaults_update, resources_list, resources_create, resources_update, resources_delete), the company's skills (list, get, search, install, update, delete), secrets and memories (search, list, save, forget). Each action accepts only its own fields, the same fields as the former single-purpose tool; any other field is rejected with an error naming the fields that action accepts. There are also workspace_status, knowledge and tool discovery. Personal memory is available to human OAuth clients, never Company or Area Runs. Member credentials can start work and answer or decline clarifications through tasks_control; Run credentials can only pause, resume or cancel a Run on their authorized Task. Run and OAuth profiles include secrets actions list and request; OAuth grants also get actions update and delete, and upload files to /upload-secret. tasks_wait watches one execution in an active Streamable HTTP SSE call for up to 600 seconds; JSON-only clients use a bounded short wait. It reports needs_input when a Run awaits a person. tasks_get lists the question in pending_interactions; an OAuth connection can answer or decline a clarification with tasks_control (answers keyed by question id). The recipient-or-admin rule still applies, and approvals and app-connection requests remain human actions: only the Melso chat agent, which is the person's own conversation, answers them, with decision (approve_once or deny) for an approval and answers: {} for a connect card, which completes only once its apps are connected. secrets action request returns {ok, request_id, url, kind, cta}; connected_apps_connect({toolkit_slug, area_id?, another_account?}) answers an app the member already has with its accounts and otherwise returns {ok, request_id, url, toolkit_slug, cta}. Both requests need the member signed in, never expire, are reused when the same conversation asks again and wait on the member's Home until they are answered or dismissed; answering one wakes the chat that asked or continues the Run's Task. cta is the link with a short label and one line of text, in Spanish for a member who chose it. An active external MCP call can deliver its final result; an ended client session has no generic wakeup from this tool.
Active Task Run credentials and the Melso chat agent also get core tasks_watch with actions watch (task_id, events?, instruction?), unwatch (task_id) and list. The subscriber is inferred: the Run's Task, or the chat; a watched Task need not be a child. Registration returns immediately: end the current turn and Melso resumes the same conversation when a selected event arrives. Watches persist across Runs until removed or the subscriber closes, with no acknowledgment step. Default events are delivered, failed, needs_input, completed, and cancelled; linked PR events pr_checks_finished and pr_checks_passed are opt-in and identify the commit SHA. Self-watches support PR events only. Runs and the chat share one subscription backend. External OAuth clients cannot use tasks_watch because they have no durable subscribing conversation. The former names watch_task, unwatch_task and list_task_watches still answer direct calls as hidden aliases during the deprecation window. See Task watches for REST contracts and authority.
workspace_status is one read-only call for the context to check before dispatching work. include names the sections to return and defaults to every section the connection may read: company (every profile), usage (OAuth and Run), connected_apps (member OAuth) and execution (member OAuth in Melso Agents). area_id and harness apply to the execution section only. The result has one key per requested section plus errors, keyed by section name: a section that fails is reported there without hiding the others.
Rarely needed operations are not listed. Find them with tools_search and invoke them through tools_call, for example {"name":"secrets_set","arguments":{...}}: skills_save (save a skill from its SKILL.md text), secrets_set (store a value you already hold; prefer secrets action request), github_installations_list, github_repositories_list, github_connect (an owner or admin's signed-in GitHub page and the request an installation answers), company_update and profile_update (these five in Melso Agents) and support_tickets_list, alongside Automations and Artifacts. The former per-operation tool names (such as areas_create, company_get or execution_options) still answer direct calls as deprecated, hidden aliases during a deprecation window, but tools/list no longer lists them; use the names above.
A member's OAuth connection also reaches their connected apps directly, with no Run and no sandbox, through the provider's session model. Melso keeps one provider session per member, company and account owner, holding exactly the accounts the member can use in the company the call acts in (their own accounts and accounts shared with that company, never another member's private account). It restores that session across calls and never hands its URL or key to the client. tools_search runs the session's own tool search, so describe one task in plain words, such as read my latest 3 emails or send a DM in Slack: plans match tasks, not tool names (slack tools matches none). The result's connected_apps holds results, the provider's results for the task field for field: primary_tool_slugs and related_tool_slugs and, when a known plan fits, recommended_plan_steps, known_pitfalls and difficulty. These three are optional; when present, follow the steps and avoid the pitfalls. connected_apps.tools lists the matched tools, primary first. Each adds app: the toolkit_slug, the app name, its effect (read, create, update, delete or unknown), its search role (primary or related) and its accounts, each with connected_account_id, account_identity when the app reports one (an email, a Stripe account name), a human description, status (active, expired, ...), reconnect_url when it is not active, ownership and whether it is the default. Its annotations follow the provider's verdict: only a pure read is readOnlyHint; sending, creating, paying, updating and deleting are destructiveHint, so a client confirms them with the person. A tool comes with its input schema plus connected_account_id, which is required when the app has more than one usable account; a related tool may be listed without one, and searching its exact name returns it. Run it with tools_call and its exact name, for example {"name":"GMAIL_FETCH_EMAILS","arguments":{"connected_account_id":"ca_...","max_results":3,"label_ids":["INBOX"]}}. The call runs in the request, in the session, with the account it names, and the app's data comes back inline. A result over 40 KB comes in parts: read the rest with connected_apps_result({cursor}) for an hour; it is never left in a file. An expired own account, or an app with no usable account, returns an error with the provider's connect link to connect it; an expired shared account names the member who can reconnect it. Read-only tools never wait on usage reconciliation; an action still waits while an earlier one is unresolved. Because it can run such actions, tools_call is annotated destructive and open-world. Runs and the Melso chat agent keep reaching apps through their own connected_apps server.
The company section of workspace_status returns the company name, description, website and instructions. company_update (found with tools_search) accepts name, website_url or instructions for an owner or admin. Saving a website does not research the company. The instructions reach every Run in the company. areas actions list and get include each Area's instructions; actions create and update can set them. Area instructions reach Runs in that Area. The MCP server does not expose Library files.* tools.
On connect the server identifies itself as melso with a title, description and website, and sends instructions that open by telling the client when to delegate: call tasks_create with the request as input for multi-step or long-running work, or work that needs the company's knowledge or connected apps. Some clients keep only the first 2,048 characters of instructions, so the delegation brief and the create, wait and read loop come first (mcpServerInstructions in handler/mcp.go).
In Melso Agents, before tasks_create or tasks_control with action: "start", call workspace_status with include: ["execution"] (optionally area_id, and harness for its model ids); the result is under execution. For each harness it says who pays for the model (funding: the person's connected AI account, a stored key, or Melso credits), how many AI accounts are connected, the company's credit state, and connect_url where the person connects an account. With no account connected, a client should offer that link before spending credits and keep the spend to one small Run; the melso-execution-options guide at https://melso.ai/skills/melso-execution-options.md covers choosing the harness and model.
Settings → MCP gives a short setup prompt that points an AI client at https://melso.ai/skills/melso-onboarding-mcp.md. That guide walks the client through connecting the remote MCP (or the CLI when it has no MCP), then setting up the company's Areas, skills, repositories and apps from what it already knows.
OAuth uses authorization code + PKCE S256, rotating refresh tokens, and scope mcp:tools. Fetch server metadata for the consent URL rather than constructing it. /api/oauth/mcp/authorize is the signed-in consent backend; it is not a substitute for user consent.
Examples use the environment variables from Overview. UUID placeholders must be replaced with your own IDs.
Financial tools
Workspaces with current manual-tool access expose financial operations as action families, using the same records and validations as the application:
| Tool | Actions |
|---|---|
customers | list, upsert |
invoices | list, get, prepare, request_approval, correct |
payments | mark_paid |
expenses | list, get, create, update, delete, from_document, upload, document_status |
income | list, get, update, delete |
taxes | calendar, list, get, request_approval, aeat_file |
fiscal_profile | get, update, set_registration, submit_setup (workspace owner/admin; set_registration saves whether the person is already registered, submit_setup sends a requested alta de autónomo to Melso's gestores) |
These fiscal tools are for autónomos and empresas: Melso Agents does not list them and refuses a call as <tool> is not available in this company, and an autónomo in setup has fiscal_profile alone. The records behind them also need a confirmed autónomo fiscal identity, and every tool but fiscal_profile current manual-tool access; a call without them is refused with an error that names what is missing. Use the action enum returned by tools/list; availability depends on the actor and workspace. Each action accepts only its own fields. For example, invoices with action: "get" takes id; expenses with action: "update" takes id, expected_version and changed fields. Read the current expense version first. Edits invalidate review and cannot alter filed evidence.
from_document reads an invoice or receipt and records it as an expense or an income. An agent that can run commands sends a local file with upload: it answers an upload_url to PUT the file to and an upload_id for from_document; the read runs in the background and document_status reports it. Without a shell, share web_upload_url so the person drops the files on the Accounting page.
taxes reads the tax calendar and the returns Melso calculates, and aeat_file answers a 303, 130, 111 or 115 as the file the AEAT form imports. request_approval answers the authenticated web page where the owner reviews and authorizes the exact version; its workspace_id selects the connection's company after the browser checks membership. This opens a gestión for Melso's professionals to review and file with Hacienda under the required authority. An unchanged authorized version proceeds without another approval; a changed draft needs approval of the new version. Authorization is separate from filing: completion requires the actual external reference and receipt. No MCP tool approves, files or pays a return. delete on an expense or an income is refused once a return covering its date is approved or filed.
Drafts and approval previews do not issue invoices. Issuance requires the user's authenticated web approval. payments records a confirmed full payment on an issued invoice; it never charges or transfers money. Fiscal updates save valid fields and report field errors and mask the IBAN. Manual tools do not consume AI credits; an Assistant conversation still requires its own funding.
Gestoría client documents use the existing Knowledge library. Fiscal conversations use Gestor chat on web or WhatsApp. support_tickets_create and catalog-only support_tickets_list are platform support; type service_request is work Melso people do that no tool covers. Provider-only service_* tools manage assigned cases, explicitly shared documents and preparation/review states; they never expose the customer's whole library to a professional Run.
Company knowledge and Artifacts
Knowledge is available to every company. Use knowledge_search, knowledge_get
and knowledge_write for pages and table records; discover schema, view, history
archive and delete operations through tools_search. knowledge_delete is a restorable soft delete; confirm with the user before using it. Company and Area permissions apply
to every call. Runs stay within Company plus their Task's current Area; isolated
Runs cannot access knowledge or Artifacts.
Discover artifacts_list, artifacts_get, artifacts_deploy and
artifacts_manage with tools_search and invoke them through tools_call.
Agents build static HTML or Vite apps in their sandbox, then use
melso artifacts deploy ./dist --source ./app --publish. Control operations use
the remote MCP in a Run and the REST twin when a person runs the CLI; ZIPs travel
over version-scoped HTTP upload tickets. Omit
--publish to create a private preview. melso artifacts checkout ID --version VERSION_ID --dir ./app retrieves a retained private source snapshot for editing.
Every completed version is retained. Publishing an older version changes code
while keeping current table data and permissions. Public links have explicit
collection, field and action grants; anonymous forms support create-only
submissions. Generated apps use window.melso through the isolated viewer and
never receive a PAT, cookie or general MCP credential. See the
Artifact architecture and CLI guide
for bindings, SDK calls, hosting and limits.
GET /.well-known/oauth-protected-resource
Auth: Public.
Request: No body.
Response: {resource:string,authorization_servers:string[],bearer_methods_supported:string[],scopes_supported:string[]}.
Status: 200; 503 OAuth URL not configured. Authentication and resource-access errors follow Overview.
curl -sS -X GET "$MELSO_URL/.well-known/oauth-protected-resource" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID"GET /.well-known/oauth-authorization-server
Auth: Public.
Request: No body.
Response: OAuth metadata including issuer, authorization/token/registration/revocation endpoints, grant types, PKCE methods and supported scope.
Status: 200; 503 OAuth URL not configured. Authentication and resource-access errors follow Overview.
curl -sS -X GET "$MELSO_URL/.well-known/oauth-authorization-server" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID"POST /oauth/register
Auth: Public dynamic client registration.
Request: client_name:string (1–200), redirect_uris:string[] (1–10), optional grant_types:string[], token_endpoint_auth_method:"none".
Response: Registered client metadata with client_id; no client secret.
Status: 201; 400 invalid metadata; 429 rate limited. Authentication and resource-access errors follow Overview.
curl -sS -X POST "$MELSO_URL/oauth/register" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" \
-H "Content-Type: application/json" -d '{"client_name":"Factory","redirect_uris":["http://127.0.0.1:8080/callback"],"token_endpoint_auth_method":"none"}'POST /oauth/token
Auth: OAuth client with valid code/verifier or refresh token.
Request: Form-urlencoded: authorization code grant uses grant_type=authorization_code, client_id, code, redirect_uri, code_verifier, resource as required by the grant. Refresh uses grant_type=refresh_token, client_id, refresh_token.
Response: {access_token:string,refresh_token:string,token_type:"Bearer",expires_in:integer,scope:string}.
Status: 200; 400 invalid grant; 429 rate limited. Authentication and resource-access errors follow Overview.
Form example: curl -sS "$MELSO_URL/oauth/token" --data-urlencode grant_type=refresh_token --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "refresh_token=$REFRESH_TOKEN".
curl -sS "$MELSO_URL/oauth/token" --data-urlencode grant_type=refresh_token --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "refresh_token=$REFRESH_TOKEN"POST /oauth/revoke
Auth: Token holder.
Request: Form-urlencoded token:string.
Response: Empty.
Status: 200; 400 missing token. Authentication and resource-access errors follow Overview.
Form example: curl -sS "$MELSO_URL/oauth/revoke" --data-urlencode "token=$MCP_TOKEN".
curl -sS "$MELSO_URL/oauth/revoke" --data-urlencode "token=$MCP_TOKEN"GET /api/oauth/mcp/authorize
Auth: Signed-in member; validates the OAuth client and requested resource.
Request: OAuth authorization query fields: client_id, redirect_uri, response_type=code, code_challenge, code_challenge_method=S256, optional scope/resource as validated by the handler.
Response: {client_name:string,scope:string}.
Status: 200; 400 invalid consent request. Authentication and resource-access errors follow Overview.
curl -sS -X GET "$MELSO_URL/api/oauth/mcp/authorize" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID"POST /api/oauth/mcp/authorize
Auth: Signed-in member approving access to their selected workspace.
Request: client_id:string, redirect_uri:string, state:string, code_challenge:string, resource:string, scope:string, workspace_id:UUID (required: the one company the connection acts in; all_workspaces is refused).
Response: {redirect_uri:string} containing a short-lived authorization code.
Status: 200; 400 invalid consent, no company or all_workspaces; 403/404 workspace access denied. Authentication and resource-access errors follow Overview.
curl -sS -X POST "$MELSO_URL/api/oauth/mcp/authorize" \
-H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" \
-H "Content-Type: application/json" -d '{"client_id":"client-id","redirect_uri":"http://127.0.0.1:8080/callback","state":"random-state","code_challenge":"S256-challenge","resource":"https://mcp.melso.ai/mcp","scope":"mcp:tools","workspace_id":"00000000-0000-4000-8000-000000000001"}'POST /api/mcp
Auth: MCP OAuth access token or active Run token, each acting in its one company. A personal access token is refused with 401.
Request: JSON-RPC {jsonrpc:"2.0",id:string|number,method:string,params?:object}. Initialize first. For tools/call, params are {name:string,arguments:object}, for example {"name":"areas","arguments":{"action":"create","title":"Product"}}.
Response: JSON-RPC {jsonrpc:"2.0",id,result} or {jsonrpc:"2.0",id,error}; tool results include content blocks and optional structured data.
Status: 200 JSON-RPC response; 202 notification; 400/403 invalid protocol or actor; 401 personal access token; 413 oversized request. Authentication and resource-access errors follow Overview.
Replace $MELSO_TOKEN in this example with $MCP_TOKEN. For a legacy 2025 client send Accept: application/json, text/event-stream and its negotiated MCP-Protocol-Version. Modern clients must use their negotiated protocol headers; the server validates protocol era in mcp_protocol.go. The same POST handler is available at /mcp.
curl -sS -X POST "$MELSO_URL/api/mcp" \
-H "Authorization: Bearer $MCP_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -H "MCP-Protocol-Version: 2025-06-18" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'POST /api/mcp/upload-file
Auth: MCP OAuth token; acts in its granted workspace. Run credentials retain their Task scope.
Request: Multipart file:binary required; same size limit (under 100 MiB per request on melso.ai and mcp.melso.ai) and optional Task/message/Run association fields as POST /api/upload-file in Tasks. Upload each file separately.
Response: AttachmentResponse (see Tasks). Pass returned IDs as attachment_ids:UUID[] to tasks_create or tasks_send.
Status: 200; 400 invalid upload; 403 denied; 503 storage unavailable. Authentication and resource-access errors follow Overview.
Append /upload-file to the MCP resource URL discovered from metadata. HTML, PDF, image and video files share this request shape. Starting a Task still requires nonempty input.
curl -sS -X POST "$MELSO_URL/api/mcp/upload-file" \
-H "Authorization: Bearer $MCP_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" -F "file=@report.pdf"POST /mcp/upload-file
Auth: MCP OAuth token; acts in its granted workspace. Run credentials retain their Task scope.
Request: Multipart file:binary required; same size limit (under 100 MiB per request on melso.ai and mcp.melso.ai) and optional Task/message/Run association fields as POST /api/upload-file in Tasks. Upload each file separately.
Response: AttachmentResponse (see Tasks). Pass returned IDs as attachment_ids:UUID[] to tasks_create or tasks_send.
Status: 200; 400 invalid upload; 403 denied; 503 storage unavailable. Authentication and resource-access errors follow Overview.
Append /upload-file to the MCP resource URL discovered from metadata. HTML, PDF, image and video files share this request shape. Starting a Task still requires nonempty input.
curl -sS -X POST "$MELSO_URL/mcp/upload-file" \
-H "Authorization: Bearer $MCP_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" -F "file=@report.pdf"POST /mcp/upload-skill
Auth: MCP OAuth token or Run credential; acts in its workspace. Also served at /api/mcp/upload-skill.
Request: Multipart file:binary required: a .zip or .skill archive of one skill folder with its SKILL.md, scripts and binary files. Optional on_conflict: fail (default), overwrite, rename or skip.
Response: {status:"created"|"updated"|"skipped"|"conflict"|"failed",reason?:string,skill?:SkillResponse,existing_skill?:{id,name}}.
Status: 201 created; 200 updated or skipped; 400 not a multipart archive; 403 denied; 409 a skill with that name exists; 413 too large. Authentication and resource-access errors follow Overview.
Use it for a skill folder that skills action install cannot fetch, such as one that exists only on your machine. The skill is installed in every Run.
(cd ~/.claude/skills && zip -r /tmp/pdf.zip pdf)
curl -sS -X POST "$MELSO_URL/mcp/upload-skill" \
-H "Authorization: Bearer $MCP_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" -F "file=@/tmp/pdf.zip"