feat(mcp-server): Add 7 missing extraction async jobs tools
- extraction.extractBatch: batch extraction with shared config - extraction.submitJob: async job submission with webhook support - extraction.getJob: get job status/results by ID - extraction.listJobs: list recent async jobs - extraction.getProductRateLimitStatus: per-product or summary rate limits - extraction.resetProductRateLimit: admin rate limit reset - extraction.sidecarMonitoringState: detailed sidecar circuit breaker state All tools require admin role and map to existing extraction-service endpoints. Fixes TypeScript optional parameter error in extractionGetProductRateLimitStatus.
This commit is contained in:
parent
4537ed271e
commit
c8fafbb564
@ -70,3 +70,163 @@ export async function extractionSidecarHealth(opts: { requestId?: string }): Pro
|
|||||||
const res = await fetch(url, { headers, signal: AbortSignal.timeout(10_000) });
|
const res = await fetch(url, { headers, signal: AbortSignal.timeout(10_000) });
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function extractionExtractBatch(
|
||||||
|
params: {
|
||||||
|
inputs: Array<{
|
||||||
|
text: string;
|
||||||
|
taskId?: string;
|
||||||
|
taskPrompt?: string;
|
||||||
|
}>;
|
||||||
|
examples?: Array<{
|
||||||
|
text: string;
|
||||||
|
extractions: ExtractionItem[];
|
||||||
|
}>;
|
||||||
|
modelId?: string;
|
||||||
|
},
|
||||||
|
opts: { requestId?: string }
|
||||||
|
): Promise<unknown> {
|
||||||
|
const url = `${config.EXTRACTION_SERVICE_URL}/api/extract/batch`;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
||||||
|
};
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
signal: AbortSignal.timeout(30_000),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(`extraction-service POST /api/extract/batch → ${res.status}: ${body}`);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function extractionSubmitJob(
|
||||||
|
params: {
|
||||||
|
inputs: Array<{
|
||||||
|
text: string;
|
||||||
|
taskId?: string;
|
||||||
|
taskPrompt?: string;
|
||||||
|
}>;
|
||||||
|
examples?: Array<{
|
||||||
|
text: string;
|
||||||
|
extractions: ExtractionItem[];
|
||||||
|
}>;
|
||||||
|
modelId?: string;
|
||||||
|
productId?: string;
|
||||||
|
webhookUrl?: string;
|
||||||
|
webhookSecret?: string;
|
||||||
|
webhookRetryAttempts?: number;
|
||||||
|
},
|
||||||
|
opts: { requestId?: string }
|
||||||
|
): Promise<unknown> {
|
||||||
|
const url = `${config.EXTRACTION_SERVICE_URL}/api/extract/jobs`;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
||||||
|
};
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
signal: AbortSignal.timeout(30_000),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(`extraction-service POST /api/extract/jobs → ${res.status}: ${body}`);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function extractionGetJob(
|
||||||
|
jobId: string,
|
||||||
|
opts: { requestId?: string }
|
||||||
|
): Promise<unknown> {
|
||||||
|
const url = `${config.EXTRACTION_SERVICE_URL}/api/extract/jobs/${jobId}`;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
||||||
|
};
|
||||||
|
const res = await fetch(url, { headers, signal: AbortSignal.timeout(10_000) });
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(`extraction-service GET /api/extract/jobs/${jobId} → ${res.status}: ${body}`);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function extractionListJobs(opts: { requestId?: string }): Promise<unknown> {
|
||||||
|
const url = `${config.EXTRACTION_SERVICE_URL}/api/extract/jobs`;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
||||||
|
};
|
||||||
|
const res = await fetch(url, { headers, signal: AbortSignal.timeout(10_000) });
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(`extraction-service GET /api/extract/jobs → ${res.status}: ${body}`);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function extractionGetProductRateLimitStatus(
|
||||||
|
productId?: string,
|
||||||
|
opts?: { requestId?: string }
|
||||||
|
): Promise<unknown> {
|
||||||
|
const url = productId
|
||||||
|
? `${config.EXTRACTION_SERVICE_URL}/api/extract/rate-limits/product?productId=${encodeURIComponent(productId)}`
|
||||||
|
: `${config.EXTRACTION_SERVICE_URL}/api/extract/rate-limits/product`;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
...(opts?.requestId ? { 'x-request-id': opts.requestId } : {}),
|
||||||
|
};
|
||||||
|
const res = await fetch(url, { headers, signal: AbortSignal.timeout(10_000) });
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(
|
||||||
|
`extraction-service GET /api/extract/rate-limits/product → ${res.status}: ${body}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function extractionResetProductRateLimit(
|
||||||
|
productId: string,
|
||||||
|
opts: { requestId?: string }
|
||||||
|
): Promise<unknown> {
|
||||||
|
const url = `${config.EXTRACTION_SERVICE_URL}/api/extract/rate-limits/product/reset`;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
||||||
|
};
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify({ productId }),
|
||||||
|
signal: AbortSignal.timeout(10_000),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(
|
||||||
|
`extraction-service POST /api/extract/rate-limits/product/reset → ${res.status}: ${body}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function extractionSidecarMonitoringState(opts: {
|
||||||
|
requestId?: string;
|
||||||
|
}): Promise<unknown> {
|
||||||
|
const url = `${config.EXTRACTION_SERVICE_URL}/api/extract/monitoring/sidecar`;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
||||||
|
};
|
||||||
|
const res = await fetch(url, { headers, signal: AbortSignal.timeout(10_000) });
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(
|
||||||
|
`extraction-service GET /api/extract/monitoring/sidecar → ${res.status}: ${body}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,13 @@ import {
|
|||||||
extractionModels,
|
extractionModels,
|
||||||
extractionCacheStats,
|
extractionCacheStats,
|
||||||
extractionSidecarHealth,
|
extractionSidecarHealth,
|
||||||
|
extractionExtractBatch,
|
||||||
|
extractionSubmitJob,
|
||||||
|
extractionGetJob,
|
||||||
|
extractionListJobs,
|
||||||
|
extractionGetProductRateLimitStatus,
|
||||||
|
extractionResetProductRateLimit,
|
||||||
|
extractionSidecarMonitoringState,
|
||||||
} from '../../lib/extraction-client.js';
|
} from '../../lib/extraction-client.js';
|
||||||
|
|
||||||
registerTool({
|
registerTool({
|
||||||
@ -55,3 +62,147 @@ registerTool({
|
|||||||
return extractionSidecarHealth({ requestId: req.id });
|
return extractionSidecarHealth({ requestId: req.id });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'extraction.extractBatch',
|
||||||
|
description:
|
||||||
|
'Run batch extraction on multiple inputs with shared configuration. Returns array of extraction results. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
inputs: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
text: z.string().min(1).describe('Text to extract from'),
|
||||||
|
taskId: z.string().optional().describe('Extraction task ID'),
|
||||||
|
taskPrompt: z.string().optional().describe('Custom task prompt'),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.min(1)
|
||||||
|
.describe('Array of extraction inputs'),
|
||||||
|
examples: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
text: z.string().min(1).describe('Example text'),
|
||||||
|
extractions: z.array(
|
||||||
|
z.object({
|
||||||
|
extraction_class: z.string(),
|
||||||
|
extraction_text: z.string(),
|
||||||
|
attributes: z.record(z.string()).optional(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.describe('Few-shot examples'),
|
||||||
|
modelId: z.string().optional().describe('Override model ID'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return extractionExtractBatch(args, { requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'extraction.submitJob',
|
||||||
|
description:
|
||||||
|
'Submit an async batch extraction job. Returns jobId for polling. Supports webhook callbacks. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
inputs: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
text: z.string().min(1).describe('Text to extract from'),
|
||||||
|
taskId: z.string().optional().describe('Extraction task ID'),
|
||||||
|
taskPrompt: z.string().optional().describe('Custom task prompt'),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.min(1)
|
||||||
|
.describe('Array of extraction inputs'),
|
||||||
|
examples: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
text: z.string().min(1).describe('Example text'),
|
||||||
|
extractions: z.array(
|
||||||
|
z.object({
|
||||||
|
extraction_class: z.string(),
|
||||||
|
extraction_text: z.string(),
|
||||||
|
attributes: z.record(z.string()).optional(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.describe('Few-shot examples'),
|
||||||
|
modelId: z.string().optional().describe('Override model ID'),
|
||||||
|
productId: z.string().optional().describe('Product ID for rate limiting'),
|
||||||
|
webhookUrl: z.string().url().optional().describe('Webhook URL for job completion'),
|
||||||
|
webhookSecret: z.string().optional().describe('Webhook secret for HMAC validation'),
|
||||||
|
webhookRetryAttempts: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.max(10)
|
||||||
|
.optional()
|
||||||
|
.describe('Webhook retry attempts'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return extractionSubmitJob(args, { requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'extraction.getJob',
|
||||||
|
description: 'Get status and results of an async extraction job by ID. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
jobId: z.string().min(1).describe('Job ID to retrieve'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return extractionGetJob(args.jobId, { requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'extraction.listJobs',
|
||||||
|
description: 'List recent async extraction jobs. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({}),
|
||||||
|
async execute(_args, req) {
|
||||||
|
return extractionListJobs({ requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'extraction.getProductRateLimitStatus',
|
||||||
|
description:
|
||||||
|
'Get product rate limit status. Pass productId for specific product, omit for summary of all products. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
productId: z.string().optional().describe('Product ID to check (omit for all products)'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return extractionGetProductRateLimitStatus(args.productId, { requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'extraction.resetProductRateLimit',
|
||||||
|
description: 'Reset rate limit for a specific product (admin operation). Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
productId: z.string().min(1).describe('Product ID to reset rate limit for'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return extractionResetProductRateLimit(args.productId, { requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'extraction.sidecarMonitoringState',
|
||||||
|
description:
|
||||||
|
'Get detailed sidecar health monitoring state and circuit breaker information. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({}),
|
||||||
|
async execute(_args, req) {
|
||||||
|
return extractionSidecarMonitoringState({ requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user