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/mcpPOST 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.
Server identity
Section titled “Server identity”| 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.
Protocol negotiation
Section titled “Protocol negotiation”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.
Authorization
Section titled “Authorization”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.
Discovery
Section titled “Discovery”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"]}OAuth endpoints
Section titled “OAuth endpoints”| 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.
Access tokens
Section titled “Access tokens”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.
The access model
Section titled “The access model”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.listknowledge.list knowledge.search account.readinessagent.get agent.brief agent.brief_item_versionswidget.list widget.config_getAnything 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.
Business and apps
Section titled “Business and apps”| 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. |
Agent Brief
Section titled “Agent Brief”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. |
Knowledge, help centre, catalog
Section titled “Knowledge, help centre, catalog”| 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. |
Widget
Section titled “Widget”| 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.
Onboarding
Section titled “Onboarding”| 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. |
The prompt is generated, not stored
Section titled “The prompt is generated, not stored”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.
Failures
Section titled “Failures”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.
Observability
Section titled “Observability”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.