Skip to content

Commit e4e6382

Browse files
committed
reformat
1 parent a6d0a23 commit e4e6382

7 files changed

Lines changed: 85 additions & 76 deletions

File tree

README.md

Lines changed: 66 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@ A Model Context Protocol (MCP) server that connects AI assistants to the [Nutrie
1616

1717
Once configured, you (or your AI agent) can process documents through natural language:
1818

19-
**You:** *"Merge report-q1.pdf and report-q2.pdf into a single document"*
20-
**AI:** *"Done! I've merged both reports into combined-report.pdf (24 pages total)."*
19+
**You:** _"Merge report-q1.pdf and report-q2.pdf into a single document"_
20+
**AI:** _"Done! I've merged both reports into combined-report.pdf (24 pages total)."_
2121

22-
**You:** *"Redact all social security numbers and email addresses from application.pdf"*
23-
**AI:** *"I found and redacted 5 SSNs and 3 email addresses. The redacted version is saved as application-redacted.pdf."*
22+
**You:** _"Redact all social security numbers and email addresses from application.pdf"_
23+
**AI:** _"I found and redacted 5 SSNs and 3 email addresses. The redacted version is saved as application-redacted.pdf."_
2424

25-
**You:** *"Digitally sign this contract with a visible signature on page 3"*
26-
**AI:** *"I've applied a PAdES-compliant digital signature to contract.pdf. The signed document is saved as contract-signed.pdf."*
25+
**You:** _"Digitally sign this contract with a visible signature on page 3"_
26+
**AI:** _"I've applied a PAdES-compliant digital signature to contract.pdf. The signed document is saved as contract-signed.pdf."_
2727

28-
**You:** *"Convert this PDF to markdown"*
29-
**AI:** *"Here's the markdown content extracted from your document..."*
28+
**You:** _"Convert this PDF to markdown"_
29+
**AI:** _"Here's the markdown content extracted from your document..."_
3030

31-
**You:** *"OCR this scanned document in German and extract the text"*
32-
**AI:** *"I've processed the scan with German OCR. Here's the extracted text..."*
31+
**You:** _"OCR this scanned document in German and extract the text"_
32+
**AI:** _"I've processed the scan with German OCR. Here's the extracted text..."_
3333

3434
## Quick Start
3535

@@ -57,11 +57,11 @@ Open Settings → Developer → Edit Config, then add:
5757
"args": ["-y", "@nutrient-sdk/dws-mcp-server"],
5858
"env": {
5959
"NUTRIENT_DWS_API_KEY": "YOUR_API_KEY_HERE",
60-
"SANDBOX_PATH": "/your/sandbox/directory"
60+
"SANDBOX_PATH": "/your/sandbox/directory",
6161
// "C:\\your\\sandbox\\directory" for Windows
62-
}
63-
}
64-
}
62+
},
63+
},
64+
},
6565
}
6666
```
6767

@@ -80,13 +80,14 @@ Create `.cursor/mcp.json` in your project root:
8080
"args": ["-y", "@nutrient-sdk/dws-mcp-server"],
8181
"env": {
8282
"NUTRIENT_DWS_API_KEY": "YOUR_API_KEY_HERE",
83-
"SANDBOX_PATH": "/your/project/documents"
83+
"SANDBOX_PATH": "/your/project/documents",
8484
// "C:\\your\\project\\documents" for Windows
85-
}
86-
}
87-
}
85+
},
86+
},
87+
},
8888
}
8989
```
90+
9091
</details>
9192

9293
<details>
@@ -102,13 +103,14 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
102103
"args": ["-y", "@nutrient-sdk/dws-mcp-server"],
103104
"env": {
104105
"NUTRIENT_DWS_API_KEY": "YOUR_API_KEY_HERE",
105-
"SANDBOX_PATH": "/your/sandbox/directory"
106+
"SANDBOX_PATH": "/your/sandbox/directory",
106107
// "C:\\your\\sandbox\\directory" for Windows
107-
}
108-
}
109-
}
108+
},
109+
},
110+
},
110111
}
111112
```
113+
112114
</details>
113115

114116
<details>
@@ -132,6 +134,7 @@ Add to `.vscode/settings.json` in your project:
132134
}
133135
}
134136
```
137+
135138
</details>
136139

137140
<details>
@@ -142,6 +145,7 @@ Any MCP-compatible client can connect using stdio transport:
142145
```bash
143146
NUTRIENT_DWS_API_KEY=your_key SANDBOX_PATH=/your/path npx @nutrient-sdk/dws-mcp-server
144147
```
148+
145149
</details>
146150

147151
### 3. Restart Your AI Client
@@ -154,26 +158,26 @@ Drop documents into your sandbox directory and start giving instructions!
154158

155159
## Available Tools
156160

157-
| Tool | Description |
158-
|------|-------------|
161+
| Tool | Description |
162+
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
159163
| **document_processor** | All-in-one document processing: merge PDFs, convert formats, apply OCR, watermark, rotate, redact, flatten annotations, extract text/tables/key-value pairs, and more |
160-
| **document_signer** | Digitally sign PDFs with PAdES-compliant CMS or CAdES signatures, with customizable visible/invisible signature appearances |
161-
| **sandbox_file_tree** | Browse files in the sandbox directory (when sandbox mode is enabled) |
162-
| **directory_tree** | Browse directory contents (when sandbox mode is disabled) |
164+
| **document_signer** | Digitally sign PDFs with PAdES-compliant CMS or CAdES signatures, with customizable visible/invisible signature appearances |
165+
| **sandbox_file_tree** | Browse files in the sandbox directory (when sandbox mode is enabled) |
166+
| **directory_tree** | Browse directory contents (when sandbox mode is disabled) |
163167

164168
### Document Processor Capabilities
165169

166-
| Feature | Description |
167-
|---------|-------------|
168-
| Document Creation | Merge PDFs, Office docs (DOCX, XLSX, PPTX), and images into a single document |
169-
| Format Conversion | PDF ↔ DOCX, images (PNG, JPEG, WebP), PDF/A, PDF/UA, HTML, Markdown |
170-
| Editing | Watermark (text/image), rotate pages, flatten annotations |
171-
| Security | Redact sensitive data (SSNs, credit cards, emails, etc.), password protection, permission control |
172-
| Data Extraction | Extract text, tables, or key-value pairs as structured JSON |
173-
| OCR | Multi-language optical character recognition for scanned documents |
174-
| Optimization | Compress and linearize PDFs without quality loss |
175-
| Annotations | Import XFDF annotations, flatten annotations |
176-
| Digital Signing | PAdES-compliant CMS and CAdES digital signatures (via document_signer tool) |
170+
| Feature | Description |
171+
| ----------------- | ------------------------------------------------------------------------------------------------- |
172+
| Document Creation | Merge PDFs, Office docs (DOCX, XLSX, PPTX), and images into a single document |
173+
| Format Conversion | PDF ↔ DOCX, images (PNG, JPEG, WebP), PDF/A, PDF/UA, HTML, Markdown |
174+
| Editing | Watermark (text/image), rotate pages, flatten annotations |
175+
| Security | Redact sensitive data (SSNs, credit cards, emails, etc.), password protection, permission control |
176+
| Data Extraction | Extract text, tables, or key-value pairs as structured JSON |
177+
| OCR | Multi-language optical character recognition for scanned documents |
178+
| Optimization | Compress and linearize PDFs without quality loss |
179+
| Annotations | Import XFDF annotations, flatten annotations |
180+
| Digital Signing | PAdES-compliant CMS and CAdES digital signatures (via document_signer tool) |
177181

178182
## Use with AI Agent Frameworks
179183

@@ -212,6 +216,7 @@ npx @nutrient-sdk/dws-mcp-server
212216
```
213217

214218
When sandbox mode is enabled:
219+
215220
- Relative paths resolve relative to the sandbox directory
216221
- All input file paths are validated to ensure they reside in the sandbox
217222
- Processed files are saved within the sandbox
@@ -224,25 +229,25 @@ Processed files are saved to a location determined by the AI. To guide output pl
224229

225230
### Environment Variables
226231

227-
| Variable | Required | Description |
228-
|----------|----------|-------------|
229-
| `NUTRIENT_DWS_API_KEY` | Yes (stdio/static) | Your Nutrient DWS API key ([get one free](https://dashboard.nutrient.io/sign_up/)) |
230-
| `SANDBOX_PATH` | Recommended | Directory to restrict file operations to |
231-
| `MCP_TRANSPORT` | No | `stdio` (default) or `http` |
232-
| `AUTH_MODE` | No | `static` (default) or `jwt` (HTTP mode only) |
233-
| `PORT` | No | HTTP port (default `3000`) |
234-
| `MCP_HOST` | No | HTTP bind host (default `127.0.0.1`) |
235-
| `MCP_ALLOWED_HOSTS` | No | Comma/space-separated allowed hostnames |
236-
| `MCP_DEBUG_LOGGING` | No | Enable HTTP request/response logging (`true`/`1`/`on`) |
237-
| `LOG_LEVEL` | No | Console log level for Winston logger (`debug` default) |
238-
| `MCP_BEARER_TOKEN` | Yes (HTTP+static) | Single bearer token for static auth |
239-
| `MCP_BEARER_TOKENS_JSON` | Optional | JSON map/array of static bearer principals |
240-
| `RESOURCE_URL` | No | Protected resource URL advertised to OAuth clients |
241-
| `AUTH_SERVER_URL` | No | Authorization server base URL |
242-
| `JWKS_URL` | Yes (HTTP+jwt) | JWKS endpoint for JWT signature validation |
243-
| `ISSUER` | No | JWT issuer (defaults to `AUTH_SERVER_URL`) |
244-
| `CLIENT_ID` | Yes (HTTP+jwt) | OAuth client ID used for token exchange |
245-
| `CLIENT_SECRET` | Yes (HTTP+jwt) | OAuth client secret used for token exchange |
232+
| Variable | Required | Description |
233+
| ------------------------ | ------------------ | ---------------------------------------------------------------------------------- |
234+
| `NUTRIENT_DWS_API_KEY` | Yes (stdio/static) | Your Nutrient DWS API key ([get one free](https://dashboard.nutrient.io/sign_up/)) |
235+
| `SANDBOX_PATH` | Recommended | Directory to restrict file operations to |
236+
| `MCP_TRANSPORT` | No | `stdio` (default) or `http` |
237+
| `AUTH_MODE` | No | `static` (default) or `jwt` (HTTP mode only) |
238+
| `PORT` | No | HTTP port (default `3000`) |
239+
| `MCP_HOST` | No | HTTP bind host (default `127.0.0.1`) |
240+
| `MCP_ALLOWED_HOSTS` | No | Comma/space-separated allowed hostnames |
241+
| `MCP_DEBUG_LOGGING` | No | Enable HTTP request/response logging (`true`/`1`/`on`) |
242+
| `LOG_LEVEL` | No | Console log level for Winston logger (`debug` default) |
243+
| `MCP_BEARER_TOKEN` | Yes (HTTP+static) | Single bearer token for static auth |
244+
| `MCP_BEARER_TOKENS_JSON` | Optional | JSON map/array of static bearer principals |
245+
| `RESOURCE_URL` | No | Protected resource URL advertised to OAuth clients |
246+
| `AUTH_SERVER_URL` | No | Authorization server base URL |
247+
| `JWKS_URL` | Yes (HTTP+jwt) | JWKS endpoint for JWT signature validation |
248+
| `ISSUER` | No | JWT issuer (defaults to `AUTH_SERVER_URL`) |
249+
| `CLIENT_ID` | Yes (HTTP+jwt) | OAuth client ID used for token exchange |
250+
| `CLIENT_SECRET` | Yes (HTTP+jwt) | OAuth client secret used for token exchange |
246251

247252
### HTTP Transport
248253

@@ -257,6 +262,7 @@ npx @nutrient-sdk/dws-mcp-server
257262
```
258263

259264
Endpoints:
265+
260266
- `POST /mcp` (MCP Streamable HTTP)
261267
- `GET /mcp` (SSE stream)
262268
- `DELETE /mcp` (session termination)
@@ -268,15 +274,18 @@ Unauthenticated HTTP requests receive `401` and a `WWW-Authenticate` header with
268274
## Troubleshooting
269275

270276
**Server not appearing in Claude Desktop?**
277+
271278
- Ensure Node.js 18+ is installed (`node --version`)
272279
- Check the config file path is correct for your OS
273280
- Restart Claude Desktop completely (check Task Manager/Activity Monitor)
274281

275282
**"API key invalid" errors?**
283+
276284
- Verify your API key at [dashboard.nutrient.io](https://dashboard.nutrient.io)
277285
- Ensure the key is set correctly in the `env` section (no extra spaces)
278286

279287
**Files not found?**
288+
280289
- Check that `SANDBOX_PATH` points to an existing directory
281290
- Ensure your documents are inside the sandbox directory
282291
- Use the `sandbox_file_tree` tool to verify visible files

src/dws/ai-redact.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ export async function performAiRedactCall(
3030

3131
// Guard against output overwriting input
3232
if (resolvedInputPath === resolvedOutputPath) {
33-
return createErrorResponse(
34-
'Error: Output path must be different from input path to prevent data corruption.',
35-
)
33+
return createErrorResponse('Error: Output path must be different from input path to prevent data corruption.')
3634
}
3735

3836
const fileBuffer = await fs.promises.readFile(resolvedInputPath)
@@ -53,7 +51,9 @@ export async function performAiRedactCall(
5351
formData.append('file1', fileBuffer, { filename: fileName })
5452
formData.append('data', JSON.stringify(dataPayload))
5553

56-
const response = apiClient ? await apiClient.post('ai/redact', formData) : await callNutrientApi('ai/redact', formData)
54+
const response = apiClient
55+
? await apiClient.post('ai/redact', formData)
56+
: await callNutrientApi('ai/redact', formData)
5757

5858
return handleFileResponse(response, resolvedOutputPath, 'AI redaction completed successfully. Output saved to')
5959
} catch (e: unknown) {

src/http/requestLogger.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ export function createRequestLoggerMiddleware(options?: { logger?: HttpLogger })
5757

5858
return (req, res, next) => {
5959
const requestIdHeader = req.headers['x-request-id']
60-
const requestId =
61-
(typeof requestIdHeader === 'string' ? requestIdHeader : requestIdHeader?.[0]) ?? randomUUID()
60+
const requestId = (typeof requestIdHeader === 'string' ? requestIdHeader : requestIdHeader?.[0]) ?? randomUUID()
6261

6362
setRequestId(requestId)
6463
res.setHeader('x-request-id', requestId)

src/http/tokenExchange.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ export class TokenExchangeClient {
8787
if (axios.isAxiosError(error)) {
8888
if (error.response?.data) {
8989
const message =
90-
typeof error.response.data === 'string'
91-
? error.response.data
92-
: JSON.stringify(error.response.data)
90+
typeof error.response.data === 'string' ? error.response.data : JSON.stringify(error.response.data)
9391
throw new Error(`Token exchange failed: ${message}`)
9492
}
9593

src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,10 @@ async function prepareSandbox(sandboxDir: string | null) {
510510
)
511511
}
512512

513-
async function runStdioServer(options: { sandboxEnabled: boolean; environment: Environment }): Promise<RunServerResult> {
513+
async function runStdioServer(options: {
514+
sandboxEnabled: boolean
515+
environment: Environment
516+
}): Promise<RunServerResult> {
514517
const { sandboxEnabled, environment } = options
515518

516519
if (!environment.nutrientApiKey) {

tests/protectedResource.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ describe('protected resource metadata', () => {
3030
resourceMetadataUrl: 'https://mcp.nutrient.io/.well-known/oauth-protected-resource',
3131
})
3232

33-
expect(header).toBe(
34-
'Bearer resource_metadata="https://mcp.nutrient.io/.well-known/oauth-protected-resource"',
35-
)
33+
expect(header).toBe('Bearer resource_metadata="https://mcp.nutrient.io/.well-known/oauth-protected-resource"')
3634
})
3735
})

tests/unit.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,13 @@ describe('API Functions', () => {
384384
vi.spyOn(sandbox, 'resolveReadFilePath').mockResolvedValueOnce('/input.pdf')
385385
vi.spyOn(sandbox, 'resolveWriteFilePath').mockResolvedValueOnce('/output.pdf')
386386

387-
const result = await performAiRedactCall('/input.pdf', 'All personally identifiable information', '/output.pdf', true, true)
387+
const result = await performAiRedactCall(
388+
'/input.pdf',
389+
'All personally identifiable information',
390+
'/output.pdf',
391+
true,
392+
true,
393+
)
388394

389395
expect(result.isError).toBe(true)
390396
expect(getTextContent(result)).toBe('Error: stage and apply cannot both be true. Choose one mode.')
@@ -415,11 +421,7 @@ describe('API Functions', () => {
415421
config: {} as InternalAxiosRequestConfig,
416422
})
417423

418-
const result = await performAiRedactCall(
419-
'/input.pdf',
420-
'All personally identifiable information',
421-
'/redacted.pdf',
422-
)
424+
const result = await performAiRedactCall('/input.pdf', 'All personally identifiable information', '/redacted.pdf')
423425

424426
expect(result.isError).toBe(false)
425427
expect(getTextContent(result)).toContain('AI redaction completed successfully')

0 commit comments

Comments
 (0)