@mjquinlan2000/lawmatics-mcp 0.1.3 → 0.1.5

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
@@ -1,41 +1,39 @@
1
1
  # @mjquinlan2000/lawmatics-mcp
2
2
 
3
- MCP server for the [Lawmatics](https://www.lawmatics.com/) legal CRM API. Exposes Lawmatics' REST API as MCP tools so AI assistants (Claude Desktop, etc.) can read data from Lawmatics accounts.
3
+ MCP server for the [Lawmatics](https://www.lawmatics.com/) legal CRM API. Exposes Lawmatics' REST API as read-only MCP tools so AI assistants (Claude Desktop, etc.) can read data from Lawmatics accounts.
4
4
 
5
- ## Installation
5
+ ## Quick Start
6
6
 
7
7
  ```sh
8
- npm install @mjquinlan2000/lawmatics-mcp
9
- # or run directly
10
- npx @mjquinlan2000/lawmatics-mcp
8
+ LM_ACCESS_TOKEN=your_token npx @mjquinlan2000/lawmatics-mcp
11
9
  ```
12
10
 
13
- ## Setup
11
+ ## Authentication
14
12
 
15
- ### 1. Obtain API credentials
13
+ ### Option 1: Access token (simplest)
16
14
 
17
- Register an application in Lawmatics to get a client ID and secret.
15
+ If you already have a Lawmatics access token, set it as an environment variable:
18
16
 
19
- ### 2. Set environment variables
17
+ ```sh
18
+ export LM_ACCESS_TOKEN=your_token
19
+ ```
20
+
21
+ ### Option 2: OAuth flow
22
+
23
+ If you have OAuth2 client credentials, you can use the built-in auth CLI to obtain tokens:
20
24
 
21
25
  ```sh
22
26
  export LM_CLIENT_ID=your_client_id
23
27
  export LM_CLIENT_SECRET=your_client_secret
24
28
  export LM_REDIRECT_URI=http://127.0.0.1:8081/callback
25
- ```
26
29
 
27
- ### 3. Authenticate
28
-
29
- ```sh
30
- # OAuth2 authorization (opens browser)
31
- yarn auth
30
+ # Opens a browser for OAuth2 authorization
31
+ npx @mjquinlan2000/lawmatics-mcp auth
32
32
  ```
33
33
 
34
- Tokens are persisted to `~/.config/lawmatics-mcp/tokens.json`.
35
-
36
- Alternatively, set `LM_ACCESS_TOKEN` directly to skip the OAuth flow.
34
+ Tokens are saved to `~/.config/lawmatics-mcp/tokens` (encrypted). See [Token Encryption](../README.md#token-encryption) for setup.
37
35
 
38
- > **Note:** Lawmatics does not support refresh tokens. When a token expires, run `yarn auth` again.
36
+ > **Note:** Lawmatics does not support refresh tokens. When a token expires, you must re-run the `auth` command.
39
37
 
40
38
  ## MCP Client Configuration
41
39
 
@@ -48,25 +46,8 @@ Add to your MCP client config (e.g. Claude Desktop `claude_desktop_config.json`)
48
46
  "command": "npx",
49
47
  "args": ["@mjquinlan2000/lawmatics-mcp"],
50
48
  "env": {
51
- "LM_CLIENT_ID": "your_client_id",
52
- "LM_CLIENT_SECRET": "your_client_secret"
53
- }
54
- }
55
- }
56
- }
57
- ```
58
-
59
- For local development with `tsx`:
60
-
61
- ```json
62
- {
63
- "mcpServers": {
64
- "lawmatics": {
65
- "command": "tsx",
66
- "args": ["src/server.ts"],
67
- "env": {
68
- "LM_CLIENT_ID": "${LM_CLIENT_ID}",
69
- "LM_CLIENT_SECRET": "${LM_CLIENT_SECRET}"
49
+ "LM_ACCESS_TOKEN": "your_token",
50
+ "NODE_MCP_SECRET_KEY": "your_64_char_hex_key"
70
51
  }
71
52
  }
72
53
  }
@@ -115,22 +96,53 @@ The server registers 86 read-only tools covering all Lawmatics GET endpoints. Re
115
96
  | Activities | `list_activities`, `get_activity` |
116
97
  | Users | `list_users`, `get_user`, `get_me` |
117
98
 
118
- ## Development
99
+ ## Local Development
119
100
 
120
101
  ```sh
121
- yarn start # Run server locally (stdio transport)
122
- yarn build # Bundle with tsup
123
- yarn typecheck # Type-check with tsc --noEmit
124
- yarn auth # Run OAuth2 flow
125
- yarn generate # Sanitize spec + regenerate typed client
102
+ git clone https://github.com/mjquinlan2000/node-mcps.git
103
+ cd node-mcps
104
+ yarn install
105
+ yarn build
106
+
107
+ # Run the server locally
108
+ yarn workspace @mjquinlan2000/lawmatics-mcp start
109
+
110
+ # Run tests
111
+ yarn workspace @mjquinlan2000/lawmatics-mcp test
112
+ yarn workspace @mjquinlan2000/lawmatics-mcp test:watch
113
+
114
+ # Regenerate typed client from API spec
115
+ yarn workspace @mjquinlan2000/lawmatics-mcp generate
116
+ ```
117
+
118
+ For local dev with an MCP client, use `tsx` directly:
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "lawmatics": {
124
+ "command": "tsx",
125
+ "args": ["/path/to/node-mcps/lawmatics-mcp/src/server.ts"],
126
+ "env": {
127
+ "LM_ACCESS_TOKEN": "your_token",
128
+ "NODE_MCP_SECRET_KEY": "your_64_char_hex_key"
129
+ }
130
+ }
131
+ }
132
+ }
126
133
  ```
127
134
 
128
135
  ## Project Structure
129
136
 
130
137
  ```
131
138
  src/
132
- ├── server.ts # MCP server entry point — registers all tools
133
- ├── auth.ts # OAuth2 config (delegates to shared oauth utility)
134
- ├── lm.ts # Configures generated HTTP client with base URL + auth
135
- └── client/ # Auto-generated typed API client (do not edit)
139
+ ├── server.ts # Entry point — CLI dispatch (server vs auth)
140
+ ├── create-server.ts # MCP server setup registers all tools
141
+ ├── tool-handler.ts # Generic tool handler factory
142
+ ├── helpers.ts # Utility functions
143
+ ├── auth.ts # OAuth2 config (delegates to shared oauth utility)
144
+ ├── lm.ts # Configures generated HTTP client with base URL + auth
145
+ ├── client/ # Auto-generated typed API client (do not edit)
146
+ ├── *.test.ts # Tests
147
+ └── ...
136
148
  ```
package/dist/auth.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getAccessToken,
3
3
  runCli
4
- } from "./chunk-MNDMZQAW.js";
4
+ } from "./chunk-226LPP43.js";
5
5
  export {
6
6
  getAccessToken,
7
7
  runCli
@@ -1,7 +1,7 @@
1
1
  // ../shared/dist/oauth.js
2
2
  import { execSync } from "child_process";
3
- import { randomBytes } from "crypto";
4
- import { mkdir, readFile, writeFile } from "fs/promises";
3
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
4
+ import { mkdir, readFile, unlink, writeFile } from "fs/promises";
5
5
  import { createServer } from "http";
6
6
  import { homedir } from "os";
7
7
  import { join } from "path";
@@ -10,18 +10,58 @@ function isRefreshable(tokens) {
10
10
  }
11
11
  function createAuth(config) {
12
12
  const configDir = join(process.env.MCP_CONFIG_DIR ?? join(homedir(), ".config"), config.name);
13
- const tokensPath = join(configDir, "tokens.json");
13
+ const legacyTokensPath = join(configDir, "tokens.json");
14
+ const encryptedTokensPath = join(configDir, "tokens");
15
+ function getEncryptionKey() {
16
+ const key = process.env.NODE_MCP_SECRET_KEY;
17
+ if (!key) {
18
+ throw new Error("NODE_MCP_SECRET_KEY environment variable is required. Generate one with: openssl rand -hex 32");
19
+ }
20
+ const buf = Buffer.from(key, "hex");
21
+ if (buf.length !== 32) {
22
+ throw new Error("NODE_MCP_SECRET_KEY must be a 64-character hex string (32 bytes). Generate one with: openssl rand -hex 32");
23
+ }
24
+ return buf;
25
+ }
26
+ function encrypt(plaintext) {
27
+ const key = getEncryptionKey();
28
+ const iv = randomBytes(12);
29
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
30
+ const encrypted = Buffer.concat([
31
+ cipher.update(plaintext, "utf-8"),
32
+ cipher.final()
33
+ ]);
34
+ const authTag = cipher.getAuthTag();
35
+ return Buffer.concat([iv, authTag, encrypted]);
36
+ }
37
+ function decrypt(data) {
38
+ const key = getEncryptionKey();
39
+ const iv = data.subarray(0, 12);
40
+ const authTag = data.subarray(12, 28);
41
+ const ciphertext = data.subarray(28);
42
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
43
+ decipher.setAuthTag(authTag);
44
+ return decipher.update(ciphertext, void 0, "utf-8") + decipher.final("utf-8");
45
+ }
14
46
  async function readTokens() {
15
47
  try {
16
- const data = await readFile(tokensPath, "utf-8");
17
- return JSON.parse(data);
48
+ const data = await readFile(encryptedTokensPath);
49
+ return JSON.parse(decrypt(data));
18
50
  } catch {
19
- return null;
51
+ try {
52
+ const data = await readFile(legacyTokensPath, "utf-8");
53
+ const tokens = JSON.parse(data);
54
+ await writeTokens(tokens);
55
+ await unlink(legacyTokensPath);
56
+ return tokens;
57
+ } catch {
58
+ return null;
59
+ }
20
60
  }
21
61
  }
22
62
  async function writeTokens(tokens) {
23
63
  await mkdir(configDir, { recursive: true });
24
- await writeFile(tokensPath, JSON.stringify(tokens, null, 2), {
64
+ await writeFile(encryptedTokensPath, encrypt(JSON.stringify(tokens)), {
25
65
  mode: 384
26
66
  });
27
67
  }
@@ -33,16 +73,25 @@ function createAuth(config) {
33
73
  }
34
74
  }
35
75
  async function exchangeCode(code, redirectUri, clientId, clientSecret) {
76
+ const useBasic = config.tokenAuthMethod === "basic";
77
+ const headers = {
78
+ "Content-Type": "application/x-www-form-urlencoded"
79
+ };
80
+ const bodyParams = {
81
+ grant_type: "authorization_code",
82
+ code,
83
+ redirect_uri: redirectUri
84
+ };
85
+ if (useBasic) {
86
+ headers.Authorization = `Basic ${btoa(`${clientId}:${clientSecret}`)}`;
87
+ } else {
88
+ bodyParams.client_id = clientId;
89
+ bodyParams.client_secret = clientSecret;
90
+ }
36
91
  const res = await fetch(config.tokenUrl, {
37
92
  method: "POST",
38
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
39
- body: new URLSearchParams({
40
- grant_type: "authorization_code",
41
- code,
42
- redirect_uri: redirectUri,
43
- client_id: clientId,
44
- client_secret: clientSecret
45
- })
93
+ headers,
94
+ body: new URLSearchParams(bodyParams)
46
95
  });
47
96
  if (!res.ok) {
48
97
  const text = await res.text();
@@ -60,15 +109,24 @@ function createAuth(config) {
60
109
  return { access_token: data.access_token };
61
110
  }
62
111
  async function refreshAccessToken(refreshToken, clientId, clientSecret) {
112
+ const useBasic = config.tokenAuthMethod === "basic";
113
+ const headers = {
114
+ "Content-Type": "application/x-www-form-urlencoded"
115
+ };
116
+ const bodyParams = {
117
+ grant_type: "refresh_token",
118
+ refresh_token: refreshToken
119
+ };
120
+ if (useBasic) {
121
+ headers.Authorization = `Basic ${btoa(`${clientId}:${clientSecret}`)}`;
122
+ } else {
123
+ bodyParams.client_id = clientId;
124
+ bodyParams.client_secret = clientSecret;
125
+ }
63
126
  const res = await fetch(config.tokenUrl, {
64
127
  method: "POST",
65
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
66
- body: new URLSearchParams({
67
- grant_type: "refresh_token",
68
- refresh_token: refreshToken,
69
- client_id: clientId,
70
- client_secret: clientSecret
71
- })
128
+ headers,
129
+ body: new URLSearchParams(bodyParams)
72
130
  });
73
131
  if (!res.ok) {
74
132
  throw new Error("Run 'yarn auth' to re-authenticate.");
@@ -184,7 +242,7 @@ ${authorizeUrl}
184
242
  });
185
243
  });
186
244
  await writeTokens(tokens);
187
- console.log(`Tokens saved to ${tokensPath}`);
245
+ console.log(`Tokens saved to ${encryptedTokensPath}`);
188
246
  }
189
247
  async function refresh() {
190
248
  const clientId = process.env[`${config.envPrefix}_CLIENT_ID`];
@@ -199,7 +257,7 @@ ${authorizeUrl}
199
257
  }
200
258
  const refreshed = await refreshAccessToken(tokens.refresh_token, clientId, clientSecret);
201
259
  await writeTokens(refreshed);
202
- console.log(`Tokens refreshed and saved to ${tokensPath}`);
260
+ console.log(`Tokens refreshed and saved to ${encryptedTokensPath}`);
203
261
  }
204
262
  function runCli2(command) {
205
263
  const cmd = command ?? process.argv[2];
@@ -233,4 +291,4 @@ export {
233
291
  getAccessToken,
234
292
  runCli
235
293
  };
236
- //# sourceMappingURL=chunk-MNDMZQAW.js.map
294
+ //# sourceMappingURL=chunk-226LPP43.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../shared/src/oauth.ts","../src/auth.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\nimport { mkdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport {\n createServer,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface OAuthConfig {\n name: string;\n authorizeUrl: string;\n tokenUrl: string;\n envPrefix: string;\n scope?: string;\n supportsRefresh: boolean;\n tokenAuthMethod?: \"body\" | \"basic\";\n}\n\ninterface BaseTokens {\n access_token: string;\n}\n\ninterface RefreshableTokens extends BaseTokens {\n refresh_token: string;\n expires_at: number;\n}\n\ntype StoredTokens = BaseTokens | RefreshableTokens;\n\nfunction isRefreshable(tokens: StoredTokens): tokens is RefreshableTokens {\n return \"refresh_token\" in tokens;\n}\n\ninterface RefreshableTokenResponse {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n}\n\nexport function createAuth(config: OAuthConfig): {\n getAccessToken: () => Promise<string>;\n runCli: (command?: string) => void;\n} {\n const configDir = join(\n process.env.MCP_CONFIG_DIR ?? join(homedir(), \".config\"),\n config.name,\n );\n const legacyTokensPath = join(configDir, \"tokens.json\");\n const encryptedTokensPath = join(configDir, \"tokens\");\n\n function getEncryptionKey(): Buffer {\n const key = process.env.NODE_MCP_SECRET_KEY;\n if (!key) {\n throw new Error(\n \"NODE_MCP_SECRET_KEY environment variable is required. \" +\n \"Generate one with: openssl rand -hex 32\",\n );\n }\n const buf = Buffer.from(key, \"hex\");\n if (buf.length !== 32) {\n throw new Error(\n \"NODE_MCP_SECRET_KEY must be a 64-character hex string (32 bytes). \" +\n \"Generate one with: openssl rand -hex 32\",\n );\n }\n return buf;\n }\n\n function encrypt(plaintext: string): Buffer {\n const key = getEncryptionKey();\n const iv = randomBytes(12);\n const cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n const encrypted = Buffer.concat([\n cipher.update(plaintext, \"utf-8\"),\n cipher.final(),\n ]);\n const authTag = cipher.getAuthTag();\n return Buffer.concat([iv, authTag, encrypted]);\n }\n\n function decrypt(data: Buffer): string {\n const key = getEncryptionKey();\n const iv = data.subarray(0, 12);\n const authTag = data.subarray(12, 28);\n const ciphertext = data.subarray(28);\n const decipher = createDecipheriv(\"aes-256-gcm\", key, iv);\n decipher.setAuthTag(authTag);\n return (\n decipher.update(ciphertext, undefined, \"utf-8\") + decipher.final(\"utf-8\")\n );\n }\n\n async function readTokens(): Promise<StoredTokens | null> {\n try {\n const data = await readFile(encryptedTokensPath);\n return JSON.parse(decrypt(data)) as StoredTokens;\n } catch {\n // Fall back to legacy plaintext tokens.json\n try {\n const data = await readFile(legacyTokensPath, \"utf-8\");\n const tokens = JSON.parse(data) as StoredTokens;\n await writeTokens(tokens);\n await unlink(legacyTokensPath);\n return tokens;\n } catch {\n return null;\n }\n }\n }\n\n async function writeTokens(tokens: StoredTokens): Promise<void> {\n await mkdir(configDir, { recursive: true });\n await writeFile(encryptedTokensPath, encrypt(JSON.stringify(tokens)), {\n mode: 0o600,\n });\n }\n\n function openBrowser(url: string): void {\n const cmd =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n try {\n execSync(`${cmd} '${url}'`, { stdio: \"ignore\" });\n } catch {\n // No browser available (e.g. headless server) — URL is already printed to console\n }\n }\n\n async function exchangeCode(\n code: string,\n redirectUri: string,\n clientId: string,\n clientSecret: string,\n ): Promise<StoredTokens> {\n const useBasic = config.tokenAuthMethod === \"basic\";\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n };\n const bodyParams: Record<string, string> = {\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n };\n if (useBasic) {\n headers.Authorization = `Basic ${btoa(`${clientId}:${clientSecret}`)}`;\n } else {\n bodyParams.client_id = clientId;\n bodyParams.client_secret = clientSecret;\n }\n const res = await fetch(config.tokenUrl, {\n method: \"POST\",\n headers,\n body: new URLSearchParams(bodyParams),\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Token exchange failed (${res.status}): ${text}`);\n }\n if (config.supportsRefresh) {\n const data = (await res.json()) as RefreshableTokenResponse;\n return {\n access_token: data.access_token,\n refresh_token: data.refresh_token,\n expires_at: Date.now() + data.expires_in * 1000,\n };\n }\n const data = (await res.json()) as { access_token: string };\n return { access_token: data.access_token };\n }\n\n async function refreshAccessToken(\n refreshToken: string,\n clientId: string,\n clientSecret: string,\n ): Promise<RefreshableTokens> {\n const useBasic = config.tokenAuthMethod === \"basic\";\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n };\n const bodyParams: Record<string, string> = {\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n };\n if (useBasic) {\n headers.Authorization = `Basic ${btoa(`${clientId}:${clientSecret}`)}`;\n } else {\n bodyParams.client_id = clientId;\n bodyParams.client_secret = clientSecret;\n }\n const res = await fetch(config.tokenUrl, {\n method: \"POST\",\n headers,\n body: new URLSearchParams(bodyParams),\n });\n if (!res.ok) {\n throw new Error(\"Run 'yarn auth' to re-authenticate.\");\n }\n const data = (await res.json()) as RefreshableTokenResponse;\n return {\n access_token: data.access_token,\n refresh_token: data.refresh_token,\n expires_at: Date.now() + data.expires_in * 1000,\n };\n }\n\n async function getAccessToken(): Promise<string> {\n const envToken = process.env[`${config.envPrefix}_ACCESS_TOKEN`];\n if (envToken) return envToken;\n\n const tokens = await readTokens();\n if (!tokens) {\n throw new Error(\n `No access token available. Set ${config.envPrefix}_ACCESS_TOKEN or run 'yarn auth'.`,\n );\n }\n\n if (config.supportsRefresh && isRefreshable(tokens)) {\n const fiveMinutes = 5 * 60 * 1000;\n if (tokens.expires_at - Date.now() < fiveMinutes) {\n const clientId = process.env[`${config.envPrefix}_CLIENT_ID`];\n const clientSecret = process.env[`${config.envPrefix}_CLIENT_SECRET`];\n if (!clientId || !clientSecret) {\n throw new Error(\n `Token is expiring and ${config.envPrefix}_CLIENT_ID/${config.envPrefix}_CLIENT_SECRET are not set for refresh. Run 'yarn auth'.`,\n );\n }\n const refreshed = await refreshAccessToken(\n tokens.refresh_token,\n clientId,\n clientSecret,\n );\n await writeTokens(refreshed);\n return refreshed.access_token;\n }\n }\n\n return tokens.access_token;\n }\n\n async function authorize(): Promise<void> {\n const clientId = process.env[`${config.envPrefix}_CLIENT_ID`];\n const clientSecret = process.env[`${config.envPrefix}_CLIENT_SECRET`];\n if (!clientId)\n throw new Error(\n `${config.envPrefix}_CLIENT_ID environment variable is required.`,\n );\n if (!clientSecret)\n throw new Error(\n `${config.envPrefix}_CLIENT_SECRET environment variable is required.`,\n );\n\n const redirectUri = process.env[`${config.envPrefix}_REDIRECT_URI`];\n if (!redirectUri)\n throw new Error(\n `${config.envPrefix}_REDIRECT_URI environment variable is required.`,\n );\n\n const redirectUrl = new URL(redirectUri);\n const port =\n Number(redirectUrl.port) ||\n (redirectUrl.protocol === \"https:\" ? 443 : 80);\n const callbackPath = redirectUrl.pathname;\n const state = randomBytes(16).toString(\"hex\");\n\n const { tokens } = await new Promise<{\n tokens: StoredTokens;\n }>((resolve, reject) => {\n const timeout = setTimeout(() => {\n server.close();\n reject(new Error(\"Authorization timed out after 120 seconds.\"));\n }, 120_000);\n\n const server = createServer(\n async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n if (url.pathname !== callbackPath) {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n const returnedState = url.searchParams.get(\"state\");\n const code = url.searchParams.get(\"code\");\n const error = url.searchParams.get(\"error\");\n\n if (error) {\n res.writeHead(400);\n res.end(`Authorization error: ${error}`);\n clearTimeout(timeout);\n server.close();\n reject(new Error(`Authorization denied: ${error}`));\n return;\n }\n\n if (returnedState !== state) {\n res.writeHead(400);\n res.end(\"State mismatch — possible CSRF attack.\");\n return;\n }\n\n if (!code) {\n res.writeHead(400);\n res.end(\"Missing authorization code.\");\n return;\n }\n\n try {\n const exchangedTokens = await exchangeCode(\n code,\n redirectUri,\n clientId,\n clientSecret,\n );\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<h1>Authorization successful!</h1><p>You can close this tab.</p>\",\n );\n clearTimeout(timeout);\n server.close();\n resolve({ tokens: exchangedTokens });\n } catch (err) {\n res.writeHead(500);\n res.end(\"Token exchange failed.\");\n clearTimeout(timeout);\n server.close();\n reject(err);\n }\n },\n );\n\n const params: Record<string, string> = {\n response_type: \"code\",\n client_id: clientId,\n redirect_uri: redirectUri,\n state,\n };\n if (config.scope) {\n params.scope = config.scope;\n }\n\n server.listen(port, \"127.0.0.1\", () => {\n const authorizeUrl = `${config.authorizeUrl}?${new URLSearchParams(params)}`;\n\n console.log(\"Opening browser for authorization...\");\n console.log(`If the browser doesn't open, visit:\\n${authorizeUrl}\\n`);\n openBrowser(authorizeUrl);\n });\n });\n\n await writeTokens(tokens);\n console.log(`Tokens saved to ${encryptedTokensPath}`);\n }\n\n async function refresh(): Promise<void> {\n const clientId = process.env[`${config.envPrefix}_CLIENT_ID`];\n const clientSecret = process.env[`${config.envPrefix}_CLIENT_SECRET`];\n if (!clientId)\n throw new Error(\n `${config.envPrefix}_CLIENT_ID environment variable is required.`,\n );\n if (!clientSecret)\n throw new Error(\n `${config.envPrefix}_CLIENT_SECRET environment variable is required.`,\n );\n\n const tokens = await readTokens();\n if (!tokens || !isRefreshable(tokens)) {\n throw new Error(\n \"No tokens found. Run 'yarn auth' first to authenticate.\",\n );\n }\n\n const refreshed = await refreshAccessToken(\n tokens.refresh_token,\n clientId,\n clientSecret,\n );\n await writeTokens(refreshed);\n console.log(`Tokens refreshed and saved to ${encryptedTokensPath}`);\n }\n\n function runCli(command?: string): void {\n const cmd = command ?? process.argv[2];\n let run: () => Promise<void>;\n if (cmd === \"refresh\" && config.supportsRefresh) {\n run = refresh;\n } else {\n run = authorize;\n }\n run().catch((err) => {\n console.error(err.message ?? err);\n process.exit(1);\n });\n }\n\n return { getAccessToken, runCli };\n}\n","import { createAuth } from \"@mjquinlan2000/shared/oauth.js\";\n\nconst { getAccessToken, runCli } = createAuth({\n name: \"lawmatics-mcp\",\n authorizeUrl: \"https://app.lawmatics.com/oauth/authorize\",\n tokenUrl: \"https://api.lawmatics.com/oauth/token\",\n envPrefix: \"LM\",\n supportsRefresh: false,\n});\n\nexport { getAccessToken, runCli };\n\nif (process.argv[1] && import.meta.filename === process.argv[1]) {\n runCli();\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,gBAAgB,kBAAkB,mBAAmB;AAC9D,SAAS,OAAO,UAAU,QAAQ,iBAAiB;AACnD,SACE,oBAGK;AACP,SAAS,eAAe;AACxB,SAAS,YAAY;AAuBrB,SAAS,cAAc,QAAoB;AACzC,SAAO,mBAAmB;AAC5B;AAQM,SAAU,WAAW,QAAmB;AAI5C,QAAM,YAAY,KAChB,QAAQ,IAAI,kBAAkB,KAAK,QAAO,GAAI,SAAS,GACvD,OAAO,IAAI;AAEb,QAAM,mBAAmB,KAAK,WAAW,aAAa;AACtD,QAAM,sBAAsB,KAAK,WAAW,QAAQ;AAEpD,WAAS,mBAAgB;AACvB,UAAM,MAAM,QAAQ,IAAI;AACxB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MACR,+FAC2C;IAE/C;AACA,UAAM,MAAM,OAAO,KAAK,KAAK,KAAK;AAClC,QAAI,IAAI,WAAW,IAAI;AACrB,YAAM,IAAI,MACR,2GAC2C;IAE/C;AACA,WAAO;EACT;AAEA,WAAS,QAAQ,WAAiB;AAChC,UAAM,MAAM,iBAAgB;AAC5B,UAAM,KAAK,YAAY,EAAE;AACzB,UAAM,SAAS,eAAe,eAAe,KAAK,EAAE;AACpD,UAAM,YAAY,OAAO,OAAO;MAC9B,OAAO,OAAO,WAAW,OAAO;MAChC,OAAO,MAAK;KACb;AACD,UAAM,UAAU,OAAO,WAAU;AACjC,WAAO,OAAO,OAAO,CAAC,IAAI,SAAS,SAAS,CAAC;EAC/C;AAEA,WAAS,QAAQ,MAAY;AAC3B,UAAM,MAAM,iBAAgB;AAC5B,UAAM,KAAK,KAAK,SAAS,GAAG,EAAE;AAC9B,UAAM,UAAU,KAAK,SAAS,IAAI,EAAE;AACpC,UAAM,aAAa,KAAK,SAAS,EAAE;AACnC,UAAM,WAAW,iBAAiB,eAAe,KAAK,EAAE;AACxD,aAAS,WAAW,OAAO;AAC3B,WACE,SAAS,OAAO,YAAY,QAAW,OAAO,IAAI,SAAS,MAAM,OAAO;EAE5E;AAEA,iBAAe,aAAU;AACvB,QAAI;AACF,YAAM,OAAO,MAAM,SAAS,mBAAmB;AAC/C,aAAO,KAAK,MAAM,QAAQ,IAAI,CAAC;IACjC,QAAQ;AAEN,UAAI;AACF,cAAM,OAAO,MAAM,SAAS,kBAAkB,OAAO;AACrD,cAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,cAAM,YAAY,MAAM;AACxB,cAAM,OAAO,gBAAgB;AAC7B,eAAO;MACT,QAAQ;AACN,eAAO;MACT;IACF;EACF;AAEA,iBAAe,YAAY,QAAoB;AAC7C,UAAM,MAAM,WAAW,EAAE,WAAW,KAAI,CAAE;AAC1C,UAAM,UAAU,qBAAqB,QAAQ,KAAK,UAAU,MAAM,CAAC,GAAG;MACpE,MAAM;KACP;EACH;AAEA,WAAS,YAAY,KAAW;AAC9B,UAAM,MACJ,QAAQ,aAAa,WACjB,SACA,QAAQ,aAAa,UACnB,UACA;AACR,QAAI;AACF,eAAS,GAAG,GAAG,KAAK,GAAG,KAAK,EAAE,OAAO,SAAQ,CAAE;IACjD,QAAQ;IAER;EACF;AAEA,iBAAe,aACb,MACA,aACA,UACA,cAAoB;AAEpB,UAAM,WAAW,OAAO,oBAAoB;AAC5C,UAAM,UAAkC;MACtC,gBAAgB;;AAElB,UAAM,aAAqC;MACzC,YAAY;MACZ;MACA,cAAc;;AAEhB,QAAI,UAAU;AACZ,cAAQ,gBAAgB,SAAS,KAAK,GAAG,QAAQ,IAAI,YAAY,EAAE,CAAC;IACtE,OAAO;AACL,iBAAW,YAAY;AACvB,iBAAW,gBAAgB;IAC7B;AACA,UAAM,MAAM,MAAM,MAAM,OAAO,UAAU;MACvC,QAAQ;MACR;MACA,MAAM,IAAI,gBAAgB,UAAU;KACrC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,OAAO,MAAM,IAAI,KAAI;AAC3B,YAAM,IAAI,MAAM,0BAA0B,IAAI,MAAM,MAAM,IAAI,EAAE;IAClE;AACA,QAAI,OAAO,iBAAiB;AAC1B,YAAMA,QAAQ,MAAM,IAAI,KAAI;AAC5B,aAAO;QACL,cAAcA,MAAK;QACnB,eAAeA,MAAK;QACpB,YAAY,KAAK,IAAG,IAAKA,MAAK,aAAa;;IAE/C;AACA,UAAM,OAAQ,MAAM,IAAI,KAAI;AAC5B,WAAO,EAAE,cAAc,KAAK,aAAY;EAC1C;AAEA,iBAAe,mBACb,cACA,UACA,cAAoB;AAEpB,UAAM,WAAW,OAAO,oBAAoB;AAC5C,UAAM,UAAkC;MACtC,gBAAgB;;AAElB,UAAM,aAAqC;MACzC,YAAY;MACZ,eAAe;;AAEjB,QAAI,UAAU;AACZ,cAAQ,gBAAgB,SAAS,KAAK,GAAG,QAAQ,IAAI,YAAY,EAAE,CAAC;IACtE,OAAO;AACL,iBAAW,YAAY;AACvB,iBAAW,gBAAgB;IAC7B;AACA,UAAM,MAAM,MAAM,MAAM,OAAO,UAAU;MACvC,QAAQ;MACR;MACA,MAAM,IAAI,gBAAgB,UAAU;KACrC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,qCAAqC;IACvD;AACA,UAAM,OAAQ,MAAM,IAAI,KAAI;AAC5B,WAAO;MACL,cAAc,KAAK;MACnB,eAAe,KAAK;MACpB,YAAY,KAAK,IAAG,IAAK,KAAK,aAAa;;EAE/C;AAEA,iBAAeC,kBAAc;AAC3B,UAAM,WAAW,QAAQ,IAAI,GAAG,OAAO,SAAS,eAAe;AAC/D,QAAI;AAAU,aAAO;AAErB,UAAM,SAAS,MAAM,WAAU;AAC/B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MACR,kCAAkC,OAAO,SAAS,mCAAmC;IAEzF;AAEA,QAAI,OAAO,mBAAmB,cAAc,MAAM,GAAG;AACnD,YAAM,cAAc,IAAI,KAAK;AAC7B,UAAI,OAAO,aAAa,KAAK,IAAG,IAAK,aAAa;AAChD,cAAM,WAAW,QAAQ,IAAI,GAAG,OAAO,SAAS,YAAY;AAC5D,cAAM,eAAe,QAAQ,IAAI,GAAG,OAAO,SAAS,gBAAgB;AACpE,YAAI,CAAC,YAAY,CAAC,cAAc;AAC9B,gBAAM,IAAI,MACR,yBAAyB,OAAO,SAAS,cAAc,OAAO,SAAS,0DAA0D;QAErI;AACA,cAAM,YAAY,MAAM,mBACtB,OAAO,eACP,UACA,YAAY;AAEd,cAAM,YAAY,SAAS;AAC3B,eAAO,UAAU;MACnB;IACF;AAEA,WAAO,OAAO;EAChB;AAEA,iBAAe,YAAS;AACtB,UAAM,WAAW,QAAQ,IAAI,GAAG,OAAO,SAAS,YAAY;AAC5D,UAAM,eAAe,QAAQ,IAAI,GAAG,OAAO,SAAS,gBAAgB;AACpE,QAAI,CAAC;AACH,YAAM,IAAI,MACR,GAAG,OAAO,SAAS,8CAA8C;AAErE,QAAI,CAAC;AACH,YAAM,IAAI,MACR,GAAG,OAAO,SAAS,kDAAkD;AAGzE,UAAM,cAAc,QAAQ,IAAI,GAAG,OAAO,SAAS,eAAe;AAClE,QAAI,CAAC;AACH,YAAM,IAAI,MACR,GAAG,OAAO,SAAS,iDAAiD;AAGxE,UAAM,cAAc,IAAI,IAAI,WAAW;AACvC,UAAM,OACJ,OAAO,YAAY,IAAI,MACtB,YAAY,aAAa,WAAW,MAAM;AAC7C,UAAM,eAAe,YAAY;AACjC,UAAM,QAAQ,YAAY,EAAE,EAAE,SAAS,KAAK;AAE5C,UAAM,EAAE,OAAM,IAAK,MAAM,IAAI,QAE1B,CAAC,SAAS,WAAU;AACrB,YAAM,UAAU,WAAW,MAAK;AAC9B,eAAO,MAAK;AACZ,eAAO,IAAI,MAAM,4CAA4C,CAAC;MAChE,GAAG,IAAO;AAEV,YAAM,SAAS,aACb,OAAO,KAAsB,QAAuB;AAClD,cAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,IAAI,EAAE;AAChE,YAAI,IAAI,aAAa,cAAc;AACjC,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,WAAW;AACnB;QACF;AAEA,cAAM,gBAAgB,IAAI,aAAa,IAAI,OAAO;AAClD,cAAM,OAAO,IAAI,aAAa,IAAI,MAAM;AACxC,cAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAE1C,YAAI,OAAO;AACT,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,wBAAwB,KAAK,EAAE;AACvC,uBAAa,OAAO;AACpB,iBAAO,MAAK;AACZ,iBAAO,IAAI,MAAM,yBAAyB,KAAK,EAAE,CAAC;AAClD;QACF;AAEA,YAAI,kBAAkB,OAAO;AAC3B,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,6CAAwC;AAChD;QACF;AAEA,YAAI,CAAC,MAAM;AACT,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,6BAA6B;AACrC;QACF;AAEA,YAAI;AACF,gBAAM,kBAAkB,MAAM,aAC5B,MACA,aACA,UACA,YAAY;AAEd,cAAI,UAAU,KAAK,EAAE,gBAAgB,YAAW,CAAE;AAClD,cAAI,IACF,kEAAkE;AAEpE,uBAAa,OAAO;AACpB,iBAAO,MAAK;AACZ,kBAAQ,EAAE,QAAQ,gBAAe,CAAE;QACrC,SAAS,KAAK;AACZ,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,wBAAwB;AAChC,uBAAa,OAAO;AACpB,iBAAO,MAAK;AACZ,iBAAO,GAAG;QACZ;MACF,CAAC;AAGH,YAAM,SAAiC;QACrC,eAAe;QACf,WAAW;QACX,cAAc;QACd;;AAEF,UAAI,OAAO,OAAO;AAChB,eAAO,QAAQ,OAAO;MACxB;AAEA,aAAO,OAAO,MAAM,aAAa,MAAK;AACpC,cAAM,eAAe,GAAG,OAAO,YAAY,IAAI,IAAI,gBAAgB,MAAM,CAAC;AAE1E,gBAAQ,IAAI,sCAAsC;AAClD,gBAAQ,IAAI;EAAwC,YAAY;CAAI;AACpE,oBAAY,YAAY;MAC1B,CAAC;IACH,CAAC;AAED,UAAM,YAAY,MAAM;AACxB,YAAQ,IAAI,mBAAmB,mBAAmB,EAAE;EACtD;AAEA,iBAAe,UAAO;AACpB,UAAM,WAAW,QAAQ,IAAI,GAAG,OAAO,SAAS,YAAY;AAC5D,UAAM,eAAe,QAAQ,IAAI,GAAG,OAAO,SAAS,gBAAgB;AACpE,QAAI,CAAC;AACH,YAAM,IAAI,MACR,GAAG,OAAO,SAAS,8CAA8C;AAErE,QAAI,CAAC;AACH,YAAM,IAAI,MACR,GAAG,OAAO,SAAS,kDAAkD;AAGzE,UAAM,SAAS,MAAM,WAAU;AAC/B,QAAI,CAAC,UAAU,CAAC,cAAc,MAAM,GAAG;AACrC,YAAM,IAAI,MACR,yDAAyD;IAE7D;AAEA,UAAM,YAAY,MAAM,mBACtB,OAAO,eACP,UACA,YAAY;AAEd,UAAM,YAAY,SAAS;AAC3B,YAAQ,IAAI,iCAAiC,mBAAmB,EAAE;EACpE;AAEA,WAASC,QAAO,SAAgB;AAC9B,UAAM,MAAM,WAAW,QAAQ,KAAK,CAAC;AACrC,QAAI;AACJ,QAAI,QAAQ,aAAa,OAAO,iBAAiB;AAC/C,YAAM;IACR,OAAO;AACL,YAAM;IACR;AACA,QAAG,EAAG,MAAM,CAAC,QAAO;AAClB,cAAQ,MAAM,IAAI,WAAW,GAAG;AAChC,cAAQ,KAAK,CAAC;IAChB,CAAC;EACH;AAEA,SAAO,EAAE,gBAAAD,iBAAgB,QAAAC,QAAM;AACjC;;;AChZA,IAAM,EAAE,gBAAgB,OAAO,IAAI,WAAW;AAAA,EAC5C,MAAM;AAAA,EACN,cAAc;AAAA,EACd,UAAU;AAAA,EACV,WAAW;AAAA,EACX,iBAAiB;AACnB,CAAC;AAID,IAAI,QAAQ,KAAK,CAAC,KAAK,YAAY,aAAa,QAAQ,KAAK,CAAC,GAAG;AAC/D,SAAO;AACT;","names":["data","getAccessToken","runCli"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getAccessToken
3
- } from "./chunk-MNDMZQAW.js";
3
+ } from "./chunk-226LPP43.js";
4
4
 
5
5
  // src/client/core/bodySerializer.gen.ts
6
6
  var jsonBodySerializer = {
@@ -816,4 +816,4 @@ client.setConfig({
816
816
  export {
817
817
  client
818
818
  };
819
- //# sourceMappingURL=chunk-YU4TVN5Z.js.map
819
+ //# sourceMappingURL=chunk-VBNUJ5ZR.js.map
package/dist/lm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  client
3
- } from "./chunk-YU4TVN5Z.js";
4
- import "./chunk-MNDMZQAW.js";
3
+ } from "./chunk-VBNUJ5ZR.js";
4
+ import "./chunk-226LPP43.js";
5
5
  export {
6
6
  client
7
7
  };