claude-flow 2.7.33 → 2.7.34
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.
- package/.claude/settings.local.json +9 -2
- package/.claude/skills/agentic-jujutsu/SKILL.md +1 -1
- package/CHANGELOG.md +75 -0
- package/bin/claude-flow +1 -1
- package/dist/src/cli/commands/mcp.js +61 -7
- package/dist/src/cli/commands/mcp.js.map +1 -1
- package/dist/src/cli/help-formatter.js +5 -0
- package/dist/src/cli/simple-commands/init/agent-copier.js +9 -3
- package/dist/src/cli/simple-commands/init/agent-copier.js.map +1 -1
- package/dist/src/core/version.js +1 -1
- package/dist/src/mcp/async/job-manager-mcp25.js +240 -0
- package/dist/src/mcp/async/job-manager-mcp25.js.map +1 -0
- package/dist/src/mcp/index.js +8 -0
- package/dist/src/mcp/index.js.map +1 -1
- package/dist/src/mcp/protocol/version-negotiation.js +182 -0
- package/dist/src/mcp/protocol/version-negotiation.js.map +1 -0
- package/dist/src/mcp/registry/mcp-registry-client-2025.js +210 -0
- package/dist/src/mcp/registry/mcp-registry-client-2025.js.map +1 -0
- package/dist/src/mcp/server-factory.js +189 -0
- package/dist/src/mcp/server-factory.js.map +1 -0
- package/dist/src/mcp/server-mcp-2025.js +283 -0
- package/dist/src/mcp/server-mcp-2025.js.map +1 -0
- package/dist/src/mcp/tool-registry-progressive.js +319 -0
- package/dist/src/mcp/tool-registry-progressive.js.map +1 -0
- package/dist/src/mcp/tools/_template.js +62 -0
- package/dist/src/mcp/tools/_template.js.map +1 -0
- package/dist/src/mcp/tools/loader.js +228 -0
- package/dist/src/mcp/tools/loader.js.map +1 -0
- package/dist/src/mcp/tools/system/search.js +224 -0
- package/dist/src/mcp/tools/system/search.js.map +1 -0
- package/dist/src/mcp/tools/system/status.js +168 -0
- package/dist/src/mcp/tools/system/status.js.map +1 -0
- package/dist/src/mcp/validation/schema-validator-2025.js +198 -0
- package/dist/src/mcp/validation/schema-validator-2025.js.map +1 -0
- package/docs/.claude-flow/metrics/performance.json +3 -3
- package/docs/.claude-flow/metrics/task-metrics.json +3 -3
- package/docs/.github-release-issue-v2.7.33.md +488 -0
- package/docs/AGENTDB_BRANCH_MERGE_VERIFICATION.md +436 -0
- package/docs/BRANCH_REVIEW_SUMMARY.md +439 -0
- package/docs/DEEP_CODE_REVIEW_v2.7.33.md +1159 -0
- package/docs/MCP_2025_FEATURE_CONFIRMATION.md +698 -0
- package/docs/NPM_PUBLISH_GUIDE_v2.7.33.md +628 -0
- package/docs/REGRESSION_TEST_REPORT_v2.7.33.md +397 -0
- package/docs/RELEASE_NOTES_v2.7.33.md +618 -0
- package/docs/RELEASE_READINESS_SUMMARY.md +377 -0
- package/docs/RELEASE_SUMMARY_v2.7.33.md +456 -0
- package/docs/agentic-flow-agentdb-mcp-integration.md +1198 -0
- package/docs/mcp-2025-implementation-summary.md +459 -0
- package/docs/mcp-spec-2025-implementation-plan.md +1330 -0
- package/docs/phase-1-2-implementation-summary.md +676 -0
- package/docs/regression-analysis-phase-1-2.md +555 -0
- package/package.json +5 -2
- package/src/cli/commands/mcp.ts +86 -9
- package/src/cli/simple-commands/init/agent-copier.js +10 -5
- package/src/mcp/async/job-manager-mcp25.ts +456 -0
- package/src/mcp/index.ts +60 -0
- package/src/mcp/protocol/version-negotiation.ts +329 -0
- package/src/mcp/registry/mcp-registry-client-2025.ts +334 -0
- package/src/mcp/server-factory.ts +426 -0
- package/src/mcp/server-mcp-2025.ts +507 -0
- package/src/mcp/tool-registry-progressive.ts +539 -0
- package/src/mcp/tools/_template.ts +174 -0
- package/src/mcp/tools/loader.ts +362 -0
- package/src/mcp/tools/system/search.ts +276 -0
- package/src/mcp/tools/system/status.ts +206 -0
- package/src/mcp/validation/schema-validator-2025.ts +294 -0
- package/docs/AGENTDB_V1.6.1_DEEP_REVIEW.md +0 -386
- package/docs/AGENT_FOLDER_STRUCTURE_FIX.md +0 -192
- package/docs/RECENT_RELEASES_SUMMARY.md +0 -375
- package/docs/V2.7.31_RELEASE_NOTES.md +0 -375
- /package/.claude/agents/analysis/{analyze-code-quality.md → code-review/analyze-code-quality.md} +0 -0
- /package/.claude/agents/architecture/{arch-system-design.md → system-design/arch-system-design.md} +0 -0
- /package/.claude/agents/data/{data-ml-model.md → ml/data-ml-model.md} +0 -0
- /package/.claude/agents/development/{dev-backend-api.md → backend/dev-backend-api.md} +0 -0
- /package/.claude/agents/devops/{ops-cicd-github.md → ci-cd/ops-cicd-github.md} +0 -0
- /package/.claude/agents/documentation/{docs-api-openapi.md → api-docs/docs-api-openapi.md} +0 -0
- /package/.claude/agents/specialized/{spec-mobile-react-native.md → mobile/spec-mobile-react-native.md} +0 -0
- /package/.claude/agents/testing/{tdd-london-swarm.md → unit/tdd-london-swarm.md} +0 -0
- /package/.claude/agents/testing/{production-validator.md → validation/production-validator.md} +0 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 2025-11 Version Negotiation Protocol
|
|
3
|
+
*
|
|
4
|
+
* Implements version negotiation and capability exchange
|
|
5
|
+
* per MCP 2025-11 specification
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ILogger } from '../../interfaces/logger.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* MCP version in YYYY-MM format
|
|
12
|
+
*/
|
|
13
|
+
export type MCPVersion = '2025-11' | '2024-11' | '2024-10';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* MCP capability flags
|
|
17
|
+
*/
|
|
18
|
+
export type MCPCapability =
|
|
19
|
+
| 'async' // Job handles, poll/resume
|
|
20
|
+
| 'registry' // Self-registration
|
|
21
|
+
| 'code_exec' // External code execution
|
|
22
|
+
| 'stream' // Streaming output
|
|
23
|
+
| 'sandbox' // Isolated execution
|
|
24
|
+
| 'schema_ref'; // Shared schema references
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* MCP handshake request/response
|
|
28
|
+
*/
|
|
29
|
+
export interface MCPHandshake {
|
|
30
|
+
mcp_version: MCPVersion;
|
|
31
|
+
client_id?: string;
|
|
32
|
+
server_id?: string;
|
|
33
|
+
transport: 'stdio' | 'http' | 'ws';
|
|
34
|
+
capabilities: MCPCapability[];
|
|
35
|
+
metadata?: {
|
|
36
|
+
name?: string;
|
|
37
|
+
version?: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Version negotiation result
|
|
44
|
+
*/
|
|
45
|
+
export interface NegotiationResult {
|
|
46
|
+
success: boolean;
|
|
47
|
+
agreed_version: MCPVersion;
|
|
48
|
+
agreed_capabilities: MCPCapability[];
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Version negotiation errors
|
|
54
|
+
*/
|
|
55
|
+
export class VersionNegotiationError extends Error {
|
|
56
|
+
constructor(
|
|
57
|
+
message: string,
|
|
58
|
+
public code: 'VERSION_MISMATCH' | 'UNSUPPORTED_CAPABILITY' | 'INVALID_HANDSHAKE'
|
|
59
|
+
) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.name = 'VersionNegotiationError';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* MCP Version Negotiator
|
|
67
|
+
*
|
|
68
|
+
* Handles version compatibility checking and capability negotiation
|
|
69
|
+
*/
|
|
70
|
+
export class VersionNegotiator {
|
|
71
|
+
// Supported versions in order of preference
|
|
72
|
+
private supportedVersions: MCPVersion[] = ['2025-11', '2024-11'];
|
|
73
|
+
|
|
74
|
+
// Current server version
|
|
75
|
+
private serverVersion: MCPVersion = '2025-11';
|
|
76
|
+
|
|
77
|
+
// Server capabilities
|
|
78
|
+
private serverCapabilities: MCPCapability[] = [
|
|
79
|
+
'async',
|
|
80
|
+
'registry',
|
|
81
|
+
'code_exec',
|
|
82
|
+
'stream',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
constructor(private logger: ILogger) {}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Negotiate version with client
|
|
89
|
+
*/
|
|
90
|
+
async negotiate(clientHandshake: MCPHandshake): Promise<NegotiationResult> {
|
|
91
|
+
this.logger.info('Starting version negotiation', {
|
|
92
|
+
clientVersion: clientHandshake.mcp_version,
|
|
93
|
+
serverVersion: this.serverVersion,
|
|
94
|
+
clientCapabilities: clientHandshake.capabilities,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Validate handshake structure
|
|
98
|
+
if (!this.isValidHandshake(clientHandshake)) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
agreed_version: this.serverVersion,
|
|
102
|
+
agreed_capabilities: [],
|
|
103
|
+
error: 'Invalid handshake structure',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check version compatibility
|
|
108
|
+
const versionResult = this.checkVersionCompatibility(clientHandshake.mcp_version);
|
|
109
|
+
if (!versionResult.compatible) {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
agreed_version: this.serverVersion,
|
|
113
|
+
agreed_capabilities: [],
|
|
114
|
+
error: versionResult.error,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Negotiate capabilities
|
|
119
|
+
const agreedCapabilities = this.negotiateCapabilities(
|
|
120
|
+
clientHandshake.capabilities
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
this.logger.info('Version negotiation successful', {
|
|
124
|
+
agreedVersion: versionResult.version,
|
|
125
|
+
agreedCapabilities,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
success: true,
|
|
130
|
+
agreed_version: versionResult.version,
|
|
131
|
+
agreed_capabilities: agreedCapabilities,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if handshake is valid
|
|
137
|
+
*/
|
|
138
|
+
private isValidHandshake(handshake: MCPHandshake): boolean {
|
|
139
|
+
return !!(
|
|
140
|
+
handshake.mcp_version &&
|
|
141
|
+
handshake.transport &&
|
|
142
|
+
Array.isArray(handshake.capabilities)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check version compatibility
|
|
148
|
+
*/
|
|
149
|
+
private checkVersionCompatibility(clientVersion: MCPVersion): {
|
|
150
|
+
compatible: boolean;
|
|
151
|
+
version: MCPVersion;
|
|
152
|
+
error?: string;
|
|
153
|
+
} {
|
|
154
|
+
// Exact match - best case
|
|
155
|
+
if (clientVersion === this.serverVersion) {
|
|
156
|
+
return { compatible: true, version: clientVersion };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check if client version is supported
|
|
160
|
+
if (this.supportedVersions.includes(clientVersion)) {
|
|
161
|
+
this.logger.warn('Client using older version, but compatible', {
|
|
162
|
+
clientVersion,
|
|
163
|
+
serverVersion: this.serverVersion,
|
|
164
|
+
});
|
|
165
|
+
return { compatible: true, version: clientVersion };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check version gap
|
|
169
|
+
const clientDate = this.parseVersion(clientVersion);
|
|
170
|
+
const serverDate = this.parseVersion(this.serverVersion);
|
|
171
|
+
const monthsDiff = this.getMonthsDifference(clientDate, serverDate);
|
|
172
|
+
|
|
173
|
+
// Reject if more than 1 cycle (1 month) difference
|
|
174
|
+
if (Math.abs(monthsDiff) > 1) {
|
|
175
|
+
return {
|
|
176
|
+
compatible: false,
|
|
177
|
+
version: this.serverVersion,
|
|
178
|
+
error: `Version mismatch: client ${clientVersion}, server ${this.serverVersion}. Difference exceeds 1 cycle.`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Accept with warning for small differences
|
|
183
|
+
this.logger.warn('Version close enough, accepting', {
|
|
184
|
+
clientVersion,
|
|
185
|
+
serverVersion: this.serverVersion,
|
|
186
|
+
monthsDiff,
|
|
187
|
+
});
|
|
188
|
+
return { compatible: true, version: this.serverVersion };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Negotiate capabilities between client and server
|
|
193
|
+
*/
|
|
194
|
+
private negotiateCapabilities(
|
|
195
|
+
clientCapabilities: MCPCapability[]
|
|
196
|
+
): MCPCapability[] {
|
|
197
|
+
// Return intersection of client and server capabilities
|
|
198
|
+
return clientCapabilities.filter(cap =>
|
|
199
|
+
this.serverCapabilities.includes(cap)
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Parse version string to date
|
|
205
|
+
*/
|
|
206
|
+
private parseVersion(version: MCPVersion): Date {
|
|
207
|
+
const [year, month] = version.split('-').map(Number);
|
|
208
|
+
return new Date(year, month - 1, 1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Calculate months difference between dates
|
|
213
|
+
*/
|
|
214
|
+
private getMonthsDifference(date1: Date, date2: Date): number {
|
|
215
|
+
const months = (date2.getFullYear() - date1.getFullYear()) * 12;
|
|
216
|
+
return months + date2.getMonth() - date1.getMonth();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Create server handshake response
|
|
221
|
+
*/
|
|
222
|
+
createServerHandshake(
|
|
223
|
+
serverId: string,
|
|
224
|
+
transport: 'stdio' | 'http' | 'ws',
|
|
225
|
+
metadata?: MCPHandshake['metadata']
|
|
226
|
+
): MCPHandshake {
|
|
227
|
+
return {
|
|
228
|
+
mcp_version: this.serverVersion,
|
|
229
|
+
server_id: serverId,
|
|
230
|
+
transport,
|
|
231
|
+
capabilities: this.serverCapabilities,
|
|
232
|
+
metadata,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get current server version
|
|
238
|
+
*/
|
|
239
|
+
getServerVersion(): MCPVersion {
|
|
240
|
+
return this.serverVersion;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get server capabilities
|
|
245
|
+
*/
|
|
246
|
+
getServerCapabilities(): MCPCapability[] {
|
|
247
|
+
return [...this.serverCapabilities];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Check if capability is supported
|
|
252
|
+
*/
|
|
253
|
+
hasCapability(capability: MCPCapability): boolean {
|
|
254
|
+
return this.serverCapabilities.includes(capability);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Add capability (dynamic capability registration)
|
|
259
|
+
*/
|
|
260
|
+
addCapability(capability: MCPCapability): void {
|
|
261
|
+
if (!this.serverCapabilities.includes(capability)) {
|
|
262
|
+
this.serverCapabilities.push(capability);
|
|
263
|
+
this.logger.info('Capability added', { capability });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Remove capability
|
|
269
|
+
*/
|
|
270
|
+
removeCapability(capability: MCPCapability): void {
|
|
271
|
+
const index = this.serverCapabilities.indexOf(capability);
|
|
272
|
+
if (index > -1) {
|
|
273
|
+
this.serverCapabilities.splice(index, 1);
|
|
274
|
+
this.logger.info('Capability removed', { capability });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Backward compatibility helper
|
|
281
|
+
* Converts legacy requests to MCP 2025-11 format
|
|
282
|
+
*/
|
|
283
|
+
export class BackwardCompatibilityAdapter {
|
|
284
|
+
constructor(private logger: ILogger) {}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Detect if request is legacy format
|
|
288
|
+
*/
|
|
289
|
+
isLegacyRequest(request: any): boolean {
|
|
290
|
+
// Legacy requests don't have mcp_version field
|
|
291
|
+
return !request.mcp_version || request.version;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Convert legacy request to MCP 2025-11 format
|
|
296
|
+
*/
|
|
297
|
+
convertToModern(legacyRequest: any): MCPHandshake {
|
|
298
|
+
this.logger.info('Converting legacy request to MCP 2025-11 format');
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
mcp_version: '2025-11',
|
|
302
|
+
client_id: legacyRequest.clientId || 'legacy-client',
|
|
303
|
+
transport: legacyRequest.transport || 'stdio',
|
|
304
|
+
capabilities: legacyRequest.capabilities || [],
|
|
305
|
+
metadata: {
|
|
306
|
+
name: legacyRequest.name || 'Legacy Client',
|
|
307
|
+
version: legacyRequest.version || '1.0.0',
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Convert modern response to legacy format if needed
|
|
314
|
+
*/
|
|
315
|
+
convertToLegacy(modernResponse: any, requestedLegacy: boolean): any {
|
|
316
|
+
if (!requestedLegacy) {
|
|
317
|
+
return modernResponse;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.logger.info('Converting response to legacy format');
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
version: modernResponse.mcp_version,
|
|
324
|
+
serverId: modernResponse.server_id,
|
|
325
|
+
capabilities: modernResponse.capabilities,
|
|
326
|
+
...modernResponse,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 2025-11 Registry Client
|
|
3
|
+
*
|
|
4
|
+
* Implements server registration and health reporting
|
|
5
|
+
* per MCP 2025-11 specification
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ILogger } from '../../interfaces/logger.js';
|
|
9
|
+
import type { MCPVersion, MCPCapability } from '../protocol/version-negotiation.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* MCP Registry entry (2025-11 format)
|
|
13
|
+
*/
|
|
14
|
+
export interface MCPRegistryEntry {
|
|
15
|
+
server_id: string;
|
|
16
|
+
version: MCPVersion;
|
|
17
|
+
endpoint: string;
|
|
18
|
+
tools: string[];
|
|
19
|
+
auth: 'bearer' | 'mutual_tls' | 'none';
|
|
20
|
+
capabilities: MCPCapability[];
|
|
21
|
+
metadata: {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
author: string;
|
|
25
|
+
homepage?: string;
|
|
26
|
+
documentation?: string;
|
|
27
|
+
repository?: string;
|
|
28
|
+
};
|
|
29
|
+
health: {
|
|
30
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
31
|
+
last_check: string; // ISO 8601
|
|
32
|
+
latency_ms: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Registry search query
|
|
38
|
+
*/
|
|
39
|
+
export interface RegistrySearchQuery {
|
|
40
|
+
category?: string;
|
|
41
|
+
tags?: string[];
|
|
42
|
+
capabilities?: MCPCapability[];
|
|
43
|
+
limit?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Registry configuration
|
|
48
|
+
*/
|
|
49
|
+
export interface RegistryConfig {
|
|
50
|
+
enabled: boolean;
|
|
51
|
+
registryUrl?: string;
|
|
52
|
+
apiKey?: string;
|
|
53
|
+
serverId: string;
|
|
54
|
+
serverEndpoint: string;
|
|
55
|
+
authMethod: 'bearer' | 'mutual_tls' | 'none';
|
|
56
|
+
metadata: MCPRegistryEntry['metadata'];
|
|
57
|
+
healthCheckInterval?: number; // milliseconds
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* MCP 2025-11 Registry Client
|
|
62
|
+
*/
|
|
63
|
+
export class MCPRegistryClient {
|
|
64
|
+
private registryUrl: string;
|
|
65
|
+
private healthCheckInterval?: NodeJS.Timeout;
|
|
66
|
+
|
|
67
|
+
constructor(
|
|
68
|
+
private config: RegistryConfig,
|
|
69
|
+
private logger: ILogger,
|
|
70
|
+
private getTools: () => Promise<string[]>,
|
|
71
|
+
private getCapabilities: () => MCPCapability[],
|
|
72
|
+
private getHealth: () => Promise<{ status: 'healthy' | 'degraded' | 'unhealthy'; latency_ms: number }>
|
|
73
|
+
) {
|
|
74
|
+
this.registryUrl = config.registryUrl || 'https://registry.mcp.anthropic.com/api/v1';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Register server with MCP Registry
|
|
79
|
+
*/
|
|
80
|
+
async register(): Promise<void> {
|
|
81
|
+
if (!this.config.enabled) {
|
|
82
|
+
this.logger.info('Registry registration disabled');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const entry = await this.buildRegistryEntry();
|
|
88
|
+
|
|
89
|
+
this.logger.info('Registering server with MCP Registry', {
|
|
90
|
+
server_id: entry.server_id,
|
|
91
|
+
endpoint: entry.endpoint,
|
|
92
|
+
capabilities: entry.capabilities,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const response = await fetch(`${this.registryUrl}/servers`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: {
|
|
98
|
+
'Content-Type': 'application/json',
|
|
99
|
+
...(this.config.apiKey && {
|
|
100
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
101
|
+
}),
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify(entry),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const error = await response.text();
|
|
108
|
+
throw new Error(`Registration failed: ${response.status} - ${error}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = await response.json();
|
|
112
|
+
this.logger.info('Server registered successfully', {
|
|
113
|
+
server_id: result.server_id,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Start periodic health reporting
|
|
117
|
+
this.startHealthReporting();
|
|
118
|
+
} catch (error) {
|
|
119
|
+
this.logger.error('Failed to register with MCP Registry', {
|
|
120
|
+
error: error instanceof Error ? error.message : String(error),
|
|
121
|
+
});
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Update server metadata in registry
|
|
128
|
+
*/
|
|
129
|
+
async updateMetadata(updates: Partial<MCPRegistryEntry>): Promise<void> {
|
|
130
|
+
if (!this.config.enabled) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
this.logger.info('Updating server metadata in registry', {
|
|
136
|
+
server_id: this.config.serverId,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const response = await fetch(
|
|
140
|
+
`${this.registryUrl}/servers/${this.config.serverId}`,
|
|
141
|
+
{
|
|
142
|
+
method: 'PATCH',
|
|
143
|
+
headers: {
|
|
144
|
+
'Content-Type': 'application/json',
|
|
145
|
+
...(this.config.apiKey && {
|
|
146
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
body: JSON.stringify(updates),
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
const error = await response.text();
|
|
155
|
+
throw new Error(`Update failed: ${response.status} - ${error}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.logger.info('Server metadata updated successfully');
|
|
159
|
+
} catch (error) {
|
|
160
|
+
this.logger.error('Failed to update metadata', {
|
|
161
|
+
error: error instanceof Error ? error.message : String(error),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Report health status to registry
|
|
168
|
+
*/
|
|
169
|
+
async reportHealth(): Promise<void> {
|
|
170
|
+
if (!this.config.enabled) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const health = await this.getHealth();
|
|
176
|
+
|
|
177
|
+
const response = await fetch(
|
|
178
|
+
`${this.registryUrl}/servers/${this.config.serverId}/health`,
|
|
179
|
+
{
|
|
180
|
+
method: 'POST',
|
|
181
|
+
headers: {
|
|
182
|
+
'Content-Type': 'application/json',
|
|
183
|
+
...(this.config.apiKey && {
|
|
184
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
185
|
+
}),
|
|
186
|
+
},
|
|
187
|
+
body: JSON.stringify({
|
|
188
|
+
status: health.status,
|
|
189
|
+
last_check: new Date().toISOString(),
|
|
190
|
+
latency_ms: health.latency_ms,
|
|
191
|
+
}),
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
this.logger.warn('Health report failed', {
|
|
197
|
+
status: response.status,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
this.logger.error('Failed to report health', {
|
|
202
|
+
error: error instanceof Error ? error.message : String(error),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Search for servers in registry
|
|
209
|
+
*/
|
|
210
|
+
async searchServers(query: RegistrySearchQuery): Promise<MCPRegistryEntry[]> {
|
|
211
|
+
try {
|
|
212
|
+
const params = new URLSearchParams();
|
|
213
|
+
|
|
214
|
+
if (query.category) {
|
|
215
|
+
params.set('category', query.category);
|
|
216
|
+
}
|
|
217
|
+
if (query.tags) {
|
|
218
|
+
params.set('tags', query.tags.join(','));
|
|
219
|
+
}
|
|
220
|
+
if (query.capabilities) {
|
|
221
|
+
params.set('capabilities', query.capabilities.join(','));
|
|
222
|
+
}
|
|
223
|
+
if (query.limit) {
|
|
224
|
+
params.set('limit', query.limit.toString());
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const response = await fetch(`${this.registryUrl}/servers?${params}`);
|
|
228
|
+
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
throw new Error(`Search failed: ${response.status}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const results = await response.json();
|
|
234
|
+
return results.servers || [];
|
|
235
|
+
} catch (error) {
|
|
236
|
+
this.logger.error('Failed to search servers', {
|
|
237
|
+
error: error instanceof Error ? error.message : String(error),
|
|
238
|
+
});
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Unregister from registry
|
|
245
|
+
*/
|
|
246
|
+
async unregister(): Promise<void> {
|
|
247
|
+
if (!this.config.enabled) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Stop health reporting
|
|
252
|
+
this.stopHealthReporting();
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
this.logger.info('Unregistering from MCP Registry', {
|
|
256
|
+
server_id: this.config.serverId,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const response = await fetch(
|
|
260
|
+
`${this.registryUrl}/servers/${this.config.serverId}`,
|
|
261
|
+
{
|
|
262
|
+
method: 'DELETE',
|
|
263
|
+
headers: {
|
|
264
|
+
...(this.config.apiKey && {
|
|
265
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
266
|
+
}),
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (!response.ok) {
|
|
272
|
+
this.logger.warn('Unregistration failed', {
|
|
273
|
+
status: response.status,
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
this.logger.info('Server unregistered successfully');
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
this.logger.error('Failed to unregister', {
|
|
280
|
+
error: error instanceof Error ? error.message : String(error),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Build registry entry from current server state
|
|
287
|
+
*/
|
|
288
|
+
private async buildRegistryEntry(): Promise<MCPRegistryEntry> {
|
|
289
|
+
const tools = await this.getTools();
|
|
290
|
+
const capabilities = this.getCapabilities();
|
|
291
|
+
const health = await this.getHealth();
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
server_id: this.config.serverId,
|
|
295
|
+
version: '2025-11',
|
|
296
|
+
endpoint: this.config.serverEndpoint,
|
|
297
|
+
tools,
|
|
298
|
+
auth: this.config.authMethod,
|
|
299
|
+
capabilities,
|
|
300
|
+
metadata: this.config.metadata,
|
|
301
|
+
health: {
|
|
302
|
+
status: health.status,
|
|
303
|
+
last_check: new Date().toISOString(),
|
|
304
|
+
latency_ms: health.latency_ms,
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Start periodic health reporting
|
|
311
|
+
*/
|
|
312
|
+
private startHealthReporting(): void {
|
|
313
|
+
const interval = this.config.healthCheckInterval || 60000; // Default: 60 seconds
|
|
314
|
+
|
|
315
|
+
this.healthCheckInterval = setInterval(async () => {
|
|
316
|
+
await this.reportHealth();
|
|
317
|
+
}, interval);
|
|
318
|
+
|
|
319
|
+
this.logger.info('Health reporting started', {
|
|
320
|
+
interval_ms: interval,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Stop health reporting
|
|
326
|
+
*/
|
|
327
|
+
private stopHealthReporting(): void {
|
|
328
|
+
if (this.healthCheckInterval) {
|
|
329
|
+
clearInterval(this.healthCheckInterval);
|
|
330
|
+
this.healthCheckInterval = undefined;
|
|
331
|
+
this.logger.info('Health reporting stopped');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|