Skip to content

Commit 8bda5a2

Browse files
tomassurinclaude
andcommitted
Require DwsApiClient via dependency injection, remove hidden fallbacks
Remove callNutrientApi legacy helper and inline axios fallbacks. All perform* functions now require an explicit DwsApiClient parameter, eliminating hidden coupling to environment variables and global state. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent cabbab1 commit 8bda5a2

10 files changed

Lines changed: 99 additions & 193 deletions

File tree

src/dws/ai-redact.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
55
import { handleApiError, handleFileResponse } from './utils.js'
66
import { createErrorResponse } from '../responses.js'
77
import { resolveReadFilePath, resolveWriteFilePath } from '../fs/sandbox.js'
8-
import { callNutrientApi } from './api.js'
98
import { DwsApiClient } from './client.js'
109

1110
/**
@@ -15,9 +14,9 @@ export async function performAiRedactCall(
1514
filePath: string,
1615
criteria: string,
1716
outputPath: string,
17+
apiClient: DwsApiClient,
1818
stage?: boolean,
1919
apply?: boolean,
20-
apiClient?: DwsApiClient,
2120
): Promise<CallToolResult> {
2221
// Resolve paths first to fail early
2322
try {
@@ -51,9 +50,7 @@ export async function performAiRedactCall(
5150
formData.append('file1', fileBuffer, { filename: fileName })
5251
formData.append('data', JSON.stringify(dataPayload))
5352

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

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

src/dws/api.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import FormData from 'form-data'
21
import { DwsApiClient, createApiClientFromApiKey, createApiClientFromTokenResolver } from './client.js'
3-
import { getApiKey } from './utils.js'
42

53
export type ApiClientAuthContext =
64
| {
@@ -19,12 +17,3 @@ export function createApiClient(context: ApiClientAuthContext): DwsApiClient {
1917

2018
return createApiClientFromTokenResolver(context.tokenResolver, context.baseUrl)
2119
}
22-
23-
/**
24-
* Legacy helper retained for backwards compatibility with tests/imports.
25-
* Prefer using DwsApiClient directly.
26-
*/
27-
export async function callNutrientApi(endpoint: string, data: FormData | Record<string, unknown>) {
28-
const client = createApiClientFromApiKey(getApiKey())
29-
return client.post(endpoint, data)
30-
}

src/dws/build.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
77
import { FileReference } from './types.js'
88
import { createErrorResponse } from '../responses.js'
99
import { resolveReadFilePath, resolveWriteFilePath } from '../fs/sandbox.js'
10-
import { callNutrientApi } from './api.js'
1110
import { DwsApiClient } from './client.js'
1211

1312
/**
@@ -16,7 +15,7 @@ import { DwsApiClient } from './client.js'
1615
export async function performBuildCall(
1716
instructions: Instructions,
1817
outputFilePath: string,
19-
apiClient?: DwsApiClient,
18+
apiClient: DwsApiClient,
2019
): Promise<CallToolResult> {
2120
const { instructions: adjustedInstructions, fileReferences } = await processInstructions(instructions)
2221

@@ -139,12 +138,12 @@ async function processFileReference(reference: string): Promise<FileReference> {
139138
async function makeApiBuildCall(
140139
instructions: Instructions,
141140
fileReferences: Map<string, FileReference>,
142-
apiClient?: DwsApiClient,
141+
apiClient: DwsApiClient,
143142
) {
144143
const allInputsAreUrls = Array.from(fileReferences.values()).every((fileRef) => fileRef.url)
145144

146145
if (allInputsAreUrls) {
147-
return apiClient ? apiClient.post('build', instructions) : callNutrientApi('build', instructions)
146+
return apiClient.post('build', instructions)
148147
} else {
149148
const formData = new FormData()
150149
formData.append('instructions', JSON.stringify(instructions))
@@ -155,6 +154,6 @@ async function makeApiBuildCall(
155154
}
156155
}
157156

158-
return apiClient ? apiClient.post('build', formData) : callNutrientApi('build', formData)
157+
return apiClient.post('build', formData)
159158
}
160159
}

src/dws/credits.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import axios from 'axios'
2-
import { getApiKey, pipeToString } from './utils.js'
3-
import { getVersion } from '../version.js'
1+
import { pipeToString } from './utils.js'
42
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
53
import { DwsApiClient } from './client.js'
64

@@ -33,16 +31,8 @@ export function sanitizeAccountInfo(data: AccountInfoResponse): Omit<AccountInfo
3331
/**
3432
* Calls the DWS /account/info endpoint and returns credit information.
3533
*/
36-
export async function performCheckCreditsCall(apiClient?: DwsApiClient): Promise<CallToolResult> {
37-
const response = apiClient
38-
? await apiClient.get('account/info')
39-
: await axios.get('https://api.nutrient.io/account/info', {
40-
headers: {
41-
Authorization: `Bearer ${getApiKey()}`,
42-
'User-Agent': `NutrientDWSMCPServer/${getVersion()}`,
43-
},
44-
responseType: 'stream',
45-
})
34+
export async function performCheckCreditsCall(apiClient: DwsApiClient): Promise<CallToolResult> {
35+
const response = await apiClient.get('account/info')
4636

4737
const raw = await pipeToString(response.data)
4838

src/dws/sign.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import FormData from 'form-data'
22
import { handleApiError, handleFileResponse } from './utils.js'
33
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
44
import { SignatureOptions } from '../schemas.js'
5-
import { callNutrientApi } from './api.js'
65
import { resolveReadFilePath, resolveWriteFilePath } from '../fs/sandbox.js'
76
import fs from 'fs'
87
import path from 'path'
@@ -14,10 +13,10 @@ import { DwsApiClient } from './client.js'
1413
export async function performSignCall(
1514
filePath: string,
1615
outputFilePath: string,
16+
apiClient: DwsApiClient,
1717
signatureOptions: SignatureOptions = { signatureType: 'cms', flatten: false },
1818
watermarkImagePath?: string,
1919
graphicImagePath?: string,
20-
apiClient?: DwsApiClient,
2120
): Promise<CallToolResult> {
2221
try {
2322
// We resolve the output path first to fail early
@@ -38,7 +37,7 @@ export async function performSignCall(
3837
await addFileToFormData(formData, 'graphic', graphicImagePath)
3938
}
4039

41-
const response = apiClient ? await apiClient.post('sign', formData) : await callNutrientApi('sign', formData)
40+
const response = await apiClient.post('sign', formData)
4241

4342
return handleFileResponse(response, resolvedOutputPath, 'File signed successfully')
4443
} catch (e: unknown) {

src/dws/utils.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,6 @@ export async function pipeToBuffer(responseData: Readable): Promise<Buffer> {
3535
})
3636
}
3737

38-
/**
39-
* Validates that the API key is set in the environment
40-
* @returns Object with error information if API key is not set
41-
*/
42-
export function getApiKey(): string {
43-
if (!process.env.NUTRIENT_DWS_API_KEY) {
44-
throw new Error('NUTRIENT_DWS_API_KEY not set in environment')
45-
}
46-
47-
return process.env.NUTRIENT_DWS_API_KEY
48-
}
49-
5038
/**
5139
* Handles API errors and converts them to a standard format
5240
* @returns Object with error information

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,10 @@ Positioning:
127127
return await performSignCall(
128128
filePath,
129129
outputPath,
130+
apiClient,
130131
signatureOptions,
131132
watermarkImagePath,
132133
graphicImagePath,
133-
apiClient,
134134
)
135135
} catch (error) {
136136
return createErrorResponse(`Error: ${error instanceof Error ? error.message : String(error)}`)
@@ -160,7 +160,7 @@ By default (when neither stage nor apply is set), redactions are detected and im
160160
}
161161

162162
try {
163-
return await performAiRedactCall(filePath, criteria, outputPath, stage, apply, apiClient)
163+
return await performAiRedactCall(filePath, criteria, outputPath, apiClient, stage, apply)
164164
} catch (error) {
165165
return createErrorResponse(`Error: ${error instanceof Error ? error.message : String(error)}`)
166166
}

tests/build-api-examples.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ import path from 'path'
55
import { performBuildCall } from '../src/dws/build.js'
66
import { BuildAPIArgs } from '../src/schemas.js'
77
import { setSandboxDirectory } from '../src/fs/sandbox.js'
8+
import { createApiClient } from '../src/dws/api.js'
9+
import { DwsApiClient } from '../src/dws/client.js'
810

911
dotenvConfig()
1012

1113
describe('performBuildCall with build-api-examples', () => {
1214
let outputDirectory: string
15+
let apiClient: DwsApiClient
1316
beforeAll(async () => {
1417
const assetsDir = path.join(__dirname, `assets`)
1518
await setSandboxDirectory(assetsDir)
1619

20+
apiClient = createApiClient({ apiKey: process.env.NUTRIENT_DWS_API_KEY! })
1721
outputDirectory = `test-output-${new Date().toISOString().replace(/[:.]/g, '-')}`
1822
})
1923

@@ -80,7 +84,7 @@ describe('performBuildCall with build-api-examples', () => {
8084
it.each(fileOutputExamples)('should process $name', async ({ example }) => {
8185
const { instructions, outputPath } = example
8286

83-
const result = await performBuildCall(instructions, `${outputDirectory}/${outputPath}`)
87+
const result = await performBuildCall(instructions, `${outputDirectory}/${outputPath}`, apiClient)
8488

8589
expect(result).toEqual(
8690
expect.objectContaining({
@@ -98,7 +102,7 @@ describe('performBuildCall with build-api-examples', () => {
98102
it.each(jsonOutputExamples)('should process $name', async ({ example }) => {
99103
const { instructions } = example
100104

101-
const result = await performBuildCall(instructions, 'dummy_path.pdf')
105+
const result = await performBuildCall(instructions, 'dummy_path.pdf', apiClient)
102106

103107
expect(result).toEqual(
104108
expect.objectContaining({

tests/signing-api-examples.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ import { performSignCall } from '../src/dws/sign.js'
55
import { SignAPIArgs } from '../src/schemas.js'
66
import path from 'path'
77
import { setSandboxDirectory } from '../src/fs/sandbox.js'
8+
import { createApiClient } from '../src/dws/api.js'
9+
import { DwsApiClient } from '../src/dws/client.js'
810

911
dotenvConfig()
1012

1113
describe('performSignCall with signing-api-examples', () => {
1214
let outputDirectory: string
15+
let apiClient: DwsApiClient
1316
beforeAll(async () => {
1417
const assetsDir = path.join(__dirname, `assets`)
1518
await setSandboxDirectory(assetsDir)
1619

20+
apiClient = createApiClient({ apiKey: process.env.NUTRIENT_DWS_API_KEY! })
1721
outputDirectory = `test-output-${new Date().toISOString().replace(/[:.]/g, '-')}`
1822
})
1923

@@ -44,6 +48,7 @@ describe('performSignCall with signing-api-examples', () => {
4448
const result = await performSignCall(
4549
filePath,
4650
`${outputDirectory}/${outputPath}`,
51+
apiClient,
4752
signatureOptions,
4853
watermarkImagePath,
4954
graphicImagePath,

0 commit comments

Comments
 (0)