Tennsa MCP connector

Tennsa OS gives your business its own AI. It knows everything across your systems, comes to you with what needs attention, and works from Claude, ChatGPT or Gemini. This connector is how: it lets an AI client such as Claude or ChatGPT read the daily briefing, and the history Tennsa has recorded behind it, on the business owner's behalf. Everything here is read-only: nothing the client can call changes a record, connects a source or moves money. The one thing a call can spend is an Investigate credit, and the tool that spends it says so.

Connect Authentication Tools What is and is not accessible Limits Testing the connector Support

Connect

Server URL: https://tennsa.com/api/v1/mcp

Transport: Streamable HTTP, in its stateless form: JSON-RPC 2.0 over HTTPS POST, one request per POST, application/json back, no session id and no server-initiated stream. A GET on the endpoint answers 405. The MCP-Protocol-Version header is honoured: an unsupported version answers 400 naming the supported ones, and a request without the header is served as a 2025-03-26 client. A present Origin header is validated (a missing one is accepted; an unrecognised one answers 403). Every response carries an X-Request-Id header.

Authentication: OAuth 2.0 authorisation code with PKCE (S256), public clients, dynamic client registration. A pasted token is not accepted.

Claude (claude.ai, Claude Desktop, Claude Code)

  1. Open Settings, then Connectors, then Add custom connector.
  2. Enter the server URL above. Claude registers itself and sends you to tennsa.com to sign in.
  3. Sign in to your Tennsa account and press Allow on the consent screen, which names the application and states what the connection does and does not reach.
  4. Claude now lists the tools below. Ask it, for example, "what does my Tennsa briefing say this morning?".

ChatGPT

  1. Open Settings, then Connectors (or Apps), and add a connector with the server URL above.
  2. ChatGPT sends you to tennsa.com to sign in and approve the connection, exactly as above. ChatGPT and Claude are registered as separate clients: an authorisation granted to one cannot be used by the other.

Gemini Enterprise

  1. Add the server as a custom MCP tool with the server URL above and OAuth 2.0 authorisation, using the client id (and the client secret, if Tennsa issued one) supplied with the listing.
  2. Gemini sends you to tennsa.com to sign in and approve, exactly as above. Gemini Enterprise is registered as its own client; an authorisation granted to it reaches nothing else.

Any other MCP client

Point the client at the server URL. An unauthenticated request receives a 401 with a WWW-Authenticate header naming the protected-resource document, from which the client discovers the authorisation server and registers itself. No client secret is issued; PKCE is what binds a code to the software that requested it.

Authentication

Document or endpointURL
Protected-resource metadata (RFC 9728)https://tennsa.com/.well-known/oauth-protected-resource
Authorisation-server metadata (RFC 8414)https://tennsa.com/.well-known/oauth-authorization-server
Authorisehttps://tennsa.com/oauth/authorize
Tokenhttps://tennsa.com/oauth/token
Revoke (RFC 7009)https://tennsa.com/oauth/revoke
Register (RFC 7591)https://tennsa.com/oauth/register

Scopes: both documents advertise mcp and offline_access. Every grant carries a rotating refresh token, and every token response states mcp offline_access; a scope parameter on the authorise request is accepted and never refused. The RFC 8707 resource parameter is accepted on the authorise and token requests and must name https://tennsa.com/api/v1/mcp; any other value answers invalid_target. Confidential clients may authenticate with client_secret_post or client_secret_basic; dynamically registered clients are public and bind their code with PKCE.

Tools

Every tool identifies the business from the bearer token; none takes an account identifier. Every tool declares its behaviour to the client, and every tool declares an outputSchema: the shape of its structuredContent, with every object closed to keys it does not name, so a host may validate what it receives. Each one is derived from what the tool actually returns rather than from an internal model, and a test drives every tool through every branch it has and validates the live response against the declared schema in both directions. Every tool is read-only and non-destructive. The tools that read Tennsa's own store are also idempotent and closed-world - they reach no third party during the call. ask_consultant is the exception on both counts: it is open-world and not idempotent, because it calls Tennsa's model, and each answered call spends one Investigate credit from the business's monthly allowance.

get_briefing

The current operational briefing: the deterministic findings computed from the connected accounting, payments, commerce and stock systems, with a freshness status. Takes one optional argument, verbose: false by default, which returns each finding's headline, severity, sources and suggested action; true adds the full narrative body and the rule name. Either way the response lists what it omitted.

Returns status (ready, stale or not_ready), computed_at, computed_by (delivery, warm, os_render or null when we did not record it - a computed_at later than the day's briefing means it was refreshed, not that something is wrong), age_seconds, a findings list, a coverage block naming the sources the briefing read, and withheld_count with a withheld_note sentence when findings were withheld (see the next section). A cold cache answers not_ready honestly; the tool never computes on demand.

{
  "status": "ready",
  "computed_at": "2026-09-08T04:30:12+00:00",
  "computed_by": "warm",
  "age_seconds": 1840,
  "findings": [
    {
      "category": {"key": "customer_payment_behavior", "label": "Customer payment behaviour"},
      "severity": "high",
      "headline": "Buildmore Holdings is 31 days overdue on R84,500",
      "body_plain": "Two invoices ... The last payment from this customer arrived 46 days late.",
      "sources": [{"key": "xero", "label": "Xero"}],
      "suggested_action": "Call before the next statement run ..."
    }
  ],
  "coverage": {"covered": ["xero", "snapscan"], "unreached": {"error": [], "timeout": []}},
  "withheld_count": 1,
  "withheld_note": "1 finding derived from connected email or calendar data are withheld from this response ..."
}

get_data_coverage

Which sources the business has connected right now, each with its label and a health state: live, degraded, not_connected or unknown. A degraded source is still connected - either its credential has stopped working (needs_reconnect) or the last briefing run could not read it, and failure_reason says which. last_success_date is a calendar day, never a time, and is null when no successful day is held; last_success_basis says which kind of null it is. last_attempt is when the last briefing run happened. Live state, so it may honestly differ from the briefing's own coverage, which was recorded when the briefing was computed. Takes no arguments. For an email or calendar source the entry says it is connected but carries no account identifier.

get_activity

Recent activity on the Tennsa account: briefings sent and refreshed, sources connected or disconnected, reports downloaded. A log of actions, never of content. Takes no arguments.

get_business_areas

The five business areas Tennsa organises a business by (financials, payments and commerce, stock, customer relations, communications), each saying whether the business has a connected source for it and carrying that area's current findings, with its own withheld count. Each answer carries the same status, computed_at, computed_by and age_seconds freshness fields as get_briefing. Takes no arguments.

Each area also carries undisclosed_sources and undisclosed_note. An area can be connected on a source this channel may never report a finding from - a Gmail, Microsoft 365 or IMAP mailbox - and on a day when no such finding was produced the withheld count is zero and there is nothing else to say so. These two fields say it regardless: an empty findings list on such an area is this channel declining to speak for what is connected there, not a statement that nothing came up. They are independent of withheld_count and read the same whether or not anything was withheld.

ask_consultant

One question to Tennsa's consultant, answered from the business's connected accounting, payments, commerce, stock and CRM sources and any documents it has uploaded. Takes one argument, question (a string of at most 2000 characters). Stateless: every call stands alone.

Email and calendar sources are never consulted through this channel. That is a property of the code path, not a filter over the answer: the Gmail, IMAP and Microsoft 365 contexts are not built for this channel at all, every excluded slot is checked empty before the prompt exists, and the call is refused if that check fails. The result always carries email_sources_note saying so, so a client cannot read silence as an empty mailbox.

Returns status (answered, not_grounded, quota_exceeded or invalid_question), answer, note (why this is not an answer, empty when it is), sources_consulted (display labels of the sources that grounded the answer), unreached (connected sources the grounding load could not read in time), email_sources_note, and the pair credits_used and credits_remaining - the latter an integer, the string unlimited where the tier's ceiling is a runaway guard rather than a quota, or null where no balance was established on that call. Never read null as zero. When nothing this channel reads can ground the question the tool answers not_grounded without running the model and without spending a credit - which for a business whose only connected sources are mail is a statement about this channel, not about what it has connected, and the note says so.

Cost and duration: each answered call uses one Investigate credit from the business's monthly allowance (the same allowance the Investigate page spends) and takes ten to thirty-five seconds. A call that does not answer spends nothing. This tool has its own rate limit (see Limits).

{
  "status": "answered",
  "answer": "Three customers have slipped past 45 days ...",
  "note": "",
  "sources_consulted": ["Xero", "SnapScan"],
  "unreached": [],
  "email_sources_note": "Connected email and calendar sources (Gmail, Microsoft 365 and IMAP mailboxes) are not consulted through this channel ...",
  "credits_used": 1,
  "credits_remaining": 487
}

get_history

What Tennsa recorded over a period, and what it did not see. Answers three questions for a date range: the state of one item as at the end of it, what changed inside it, and the business's own documents dated in it. Takes a period (start_date and end_date, each YYYY-MM-DD) and optionally a source and an entity_id.

Every answer carries coverage: how many days in the range Tennsa OS read, how many it could not reach, and how many had no run at all. That is the point of the tool. A day Tennsa did not observe is never reported as a day on which nothing happened, and coverage_note is the sentence a client can repeat to the owner.

Returns status (answered, no_history, declined, invalid_period, invalid_source or unavailable), note, period, source, coverage, coverage_note, state (with its own basis and trusted flag), changes with changes_basis and changes_total, transactions_summary (per-kind totals over every document in the period), transactions with transaction_count and transactions_total, omitted, the completeness flags changes_complete, transactions_complete and truncated, and withheld_count with withheld_note. An empty changes list means three different things, so changes_basis says which: observed (we hold this kind of history for that source and nothing moved), not_recorded_for_this_source (we have never recorded it there) or not_asked (no source was named). A long period is capped at 200 rows and truncated says so, with the totals beside it. A period earlier than the records held answers declined and says how far back they go, rather than guessing. A read that failed answers unavailable, which is not an empty period. History derived from connected email or calendar data is withheld, and asking for such a source by name is refused.

This is an as-of view of what was observed and stored. It can honestly differ both from get_briefing, which is a compute-time snapshot, and from get_data_coverage, which is live connection state. None of the three is wrong when they disagree, and a client should not try to reconcile them.

{
  "status": "answered",
  "period": { "start": "2026-08-01", "end": "2026-08-31" },
  "source": "iq_retail",
  "coverage": { "window_days": 31, "read_days": 28, "unreached_days": 3, "not_run_days": 0, "unknown_days": 0 },
  "coverage_note": "though we could not read iq_retail on 2026-08-04, 2026-08-05 and 2026-08-06, so this period is measured over the 28 days we did see",
  "state": { "entity_id": "CEM-42", "as_of": "2026-08-29", "basis": "carried", "trusted": true, "state": { "QtyOnHand": 18.0 } },
  "changes": [ { "entity_id": "CEM-42", "as_of_date": "2026-08-29", "state": { "QtyOnHand": 18.0 } } ],
  "changes_basis": "observed",
  "transactions": [],
  "transaction_count": 0,
  "transactions_total": 0,
  "truncated": false,
  "withheld_count": 0
}

What is and is not accessible

Accessible

  • The computed findings: headline, plain-text body, severity, category, contributing source labels, suggested action.
  • Connection state per source (connected, needs reconnecting).
  • The account activity log and the business-area summary.
  • A consultant answer grounded on the connected non-email sources and uploaded documents, naming the sources it consulted.

Not accessible

  • Findings derived from connected email or calendar data (Gmail, Microsoft 365 and IMAP mailboxes). They are withheld by provenance at the boundary, the response says how many were withheld, and the guard fails closed: a finding with no recorded provenance is withheld too.
  • Email or calendar content through the consultant. The three mail sources are excluded from the consultant's grounding on this channel by construction, and the answer names the sources it did consult.
  • The raw contributing data behind a finding (invoice lists, message subjects, sender addresses). Only the narrative fields are served. get_history is the one exception and a narrow one: it serves the business's own recorded documents for a period - date, kind, counterparty, amount, currency, status and reference - because that is what a question about a period asks for. It serves no figure Tennsa has computed about a third party.
  • Connected-account identifiers for mail sources, credentials of any kind, and every other Tennsa API route.
  • Any write: the connection cannot change a record, connect or disconnect a source, or make a payment. The one thing a call spends is the meter - an answered ask_consultant call costs one Investigate credit from the monthly allowance.

Why the email exclusion exists: Google's Workspace API user-data policy restricts transfers of Google user data, including data derived from it, to third parties. A tool result handed to an AI vendor is such a transfer, so findings computed from mailbox content stay inside Tennsa's own briefing. Microsoft 365 is excluded as a privacy default for the same class of content. Details in the privacy policy.

Limits

Testing the connector

A reviewer can exercise every tool in a few minutes with a Tennsa test account (credentials are supplied with the directory submission, never on this page).

  1. Add the connector in your client using the server URL above and sign in with the test account when redirected.
  2. Ask for the briefing. Expect a ready briefing with several findings across the account's connected sources (accounting, payments, stock), and a withheld note if any email-derived findings exist.
  3. Ask which sources are connected. Expect the connected list, with mail sources showing connected but carrying no account identifier.
  4. Ask about a business area, for example stock. Expect that area's findings and connection state.
  5. Ask what happened lately on the account. Expect recent activity: briefings sent, sources connected, reports downloaded.
  6. Ask for the history of a period Tennsa has recorded, for example last month. Expect an answered result whose coverage says how many days were read and how many were not, and which never reports an unread day as a day with nothing in it.
  7. Ask for a period from before the account existed, for example 2019. Expect declined, with a note saying how far back the records go. This is the property the tool exists for: it refuses rather than presenting a gap as a fact.
  8. Ask the consultant a question, for example which customers are paying late. Expect an answered result within about thirty seconds naming the sources it consulted, none of them a mail source.
  9. Remove the connector in your client, or revoke at /oauth/revoke. Expect the next call to answer 401.

The discovery documents at the two .well-known URLs above are pure functions of the base URL and answer without touching a database, so they respond in milliseconds.

Support

Support for this connector: info@tennsa.com. Every response carries an X-Request-Id header; quote it, with the client you are using, and we can find the exact call. Nothing about a tool call is logged beyond the tool name, its status and counts.

Operated by Tennsa (Pty) Ltd, South Africa. Privacy policy and terms of service.