@oliverames/mcp-server-for-wave 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -359,10 +359,14 @@ the live schema.
359
359
 
360
360
  ## Hosted Connector
361
361
 
362
- `worker/` holds a Cloudflare Worker serving the same tools at a URL, with
363
- per-user OAuth instead of a shared token. Users authorize against their own
364
- Wave account, tokens are encrypted before storage, and write access is chosen
365
- at authorization time so a read-only connection cannot be escalated later.
362
+ A Cloudflare Worker serves the same tools over OAuth instead of a shared token.
363
+ Users authorize against their own Wave account, tokens are encrypted before
364
+ storage, and write access is chosen at authorization time so a read-only
365
+ connection cannot be escalated later.
366
+
367
+ The deployment at **https://wave.amesvt.com/mcp** is private: an owner
368
+ allowlist restricts it to one Wave account, and any other account is refused
369
+ before a token is stored. Deploy your own copy from `worker/` to use it.
366
370
 
367
371
  See [worker/README.md](worker/README.md) for setup and the security model.
368
372
 
@@ -375,11 +379,12 @@ index.js Single-file server: client, 74 tools, 7 resources
375
379
  scripts/
376
380
  smoke-validate-graphql.mjs Schema-check every query against live Wave
377
381
  smoke-list-tools.mjs Start over stdio and enumerate what is advertised
382
+ smoke-packed-install.mjs Pack, install, and launch the way npx does
378
383
  sync-plugin-metadata.mjs Propagate the version to every host manifest
379
384
  check-release-consistency Fail the build when anything disagrees
380
385
  build-mcpb.mjs Desktop bundle
381
386
  worker/ Hosted OAuth connector
382
- test/unit.test.mjs 54 tests, no network
387
+ test/unit.test.mjs 58 tests, no network
383
388
  ```
384
389
 
385
390
  The tool layer lives in one file on purpose. It is imported unchanged by the
@@ -398,8 +403,9 @@ at all, which catches a field Wave renames before a user does.
398
403
 
399
404
  ```bash
400
405
  npm install
401
- npm test # 54 unit tests, no network
406
+ npm test # 58 unit tests, no network
402
407
  npm run smoke:list-tools # start over stdio, enumerate tools
408
+ npm run smoke:packed # pack, install, and launch via the bin symlink
403
409
  npm run smoke:schema # validate every query against live Wave
404
410
  npm run release:check # version parity across 8 manifests
405
411
  npm run build:mcpb # desktop bundle
@@ -24,6 +24,19 @@ server is simpler and has a smaller surface. The hosted connector earns its
24
24
  complexity when more than one person needs access, or when your client cannot
25
25
  launch a subprocess.
26
26
 
27
+ ## Who may connect
28
+
29
+ A hosted connector is reachable by anyone who learns its URL, so it carries an
30
+ owner allowlist. `ALLOWED_WAVE_USERS` names the Wave accounts, by user id or
31
+ account email, permitted to hold a connection. Anyone else who authorizes at
32
+ Wave is refused at `/callback` before a token is stored, and the check runs
33
+ again on every MCP session, so removing an entry ends a connection that already
34
+ exists rather than only blocking new ones.
35
+
36
+ The deployment at `wave.amesvt.com` is private and allows a single account. A
37
+ copy you self-host should name your own account, or leave the variable empty to
38
+ accept any Wave account.
39
+
27
40
  ## Access levels
28
41
 
29
42
  Write access is chosen during authorization, not afterwards. A read-only
package/index.js CHANGED
@@ -29,7 +29,7 @@ import { z } from "zod";
29
29
  // import-time config resolution and the stdio autostart are both skipped.
30
30
  const IS_CLOUDFLARE_WORKERS = globalThis.navigator?.userAgent === "Cloudflare-Workers";
31
31
 
32
- const SERVER_VERSION = "1.0.0";
32
+ const SERVER_VERSION = "1.0.2";
33
33
  const WAVE_ENDPOINT = "https://gql.waveapps.com/graphql/public";
34
34
  const WAVE_API_HOST = "gql.waveapps.com";
35
35
  const MAX_TOKEN_FILE_BYTES = 4096;
@@ -312,6 +312,19 @@ function readOnePasswordSecret(reference) {
312
312
  return token;
313
313
  }
314
314
 
315
+ /**
316
+ * Strip credentials from text headed for stderr.
317
+ *
318
+ * The factory's sanitizeErrorMessage closes over the live token and is not
319
+ * reachable from process-level handlers, so this covers the header forms that
320
+ * show up in a stack trace.
321
+ */
322
+ function redactTokens(value) {
323
+ return String(value ?? "")
324
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED_TOKEN]")
325
+ .replace(/Authorization:\s*[^\r\n]+/gi, "Authorization: [REDACTED_TOKEN]");
326
+ }
327
+
315
328
  function detectAgent() {
316
329
  const env = globalThis.process?.env ?? {};
317
330
  if (env.CLAUDECODE || env.CLAUDE_CODE_ENTRYPOINT) return "claude-code";
@@ -5774,6 +5787,7 @@ export const __testables = {
5774
5787
  extractFromJsonMcpConfig,
5775
5788
  pickRuntimeKeys,
5776
5789
  isDirectRun,
5790
+ redactTokens,
5777
5791
  };
5778
5792
 
5779
5793
  // --- Start ---
@@ -5808,9 +5822,24 @@ function isDirectRun() {
5808
5822
  }
5809
5823
  }
5810
5824
 
5825
+ if (!IS_CLOUDFLARE_WORKERS) {
5826
+ // Without these, an unhandled rejection takes the process down with no
5827
+ // usable output, and the MCP client reports only a dead server. Sanitize
5828
+ // first: a stack can carry the access token.
5829
+ globalThis.process.on("uncaughtException", (error) => {
5830
+ console.error(`Uncaught exception: ${redactTokens(error?.stack || error)}`);
5831
+ globalThis.process.exit(1);
5832
+ });
5833
+
5834
+ globalThis.process.on("unhandledRejection", (reason) => {
5835
+ console.error(`Unhandled promise rejection: ${redactTokens(reason?.stack || reason)}`);
5836
+ globalThis.process.exit(1);
5837
+ });
5838
+ }
5839
+
5811
5840
  if (isDirectRun()) {
5812
5841
  main().catch((error) => {
5813
- console.error(`Fatal error starting MCP Server for Wave: ${error?.message ?? error}`);
5842
+ console.error(`Fatal error starting MCP Server for Wave: ${redactTokens(error?.message ?? error)}`);
5814
5843
  globalThis.process.exit(1);
5815
5844
  });
5816
5845
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oliverames/mcp-server-for-wave",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Local MCP server for Wave Accounting: invoices, estimates, customers, products, taxes, and bookkeeping",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -49,7 +49,7 @@
49
49
  "presmoke:packed": "npm run deps:ensure",
50
50
  "smoke:packed": "node scripts/smoke-packed-install.mjs",
51
51
  "sync:plugin": "node scripts/sync-plugin-metadata.mjs",
52
- "version": "npm run sync:plugin && git add .agents/plugins/marketplace.json .claude-plugin/marketplace.json .claude-plugin/plugin.json codex/.codex-plugin/plugin.json codex/.codex-plugin/mcp.json .hermes-plugin/marketplace.json .hermes-plugin/plugin.json .hermes-plugin/mcp.json .antigravity-plugin/marketplace.json .antigravity-plugin/plugin.json .antigravity-plugin/mcp_config.json .mcp.json",
52
+ "version": "npm run sync:plugin && git add index.js .agents/plugins/marketplace.json .claude-plugin/marketplace.json .claude-plugin/plugin.json codex/.codex-plugin/plugin.json codex/.codex-plugin/mcp.json .hermes-plugin/marketplace.json .hermes-plugin/plugin.json .hermes-plugin/mcp.json .antigravity-plugin/marketplace.json .antigravity-plugin/plugin.json .antigravity-plugin/mcp_config.json .mcp.json",
53
53
  "release:check": "node scripts/check-release-consistency.mjs",
54
54
  "release:check:registry": "node scripts/check-release-consistency.mjs --registry",
55
55
  "build:mcpb": "node scripts/build-mcpb.mjs"