@bbookmind/mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +27 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.env.example +15 -0
- package/.github/workflows/validate.yml +55 -0
- package/CEO_QUICKSTART.md +161 -0
- package/CLAUDE.md +30 -0
- package/CNAME +1 -0
- package/LAUNCH_CONTENT.md +210 -0
- package/LICENSE +21 -0
- package/README.md +196 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +283 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/db.d.ts +6 -0
- package/dist/lib/db.d.ts.map +1 -0
- package/dist/lib/db.js +209 -0
- package/dist/lib/db.js.map +1 -0
- package/dist/lib/llm.d.ts +14 -0
- package/dist/lib/llm.d.ts.map +1 -0
- package/dist/lib/llm.js +110 -0
- package/dist/lib/llm.js.map +1 -0
- package/dist/lib/qbo.d.ts +35 -0
- package/dist/lib/qbo.d.ts.map +1 -0
- package/dist/lib/qbo.js +157 -0
- package/dist/lib/qbo.js.map +1 -0
- package/dist/tools/anomalies.d.ts +3 -0
- package/dist/tools/anomalies.d.ts.map +1 -0
- package/dist/tools/anomalies.js +142 -0
- package/dist/tools/anomalies.js.map +1 -0
- package/dist/tools/approveCategories.d.ts +3 -0
- package/dist/tools/approveCategories.d.ts.map +1 -0
- package/dist/tools/approveCategories.js +41 -0
- package/dist/tools/approveCategories.js.map +1 -0
- package/dist/tools/categorize.d.ts +3 -0
- package/dist/tools/categorize.d.ts.map +1 -0
- package/dist/tools/categorize.js +160 -0
- package/dist/tools/categorize.js.map +1 -0
- package/dist/tools/chaseEmail.d.ts +3 -0
- package/dist/tools/chaseEmail.d.ts.map +1 -0
- package/dist/tools/chaseEmail.js +29 -0
- package/dist/tools/chaseEmail.js.map +1 -0
- package/dist/tools/fetchTransactions.d.ts +3 -0
- package/dist/tools/fetchTransactions.d.ts.map +1 -0
- package/dist/tools/fetchTransactions.js +72 -0
- package/dist/tools/fetchTransactions.js.map +1 -0
- package/dist/tools/learnRules.d.ts +3 -0
- package/dist/tools/learnRules.d.ts.map +1 -0
- package/dist/tools/learnRules.js +72 -0
- package/dist/tools/learnRules.js.map +1 -0
- package/dist/tools/listCompanies.d.ts +3 -0
- package/dist/tools/listCompanies.d.ts.map +1 -0
- package/dist/tools/listCompanies.js +26 -0
- package/dist/tools/listCompanies.js.map +1 -0
- package/dist/tools/monthlyClose.d.ts +3 -0
- package/dist/tools/monthlyClose.d.ts.map +1 -0
- package/dist/tools/monthlyClose.js +92 -0
- package/dist/tools/monthlyClose.js.map +1 -0
- package/dist/tools/reconcile.d.ts +3 -0
- package/dist/tools/reconcile.d.ts.map +1 -0
- package/dist/tools/reconcile.js +86 -0
- package/dist/tools/reconcile.js.map +1 -0
- package/dist/tools/rejectCategories.d.ts +3 -0
- package/dist/tools/rejectCategories.d.ts.map +1 -0
- package/dist/tools/rejectCategories.js +36 -0
- package/dist/tools/rejectCategories.js.map +1 -0
- package/dist/tools/selectCompany.d.ts +3 -0
- package/dist/tools/selectCompany.d.ts.map +1 -0
- package/dist/tools/selectCompany.js +28 -0
- package/dist/tools/selectCompany.js.map +1 -0
- package/dist/tools/syncToQbo.d.ts +3 -0
- package/dist/tools/syncToQbo.d.ts.map +1 -0
- package/dist/tools/syncToQbo.js +53 -0
- package/dist/tools/syncToQbo.js.map +1 -0
- package/dist/tools/undoCategories.d.ts +3 -0
- package/dist/tools/undoCategories.d.ts.map +1 -0
- package/dist/tools/undoCategories.js +50 -0
- package/dist/tools/undoCategories.js.map +1 -0
- package/dist/tools/vendorSearch.d.ts +3 -0
- package/dist/tools/vendorSearch.d.ts.map +1 -0
- package/dist/tools/vendorSearch.js +39 -0
- package/dist/tools/vendorSearch.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/index.html +403 -0
- package/install/claude-desktop.json +14 -0
- package/install/cline.json +16 -0
- package/install/continue.json +14 -0
- package/install/cursor.json +14 -0
- package/install/goose.yaml +10 -0
- package/install/lm-studio.json +14 -0
- package/install/vscode-copilot.json +14 -0
- package/install/windsurf.json +14 -0
- package/mcp-server.toml +30 -0
- package/package.json +49 -0
- package/server.json +136 -0
- package/src/index.ts +313 -0
- package/src/lib/db.ts +212 -0
- package/src/lib/llm.ts +140 -0
- package/src/lib/qbo.ts +187 -0
- package/src/tools/anomalies.ts +144 -0
- package/src/tools/approveCategories.ts +46 -0
- package/src/tools/categorize.ts +166 -0
- package/src/tools/chaseEmail.ts +30 -0
- package/src/tools/fetchTransactions.ts +79 -0
- package/src/tools/learnRules.ts +71 -0
- package/src/tools/listCompanies.ts +30 -0
- package/src/tools/monthlyClose.ts +95 -0
- package/src/tools/reconcile.ts +86 -0
- package/src/tools/rejectCategories.ts +43 -0
- package/src/tools/selectCompany.ts +33 -0
- package/src/tools/syncToQbo.ts +56 -0
- package/src/tools/undoCategories.ts +50 -0
- package/src/tools/vendorSearch.ts +32 -0
- package/src/types.ts +11 -0
- package/tsconfig.json +23 -0
package/mcp-server.toml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[server]
|
|
2
|
+
id = "io.bookmind.mcp"
|
|
3
|
+
name = "BookMind"
|
|
4
|
+
version = "1.0.0"
|
|
5
|
+
description = "AI bookkeeping for QuickBooks Online — auto-categorize, reconcile, monthly close, anomalies, vendor search, chase emails"
|
|
6
|
+
homepage = "https://github.com/bbookmind/mcp"
|
|
7
|
+
repository = "https://github.com/bbookmind/mcp"
|
|
8
|
+
license = "MIT"
|
|
9
|
+
|
|
10
|
+
[author]
|
|
11
|
+
name = "BookMind"
|
|
12
|
+
email = "support@bookmind.dev"
|
|
13
|
+
|
|
14
|
+
[install]
|
|
15
|
+
npm = "@bbookmind/mcp"
|
|
16
|
+
npx = "npx @bookmind/mcp"
|
|
17
|
+
|
|
18
|
+
[transport]
|
|
19
|
+
type = "stdio"
|
|
20
|
+
command = "npx"
|
|
21
|
+
args = ["-y", "@bbookmind/mcp"]
|
|
22
|
+
|
|
23
|
+
[capabilities]
|
|
24
|
+
tools = true
|
|
25
|
+
resources = true
|
|
26
|
+
prompts = true
|
|
27
|
+
|
|
28
|
+
[features]
|
|
29
|
+
data_storage = "local_sqlite"
|
|
30
|
+
offline_mode = true
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bbookmind/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI bookkeeping for QuickBooks Online. Use any model, any provider.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"dev": "tsc --watch",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"lint": "eslint src --ext .ts"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"quickbooks",
|
|
18
|
+
"bookkeeping",
|
|
19
|
+
"accounting",
|
|
20
|
+
"ai-agent"
|
|
21
|
+
],
|
|
22
|
+
"author": "bbookmind",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/bbookmind/mcp.git"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
30
|
+
"sql.js": "^1.10.0",
|
|
31
|
+
"zod": "^3.23.0",
|
|
32
|
+
"axios": "^1.7.0",
|
|
33
|
+
"date-fns": "^3.6.0",
|
|
34
|
+
"uuid": "^9.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^20.12.0",
|
|
38
|
+
"@types/uuid": "^9.0.0",
|
|
39
|
+
"typescript": "^5.4.0",
|
|
40
|
+
"vitest": "^1.5.0",
|
|
41
|
+
"eslint": "^8.57.0",
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
43
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
44
|
+
"@types/sql.js": "^1.4.7"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://registry.modelcontextprotocol.io/schemas/server-schema.json",
|
|
3
|
+
"id": "io.bookmind.mcp",
|
|
4
|
+
"name": "BookMind",
|
|
5
|
+
"description": "AI bookkeeping for QuickBooks Online. Auto-categorize transactions, reconcile bank feeds, generate monthly close packages, detect anomalies, and draft chase emails — all from your AI agent. Provider-agnostic, agent-agnostic, rules-first.",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"categories": ["finance", "accounting", "bookkeeping", "quickbooks"],
|
|
8
|
+
"tags": ["quickbooks", "bookkeeping", "accounting", "mcp", "finance", "qbo", "ai-agent"],
|
|
9
|
+
"homepage": "https://github.com/bbookmind/mcp",
|
|
10
|
+
"repository": "https://github.com/bbookmind/mcp",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": {
|
|
13
|
+
"name": "BookMind",
|
|
14
|
+
"email": "support@bookmind.dev"
|
|
15
|
+
},
|
|
16
|
+
"install": {
|
|
17
|
+
"npm": "@bbookmind/mcp",
|
|
18
|
+
"npx": "npx @bookmind/mcp"
|
|
19
|
+
},
|
|
20
|
+
"transport": {
|
|
21
|
+
"type": "stdio",
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": ["-y", "@bbookmind/mcp"]
|
|
24
|
+
},
|
|
25
|
+
"env": {
|
|
26
|
+
"QBO_CLIENT_ID": {
|
|
27
|
+
"description": "QuickBooks Online OAuth Client ID",
|
|
28
|
+
"required": true
|
|
29
|
+
},
|
|
30
|
+
"QBO_CLIENT_SECRET": {
|
|
31
|
+
"description": "QuickBooks Online OAuth Client Secret",
|
|
32
|
+
"required": true
|
|
33
|
+
},
|
|
34
|
+
"QBO_REDIRECT_URI": {
|
|
35
|
+
"description": "OAuth redirect URI",
|
|
36
|
+
"default": "http://localhost:3000/callback",
|
|
37
|
+
"required": false
|
|
38
|
+
},
|
|
39
|
+
"QBO_ENVIRONMENT": {
|
|
40
|
+
"description": "QuickBooks environment (sandbox or production)",
|
|
41
|
+
"default": "sandbox",
|
|
42
|
+
"required": false
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"tools": [
|
|
46
|
+
{
|
|
47
|
+
"name": "bookmind.categorize",
|
|
48
|
+
"description": "Auto-categorize uncategorized transactions using learned rules + any LLM provider",
|
|
49
|
+
"risk": "write"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "bookmind.reconcile",
|
|
53
|
+
"description": "Match bank transactions to invoices/bills by amount, date, and vendor",
|
|
54
|
+
"risk": "write"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "bookmind.monthly_close",
|
|
58
|
+
"description": "Generate full monthly close package: P&L, Balance Sheet, Cash Flow, action items",
|
|
59
|
+
"risk": "read"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "bookmind.anomalies",
|
|
63
|
+
"description": "Detect duplicate payments, vendor cost drift, outliers, and frequency anomalies",
|
|
64
|
+
"risk": "read"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "bookmind.vendor_search",
|
|
68
|
+
"description": "Natural language vendor transaction search",
|
|
69
|
+
"risk": "read"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "bookmind.chase_email",
|
|
73
|
+
"description": "Generate professional overdue invoice emails in friendly/firm/escalation tones",
|
|
74
|
+
"risk": "write"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "bookmind.fetch_transactions",
|
|
78
|
+
"description": "Pull transactions, invoices, bills from QuickBooks Online",
|
|
79
|
+
"risk": "read"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "bookmind.sync_to_qbo",
|
|
83
|
+
"description": "Push approved categorizations back to QuickBooks",
|
|
84
|
+
"risk": "write"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "bookmind.approve_categorizations",
|
|
88
|
+
"description": "Approve pending categorizations in batch",
|
|
89
|
+
"risk": "write"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "bookmind.reject_categorizations",
|
|
93
|
+
"description": "Reject categorizations with feedback, auto-create rules from corrections",
|
|
94
|
+
"risk": "write"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "bookmind.undo_categorize",
|
|
98
|
+
"description": "Rollback categorization changes from audit log",
|
|
99
|
+
"risk": "write"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "bookmind.learn_rules",
|
|
103
|
+
"description": "Create persistent categorization rules from user corrections",
|
|
104
|
+
"risk": "write"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "bookmind.select_company",
|
|
108
|
+
"description": "Switch between connected QuickBooks companies",
|
|
109
|
+
"risk": "write"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "bookmind.list_companies",
|
|
113
|
+
"description": "List all connected QuickBooks companies with OAuth status",
|
|
114
|
+
"risk": "read"
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"features": {
|
|
118
|
+
"auth": {
|
|
119
|
+
"type": "oauth2",
|
|
120
|
+
"provider": "intuit"
|
|
121
|
+
},
|
|
122
|
+
"ai_providers": ["openai", "anthropic", "google", "groq", "mistral", "nvidia", "ollama"],
|
|
123
|
+
"data_storage": "local_sqlite",
|
|
124
|
+
"offline_mode": true
|
|
125
|
+
},
|
|
126
|
+
"screenshots": [
|
|
127
|
+
{
|
|
128
|
+
"url": "https://raw.githubusercontent.com/bookmind/mcp/main/assets/screenshot-cli.png",
|
|
129
|
+
"alt": "BookMind CLI running monthly close"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"url": "https://raw.githubusercontent.com/bookmind/mcp/main/assets/screenshot-categorize.png",
|
|
133
|
+
"alt": "AI categorization in action"
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
ListResourcesRequestSchema,
|
|
8
|
+
ReadResourceRequestSchema,
|
|
9
|
+
ListPromptsRequestSchema,
|
|
10
|
+
GetPromptRequestSchema,
|
|
11
|
+
ErrorCode,
|
|
12
|
+
McpError,
|
|
13
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
import { getDb, saveDb, getCurrentCompanyId } from "./lib/db.js";
|
|
15
|
+
import { activeCompanyId } from "./types.js";
|
|
16
|
+
import { categorizeTool } from "./tools/categorize.js";
|
|
17
|
+
import { reconcileTool } from "./tools/reconcile.js";
|
|
18
|
+
import { anomaliesTool } from "./tools/anomalies.js";
|
|
19
|
+
import { monthlyCloseTool } from "./tools/monthlyClose.js";
|
|
20
|
+
import { vendorSearchTool } from "./tools/vendorSearch.js";
|
|
21
|
+
import { chaseEmailTool } from "./tools/chaseEmail.js";
|
|
22
|
+
import { learnRulesTool } from "./tools/learnRules.js";
|
|
23
|
+
import { approveCategoriesTool } from "./tools/approveCategories.js";
|
|
24
|
+
import { rejectCategoriesTool } from "./tools/rejectCategories.js";
|
|
25
|
+
import { undoCategoriesTool } from "./tools/undoCategories.js";
|
|
26
|
+
import { fetchTransactionsTool } from "./tools/fetchTransactions.js";
|
|
27
|
+
import { syncToQboTool } from "./tools/syncToQbo.js";
|
|
28
|
+
import { selectCompanyTool } from "./tools/selectCompany.js";
|
|
29
|
+
import { listCompaniesTool } from "./tools/listCompanies.js";
|
|
30
|
+
|
|
31
|
+
const server = new Server(
|
|
32
|
+
{
|
|
33
|
+
name: "bookmind",
|
|
34
|
+
version: "1.0.0",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
capabilities: {
|
|
38
|
+
tools: {},
|
|
39
|
+
resources: {},
|
|
40
|
+
prompts: {},
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const tools = [
|
|
46
|
+
categorizeTool,
|
|
47
|
+
reconcileTool,
|
|
48
|
+
anomaliesTool,
|
|
49
|
+
monthlyCloseTool,
|
|
50
|
+
vendorSearchTool,
|
|
51
|
+
chaseEmailTool,
|
|
52
|
+
learnRulesTool,
|
|
53
|
+
approveCategoriesTool,
|
|
54
|
+
rejectCategoriesTool,
|
|
55
|
+
undoCategoriesTool,
|
|
56
|
+
fetchTransactionsTool,
|
|
57
|
+
syncToQboTool,
|
|
58
|
+
selectCompanyTool,
|
|
59
|
+
listCompaniesTool,
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const resourceTemplates = [
|
|
63
|
+
{
|
|
64
|
+
uriTemplate: "bookmind://rules",
|
|
65
|
+
name: "Categorization Rules",
|
|
66
|
+
description: "List all categorization rules for the current company",
|
|
67
|
+
mimeType: "application/json",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
uriTemplate: "bookmind://accounts",
|
|
71
|
+
name: "QBO Accounts",
|
|
72
|
+
description: "List QuickBooks Online accounts",
|
|
73
|
+
mimeType: "application/json",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
uriTemplate: "bookmind://anomalies/{id}",
|
|
77
|
+
name: "Anomaly Details",
|
|
78
|
+
description: "Get details of a specific anomaly by ID",
|
|
79
|
+
mimeType: "application/json",
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const prompts = [
|
|
84
|
+
{
|
|
85
|
+
name: "monthly-close",
|
|
86
|
+
description: "Generate a monthly close report with reconciliation and anomaly review",
|
|
87
|
+
arguments: [
|
|
88
|
+
{ name: "month", description: "Month to close (YYYY-MM)", required: true },
|
|
89
|
+
{ name: "year", description: "Year (optional, defaults to current year)", required: false },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "categorize",
|
|
94
|
+
description: "Interactive transaction categorization workflow",
|
|
95
|
+
arguments: [
|
|
96
|
+
{ name: "transaction_ids", description: "Comma-separated list of transaction IDs, or 'all' for pending", required: true },
|
|
97
|
+
{ name: "auto_approve", description: "Auto-approve high-confidence suggestions", required: false },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "reconcile",
|
|
102
|
+
description: "Reconcile transactions with bank statements",
|
|
103
|
+
arguments: [
|
|
104
|
+
{ name: "start_date", description: "Start date (YYYY-MM-DD)", required: true },
|
|
105
|
+
{ name: "end_date", description: "End date (YYYY-MM-DD)", required: true },
|
|
106
|
+
{ name: "account_id", description: "Account ID to reconcile", required: false },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
112
|
+
tools: tools.map((t) => ({
|
|
113
|
+
name: t.name,
|
|
114
|
+
description: t.description,
|
|
115
|
+
inputSchema: t.schema,
|
|
116
|
+
})),
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
120
|
+
resources: resourceTemplates.map((rt) => ({
|
|
121
|
+
uri: rt.uriTemplate.replace("{id}", "*"),
|
|
122
|
+
name: rt.name,
|
|
123
|
+
description: rt.description,
|
|
124
|
+
mimeType: rt.mimeType,
|
|
125
|
+
})),
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
129
|
+
const uri = request.params.uri;
|
|
130
|
+
const db = await getDb();
|
|
131
|
+
const coId = getCurrentCompanyId();
|
|
132
|
+
|
|
133
|
+
if (uri === "bookmind://rules") {
|
|
134
|
+
const rows = db.exec(`SELECT id, vendor_pattern, description_pattern, category, confidence, priority, uses FROM categorization_rules WHERE company_id='${coId}' OR company_id='default' ORDER BY priority DESC`);
|
|
135
|
+
const rules = rows[0]?.values || [];
|
|
136
|
+
return {
|
|
137
|
+
contents: [
|
|
138
|
+
{
|
|
139
|
+
uri,
|
|
140
|
+
mimeType: "application/json",
|
|
141
|
+
text: JSON.stringify(rules.map(([id, vp, dp, cat, conf, pri, uses]) => ({ id, vendor_pattern: vp, description_pattern: dp, category: cat, confidence: conf, priority: pri, uses })), null, 2),
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (uri === "bookmind://accounts") {
|
|
148
|
+
try {
|
|
149
|
+
const { qboClient } = await import("./lib/qbo.js");
|
|
150
|
+
if (!qboClient.isAuthenticated()) {
|
|
151
|
+
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ error: "QBO not connected" }, null, 2) }] };
|
|
152
|
+
}
|
|
153
|
+
const accounts: any = await qboClient.getAccounts();
|
|
154
|
+
const accList = accounts.QueryResponse?.Account || [];
|
|
155
|
+
return {
|
|
156
|
+
contents: [
|
|
157
|
+
{
|
|
158
|
+
uri,
|
|
159
|
+
mimeType: "application/json",
|
|
160
|
+
text: JSON.stringify(accList.map((a: any) => ({ id: a.Id, name: a.Name, type: a.AccountType, balance: a.CurrentBalance })), null, 2),
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
} catch (e: any) {
|
|
165
|
+
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ error: e.message }, null, 2) }] };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const anomalyMatch = uri.match(/^bookmind:\/\/anomalies\/(.+)$/);
|
|
170
|
+
if (anomalyMatch) {
|
|
171
|
+
const anomalyId = anomalyMatch[1];
|
|
172
|
+
const rows: any = db.exec(`SELECT id, type, severity, description, transaction_id, vendor, amount, detected_at FROM anomalies WHERE id='${anomalyId.replace(/'/g, "''")}' AND company_id='${coId}'`);
|
|
173
|
+
const anomaly: any = rows[0]?.values?.[0];
|
|
174
|
+
if (!anomaly) {
|
|
175
|
+
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ error: "Anomaly not found" }, null, 2) }] };
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
contents: [
|
|
179
|
+
{
|
|
180
|
+
uri,
|
|
181
|
+
mimeType: "application/json",
|
|
182
|
+
text: JSON.stringify({ id: anomaly[0], type: anomaly[1], severity: anomaly[2], description: anomaly[3], transaction_id: anomaly[4], vendor: anomaly[5], amount: anomaly[6], detected_at: anomaly[7] }, null, 2),
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown resource: ${uri}`);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
192
|
+
prompts: prompts.map((p) => ({
|
|
193
|
+
name: p.name,
|
|
194
|
+
description: p.description,
|
|
195
|
+
arguments: p.arguments,
|
|
196
|
+
})),
|
|
197
|
+
}));
|
|
198
|
+
|
|
199
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
200
|
+
const { name, arguments: args } = request.params;
|
|
201
|
+
const db = await getDb();
|
|
202
|
+
const coId = getCurrentCompanyId();
|
|
203
|
+
|
|
204
|
+
if (name === "monthly-close") {
|
|
205
|
+
const month = args?.month;
|
|
206
|
+
if (!month) throw new McpError(ErrorCode.InvalidParams, "Missing required argument: month");
|
|
207
|
+
const [year, mon] = month.split("-");
|
|
208
|
+
const startDate = `${year}-${mon}-01`;
|
|
209
|
+
const endDate = new Date(parseInt(year), parseInt(mon), 0).toISOString().split("T")[0];
|
|
210
|
+
|
|
211
|
+
const rows: any = db.exec(`SELECT COUNT(*), SUM(amount), SUM(CASE WHEN status='categorized' THEN 1 ELSE 0 END), SUM(CASE WHEN status='uncategorized' THEN 1 ELSE 0 END) FROM transactions WHERE company_id='${coId}' AND date >= '${startDate}' AND date <= '${endDate}'`);
|
|
212
|
+
const stats: any = rows[0]?.values?.[0] || [0, 0, 0, 0];
|
|
213
|
+
const anomalyRows: any = db.exec(`SELECT COUNT(*), SUM(CASE WHEN acknowledged=0 THEN 1 ELSE 0 END) FROM anomalies WHERE company_id='${coId}' AND detected_at >= '${startDate}'`);
|
|
214
|
+
const anomalies: any = anomalyRows[0]?.values?.[0] || [0, 0];
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
description: `Monthly close for ${month}`,
|
|
218
|
+
messages: [
|
|
219
|
+
{
|
|
220
|
+
role: "user",
|
|
221
|
+
content: {
|
|
222
|
+
type: "text",
|
|
223
|
+
text: `Generate monthly close report for ${month}:
|
|
224
|
+
- Total transactions: ${stats[0]}
|
|
225
|
+
- Total volume: $${parseFloat(stats[1] || 0).toLocaleString(undefined, { minimumFractionDigits: 2 })}
|
|
226
|
+
- Categorized: ${stats[2]}
|
|
227
|
+
- Uncategorized: ${stats[3]}
|
|
228
|
+
- Anomalies detected: ${anomalies[0]}
|
|
229
|
+
- Unacknowledged anomalies: ${anomalies[1]}
|
|
230
|
+
|
|
231
|
+
Run reconcile, review anomalies, then generate close report.`,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (name === "categorize") {
|
|
239
|
+
const txIds = args?.transaction_ids;
|
|
240
|
+
if (!txIds) throw new McpError(ErrorCode.InvalidParams, "Missing required argument: transaction_ids");
|
|
241
|
+
const autoApprove = args?.auto_approve === "true";
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
description: "Categorize transactions",
|
|
245
|
+
messages: [
|
|
246
|
+
{
|
|
247
|
+
role: "user",
|
|
248
|
+
content: {
|
|
249
|
+
type: "text",
|
|
250
|
+
text: `Categorize transactions: ${txIds}
|
|
251
|
+
Auto-approve high-confidence suggestions: ${autoApprove}
|
|
252
|
+
|
|
253
|
+
1. Run bookmind.categorize with the transaction IDs
|
|
254
|
+
2. Review suggestions
|
|
255
|
+
3. Run bookmind.approve_categorizations or bookmind.reject_categorizations as needed
|
|
256
|
+
4. Optionally run bookmind.learn_rules to create rules from corrections`,
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (name === "reconcile") {
|
|
264
|
+
const startDate = args?.start_date;
|
|
265
|
+
const endDate = args?.end_date;
|
|
266
|
+
if (!startDate || !endDate) throw new McpError(ErrorCode.InvalidParams, "Missing required arguments: start_date, end_date");
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
description: "Reconcile transactions",
|
|
270
|
+
messages: [
|
|
271
|
+
{
|
|
272
|
+
role: "user",
|
|
273
|
+
content: {
|
|
274
|
+
type: "text",
|
|
275
|
+
text: `Reconcile transactions from ${startDate} to ${endDate}
|
|
276
|
+
Account: ${args?.account_id || "all"}
|
|
277
|
+
|
|
278
|
+
1. Run bookmind.fetch_transactions with the date range
|
|
279
|
+
2. Run bookmind.reconcile with the fetched transactions
|
|
280
|
+
3. Review matches and exceptions
|
|
281
|
+
4. Approve reconciliations`,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown prompt: ${name}`);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
292
|
+
const tool = tools.find((t) => t.name === request.params.name);
|
|
293
|
+
if (!tool) {
|
|
294
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const db = await getDb();
|
|
299
|
+
const result = await tool.handler(request.params.arguments ?? {}, db);
|
|
300
|
+
await saveDb();
|
|
301
|
+
return {
|
|
302
|
+
content: [{ type: "text", text: typeof result === "string" ? result : JSON.stringify(result, null, 2) }],
|
|
303
|
+
};
|
|
304
|
+
} catch (error) {
|
|
305
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
306
|
+
throw new McpError(ErrorCode.InternalError, message);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const transport = new StdioServerTransport();
|
|
311
|
+
await server.connect(transport);
|
|
312
|
+
console.error(`BookMind v1.0.0 — ${tools.length} tools, ${resourceTemplates.length} resources, ${prompts.length} prompts registered`);
|
|
313
|
+
console.error(`Active company: ${activeCompanyId.current || "default"}`);
|