Environment: prod
Model Context Protocol server for SuperOffice CRM. Connects an MCP-aware AI client to SuperOffice search, contact, and identity tools.
Currently available by invitation only.
https://mcp.superoffice.com/mcp
Clients authenticate via SuperOffice OpenID Connect. The server proxies OIDC to SuperOffice and requires a valid access token on every request.
Download the bundle and open it in any MCPB-compatible MCP client for one-click installation and automatic OAuth login.
Download SuperOffice MCP bundle (.mcpb)Add this to your MCP client configuration (for example, Claude Desktop's mcpServers block):
{
"mcpServers": {
"superoffice": {
"url": "https://mcp.superoffice.com/mcp"
}
}
}
Step-by-step guides for connecting your AI client:
get_appointment — Returns the full appointment record plus href (direct CRM web UI link).
Prefer this over search_appointments when the appointment_id is already known.
Cross-reference IDs (contact_id, person_id, project_id, sale_id) are included
for navigating to linked entities.get_company — Returns the full company record plus `href` (direct CRM web UI link).
Prefer this over search_companies when the company_id is already known.get_document_contents — Return a document's raw file contents (the original file) as a file/blob.
Fetches the actual bytes of a stored CRM document (Office file, PDF, or
email) by its `document_id` — call search_documents first to find the ID.
The result is an MCP-native file block: consume it directly to read or
analyze what the document contains, or hand it back to the user as the
original file.
To just read/summarize what a document says, prefer get_document_markdown
instead — markdown text is far cheaper to consume than raw file bytes. Use
this tool when the user needs the file itself (to download, or when the
format matters, e.g. an image or a template to reuse).
Documents whose content lives in SharePoint cannot be fetched with the
MCP's credentials and fail with an auth error; contents larger than 20 MB
are rejected rather than returned.get_document_markdown — Return a document's contents as markdown text — the cheap way to read what it says.
Fetches a stored CRM document (Office file, PDF, or email) by its
`document_id` — call search_documents first to find the ID — and converts
it to markdown, which costs far fewer tokens to consume than the original
file. Prefer this over get_document_contents whenever the user just wants
to know what a document contains; fall back to get_document_contents only
when the original file itself is needed (download, or a format markdown
can't represent, e.g. an image).
Documents whose content lives in SharePoint cannot be fetched with the
MCP's credentials and fail with an auth error. Unsupported file formats
(e.g. legacy .doc/.ppt, images) fail with a validation error.get_person — Returns the full person record plus `href` (direct CRM web UI link).
Prefer this over search_persons when the person_id is already known.get_project — Returns the full project record plus href (direct CRM web UI link).
Only use this over search_projects when the project_id is already known.
Use list_project_activities to fetch activities linked to the project.get_sale — Fetch the complete record for one sale by its numeric ID.
Returns all sale fields (heading, amount, earnings, probability, description,
status, stage, owner, linked company/person), plus HasGuide (whether a stage
guide exists) and href — a direct link to the sale in the CRM web UI. Include
the href when presenting a sale to the user so they can open it directly.
Call this before get_sale_guide to check HasGuide.get_sale_guide — Return the pipeline guide for a sale: current stage, next stage, and all
stages in order (each with its win probability), and href.
Only call this when get_sale returned HasGuide=true. If HasGuide is false,
no guide exists for this sale type — tell the user that instead of calling
this tool.get_ticket — Returns a ticket — its conversation, as message-thread summaries, newest first.
This is the ticket's actual content: what was said, and by whom. Call this
for any question about a ticket's messages, conversation, history, or who
said/replied what (e.g. "show me ticket 123", "message history", "what did
they say", "who has replied"). For the ticket's bookkeeping fields instead
(status, category, owner), call get_ticket_metadata.
messages are METADATA only — author, date, security level (Internal/External),
sentiment, attachment count, and badge (Reply/Forward/Comment/Incoming/Outgoing —
use this to see who has replied). It does NOT return the message text; the first
entry is the most recent message — use get_ticket_message with this
ticket_id and the message's ticket_message_id to read the full body.
Capped at `limit` (default 25, max 100). has_more=true means older messages
exist beyond that cap — since ordering is newest-first, what's missing is the
START of the thread (often the customer's original problem statement), not the
latest activity. Never present a capped list as the complete conversation; if
you need the exact total, call get_ticket_metadata's num_messages.get_ticket_message — Returns the full content of one ticket message: its plain-text body, plus
html_body only for HTML-only messages that have no plain-text body (empty
otherwise, to avoid sending the same content twice).
Use get_ticket first to find the ticket_id and ticket_message_id of the
message you want to read — never guess a message_id. An unknown message_id
raises a ToolError listing that ticket's valid message IDs so you can retry
with a real one; a message_id that belongs to a different ticket raises a
mismatch error naming the ticket that actually owns it.get_ticket_metadata — Returns a ticket's bookkeeping record — status, category, owner, counts,
cross-reference IDs, and href. This is NOT the ticket's conversation and
contains no message text; for what was said or who replied, call
get_ticket(ticket_id) instead.
Prefer this over search_tickets when the ticket_id is already known.
Cross-reference IDs (contact_id, sale_id, project_id) are included
for navigating to linked entities.list_contact_activities — Returns activities (appointments, documents, chats, mailings, form submissions) for a company.
Use get_company or search_companies to find the contact_id first.
Results are sorted newest first.list_person_activities — Returns activities (appointments, documents, chats, mailings, form submissions) for a person.
Use get_person or search_persons to find the person_id first.
Results are sorted newest first.list_project_activities — Returns activities (appointments, documents, mailings) for a project.
Use search_projects or get_project to find the project_id first.
Results are sorted newest first.list_project_members — List the members (people involved) on a project.
The first entry (when present) is the project-card owner (responsible associate),
flagged is_root=True — the implicit "root" member that isn't an explicit
ProjectMember row. Treat it as an important member even though it comes from the
project card rather than the member list. The remaining entries are the explicit
members, each with name, role, linked person/company, and href — a link to the
member (person) in the CRM web UI.
Note: the root member is a forward-only annotation. Because it has no ProjectMember
row, list_projects_for_member(person) will NOT return this project for the root
member — only for the explicit members shown here.
Get the project_id from search_projects or get_project first.list_projects_for_member — Find the projects a person is currently a project-member of (reverse of list_project_members).
Returns membership only — projects where the person is an actual project-member,
NOT projects where they are merely the project-card owner. One row per project with
project_name, project_id, the role, the project's status name, its end_date, and
href (a direct link to the project).
Asymmetry with list_project_members: this excludes the project-card owner (the
is_root=True member there). So a person shown ONLY as the root member of a project
will not have that project returned here — the root member is a forward-only
annotation, not a symmetric membership link. Explicit members round-trip both ways.
These are the person's current memberships — SuperOffice doesn't retain
removed/former members, so a project the person was once a member of but has since
been removed from can't be listed.
No active/ended filter: SuperOffice project status is a tenant-configured list with
no system "completed" flag (status ids and names differ per database and language),
so the server can't classify active vs ended across tenants. Each row instead carries
the raw status (in the tenant's language, e.g. "Avsluttet" or "Completed") and
end_date — filter on those yourself (e.g. treat a past end_date as expired, or a
status you recognise as finished).
Resolve the person first: search_persons by name, or whoami() for
"projects I'm a member of".list_sale_activities — List activities (appointments and documents) linked to a sale, newest first.
Returns type, date, heading, owner, and completion status per activity.
Get the sale_id from search_sales or get_sale first.list_sale_stakeholders — List the stakeholders (people involved) on a sale.
The first entry (when present) is the sale-card's main contact/person, flagged
is_root=True — the implicit "root" stakeholder that isn't an explicit
SaleStakeholder row. Treat it as an important stakeholder even though it comes
from the sale card rather than the stakeholder list. The remaining entries are
the explicit stakeholders, each with name, role (e.g. decision maker), linked
person/company, email, phone, any comment, and href — a link to the stakeholder
(person, or company) in the CRM web UI.
These are the sale's current stakeholders — SuperOffice doesn't retain
removed/former stakeholders, so past stakeholders can't be listed.
Get the sale_id from search_sales or get_sale first.list_sales_for_stakeholder — Find the sales a person or company is currently a stakeholder on (the reverse of
list_sale_stakeholders).
Provide exactly one of:
- person_id — "which deals is <person> a stakeholder on".
- company_id — "which deals is <company> a stakeholder on" — includes both the
company itself as a stakeholder and any of its people as stakeholders.
Returns one row per sale with sale_name, sale_id, status, the role (e.g. decision
maker), the linked company, any comment, and href — a direct link to the sale in
the CRM web UI (include it when presenting a sale).
Only current stakeholders are returned — SuperOffice keeps only present
stakeholder links, so someone removed as a stakeholder won't appear and there is no
way to list past/former stakeholders. The `status` filter scopes by the sale's own
status (open vs closed/won/lost), not by stakeholder history: "all" (default),
"open", or "closed" — a person can still be a current stakeholder on a closed sale.
Resolve the entity first: search_persons / search_companies by name, or whoami()
for "sales I'm a stakeholder on". Note this is distinct from
search_sales(person_id=...), which matches the sale's primary linked person.list_user_activities — Returns activity-archive items (follow-ups, tasks, documents) for a user.
Do NOT use for "diary", "calendar", "schedule", or "appointments" queries — use search_appointments instead.
Call whoami() first to get the associate_id for the current user.
Results are sorted newest first.search_appointments — Search for appointments (meetings, calls) in the CRM database.
Use this for any query about a user's diary, calendar, schedule, or appointments —
this is the authoritative source for what is booked in a user's diary.
Returns up to `limit` matching results.
"my" / "our" queries — call whoami() first:
- "my appointments" / "appointments I own" / "my calendar"
-> pass associate_id from whoami() as our_contact_id.
- "appointments at my company" -> pass company_id from whoami().search_companies — Search for companies in the CRM database.
Returns up to `limit` matching results; use `get_company` with the returned `company_id` for the full record.
"my" / "our" queries — call whoami() first, then choose the right parameter:
- "my company" (the company the user belongs to) -> pass company_id from whoami().
- "my companies" / "companies I manage" (companies assigned to the user)
-> pass associate_id from whoami() as our_contact_id.search_documents — Search for documents in the CRM database.
A document is anything stored in CRM as a file — Office files (Word,
Excel, PowerPoint), PDFs, and emails.
Two different filters narrow by document kind, and picking the wrong one
silently loses rows:
- `is_email` — is it an email at all? Covers every email template at once.
A question about "emails" is a document query: use this tool with
`is_email=true`. Results also carry an `is_email` flag.
- `document_template` — one specific template ("find quotes/offers/contracts").
Templates are tenant-defined, so the name follows the tenant's language (a
quote may be "Tilbud" in a Norwegian tenant). Results carry `archive_type`.
Never use `document_template` to find emails. A tenant usually has several
email templates — incoming and outgoing are separate ones — so naming just one
drops the rest with no error. Subject keywords are language-dependent too:
prefer `document_template` over subject for a kind question, and fall back to
subject only with the tenant's own wording.
There is no separate get_document tool: pass `document_id` to look up one
document by ID. The company/person/sale/project filters match documents
linked to that entity, not its sender or author.
Lookup patterns — call the named tool first to get the required ID:
- "documents for company X" -> search_companies(name="X"), then company_id.
- "my documents" -> whoami(), then our_contact_id.search_external_companies — Search EXTERNAL sources for companies — national business registries
and similar sources outside SuperOffice. This does NOT search the
tenant's own CRM contacts; use search_companies for that. Useful for
finding real, verified company data when the company may not already
exist as a CRM contact.search_persons — Search for persons/contacts in the CRM database.
Returns up to `limit` matching results; use `get_person` with the returned `person_id` for the full record.
"my" / "our" queries — call whoami() first:
- "people at my company" / "my colleagues" -> pass company_id from whoami().search_projects — Search for projects by name and/or owner.
At least one of name, our_contact_id, project_owner_full_name, or is_guided must be provided.
For 'list guided projects' / 'find guided projects' queries, set is_guided=True.
Returns matching projects with ID, name, type, status, dates, and owner.
Use get_project with the returned project_id for the full record.
Results are sorted by name.search_sales — Search sales (opportunities) in the CRM.
At least one filter parameter is required — a call with no filters fails.
Returns up to `limit` lightweight rows: sale_id, heading, amount (tenant
currency), close date, status, stage, owner, and linked company/person.
For the full record (description, earnings, probability, guide status,
web link) call get_sale with the returned sale_id.
Lookup patterns — resolve IDs with the named tool first:
- "sales for company X" -> search_companies(name="X"), pass contactId as company_id
- "my sales / sales I own" -> whoami(), pass associate_id as our_contact_id
- "sales at my company" -> whoami(), pass company_id
- sale by name -> heading="..." (prefix match on the title)
If nothing matches, broaden the search: drop stage/status, widen the date
range, or shorten the heading prefix.search_tickets — Search for tickets (service requests) in the CRM database.
Returns up to `limit` matching results; use get_ticket with the returned
ticket_id to read the conversation, or get_ticket_metadata for its full
status/category/owner record.
Lookup patterns — call the named tool first to get the required ID:
- "tickets for company X" -> call search_companies(name="X"), then pass contact_id as company_id.
- "my tickets" / "tickets I own" -> call whoami(), then pass associate_id as our_contact_id.
- "open tickets" / "closed tickets" -> pass status="open" or status="closed".
- "unassigned tickets" / "tickets with no owner" -> pass unassigned=true.
Do not combine with our_contact_id — a ticket can't be both unassigned and owned.
Closed tickets are excluded unless status="closed" is passed, so "are there any
unassigned tickets at all?" needs a second call with status="closed" to be sure.
- "tickets in category X" -> pass category="X" (name or numeric ID); subcategories are
included automatically, so a parent category also matches its children.search_tickets_freetext — Search for tickets using ranked free text matching across multiple fields.
Unlike search_tickets (structured field filters), this tool scores and ranks
results by relevance: the user's own tickets, recently modified tickets, and
word-boundary matches score higher.
Searches ticket METADATA only — title, sender, author, and linked
company/person/project. It does NOT search the ticket's message or conversation
history; to read a ticket's contents, use get_ticket with the returned ticket_id,
or get_ticket_metadata for its status/category/owner record.
Matching scope: all query words must match fields within the same ticket.
Cross-ticket matching is not supported — "Jostein WebTools" only matches if both
words appear on the same ticket.
Keep queries short and specific (a few words — names, company, subject). The query is
sent to SuperOffice exactly as given, so results match CRM Online; only empty or
excessively long queries are rejected.
Prefer search_tickets when you already know specific IDs or structured criteria.
Otherwise, prefer search_tickets_freetext as it returns more comprehensive results.
Note: matching is locale-sensitive; queries in the tenant's language work best.
Returns up to `limit` ranked results; use get_ticket with the returned
ticket_id to read the conversation, or get_ticket_metadata for its
status/category/owner record.server_info — Get information about the MCP server.
Returns:
A dictionary containing server information.whoami — Returns the authenticated user's identity. Call this first before any "my" or "our" query.
Use the returned values to drive other tools:
- company_id -> "my company" (get_company), "people at my company" (search_persons),
"appointments at my company" (search_appointments)
- associate_id -> pass as our_contact_id for "my companies" (search_companies),
"my appointments" (search_appointments)
- person_id -> identifies the user as a CRM person
Also returns associate_name, email, role, group_id, ej_user_id, context_identifier.