AI Connect (ChatGPT / Claude)
AI Connect is the second channel of the ASPLUS AI platform: it exposes the same authoritative ASPLUS accounting capabilities as AIRA to external MCP-compatible AI clients — ChatGPT and Claude today. It reuses the channel-neutral Action layer, so there is no second business-logic system.
Status: LIVE on production (validated with both ChatGPT and Claude), Pro / Lifetime tier only (gated per company). Feature flag
AI_CONNECT_ENABLED. Read and write are available; writes always run through a preview → confirm contract.
1. What it is (in one paragraph)
Connect ChatGPT or Claude to your ASPLUS company over the Model Context Protocol
(MCP). Once connected, you can ask the AI things like "what's my outstanding AR?"
or "record a RM500 cash sale" from inside ChatGPT/Claude, and it uses ASPLUS's own
tools to answer or to make the change. Reads return live figures scoped to the
connected company. Writes never commit on the tool call — the tool returns a
preview + a confirmation token, and nothing is saved until a second, explicit
confirm_action step. The connection is OAuth 2.1; ASPLUS pins the company
server-side from the person who authorized it — the AI never chooses the tenant.
PART A — User Guide
A1. Connect ChatGPT / Claude
- In ASPLUS, open AI (top navbar) → AI Connect. You'll see cards for ChatGPT, Claude, and Other, plus your MCP address.
- MCP connections are started by the AI client, so copy the ASPLUS MCP
address (e.g.
https://app.asplus.my/mcp) and add it as a connector / custom MCP server in ChatGPT (developer mode / connectors) or in Claude. - The client discovers ASPLUS's OAuth automatically and opens an ASPLUS sign-in + consent page. Approve it. The company you're currently in becomes the pinned company for that connection.
- Back on the AI Connect page you'll see the live connection with a Disconnect button (revokes the connection + all its tokens immediately).
A2. What you can do
- Read: sales/expense summaries, P&L, balance sheet, trial balance, cash flow, cash & bank balance, outstanding invoices/bills, and lookups for invoices, bills, contacts and transactions — all for the connected company only.
- Write (with confirmation): record money in/out, record an invoice or bill payment, and create a draft invoice or quotation. The AI shows you a preview first; you approve; only then is it saved.
A3. How writes stay safe
A write tool prepares the change and returns a human-readable preview plus a
one-time confirmation token — nothing is written. To actually save, the AI calls
confirm_action with that token, which executes exactly once (idempotent,
re-authorized, tenant-locked). Tokens expire (default 15 min). ChatGPT/Claude also
force their own manual confirm before a write, so you always get a second checkpoint.
AI Connect never processes payments or handles credentials.
A4. Who can use it (Pro, per company)
AI Connect is available on Pro / Lifetime plans, checked against the active company's own subscription at consent time and on every tool call. A free/expired company is declined even if a sibling company in the same account is Pro. Non-entitled users see an upgrade nudge instead of the connect cards.
A5. Credits
Plain MCP reads incur no ASPLUS provider cost (the AI client pays for its own inference), so reads are logged but not charged by default. Writes are non-billable. (There is a per-read knob for future server-side AI work.)
PART B — Setup & Testing (internal)
B1. Reveal + environment
Set on the target environment and config:clear:
AI_CONNECT_ENABLED=true
AI_CONNECT_MCP_RESOURCE=https://app.asplus.my/mcp # (staging: https://staging.asplus.my/mcp)
AI_CONNECT_OAUTH_ISSUER=https://app.asplus.my # (staging: https://staging.asplus.my)
Optional: AI_CONNECT_ACCESS_TTL, AI_CONNECT_REFRESH_TTL, AI_CONNECT_ALLOW_DCR,
AI_CONNECT_PENDING_TTL_MINUTES, AI_CONNECT_CREDITS_PER_READ.
B2. nginx (RunCloud) — required for discovery
RunCloud's default ~ /\. rule returns 403 for /.well-known/*, and forwarding
.well-known through try_files …/index.php makes Laravel see the root path
(401). Fix: the app serves dotless mirror routes /mcp-oauth/*, and a
RunCloud location.main-before block rewrites the well-known paths to them:
rewrite ^/\.well-known/oauth-protected-resource(.*)$ /mcp-oauth/protected-resource$1 last;
rewrite ^/\.well-known/oauth-authorization-server$ /mcp-oauth/authorization-server last;
Verify live: GET /.well-known/oauth-protected-resource and
/.well-known/oauth-authorization-server → 200 JSON; POST /mcp (no token) →
401 with a WWW-Authenticate: Bearer resource_metadata=… challenge.
B3. Connect a client
Add https://<host>/mcp as a connector in ChatGPT (developer mode) or Claude. The
client runs: OAuth discovery → Dynamic Client Registration → consent
(ASPLUS login, company pinned) → token → tools/list. Then try a read
("outstanding invoices") and a write ("record RM500 cash sale" → preview → confirm).
B4. Automated suite
php artisan test tests/Feature/AiConnect
Covers OAuth (PKCE/DCR/token/refresh/revoke), MCP JSON-RPC (initialize/tools/list/ tools/call/search/fetch), the guarded gateway (permission + entitlement + credit + audit), and the write preview→confirm flow.
PART C — Reference
C1. Architecture
AI Connect plugs into the channel-neutral Action layer shared with AIRA:
ActorContext (CHANNEL_MCP), ActionAuthorizer (tenant + permission group +
active-company Pro sub + entitled tier), the concrete App\Actions\Accounting\*
write actions, and AiActionRecorder (audit → ai_action_executions, channel =
mcp). Reads reuse the AIRA read tools (App\Services\AiAgent\Tools\Read\*) via
a synthesized AgentTenantContext. Everything funnels through the single guarded
boundary App\Services\AiConnect\McpToolGateway.
C2. OAuth 2.1 (authorization server + resource server)
- Discovery:
/.well-known/oauth-protected-resource(RFC 9728, path-aware…/oauth-protected-resource/mcptoo) and/.well-known/oauth-authorization-server(RFC 8414) — plus dotless mirrors/mcp-oauth/*. - Endpoints:
POST /oauth/register(DCR, RFC 7591),GET/POST /oauth/authorize(consent + approve),POST /oauth/token(code + refresh),POST /oauth/revoke(RFC 7009). - PKCE S256 mandatory; audience-bound to the MCP resource (RFC 8707);
issin the redirect (RFC 9207). Access token ~1h, refresh rotates on use (~60d). Tokens are stored hashed only.
C3. MCP transport + tools
POST /mcp— Streamable-HTTP JSON-RPC:initialize,tools/list,tools/call,ping, plus the ChatGPT pairsearch/fetch. Bearer token in the header; revoked/insufficient → safe error.- Read tools (15): get_sales_summary, get_expense_summary, get_profit_and_loss, get_balance_sheet, get_cash_flow, get_trial_balance, get_cash_bank_balance, get_outstanding_invoices, get_outstanding_bills, find_invoice, get_invoice_status, find_bill, get_bill_status, find_contact, find_transaction. (AIRA's send_report_pdf is intentionally excluded — it's a WhatsApp side-effect.)
- Write tools (6): create_money_in, create_money_out, record_invoice_payment,
record_bill_payment, create_invoice (draft), create_quotation — each prepares
and returns a preview +
confirmation_token. - confirm_action — executes a prepared write exactly once using the token.
C4. Write contract (preview → confirm)
write tool → McpToolGateway::prepareWrite → validates + previews, stores an
ai_connect_pending_actions row + a hashed confirmation token, no commit →
returns { requires_confirmation, preview, confirmation_token, expires_in }.
confirm_action → McpToolGateway::confirmWrite → in a locked transaction:
re-authorize, execute the underlying App\Actions\Accounting\*, write provenance
(ai_action_executions, channel mcp), mark executed. Replays return the prior
result; expired tokens are refused.
C5. Data model
ai_connections (provider-agnostic, company/team pinned), ai_connect_events
(audit), ai_connect_oauth_clients, ai_connect_auth_codes, ai_connect_tokens
(opaque, hashed), ai_connect_pending_actions (write preview + token state). Credits
share the per-company ai_credit_transactions wallet (reads default to 0).
C6. Config / env
config/ai_connect.php — enabled flag, providers (chatgpt/claude/other), the
read_tools + write_tools lists, pending_action_ttl_minutes, credits.per_read
(default 0), server.resource / server.name, and the oauth.* block (issuer,
TTLs, scopes, DCR toggle).
C7. Security invariants
OAuth 2.1 + PKCE; tokens hashed at rest; company pinned server-side from the
consenting user (never from the LLM's arguments); every tool call re-checks
permission group + per-company active Pro subscription + entitled tier via
ActionAuthorizer; writes are non-committing until confirm_action (idempotent,
tenant-locked, re-authorized); full audit on ai_connect_events +
ai_action_executions. AI Connect never processes payments or credentials and
never exposes secrets/tokens in the UI, logs or URLs.
C8. Deploy notes
Prod diverges from staging (no Integrations/ASFusion), so AI Connect ships to prod
as an isolated cherry-pick off origin/production. Reveal = set the three env vars
above + add the location.main-before rewrite block + flip AI_CONNECT_ENABLED=true
- add the connector at
https://app.asplus.my/mcp. CSS is pre-built (Mix, committed) — new Tailwind classes only work if already inpublic/css/user-app.css.