Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
26c2c15
docs: add DWS MCP Server OAuth design plan
tomassurin Mar 11, 2026
d5fa9cb
feat(dws): introduce session-scoped DWS API client
tomassurin Mar 11, 2026
bfd93c9
feat(auth): add static/jwt auth middleware and token exchange
tomassurin Mar 11, 2026
ce91abd
feat(server): add streamable HTTP transport with session binding
tomassurin Mar 11, 2026
e9f47a4
chore(runtime): document and configure HTTP/OAuth deployment
tomassurin Mar 11, 2026
2c55d24
Add debug logging
tomassurin Mar 11, 2026
5131359
Add proper logger
tomassurin Mar 11, 2026
a6d6030
Revert "docs: add DWS MCP Server OAuth design plan"
tomassurin Mar 11, 2026
dacba0a
Relax JWT audience matching for Codex auth tokens
tomassurin Mar 11, 2026
a6d0a23
Cleanup and live rebuild
tomassurin Mar 11, 2026
e4e6382
reformat
tomassurin Mar 11, 2026
6aa79ac
Add GitHub Actions test workflow
tomassurin Mar 11, 2026
6f519cd
Add private_key_jwt auth method
tomassurin Mar 12, 2026
e3820ac
CORS handling
tomassurin Mar 12, 2026
cabbab1
Mention mcp inspector
tomassurin Mar 13, 2026
8bda5a2
Require DwsApiClient via dependency injection, remove hidden fallbacks
tomassurin Mar 13, 2026
4fe10a1
Add JSDoc to DwsApiClient, factory functions, and auth context types
tomassurin Mar 13, 2026
83fb3e4
Log session miss
tomassurin Mar 13, 2026
253adc9
Simplify JWT auth by forwarding OAuth access tokens directly
tomassurin Mar 13, 2026
211fb31
Default JWT auth config to api.nutrient.io, drop token exchange tests
tomassurin Mar 13, 2026
c6b1d87
Extract shared hashPrincipal and parseBearerToken into authUtils
tomassurin Mar 16, 2026
c8c41b2
Handle batch JSON-RPC initialize requests, clarify CORS policy
tomassurin Mar 16, 2026
eb53f07
Add test for JWT unknown-client fallback when sub/azp are missing
tomassurin Mar 16, 2026
df8de01
Add test for session cleanup via DELETE /mcp
tomassurin Mar 16, 2026
7e2a632
OAuth in stdio mode
tomassurin Mar 16, 2026
c8f1843
Add logging in stdio mode and fix the oauth flow in stdio mode
tomassurin Mar 16, 2026
5288407
Defaults
tomassurin Mar 16, 2026
7cc3145
Drop static auth in http mode
tomassurin Mar 16, 2026
e3fa52b
Enable DCR
tomassurin Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.git
.context
node_modules
dist
tests
coverage
pnpm-debug.log
*.local
29 changes: 28 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,29 @@
# Used in testing
# Core DWS API
NUTRIENT_DWS_API_KEY=your-nutrient-dws-api-key
DWS_API_BASE_URL=https://api.nutrient.io

# Transport
MCP_TRANSPORT=stdio
PORT=3000
MCP_HOST=127.0.0.1
MCP_ALLOWED_HOSTS=
MCP_DEBUG_LOGGING=false

# HTTP auth mode (static|jwt)
AUTH_MODE=static

# Static auth options (HTTP mode)
MCP_BEARER_TOKEN=
MCP_BEARER_CLIENT_ID=default-client
MCP_BEARER_SCOPES=mcp:invoke
MCP_BEARER_ALLOWED_TOOLS=
MCP_BEARER_TOKENS_JSON=

# OAuth/JWT mode options
RESOURCE_URL=https://mcp.nutrient.io/mcp
AUTH_SERVER_URL=https://api.nutrient.io
JWKS_URL=
ISSUER=https://api.nutrient.io

# Optional sandbox root
SANDBOX_PATH=
44 changes: 44 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Tests

on:
push:
branches:
- '**'
pull_request:

concurrency:
group: tests-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Lint, Build, Test
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm exec eslint src tests

- name: Build
run: pnpm run build

- name: Run tests
run: pnpm run test:ci
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ COPY --from=builder /app/dist ./dist
RUN chown -R appuser:appgroup /app
USER appuser

# MCP runs over stdio
# MCP supports stdio and HTTP transports
EXPOSE 3000
ENTRYPOINT ["node", "dist/index.js"]
106 changes: 64 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ A Model Context Protocol (MCP) server that connects AI assistants to the [Nutrie

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

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

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

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

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

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

## Quick Start

Expand Down Expand Up @@ -57,11 +57,11 @@ Open Settings → Developer → Edit Config, then add:
"args": ["-y", "@nutrient-sdk/dws-mcp-server"],
"env": {
"NUTRIENT_DWS_API_KEY": "YOUR_API_KEY_HERE",
"SANDBOX_PATH": "/your/sandbox/directory"
"SANDBOX_PATH": "/your/sandbox/directory",
// "C:\\your\\sandbox\\directory" for Windows
}
}
}
},
},
},
}
```

Expand All @@ -80,13 +80,14 @@ Create `.cursor/mcp.json` in your project root:
"args": ["-y", "@nutrient-sdk/dws-mcp-server"],
"env": {
"NUTRIENT_DWS_API_KEY": "YOUR_API_KEY_HERE",
"SANDBOX_PATH": "/your/project/documents"
"SANDBOX_PATH": "/your/project/documents",
// "C:\\your\\project\\documents" for Windows
}
}
}
},
},
},
}
```

</details>

<details>
Expand All @@ -102,13 +103,14 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
"args": ["-y", "@nutrient-sdk/dws-mcp-server"],
"env": {
"NUTRIENT_DWS_API_KEY": "YOUR_API_KEY_HERE",
"SANDBOX_PATH": "/your/sandbox/directory"
"SANDBOX_PATH": "/your/sandbox/directory",
// "C:\\your\\sandbox\\directory" for Windows
}
}
}
},
},
},
}
```

</details>

<details>
Expand All @@ -132,6 +134,7 @@ Add to `.vscode/settings.json` in your project:
}
}
```

</details>

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

</details>

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

## Available Tools

| Tool | Description |
|------|-------------|
| Tool | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **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 |
| **document_signer** | Digitally sign PDFs with PAdES-compliant CMS or CAdES signatures, with customizable visible/invisible signature appearances |
| **sandbox_file_tree** | Browse files in the sandbox directory (when sandbox mode is enabled) |
| **directory_tree** | Browse directory contents (when sandbox mode is disabled) |
| **document_signer** | Digitally sign PDFs with PAdES-compliant CMS or CAdES signatures, with customizable visible/invisible signature appearances |
| **sandbox_file_tree** | Browse files in the sandbox directory (when sandbox mode is enabled) |
| **directory_tree** | Browse directory contents (when sandbox mode is disabled) |

### Document Processor Capabilities

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

## Use with AI Agent Frameworks

Expand Down Expand Up @@ -212,6 +216,7 @@ npx @nutrient-sdk/dws-mcp-server
```

When sandbox mode is enabled:

- Relative paths resolve relative to the sandbox directory
- All input file paths are validated to ensure they reside in the sandbox
- Processed files are saved within the sandbox
Expand All @@ -222,25 +227,42 @@ When sandbox mode is enabled:

Processed files are saved to a location determined by the AI. To guide output placement, use natural language (e.g., "save the result to `output/result.pdf`") or create an `output` directory in your sandbox.

### Authentication

The server authenticates to the Nutrient DWS API (`https://api.nutrient.io`) using one of:

| Method | When | Config |
|--------|------|--------|
| **API key** | `NUTRIENT_DWS_API_KEY` is set | Static key passed as Bearer token to DWS API |
| **OAuth browser flow** | No API key set | Opens browser for Nutrient OAuth consent, caches token locally |

When no API key is configured, the server opens a browser-based OAuth flow on the first tool call (similar to `gh auth login`). Tokens are cached at `~/.nutrient/credentials.json` and refreshed automatically.

### Environment Variables

| Variable | Required | Description |
|----------|----------|-------------|
| `NUTRIENT_DWS_API_KEY` | Yes | Your Nutrient DWS API key ([get one free](https://dashboard.nutrient.io/sign_up/)) |
| `SANDBOX_PATH` | Recommended | Directory to restrict file operations to |
| Variable | Required | Description |
| ---------------------- | ----------- | ---------------------------------------------------------------------------------- |
| `NUTRIENT_DWS_API_KEY` | No* | Nutrient DWS API key ([get one free](https://dashboard.nutrient.io/sign_up/)) |
| `SANDBOX_PATH` | Recommended | Directory to restrict file operations to |
| `LOG_LEVEL` | No | Console log level for Winston logger (`debug` default) |

\* If omitted, the server uses an OAuth browser flow to authenticate with the Nutrient API.

## Troubleshooting

**Server not appearing in Claude Desktop?**

- Ensure Node.js 18+ is installed (`node --version`)
- Check the config file path is correct for your OS
- Restart Claude Desktop completely (check Task Manager/Activity Monitor)

**"API key invalid" errors?**

- Verify your API key at [dashboard.nutrient.io](https://dashboard.nutrient.io)
- Ensure the key is set correctly in the `env` section (no extra spaces)

**Files not found?**

- Check that `SANDBOX_PATH` points to an existing directory
- Ensure your documents are inside the sandbox directory
- Use the `sandbox_file_tree` tool to verify visible files
Expand Down
Loading
Loading