Production-proven patterns extracted from Craft Agents, an Agent-native desktop app built entirely with AI assistance.
A clean separation of concerns for building MCP server configurations.
/**
* SourceServerBuilder - builds server configs from sources
*
* Separation of concerns:
* - SourceCredentialManager: handles credentials
* - SourceServerBuilder: handles server configuration
*/
export class SourceServerBuilder {
/**
* Build MCP server config from a source
* Supports HTTP/SSE (remote) and stdio (local subprocess) transports
*/
buildMcpServer(source: LoadedSource, token: string | null): McpServerConfig | null {
// Handle stdio transport (local subprocess servers)
if (mcp.transport === 'stdio') {
return {
type: 'stdio',
command: mcp.command,
args: mcp.args,
env: mcp.env,
};
}
// Handle HTTP/SSE transport (remote servers)
const url = normalizeMcpUrl(mcp.url);
const config: McpServerConfig = {
type: url.includes('/sse') ? 'sse' : 'http',
url,
};
// Inject authentication header
if (token) {
config.headers = { Authorization: `Bearer ${token}` };
}
return config;
}
/**
* Build API server with OAuth token auto-refresh support
*/
async buildApiServer(
source: LoadedSource,
credential: ApiCredential | null,
getToken?: () => Promise<string> // Token getter for OAuth auto-refresh
): Promise<McpServer | null> {
// Google/Slack APIs - use token getter with auto-refresh
if (provider === 'google' || provider === 'slack') {
return createApiServer(config, getToken);
}
// Static credential auth
return createApiServer(config, credential);
}
/**
* Build all servers from sources with credentials
*/
async buildAll(
sourcesWithCredentials: SourceWithCredential[],
getTokenForSource?: (source: LoadedSource) => (() => Promise<string>) | undefined
): Promise<BuiltServers> {
// Returns { mcpServers, apiServers, errors }
}
}Key Pattern: Pass token getter functions for OAuth sources to support auto-refresh without rebuilding servers.
Create a single flexible MCP tool per API that handles any endpoint.
/**
* Create a single flexible MCP tool for an API configuration.
* Accepts { path, method, params } and handles auth automatically.
*/
export function createApiTool(config: ApiConfig, credential: ApiCredentialSource) {
return tool(
`api_${config.name}`,
buildToolDescription(config),
{
path: z.string().describe('API endpoint path'),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']),
params: z.record(z.string(), z.unknown()).optional(),
// IMPORTANT: _intent field for smart summarization
_intent: z.string().optional().describe(
'REQUIRED: Describe what you are trying to accomplish (1-2 sentences)'
),
},
async (args) => {
// Resolve credential (static or token getter)
const resolvedCredential = isTokenGetter(credential)
? await credential()
: credential;
// Build URL and headers with auth
const url = buildUrl(config.baseUrl, path, method, params, config.auth, resolvedCredential);
const headers = buildHeaders(config.auth, resolvedCredential, config.defaultHeaders);
const response = await fetch(url, { method, headers, body: ... });
// Smart summarization for large responses
if (estimateTokens(text) > TOKEN_LIMIT) {
return summarizeLargeResult(text, {
toolName: `api_${config.name}`,
path,
input: params,
modelIntent: _intent, // Uses intent for context-aware summarization
});
}
return { content: [{ type: 'text', text }] };
}
);
}Key Patterns:
- Single flexible tool instead of one tool per endpoint
_intentfield injected into schema for context-aware summarization- Token getter pattern for OAuth auto-refresh
- Automatic large response handling with intent-preserved summarization
Multi-backend credential storage with priority-based resolution.
/**
* Backend priority:
* 1. Environment variables (server deployment, read-only)
* 2. Encrypted file storage (cross-platform, no OS keychain prompts)
*/
export class CredentialManager {
private backends: CredentialBackend[] = [];
private writeBackend: CredentialBackend | null = null;
private initialized = false;
private initPromise: Promise<void> | null = null;
/**
* Lazy initialization - called automatically by all public methods
*/
private async ensureInitialized(): Promise<void> {
if (this.initialized) return;
if (this.initPromise) return this.initPromise;
this.initPromise = this._doInitialize().catch((err) => {
this.initPromise = null; // Allow retry on failure
throw err;
});
await this.initPromise;
}
/**
* Get credential by ID, trying all backends in priority order
*/
async get(id: CredentialId): Promise<StoredCredential | null> {
await this.ensureInitialized();
for (const backend of this.backends) {
const cred = await backend.get(id);
if (cred) return cred;
}
return null;
}
/**
* Set credential using the write backend
*/
async set(id: CredentialId, credential: StoredCredential): Promise<void> {
await this.ensureInitialized();
if (!this.writeBackend) throw new Error('No writable backend');
await this.writeBackend.set(id, credential);
}
// Convenience methods
async getApiKey(): Promise<string | null> { ... }
async getClaudeOAuth(): Promise<string | null> { ... }
async getWorkspaceOAuth(workspaceId: string): Promise<OAuthCredentials | null> { ... }
/** Check if credential is expired (with 5-minute buffer) */
isExpired(credential: StoredCredential): boolean {
if (!credential.expiresAt) return false;
return Date.now() > credential.expiresAt - 5 * 60 * 1000;
}
}
// Singleton pattern
let manager: CredentialManager | null = null;
export function getCredentialManager(): CredentialManager {
if (!manager) manager = new CredentialManager();
return manager;
}Key Patterns:
- Priority-based backend resolution - environment vars first, then encrypted storage
- Lazy initialization with race protection -
initPromiseprevents concurrent init - Typed credential IDs -
{ type: 'anthropic_api_key' }or{ type: 'workspace_oauth', workspaceId } - Expiry buffer - 5-minute buffer before actual expiry
- Convenience methods - typed wrappers for common credential types
Three-level permission system with session-scoped state isolation.
/**
* Permission Modes:
* - 'safe': Read-only exploration mode (blocks writes, never prompts)
* - 'ask': Ask for permission on dangerous operations (default)
* - 'allow-all': Skip all permission checks
*/
export type PermissionMode = 'safe' | 'ask' | 'allow-all';
/**
* Manager for per-session permission mode state.
* Each session has its own state - NO GLOBAL STATE.
*/
class ModeManager {
private states: Map<string, ModeState> = new Map();
private callbacks: Map<string, ModeCallbacks> = new Map();
private subscribers: Map<string, Set<() => void>> = new Map();
setPermissionMode(sessionId: string, mode: PermissionMode): void {
const newState = { ...this.getState(sessionId), permissionMode: mode };
this.states.set(sessionId, newState);
// Notify internal callbacks
this.callbacks.get(sessionId)?.onStateChange?.(newState);
// Notify React subscribers (useSyncExternalStore)
this.subscribers.get(sessionId)?.forEach(cb => cb());
}
/**
* Subscribe for React useSyncExternalStore pattern
*/
subscribe(sessionId: string, callback: () => void): () => void {
this.subscribers.get(sessionId)?.add(callback);
return () => this.subscribers.get(sessionId)?.delete(callback);
}
}
export const modeManager = new ModeManager();/**
* Get detailed reason why a bash command was rejected.
* Uses AST-based validation to properly handle compound commands.
*/
export function getBashRejectionReason(
command: string,
config: ToolCheckConfig
): BashRejectionReason | null {
// Step 1: Check control characters (before parsing)
const controlChar = hasControlCharacters(command);
if (controlChar) return { type: 'control_char', ... };
// Step 2: AST-based validation
// Handles compound commands like `git status && git log`
const astResult = validateBashCommand(command, config.readOnlyBashPatterns);
if (astResult.allowed) return null;
// Step 3: Convert AST rejection to detailed reason
switch (astResult.reason.type) {
case 'pipeline': return { type: 'dangerous_operator', operator: '|', ... };
case 'redirect': return { type: 'dangerous_operator', operator: reason.op, ... };
case 'unsafe_command':
// Find relevant patterns to help agent understand
const relevantPatterns = findRelevantPatterns(command, config.readOnlyBashPatterns);
const mismatchAnalysis = analyzePatternMismatch(command, config.readOnlyBashPatterns);
return { type: 'no_safe_pattern', command, relevantPatterns, mismatchAnalysis };
}
}/**
* Centralized check: should a tool be allowed based on permission mode?
*/
export function shouldAllowToolInMode(
toolName: string,
toolInput: unknown,
mode: PermissionMode,
options?: { plansFolderPath?: string; permissionsContext?: PermissionsContext }
): ToolCheckResult {
// Allow-all: no restrictions
if (mode === 'allow-all') return { allowed: true };
// Ask mode: all allowed (user will be prompted)
if (mode === 'ask') return { allowed: true };
// Safe mode: check against read-only allowlist
if (ALWAYS_ALLOWED_TOOLS.has(toolName)) return { allowed: true };
// Bash: detailed rejection with pattern analysis
if (toolName === 'Bash') {
const rejection = getBashRejectionReason(command, config);
if (!rejection) return { allowed: true };
return { allowed: false, reason: formatBashRejectionMessage(rejection, config) };
}
// Write/Edit: allow if targeting plans folder
if (toolName === 'Write' || toolName === 'Edit') {
if (filePath.startsWith(options?.plansFolderPath)) return { allowed: true };
if (matchesAllowedWritePath(filePath, config.allowedWritePaths)) return { allowed: true };
}
// MCP tools: check read-only patterns
if (toolName.startsWith('mcp__')) {
if (isReadOnlyMcpToolWithConfig(toolName, config)) return { allowed: true };
return { allowed: false, reason: 'MCP write operations blocked in Explore mode' };
}
// API tools: GET always allowed, mutations need whitelist
if (toolName.startsWith('api_')) {
if (method === 'GET') return { allowed: true };
if (isApiCallAllowedWithConfig(method, path, config)) return { allowed: true };
return { allowed: false, reason: `API ${method} blocked in Explore mode` };
}
return { allowed: true };
}Key Patterns:
- Session-scoped state - no global contamination
- React integration -
subscribe()foruseSyncExternalStore - AST-based validation - handles compound commands correctly
- Detailed rejection messages - includes relevant patterns and suggestions
- Plans folder exception - always allow writes to plans directory
Non-interactive query execution for automation.
/**
* HeadlessRunner executes queries in non-interactive mode.
*
* Handles interactions automatically:
* - Permissions: based on policy (deny-all, allow-safe, allow-all)
* - Questions: returns empty answers
* - Auth: fails if credentials missing
*/
export class HeadlessRunner {
/**
* Map headless permission policy to PermissionMode
*/
private policyToPermissionMode(policy: HeadlessConfig['permissionPolicy']): PermissionMode {
switch (policy) {
case 'allow-all': return 'allow-all';
case 'allow-safe': return 'ask';
case 'deny-all':
default: return 'safe';
}
}
/**
* Run with streaming events
*/
async *runStreaming(): AsyncGenerator<HeadlessEvent> {
// Wrap prompt with headless mode XML tags
const wrappedPrompt = `<headless_mode tools_usage="no-interactive-tools" safe_mode="disabled">
${this.config.prompt}
</headless_mode>`;
for await (const event of this.agent.chat(wrappedPrompt)) {
switch (event.type) {
case 'text_delta':
yield { type: 'text_delta', text: event.text };
break;
case 'tool_start':
yield { type: 'tool_start', id: event.toolUseId, name: event.toolName, input: event.input };
break;
case 'tool_result':
toolCalls.push({ ... });
yield { type: 'tool_result', ... };
break;
case 'complete':
yield { type: 'complete', result: { success: true, response, toolCalls, usage } };
break;
}
}
}
/**
* Auto-handle permission requests based on policy
*/
private setupPermissionHandler(): void {
this.agent.onPermissionRequest = (request) => {
if (this.config.permissionPolicy === 'allow-all') {
this.agent.respondToPermission(request.requestId, true, false);
return;
}
if (this.config.permissionPolicy === 'allow-safe') {
const baseCommand = request.command.trim().split(/\s+/)[0];
const allowed = SAFE_COMMANDS.has(baseCommand);
this.agent.respondToPermission(request.requestId, allowed, false);
return;
}
// deny-all
this.agent.respondToPermission(request.requestId, false, false);
};
}
}Key Patterns:
- Policy-to-mode mapping - separate abstraction for headless vs interactive
- XML wrapper for context - signals headless mode to agent
- Streaming event generator -
AsyncGenerator<HeadlessEvent> - Auto permission handling - based on policy, no user prompts
- Session resumption -
--sessionand--session-resumeflags
Tools that are bound to a specific session with callbacks.
/**
* Registry mapping session IDs to their callbacks
*/
const sessionScopedToolCallbackRegistry = new Map<string, SessionScopedToolCallbacks>();
export interface SessionScopedToolCallbacks {
/** Called when a plan is submitted - triggers plan message display */
onPlanSubmitted?: (planPath: string) => void;
/**
* Called when authentication is requested - triggers auth UI and forceAbort.
* Flow:
* 1. Tool calls onAuthRequest
* 2. Session manager creates auth-request message and calls forceAbort
* 3. User completes auth in UI
* 4. Auth result sent as "faked user message"
* 5. Agent resumes and processes result
*/
onAuthRequest?: (request: AuthRequest) => void;
}
/**
* Create session-scoped SubmitPlan tool.
* SessionId is captured at creation time (closure).
*/
export function createSubmitPlanTool(sessionId: string) {
return tool(
'SubmitPlan',
`Submit a plan for user review.
...
**IMPORTANT:** After calling this tool:
- Execution will be **automatically paused** to present the plan
- No further tool calls or text output will be processed
- The conversation will resume when user responds`,
{
planPath: z.string().describe('Absolute path to the plan markdown file'),
},
async (args) => {
// Verify file exists
if (!existsSync(args.planPath)) {
return { content: [{ type: 'text', text: 'Error: Plan file not found' }] };
}
// Store plan file path
setLastPlanFilePath(sessionId, args.planPath);
// Get callbacks and notify UI
const callbacks = getSessionScopedToolCallbacks(sessionId);
if (callbacks?.onPlanSubmitted) {
callbacks.onPlanSubmitted(args.planPath); // This triggers forceAbort
}
return { content: [{ type: 'text', text: 'Plan submitted. Waiting for feedback.' }] };
}
);
}
/**
* Create session-scoped OAuth trigger tool.
* Triggers auth request that pauses execution.
*/
export function createOAuthTriggerTool(sessionId: string, workspaceRootPath: string) {
return tool(
'source_oauth_trigger',
`Start OAuth authentication for an MCP source.
...
**IMPORTANT:** After calling this tool:
- Execution will be **automatically paused** while OAuth completes
- A browser window will open for user authentication`,
{
sourceSlug: z.string().describe('The slug of the source to authenticate'),
},
async (args) => {
const source = loadSourceConfig(workspaceRootPath, args.sourceSlug);
// Validate source...
const callbacks = getSessionScopedToolCallbacks(sessionId);
if (!callbacks?.onAuthRequest) {
return { content: [{ type: 'text', text: 'No auth handler available' }], isError: true };
}
// Build and trigger auth request
const authRequest: McpOAuthAuthRequest = {
type: 'oauth',
requestId: crypto.randomUUID(),
sessionId,
sourceSlug: args.sourceSlug,
sourceName: source.name,
};
callbacks.onAuthRequest(authRequest); // This triggers forceAbort
return { content: [{ type: 'text', text: 'OAuth requested. Opening browser.' }] };
}
);
}Key Patterns:
- Closure capture -
sessionIdcaptured at tool creation time - Callback registry -
Map<string, Callbacks>for session isolation - ForceAbort pattern - callbacks trigger agent pause, result comes as new message
- Tool description warns about pause - agent knows to not add more output after
- Request ID for correlation -
crypto.randomUUID()ties request to response
| Pattern | Use When |
|---|---|
| SourceServerBuilder | Building MCP/API server configs from config files |
| API Tool Factory | Creating flexible API tools with auto-auth |
| CredentialManager | Storing/retrieving secrets with multi-backend support |
| Permission Mode | Implementing safe/ask/allow-all permission levels |
| Headless Runner | Running agent queries in CI/automation |
| Session-Scoped Tools | Tools that need to interact with UI or pause execution |
These patterns are extracted from Craft Agents, an open-source Agent-native desktop app built with Claude Agent SDK.