multi-agent-protocol 0.0.3 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,877 @@
1
+ # MAP Protocol Integration Design Specification
2
+
3
+ This document specifies how to integrate agent-iam as an IAM provider for the Multi-Agent Protocol (MAP).
4
+
5
+ ## Overview
6
+
7
+ agent-iam provides capability-based tokens with optional identity binding, federation metadata, and agent capabilities. MAP consumes these tokens through a custom authenticator that maps agent-iam concepts to MAP's native types.
8
+
9
+ ```
10
+ ┌─────────────────────────────────────────────────────────────────────────┐
11
+ │ MAP System │
12
+ │ │
13
+ │ ┌──────────────────┐ ┌─────────────────────┐ ┌────────────────┐ │
14
+ │ │ MAP Connection │───▶│ AgentIAMAuthenticator│───▶│ MAP Router │ │
15
+ │ │ (with token) │ │ │ │ │ │
16
+ │ └──────────────────┘ │ - Verify token │ │ - Route msgs │ │
17
+ │ │ - Extract identity │ │ - Enforce │ │
18
+ │ │ - Map capabilities │ │ permissions │ │
19
+ │ └─────────────────────┘ └────────────────┘ │
20
+ │ │ │
21
+ │ ▼ │
22
+ │ ┌─────────────────────┐ │
23
+ │ │ agent-iam │ │
24
+ │ │ TokenService │ │
25
+ │ └─────────────────────┘ │
26
+ └─────────────────────────────────────────────────────────────────────────┘
27
+ ```
28
+
29
+ ## 1. Authentication Method
30
+
31
+ ### 1.1 Custom Auth Method: `x-agent-iam`
32
+
33
+ Register a custom authentication method for agent-iam tokens:
34
+
35
+ ```typescript
36
+ // map-protocol/ts-sdk/src/server/auth/authenticators/agent-iam.ts
37
+
38
+ import { TokenService, type AgentToken } from 'agent-iam';
39
+ import type { Authenticator, AuthContext, AuthResult } from '../types';
40
+
41
+ export interface AgentIAMAuthenticatorOptions {
42
+ /** The agent-iam TokenService instance */
43
+ tokenService: TokenService;
44
+
45
+ /** System ID for this MAP system (used in identity binding) */
46
+ systemId: string;
47
+
48
+ /** Whether to require identity binding */
49
+ requireIdentity?: boolean;
50
+
51
+ /** Allowed tenant IDs (undefined = all) */
52
+ allowedTenants?: string[];
53
+ }
54
+
55
+ export class AgentIAMAuthenticator implements Authenticator {
56
+ readonly methods = ['x-agent-iam'] as const;
57
+
58
+ private tokenService: TokenService;
59
+ private systemId: string;
60
+ private requireIdentity: boolean;
61
+ private allowedTenants?: string[];
62
+
63
+ constructor(options: AgentIAMAuthenticatorOptions) {
64
+ this.tokenService = options.tokenService;
65
+ this.systemId = options.systemId;
66
+ this.requireIdentity = options.requireIdentity ?? false;
67
+ this.allowedTenants = options.allowedTenants;
68
+ }
69
+
70
+ async authenticate(
71
+ credentials: AuthCredentials,
72
+ context: AuthContext
73
+ ): Promise<AuthResult> {
74
+ // Extract token from credentials
75
+ const tokenStr = credentials.credentials?.token;
76
+ if (!tokenStr || typeof tokenStr !== 'string') {
77
+ return {
78
+ success: false,
79
+ error: {
80
+ code: 'invalid_credentials',
81
+ message: 'Missing or invalid agent-iam token',
82
+ },
83
+ };
84
+ }
85
+
86
+ try {
87
+ // Deserialize and verify token
88
+ const token = this.tokenService.deserialize(tokenStr);
89
+ const verification = this.tokenService.verify(token);
90
+
91
+ if (!verification.valid) {
92
+ return {
93
+ success: false,
94
+ error: {
95
+ code: 'invalid_token',
96
+ message: verification.error ?? 'Token verification failed',
97
+ },
98
+ };
99
+ }
100
+
101
+ // Check identity requirement
102
+ if (this.requireIdentity && !token.identity) {
103
+ return {
104
+ success: false,
105
+ error: {
106
+ code: 'identity_required',
107
+ message: 'Token must include identity binding',
108
+ },
109
+ };
110
+ }
111
+
112
+ // Check tenant restriction
113
+ if (this.allowedTenants && token.identity?.tenantId) {
114
+ if (!this.allowedTenants.includes(token.identity.tenantId)) {
115
+ return {
116
+ success: false,
117
+ error: {
118
+ code: 'tenant_not_allowed',
119
+ message: `Tenant ${token.identity.tenantId} not allowed`,
120
+ },
121
+ };
122
+ }
123
+ }
124
+
125
+ // Check federation (if token came from another system)
126
+ if (token.federation && token.identity?.systemId !== this.systemId) {
127
+ if (!token.federation.crossSystemAllowed) {
128
+ return {
129
+ success: false,
130
+ error: {
131
+ code: 'federation_not_allowed',
132
+ message: 'Token does not allow cross-system use',
133
+ },
134
+ };
135
+ }
136
+
137
+ if (token.federation.allowedSystems &&
138
+ !token.federation.allowedSystems.includes(this.systemId)) {
139
+ return {
140
+ success: false,
141
+ error: {
142
+ code: 'system_not_allowed',
143
+ message: `Token not allowed for system ${this.systemId}`,
144
+ },
145
+ };
146
+ }
147
+ }
148
+
149
+ // Build principal from token
150
+ const principal = this.buildPrincipal(token);
151
+
152
+ return {
153
+ success: true,
154
+ principal,
155
+ // Pass the full token for later use
156
+ metadata: { agentIamToken: token },
157
+ };
158
+ } catch (error) {
159
+ return {
160
+ success: false,
161
+ error: {
162
+ code: 'token_parse_error',
163
+ message: error instanceof Error ? error.message : 'Failed to parse token',
164
+ },
165
+ };
166
+ }
167
+ }
168
+
169
+ private buildPrincipal(token: AgentToken): AuthPrincipal {
170
+ return {
171
+ id: token.agentId,
172
+ issuer: token.identity?.systemId ?? this.systemId,
173
+ claims: {
174
+ // Core token info
175
+ agentId: token.agentId,
176
+ parentId: token.parentId,
177
+ scopes: token.scopes,
178
+ delegationDepth: token.currentDepth,
179
+
180
+ // Identity binding (if present)
181
+ ...(token.identity && {
182
+ principalId: token.identity.principalId,
183
+ principalType: token.identity.principalType,
184
+ tenantId: token.identity.tenantId,
185
+ organizationId: token.identity.organizationId,
186
+ }),
187
+
188
+ // Federation info (if present)
189
+ ...(token.federation && {
190
+ federationOrigin: token.federation.originSystem,
191
+ federationHops: token.federation.hopCount,
192
+ }),
193
+ },
194
+ expiresAt: token.expiresAt ? new Date(token.expiresAt).getTime() : undefined,
195
+ };
196
+ }
197
+ }
198
+ ```
199
+
200
+ ### 1.2 Authentication Flow
201
+
202
+ ```
203
+ Client MAP Server agent-iam
204
+ │ │ │
205
+ │── map/connect ────────────────▶│ │
206
+ │ { auth: { │ │
207
+ │ method: "x-agent-iam", │ │
208
+ │ credentials: { │ │
209
+ │ token: "<serialized>" │ │
210
+ │ } │ │
211
+ │ }} │ │
212
+ │ │ │
213
+ │ │── deserialize & verify ──────▶│
214
+ │ │ │
215
+ │ │◀── verification result ───────│
216
+ │ │ │
217
+ │ │── build principal │
218
+ │ │── extract capabilities │
219
+ │ │ │
220
+ │◀── connected ──────────────────│ │
221
+ │ { principal, capabilities } │ │
222
+ ```
223
+
224
+ ## 2. Capability Mapping
225
+
226
+ ### 2.1 Scopes to ParticipantCapabilities
227
+
228
+ Map agent-iam scopes to MAP participant capabilities:
229
+
230
+ ```typescript
231
+ // map-protocol/ts-sdk/src/server/auth/agent-iam-mapper.ts
232
+
233
+ import type { AgentToken, AgentCapabilities } from 'agent-iam';
234
+ import type { ParticipantCapabilities, AgentPermissions } from '../../types';
235
+
236
+ /**
237
+ * Configuration for mapping agent-iam tokens to MAP capabilities
238
+ */
239
+ export interface CapabilityMapperConfig {
240
+ /** Map agent-iam scopes to MAP capabilities */
241
+ scopeMappings?: {
242
+ /** Scopes that grant observation capability */
243
+ observation?: string[];
244
+ /** Scopes that grant messaging capability */
245
+ messaging?: string[];
246
+ /** Scopes that grant lifecycle (spawn/register) capability */
247
+ lifecycle?: string[];
248
+ /** Scopes that grant scope management capability */
249
+ scopes?: string[];
250
+ /** Scopes that grant federation capability */
251
+ federation?: string[];
252
+ };
253
+
254
+ /** Default capabilities when no mappings match */
255
+ defaults?: Partial<ParticipantCapabilities>;
256
+ }
257
+
258
+ const DEFAULT_SCOPE_MAPPINGS = {
259
+ observation: ['map:observe:*', 'map:*'],
260
+ messaging: ['map:message:*', 'map:*'],
261
+ lifecycle: ['map:lifecycle:*', 'map:agent:*', 'map:*'],
262
+ scopes: ['map:scope:*', 'map:*'],
263
+ federation: ['map:federation:*', 'map:*'],
264
+ };
265
+
266
+ export class AgentIAMCapabilityMapper {
267
+ private scopeMappings: Required<CapabilityMapperConfig['scopeMappings']>;
268
+ private defaults: Partial<ParticipantCapabilities>;
269
+
270
+ constructor(config?: CapabilityMapperConfig) {
271
+ this.scopeMappings = {
272
+ ...DEFAULT_SCOPE_MAPPINGS,
273
+ ...config?.scopeMappings,
274
+ };
275
+ this.defaults = config?.defaults ?? {};
276
+ }
277
+
278
+ /**
279
+ * Map an agent-iam token to MAP ParticipantCapabilities
280
+ */
281
+ mapToParticipantCapabilities(token: AgentToken): ParticipantCapabilities {
282
+ const caps = token.agentCapabilities;
283
+ const scopes = token.scopes;
284
+
285
+ return {
286
+ observation: this.mapObservation(scopes, caps),
287
+ messaging: this.mapMessaging(scopes, caps),
288
+ lifecycle: this.mapLifecycle(scopes, caps),
289
+ scopes: this.mapScopes(scopes, caps),
290
+ federation: this.mapFederation(scopes, caps, token.federation),
291
+ ...this.defaults,
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Map an agent-iam token to MAP AgentPermissions
297
+ */
298
+ mapToAgentPermissions(token: AgentToken): AgentPermissions {
299
+ const caps = token.agentCapabilities;
300
+
301
+ return {
302
+ canSee: this.mapVisibility(caps),
303
+ canMessage: this.mapMessagePermissions(caps),
304
+ acceptsFrom: this.mapAcceptsFrom(caps),
305
+ };
306
+ }
307
+
308
+ private hasScope(scopes: string[], patterns: string[]): boolean {
309
+ return patterns.some(pattern =>
310
+ scopes.some(scope => this.scopeMatches(pattern, scope))
311
+ );
312
+ }
313
+
314
+ private scopeMatches(pattern: string, scope: string): boolean {
315
+ if (pattern === scope) return true;
316
+ if (pattern === '*') return true;
317
+ if (pattern.endsWith(':*')) {
318
+ return scope.startsWith(pattern.slice(0, -1));
319
+ }
320
+ return false;
321
+ }
322
+
323
+ private mapObservation(
324
+ scopes: string[],
325
+ caps?: AgentCapabilities
326
+ ): ParticipantCapabilities['observation'] {
327
+ const canObserve = caps?.canObserve ??
328
+ this.hasScope(scopes, this.scopeMappings.observation);
329
+ return {
330
+ canObserve,
331
+ canQuery: canObserve,
332
+ };
333
+ }
334
+
335
+ private mapMessaging(
336
+ scopes: string[],
337
+ caps?: AgentCapabilities
338
+ ): ParticipantCapabilities['messaging'] {
339
+ const hasMessagingScope = this.hasScope(scopes, this.scopeMappings.messaging);
340
+ return {
341
+ canSend: caps?.canMessage ?? hasMessagingScope,
342
+ canReceive: caps?.canReceive ?? hasMessagingScope,
343
+ canBroadcast: caps?.canMessage ?? hasMessagingScope,
344
+ };
345
+ }
346
+
347
+ private mapLifecycle(
348
+ scopes: string[],
349
+ caps?: AgentCapabilities
350
+ ): ParticipantCapabilities['lifecycle'] {
351
+ const hasLifecycleScope = this.hasScope(scopes, this.scopeMappings.lifecycle);
352
+ return {
353
+ canSpawn: caps?.canSpawn ?? hasLifecycleScope,
354
+ canRegister: hasLifecycleScope,
355
+ canUnregister: hasLifecycleScope,
356
+ canSteer: hasLifecycleScope,
357
+ canStop: hasLifecycleScope,
358
+ };
359
+ }
360
+
361
+ private mapScopes(
362
+ scopes: string[],
363
+ caps?: AgentCapabilities
364
+ ): ParticipantCapabilities['scopes'] {
365
+ const hasScopesScope = this.hasScope(scopes, this.scopeMappings.scopes);
366
+ return {
367
+ canCreateScopes: caps?.canCreateScopes ?? hasScopesScope,
368
+ canManageScopes: hasScopesScope,
369
+ };
370
+ }
371
+
372
+ private mapFederation(
373
+ scopes: string[],
374
+ caps?: AgentCapabilities,
375
+ federation?: AgentToken['federation']
376
+ ): ParticipantCapabilities['federation'] {
377
+ const hasFederationScope = this.hasScope(scopes, this.scopeMappings.federation);
378
+ const canFederate = (caps?.canFederate ?? hasFederationScope) &&
379
+ (federation?.crossSystemAllowed ?? true);
380
+ return {
381
+ canFederate,
382
+ };
383
+ }
384
+
385
+ private mapVisibility(
386
+ caps?: AgentCapabilities
387
+ ): AgentPermissions['canSee'] {
388
+ // Map agent-iam visibility to MAP canSee
389
+ const visibility = caps?.visibility ?? 'public';
390
+
391
+ switch (visibility) {
392
+ case 'public':
393
+ return { agents: 'all', scopes: 'all', structure: 'full' };
394
+ case 'scope':
395
+ return { agents: 'scoped', scopes: 'member', structure: 'local' };
396
+ case 'parent-only':
397
+ return { agents: 'hierarchy', scopes: 'member', structure: 'local' };
398
+ case 'system':
399
+ return { agents: 'direct', scopes: 'member', structure: 'none' };
400
+ default:
401
+ return { agents: 'all', scopes: 'all', structure: 'full' };
402
+ }
403
+ }
404
+
405
+ private mapMessagePermissions(
406
+ caps?: AgentCapabilities
407
+ ): AgentPermissions['canMessage'] {
408
+ if (caps?.canMessage === false) {
409
+ return { agents: 'direct', scopes: 'member' };
410
+ }
411
+ return { agents: 'all', scopes: 'all' };
412
+ }
413
+
414
+ private mapAcceptsFrom(
415
+ caps?: AgentCapabilities
416
+ ): AgentPermissions['acceptsFrom'] {
417
+ if (caps?.canReceive === false) {
418
+ return { agents: 'hierarchy', clients: 'none', systems: 'none' };
419
+ }
420
+ return { agents: 'all', clients: 'all', systems: 'all' };
421
+ }
422
+ }
423
+ ```
424
+
425
+ ### 2.2 Integration with Connection Handler
426
+
427
+ ```typescript
428
+ // map-protocol/ts-sdk/src/server/handlers/connection.ts
429
+
430
+ import { AgentIAMAuthenticator } from '../auth/authenticators/agent-iam';
431
+ import { AgentIAMCapabilityMapper } from '../auth/agent-iam-mapper';
432
+ import type { AgentToken } from 'agent-iam';
433
+
434
+ export function createConnectionHandler(
435
+ options: ConnectionHandlerOptions & {
436
+ agentIamMapper?: AgentIAMCapabilityMapper;
437
+ }
438
+ ) {
439
+ const mapper = options.agentIamMapper ?? new AgentIAMCapabilityMapper();
440
+
441
+ return {
442
+ 'map/connect': async (params, context) => {
443
+ // ... authentication happens via AuthManager ...
444
+
445
+ // After successful auth, extract agent-iam token from metadata
446
+ const agentIamToken = context.authResult?.metadata?.agentIamToken as AgentToken | undefined;
447
+
448
+ if (agentIamToken) {
449
+ // Map to MAP capabilities
450
+ const participantCapabilities = mapper.mapToParticipantCapabilities(agentIamToken);
451
+ const agentPermissions = mapper.mapToAgentPermissions(agentIamToken);
452
+
453
+ // Use these capabilities for the connection
454
+ context.capabilities = participantCapabilities;
455
+ context.defaultAgentPermissions = agentPermissions;
456
+ }
457
+
458
+ // ... rest of connection handling ...
459
+ },
460
+ };
461
+ }
462
+ ```
463
+
464
+ ## 3. Agent Spawn Integration
465
+
466
+ ### 3.1 Token Delegation on Spawn
467
+
468
+ When an agent spawns a child, delegate the agent-iam token:
469
+
470
+ ```typescript
471
+ // map-protocol/ts-sdk/src/server/handlers/lifecycle.ts
472
+
473
+ import { TokenService, type DelegationRequest } from 'agent-iam';
474
+
475
+ export function createLifecycleHandler(
476
+ options: LifecycleHandlerOptions & {
477
+ tokenService?: TokenService;
478
+ }
479
+ ) {
480
+ return {
481
+ 'map/agents/spawn': async (params, context) => {
482
+ const parentToken = context.authResult?.metadata?.agentIamToken;
483
+
484
+ if (parentToken && options.tokenService) {
485
+ // Build delegation request from spawn params
486
+ const delegationRequest: DelegationRequest = {
487
+ agentId: params.agentId,
488
+ requestedScopes: params.requestedScopes ?? parentToken.scopes,
489
+ ttlMinutes: params.ttlMinutes,
490
+
491
+ // Map spawn params to agent capabilities
492
+ agentCapabilities: params.capabilities ? {
493
+ canSpawn: params.capabilities.canSpawn,
494
+ canMessage: params.capabilities.canSend,
495
+ canReceive: params.capabilities.canReceive,
496
+ visibility: params.visibility,
497
+ } : undefined,
498
+
499
+ // Inherit identity by default
500
+ inheritIdentity: params.inheritIdentity ?? true,
501
+ };
502
+
503
+ // Delegate token for child
504
+ const childToken = options.tokenService.delegate(parentToken, delegationRequest);
505
+
506
+ // Pass to spawned agent
507
+ context.childToken = childToken;
508
+ }
509
+
510
+ // ... spawn the agent ...
511
+ },
512
+ };
513
+ }
514
+ ```
515
+
516
+ ### 3.2 Token Passing to Subprocess Agents
517
+
518
+ ```typescript
519
+ // map-protocol/ts-sdk/src/server/subprocess-spawner.ts
520
+
521
+ import { AGENT_TOKEN_ENV } from 'agent-iam';
522
+
523
+ export class SubprocessSpawner {
524
+ spawn(command: string[], options: SpawnOptions & { childToken?: AgentToken }) {
525
+ const env = {
526
+ ...process.env,
527
+ ...options.env,
528
+ };
529
+
530
+ // Pass delegated token via environment
531
+ if (options.childToken) {
532
+ env[AGENT_TOKEN_ENV] = this.tokenService.serialize(options.childToken);
533
+ }
534
+
535
+ return spawn(command[0], command.slice(1), { env });
536
+ }
537
+ }
538
+ ```
539
+
540
+ ## 4. Federation Gateway
541
+
542
+ ### 4.1 Cross-System Token Handling
543
+
544
+ ```typescript
545
+ // map-protocol/ts-sdk/src/federation/agent-iam-gateway.ts
546
+
547
+ import { TokenService, type AgentToken } from 'agent-iam';
548
+
549
+ export interface FederationGatewayConfig {
550
+ /** This system's ID */
551
+ systemId: string;
552
+
553
+ /** Token service for this system */
554
+ tokenService: TokenService;
555
+
556
+ /** Trusted peer systems and their public keys */
557
+ trustedPeers: {
558
+ [systemId: string]: {
559
+ /** Token service for verifying their tokens */
560
+ tokenService: TokenService;
561
+ /** Scope mapping (their scopes → our scopes) */
562
+ scopeMapping?: Record<string, string | null>;
563
+ };
564
+ };
565
+ }
566
+
567
+ export class AgentIAMFederationGateway {
568
+ constructor(private config: FederationGatewayConfig) {}
569
+
570
+ /**
571
+ * Handle incoming federated token from another system
572
+ */
573
+ async handleIncomingToken(
574
+ sourceSystemId: string,
575
+ incomingToken: AgentToken
576
+ ): Promise<{ localToken: AgentToken; allowed: boolean; reason?: string }> {
577
+ const peer = this.config.trustedPeers[sourceSystemId];
578
+
579
+ if (!peer) {
580
+ return { localToken: incomingToken, allowed: false, reason: 'Unknown peer system' };
581
+ }
582
+
583
+ // Verify with peer's token service
584
+ const verification = peer.tokenService.verify(incomingToken);
585
+ if (!verification.valid) {
586
+ return { localToken: incomingToken, allowed: false, reason: verification.error };
587
+ }
588
+
589
+ // Check federation metadata
590
+ if (!incomingToken.federation?.crossSystemAllowed) {
591
+ return { localToken: incomingToken, allowed: false, reason: 'Token does not allow federation' };
592
+ }
593
+
594
+ if (incomingToken.federation.allowedSystems &&
595
+ !incomingToken.federation.allowedSystems.includes(this.config.systemId)) {
596
+ return { localToken: incomingToken, allowed: false, reason: 'System not in allowed list' };
597
+ }
598
+
599
+ // Check hop count
600
+ const hopCount = (incomingToken.federation.hopCount ?? 0) + 1;
601
+ if (hopCount > (incomingToken.federation.maxHops ?? 3)) {
602
+ return { localToken: incomingToken, allowed: false, reason: 'Max hops exceeded' };
603
+ }
604
+
605
+ // Translate scopes if mapping configured
606
+ const translatedScopes = this.translateScopes(
607
+ incomingToken.scopes,
608
+ peer.scopeMapping
609
+ );
610
+
611
+ // Create local token with federated identity
612
+ const localToken = this.config.tokenService.createRootToken({
613
+ agentId: `federated:${sourceSystemId}:${incomingToken.agentId}`,
614
+ scopes: translatedScopes,
615
+ constraints: incomingToken.constraints,
616
+ delegatable: incomingToken.delegatable &&
617
+ (incomingToken.federation.allowFurtherFederation ?? true),
618
+ maxDelegationDepth: Math.min(incomingToken.maxDelegationDepth, 2),
619
+ ttlDays: 1, // Short TTL for federated tokens
620
+
621
+ // Preserve identity with federation info
622
+ identity: {
623
+ systemId: this.config.systemId,
624
+ principalId: incomingToken.identity?.principalId
625
+ ? `federated:${sourceSystemId}:${incomingToken.identity.principalId}`
626
+ : undefined,
627
+ principalType: incomingToken.identity?.principalType,
628
+ tenantId: incomingToken.identity?.tenantId,
629
+ federatedFrom: {
630
+ sourceOrganization: sourceSystemId,
631
+ originalPrincipalId: incomingToken.identity?.principalId ?? incomingToken.agentId,
632
+ originalSystemId: incomingToken.federation.originSystem ?? sourceSystemId,
633
+ federatedAt: new Date().toISOString(),
634
+ },
635
+ },
636
+
637
+ // Update federation metadata
638
+ federation: {
639
+ crossSystemAllowed: incomingToken.federation.allowFurtherFederation ?? false,
640
+ originSystem: incomingToken.federation.originSystem ?? sourceSystemId,
641
+ hopCount,
642
+ maxHops: incomingToken.federation.maxHops,
643
+ allowFurtherFederation: false, // Don't allow further federation by default
644
+ },
645
+
646
+ // Preserve capabilities with attenuation
647
+ agentCapabilities: incomingToken.agentCapabilities ? {
648
+ ...incomingToken.agentCapabilities,
649
+ canFederate: false, // Federated tokens can't federate further
650
+ } : undefined,
651
+ });
652
+
653
+ return { localToken, allowed: true };
654
+ }
655
+
656
+ /**
657
+ * Prepare a token for sending to another system
658
+ */
659
+ prepareOutgoingToken(
660
+ token: AgentToken,
661
+ targetSystemId: string
662
+ ): { serialized: string; allowed: boolean; reason?: string } {
663
+ if (!token.federation?.crossSystemAllowed) {
664
+ return { serialized: '', allowed: false, reason: 'Token does not allow federation' };
665
+ }
666
+
667
+ if (token.federation.allowedSystems &&
668
+ !token.federation.allowedSystems.includes(targetSystemId)) {
669
+ return { serialized: '', allowed: false, reason: 'Target system not allowed' };
670
+ }
671
+
672
+ return {
673
+ serialized: this.config.tokenService.serialize(token),
674
+ allowed: true,
675
+ };
676
+ }
677
+
678
+ private translateScopes(
679
+ scopes: string[],
680
+ mapping?: Record<string, string | null>
681
+ ): string[] {
682
+ if (!mapping) return scopes;
683
+
684
+ return scopes
685
+ .map(scope => {
686
+ const mapped = mapping[scope];
687
+ if (mapped === null) return null; // Blocked scope
688
+ return mapped ?? scope; // Use mapping or original
689
+ })
690
+ .filter((s): s is string => s !== null);
691
+ }
692
+ }
693
+ ```
694
+
695
+ ## 5. Server Setup
696
+
697
+ ### 5.1 Complete Server Configuration
698
+
699
+ ```typescript
700
+ // Example: Setting up a MAP server with agent-iam authentication
701
+
702
+ import { TokenService, generateSecret, Broker } from 'agent-iam';
703
+ import { createMAPServer } from 'map-protocol/server';
704
+ import { AgentIAMAuthenticator } from 'map-protocol/server/auth/authenticators/agent-iam';
705
+ import { AgentIAMCapabilityMapper } from 'map-protocol/server/auth/agent-iam-mapper';
706
+
707
+ // Initialize agent-iam
708
+ const secret = generateSecret(); // Or load from config
709
+ const tokenService = new TokenService(secret);
710
+ const broker = new Broker();
711
+
712
+ // Create the authenticator
713
+ const agentIamAuth = new AgentIAMAuthenticator({
714
+ tokenService,
715
+ systemId: 'my-map-system',
716
+ requireIdentity: true, // Require identity for audit
717
+ allowedTenants: ['acme-corp', 'partner-inc'], // Multi-tenant
718
+ });
719
+
720
+ // Create the capability mapper
721
+ const capabilityMapper = new AgentIAMCapabilityMapper({
722
+ scopeMappings: {
723
+ observation: ['map:observe:*', 'system:*'],
724
+ messaging: ['map:message:*', 'agent:*'],
725
+ lifecycle: ['map:lifecycle:*', 'agent:spawn:*'],
726
+ },
727
+ defaults: {
728
+ streaming: {
729
+ supportsAck: true,
730
+ supportsFlowControl: false,
731
+ supportsPause: false,
732
+ },
733
+ },
734
+ });
735
+
736
+ // Create the server
737
+ const server = createMAPServer({
738
+ port: 8080,
739
+
740
+ auth: {
741
+ required: true,
742
+ authenticators: [agentIamAuth],
743
+ bypassForTransports: { stdio: true }, // Trust local subprocesses
744
+ },
745
+
746
+ agentIamMapper: capabilityMapper,
747
+ tokenService,
748
+
749
+ // Federation config
750
+ federation: {
751
+ enabled: true,
752
+ systemId: 'my-map-system',
753
+ trustedPeers: {
754
+ 'partner-system': {
755
+ publicKey: '...', // For verifying their tokens
756
+ scopeMapping: {
757
+ 'partner:resource:read': 'shared:resource:read',
758
+ 'partner:admin:*': null, // Block admin scopes
759
+ },
760
+ },
761
+ },
762
+ },
763
+ });
764
+
765
+ await server.start();
766
+ ```
767
+
768
+ ### 5.2 Client Connection with agent-iam Token
769
+
770
+ ```typescript
771
+ // Example: Connecting to MAP server with agent-iam token
772
+
773
+ import { Broker } from 'agent-iam';
774
+ import { createMAPClient } from 'map-protocol/client';
775
+
776
+ // Get or create a token
777
+ const broker = new Broker();
778
+ const token = broker.createRootToken({
779
+ agentId: 'my-agent',
780
+ scopes: ['map:*', 'github:repo:read'],
781
+ identity: {
782
+ systemId: 'my-map-system',
783
+ principalId: 'user@acme-corp.com',
784
+ principalType: 'human',
785
+ tenantId: 'acme-corp',
786
+ },
787
+ federation: {
788
+ crossSystemAllowed: true,
789
+ maxHops: 2,
790
+ },
791
+ agentCapabilities: {
792
+ canSpawn: true,
793
+ canMessage: true,
794
+ canReceive: true,
795
+ visibility: 'public',
796
+ },
797
+ ttlDays: 1,
798
+ });
799
+
800
+ // Connect to MAP server
801
+ const client = await createMAPClient({
802
+ url: 'ws://localhost:8080',
803
+ auth: {
804
+ method: 'x-agent-iam',
805
+ credentials: {
806
+ token: broker.serializeToken(token),
807
+ },
808
+ },
809
+ });
810
+
811
+ // Now use the client...
812
+ await client.send({
813
+ to: { agent: 'other-agent' },
814
+ payload: { type: 'hello' },
815
+ });
816
+ ```
817
+
818
+ ## 6. Type Mappings Summary
819
+
820
+ | agent-iam | MAP | Notes |
821
+ |-----------|-----|-------|
822
+ | `AgentToken.agentId` | `AuthPrincipal.id` | Direct mapping |
823
+ | `AgentToken.scopes` | `ParticipantCapabilities.*` | Via mapper config |
824
+ | `AgentToken.identity.principalId` | `AuthPrincipal.claims.principalId` | In claims |
825
+ | `AgentToken.identity.systemId` | `AuthPrincipal.issuer` | Token issuer |
826
+ | `AgentToken.identity.tenantId` | `AuthPrincipal.claims.tenantId` | For multi-tenant |
827
+ | `AgentToken.agentCapabilities.canSpawn` | `ParticipantCapabilities.lifecycle.canSpawn` | Direct |
828
+ | `AgentToken.agentCapabilities.canMessage` | `ParticipantCapabilities.messaging.canSend` | Direct |
829
+ | `AgentToken.agentCapabilities.visibility` | `AgentPermissions.canSee` | Mapped |
830
+ | `AgentToken.federation.crossSystemAllowed` | Gateway routing decision | Federation control |
831
+ | `AgentToken.expiresAt` | `AuthPrincipal.expiresAt` | Token expiry |
832
+
833
+ ## 7. Security Considerations
834
+
835
+ ### 7.1 Token Validation
836
+
837
+ - Always verify token signature before trusting any claims
838
+ - Check token expiration before each operation
839
+ - Validate scopes against requested operations
840
+ - Verify federation metadata for cross-system requests
841
+
842
+ ### 7.2 Federation Security
843
+
844
+ - Maintain separate signing keys per system (never share)
845
+ - Use scope mapping to restrict federated tokens
846
+ - Limit hop count to prevent routing loops
847
+ - Log all federation events for audit
848
+
849
+ ### 7.3 Identity Binding
850
+
851
+ - Require identity for audit-sensitive operations
852
+ - Preserve identity through delegation chain
853
+ - Include external auth info when available
854
+ - Log principal ID with all security-relevant events
855
+
856
+ ## 8. Implementation Checklist
857
+
858
+ ### MAP Server Side
859
+
860
+ - [ ] Implement `AgentIAMAuthenticator`
861
+ - [ ] Implement `AgentIAMCapabilityMapper`
862
+ - [ ] Update connection handler to use mapper
863
+ - [ ] Update spawn handler for token delegation
864
+ - [ ] Implement federation gateway
865
+ - [ ] Add configuration options
866
+ - [ ] Write tests
867
+
868
+ ### Integration Tests
869
+
870
+ - [ ] Token verification flow
871
+ - [ ] Capability mapping accuracy
872
+ - [ ] Delegation on spawn
873
+ - [ ] Federation gateway (same trust domain)
874
+ - [ ] Federation gateway (cross-org)
875
+ - [ ] Token expiration handling
876
+ - [ ] Identity inheritance
877
+