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
package/src/cli/commands/mcp.ts
CHANGED
|
@@ -6,10 +6,17 @@ import { Command } from '@cliffy/command';
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { logger } from '../../core/logger.js';
|
|
8
8
|
import { configManager } from '../../core/config.js';
|
|
9
|
-
import { MCPServer } from '../../mcp/server.js';
|
|
9
|
+
import { MCPServer, type IMCPServer } from '../../mcp/server.js';
|
|
10
10
|
import { eventBus } from '../../core/event-bus.js';
|
|
11
|
+
import {
|
|
12
|
+
createMCPServer,
|
|
13
|
+
isMCP2025Available,
|
|
14
|
+
getServerCapabilities,
|
|
15
|
+
type ExtendedMCPConfig,
|
|
16
|
+
} from '../../mcp/server-factory.js';
|
|
17
|
+
import type { MCP2025Server } from '../../mcp/server-mcp-2025.js';
|
|
11
18
|
|
|
12
|
-
let mcpServer:
|
|
19
|
+
let mcpServer: IMCPServer | MCP2025Server | null = null;
|
|
13
20
|
|
|
14
21
|
export const mcpCommand = new Command()
|
|
15
22
|
.description('Manage MCP server and tools')
|
|
@@ -32,29 +39,95 @@ export const mcpCommand = new Command()
|
|
|
32
39
|
.option('--transport <transport:string>', 'Transport type (stdio, http)', {
|
|
33
40
|
default: 'stdio',
|
|
34
41
|
})
|
|
42
|
+
.option('--mcp2025', 'Enable MCP 2025-11 features (version negotiation, async jobs, etc.)', {
|
|
43
|
+
default: false,
|
|
44
|
+
})
|
|
45
|
+
.option('--no-legacy', 'Disable legacy client support', { default: false })
|
|
35
46
|
.action(async (options: any) => {
|
|
36
47
|
try {
|
|
37
48
|
const config = await configManager.load();
|
|
38
49
|
|
|
39
|
-
//
|
|
40
|
-
const
|
|
50
|
+
// Check if MCP 2025-11 dependencies are available
|
|
51
|
+
const mcp2025Available = isMCP2025Available();
|
|
52
|
+
const enableMCP2025 = options.mcp2025 && mcp2025Available;
|
|
53
|
+
|
|
54
|
+
if (options.mcp2025 && !mcp2025Available) {
|
|
55
|
+
console.log(
|
|
56
|
+
chalk.yellow(
|
|
57
|
+
'⚠️ MCP 2025-11 dependencies not found. Install with: npm install uuid ajv ajv-formats ajv-errors'
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
console.log(chalk.yellow(' Falling back to legacy MCP server...'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build extended configuration
|
|
64
|
+
const mcpConfig: ExtendedMCPConfig = {
|
|
41
65
|
...config.mcp,
|
|
42
66
|
port: options.port,
|
|
43
67
|
host: options.host,
|
|
44
68
|
transport: options.transport,
|
|
69
|
+
features: {
|
|
70
|
+
enableMCP2025,
|
|
71
|
+
supportLegacyClients: options.legacy !== false,
|
|
72
|
+
enableVersionNegotiation: enableMCP2025,
|
|
73
|
+
enableAsyncJobs: enableMCP2025,
|
|
74
|
+
enableRegistryIntegration: false, // Opt-in via env var
|
|
75
|
+
enableSchemaValidation: enableMCP2025,
|
|
76
|
+
enableProgressiveDisclosure: true, // Phase 1 feature (always enabled)
|
|
77
|
+
},
|
|
78
|
+
mcp2025: enableMCP2025
|
|
79
|
+
? {
|
|
80
|
+
async: {
|
|
81
|
+
enabled: true,
|
|
82
|
+
maxJobs: 100,
|
|
83
|
+
jobTTL: 3600000,
|
|
84
|
+
},
|
|
85
|
+
registry: {
|
|
86
|
+
enabled: process.env.MCP_REGISTRY_ENABLED === 'true',
|
|
87
|
+
url: process.env.MCP_REGISTRY_URL,
|
|
88
|
+
apiKey: process.env.MCP_REGISTRY_API_KEY,
|
|
89
|
+
},
|
|
90
|
+
validation: {
|
|
91
|
+
enabled: true,
|
|
92
|
+
strictMode: false,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
: undefined,
|
|
45
96
|
};
|
|
46
97
|
|
|
47
|
-
|
|
98
|
+
// Create server using factory
|
|
99
|
+
mcpServer = await createMCPServer(mcpConfig, eventBus, logger, {
|
|
100
|
+
autoDetectFeatures: false, // Use explicit config
|
|
101
|
+
});
|
|
102
|
+
|
|
48
103
|
await mcpServer.start();
|
|
49
104
|
|
|
105
|
+
// Get capabilities
|
|
106
|
+
const capabilities = getServerCapabilities(mcpConfig);
|
|
107
|
+
|
|
50
108
|
console.log(chalk.green(`✅ MCP server started on ${options.host}:${options.port}`));
|
|
51
|
-
console.log(chalk.cyan(`📡 Server URL: http://${options.host}:${options.port}`));
|
|
52
|
-
console.log(chalk.cyan(`🔧 Available tools: Research, Code, Terminal, Memory`));
|
|
53
109
|
console.log(
|
|
54
|
-
chalk.cyan(
|
|
110
|
+
chalk.cyan(`🎯 Mode: ${enableMCP2025 ? 'MCP 2025-11 Enhanced' : 'Legacy Compatible'}`)
|
|
55
111
|
);
|
|
112
|
+
console.log(chalk.cyan(`📡 Transport: ${options.transport}`));
|
|
113
|
+
|
|
114
|
+
if (capabilities.length > 0) {
|
|
115
|
+
console.log(chalk.cyan(`✨ Capabilities: ${capabilities.join(', ')}`));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (enableMCP2025) {
|
|
119
|
+
console.log(chalk.green(' • Version negotiation (YYYY-MM format)'));
|
|
120
|
+
console.log(chalk.green(' • Async job support (poll/resume)'));
|
|
121
|
+
console.log(chalk.green(' • JSON Schema 1.1 validation'));
|
|
122
|
+
console.log(chalk.green(' • Progressive disclosure (98.7% token reduction)'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (options.transport === 'http') {
|
|
126
|
+
console.log(chalk.cyan(`📚 Server URL: http://${options.host}:${options.port}`));
|
|
127
|
+
}
|
|
56
128
|
} catch (error) {
|
|
57
129
|
console.error(chalk.red(`❌ Failed to start MCP server: ${(error as Error).message}`));
|
|
130
|
+
logger.error('MCP server startup failed', { error });
|
|
58
131
|
process.exit(1);
|
|
59
132
|
}
|
|
60
133
|
}),
|
|
@@ -151,7 +224,11 @@ export const mcpCommand = new Command()
|
|
|
151
224
|
|
|
152
225
|
console.log(chalk.yellow('🔄 Starting MCP server...'));
|
|
153
226
|
const config = await configManager.load();
|
|
154
|
-
|
|
227
|
+
|
|
228
|
+
// Use factory to create server with same capabilities as before
|
|
229
|
+
mcpServer = await createMCPServer(config.mcp, eventBus, logger, {
|
|
230
|
+
autoDetectFeatures: true, // Auto-detect on restart
|
|
231
|
+
});
|
|
155
232
|
await mcpServer.start();
|
|
156
233
|
|
|
157
234
|
console.log(
|
|
@@ -135,30 +135,35 @@ export async function copyAgentFiles(targetDir, options = {}) {
|
|
|
135
135
|
* Create agent directories structure
|
|
136
136
|
*/
|
|
137
137
|
export async function createAgentDirectories(targetDir, dryRun = false) {
|
|
138
|
-
// Flat structure - all .md files directly in category folders
|
|
139
138
|
const agentDirs = [
|
|
140
139
|
'.claude',
|
|
141
140
|
'.claude/agents',
|
|
142
141
|
'.claude/agents/core',
|
|
143
|
-
'.claude/agents/swarm',
|
|
142
|
+
'.claude/agents/swarm',
|
|
144
143
|
'.claude/agents/hive-mind',
|
|
145
144
|
'.claude/agents/consensus',
|
|
146
145
|
'.claude/agents/optimization',
|
|
147
146
|
'.claude/agents/github',
|
|
148
147
|
'.claude/agents/sparc',
|
|
149
148
|
'.claude/agents/testing',
|
|
149
|
+
'.claude/agents/testing/unit',
|
|
150
|
+
'.claude/agents/testing/validation',
|
|
150
151
|
'.claude/agents/templates',
|
|
151
152
|
'.claude/agents/analysis',
|
|
153
|
+
'.claude/agents/analysis/code-review',
|
|
152
154
|
'.claude/agents/architecture',
|
|
155
|
+
'.claude/agents/architecture/system-design',
|
|
153
156
|
'.claude/agents/data',
|
|
157
|
+
'.claude/agents/data/ml',
|
|
154
158
|
'.claude/agents/development',
|
|
159
|
+
'.claude/agents/development/backend',
|
|
155
160
|
'.claude/agents/devops',
|
|
161
|
+
'.claude/agents/devops/ci-cd',
|
|
156
162
|
'.claude/agents/documentation',
|
|
163
|
+
'.claude/agents/documentation/api-docs',
|
|
157
164
|
'.claude/agents/specialized',
|
|
165
|
+
'.claude/agents/specialized/mobile',
|
|
158
166
|
'.claude/agents/flow-nexus',
|
|
159
|
-
'.claude/agents/goal',
|
|
160
|
-
'.claude/agents/neural',
|
|
161
|
-
'.claude/agents/reasoning',
|
|
162
167
|
'.claude/commands',
|
|
163
168
|
'.claude/commands/flow-nexus'
|
|
164
169
|
];
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 2025-11 Async Job Manager
|
|
3
|
+
*
|
|
4
|
+
* Implements async job lifecycle per MCP 2025-11 specification:
|
|
5
|
+
* - Job handles with request_id
|
|
6
|
+
* - Poll/resume semantics
|
|
7
|
+
* - Progress tracking
|
|
8
|
+
* - Job persistence
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { EventEmitter } from 'events';
|
|
12
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
13
|
+
import type { ILogger } from '../../interfaces/logger.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* MCP tool request (2025-11 format)
|
|
17
|
+
*/
|
|
18
|
+
export interface MCPToolRequest {
|
|
19
|
+
request_id: string;
|
|
20
|
+
tool_id: string;
|
|
21
|
+
arguments: Record<string, any>;
|
|
22
|
+
session?: string;
|
|
23
|
+
mode: 'async' | 'sync';
|
|
24
|
+
context?: {
|
|
25
|
+
trace_id?: string;
|
|
26
|
+
client_name?: string;
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* MCP job handle (2025-11 format)
|
|
33
|
+
*/
|
|
34
|
+
export interface MCPJobHandle {
|
|
35
|
+
request_id: string;
|
|
36
|
+
job_id: string;
|
|
37
|
+
status: 'in_progress' | 'success' | 'error';
|
|
38
|
+
poll_after: number; // seconds
|
|
39
|
+
progress?: {
|
|
40
|
+
percent: number;
|
|
41
|
+
message?: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* MCP job result (2025-11 format)
|
|
47
|
+
*/
|
|
48
|
+
export interface MCPJobResult {
|
|
49
|
+
request_id: string;
|
|
50
|
+
status: 'success' | 'error' | 'in_progress';
|
|
51
|
+
result?: any;
|
|
52
|
+
error?: {
|
|
53
|
+
code: string;
|
|
54
|
+
message: string;
|
|
55
|
+
details?: any;
|
|
56
|
+
};
|
|
57
|
+
progress?: {
|
|
58
|
+
percent: number;
|
|
59
|
+
message?: string;
|
|
60
|
+
};
|
|
61
|
+
metadata: {
|
|
62
|
+
duration_ms?: number;
|
|
63
|
+
tokens_used?: number;
|
|
64
|
+
[key: string]: any;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Internal job state
|
|
70
|
+
*/
|
|
71
|
+
interface AsyncJob {
|
|
72
|
+
request_id: string;
|
|
73
|
+
job_id: string;
|
|
74
|
+
tool_id: string;
|
|
75
|
+
arguments: Record<string, any>;
|
|
76
|
+
mode: 'async' | 'sync';
|
|
77
|
+
status: 'queued' | 'running' | 'success' | 'error' | 'cancelled';
|
|
78
|
+
progress: number;
|
|
79
|
+
progress_message?: string;
|
|
80
|
+
result?: any;
|
|
81
|
+
error?: any;
|
|
82
|
+
context?: any;
|
|
83
|
+
created_at: Date;
|
|
84
|
+
started_at?: Date;
|
|
85
|
+
completed_at?: Date;
|
|
86
|
+
tokens_used?: number;
|
|
87
|
+
abortController?: AbortController;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Job persistence interface
|
|
92
|
+
*/
|
|
93
|
+
export interface JobPersistence {
|
|
94
|
+
save(job: AsyncJob): Promise<void>;
|
|
95
|
+
load(job_id: string): Promise<AsyncJob | null>;
|
|
96
|
+
list(filter?: { status?: string; limit?: number }): Promise<AsyncJob[]>;
|
|
97
|
+
delete(job_id: string): Promise<void>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Simple in-memory job persistence (fallback)
|
|
102
|
+
*/
|
|
103
|
+
export class MemoryJobPersistence implements JobPersistence {
|
|
104
|
+
private jobs: Map<string, AsyncJob> = new Map();
|
|
105
|
+
|
|
106
|
+
async save(job: AsyncJob): Promise<void> {
|
|
107
|
+
this.jobs.set(job.job_id, { ...job });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async load(job_id: string): Promise<AsyncJob | null> {
|
|
111
|
+
const job = this.jobs.get(job_id);
|
|
112
|
+
return job ? { ...job } : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async list(filter?: { status?: string; limit?: number }): Promise<AsyncJob[]> {
|
|
116
|
+
let jobs = Array.from(this.jobs.values());
|
|
117
|
+
|
|
118
|
+
if (filter?.status) {
|
|
119
|
+
jobs = jobs.filter(j => j.status === filter.status);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (filter?.limit) {
|
|
123
|
+
jobs = jobs.slice(0, filter.limit);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return jobs;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async delete(job_id: string): Promise<void> {
|
|
130
|
+
this.jobs.delete(job_id);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* MCP 2025-11 Async Job Manager
|
|
136
|
+
*/
|
|
137
|
+
export class MCPAsyncJobManager extends EventEmitter {
|
|
138
|
+
private jobs: Map<string, AsyncJob> = new Map();
|
|
139
|
+
private executors: Map<string, Promise<any>> = new Map();
|
|
140
|
+
private persistence: JobPersistence;
|
|
141
|
+
|
|
142
|
+
constructor(
|
|
143
|
+
persistence: JobPersistence | null,
|
|
144
|
+
private logger: ILogger,
|
|
145
|
+
private config: {
|
|
146
|
+
maxJobs?: number;
|
|
147
|
+
jobTTL?: number;
|
|
148
|
+
defaultPollInterval?: number;
|
|
149
|
+
} = {}
|
|
150
|
+
) {
|
|
151
|
+
super();
|
|
152
|
+
this.persistence = persistence || new MemoryJobPersistence();
|
|
153
|
+
|
|
154
|
+
// Default config
|
|
155
|
+
this.config.maxJobs = this.config.maxJobs || 1000;
|
|
156
|
+
this.config.jobTTL = this.config.jobTTL || 86400000; // 24 hours
|
|
157
|
+
this.config.defaultPollInterval = this.config.defaultPollInterval || 5;
|
|
158
|
+
|
|
159
|
+
// Cleanup expired jobs periodically
|
|
160
|
+
setInterval(() => this.cleanupExpiredJobs(), 3600000); // Every hour
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Submit async job (MCP 2025-11 format)
|
|
165
|
+
*/
|
|
166
|
+
async submitJob(
|
|
167
|
+
request: MCPToolRequest,
|
|
168
|
+
executor: (args: any, onProgress: (percent: number, message?: string) => void) => Promise<any>
|
|
169
|
+
): Promise<MCPJobHandle> {
|
|
170
|
+
// Check capacity
|
|
171
|
+
if (this.jobs.size >= this.config.maxJobs!) {
|
|
172
|
+
throw new Error('Job queue full. Please try again later.');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check for duplicate request_id (prevent race conditions)
|
|
176
|
+
const existingJob = Array.from(this.jobs.values()).find(
|
|
177
|
+
j => j.request_id === request.request_id &&
|
|
178
|
+
(j.status === 'queued' || j.status === 'running')
|
|
179
|
+
);
|
|
180
|
+
if (existingJob) {
|
|
181
|
+
throw new Error(`Duplicate request_id: ${request.request_id}. Job already submitted.`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Create job
|
|
185
|
+
const job: AsyncJob = {
|
|
186
|
+
request_id: request.request_id,
|
|
187
|
+
job_id: uuidv4(),
|
|
188
|
+
tool_id: request.tool_id,
|
|
189
|
+
arguments: request.arguments,
|
|
190
|
+
mode: request.mode,
|
|
191
|
+
status: 'queued',
|
|
192
|
+
progress: 0,
|
|
193
|
+
context: request.context,
|
|
194
|
+
created_at: new Date(),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Save to persistence
|
|
198
|
+
await this.persistence.save(job);
|
|
199
|
+
this.jobs.set(job.job_id, job);
|
|
200
|
+
|
|
201
|
+
this.logger.info('Job submitted', {
|
|
202
|
+
job_id: job.job_id,
|
|
203
|
+
request_id: job.request_id,
|
|
204
|
+
tool_id: job.tool_id,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Start execution in background
|
|
208
|
+
this.executeJob(job, executor);
|
|
209
|
+
|
|
210
|
+
// Return job handle immediately
|
|
211
|
+
return {
|
|
212
|
+
request_id: job.request_id,
|
|
213
|
+
job_id: job.job_id,
|
|
214
|
+
status: 'in_progress',
|
|
215
|
+
poll_after: this.config.defaultPollInterval!,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Poll job status
|
|
221
|
+
*/
|
|
222
|
+
async pollJob(job_id: string): Promise<MCPJobHandle> {
|
|
223
|
+
const job = await this.persistence.load(job_id);
|
|
224
|
+
|
|
225
|
+
if (!job) {
|
|
226
|
+
throw new Error(`Job not found: ${job_id}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const status = job.status === 'success' ? 'success' :
|
|
230
|
+
job.status === 'error' ? 'error' : 'in_progress';
|
|
231
|
+
|
|
232
|
+
const handle: MCPJobHandle = {
|
|
233
|
+
request_id: job.request_id,
|
|
234
|
+
job_id: job.job_id,
|
|
235
|
+
status,
|
|
236
|
+
poll_after: status === 'in_progress' ? this.config.defaultPollInterval! : 0,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
if (status === 'in_progress') {
|
|
240
|
+
handle.progress = {
|
|
241
|
+
percent: job.progress,
|
|
242
|
+
message: job.progress_message,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return handle;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Resume job (get results)
|
|
251
|
+
*/
|
|
252
|
+
async resumeJob(job_id: string): Promise<MCPJobResult> {
|
|
253
|
+
const job = await this.persistence.load(job_id);
|
|
254
|
+
|
|
255
|
+
if (!job) {
|
|
256
|
+
throw new Error(`Job not found: ${job_id}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const result: MCPJobResult = {
|
|
260
|
+
request_id: job.request_id,
|
|
261
|
+
status: job.status === 'success' ? 'success' :
|
|
262
|
+
job.status === 'error' ? 'error' : 'in_progress',
|
|
263
|
+
metadata: {},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
if (job.status === 'success') {
|
|
267
|
+
result.result = job.result;
|
|
268
|
+
result.metadata.duration_ms = job.completed_at && job.started_at
|
|
269
|
+
? job.completed_at.getTime() - job.started_at.getTime()
|
|
270
|
+
: undefined;
|
|
271
|
+
result.metadata.tokens_used = job.tokens_used;
|
|
272
|
+
} else if (job.status === 'error') {
|
|
273
|
+
result.error = {
|
|
274
|
+
code: 'EXECUTION_ERROR',
|
|
275
|
+
message: job.error?.message || 'Job execution failed',
|
|
276
|
+
details: job.error,
|
|
277
|
+
};
|
|
278
|
+
} else {
|
|
279
|
+
// Still in progress
|
|
280
|
+
result.progress = {
|
|
281
|
+
percent: job.progress,
|
|
282
|
+
message: job.progress_message,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Cancel a running job
|
|
291
|
+
*/
|
|
292
|
+
async cancelJob(job_id: string): Promise<boolean> {
|
|
293
|
+
const job = this.jobs.get(job_id);
|
|
294
|
+
|
|
295
|
+
if (!job) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (job.status === 'success' || job.status === 'error') {
|
|
300
|
+
return false; // Already finished
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Abort execution if AbortController is available
|
|
304
|
+
if (job.abortController) {
|
|
305
|
+
job.abortController.abort();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
job.status = 'cancelled';
|
|
309
|
+
job.completed_at = new Date();
|
|
310
|
+
await this.persistence.save(job);
|
|
311
|
+
|
|
312
|
+
this.emit('job:cancelled', job_id);
|
|
313
|
+
this.logger.info('Job cancelled', { job_id });
|
|
314
|
+
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* List jobs
|
|
320
|
+
*/
|
|
321
|
+
async listJobs(filter?: {
|
|
322
|
+
status?: string;
|
|
323
|
+
limit?: number;
|
|
324
|
+
}): Promise<AsyncJob[]> {
|
|
325
|
+
return await this.persistence.list(filter);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Execute job in background
|
|
330
|
+
*/
|
|
331
|
+
private async executeJob(
|
|
332
|
+
job: AsyncJob,
|
|
333
|
+
executor: (args: any, onProgress: (percent: number, message?: string) => void) => Promise<any>
|
|
334
|
+
): Promise<void> {
|
|
335
|
+
// Update status to running
|
|
336
|
+
job.status = 'running';
|
|
337
|
+
job.started_at = new Date();
|
|
338
|
+
|
|
339
|
+
// Create AbortController for cancellation support
|
|
340
|
+
job.abortController = new AbortController();
|
|
341
|
+
|
|
342
|
+
await this.persistence.save(job);
|
|
343
|
+
|
|
344
|
+
this.emit('job:started', job.job_id);
|
|
345
|
+
this.logger.info('Job started', { job_id: job.job_id, tool_id: job.tool_id });
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
// Progress callback
|
|
349
|
+
const onProgress = (percent: number, message?: string) => {
|
|
350
|
+
job.progress = Math.min(100, Math.max(0, percent));
|
|
351
|
+
job.progress_message = message;
|
|
352
|
+
this.persistence.save(job).catch(err =>
|
|
353
|
+
this.logger.error('Failed to save progress', { job_id: job.job_id, error: err })
|
|
354
|
+
);
|
|
355
|
+
this.emit('job:progress', job.job_id, job.progress, message);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// Check if already cancelled
|
|
359
|
+
if (job.abortController.signal.aborted) {
|
|
360
|
+
throw new Error('Job cancelled before execution');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Execute with abort support
|
|
364
|
+
const result = await executor(job.arguments, onProgress);
|
|
365
|
+
|
|
366
|
+
// Mark successful
|
|
367
|
+
job.status = 'success';
|
|
368
|
+
job.result = result;
|
|
369
|
+
job.progress = 100;
|
|
370
|
+
job.completed_at = new Date();
|
|
371
|
+
await this.persistence.save(job);
|
|
372
|
+
|
|
373
|
+
this.emit('job:completed', job.job_id, result);
|
|
374
|
+
this.logger.info('Job completed', {
|
|
375
|
+
job_id: job.job_id,
|
|
376
|
+
duration_ms: job.completed_at.getTime() - job.started_at!.getTime(),
|
|
377
|
+
});
|
|
378
|
+
} catch (error: any) {
|
|
379
|
+
// Mark failed
|
|
380
|
+
job.status = 'error';
|
|
381
|
+
job.error = {
|
|
382
|
+
message: error.message,
|
|
383
|
+
stack: error.stack,
|
|
384
|
+
code: error.code,
|
|
385
|
+
};
|
|
386
|
+
job.completed_at = new Date();
|
|
387
|
+
await this.persistence.save(job);
|
|
388
|
+
|
|
389
|
+
this.emit('job:failed', job.job_id, error);
|
|
390
|
+
this.logger.error('Job failed', {
|
|
391
|
+
job_id: job.job_id,
|
|
392
|
+
error: error.message,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Cleanup expired jobs
|
|
399
|
+
*/
|
|
400
|
+
private async cleanupExpiredJobs(): Promise<number> {
|
|
401
|
+
const now = Date.now();
|
|
402
|
+
const jobs = await this.persistence.list();
|
|
403
|
+
let cleaned = 0;
|
|
404
|
+
|
|
405
|
+
for (const job of jobs) {
|
|
406
|
+
const age = now - job.created_at.getTime();
|
|
407
|
+
|
|
408
|
+
// Remove if expired and not running
|
|
409
|
+
if (age > this.config.jobTTL! && job.status !== 'running') {
|
|
410
|
+
await this.persistence.delete(job.job_id);
|
|
411
|
+
this.jobs.delete(job.job_id);
|
|
412
|
+
cleaned++;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (cleaned > 0) {
|
|
417
|
+
this.logger.info('Cleaned up expired jobs', { count: cleaned });
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return cleaned;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get metrics
|
|
425
|
+
*/
|
|
426
|
+
getMetrics() {
|
|
427
|
+
const jobs = Array.from(this.jobs.values());
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
total: jobs.length,
|
|
431
|
+
byStatus: {
|
|
432
|
+
queued: jobs.filter(j => j.status === 'queued').length,
|
|
433
|
+
running: jobs.filter(j => j.status === 'running').length,
|
|
434
|
+
success: jobs.filter(j => j.status === 'success').length,
|
|
435
|
+
error: jobs.filter(j => j.status === 'error').length,
|
|
436
|
+
cancelled: jobs.filter(j => j.status === 'cancelled').length,
|
|
437
|
+
},
|
|
438
|
+
averageDuration: this.calculateAverageDuration(jobs),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private calculateAverageDuration(jobs: AsyncJob[]): number {
|
|
443
|
+
const completed = jobs.filter(j =>
|
|
444
|
+
(j.status === 'success' || j.status === 'error') &&
|
|
445
|
+
j.started_at && j.completed_at
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
if (completed.length === 0) return 0;
|
|
449
|
+
|
|
450
|
+
const total = completed.reduce((sum, j) =>
|
|
451
|
+
sum + (j.completed_at!.getTime() - j.started_at!.getTime()), 0
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
return total / completed.length;
|
|
455
|
+
}
|
|
456
|
+
}
|
package/src/mcp/index.ts
CHANGED
|
@@ -27,6 +27,66 @@ export type { SDKIntegrationConfig } from './sdk-integration.js';
|
|
|
27
27
|
// Core MCP Server
|
|
28
28
|
export { MCPServer, type IMCPServer } from './server.js';
|
|
29
29
|
|
|
30
|
+
// MCP 2025-11 Server and Components
|
|
31
|
+
export { MCP2025Server, type MCP2025ServerConfig } from './server-mcp-2025.js';
|
|
32
|
+
export {
|
|
33
|
+
MCPServerFactory,
|
|
34
|
+
createMCPServer,
|
|
35
|
+
isMCP2025Available,
|
|
36
|
+
getServerCapabilities,
|
|
37
|
+
type MCPFeatureFlags,
|
|
38
|
+
type ExtendedMCPConfig,
|
|
39
|
+
} from './server-factory.js';
|
|
40
|
+
|
|
41
|
+
// MCP 2025-11 Protocol Components
|
|
42
|
+
export {
|
|
43
|
+
VersionNegotiator,
|
|
44
|
+
BackwardCompatibilityAdapter,
|
|
45
|
+
type MCPHandshake,
|
|
46
|
+
type MCPVersion,
|
|
47
|
+
type MCPCapability,
|
|
48
|
+
type NegotiationResult as MCP2025NegotiationResult,
|
|
49
|
+
} from './protocol/version-negotiation.js';
|
|
50
|
+
|
|
51
|
+
// MCP 2025-11 Async Job Management
|
|
52
|
+
export {
|
|
53
|
+
MCPAsyncJobManager,
|
|
54
|
+
type MCPToolRequest,
|
|
55
|
+
type MCPJobHandle,
|
|
56
|
+
type MCPJobResult,
|
|
57
|
+
type AsyncJob,
|
|
58
|
+
type JobStatus,
|
|
59
|
+
} from './async/job-manager-mcp25.js';
|
|
60
|
+
|
|
61
|
+
// MCP 2025-11 Registry Integration
|
|
62
|
+
export {
|
|
63
|
+
MCPRegistryClient,
|
|
64
|
+
type RegistryConfig,
|
|
65
|
+
type ServerRegistryEntry,
|
|
66
|
+
} from './registry/mcp-registry-client-2025.js';
|
|
67
|
+
|
|
68
|
+
// MCP 2025-11 Schema Validation
|
|
69
|
+
export {
|
|
70
|
+
SchemaValidator,
|
|
71
|
+
upgradeToolSchema,
|
|
72
|
+
type ValidationResult,
|
|
73
|
+
} from './validation/schema-validator-2025.js';
|
|
74
|
+
|
|
75
|
+
// Progressive Tool Registry (Phase 1 & 2)
|
|
76
|
+
export {
|
|
77
|
+
ProgressiveToolRegistry,
|
|
78
|
+
createProgressiveToolRegistry,
|
|
79
|
+
createProgressiveClaudeFlowSdkServer,
|
|
80
|
+
type ProgressiveToolRegistryConfig,
|
|
81
|
+
} from './tool-registry-progressive.js';
|
|
82
|
+
|
|
83
|
+
// Dynamic Tool Loader (Phase 1)
|
|
84
|
+
export {
|
|
85
|
+
DynamicToolLoader,
|
|
86
|
+
type ToolMetadata,
|
|
87
|
+
type ToolSearchQuery,
|
|
88
|
+
} from './tools/loader.js';
|
|
89
|
+
|
|
30
90
|
// Lifecycle Management
|
|
31
91
|
export {
|
|
32
92
|
MCPLifecycleManager,
|