Skip to content
ProductOpen console

MCP server

The workspace MCP server lets an MCP client administer one business. It speaks Streamable HTTP with JSON-RPC 2.0 request bodies.

POST https://mw.omazy.ai/api/v1/mcp

POST is the only method. GET returns 405: there is no server-initiated stream.

For connecting a client, see Connect an AI assistant. This page is the specification.

Field Value
name omazy-admin
title Omazy Workspace
capabilities tools

description, websiteUrl and icons are also returned, but only when the negotiated protocol revision defines them. Older revisions get the three fields above and nothing else, because sending fields a revision does not define is how you break strict clients.

Send your revision in initialize. The server answers with the revision it will actually use, which may be older than the one you asked for. Use the value in the response, not the one you sent.

Every method requires a token, including initialize and ping. There is no unauthenticated handshake.

This is deliberate and worth understanding if you are building a client. Answering 200 to an unauthenticated initialize tells the client that no authorization is needed here, so after it completes OAuth it has no auth requirement recorded and never attaches the token it just obtained. The connection then fails in a way that looks like the server rejected a valid key.

An unauthenticated call gets 401 with a challenge:

WWW-Authenticate: Bearer realm="OAuth",
resource_metadata="https://mw.omazy.ai/.well-known/oauth-protected-resource",
error="invalid_token", error_description="Missing or invalid access token"

That header is the entry point. A client with no prior knowledge of this server can start from the 401 alone.

Both documents sit at the origin root, per RFC 9728 and RFC 8414.

Document Path
Protected resource /.well-known/oauth-protected-resource
Authorization server /.well-known/oauth-authorization-server

The protected-resource document names the resource, the authorization server, and the scopes:

{
"resource": "https://mw.omazy.ai/api/v1/mcp",
"resource_name": "Omazy Workspace",
"authorization_servers": ["https://mw.omazy.ai"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["mcp:read", "mcp:write"]
}
Purpose Endpoint
Dynamic registration (RFC 7591) /api/v1/oauth/register
Authorize /api/v1/oauth/authorize
Token /api/v1/oauth/token
Revoke (RFC 7009) /api/v1/oauth/revoke
Property Value
Grant types authorization_code, refresh_token
Response types code
PKCE S256, required
Client authentication none (public clients)
Scopes mcp:read, mcp:write

Registration is open, and the redirect_uri allowlist is frozen at registration and matched exactly. Hosted https:// callbacks are accepted, as are loopback http:// on 127.0.0.1, ::1 and localhost, and custom schemes. Public plain-http and URIs carrying a fragment are rejected.

Authorization codes are single-use and expire in 60 seconds. Refresh tokens rotate on use, and replaying a spent one revokes the entire token family including its access key, on the assumption that a replay means a copy leaked.

An issued access token is an ordinary workspace API key. The OAuth flow is a minting front-end, not a second identity system, so the auth gate, the role model and the request log are the same whether a key arrived through OAuth or was minted by hand.

Keys last 7 days by default, 90 at the most, and are stored hashed. Refresh before expiry or reconnect.

Three things bound what a call can do. All three apply.

Workspace scope. A key is bound to one workspace, decided at approval time from the approver’s real membership. A client cannot widen it by asking, and a tool that needs a workspace refuses an account-scoped key outright.

Role. Tools declare a minimum role, and the key carries the approver’s actual role. RoleAccount tools are reachable by any authenticated key, RoleMember needs member or above, RoleManager needs manager or above.

Scope allowlist. A key may carry an explicit list of tool names. When present, anything not on it is refused regardless of role. This is what implements read-only, and it is the reason read-only is not expressed by role alone: workspace_create is an account-scoped tool, so a member-role key could otherwise still reach it.

A read-only connection is issued at member role carrying this allowlist:

workspace_list workspace_get app_list
knowledge_list knowledge_search account_readiness
agent_get agent_brief agent_brief_item_versions
widget_list widget_config_get

Anything absent is unavailable, so an error in that list withdraws access rather than granting it.

26 tools. app is optional everywhere it appears and defaults to the workspace’s default app.

Names are alphanumerics and underscores only, matching ^[a-zA-Z0-9_]{1,64}$. That is the intersection of what MCP clients accept: some allow a dot, others silently filter out any tool whose name contains one, which presents as a server with no tools rather than as a naming error.

Tools were previously named noun.verb. The dotted spelling is still accepted on tools/call, and per-key scope allowlists are matched in canonical form, so a client or key holding an old name keeps working. Only the underscored names are listed.

Tool Role Required Does
workspace_list account Businesses the account belongs to.
workspace_get member The business this key is scoped to.
workspace_create account business_name New business, default app, draft agent.
app_list member Apps in the business.
account_readiness member Onboarding readiness score.
Tool Role Required Does
agent_get member Full agent config: identity, live prompt, model config, capabilities, suggestion pills, and which layer owns the prompt.
agent_update manager Agent metadata. Refuses system_prompt on a Brief-managed agent.
agent_suggestions_set manager rules Replace the answer suggestion pills. Live immediately.

The Brief is the versioned source the system prompt is composed from. Edits are staged; nothing reaches the live agent until you publish.

Tool Role Required Does
agent_brief member Items, composed text, published text, and dirty.
agent_brief_item_set manager Create a block, or edit one by item_id.
agent_brief_item_enable manager item_id, enabled Include or exclude a block.
agent_brief_item_versions member item_id Prior versions of a block, newest first.
agent_brief_item_restore manager item_id, version Roll a block back.
agent_brief_item_delete manager item_id Delete a block and its history.
agent_brief_publish manager Compose the enabled blocks into the live prompt.
Tool Role Required Does
knowledge_list member Documents on the agent.
knowledge_search member query Retrieval over the knowledge base.
knowledge_add manager kind, title Add a document. Indexed on publish.
helpcenter_add_article manager title, body Add a help article.
catalog_add_product manager name Add a product.
Tool Role Required Does
widget_list member Deployments, with ids for the config tools.
widget_config_get member widget_id The editable config document.
widget_config_set manager widget_id, config Replace the config. Draft unless publish is true.
widget_create manager Create and publish a widget, returning the install snippet.

widget_config_set replaces the whole document. Read the current one, apply your edits, send it all back.

Tool Role Required Does
intake_propose manager website_url Read a website and propose brand, persona and FAQs.
onboard_business manager website_url Run the onboarding pass end to end.

agents.system_prompt is a derived column on any agent that has Brief items. Publishing composes the enabled blocks and overwrites it wholesale.

A direct write to the prompt therefore survives only until the next publish. The change looks applied, the agent answers differently for a while, and then it reverts with no record of what it replaced.

agent_get reports which layer owns the prompt:

{
"prompt": {
"characters": 6037,
"source": "brief",
"brief_items": 11,
"warning": "system_prompt is composed from the Brief and will be overwritten on the next publish..."
}
}

source is brief or direct. When it is brief, agent_update refuses a system_prompt write and names the tools to use instead. It also refuses when it cannot determine the answer, because an irreversible overwrite is not a safe default for an unknown. Pass force: true to override, accepting that the next publish undoes it and the Brief is not updated to match.

The supported path is agent_brief_item_set, then agent_brief_publish.

Protocol failures come back as JSON-RPC errors: -32700 parse error, -32601 unknown method, -32602 unknown tool, -32001 unauthorized.

Tool failures do not. They return a normal result carrying isError, which is what lets a model read the message and correct itself:

{
"content": [{ "type": "text", "text": "this agent's system_prompt is composed from 11 Brief items..." }],
"isError": true
}

The message is prose, meant to be read. A stable failure code is recorded against every call in the activity log, but it is not returned in the response, so there is currently nothing machine-readable to branch on. Treat isError as the signal and the text as the explanation.

Every call is recorded with its tool, outcome, failure code and duration. Arguments are never stored.

Owners and managers can read this in the console under Settings → Connected AI, alongside each connection’s last-used time and call count. A connection showing zero calls has never reached the server, which distinguishes a client that failed to connect from one that connected and did nothing.

Omazy CX documentation. One voice. Every channel. Always on.

Founded by Mosthofa Imran · imran@omazy.ai