Chapter 13: SDK Bridge Architecture
This chapter explores how the TypeScript SDK bridges MCP hosts (like Claude Desktop) to the Pierre server, translating between stdio (MCP standard) and HTTP (Pierre’s transport).
SDK Bridge Pattern
The SDK acts as a transparent bridge between MCP hosts and Pierre server:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Claude │ stdio │ SDK │ HTTP │ Pierre │
│ Desktop │◄───────►│ Bridge │◄───────►│ Server │
│ (MCP Host) │ │ (TypeScript)│ │ (Rust) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
│ tools/list │ GET /mcp/tools │
├────────────────────────►├────────────────────────►│
│ │ │
│ tools (JSON-RPC) │ HTTP 200 │
│◄────────────────────────┼◄────────────────────────┤
Source: sdk/src/bridge.ts:70-84
export interface BridgeConfig {
pierreServerUrl: string;
jwtToken?: string;
apiKey?: string;
oauthClientId?: string;
oauthClientSecret?: string;
userEmail?: string;
userPassword?: string;
callbackPort?: number;
disableBrowser?: boolean;
tokenValidationTimeoutMs?: number;
proactiveConnectionTimeoutMs?: number;
proactiveToolsListTimeoutMs?: number;
toolCallConnectionTimeoutMs?: number;
}
Configuration:
pierreServerUrl: Pierre HTTP endpoint (e.g.,http://localhost:8081)jwtToken/apiKey: Pre-existing authenticationoauthClientId/oauthClientSecret: OAuth app credentialsuserEmail/userPassword: Login credentialscallbackPort: OAuth callback listener port
OAuth Client Provider
The SDK implements OAuth 2.0 client for Pierre authentication:
Source: sdk/src/bridge.ts:113-150
class PierreOAuthClientProvider implements OAuthClientProvider {
private serverUrl: string;
private config: BridgeConfig;
private clientInfo: OAuthClientInformationFull | undefined = undefined;
private savedTokens: OAuthTokens | undefined = undefined;
private codeVerifierValue: string | undefined = undefined;
private stateValue: string | undefined = undefined;
private callbackServer: any = undefined;
private authorizationPending: Promise<any> | undefined = undefined;
private callbackPort: number = 0;
private callbackSessionToken: string | undefined = undefined;
// Secure token storage using OS keychain
private secureStorage: SecureTokenStorage | undefined = undefined;
private allStoredTokens: StoredTokens = {};
// Client-side client info storage (client info is not sensitive, can stay in file)
private clientInfoPath: string;
constructor(serverUrl: string, config: BridgeConfig) {
this.serverUrl = serverUrl;
this.config = config;
// Initialize client info storage path
const os = require('os');
const path = require('path');
this.clientInfoPath = path.join(os.homedir(), '.pierre-mcp-client-info.json');
// NOTE: Secure storage initialization is async, so it's deferred to start()
// to avoid race conditions with constructor completion
// See initializePierreConnection() for the actual initialization
// Load client info from storage (synchronous, non-sensitive)
this.loadClientInfo();
this.log(`OAuth client provider created for server: ${serverUrl}`);
this.log(`Using OS keychain for secure token storage (will initialize on start)`);
this.log(`Client info storage path: ${this.clientInfoPath}`);
}
OAuth flow:
- Discovery: Fetch
/.well-known/oauth-authorization-serverfor endpoints - Registration: Register OAuth client with Pierre (RFC 7591)
- Authorization: Open browser to
/oauth/authorize - Callback: Listen for OAuth callback on localhost
- Token exchange: POST to
/oauth/tokenwith authorization code - Token storage: Save to OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)
Secure Token Storage
The SDK stores OAuth tokens in OS-native secure storage:
Source: sdk/src/bridge.ts:59-68
interface StoredTokens {
pierre?: OAuthTokens & { saved_at?: number };
providers?: Record<string, {
access_token: string;
refresh_token?: string;
expires_at?: number;
token_type?: string;
scope?: string;
}>;
}
Storage locations:
- macOS: Keychain (
securitycommand-line tool) - Windows: Credential Manager (Windows Credential Store API)
- Linux: Secret Service API (libsecret)
Security: Tokens never stored in plaintext files. OS-native encryption protects credentials.
MCP Host Integration
The SDK integrates with MCP hosts via stdio transport:
Source: sdk/src/bridge.ts:13-16
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
Components:
Server: MCP server exposed to host via stdioStdioServerTransport: stdio transport for MCP host communicationClient: MCP client connecting to PierreStreamableHTTPClientTransport: HTTP transport for Pierre connection
Message Routing
The SDK routes messages bidirectionally:
Claude Desktop → Server (stdio) → Client (HTTP) → Pierre
Claude Desktop ← Server (stdio) ← Client (HTTP) ← Pierre
Request flow:
- MCP host sends JSON-RPC to SDK’s stdio (e.g.,
tools/call) - SDK’s Server receives via
StdioServerTransport - SDK’s Client forwards to Pierre via
StreamableHTTPClientTransport - Pierre processes and returns JSON-RPC response
- SDK’s Client receives HTTP response
- SDK’s Server sends JSON-RPC to MCP host via stdio
Automatic OAuth Handling
The SDK handles OAuth flows transparently:
Source: sdk/src/bridge.ts:48-57
// Define custom notification schema for Pierre's OAuth completion notifications
const OAuthCompletedNotificationSchema = z.object({
method: z.literal('notifications/oauth_completed'),
params: z.object({
provider: z.string(),
success: z.boolean(),
message: z.string(),
user_id: z.string().optional()
}).optional()
});
OAuth notifications:
- Pierre sends
notifications/oauth_completedvia SSE - SDK receives notification and updates stored tokens
- Future requests use refreshed tokens automatically
Key Takeaways
-
Bridge pattern: SDK translates stdio (MCP standard) <-> HTTP (Pierre transport).
-
OAuth client: Full OAuth 2.0 implementation with discovery, registration, and token exchange.
-
Secure storage: OS-native keychain for token storage (never plaintext files).
-
Transparent integration: MCP hosts (Claude Desktop) connect via stdio without knowing about HTTP backend.
-
Bidirectional routing: Messages flow both directions through SDK bridge.
-
Automatic token refresh: SDK handles token expiration and refresh transparently.
-
MCP SDK: Built on official
@modelcontextprotocol/sdkfor standard compliance.
Next Chapter: Chapter 14: Type Generation & Tools-to-Types - Learn how Pierre generates TypeScript types from Rust tool definitions for type-safe SDK development.