argulens-mcp-server 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/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # @argulens/mcp-server
2
+
3
+ MCP server for [ArguLens](https://argulens.com) — analyze legal documents directly from Claude, ChatGPT, or any MCP-compatible AI assistant.
4
+
5
+ ## Setup
6
+
7
+ ### 1. Get an API key
8
+
9
+ 1. Sign up at [argulens.com](https://argulens.com)
10
+ 2. Go to [My Cases > API Keys](https://argulens.com/my-cases/api-keys)
11
+ 3. Create a new API key
12
+
13
+ ### 2. Install
14
+
15
+ #### Claude Desktop
16
+
17
+ Add to your `claude_desktop_config.json`:
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "argulens": {
23
+ "command": "npx",
24
+ "args": ["@argulens/mcp-server"],
25
+ "env": {
26
+ "ARGULENS_API_KEY": "al_live_your_key_here"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ #### Claude Code
34
+
35
+ ```bash
36
+ claude mcp add argulens -- npx @argulens/mcp-server
37
+ ```
38
+
39
+ Then set the environment variable:
40
+ ```bash
41
+ export ARGULENS_API_KEY=al_live_your_key_here
42
+ ```
43
+
44
+ ## Available Tools
45
+
46
+ | Tool | Description |
47
+ |------|-------------|
48
+ | `analyze_case` | Upload documents and get a 7-section case pack (summary, timeline, evidence, risks, etc.) |
49
+ | `chat_about_case` | Ask evidence-grounded follow-up questions about a case |
50
+ | `parse_document` | Extract text from PDF, DOCX, images (OCR), or email files |
51
+ | `draft_document` | Generate court-ready documents (affidavits, submissions, defence notices) |
52
+ | `check_usage` | Check your current usage and remaining quota |
53
+
54
+ ## Usage Examples
55
+
56
+ Once configured, you can ask your AI assistant:
57
+
58
+ - "Analyze this divorce petition and tell me my strengths and weaknesses"
59
+ - "What are the risks in my custody case based on these documents?"
60
+ - "Draft an affidavit based on my case analysis"
61
+ - "How much of my ArguLens quota have I used this month?"
62
+
63
+ ## Billing
64
+
65
+ Usage through MCP counts against your ArguLens plan:
66
+
67
+ - **Free**: 50 analyses/month, 100 chat messages/month
68
+ - **Pro** ($11/month): 100 analyses/month, 200 chat messages/month
69
+
70
+ When you hit your limit, the tool will return an error with a link to upgrade.
71
+
72
+ ## Environment Variables
73
+
74
+ | Variable | Required | Description |
75
+ |----------|----------|-------------|
76
+ | `ARGULENS_API_KEY` | Yes | Your ArguLens API key (starts with `al_live_`) |
77
+ | `ARGULENS_BASE_URL` | No | Custom API URL (defaults to `https://argulens.com`) |
@@ -0,0 +1,10 @@
1
+ export declare class ArguLensClient {
2
+ private baseUrl;
3
+ private apiKey;
4
+ constructor();
5
+ request(path: string, options?: {
6
+ method?: string;
7
+ body?: unknown;
8
+ }): Promise<unknown>;
9
+ }
10
+ export declare const client: ArguLensClient;
package/dist/client.js ADDED
@@ -0,0 +1,39 @@
1
+ const BASE_URL = process.env.ARGULENS_BASE_URL || "https://argulens.com";
2
+ const API_KEY = process.env.ARGULENS_API_KEY;
3
+ if (!API_KEY) {
4
+ console.error("ARGULENS_API_KEY environment variable is required");
5
+ process.exit(1);
6
+ }
7
+ export class ArguLensClient {
8
+ baseUrl;
9
+ apiKey;
10
+ constructor() {
11
+ this.baseUrl = BASE_URL;
12
+ this.apiKey = API_KEY;
13
+ }
14
+ async request(path, options = {}) {
15
+ const { method = "GET", body } = options;
16
+ const res = await fetch(`${this.baseUrl}${path}`, {
17
+ method,
18
+ headers: {
19
+ Authorization: `Bearer ${this.apiKey}`,
20
+ "Content-Type": "application/json",
21
+ "User-Agent": "argulens-mcp/0.1.0",
22
+ },
23
+ ...(body ? { body: JSON.stringify(body) } : {}),
24
+ });
25
+ if (!res.ok) {
26
+ const error = await res.json().catch(() => ({ error: res.statusText }));
27
+ const message = error.error || res.statusText;
28
+ if (res.status === 401) {
29
+ throw new Error(`Invalid or revoked API key. Generate a new key at ${this.baseUrl}/my-cases/api-keys`);
30
+ }
31
+ if (res.status === 429) {
32
+ throw new Error(`Rate limit or quota exceeded. Upgrade your plan at ${this.baseUrl}/pricing`);
33
+ }
34
+ throw new Error(`ArguLens API error (${res.status}): ${message}`);
35
+ }
36
+ return res.json();
37
+ }
38
+ }
39
+ export const client = new ArguLensClient();
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { client } from "./client.js";
6
+ const server = new McpServer({
7
+ name: "argulens",
8
+ version: "0.1.0",
9
+ });
10
+ // Tool: analyze_case
11
+ server.registerTool("analyze_case", {
12
+ title: "Analyze Legal Case",
13
+ description: "Upload legal documents and get a structured 7-section case pack: summary, timeline, evidence index, missing evidence, risks, opposing arguments, and response draft. Supports Singapore family law (Women's Charter, HDB, CPF) and worldwide disputes.",
14
+ inputSchema: {
15
+ documentTexts: z
16
+ .array(z.object({
17
+ name: z.string().describe("Document filename"),
18
+ content: z.string().describe("Extracted text content of the document"),
19
+ }))
20
+ .describe("Array of documents with name and text content"),
21
+ caseType: z
22
+ .enum([
23
+ "divorce",
24
+ "annulment",
25
+ "custody",
26
+ "support",
27
+ "property",
28
+ "landlord-tenant",
29
+ "contract",
30
+ "personal-injury",
31
+ "employment",
32
+ "immigration",
33
+ "other",
34
+ ])
35
+ .describe("Type of legal case"),
36
+ role: z
37
+ .enum(["plaintiff", "defendant", "petitioner", "respondent", "applicant"])
38
+ .describe("Your role in the case"),
39
+ jurisdiction: z
40
+ .string()
41
+ .default("Singapore")
42
+ .describe("Jurisdiction (e.g. Singapore, Malaysia, India)"),
43
+ },
44
+ }, async ({ documentTexts, caseType, role, jurisdiction }) => {
45
+ const result = await client.request("/api/analyze", {
46
+ method: "POST",
47
+ body: { documentTexts, caseType, role, jurisdiction },
48
+ });
49
+ return {
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: JSON.stringify(result, null, 2),
54
+ },
55
+ ],
56
+ };
57
+ });
58
+ // Tool: chat_about_case
59
+ server.registerTool("chat_about_case", {
60
+ title: "Chat About a Legal Case",
61
+ description: "Ask evidence-grounded questions about a legal case. Every answer cites specific uploaded documents. Useful for follow-up questions after analyzing a case.",
62
+ inputSchema: {
63
+ message: z.string().describe("Your question about the case"),
64
+ caseId: z
65
+ .string()
66
+ .optional()
67
+ .describe("Case ID from a previous analysis (optional)"),
68
+ },
69
+ }, async ({ message, caseId }) => {
70
+ const result = await client.request("/api/chat", {
71
+ method: "POST",
72
+ body: {
73
+ messages: [{ role: "user", content: message }],
74
+ ...(caseId ? { caseId } : {}),
75
+ },
76
+ });
77
+ return {
78
+ content: [
79
+ {
80
+ type: "text",
81
+ text: JSON.stringify(result, null, 2),
82
+ },
83
+ ],
84
+ };
85
+ });
86
+ // Tool: parse_document
87
+ server.registerTool("parse_document", {
88
+ title: "Parse Legal Document",
89
+ description: "Extract text from a legal document file. Supports PDF, DOCX, images (OCR), and email files. Returns the extracted text content.",
90
+ inputSchema: {
91
+ fileBase64: z
92
+ .string()
93
+ .describe("Base64-encoded file content"),
94
+ filename: z
95
+ .string()
96
+ .describe("Filename with extension (e.g. notice.pdf, contract.docx)"),
97
+ },
98
+ }, async ({ fileBase64, filename }) => {
99
+ const result = await client.request("/api/parse", {
100
+ method: "POST",
101
+ body: { file: fileBase64, filename },
102
+ });
103
+ return {
104
+ content: [
105
+ {
106
+ type: "text",
107
+ text: JSON.stringify(result, null, 2),
108
+ },
109
+ ],
110
+ };
111
+ });
112
+ // Tool: draft_document
113
+ server.registerTool("draft_document", {
114
+ title: "Draft Legal Document",
115
+ description: "Generate a court-ready legal document from case analysis. Supported types: notice of contest, reply to defence, affidavit, written submissions, request for discovery.",
116
+ inputSchema: {
117
+ caseId: z.string().describe("Case ID from a previous analysis"),
118
+ documentType: z
119
+ .enum([
120
+ "notice-of-contest",
121
+ "reply-to-defence",
122
+ "affidavit-general",
123
+ "affidavit-assets-means",
124
+ "written-submissions",
125
+ "request-for-discovery",
126
+ "notice-act-in-person",
127
+ ])
128
+ .describe("Type of legal document to generate"),
129
+ instructions: z
130
+ .string()
131
+ .optional()
132
+ .describe("Additional instructions for the document"),
133
+ },
134
+ }, async ({ caseId, documentType, instructions }) => {
135
+ const result = await client.request("/api/draft", {
136
+ method: "POST",
137
+ body: { caseId, documentType, instructions },
138
+ });
139
+ return {
140
+ content: [
141
+ {
142
+ type: "text",
143
+ text: JSON.stringify(result, null, 2),
144
+ },
145
+ ],
146
+ };
147
+ });
148
+ // Tool: check_usage
149
+ server.registerTool("check_usage", {
150
+ title: "Check Usage & Quota",
151
+ description: "Check your current ArguLens usage and remaining quota for analyses and chat messages.",
152
+ }, async () => {
153
+ const result = await client.request("/api/usage");
154
+ return {
155
+ content: [
156
+ {
157
+ type: "text",
158
+ text: JSON.stringify(result, null, 2),
159
+ },
160
+ ],
161
+ };
162
+ });
163
+ // Start the server
164
+ const transport = new StdioServerTransport();
165
+ server.connect(transport).catch((err) => {
166
+ console.error("Failed to start ArguLens MCP server:", err.message);
167
+ process.exit(1);
168
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "argulens-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for ArguLens — analyze legal documents from any AI assistant",
5
+ "bin": {
6
+ "argulens-mcp-server": "dist/index.js"
7
+ },
8
+ "main": "./dist/index.js",
9
+ "type": "module",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.12.0"
16
+ },
17
+ "devDependencies": {
18
+ "typescript": "^5.7.0",
19
+ "@types/node": "^22.0.0"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "legal",
24
+ "singapore",
25
+ "family-law",
26
+ "document-analysis",
27
+ "argulens"
28
+ ],
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/venkata-srinivasan/argulens"
33
+ }
34
+ }
package/src/client.ts ADDED
@@ -0,0 +1,58 @@
1
+ const BASE_URL = process.env.ARGULENS_BASE_URL || "https://argulens.com"
2
+ const API_KEY = process.env.ARGULENS_API_KEY
3
+
4
+ if (!API_KEY) {
5
+ console.error("ARGULENS_API_KEY environment variable is required")
6
+ process.exit(1)
7
+ }
8
+
9
+ export class ArguLensClient {
10
+ private baseUrl: string
11
+ private apiKey: string
12
+
13
+ constructor() {
14
+ this.baseUrl = BASE_URL
15
+ this.apiKey = API_KEY!
16
+ }
17
+
18
+ async request(
19
+ path: string,
20
+ options: {
21
+ method?: string
22
+ body?: unknown
23
+ } = {}
24
+ ): Promise<unknown> {
25
+ const { method = "GET", body } = options
26
+
27
+ const res = await fetch(`${this.baseUrl}${path}`, {
28
+ method,
29
+ headers: {
30
+ Authorization: `Bearer ${this.apiKey}`,
31
+ "Content-Type": "application/json",
32
+ "User-Agent": "argulens-mcp/0.1.0",
33
+ },
34
+ ...(body ? { body: JSON.stringify(body) } : {}),
35
+ })
36
+
37
+ if (!res.ok) {
38
+ const error = await res.json().catch(() => ({ error: res.statusText }))
39
+ const message = (error as { error?: string }).error || res.statusText
40
+
41
+ if (res.status === 401) {
42
+ throw new Error(
43
+ `Invalid or revoked API key. Generate a new key at ${this.baseUrl}/my-cases/api-keys`
44
+ )
45
+ }
46
+ if (res.status === 429) {
47
+ throw new Error(
48
+ `Rate limit or quota exceeded. Upgrade your plan at ${this.baseUrl}/pricing`
49
+ )
50
+ }
51
+ throw new Error(`ArguLens API error (${res.status}): ${message}`)
52
+ }
53
+
54
+ return res.json()
55
+ }
56
+ }
57
+
58
+ export const client = new ArguLensClient()
package/src/index.ts ADDED
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
5
+ import { z } from "zod"
6
+ import { client } from "./client.js"
7
+
8
+ const server = new McpServer({
9
+ name: "argulens",
10
+ version: "0.1.0",
11
+ })
12
+
13
+ // Tool: analyze_case
14
+ server.registerTool("analyze_case", {
15
+ title: "Analyze Legal Case",
16
+ description:
17
+ "Upload legal documents and get a structured 7-section case pack: summary, timeline, evidence index, missing evidence, risks, opposing arguments, and response draft. Supports Singapore family law (Women's Charter, HDB, CPF) and worldwide disputes.",
18
+ inputSchema: {
19
+ documentTexts: z
20
+ .array(
21
+ z.object({
22
+ name: z.string().describe("Document filename"),
23
+ content: z.string().describe("Extracted text content of the document"),
24
+ })
25
+ )
26
+ .describe("Array of documents with name and text content"),
27
+ caseType: z
28
+ .enum([
29
+ "divorce",
30
+ "annulment",
31
+ "custody",
32
+ "support",
33
+ "property",
34
+ "landlord-tenant",
35
+ "contract",
36
+ "personal-injury",
37
+ "employment",
38
+ "immigration",
39
+ "other",
40
+ ])
41
+ .describe("Type of legal case"),
42
+ role: z
43
+ .enum(["plaintiff", "defendant", "petitioner", "respondent", "applicant"])
44
+ .describe("Your role in the case"),
45
+ jurisdiction: z
46
+ .string()
47
+ .default("Singapore")
48
+ .describe("Jurisdiction (e.g. Singapore, Malaysia, India)"),
49
+ },
50
+ }, async ({ documentTexts, caseType, role, jurisdiction }) => {
51
+ const result = await client.request("/api/analyze", {
52
+ method: "POST",
53
+ body: { documentTexts, caseType, role, jurisdiction },
54
+ })
55
+ return {
56
+ content: [
57
+ {
58
+ type: "text" as const,
59
+ text: JSON.stringify(result, null, 2),
60
+ },
61
+ ],
62
+ }
63
+ })
64
+
65
+ // Tool: chat_about_case
66
+ server.registerTool("chat_about_case", {
67
+ title: "Chat About a Legal Case",
68
+ description:
69
+ "Ask evidence-grounded questions about a legal case. Every answer cites specific uploaded documents. Useful for follow-up questions after analyzing a case.",
70
+ inputSchema: {
71
+ message: z.string().describe("Your question about the case"),
72
+ caseId: z
73
+ .string()
74
+ .optional()
75
+ .describe("Case ID from a previous analysis (optional)"),
76
+ },
77
+ }, async ({ message, caseId }) => {
78
+ const result = await client.request("/api/chat", {
79
+ method: "POST",
80
+ body: {
81
+ messages: [{ role: "user", content: message }],
82
+ ...(caseId ? { caseId } : {}),
83
+ },
84
+ })
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text" as const,
89
+ text: JSON.stringify(result, null, 2),
90
+ },
91
+ ],
92
+ }
93
+ })
94
+
95
+ // Tool: parse_document
96
+ server.registerTool("parse_document", {
97
+ title: "Parse Legal Document",
98
+ description:
99
+ "Extract text from a legal document file. Supports PDF, DOCX, images (OCR), and email files. Returns the extracted text content.",
100
+ inputSchema: {
101
+ fileBase64: z
102
+ .string()
103
+ .describe("Base64-encoded file content"),
104
+ filename: z
105
+ .string()
106
+ .describe("Filename with extension (e.g. notice.pdf, contract.docx)"),
107
+ },
108
+ }, async ({ fileBase64, filename }) => {
109
+ const result = await client.request("/api/parse", {
110
+ method: "POST",
111
+ body: { file: fileBase64, filename },
112
+ })
113
+ return {
114
+ content: [
115
+ {
116
+ type: "text" as const,
117
+ text: JSON.stringify(result, null, 2),
118
+ },
119
+ ],
120
+ }
121
+ })
122
+
123
+ // Tool: draft_document
124
+ server.registerTool("draft_document", {
125
+ title: "Draft Legal Document",
126
+ description:
127
+ "Generate a court-ready legal document from case analysis. Supported types: notice of contest, reply to defence, affidavit, written submissions, request for discovery.",
128
+ inputSchema: {
129
+ caseId: z.string().describe("Case ID from a previous analysis"),
130
+ documentType: z
131
+ .enum([
132
+ "notice-of-contest",
133
+ "reply-to-defence",
134
+ "affidavit-general",
135
+ "affidavit-assets-means",
136
+ "written-submissions",
137
+ "request-for-discovery",
138
+ "notice-act-in-person",
139
+ ])
140
+ .describe("Type of legal document to generate"),
141
+ instructions: z
142
+ .string()
143
+ .optional()
144
+ .describe("Additional instructions for the document"),
145
+ },
146
+ }, async ({ caseId, documentType, instructions }) => {
147
+ const result = await client.request("/api/draft", {
148
+ method: "POST",
149
+ body: { caseId, documentType, instructions },
150
+ })
151
+ return {
152
+ content: [
153
+ {
154
+ type: "text" as const,
155
+ text: JSON.stringify(result, null, 2),
156
+ },
157
+ ],
158
+ }
159
+ })
160
+
161
+ // Tool: check_usage
162
+ server.registerTool("check_usage", {
163
+ title: "Check Usage & Quota",
164
+ description:
165
+ "Check your current ArguLens usage and remaining quota for analyses and chat messages.",
166
+ }, async () => {
167
+ const result = await client.request("/api/usage")
168
+ return {
169
+ content: [
170
+ {
171
+ type: "text" as const,
172
+ text: JSON.stringify(result, null, 2),
173
+ },
174
+ ],
175
+ }
176
+ })
177
+
178
+ // Start the server
179
+ const transport = new StdioServerTransport()
180
+ server.connect(transport).catch((err: Error) => {
181
+ console.error("Failed to start ArguLens MCP server:", err.message)
182
+ process.exit(1)
183
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": ["src"]
14
+ }