diff --git a/package.json b/package.json index 3c887a5..3c9796c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@decodo/mcp-server", - "version": "1.2.3", + "version": "1.2.4", "description": "Decodo MCP Server", "bin": { "decodo-mcp": "./build/index.js" diff --git a/server.json b/server.json index bb424b7..535a200 100644 --- a/server.json +++ b/server.json @@ -7,13 +7,13 @@ "url": "https://github.com/Decodo/mcp-server", "source": "github" }, - "version": "1.2.3", + "version": "1.2.4", "packages": [ { "registryType": "npm", "registryBaseUrl": "https://registry.npmjs.org", "identifier": "@decodo/mcp-server", - "version": "1.2.3", + "version": "1.2.4", "transport": { "type": "stdio" } diff --git a/src/auth/__tests__/credential.test.ts b/src/auth/__tests__/credential.test.ts new file mode 100644 index 0000000..970ae5f --- /dev/null +++ b/src/auth/__tests__/credential.test.ts @@ -0,0 +1,85 @@ +import { AUTH_TYPE } from '../constants'; +import { credentialFromAuthHeader, credentialFromValue } from '../credential'; +import { detectCredentialType } from '../detect-credential-type'; + +const base64 = (value: string) => Buffer.from(value).toString('base64'); + +describe('detectCredentialType', () => { + it('treats base64 of user:pass as a token', () => { + expect(detectCredentialType(base64('testuser:testpass'))).toBe(AUTH_TYPE.TOKEN); + }); + + it('treats a value that does not decode to printable ascii as an api key', () => { + expect(detectCredentialType('sk-live-abc123')).toBe(AUTH_TYPE.API_KEY); + }); + + it('treats base64 without a colon as an api key', () => { + expect(detectCredentialType(base64('nocolonhere'))).toBe(AUTH_TYPE.API_KEY); + }); +}); + +describe('credentialFromValue', () => { + it('detects a token', () => { + expect(credentialFromValue(base64('user:pass'))).toEqual({ + type: AUTH_TYPE.TOKEN, + value: base64('user:pass'), + }); + }); + + it('detects an api key', () => { + expect(credentialFromValue('sk-live-abc123')).toEqual({ + type: AUTH_TYPE.API_KEY, + value: 'sk-live-abc123', + }); + }); + + it('trims surrounding whitespace', () => { + expect(credentialFromValue(' sk-live-abc123 ')?.value).toBe('sk-live-abc123'); + }); + + it('returns undefined for empty and whitespace-only values', () => { + expect(credentialFromValue('')).toBeUndefined(); + expect(credentialFromValue(' ')).toBeUndefined(); + }); +}); + +describe('credentialFromAuthHeader', () => { + it('maps Basic to a token', () => { + expect(credentialFromAuthHeader('Basic dGVzdDp0ZXN0')).toEqual({ + type: AUTH_TYPE.TOKEN, + value: 'dGVzdDp0ZXN0', + }); + }); + + it('maps Bearer to an api key', () => { + expect(credentialFromAuthHeader('Bearer sk-live-abc123')).toEqual({ + type: AUTH_TYPE.API_KEY, + value: 'sk-live-abc123', + }); + }); + + it('trusts the scheme over the shape of the value', () => { + expect(credentialFromAuthHeader(`Bearer ${base64('user:pass')}`)?.type).toBe(AUTH_TYPE.API_KEY); + }); + + it('rejects an unknown scheme', () => { + expect(credentialFromAuthHeader('Token abc123')).toBeUndefined(); + }); + + it('is case sensitive on the scheme', () => { + expect(credentialFromAuthHeader('basic dGVzdDp0ZXN0')).toBeUndefined(); + }); + + it('rejects a missing value, a missing scheme and extra parts', () => { + expect(credentialFromAuthHeader('Basic')).toBeUndefined(); + expect(credentialFromAuthHeader('dGVzdDp0ZXN0')).toBeUndefined(); + expect(credentialFromAuthHeader('Basic a b')).toBeUndefined(); + }); + + it('tolerates padding whitespace', () => { + expect(credentialFromAuthHeader(' Basic dGVzdDp0ZXN0 ')).toEqual({ + type: AUTH_TYPE.TOKEN, + value: 'dGVzdDp0ZXN0', + }); + }); +}); diff --git a/src/auth/constants.ts b/src/auth/constants.ts new file mode 100644 index 0000000..541169d --- /dev/null +++ b/src/auth/constants.ts @@ -0,0 +1,9 @@ +export const AUTH_TYPE = { + TOKEN: 'token', + API_KEY: 'apiKey', +} as const; + +export const AUTH_SCHEME = { + BASIC: 'Basic', + BEARER: 'Bearer', +} as const; diff --git a/src/auth/credential.ts b/src/auth/credential.ts new file mode 100644 index 0000000..01a5637 --- /dev/null +++ b/src/auth/credential.ts @@ -0,0 +1,31 @@ +import { AUTH_SCHEME, AUTH_TYPE } from './constants'; +import { detectCredentialType } from './detect-credential-type'; +import type { AuthCredential, AuthType } from './types'; + +const TYPE_BY_SCHEME: Record = { + [AUTH_SCHEME.BASIC]: AUTH_TYPE.TOKEN, + [AUTH_SCHEME.BEARER]: AUTH_TYPE.API_KEY, +}; + +const toCredential = (type: AuthType | undefined, value: string): AuthCredential | undefined => { + const trimmed = value.trim(); + + if (!type || !trimmed) { + return; + } + + return { type, value: trimmed }; +}; + +export const credentialFromValue = (value: string): AuthCredential | undefined => + toCredential(detectCredentialType(value), value); + +export const credentialFromAuthHeader = (header: string): AuthCredential | undefined => { + const [scheme, value, ...rest] = header.trim().split(/\s+/); + + if (!scheme || !value || rest.length > 0) { + return; + } + + return toCredential(TYPE_BY_SCHEME[scheme], value); +}; diff --git a/src/auth/detect-credential-type.ts b/src/auth/detect-credential-type.ts new file mode 100644 index 0000000..ba12139 --- /dev/null +++ b/src/auth/detect-credential-type.ts @@ -0,0 +1,14 @@ +import { AUTH_TYPE } from './constants'; +import type { AuthType } from './types'; + +const PRINTABLE_ASCII = /^[\x20-\x7e]+$/; + +export const detectCredentialType = (value: string): AuthType => { + const decoded = Buffer.from(value, 'base64').toString('utf8'); + + if (PRINTABLE_ASCII.test(decoded) && decoded.includes(':')) { + return AUTH_TYPE.TOKEN; + } + + return AUTH_TYPE.API_KEY; +}; diff --git a/src/auth/index.ts b/src/auth/index.ts new file mode 100644 index 0000000..e5bb667 --- /dev/null +++ b/src/auth/index.ts @@ -0,0 +1,4 @@ +export { AUTH_SCHEME, AUTH_TYPE } from './constants'; +export { credentialFromAuthHeader, credentialFromValue } from './credential'; +export { detectCredentialType } from './detect-credential-type'; +export type { AuthCredential, AuthType } from './types'; diff --git a/src/auth/types.ts b/src/auth/types.ts new file mode 100644 index 0000000..7c2aedd --- /dev/null +++ b/src/auth/types.ts @@ -0,0 +1,8 @@ +import type { AUTH_TYPE } from './constants'; + +export type AuthType = (typeof AUTH_TYPE)[keyof typeof AUTH_TYPE]; + +export type AuthCredential = { + type: AuthType; + value: string; +}; diff --git a/src/clients/__tests__/scraper-api-client.test.ts b/src/clients/__tests__/scraper-api-client.test.ts index 0593870..7b4c9c9 100644 --- a/src/clients/__tests__/scraper-api-client.test.ts +++ b/src/clients/__tests__/scraper-api-client.test.ts @@ -1,10 +1,17 @@ import { Target } from '@decodo/sdk-ts'; +import { AUTH_TYPE } from '../../auth'; +import type { AuthCredential } from '../../auth'; import { ScraperApiClient } from '../scraper-api-client'; import { ScrapingMCPParams } from '../../types'; const client = new ScraperApiClient({ maxRetries: 1, delayMs: 0 }); -const auth = 'dGVzdDp0ZXN0'; +const tokenValue = 'dGVzdDp0ZXN0'; +const apiKeyValue = 'sk-test-api-key'; + +const auth: AuthCredential = { type: AUTH_TYPE.TOKEN, value: tokenValue }; +const apiKeyAuth: AuthCredential = { type: AUTH_TYPE.API_KEY, value: apiKeyValue }; + const defaultArgs = { auth, scrapingParams: { url: 'https://example.com' } }; const mockFetch = jest.fn(); @@ -53,11 +60,33 @@ describe('ScraperApiClient', () => { expect(url).toBe('https://scraper-api.decodo.com/v2/scrape'); expect(init.method).toBe('POST'); expect(init.headers).toMatchObject({ - Authorization: `Basic ${auth}`, + Authorization: `Basic ${tokenValue}`, 'x-integration': 'mcp', }); }); + it('posts to the data API with Bearer auth when given an API key', async () => { + await client.scrape({ ...defaultArgs, auth: apiKeyAuth }); + + const { url, init } = lastRequest(); + + expect(url).toBe('https://data.decodo.com/v1/scrape'); + expect(init.headers).toMatchObject({ + Authorization: `Bearer ${apiKeyValue}`, + 'x-integration': 'mcp', + }); + }); + + it('sends the same body on both transports', async () => { + await client.scrape(defaultArgs); + const tokenBody = requestBody(); + + await client.scrape({ ...defaultArgs, auth: apiKeyAuth }); + const apiKeyBody = requestBody(); + + expect(apiKeyBody).toEqual(tokenBody); + }); + it('returns the first result content', async () => { respondWith(() => scrapeResponse({ title: 'Example' })); diff --git a/src/clients/scraper-api-client.ts b/src/clients/scraper-api-client.ts index 4c5f3a4..6e44c95 100644 --- a/src/clients/scraper-api-client.ts +++ b/src/clients/scraper-api-client.ts @@ -7,6 +7,8 @@ import { } from '@decodo/sdk-ts'; import type { ScrapeRequest, SyncResponse } from '@decodo/sdk-ts'; import { ScrapingMCPParams } from 'types'; +import { AUTH_TYPE } from '../auth'; +import type { AuthCredential, AuthType } from '../auth'; import { ProgressNotifier, ProgressExtra } from '../utils'; import { log } from '../logger'; import { @@ -81,10 +83,12 @@ export class ScraperApiClient { private sdkError = ({ error, target, + authType, startMs, }: { error: unknown; target: string; + authType: AuthType; startMs: number; }): unknown => { const latencyMs = Date.now() - startMs; @@ -97,6 +101,7 @@ export class ScraperApiClient { log('error', 'tool_call', { outcome: 'error', target, + auth_type: authType, error_type: 'upstream_api', upstream_status: error.statusCode, message: sdkMessage, @@ -112,6 +117,7 @@ export class ScraperApiClient { log('error', 'tool_call', { outcome: 'error', target, + auth_type: authType, error_type: 'network', error_code: errorCode, message, @@ -124,6 +130,7 @@ export class ScraperApiClient { log('error', 'tool_call', { outcome: 'error', target, + auth_type: authType, error_type: 'unexpected', message, latency_ms: latencyMs, @@ -132,12 +139,15 @@ export class ScraperApiClient { return error; }; + private sdkCredentials = (auth: AuthCredential) => + auth.type === AUTH_TYPE.API_KEY ? { apiKey: auth.value } : { token: auth.value }; + scrape = async ({ auth, scrapingParams, extra, }: { - auth: string; + auth: AuthCredential; scrapingParams: ScrapingMCPParams; extra?: ProgressExtra; }) => { @@ -153,7 +163,10 @@ export class ScraperApiClient { const { target } = params; const { webScrapingApi } = new DecodoClient({ - webScrapingApi: { token: auth, integrationHeader: INTEGRATION_HEADER }, + webScrapingApi: { + ...this.sdkCredentials(auth), + integrationHeader: INTEGRATION_HEADER, + }, timeoutMs: REQUEST_TIMEOUT_MS, }); @@ -172,6 +185,7 @@ export class ScraperApiClient { log('info', 'tool_call', { outcome: 'success', target, + auth_type: auth.type, upstream_status: res.results[0]?.status_code ?? null, latency_ms: Date.now() - startMs, attempt, @@ -208,7 +222,7 @@ export class ScraperApiClient { } } - throw this.sdkError({ error: lastError, target, startMs }); + throw this.sdkError({ error: lastError, target, authType: auth.type, startMs }); } finally { notifier.stopWaitingNotifications(); } diff --git a/src/index.ts b/src/index.ts index a5f927c..31aa69a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import 'dotenv/config'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { credentialFromValue } from './auth'; import { ScraperAPIStdioServer } from './server/sapi-stdio-server'; import { resolveToolsets } from './utils'; @@ -15,9 +16,14 @@ const parseEnvsOrExit = () => { } } - return { - sapiAuth: process.env['SCRAPER_API_TOKEN'] as string, - }; + const sapiAuth = credentialFromValue(process.env['SCRAPER_API_TOKEN'] as string); + + if (!sapiAuth) { + console.error('env SCRAPER_API_TOKEN missing'); + process.exit(1); + } + + return { sapiAuth }; }; const main = async () => { @@ -32,7 +38,7 @@ const main = async () => { }); await sapiMcpServer.connect(transport); - console.error('MCP Server running on stdio'); + console.error(`MCP Server running on stdio (auth: ${sapiAuth.type})`); }; main().catch(error => { diff --git a/src/server.ts b/src/server.ts index 576ac54..ac1064e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,6 +2,7 @@ import 'dotenv/config'; import cors from 'cors'; import express from 'express'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import { credentialFromAuthHeader } from './auth'; import { corsOptions } from './server/cors'; import { ScraperAPIHttpServer } from './server/sapi-http-server'; import { resolveToolsets } from './utils'; @@ -23,18 +24,16 @@ app.post('/mcp', async (req, res) => { return; } - const parts = auth.split(' '); + const credential = credentialFromAuthHeader(auth); - if (parts.length !== 2 || parts[0] !== 'Basic') { - res.status(401).send("Valid 'Basic' authorization required"); + if (!credential) { + res.status(401).send("Valid 'Basic' or 'Bearer' authorization required"); return; } - const token = parts[1]; - const toolsets = resolveToolsets(req.query.toolsets as string); - const server = new ScraperAPIHttpServer({ toolsets, auth: token }); + const server = new ScraperAPIHttpServer({ toolsets, auth: credential }); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, diff --git a/src/server/__tests__/auth.test.ts b/src/server/__tests__/auth.test.ts index 8a34bce..c296e54 100644 --- a/src/server/__tests__/auth.test.ts +++ b/src/server/__tests__/auth.test.ts @@ -1,3 +1,5 @@ +import { AUTH_TYPE } from '../../auth'; +import type { AuthCredential } from '../../auth'; import { ScraperAPIBaseServer } from '../sapi-base-server'; import { ScraperApiClient } from '../../clients/scraper-api-client'; @@ -9,11 +11,21 @@ jest.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({ describe('Auth flow', () => { describe('base64 encoding', () => { - it('passes auth token directly to server', () => { - const base64Token = 'dGVzdHVzZXI6dGVzdHBhc3M='; - const server = new ScraperAPIBaseServer({ auth: base64Token, toolsets: [] }); + it('passes the token credential directly to server', () => { + const auth: AuthCredential = { + type: AUTH_TYPE.TOKEN, + value: 'dGVzdHVzZXI6dGVzdHBhc3M=', + }; + const server = new ScraperAPIBaseServer({ auth, toolsets: [] }); + + expect(server.auth).toEqual(auth); + }); - expect(server.auth).toBe(base64Token); + it('passes an api key credential directly to server', () => { + const auth: AuthCredential = { type: AUTH_TYPE.API_KEY, value: 'sk-live-abc123' }; + const server = new ScraperAPIBaseServer({ auth, toolsets: [] }); + + expect(server.auth).toEqual(auth); }); it('auth token format is valid base64 encoding of username:password', () => { @@ -35,14 +47,16 @@ describe('Auth flow', () => { }); describe('missing credentials handling', () => { - it('ScraperAPIBaseServer accepts empty auth string', () => { - const server = new ScraperAPIBaseServer({ auth: '', toolsets: [] }); + it('ScraperAPIBaseServer accepts an empty credential value', () => { + const auth: AuthCredential = { type: AUTH_TYPE.TOKEN, value: '' }; + const server = new ScraperAPIBaseServer({ auth, toolsets: [] }); - expect(server.auth).toBe(''); + expect(server.auth).toEqual(auth); }); - it('server initializes with empty auth and creates client', () => { - const server = new ScraperAPIBaseServer({ auth: '', toolsets: [] }); + it('server initializes with an empty credential and creates client', () => { + const auth: AuthCredential = { type: AUTH_TYPE.TOKEN, value: '' }; + const server = new ScraperAPIBaseServer({ auth, toolsets: [] }); expect(server.sapiClient).toBeInstanceOf(ScraperApiClient); }); @@ -114,10 +128,10 @@ describe('Auth flow', () => { describe('auth token propagation', () => { it('auth is passed to tool registration', () => { - const auth = 'bXl0b2tlbjEyMw=='; + const auth: AuthCredential = { type: AUTH_TYPE.TOKEN, value: 'bXl0b2tlbjEyMw==' }; const server = new ScraperAPIBaseServer({ auth, toolsets: [] }); - expect(server.auth).toBe(auth); + expect(server.auth).toEqual(auth); expect(server.server).toBeDefined(); expect(server.sapiClient).toBeDefined(); }); diff --git a/src/server/__tests__/server-registration.test.ts b/src/server/__tests__/server-registration.test.ts index f091f08..9533cd4 100644 --- a/src/server/__tests__/server-registration.test.ts +++ b/src/server/__tests__/server-registration.test.ts @@ -1,7 +1,11 @@ +import { AUTH_TYPE } from '../../auth'; +import type { AuthCredential } from '../../auth'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { ScraperAPIBaseServer } from '../sapi-base-server'; import { TOOLSET } from '../../constants'; +const testAuth: AuthCredential = { type: AUTH_TYPE.TOKEN, value: 'test' }; + const mockRegisterTool = jest.fn().mockReturnThis(); jest.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({ @@ -17,16 +21,16 @@ describe('Server registration', () => { describe('server metadata', () => { it('creates McpServer with correct name and version', () => { - new ScraperAPIBaseServer({ auth: 'test', toolsets: [] }); + new ScraperAPIBaseServer({ auth: testAuth, toolsets: [] }); expect(McpServer).toHaveBeenCalledWith({ name: 'decodo', - version: '1.2.3', + version: '1.2.4', }); }); it('server instance is accessible', () => { - const server = new ScraperAPIBaseServer({ auth: 'test', toolsets: [] }); + const server = new ScraperAPIBaseServer({ auth: testAuth, toolsets: [] }); expect(server.server).toBeDefined(); }); @@ -34,7 +38,7 @@ describe('Server registration', () => { describe('tool registration', () => { it('registers all tools when no toolsets specified', () => { - new ScraperAPIBaseServer({ auth: 'test', toolsets: [] }); + new ScraperAPIBaseServer({ auth: testAuth, toolsets: [] }); const expectedToolCount = ScraperAPIBaseServer.allTools.length; expect(mockRegisterTool).toHaveBeenCalledTimes(expectedToolCount); @@ -43,7 +47,7 @@ describe('Server registration', () => { it('registers only web toolset tools when web specified', () => { mockRegisterTool.mockClear(); - new ScraperAPIBaseServer({ auth: 'test', toolsets: [TOOLSET.WEB] }); + new ScraperAPIBaseServer({ auth: testAuth, toolsets: [TOOLSET.WEB] }); const webTools = ScraperAPIBaseServer.allTools.filter(t => t.toolset === TOOLSET.WEB); expect(mockRegisterTool).toHaveBeenCalledTimes(webTools.length); @@ -52,7 +56,7 @@ describe('Server registration', () => { it('registers only AI toolset tools when ai specified', () => { mockRegisterTool.mockClear(); - new ScraperAPIBaseServer({ auth: 'test', toolsets: [TOOLSET.AI] }); + new ScraperAPIBaseServer({ auth: testAuth, toolsets: [TOOLSET.AI] }); const aiTools = ScraperAPIBaseServer.allTools.filter(t => t.toolset === TOOLSET.AI); expect(mockRegisterTool).toHaveBeenCalledTimes(aiTools.length); @@ -61,7 +65,7 @@ describe('Server registration', () => { it('registers multiple toolsets when specified', () => { mockRegisterTool.mockClear(); - new ScraperAPIBaseServer({ auth: 'test', toolsets: [TOOLSET.WEB, TOOLSET.AI] }); + new ScraperAPIBaseServer({ auth: testAuth, toolsets: [TOOLSET.WEB, TOOLSET.AI] }); const webTools = ScraperAPIBaseServer.allTools.filter(t => t.toolset === TOOLSET.WEB); const aiTools = ScraperAPIBaseServer.allTools.filter(t => t.toolset === TOOLSET.AI); @@ -71,7 +75,7 @@ describe('Server registration', () => { it('each tool is registered with name and config', () => { mockRegisterTool.mockClear(); - new ScraperAPIBaseServer({ auth: 'test', toolsets: [] }); + new ScraperAPIBaseServer({ auth: testAuth, toolsets: [] }); const calls = mockRegisterTool.mock.calls; for (const call of calls) { @@ -133,7 +137,7 @@ describe('Server registration', () => { }); it('creates ScraperApiClient instance', () => { - const server = new ScraperAPIBaseServer({ auth: 'test', toolsets: [] }); + const server = new ScraperAPIBaseServer({ auth: testAuth, toolsets: [] }); expect(server.sapiClient).toBeDefined(); }); diff --git a/src/server/sapi-base-server.ts b/src/server/sapi-base-server.ts index 79c6164..a3e4921 100644 --- a/src/server/sapi-base-server.ts +++ b/src/server/sapi-base-server.ts @@ -1,5 +1,6 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import type { AuthCredential } from '../auth'; import { ScraperApiClient } from '../clients/scraper-api-client'; import { PACKAGE_VERSION } from '../version'; import { @@ -43,9 +44,9 @@ export class ScraperAPIBaseServer { sapiClient: ScraperApiClient; - auth: string = ''; + auth: AuthCredential; - constructor({ auth, toolsets = [] }: { auth: string; toolsets: TOOLSET[] }) { + constructor({ auth, toolsets = [] }: { auth: AuthCredential; toolsets: TOOLSET[] }) { this.server = new McpServer({ name: 'decodo', version: PACKAGE_VERSION, diff --git a/src/server/sapi-http-server.ts b/src/server/sapi-http-server.ts index be4a614..0552109 100644 --- a/src/server/sapi-http-server.ts +++ b/src/server/sapi-http-server.ts @@ -1,4 +1,5 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { AuthCredential } from '../auth'; import { ScraperApiClient } from '../clients/scraper-api-client'; import { ScraperAPIBaseServer } from './sapi-base-server'; import { TOOLSET } from '../constants'; @@ -8,7 +9,7 @@ export class ScraperAPIHttpServer extends ScraperAPIBaseServer { sapiClient: ScraperApiClient; - constructor({ toolsets = [], auth }: { toolsets: TOOLSET[]; auth: string }) { + constructor({ toolsets = [], auth }: { toolsets: TOOLSET[]; auth: AuthCredential }) { super({ auth, toolsets }); } } diff --git a/src/server/sapi-stdio-server.ts b/src/server/sapi-stdio-server.ts index 7aeb965..99bf3df 100644 --- a/src/server/sapi-stdio-server.ts +++ b/src/server/sapi-stdio-server.ts @@ -1,4 +1,5 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { AuthCredential } from '../auth'; import { ScraperApiClient } from '../clients/scraper-api-client'; import { ScraperAPIBaseServer } from './sapi-base-server'; import { TOOLSET } from '../constants'; @@ -8,7 +9,7 @@ export class ScraperAPIStdioServer extends ScraperAPIBaseServer { sapiClient: ScraperApiClient; - constructor({ auth, toolsets = [] }: { auth: string; toolsets: TOOLSET[] }) { + constructor({ auth, toolsets = [] }: { auth: AuthCredential; toolsets: TOOLSET[] }) { super({ auth, toolsets }); } } diff --git a/src/tools/tool.ts b/src/tools/tool.ts index 03adb02..f057286 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -1,11 +1,12 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { AuthCredential } from '../auth'; import { ScraperApiClient } from '../clients/scraper-api-client'; import { TOOLSET } from '../constants'; export type ToolRegistrationArgs = { server: McpServer; sapiClient: ScraperApiClient; - auth: string; + auth: AuthCredential; }; export abstract class Tool {