mcp-agentic-pipelines 1.0.1
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/.env.example +93 -0
- package/README.md +258 -0
- package/package.json +70 -0
- package/packages/clinical/package.json +22 -0
- package/packages/clinical/src/index.ts +262 -0
- package/packages/clinical/tsconfig.json +13 -0
- package/packages/core/package.json +21 -0
- package/packages/core/src/config.ts +138 -0
- package/packages/core/src/errors.ts +100 -0
- package/packages/core/src/index.ts +104 -0
- package/packages/core/src/llm-config.ts +213 -0
- package/packages/core/src/logging.ts +66 -0
- package/packages/core/src/python-bridge.ts +384 -0
- package/packages/core/src/rate-limiter.ts +136 -0
- package/packages/core/src/types.ts +203 -0
- package/packages/core/src/validation.ts +101 -0
- package/packages/core/tsconfig.json +10 -0
- package/packages/deeppipe/package.json +21 -0
- package/packages/deeppipe/src/index.ts +424 -0
- package/packages/deeppipe/tsconfig.json +13 -0
- package/packages/piste/package.json +20 -0
- package/packages/piste/src/index.ts +48 -0
- package/packages/piste/tsconfig.json +13 -0
- package/packages/precis/package.json +20 -0
- package/packages/precis/src/index.ts +67 -0
- package/packages/precis/tsconfig.json +13 -0
- package/packages/server/package.json +31 -0
- package/packages/server/src/index.ts +427 -0
- package/packages/server/tsconfig.json +17 -0
- package/setup.mjs +141 -0
- package/test.mjs +337 -0
- package/vendors/clinical-intake/pipeline.mjs +349 -0
- package/vendors/clinical-intake/questions/en.txt +9 -0
- package/vendors/clinical-intake/questions/fr.txt +9 -0
- package/vendors/piste/.env.example +73 -0
- package/vendors/piste/app/core/__init__.py +4 -0
- package/vendors/piste/app/core/config.py +83 -0
- package/vendors/piste/app/core/debuglog.py +16 -0
- package/vendors/piste/app/core/middleware.py +40 -0
- package/vendors/piste/bridge_piste.py +301 -0
- package/vendors/piste/pipeline/__init__.py +4 -0
- package/vendors/piste/pipeline/compiler.py +68 -0
- package/vendors/piste/pipeline/offline/__init__.py +28 -0
- package/vendors/piste/pipeline/offline/verifaid_pipeline.py +247 -0
- package/vendors/piste/pipeline/replay.py +15 -0
- package/vendors/piste/pipeline/replay_engine.py +249 -0
- package/vendors/piste/pipeline/signatures/__init__.py +4 -0
- package/vendors/piste/pipeline/signatures/signatures.py +136 -0
- package/vendors/piste/pipeline/stage1/__init__.py +21 -0
- package/vendors/piste/pipeline/stage1/atomic_decomposer.py +61 -0
- package/vendors/piste/pipeline/stage1/check_worthiness.py +100 -0
- package/vendors/piste/pipeline/stage1/orchestrator.py +175 -0
- package/vendors/piste/pipeline/stage1/test_stage1.py +162 -0
- package/vendors/piste/pipeline/stage2/__init__.py +34 -0
- package/vendors/piste/pipeline/stage2/blind_retriever.py +303 -0
- package/vendors/piste/pipeline/stage2/canonical_mapper.py +124 -0
- package/vendors/piste/pipeline/stage2/credibility_scorer.py +85 -0
- package/vendors/piste/pipeline/stage2/orchestrator.py +311 -0
- package/vendors/piste/pipeline/stage2/query_refiner.py +88 -0
- package/vendors/piste/pipeline/stage2/search_decision.py +69 -0
- package/vendors/piste/pipeline/stage2/test_stage2.py +265 -0
- package/vendors/piste/pipeline/stage3/__init__.py +20 -0
- package/vendors/piste/pipeline/stage3/classifier.py +79 -0
- package/vendors/piste/pipeline/stage3/orchestrator.py +225 -0
- package/vendors/piste/pipeline/stage3/test_stage3.py +101 -0
- package/vendors/piste/pipeline/stage4/__init__.py +33 -0
- package/vendors/piste/pipeline/stage4/criticality_gate.py +177 -0
- package/vendors/piste/pipeline/stage4/orchestrator.py +269 -0
- package/vendors/piste/pipeline/stage4/test_stage4.py +192 -0
- package/vendors/piste/pipeline/stage4/verdict_aggregator.py +157 -0
- package/vendors/piste/requirements.txt +53 -0
- package/vendors/precis/backend/__init__.py +6 -0
- package/vendors/precis/backend/agents/__init__.py +3 -0
- package/vendors/precis/backend/agents/data_synthesis.py +105 -0
- package/vendors/precis/backend/agents/dist_free_synth.py +97 -0
- package/vendors/precis/backend/agents/exact_hash_retriever.py +327 -0
- package/vendors/precis/backend/agents/fusion_ranker.py +64 -0
- package/vendors/precis/backend/agents/guardrail.py +175 -0
- package/vendors/precis/backend/agents/query_expander.py +89 -0
- package/vendors/precis/backend/agents/radial_interpol.py +99 -0
- package/vendors/precis/backend/agents/report_generator.py +92 -0
- package/vendors/precis/backend/agents/semantic_reranker.py +135 -0
- package/vendors/precis/backend/agents/stat_anomaly.py +93 -0
- package/vendors/precis/backend/agents/vector_index.py +123 -0
- package/vendors/precis/backend/agents/veri_score.py +341 -0
- package/vendors/precis/backend/agents/work_order_extractor.py +205 -0
- package/vendors/precis/backend/api/__init__.py +3 -0
- package/vendors/precis/backend/api/routes/__init__.py +3 -0
- package/vendors/precis/backend/config.py +88 -0
- package/vendors/precis/backend/core/__init__.py +13 -0
- package/vendors/precis/backend/core/hashing.py +22 -0
- package/vendors/precis/backend/core/metrics.py +77 -0
- package/vendors/precis/backend/core/multitoken.py +166 -0
- package/vendors/precis/backend/core/pmi.py +54 -0
- package/vendors/precis/backend/core/stemming.py +74 -0
- package/vendors/precis/backend/core/tracing.py +150 -0
- package/vendors/precis/backend/data/__init__.py +3 -0
- package/vendors/precis/backend/data/chunker.py +57 -0
- package/vendors/precis/backend/data/pdf_parser.py +42 -0
- package/vendors/precis/backend/db/__init__.py +3 -0
- package/vendors/precis/backend/db/models.py +173 -0
- package/vendors/precis/backend/db/repository.py +269 -0
- package/vendors/precis/backend/llm/__init__.py +3 -0
- package/vendors/precis/backend/llm/anthropic_provider.py +39 -0
- package/vendors/precis/backend/llm/base.py +147 -0
- package/vendors/precis/backend/llm/deepseek_provider.py +43 -0
- package/vendors/precis/backend/llm/factory.py +60 -0
- package/vendors/precis/backend/llm/google_provider.py +39 -0
- package/vendors/precis/backend/llm/ollama_provider.py +54 -0
- package/vendors/precis/backend/llm/openai_provider.py +50 -0
- package/vendors/precis/backend/main.py +677 -0
- package/vendors/precis/backend/orchestrator/__init__.py +3 -0
- package/vendors/precis/backend/orchestrator/planner.py +81 -0
- package/vendors/precis/backend/orchestrator/router.py +319 -0
- package/vendors/precis/backend/orchestrator/types.py +58 -0
- package/vendors/precis/bridge_precis.py +185 -0
- package/vendors/precis/data/sample_reports/README.md +8 -0
- package/vendors/precis/data/seed_data.py +115 -0
- package/vendors/precis/requirements.txt +19 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Bucket Rate Limiter
|
|
3
|
+
*
|
|
4
|
+
* Per-tool rate limiting using the token bucket algorithm.
|
|
5
|
+
* Different rate categories for costly vs read-only operations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { RateLimitError } from './errors.js';
|
|
9
|
+
|
|
10
|
+
// ── Token Bucket ─────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export class TokenBucket {
|
|
13
|
+
private tokens: number;
|
|
14
|
+
private lastRefill: number;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly maxTokens: number,
|
|
18
|
+
private readonly refillRate: number, // tokens per second
|
|
19
|
+
) {
|
|
20
|
+
this.tokens = maxTokens;
|
|
21
|
+
this.lastRefill = Date.now();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Attempt to consume tokens from the bucket.
|
|
26
|
+
* @returns true if tokens were available and consumed.
|
|
27
|
+
*/
|
|
28
|
+
tryConsume(count: number = 1): boolean {
|
|
29
|
+
this.refill();
|
|
30
|
+
if (this.tokens >= count) {
|
|
31
|
+
this.tokens -= count;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Estimate milliseconds until the next token is available.
|
|
39
|
+
*/
|
|
40
|
+
timeUntilNextToken(): number {
|
|
41
|
+
this.refill();
|
|
42
|
+
if (this.tokens >= 1) return 0;
|
|
43
|
+
// tokens is between 0 and 1 (fractional). Need (1 - tokens) worth of refill.
|
|
44
|
+
const needed = 1 - this.tokens;
|
|
45
|
+
return Math.ceil((needed / this.refillRate) * 1000);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private refill(): void {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
const elapsed = (now - this.lastRefill) / 1000;
|
|
51
|
+
this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRate);
|
|
52
|
+
this.lastRefill = now;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Rate Limiter ─────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/** Rate limit category — determines the token bucket parameters. */
|
|
59
|
+
export type RateCategory = 'costly' | 'write' | 'read';
|
|
60
|
+
|
|
61
|
+
const RATE_LIMITS: Record<RateCategory, { maxTokens: number; refillRate: number }> = {
|
|
62
|
+
costly: { maxTokens: 1, refillRate: 1 }, // 1 req/s
|
|
63
|
+
write: { maxTokens: 5, refillRate: 5 }, // 5 req/s
|
|
64
|
+
read: { maxTokens: 30, refillRate: 30 }, // 30 req/s
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export class RateLimiter {
|
|
68
|
+
private buckets = new Map<string, TokenBucket>();
|
|
69
|
+
private enabled: boolean;
|
|
70
|
+
|
|
71
|
+
constructor(
|
|
72
|
+
private readonly defaultMaxRPS: number = 10,
|
|
73
|
+
enabled: boolean = true,
|
|
74
|
+
) {
|
|
75
|
+
this.enabled = enabled;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check and consume a rate limit token for a tool.
|
|
80
|
+
* Throws RateLimitError if limit exceeded.
|
|
81
|
+
*
|
|
82
|
+
* @param toolName - MCP tool name.
|
|
83
|
+
* @param category - Rate category override (auto-detected if omitted).
|
|
84
|
+
*/
|
|
85
|
+
check(toolName: string, category?: RateCategory): void {
|
|
86
|
+
if (!this.enabled) return;
|
|
87
|
+
|
|
88
|
+
const bucket = this.getBucket(toolName, category);
|
|
89
|
+
if (!bucket.tryConsume()) {
|
|
90
|
+
const retryMs = bucket.timeUntilNextToken();
|
|
91
|
+
throw new RateLimitError(toolName, retryMs);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create or retrieve a token bucket for a tool.
|
|
97
|
+
*/
|
|
98
|
+
private getBucket(toolName: string, category?: RateCategory): TokenBucket {
|
|
99
|
+
if (!this.buckets.has(toolName)) {
|
|
100
|
+
const cat = category ?? this.detectCategory(toolName);
|
|
101
|
+
const limits = RATE_LIMITS[cat];
|
|
102
|
+
this.buckets.set(toolName, new TokenBucket(limits.maxTokens, limits.refillRate));
|
|
103
|
+
}
|
|
104
|
+
return this.buckets.get(toolName)!;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Auto-detect rate category from tool name prefix.
|
|
109
|
+
*/
|
|
110
|
+
private detectCategory(toolName: string): RateCategory {
|
|
111
|
+
// Costly: full pipeline runs, audio processing
|
|
112
|
+
if (
|
|
113
|
+
toolName.startsWith('piste_fact_check') ||
|
|
114
|
+
toolName.startsWith('clinical_process') ||
|
|
115
|
+
toolName.startsWith('precis_query')
|
|
116
|
+
) {
|
|
117
|
+
return 'costly';
|
|
118
|
+
}
|
|
119
|
+
// Write: ingestion, upload
|
|
120
|
+
if (
|
|
121
|
+
toolName.includes('ingest') ||
|
|
122
|
+
toolName.includes('upload') ||
|
|
123
|
+
toolName.includes('remove') ||
|
|
124
|
+
toolName.includes('extract')
|
|
125
|
+
) {
|
|
126
|
+
return 'write';
|
|
127
|
+
}
|
|
128
|
+
// Read: everything else
|
|
129
|
+
return 'read';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Create a rate limiter from configuration. */
|
|
134
|
+
export function createRateLimiter(enabled: boolean, maxRPS: number): RateLimiter {
|
|
135
|
+
return new RateLimiter(maxRPS, enabled);
|
|
136
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared TypeScript Types
|
|
3
|
+
*
|
|
4
|
+
* Common types used across all integration packages.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ResolvedLLMConfig } from './llm-config.js';
|
|
8
|
+
|
|
9
|
+
// ── MCP Tool Types ───────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
/** Standard MCP tool response content item. */
|
|
12
|
+
export interface MCPTextContent {
|
|
13
|
+
type: 'text';
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Standard MCP tool response. */
|
|
18
|
+
export interface MCPToolResponse {
|
|
19
|
+
content: MCPTextContent[];
|
|
20
|
+
isError?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** MCP resource content. */
|
|
24
|
+
export interface MCPResourceContent {
|
|
25
|
+
uri: string;
|
|
26
|
+
mimeType: string;
|
|
27
|
+
text?: string;
|
|
28
|
+
blob?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Definition of a registered MCP tool. */
|
|
32
|
+
export interface ToolDefinition {
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
inputSchema: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Definition of a registered MCP resource. */
|
|
39
|
+
export interface ResourceDefinition {
|
|
40
|
+
uri: string;
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
mimeType: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Definition of a registered MCP prompt. */
|
|
47
|
+
export interface PromptDefinition {
|
|
48
|
+
name: string;
|
|
49
|
+
description: string;
|
|
50
|
+
arguments?: Array<{
|
|
51
|
+
name: string;
|
|
52
|
+
description: string;
|
|
53
|
+
required: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── Health Status ────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export interface ServiceHealth {
|
|
60
|
+
service: string;
|
|
61
|
+
status: 'healthy' | 'unhealthy' | 'disabled';
|
|
62
|
+
provider?: string;
|
|
63
|
+
model?: string;
|
|
64
|
+
error?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── LLM Provider Info ────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export interface LLMProviderInfo {
|
|
70
|
+
provider: string;
|
|
71
|
+
baseUrl: string;
|
|
72
|
+
defaultModel: string;
|
|
73
|
+
isOpenAICompatible: boolean;
|
|
74
|
+
configured: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Audio Types (Clinical Intake) ────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
export interface AudioTurn {
|
|
80
|
+
role: 'user' | 'assistant';
|
|
81
|
+
text: string;
|
|
82
|
+
audioBase64?: string;
|
|
83
|
+
turnNumber: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ClinicalSession {
|
|
87
|
+
sessionId: string;
|
|
88
|
+
patientName: string;
|
|
89
|
+
lang: 'en' | 'fr';
|
|
90
|
+
turns: AudioTurn[];
|
|
91
|
+
createdAt: string;
|
|
92
|
+
isComplete: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Search Types (DeepPipe) ──────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
export interface SearchHit {
|
|
98
|
+
score: number;
|
|
99
|
+
source: string;
|
|
100
|
+
snippet?: string;
|
|
101
|
+
documentId: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface SearchResults {
|
|
105
|
+
hits: SearchHit[];
|
|
106
|
+
totalHits: number;
|
|
107
|
+
elapsedMs: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface StoredDocument {
|
|
111
|
+
id: number;
|
|
112
|
+
source: string;
|
|
113
|
+
wordCount: number;
|
|
114
|
+
indexedAt: string;
|
|
115
|
+
format?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── Chat Types (DeepPipe RAG) ────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
export interface ChatSource {
|
|
121
|
+
index: number;
|
|
122
|
+
documentId: number;
|
|
123
|
+
title: string;
|
|
124
|
+
sourcePath: string;
|
|
125
|
+
score: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ChatContext {
|
|
129
|
+
grounded: boolean;
|
|
130
|
+
sources: ChatSource[];
|
|
131
|
+
messages: Array<{ role: string; content: string }>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Fact-Check Types (Piste) ─────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
export type VerdictLabel =
|
|
137
|
+
| 'TRUE'
|
|
138
|
+
| 'MOSTLY_TRUE'
|
|
139
|
+
| 'HALF_TRUE'
|
|
140
|
+
| 'MOSTLY_FALSE'
|
|
141
|
+
| 'FALSE'
|
|
142
|
+
| 'PANTS_ON_FIRE'
|
|
143
|
+
| 'UNVERIFIABLE';
|
|
144
|
+
|
|
145
|
+
export interface FactCheckVerdict {
|
|
146
|
+
runId: string;
|
|
147
|
+
claimId: string;
|
|
148
|
+
verdict: {
|
|
149
|
+
label: VerdictLabel;
|
|
150
|
+
distribution: Record<string, number>;
|
|
151
|
+
explanation: string;
|
|
152
|
+
sources: Array<{
|
|
153
|
+
url: string;
|
|
154
|
+
title: string;
|
|
155
|
+
classification: 'SUPPORTS' | 'REFUTES' | 'UNRELATED';
|
|
156
|
+
}>;
|
|
157
|
+
};
|
|
158
|
+
auditUrl: string;
|
|
159
|
+
elapsedMs: number;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── Precis Types (Agentic RAG) ───────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
export interface PrecisQueryResult {
|
|
165
|
+
status: 'success' | 'blocked' | 'error';
|
|
166
|
+
traceId: string;
|
|
167
|
+
plan?: {
|
|
168
|
+
subtasks: Array<{ id: string; type: string; query: string }>;
|
|
169
|
+
reasoning: string;
|
|
170
|
+
};
|
|
171
|
+
report?: Record<string, unknown>;
|
|
172
|
+
evaluation?: {
|
|
173
|
+
relevancy: number;
|
|
174
|
+
trust: number;
|
|
175
|
+
exhaustivity: number;
|
|
176
|
+
hallucinationRate: number;
|
|
177
|
+
citationCoverage: number;
|
|
178
|
+
flaggedIssues: string[];
|
|
179
|
+
};
|
|
180
|
+
guardrail?: {
|
|
181
|
+
action: 'pass' | 'flag' | 'redact' | 'block';
|
|
182
|
+
issues: string[];
|
|
183
|
+
requiresHumanReview: boolean;
|
|
184
|
+
};
|
|
185
|
+
error?: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Work Order Types (Precis) ────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
export interface WorkOrder {
|
|
191
|
+
id: string;
|
|
192
|
+
tailNumber: string;
|
|
193
|
+
workOrderNumber: string;
|
|
194
|
+
date: string;
|
|
195
|
+
aircraftModel: string;
|
|
196
|
+
partNumbers: string[];
|
|
197
|
+
mechanicId: string;
|
|
198
|
+
station: string;
|
|
199
|
+
hoursWorked: number;
|
|
200
|
+
inspectorStamp: string;
|
|
201
|
+
adSbReferences: string[];
|
|
202
|
+
fieldsExtracted: number;
|
|
203
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Input Validation
|
|
3
|
+
*
|
|
4
|
+
* Wraps Zod schemas for validating MCP tool input arguments.
|
|
5
|
+
* Returns structured ValidationErrors on failure.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z, ZodError } from 'zod';
|
|
9
|
+
import { ValidationError } from './errors.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate tool arguments against a Zod schema.
|
|
13
|
+
* Throws ValidationError with field-level detail on failure.
|
|
14
|
+
*
|
|
15
|
+
* @param schema - Zod schema to validate against.
|
|
16
|
+
* @param args - Raw arguments from MCP tool call.
|
|
17
|
+
* @param toolName - Tool name for error context.
|
|
18
|
+
* @returns Parsed and typed arguments.
|
|
19
|
+
*/
|
|
20
|
+
export function validateArgs<T extends z.ZodType>(
|
|
21
|
+
schema: T,
|
|
22
|
+
args: unknown,
|
|
23
|
+
toolName: string,
|
|
24
|
+
): z.infer<T> {
|
|
25
|
+
try {
|
|
26
|
+
return schema.parse(args ?? {});
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error instanceof ZodError) {
|
|
29
|
+
const messages = error.errors
|
|
30
|
+
.map((e) => `${e.path.join('.') || '(root)'}: ${e.message}`)
|
|
31
|
+
.join('; ');
|
|
32
|
+
throw new ValidationError(toolName, messages);
|
|
33
|
+
}
|
|
34
|
+
throw new ValidationError(toolName, String(error));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate and sanitize a string input.
|
|
40
|
+
* Strips null bytes, normalizes Unicode, trims whitespace.
|
|
41
|
+
*/
|
|
42
|
+
export function sanitizeString(input: unknown, maxLength: number = 4000): string {
|
|
43
|
+
if (typeof input !== 'string') return '';
|
|
44
|
+
return input
|
|
45
|
+
.replace(/\u0000/g, '') // Strip null bytes
|
|
46
|
+
.normalize('NFKC') // Unicode normalization
|
|
47
|
+
.trim()
|
|
48
|
+
.slice(0, maxLength);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validate that a base64 string is well-formed and within size limits.
|
|
53
|
+
*/
|
|
54
|
+
export function validateBase64(input: unknown, maxBytes: number = 10 * 1024 * 1024): { valid: true; buffer: Buffer } | { valid: false; error: string } {
|
|
55
|
+
if (typeof input !== 'string') {
|
|
56
|
+
return { valid: false, error: 'Input must be a base64-encoded string' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Estimate decoded size: base64 is ~4/3 of original
|
|
60
|
+
if (input.length > maxBytes * 1.4) {
|
|
61
|
+
return { valid: false, error: `Base64 input exceeds maximum size of ${Math.round(maxBytes / 1024 / 1024)} MB` };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const buffer = Buffer.from(input, 'base64');
|
|
66
|
+
if (buffer.length > maxBytes) {
|
|
67
|
+
return { valid: false, error: `Decoded data exceeds maximum size of ${Math.round(maxBytes / 1024 / 1024)} MB` };
|
|
68
|
+
}
|
|
69
|
+
if (buffer.length === 0 && input.length > 0) {
|
|
70
|
+
return { valid: false, error: 'Invalid base64 encoding' };
|
|
71
|
+
}
|
|
72
|
+
return { valid: true, buffer };
|
|
73
|
+
} catch {
|
|
74
|
+
return { valid: false, error: 'Invalid base64 encoding' };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Clamp an integer value within [min, max], with a fallback for NaN.
|
|
80
|
+
*/
|
|
81
|
+
export function clampInt(raw: unknown, fallback: number, min: number, max: number): number {
|
|
82
|
+
const n = Number(raw ?? fallback);
|
|
83
|
+
if (Number.isNaN(n)) return fallback;
|
|
84
|
+
return Math.min(Math.max(Math.floor(n), min), max);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Common reusable Zod schemas ──────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/** Positive integer with bounds. */
|
|
90
|
+
export const intSchema = (min: number, max: number, fallback: number) =>
|
|
91
|
+
z.coerce.number().int().min(min).max(max).default(fallback);
|
|
92
|
+
|
|
93
|
+
/** Non-empty string with max length. */
|
|
94
|
+
export const stringSchema = (maxLength: number = 4000) =>
|
|
95
|
+
z.string().min(1).max(maxLength).transform((s) => sanitizeString(s, maxLength));
|
|
96
|
+
|
|
97
|
+
/** Language locale enum. */
|
|
98
|
+
export const localeSchema = z.enum(['en', 'fr']);
|
|
99
|
+
|
|
100
|
+
/** Base64-encoded data. */
|
|
101
|
+
export const base64Schema = z.string().min(1);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unified-mcp/deeppipe",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DeepPipe integration for unified MCP server — document ingestion, full-text search, and RAG chat via @kordabjinan/deeppipe",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.json",
|
|
10
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@unified-mcp/core": "^0.1.0",
|
|
16
|
+
"@kordabjinan/deeppipe": "^0.1.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"vitest": "^2.0.0"
|
|
20
|
+
}
|
|
21
|
+
}
|