@mpurdon/mcp-freshbooks 0.1.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/LICENSE +21 -0
- package/README.md +221 -0
- package/dist/freshbooks/auth.js +147 -0
- package/dist/freshbooks/auth.js.map +1 -0
- package/dist/freshbooks/client.js +225 -0
- package/dist/freshbooks/client.js.map +1 -0
- package/dist/freshbooks/dotenv.js +53 -0
- package/dist/freshbooks/dotenv.js.map +1 -0
- package/dist/freshbooks/types.js +6 -0
- package/dist/freshbooks/types.js.map +1 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/dist/setup.js +292 -0
- package/dist/setup.js.map +1 -0
- package/dist/tools/account.js +14 -0
- package/dist/tools/account.js.map +1 -0
- package/dist/tools/clients.js +39 -0
- package/dist/tools/clients.js.map +1 -0
- package/dist/tools/invoice_generator.js +235 -0
- package/dist/tools/invoice_generator.js.map +1 -0
- package/dist/tools/invoices.js +247 -0
- package/dist/tools/invoices.js.map +1 -0
- package/dist/tools/items.js +24 -0
- package/dist/tools/items.js.map +1 -0
- package/dist/tools/time_entries.js +204 -0
- package/dist/tools/time_entries.js.map +1 -0
- package/dist/tools/timesheet.js +395 -0
- package/dist/tools/timesheet.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal .env loader — no dependency. Handles `KEY=value` lines, `#` comments,
|
|
3
|
+
* and single/double-quoted values. Never overrides a variable already present in
|
|
4
|
+
* the environment (the MCP client's `env` block always wins).
|
|
5
|
+
*
|
|
6
|
+
* Looks for `.env` in the package root first (so it works regardless of the
|
|
7
|
+
* server's working directory), then falls back to the current directory. The
|
|
8
|
+
* first file found wins.
|
|
9
|
+
*/
|
|
10
|
+
import { promises as fs } from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
function candidatePaths() {
|
|
14
|
+
// Compiled location is dist/freshbooks/dotenv.js — package root is two up.
|
|
15
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const packageRoot = path.resolve(here, "..", "..");
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const p of [path.join(packageRoot, ".env"), path.join(cwd, ".env")]) {
|
|
20
|
+
if (!out.includes(p))
|
|
21
|
+
out.push(p);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
export async function loadDotEnv() {
|
|
26
|
+
for (const envPath of candidatePaths()) {
|
|
27
|
+
let raw;
|
|
28
|
+
try {
|
|
29
|
+
raw = await fs.readFile(envPath, "utf8");
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
37
|
+
continue;
|
|
38
|
+
const eq = trimmed.indexOf("=");
|
|
39
|
+
if (eq === -1)
|
|
40
|
+
continue;
|
|
41
|
+
const key = trimmed.slice(0, eq).trim();
|
|
42
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
43
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
44
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
45
|
+
value = value.slice(1, -1);
|
|
46
|
+
}
|
|
47
|
+
if (!(key in process.env))
|
|
48
|
+
process.env[key] = value;
|
|
49
|
+
}
|
|
50
|
+
return; // first .env wins
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=dotenv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dotenv.js","sourceRoot":"","sources":["../../src/freshbooks/dotenv.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,SAAS,cAAc;IACrB,2EAA2E;IAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QACzE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,EAAE,CAAC;QACvC,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YAClD,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,EAAE,KAAK,CAAC,CAAC;gBAAE,SAAS;YACxB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACxC,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACzC,IACE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAC9C,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC9C,CAAC;gBACD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC7B,CAAC;YACD,IAAI,CAAC,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtD,CAAC;QACD,OAAO,CAAC,kBAAkB;IAC5B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/freshbooks/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* FreshBooks MCP server (stdio transport for Claude Desktop).
|
|
4
|
+
*
|
|
5
|
+
* On startup:
|
|
6
|
+
* 1. Load app credentials from env (FRESHBOOKS_CLIENT_ID/SECRET).
|
|
7
|
+
* 2. Load tokens from disk (default ~/.freshbooks-mcp/tokens.json, mode 0600 enforced).
|
|
8
|
+
* 3. If access token is expired, refresh proactively before serving the first request.
|
|
9
|
+
* 4. Register tools and start the stdio transport.
|
|
10
|
+
*/
|
|
11
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
+
import { computeExpiresAt, defaultTokensPath, loadAppCredentials, loadTokens, refreshAccessToken, saveTokens, } from "./freshbooks/auth.js";
|
|
14
|
+
import { FreshBooksClient } from "./freshbooks/client.js";
|
|
15
|
+
import { loadDotEnv } from "./freshbooks/dotenv.js";
|
|
16
|
+
import { CreateInvoiceInput, DeleteInvoiceInput, GetInvoiceInput, ListInvoicesInput, SendInvoiceInput, UpdateInvoiceInput, createInvoice, deleteInvoice, getInvoice, listInvoices, sendInvoice, updateInvoice, } from "./tools/invoices.js";
|
|
17
|
+
import { GetClientInput, ListClientsInput, getClient, listClients, } from "./tools/clients.js";
|
|
18
|
+
import { ListItemsInput, listItems } from "./tools/items.js";
|
|
19
|
+
import { GetAccountInfoInput, getAccountInfo } from "./tools/account.js";
|
|
20
|
+
import { GenerateTimesheetInput, generateTimesheet, } from "./tools/timesheet.js";
|
|
21
|
+
import { GenerateInvoiceInput, generateInvoice, } from "./tools/invoice_generator.js";
|
|
22
|
+
import { ListTimeEntriesInput, CreateTimeEntryInput, listTimeEntries, createTimeEntry, } from "./tools/time_entries.js";
|
|
23
|
+
const SERVER_NAME = "freshbooks";
|
|
24
|
+
const SERVER_VERSION = "0.1.0";
|
|
25
|
+
function jsonResult(data) {
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function errorResult(err) {
|
|
31
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32
|
+
// Surface FreshBooks API error bodies when present, but never echo headers.
|
|
33
|
+
const body = err && typeof err === "object" && "body" in err
|
|
34
|
+
? err.body
|
|
35
|
+
: undefined;
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: JSON.stringify({
|
|
41
|
+
error: message,
|
|
42
|
+
...(body !== undefined ? { detail: body } : {}),
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Adapter: wraps a typed handler so it conforms to the MCP SDK's tool callback
|
|
51
|
+
* signature. The SDK already validates inputs against the registered ZodRawShape
|
|
52
|
+
* before invoking the callback, so we accept its loose `{[x: string]: any}` shape
|
|
53
|
+
* and re-cast through `unknown` to our typed handler. Errors are mapped to
|
|
54
|
+
* isError results so Claude can reason about failures.
|
|
55
|
+
*/
|
|
56
|
+
function tool(_schema, handler) {
|
|
57
|
+
return async (raw) => {
|
|
58
|
+
try {
|
|
59
|
+
const input = raw;
|
|
60
|
+
return jsonResult(await handler(input));
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return errorResult(err);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async function main() {
|
|
68
|
+
// Pick up FRESHBOOKS_* vars from a .env file if present (env block always wins).
|
|
69
|
+
await loadDotEnv();
|
|
70
|
+
const creds = loadAppCredentials();
|
|
71
|
+
const tokensPath = defaultTokensPath();
|
|
72
|
+
const tokens = await loadTokens(tokensPath);
|
|
73
|
+
// Proactive refresh if expired or expiring within 60s.
|
|
74
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
75
|
+
if (tokens.expires_at <= nowSec + 60) {
|
|
76
|
+
try {
|
|
77
|
+
const fresh = await refreshAccessToken(creds, tokens.refresh_token);
|
|
78
|
+
tokens.access_token = fresh.access_token;
|
|
79
|
+
tokens.refresh_token = fresh.refresh_token;
|
|
80
|
+
tokens.expires_at = computeExpiresAt(fresh.expires_in);
|
|
81
|
+
tokens.updated_at = new Date().toISOString();
|
|
82
|
+
await saveTokens(tokens, tokensPath);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
86
|
+
throw new Error(`Could not refresh FreshBooks access token at startup (${msg}). ` +
|
|
87
|
+
"Re-run the freshbooks-setup command to re-authorize.");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const fbClient = new FreshBooksClient({ creds, tokens, tokensPath });
|
|
91
|
+
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
|
|
92
|
+
// ---------- Invoices ----------
|
|
93
|
+
server.tool("list_invoices", "List invoices with optional filters (status, client, date range, search).", ListInvoicesInput.shape, tool(ListInvoicesInput, (i) => listInvoices(fbClient, i)));
|
|
94
|
+
server.tool("get_invoice", "Get a single invoice by id.", GetInvoiceInput.shape, tool(GetInvoiceInput, (i) => getInvoice(fbClient, i)));
|
|
95
|
+
server.tool("create_invoice", "Create a draft invoice for a client. Use list_clients to find client_id.", CreateInvoiceInput.shape, tool(CreateInvoiceInput, (i) => createInvoice(fbClient, i)));
|
|
96
|
+
server.tool("update_invoice", "Partially update an invoice. If `lines` is given, REPLACES all line items.", UpdateInvoiceInput.shape, tool(UpdateInvoiceInput, (i) => updateInvoice(fbClient, i)));
|
|
97
|
+
server.tool("send_invoice", "Email an invoice to its client. Optional recipient/subject/body overrides.", SendInvoiceInput.shape, tool(SendInvoiceInput, (i) => sendInvoice(fbClient, i)));
|
|
98
|
+
server.tool("delete_invoice", "Soft-delete an invoice (FreshBooks marks vis_state=1; recoverable in UI).", DeleteInvoiceInput.shape, tool(DeleteInvoiceInput, (i) => deleteInvoice(fbClient, i)));
|
|
99
|
+
// ---------- Clients ----------
|
|
100
|
+
server.tool("list_clients", "List clients with optional organization search.", ListClientsInput.shape, tool(ListClientsInput, (i) => listClients(fbClient, i)));
|
|
101
|
+
server.tool("get_client", "Get a single client by id.", GetClientInput.shape, tool(GetClientInput, (i) => getClient(fbClient, i)));
|
|
102
|
+
// ---------- Items ----------
|
|
103
|
+
server.tool("list_items", "List saved line items (useful when constructing invoices).", ListItemsInput.shape, tool(ListItemsInput, (i) => listItems(fbClient, i)));
|
|
104
|
+
// ---------- Account ----------
|
|
105
|
+
server.tool("get_account_info", "Return the discovered account_id and business name. Use to verify setup.", GetAccountInfoInput.shape, tool(GetAccountInfoInput, (i) => getAccountInfo(fbClient, i)));
|
|
106
|
+
// ---------- Timesheet ----------
|
|
107
|
+
server.tool("generate_timesheet", "Fetch time entries from FreshBooks and generate a Consultant-Bi-Weekly-Timesheet Excel file in ~/Documents/trajector/timesheets/.", GenerateTimesheetInput.shape, tool(GenerateTimesheetInput, (i) => generateTimesheet(fbClient, i)));
|
|
108
|
+
// ---------- Time Entries ----------
|
|
109
|
+
server.tool("list_time_entries", "List time entries for a date range. Returns a day-by-day breakdown, total hours, and which weekdays are missing entries. Use this before creating entries to see what already exists.", ListTimeEntriesInput.shape, tool(ListTimeEntriesInput, (i) => listTimeEntries(fbClient, i)));
|
|
110
|
+
server.tool("create_time_entry", "Create a single time entry in FreshBooks for a given date, project, service, and duration. Use list_time_entries first to find project_id and service_id.", CreateTimeEntryInput.shape, tool(CreateTimeEntryInput, (i) => createTimeEntry(fbClient, i)));
|
|
111
|
+
server.tool("generate_invoice", "Fetch time entries for a date range, group by project/service, look up billable rates, and create a draft FreshBooks invoice for Trajector. Returns invoice ID, number, total, and a line-item breakdown.", GenerateInvoiceInput.shape, tool(GenerateInvoiceInput, (i) => generateInvoice(fbClient, i)));
|
|
112
|
+
// ---------- Transport ----------
|
|
113
|
+
const transport = new StdioServerTransport();
|
|
114
|
+
await server.connect(transport);
|
|
115
|
+
// Server runs until the transport closes.
|
|
116
|
+
const shutdown = () => process.exit(0);
|
|
117
|
+
process.on("SIGINT", shutdown);
|
|
118
|
+
process.on("SIGTERM", shutdown);
|
|
119
|
+
}
|
|
120
|
+
// Guard late rejections (e.g. a FreshBooks request that resolves after the MCP
|
|
121
|
+
// layer already returned) so they can't crash the process and reset state.
|
|
122
|
+
process.on("unhandledRejection", (reason) => {
|
|
123
|
+
process.stderr.write(`[freshbooks] unhandled rejection (ignored): ${reason}\n`);
|
|
124
|
+
});
|
|
125
|
+
main().catch((err) => {
|
|
126
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
127
|
+
// stderr is fine: MCP stdio uses stdout for JSON-RPC, stderr is logging.
|
|
128
|
+
process.stderr.write(`[freshbooks] fatal: ${message}\n`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
});
|
|
131
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Interactive OAuth setup CLI.
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 1. Read FRESHBOOKS_CLIENT_ID/SECRET from env (or .env if present).
|
|
7
|
+
* 2. Ensure a locally-trusted TLS cert exists via mkcert (required because
|
|
8
|
+
* FreshBooks only accepts HTTPS redirect URIs).
|
|
9
|
+
* 3. Open the FreshBooks authorize URL in the default browser.
|
|
10
|
+
* 4. Spin up a one-shot HTTPS listener on https://localhost:8765/callback
|
|
11
|
+
* to capture the authorization code.
|
|
12
|
+
* 5. Exchange code for tokens.
|
|
13
|
+
* 6. Hit /auth/api/v1/users/me to discover account_id/business_name.
|
|
14
|
+
* 7. Persist {access_token, refresh_token, expires_at, account_id, ...} to
|
|
15
|
+
* ~/.freshbooks-mcp/tokens.json with mode 0600.
|
|
16
|
+
* 8. Print the exact Claude Desktop config snippet.
|
|
17
|
+
*
|
|
18
|
+
* Reads .env from cwd if FRESHBOOKS_CLIENT_ID is not already set in env.
|
|
19
|
+
*
|
|
20
|
+
* Prerequisites:
|
|
21
|
+
* brew install mkcert
|
|
22
|
+
* mkcert -install (once per machine — installs the local CA)
|
|
23
|
+
*/
|
|
24
|
+
import https from "node:https";
|
|
25
|
+
import { exec, execSync } from "node:child_process";
|
|
26
|
+
import { promises as fs } from "node:fs";
|
|
27
|
+
import path from "node:path";
|
|
28
|
+
import os from "node:os";
|
|
29
|
+
import { URL } from "node:url";
|
|
30
|
+
import { computeExpiresAt, defaultTokensPath, exchangeAuthCode, loadAppCredentials, saveTokens, } from "./freshbooks/auth.js";
|
|
31
|
+
import { loadDotEnv } from "./freshbooks/dotenv.js";
|
|
32
|
+
const AUTHORIZE_URL = "https://auth.freshbooks.com/oauth/authorize/";
|
|
33
|
+
const IDENTITY_URL = "https://api.freshbooks.com/auth/api/v1/users/me";
|
|
34
|
+
const CERTS_DIR = path.join(os.homedir(), ".freshbooks-mcp");
|
|
35
|
+
function openInBrowser(url) {
|
|
36
|
+
const cmd = process.platform === "darwin"
|
|
37
|
+
? `open "${url}"`
|
|
38
|
+
: process.platform === "win32"
|
|
39
|
+
? `start "" "${url}"`
|
|
40
|
+
: `xdg-open "${url}"`;
|
|
41
|
+
exec(cmd, (err) => {
|
|
42
|
+
if (err) {
|
|
43
|
+
process.stderr.write(`(could not auto-open browser: ${err.message})\n`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Ensure mkcert-generated TLS cert files exist for localhost.
|
|
49
|
+
* Certs are stored in ~/.freshbooks-mcp/ alongside tokens.
|
|
50
|
+
* Returns the PEM-encoded key and cert as strings.
|
|
51
|
+
*/
|
|
52
|
+
async function ensureCerts() {
|
|
53
|
+
const keyPath = path.join(CERTS_DIR, "localhost-key.pem");
|
|
54
|
+
const certPath = path.join(CERTS_DIR, "localhost.pem");
|
|
55
|
+
const keyExists = await fs
|
|
56
|
+
.stat(keyPath)
|
|
57
|
+
.then(() => true)
|
|
58
|
+
.catch(() => false);
|
|
59
|
+
const certExists = await fs
|
|
60
|
+
.stat(certPath)
|
|
61
|
+
.then(() => true)
|
|
62
|
+
.catch(() => false);
|
|
63
|
+
if (!keyExists || !certExists) {
|
|
64
|
+
// Check mkcert is available.
|
|
65
|
+
const hasMkcert = process.platform === "win32"
|
|
66
|
+
? (() => {
|
|
67
|
+
try {
|
|
68
|
+
execSync("where mkcert", { stdio: "pipe" });
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
})()
|
|
75
|
+
: (() => {
|
|
76
|
+
try {
|
|
77
|
+
execSync("which mkcert", { stdio: "pipe" });
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
if (!hasMkcert) {
|
|
85
|
+
throw new Error("mkcert is required to create a locally-trusted TLS certificate for the OAuth callback,\n" +
|
|
86
|
+
"because FreshBooks only accepts HTTPS redirect URIs.\n\n" +
|
|
87
|
+
"Install it and set up the local CA:\n" +
|
|
88
|
+
" brew install mkcert\n" +
|
|
89
|
+
" mkcert -install # installs the local CA — only needed once per machine\n\n" +
|
|
90
|
+
"Then re-run: npm run setup");
|
|
91
|
+
}
|
|
92
|
+
process.stdout.write("Generating locally-trusted TLS certificate with mkcert...\n");
|
|
93
|
+
await fs.mkdir(CERTS_DIR, { recursive: true, mode: 0o700 });
|
|
94
|
+
execSync(`mkcert -key-file "${keyPath}" -cert-file "${certPath}" localhost 127.0.0.1`, { stdio: "inherit" });
|
|
95
|
+
process.stdout.write(`TLS cert saved to ${CERTS_DIR}\n`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
process.stdout.write("Using existing TLS cert.\n");
|
|
99
|
+
}
|
|
100
|
+
const [key, cert] = await Promise.all([
|
|
101
|
+
fs.readFile(keyPath, "utf8"),
|
|
102
|
+
fs.readFile(certPath, "utf8"),
|
|
103
|
+
]);
|
|
104
|
+
return { key, cert };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Listens on the redirect URI over HTTPS for exactly one /callback request.
|
|
108
|
+
* Closes the server and resolves on success, rejects on error/timeout.
|
|
109
|
+
*/
|
|
110
|
+
async function awaitCallback(redirectUri, expectedState, tlsCreds, timeoutMs = 5 * 60 * 1000) {
|
|
111
|
+
const parsed = new URL(redirectUri);
|
|
112
|
+
const port = Number(parsed.port || 443);
|
|
113
|
+
const expectedPath = parsed.pathname;
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const server = https.createServer({ key: tlsCreds.key, cert: tlsCreds.cert }, (req, res) => {
|
|
116
|
+
if (!req.url) {
|
|
117
|
+
res.statusCode = 400;
|
|
118
|
+
res.end("missing url");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const reqUrl = new URL(req.url, `https://localhost:${port}`);
|
|
122
|
+
if (reqUrl.pathname !== expectedPath) {
|
|
123
|
+
res.statusCode = 404;
|
|
124
|
+
res.end("not found");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const code = reqUrl.searchParams.get("code");
|
|
128
|
+
const state = reqUrl.searchParams.get("state");
|
|
129
|
+
const error = reqUrl.searchParams.get("error");
|
|
130
|
+
if (error) {
|
|
131
|
+
res.statusCode = 400;
|
|
132
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
133
|
+
res.end(htmlPage(`Authorization failed: ${escapeHtml(error)}`));
|
|
134
|
+
server.close();
|
|
135
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!code) {
|
|
139
|
+
res.statusCode = 400;
|
|
140
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
141
|
+
res.end(htmlPage("No authorization code in callback URL."));
|
|
142
|
+
server.close();
|
|
143
|
+
reject(new Error("No code in callback"));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (state !== expectedState) {
|
|
147
|
+
res.statusCode = 400;
|
|
148
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
149
|
+
res.end(htmlPage("State mismatch — possible CSRF. Aborting."));
|
|
150
|
+
server.close();
|
|
151
|
+
reject(new Error("OAuth state mismatch"));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
res.statusCode = 200;
|
|
155
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
156
|
+
res.end(htmlPage("Authorization complete. You can close this tab and return to your terminal."));
|
|
157
|
+
server.close();
|
|
158
|
+
resolve({ code, state });
|
|
159
|
+
});
|
|
160
|
+
server.on("error", (err) => reject(err));
|
|
161
|
+
server.listen(port, "127.0.0.1");
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
server.close();
|
|
164
|
+
reject(new Error(`Timed out after ${Math.round(timeoutMs / 1000)}s waiting for callback.`));
|
|
165
|
+
}, timeoutMs).unref();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function htmlPage(message) {
|
|
169
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>FreshBooks MCP</title>
|
|
170
|
+
<style>body{font-family:-apple-system,system-ui,sans-serif;padding:3rem;max-width:40rem;margin:auto;color:#222}</style>
|
|
171
|
+
</head><body><h2>FreshBooks MCP setup</h2><p>${message}</p></body></html>`;
|
|
172
|
+
}
|
|
173
|
+
function escapeHtml(s) {
|
|
174
|
+
return s.replace(/[&<>"']/g, (ch) => {
|
|
175
|
+
switch (ch) {
|
|
176
|
+
case "&":
|
|
177
|
+
return "&";
|
|
178
|
+
case "<":
|
|
179
|
+
return "<";
|
|
180
|
+
case ">":
|
|
181
|
+
return ">";
|
|
182
|
+
case '"':
|
|
183
|
+
return """;
|
|
184
|
+
case "'":
|
|
185
|
+
return "'";
|
|
186
|
+
default:
|
|
187
|
+
return ch;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async function discoverAccount(accessToken) {
|
|
192
|
+
const res = await fetch(IDENTITY_URL, {
|
|
193
|
+
headers: {
|
|
194
|
+
Authorization: `Bearer ${accessToken}`,
|
|
195
|
+
"Api-Version": "alpha",
|
|
196
|
+
Accept: "application/json",
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
if (!res.ok) {
|
|
200
|
+
const text = await res.text();
|
|
201
|
+
throw new Error(`Identity lookup failed: ${res.status} ${text.slice(0, 300)}`);
|
|
202
|
+
}
|
|
203
|
+
const data = (await res.json());
|
|
204
|
+
const memberships = data.response?.business_memberships ?? [];
|
|
205
|
+
if (memberships.length === 0) {
|
|
206
|
+
throw new Error("No business memberships found on this FreshBooks user. " +
|
|
207
|
+
"Make sure you authorized with the correct account.");
|
|
208
|
+
}
|
|
209
|
+
if (memberships.length > 1) {
|
|
210
|
+
process.stderr.write(`Note: ${memberships.length} businesses found; using "${memberships[0].business.name}". ` +
|
|
211
|
+
"Edit ~/.freshbooks-mcp/tokens.json manually if you want a different one.\n");
|
|
212
|
+
}
|
|
213
|
+
const m = memberships[0];
|
|
214
|
+
return {
|
|
215
|
+
account_id: m.business.account_id,
|
|
216
|
+
business_id: m.business.id,
|
|
217
|
+
business_name: m.business.name,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function buildAuthorizeUrl(creds, state) {
|
|
221
|
+
const url = new URL(AUTHORIZE_URL);
|
|
222
|
+
url.searchParams.set("client_id", creds.client_id);
|
|
223
|
+
url.searchParams.set("response_type", "code");
|
|
224
|
+
url.searchParams.set("redirect_uri", creds.redirect_uri);
|
|
225
|
+
url.searchParams.set("state", state);
|
|
226
|
+
return url.toString();
|
|
227
|
+
}
|
|
228
|
+
function generateState() {
|
|
229
|
+
const bytes = new Uint8Array(16);
|
|
230
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
231
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
232
|
+
}
|
|
233
|
+
function printClaudeDesktopSnippet(creds) {
|
|
234
|
+
const distPath = path.resolve(process.cwd(), "dist", "index.js");
|
|
235
|
+
const snippet = {
|
|
236
|
+
mcpServers: {
|
|
237
|
+
freshbooks: {
|
|
238
|
+
command: "node",
|
|
239
|
+
args: [distPath],
|
|
240
|
+
env: {
|
|
241
|
+
FRESHBOOKS_CLIENT_ID: creds.client_id,
|
|
242
|
+
FRESHBOOKS_CLIENT_SECRET: creds.client_secret,
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
process.stdout.write("\n--- Claude Desktop config snippet ---\n");
|
|
248
|
+
process.stdout.write("Paste the `freshbooks` entry into the `mcpServers` object in:\n");
|
|
249
|
+
process.stdout.write(" macOS: ~/Library/Application Support/Claude/claude_desktop_config.json\n");
|
|
250
|
+
process.stdout.write(" Windows: %APPDATA%\\Claude\\claude_desktop_config.json\n\n");
|
|
251
|
+
process.stdout.write(JSON.stringify(snippet, null, 2));
|
|
252
|
+
process.stdout.write("\n\n");
|
|
253
|
+
process.stdout.write("(The client secret is included above — keep this snippet private.)\n");
|
|
254
|
+
}
|
|
255
|
+
async function main() {
|
|
256
|
+
await loadDotEnv();
|
|
257
|
+
const creds = loadAppCredentials();
|
|
258
|
+
const tlsCreds = await ensureCerts();
|
|
259
|
+
const state = generateState();
|
|
260
|
+
const authUrl = buildAuthorizeUrl(creds, state);
|
|
261
|
+
process.stdout.write("Opening FreshBooks authorization in your browser.\n");
|
|
262
|
+
process.stdout.write(`If it does not open, paste this URL manually:\n\n ${authUrl}\n\n`);
|
|
263
|
+
openInBrowser(authUrl);
|
|
264
|
+
process.stdout.write(`Listening on ${creds.redirect_uri} for callback...\n`);
|
|
265
|
+
const { code } = await awaitCallback(creds.redirect_uri, state, tlsCreds);
|
|
266
|
+
process.stdout.write("Got authorization code, exchanging for tokens...\n");
|
|
267
|
+
const exchanged = await exchangeAuthCode(creds, code);
|
|
268
|
+
process.stdout.write("Got tokens, discovering account...\n");
|
|
269
|
+
const account = await discoverAccount(exchanged.access_token);
|
|
270
|
+
const bundle = {
|
|
271
|
+
access_token: exchanged.access_token,
|
|
272
|
+
refresh_token: exchanged.refresh_token,
|
|
273
|
+
expires_at: computeExpiresAt(exchanged.expires_in),
|
|
274
|
+
account_id: account.account_id,
|
|
275
|
+
business_id: account.business_id,
|
|
276
|
+
business_name: account.business_name,
|
|
277
|
+
updated_at: new Date().toISOString(),
|
|
278
|
+
};
|
|
279
|
+
const tokensPath = defaultTokensPath();
|
|
280
|
+
await saveTokens(bundle, tokensPath);
|
|
281
|
+
process.stdout.write(`Tokens saved to ${tokensPath} (mode 0600).\n`);
|
|
282
|
+
process.stdout.write(`Connected to business: ${account.business_name ?? "(unnamed)"} ` +
|
|
283
|
+
`(account_id=${account.account_id}).\n`);
|
|
284
|
+
printClaudeDesktopSnippet(creds);
|
|
285
|
+
process.stdout.write("Setup complete.\n");
|
|
286
|
+
}
|
|
287
|
+
main().catch((err) => {
|
|
288
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
289
|
+
process.stderr.write(`\n[setup] failed: ${msg}\n`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
});
|
|
292
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account info tool — returns the discovered account_id and business name.
|
|
3
|
+
* Useful for verifying setup and confirming you're hitting the right tenant.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
export const GetAccountInfoInput = z.object({}).strict();
|
|
7
|
+
export async function getAccountInfo(client, _input) {
|
|
8
|
+
return {
|
|
9
|
+
account_id: client.accountId,
|
|
10
|
+
business_id: client.businessId,
|
|
11
|
+
business_name: client.businessName,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../src/tools/account.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAEzD,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAwB,EACxB,MAA2C;IAM3C,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,SAAS;QAC5B,WAAW,EAAE,MAAM,CAAC,UAAU;QAC9B,aAAa,EAAE,MAAM,CAAC,YAAY;KACnC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client tools: list_clients, get_client.
|
|
3
|
+
*
|
|
4
|
+
* Endpoint: /accounting/account/<account_id>/users/clients
|
|
5
|
+
* Single client: /accounting/account/<account_id>/users/clients/<client_id>
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
export const ListClientsInput = z
|
|
9
|
+
.object({
|
|
10
|
+
search: z
|
|
11
|
+
.string()
|
|
12
|
+
.max(200)
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Match organization or email."),
|
|
15
|
+
page: z.number().int().min(1).default(1),
|
|
16
|
+
per_page: z.number().int().min(1).max(100).default(20),
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
export const GetClientInput = z
|
|
20
|
+
.object({
|
|
21
|
+
client_id: z.number().int().positive(),
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
export async function listClients(client, input) {
|
|
25
|
+
const path = `/accounting/account/${client.accountId}/users/clients`;
|
|
26
|
+
const query = {
|
|
27
|
+
page: input.page,
|
|
28
|
+
per_page: input.per_page,
|
|
29
|
+
};
|
|
30
|
+
if (input.search) {
|
|
31
|
+
query["search[organization_like]"] = input.search;
|
|
32
|
+
}
|
|
33
|
+
return client.accounting("GET", path, "", { query });
|
|
34
|
+
}
|
|
35
|
+
export async function getClient(client, input) {
|
|
36
|
+
const path = `/accounting/account/${client.accountId}/users/clients/${input.client_id}`;
|
|
37
|
+
return client.accounting("GET", path, "client");
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=clients.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clients.js","sourceRoot":"","sources":["../../src/tools/clients.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC;KAC9B,MAAM,CAAC;IACN,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,8BAA8B,CAAC;IAC3C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACxC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACvD,CAAC;KACD,MAAM,EAAE,CAAC;AAEZ,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC;KAC5B,MAAM,CAAC;IACN,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CACvC,CAAC;KACD,MAAM,EAAE,CAAC;AAEZ,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAwB,EACxB,KAAuC;IAEvC,MAAM,IAAI,GAAG,uBAAuB,MAAM,CAAC,SAAS,gBAAgB,CAAC;IACrE,MAAM,KAAK,GAAoC;QAC7C,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAC;IACF,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,KAAK,CAAC,2BAA2B,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;IACpD,CAAC;IAQD,OAAO,MAAM,CAAC,UAAU,CAAS,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAwB,EACxB,KAAqC;IAErC,MAAM,IAAI,GAAG,uBAAuB,MAAM,CAAC,SAAS,kBAAkB,KAAK,CAAC,SAAS,EAAE,CAAC;IACxF,OAAO,MAAM,CAAC,UAAU,CAAS,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC1D,CAAC"}
|