Install
$ agentstack add mcp-teguhcanggu-jurnal-mcp ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README — it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming — see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps — measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
jurnal-mcp
[](LICENSE) [](https://www.python.org/)
Read-only Model Context Protocol server for the Mekari Jurnal accounting API. Lets MCP clients (Claude Code, Claude Desktop, etc.) read contacts, invoices, journal entries, and financial reports across one or more Jurnal companies.
> Read-only by design. No tool in this server creates, updates, or > deletes data in Jurnal. Use it to read accounting facts and let the AI > assemble accurate reports from them.
Features
- 27 MCP tools covering master data, transactions, and 8 financial reports
(P&L, balance sheet, cash flow, trial balance, GL, aged AP/AR, executive summary)
- HMAC-SHA256 request signing per Mekari's official auth scheme — stateless,
no token refresh, no OAuth flow
- Multi-profile support: connect several Jurnal companies/credentials and
switch per call via a profile argument
- Python 3.11+,
uvpackaged,
FastMCP transport
Requirements
- Python ≥ 3.11
uvfor dependency management- A Mekari developer application with
Client ID+Client Secretper
Jurnal company you want to access. Apply or check yours at developers.mekari.com.
Install
git clone https://github.com//jurnal-mcp.git
cd jurnal-mcp/jurnal-mcp-server
uv sync
Configure
Copy the example env file and fill in your credentials:
cp ../.env.example .env # path is relative to jurnal-mcp-server/
Edit jurnal-mcp-server/.env:
JURNAL_PROFILE_MAIN_CLIENT_ID=
JURNAL_PROFILE_MAIN_CLIENT_SECRET=
JURNAL_DEFAULT_PROFILE=main
To connect more than one Jurnal company, add more profiles by repeating the pattern with a different ``:
JURNAL_PROFILE_BRANCH_A_CLIENT_ID=...
JURNAL_PROFILE_BRANCH_A_CLIENT_SECRET=...
JURNAL_PROFILE_BRANCH_B_CLIENT_ID=...
JURNAL_PROFILE_BRANCH_B_CLIENT_SECRET=...
` is any uppercase identifier you choose. Each tool accepts an optional profile argument that selects which credentials to use; calls without an explicit profile fall back to JURNALDEFAULTPROFILE`.
> Security: keep .env gitignored, restrict permissions > (chmod 600 .env), and never commit credentials. The .gitignore > already excludes .env.
Run as a standalone MCP server
cd jurnal-mcp-server
uv run main.py
The server speaks MCP over stdio.
Add to Claude Code
Add this entry to your ~/.claude/mcp.json (or the equivalent for your MCP host):
{
"mcpServers": {
"jurnal": {
"type": "stdio",
"command": "/path/to/uv",
"args": [
"--directory",
"/absolute/path/to/jurnal-mcp/jurnal-mcp-server",
"run",
"main.py"
],
"env": {
"JURNAL_PROFILE_MAIN_CLIENT_ID": "...",
"JURNAL_PROFILE_MAIN_CLIENT_SECRET": "...",
"JURNAL_DEFAULT_PROFILE": "main"
}
}
}
}
You can also keep credentials in jurnal-mcp-server/.env and omit the env block — the server loads .env automatically on startup.
Available tools (27)
All tools are read-only (GET). Every tool takes an optional profile: str | None argument; omit to use JURNAL_DEFAULT_PROFILE.
Auth & discovery
| Tool | Endpoint | Description | |------|----------|-------------| | list_profiles | (local) | Profiles discovered from env vars + the default | | whoami | GET /api/v1/users/me | Authenticated Mekari user | | get_active_company | GET /api/v1/companies/active | Active company for the credentials | | list_companies | GET /api/v1/companies | Companies accessible to the credentials |
Master data
| Tool | Endpoint | |------|----------| | list_contacts(contact_index?) | GET /api/v1/contacts | | get_contact(id) | GET /api/v1/contacts/{id} | | list_customers(page?, page_size?, sort_key?, sort_order?) | GET /api/v1/customers | | list_vendors(page?, page_size?, sort_key?, sort_order?) | GET /api/v1/vendors | | list_products(include_archive?) | GET /api/v1/products | | list_accounts(include_archive?) | GET /api/v1/accounts (chart of accounts) |
Transactions
| Tool | Endpoint | |------|----------| | list_sales_invoices(page?, page_size?, sort_key?, sort_order?) | GET /api/v1/sales_invoices | | get_sales_invoice(id) | GET /api/v1/sales_invoices/{id} | | list_purchase_invoices(page?, page_size?, sort_key?, sort_order?) | GET /api/v1/purchase_invoices | | get_purchase_invoice(id) | GET /api/v1/purchase_invoices/{id} | | list_journal_entries() | GET /api/v1/journal_entries | | get_journal_entry(id) | GET /api/v1/journal_entries/{id} | | list_expenses() | GET /api/v1/expenses | | list_receive_payments() | GET /api/v1/receive_payments | | list_purchase_payments() | GET /api/v1/purchase_payments |
Financial reports
All report endpoints accept dates in dd/mm/yyyy form (e.g. "01/01/2026").
| Tool | Endpoint | |------|----------| | get_profit_and_loss(start_date, end_date, period?, compare_type?) | GET /api/v1/profit_and_loss | | get_balance_sheet(end_date, compare?, period?, compare_type?) | GET /api/v1/balance_sheet | | get_cash_flow(start_date, end_date, period?, compare_type?) | GET /api/v1/cash_flow | | get_general_ledger(start_date, end_date, account_id?, compare_type?) | GET /api/v1/general_ledger | | get_trial_balance(start_date?, end_date?, period?, compare_type?) | GET /api/v1/trial_balance | | get_aged_payables(end_date?, period?, compare_type?) | GET /api/v1/aged_payables | | get_aged_receivables(end_date?, period?, compare_type?) | GET /api/v1/aged_receivables | | get_executive_summary(start_date?, end_date?, period?, compare_type?) | GET /api/v1/executive_summary |
compare_type accepts: custom, day, week, month, year, yesterday, last_week, last_month, last_year.
Authentication scheme (HMAC)
Every request is signed with HMAC-SHA256 over a canonical string:
date:
HTTP/1.1
The two lines are joined by \n. The signature is base64-encoded and sent in the Authorization header alongside Date:
Date: Wed, 29 Apr 2026 03:00:00 GMT
Authorization: hmac username="", algorithm="hmac-sha256", headers="date request-line", signature=""
Mekari rejects requests whose Date differs from server time by more than 300 seconds. Make sure the host clock is in sync (e.g. via NTP).
This server only signs GET requests, so the Digest header that Mekari requires for write methods is not sent. Adding write tools is intentionally out of scope.
References:
Multi-profile usage examples
# In an MCP client conversation:
# 1) Discover profiles
list_profiles()
# -> {"profiles": ["branch_a", "branch_b", "main"], "default": "main"}
# 2) Read a specific company's profit & loss (dates in dd/mm/yyyy)
get_profit_and_loss(
profile="branch_a",
start_date="01/01/2026",
end_date="31/03/2026",
)
# 3) List contacts from another profile
list_contacts(profile="branch_b")
If only one Mekari Client ID gives access to multiple companies, you can configure a single profile and skip the profile argument entirely.
Development
Install dev dependencies and run the offline test suite:
cd jurnal-mcp-server
uv sync --all-extras --dev
uv run pytest tests/ -v
The test suite verifies the HMAC string-to-sign matches Mekari's reference implementation, exercises the path/query construction, and the profile resolver. It does not call the live API.
Verifying tools end-to-end
After credentials are in place, a quick smoke test:
uv run python -c "from jurnal import JurnalClient; print(JurnalClient.from_profile().get('/api/v1/users/me'))"
A 200 response with user info confirms credentials and signing work. A 401 usually means a clock skew over 300 seconds, a wrong client ID, or a secret with whitespace.
Status & limitations
- Read-only. No write tools are exposed; the server signs only
GET. - Date format. All report tools take dates in
dd/mm/yyyyform. The
swagger spec only documents this format explicitly for general_ledger, but in practice the same format works across the report endpoints, so this server uses it uniformly for predictability.
- No retry/backoff on rate limits yet; add as needed for your workload.
License
MIT — see [LICENSE](LICENSE).
Acknowledgements
for the reference signing flow.
- FastMCP for the MCP plumbing.
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Teguhcanggu
- Source: Teguhcanggu/jurnal-mcp
- License: MIT
- Homepage: https://github.com/Teguhcanggu/jurnal-mcp
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.