;
warehouseDistanceKm: number;
paymentVerified: boolean;
carrierAssigned: boolean;
}
@Service()
export class FulfillmentEngine {
private readonly activeOrderId = signal<string>('ORD-9942');
readonly metrics = signal<FulfillmentMetrics>({
orderId: 'ORD-9942',
itemsPending: 3,
warehouseDistanceKm: 42,
paymentVerified: true,
carrierAssigned: false,
});
readonly isReadyToShip = computed(() => {
const m = this.metrics();
return m.paymentVerified && m.carrierAssigned && m.itemsPending === 0;
});
readonly estimatedDelayHours = computed(() => {
const m = this.metrics();
if (!m.paymentVerified) return 24;
if (!m.carrierAssigned) return 12;
return Math.ceil(m.warehouseDistanceKm / 15);
});
updateCarrierStatus(assigned: boolean): void {
this.metrics.update(prev => ({ ...prev, carrierAssigned: assigned }));
}
}
**Why this structure?** Signals guarantee that tool execution always reads the latest state without manual subscription management. Computed values centralize business logic, ensuring AI tools derive insights from the same rules that drive the UI.
### Step 2: Register Tools via Provider System
Angular v22 exposes `provideWebMcpTools()` to register capabilities at the application level. Tools are defined with a strict contract: a machine-readable name, a human-readable description for AI routing, an input schema, and an execution function.
```typescript
import { ApplicationConfig, provideWebMcpTools } from '@angular/core';
import { fulfillToolRegistry } from './ai-tools/fulfillment-tools';
export const appConfig: ApplicationConfig = {
providers: [
provideWebMcpTools(fulfillToolRegistry)
]
};
Why global registration? WebMCP tools operate at the browser level. Registering them in appConfig ensures they are available across all routes and components. Route-level registration is possible but unnecessary unless tool scope must be strictly isolated.
Step 3: Implement Tool Execution with Injection Context
Each tool's execute function runs inside Angular's injection context. This allows direct use of inject() to access services and read signals synchronously. The WebMCP spec expects a standardized response format containing content blocks.
import { inject } from '@angular/core';
import type { WebMcpTool } from '@angular/core';
import { FulfillmentEngine } from '../services/fulfillment-engine.service';
export const fulfillToolRegistry: WebMcpTool[] = [
{
name: 'fetch_fulfillment_metrics',
description: 'Retrieves current order fulfillment status and readiness indicators.',
inputSchema: { type: 'object', properties: {} },
execute: () => {
const engine = inject(FulfillmentEngine);
const m = engine.metrics();
const ready = engine.isReadyToShip();
return {
content: [
{
type: 'text',
text: `Order ${m.orderId} | Items Pending: ${m.itemsPending} | ` +
`Payment: ${m.paymentVerified ? 'Verified' : 'Pending'} | ` +
`Carrier: ${m.carrierAssigned ? 'Assigned' : 'Unassigned'} | ` +
`Ready to Ship: ${ready}`
}
]
};
}
},
{
name: 'assess_shipping_delay',
description: 'Calculates estimated delay based on payment status, carrier assignment, and warehouse distance.',
inputSchema: { type: 'object', properties: {} },
execute: () => {
const engine = inject(FulfillmentEngine);
const delay = engine.estimatedDelayHours();
const reason = delay > 12 ? 'Payment verification pending' :
delay > 6 ? 'Carrier assignment pending' :
'Standard transit calculation';
return {
content: [
{
type: 'text',
text: `Estimated delay: ${delay} hours. Primary factor: ${reason}.`
}
]
};
}
},
{
name: 'generate_customer_update',
description: 'Drafts a customer-facing shipping notification based on current fulfillment state.',
inputSchema: { type: 'object', properties: {} },
execute: () => {
const engine = inject(FulfillmentEngine);
const m = engine.metrics();
const ready = engine.isReadyToShip();
const message = ready
? `Your order ${m.orderId} is ready for dispatch. Expect delivery within standard transit windows.`
: `Your order ${m.orderId} is currently being processed. We are finalizing carrier assignment and will notify you once shipped.`;
return {
content: [
{
type: 'text',
text: message
}
]
};
}
}
];
Why this execution pattern? The inject() call resolves dependencies at runtime without requiring manual provider passing. Reading signals synchronously inside execute is safe because WebMCP tool invocation is event-driven, not tied to Angular's change detection cycle. The response format adheres to the WebMCP content block specification, ensuring compatibility with Gemini and other compliant models.
Pitfall Guide
1. Blocking the Main Thread in execute
Explanation: Tool execution runs on the main thread. Heavy computations, synchronous loops, or unoptimized signal chains will freeze the browser and degrade AI responsiveness.
Fix: Keep execute lightweight. Offload heavy processing to Web Workers or pre-compute values in signals. Use toSignal or effect for async data, but never block the execution context.
Explanation: AI models parse tool descriptions and schemas to decide when to invoke them. Including internal IDs, PII, or security tokens in descriptions or default responses exposes data to model logs.
Fix: Sanitize all tool outputs. Use generic descriptions ("Retrieves fulfillment status") instead of implementation details. Strip sensitive fields before returning content blocks.
3. Ignoring Angular's Change Detection Cycle
Explanation: While execute runs outside change detection, reading signals that trigger expensive template updates can cause performance degradation if the AI rapidly polls tools.
Fix: Use untracked() when reading signals inside execute if you don't need reactive side effects. Ensure computed signals are memoized and avoid triggering UI updates during tool invocation.
Explanation: Developers often add unnecessary parameters to inputSchema, assuming AI needs explicit arguments. Modern models like Gemini infer context from natural language and tool descriptions.
Fix: Keep schemas minimal. Use { type: 'object', properties: {} } for stateless tools. Only add parameters when the tool requires explicit user input (e.g., orderId, region).
5. Treating Experimental APIs as Production-Ready
Explanation: provideWebMcpTools() is an experimental Angular v22 feature. Relying on it for critical business flows without fallbacks risks breakage during spec changes.
Fix: Wrap tool registration in feature flags. Implement graceful degradation (e.g., fallback to REST endpoints if WebMCP is unavailable). Monitor Angular release notes for spec alignment.
Explanation: If a signal throws or a service is uninitialized, execute will crash silently or return malformed responses, breaking the AI conversation flow.
Fix: Wrap execution logic in try/catch blocks. Return structured error messages in the content array. Validate service state before reading signals.
Explanation: Duplicating business rules inside execute functions creates maintenance debt and diverges from the UI's actual behavior.
Fix: Always derive tool outputs from existing computed signals or service methods. Tools should be thin adapters, not logic containers.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| AI needs real-time app state | WebMCP Signal Integration | Direct memory access, zero network latency, reactive updates | Low (internal API) |
| AI requires external data | Traditional REST/GraphQL Endpoint | Decoupled from frontend state, supports pagination/auth | Medium (infrastructure) |
| Legacy app with no signals | DOM Scraping / Screenshot Parsing | No refactoring required, quick prototype | High (maintenance, fragility) |
| Multi-tenant SaaS with strict compliance | WebMCP with Sanitization Layer | Controlled data exposure, audit-friendly tool contracts | Medium (security overhead) |
Configuration Template
// app.config.ts
import { ApplicationConfig, provideWebMcpTools } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { fulfillmentToolRegistry } from './ai-tools/fulfillment-tools';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
// Register WebMCP tools only in supported environments
...(typeof window !== 'undefined' && window.WebMCP
? [provideWebMcpTools(fulfillmentToolRegistry)]
: [])
]
};
Quick Start Guide
- Install Angular v22: Ensure your project uses
@angular/core@^22.0.0 or later.
- Create a Signal-Based Service: Define state using
signal() and business rules using computed().
- Define Tool Registry: Create an array of
WebMcpTool objects with name, description, inputSchema, and execute.
- Register in App Config: Add
provideWebMcpTools(yourRegistry) to appConfig.providers.
- Verify with Inspector: Open the WebMCP Model Context Tool Inspector in Chrome. Confirm tools appear, invoke them manually, and validate signal-driven responses.