diff --git a/package.json b/package.json index 939bda6..a1394b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dudko.dev/agent-web", - "version": "0.0.11", + "version": "0.0.12", "description": "Headless, configurable in-browser LLM agent on the Vercel AI SDK: multi-provider bring-your-own-key (OpenAI, Anthropic, Google, xAI, DeepSeek, gateway, openai-compatible) AND local WebGPU/WebLLM, native + prompted tool-calling, structured output, an encrypted IndexedDB token vault, and a plan \u2192 execute \u2192 replan \u2192 synthesize loop. UI-agnostic.", "type": "module", "sideEffects": false, diff --git a/src/mcp/index.ts b/src/mcp/index.ts index 6140c15..957d815 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -1,4 +1,9 @@ export { connectMcpHttp, flattenContent } from './http.js' +// Re-exported so a host can identify an authorization failure by identity. +// The SDK's class does not set `name`, so `err.name === 'UnauthorizedError'` +// is always false — string sniffing is not an option, and a host that only +// depends on this package has no other handle on the class. +export { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' export type { McpHttpServerConfig, McpCatalogEntry, diff --git a/tests/mcp.test.ts b/tests/mcp.test.ts index 77d685e..404881e 100644 --- a/tests/mcp.test.ts +++ b/tests/mcp.test.ts @@ -9,6 +9,7 @@ import { flattenContent, MemoryOAuthStorage, readOAuthCallback, + UnauthorizedError, VaultOAuthStorage, } from '../dist/mcp.js' @@ -849,3 +850,20 @@ test('OAuth: a dead refresh token falls back to a fresh authorization', async () // the same doomed refresh. assert.equal(await provider.tokens(), undefined) }) + +test('the subpath re-exports UnauthorizedError so a host can identify auth failures', async () => { + const mock = createMockServer({ requireAuth: true }) + const provider = makeProvider() + const mcp = await connectMcpHttp({ + docs: { url: MCP_URL, authProvider: provider, fetch: mock.fetchFn }, + }) + assert.equal(mcp.results[0].needsAuthorization, true) + + // The SDK's class never assigns `this.name`, so it reads as "Error" — + // a host checking err.name would silently never match. Identity is the + // only reliable handle, and it has to be reachable from this package. + const err = new UnauthorizedError() + assert.equal(err.name, 'Error', 'guards the assumption this export exists for') + assert.ok(err instanceof UnauthorizedError) + assert.ok(err instanceof Error) +})