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,427 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Agentic Pipelines — Main Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Integrates piste, clinical-intake, precis-agentic-pipeline,
|
|
6
|
+
* DeepPipe, and @kordabjinan/deeppipe into a single MCP server.
|
|
7
|
+
*
|
|
8
|
+
* Transport: stdio (primary) | SSE (secondary)
|
|
9
|
+
* LLM: Multi-provider — OpenAI, Anthropic, Google, DeepSeek, Groq, Ollama, OpenRouter, Azure, Custom
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
13
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
+
import {
|
|
15
|
+
CallToolRequestSchema,
|
|
16
|
+
ListToolsRequestSchema,
|
|
17
|
+
ListResourcesRequestSchema,
|
|
18
|
+
ListPromptsRequestSchema,
|
|
19
|
+
ReadResourceRequestSchema,
|
|
20
|
+
GetPromptRequestSchema,
|
|
21
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
loadConfig,
|
|
25
|
+
createLogger,
|
|
26
|
+
createRateLimiter,
|
|
27
|
+
listProviders,
|
|
28
|
+
PROVIDER_DEFAULTS,
|
|
29
|
+
PythonServiceManager,
|
|
30
|
+
LLMNotConfiguredError,
|
|
31
|
+
InternalError,
|
|
32
|
+
type Config,
|
|
33
|
+
type Logger,
|
|
34
|
+
type RateLimiter,
|
|
35
|
+
type ToolDefinition,
|
|
36
|
+
type ResourceDefinition,
|
|
37
|
+
type PromptDefinition,
|
|
38
|
+
} from '@unified-mcp/core';
|
|
39
|
+
|
|
40
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
41
|
+
// Bootstrap
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
const config: Config = loadConfig();
|
|
45
|
+
const logger: Logger = createLogger(config.MCP_LOG_LEVEL);
|
|
46
|
+
const rateLimiter: RateLimiter = createRateLimiter(config.MCP_RATE_LIMIT_ENABLED, config.MCP_RATE_LIMIT_MAX_RPS);
|
|
47
|
+
const pythonManager = new PythonServiceManager(logger);
|
|
48
|
+
|
|
49
|
+
logger.info(`MCP Agentic Pipelines v${config.MCP_SERVER_VERSION} starting...`);
|
|
50
|
+
logger.info(`Transport: ${config.MCP_TRANSPORT}`);
|
|
51
|
+
logger.info(`LLM Default Provider: ${config.LLM_DEFAULT_PROVIDER}`);
|
|
52
|
+
logger.info(`Log level: ${config.MCP_LOG_LEVEL}`);
|
|
53
|
+
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
55
|
+
// Tool, Resource, Prompt registries
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
57
|
+
|
|
58
|
+
const tools: ToolDefinition[] = [];
|
|
59
|
+
const resources: ResourceDefinition[] = [];
|
|
60
|
+
const prompts: PromptDefinition[] = [];
|
|
61
|
+
|
|
62
|
+
// Tool handler map: toolName → handler function
|
|
63
|
+
const toolHandlers = new Map<string, (args: unknown) => Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }>>();
|
|
64
|
+
|
|
65
|
+
// Resource handler map: uri pattern → handler
|
|
66
|
+
const resourceHandlers = new Map<string, (uri: string) => Promise<{ contents: Array<{ uri: string; mimeType: string; text?: string }> }>>();
|
|
67
|
+
|
|
68
|
+
// Prompt handler map: promptName → handler
|
|
69
|
+
const promptHandlers = new Map<string, (args?: Record<string, string>) => Promise<{ messages: Array<{ role: 'user' | 'assistant'; content: { type: 'text'; text: string } }> }>>();
|
|
70
|
+
|
|
71
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
72
|
+
// Built-in tools (always available)
|
|
73
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
74
|
+
|
|
75
|
+
// Tool: mcp_health — check server and integration status
|
|
76
|
+
tools.push({
|
|
77
|
+
name: 'mcp_health',
|
|
78
|
+
description: 'Check the health status of the MCP server and all integration packages. Returns provider configuration, available tools count, and service reachability.',
|
|
79
|
+
inputSchema: { type: 'object', properties: {} },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
toolHandlers.set('mcp_health', async () => {
|
|
83
|
+
const health = {
|
|
84
|
+
server: {
|
|
85
|
+
name: config.MCP_SERVER_NAME,
|
|
86
|
+
version: config.MCP_SERVER_VERSION,
|
|
87
|
+
transport: config.MCP_TRANSPORT,
|
|
88
|
+
uptime: process.uptime(),
|
|
89
|
+
},
|
|
90
|
+
tools: {
|
|
91
|
+
total: tools.length,
|
|
92
|
+
registered: Array.from(toolHandlers.keys()),
|
|
93
|
+
},
|
|
94
|
+
resources: resources.length,
|
|
95
|
+
prompts: prompts.length,
|
|
96
|
+
llm: {
|
|
97
|
+
defaultProvider: config.LLM_DEFAULT_PROVIDER,
|
|
98
|
+
deepPipe: {
|
|
99
|
+
provider: config.deepPipeLLM.provider,
|
|
100
|
+
model: config.deepPipeLLM.model,
|
|
101
|
+
configured: !!config.deepPipeLLM.apiKey,
|
|
102
|
+
},
|
|
103
|
+
piste: {
|
|
104
|
+
provider: config.pisteLLM.provider,
|
|
105
|
+
model: config.pisteLLM.model,
|
|
106
|
+
configured: !!config.pisteLLM.apiKey,
|
|
107
|
+
},
|
|
108
|
+
precis: {
|
|
109
|
+
provider: config.precisLLM.provider,
|
|
110
|
+
model: config.precisLLM.model,
|
|
111
|
+
configured: !!config.precisLLM.apiKey,
|
|
112
|
+
},
|
|
113
|
+
clinical: {
|
|
114
|
+
provider: config.clinicalLLM.provider,
|
|
115
|
+
model: config.clinicalLLM.model,
|
|
116
|
+
configured: !!config.clinicalLLM.apiKey,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
services: {
|
|
120
|
+
piste: { configured: !!config.PISTE_API_URL },
|
|
121
|
+
precis: { configured: !!config.PRECIS_API_URL },
|
|
122
|
+
clinical: {
|
|
123
|
+
stt: { provider: config.CLINICAL_STT_PROVIDER, configured: !!config.GROQ_API_KEY },
|
|
124
|
+
tts: { provider: config.CLINICAL_TTS_PROVIDER, configured: !!config.ELEVENLABS_API_KEY },
|
|
125
|
+
},
|
|
126
|
+
deepPipe: { indexPath: config.DEEPPIPE_INDEX_PATH },
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(health, null, 2) }] };
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Tool: mcp_list_providers — list all supported LLM providers with defaults
|
|
134
|
+
tools.push({
|
|
135
|
+
name: 'mcp_list_providers',
|
|
136
|
+
description: 'List all supported LLM providers with their default models and base URLs. Use this to see which providers are available for configuration.',
|
|
137
|
+
inputSchema: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
filter: {
|
|
141
|
+
type: 'string',
|
|
142
|
+
description: 'Optional: filter providers by name substring (e.g. "open" matches openai, openrouter)',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
toolHandlers.set('mcp_list_providers', async (args: unknown) => {
|
|
149
|
+
const filter = typeof (args as any)?.filter === 'string' ? (args as any).filter.toLowerCase() : '';
|
|
150
|
+
|
|
151
|
+
const providers = listProviders()
|
|
152
|
+
.filter((p) => !filter || p.includes(filter))
|
|
153
|
+
.map((provider) => {
|
|
154
|
+
const defaults = PROVIDER_DEFAULTS[provider as keyof typeof PROVIDER_DEFAULTS];
|
|
155
|
+
const isDefault = provider === config.LLM_DEFAULT_PROVIDER;
|
|
156
|
+
|
|
157
|
+
// Check which components are using this provider
|
|
158
|
+
const usedBy: string[] = [];
|
|
159
|
+
if (config.deepPipeLLM.provider === provider) usedBy.push('deeppipe');
|
|
160
|
+
if (config.pisteLLM.provider === provider) usedBy.push('piste');
|
|
161
|
+
if (config.precisLLM.provider === provider) usedBy.push('precis');
|
|
162
|
+
if (config.clinicalLLM.provider === provider) usedBy.push('clinical');
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
provider,
|
|
166
|
+
defaultModel: defaults.defaultModel,
|
|
167
|
+
baseUrl: defaults.baseUrl || '(user-specified)',
|
|
168
|
+
isDefaultProvider: isDefault,
|
|
169
|
+
usedByComponents: usedBy.length > 0 ? usedBy : ['(none)'],
|
|
170
|
+
howToConfigure: isDefault
|
|
171
|
+
? `Set LLM_DEFAULT_API_KEY in .env`
|
|
172
|
+
: `Set LLM_DEFAULT_PROVIDER=${provider} and LLM_DEFAULT_API_KEY in .env, or set per-component like DEEPPIPE_LLM_PROVIDER=${provider}`,
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(providers, null, 2) }] };
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
180
|
+
// Lazy-load integration packages
|
|
181
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
182
|
+
|
|
183
|
+
async function loadIntegrations(): Promise<void> {
|
|
184
|
+
// Each integration is loaded with a 10s timeout so one can't hang the server.
|
|
185
|
+
|
|
186
|
+
const withTimeout = async <T>(name: string, fn: () => Promise<T>): Promise<T | null> => {
|
|
187
|
+
try {
|
|
188
|
+
return await Promise.race([
|
|
189
|
+
fn(),
|
|
190
|
+
new Promise<null>((_, reject) => setTimeout(() => reject(new Error('TIMEOUT')), 10000)),
|
|
191
|
+
]);
|
|
192
|
+
} catch (err: any) {
|
|
193
|
+
logger.warn(`Integration skipped: ${name} — ${err?.message || err}`);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// -- DeepPipe (direct engine import) --
|
|
199
|
+
await withTimeout('deeppipe', async () => {
|
|
200
|
+
const { registerDeepPipe } = await import('@unified-mcp/deeppipe');
|
|
201
|
+
registerDeepPipe({ config, logger, rateLimiter, tools, resources, prompts, toolHandlers, resourceHandlers, promptHandlers });
|
|
202
|
+
logger.info('Integration loaded: deeppipe (engine direct)');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// -- Piste (Python bridge to DSPy pipeline) --
|
|
206
|
+
await withTimeout('piste', async () => {
|
|
207
|
+
const { registerPiste } = await import('@unified-mcp/piste');
|
|
208
|
+
registerPiste({ config, logger, rateLimiter, tools, resources, prompts, toolHandlers, resourceHandlers, promptHandlers, pythonManager });
|
|
209
|
+
logger.info('Integration loaded: piste (Python bridge)');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// -- Precis (Python bridge to RAG pipeline) --
|
|
213
|
+
await withTimeout('precis', async () => {
|
|
214
|
+
const { registerPrecis } = await import('@unified-mcp/precis');
|
|
215
|
+
registerPrecis({ config, logger, rateLimiter, tools, resources, prompts, toolHandlers, resourceHandlers, promptHandlers, pythonManager });
|
|
216
|
+
logger.info('Integration loaded: precis (Python bridge)');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// -- Clinical Intake (native TypeScript pipeline) --
|
|
220
|
+
await withTimeout('clinical', async () => {
|
|
221
|
+
const { registerClinical } = await import('@unified-mcp/clinical');
|
|
222
|
+
registerClinical({ config, logger, rateLimiter, tools, resources, prompts, toolHandlers, resourceHandlers, promptHandlers });
|
|
223
|
+
logger.info('Integration loaded: clinical (native pipeline)');
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
228
|
+
// Dependency guard — verifies setup.mjs was run before starting
|
|
229
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
230
|
+
|
|
231
|
+
import { existsSync } from 'fs';
|
|
232
|
+
import { resolve } from 'path';
|
|
233
|
+
|
|
234
|
+
function verifyDependencies(): void {
|
|
235
|
+
const targetDir = resolve(process.cwd(), '.python-packages');
|
|
236
|
+
if (!existsSync(targetDir)) {
|
|
237
|
+
process.stderr.write('\n⚠ .python-packages/ not found.\n');
|
|
238
|
+
process.stderr.write(' Run: node setup.mjs\n\n');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// Quick file-system check — setup.mjs already verified everything
|
|
242
|
+
const keyMods = ['dspy', 'litellm', 'fastapi', 'nltk'];
|
|
243
|
+
const missing = keyMods.filter(m => !existsSync(resolve(targetDir, m)));
|
|
244
|
+
if (missing.length > 0) {
|
|
245
|
+
process.stderr.write(`\n⚠ Missing packages: ${missing.join(', ')}\n`);
|
|
246
|
+
process.stderr.write(' Run: node setup.mjs\n\n');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
251
|
+
// MCP Server Setup
|
|
252
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
253
|
+
|
|
254
|
+
async function main(): Promise<void> {
|
|
255
|
+
verifyDependencies();
|
|
256
|
+
|
|
257
|
+
// Load all integration packages
|
|
258
|
+
await loadIntegrations();
|
|
259
|
+
|
|
260
|
+
logger.info(`Registered ${tools.length} tools, ${resources.length} resources, ${prompts.length} prompts`);
|
|
261
|
+
|
|
262
|
+
// Create MCP server
|
|
263
|
+
const server = new Server(
|
|
264
|
+
{
|
|
265
|
+
name: config.MCP_SERVER_NAME,
|
|
266
|
+
version: config.MCP_SERVER_VERSION,
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
capabilities: {
|
|
270
|
+
tools: {},
|
|
271
|
+
resources: {},
|
|
272
|
+
prompts: {},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// ── List Tools ─────────────────────────────────────────────────
|
|
278
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
279
|
+
tools: tools.map((t) => ({
|
|
280
|
+
name: t.name,
|
|
281
|
+
description: t.description,
|
|
282
|
+
inputSchema: t.inputSchema,
|
|
283
|
+
})),
|
|
284
|
+
}));
|
|
285
|
+
|
|
286
|
+
// ── Call Tool ──────────────────────────────────────────────────
|
|
287
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
288
|
+
const { name, arguments: args } = request.params;
|
|
289
|
+
const handler = toolHandlers.get(name);
|
|
290
|
+
|
|
291
|
+
if (!handler) {
|
|
292
|
+
return {
|
|
293
|
+
content: [{ type: 'text' as const, text: JSON.stringify({ code: 'UNKNOWN_TOOL', message: `Tool "${name}" is not registered.` }) }],
|
|
294
|
+
isError: true,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
// Rate limit check
|
|
300
|
+
rateLimiter.check(name);
|
|
301
|
+
|
|
302
|
+
logger.debug(`Tool called: ${name}`, name);
|
|
303
|
+
const result = await handler(args ?? {});
|
|
304
|
+
return result;
|
|
305
|
+
} catch (error: any) {
|
|
306
|
+
logger.error(`Tool error: ${name}`, name, error.message);
|
|
307
|
+
|
|
308
|
+
// If it's already an MCPToolError, use its response format
|
|
309
|
+
if (error?.toMCPResponse) {
|
|
310
|
+
return error.toMCPResponse();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return new InternalError(error?.message ?? String(error)).toMCPResponse();
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// ── List Resources ─────────────────────────────────────────────
|
|
318
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
319
|
+
resources: resources.map((r) => ({
|
|
320
|
+
uri: r.uri,
|
|
321
|
+
name: r.name,
|
|
322
|
+
description: r.description,
|
|
323
|
+
mimeType: r.mimeType,
|
|
324
|
+
})),
|
|
325
|
+
}));
|
|
326
|
+
|
|
327
|
+
// ── Read Resource ──────────────────────────────────────────────
|
|
328
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
329
|
+
const { uri } = request.params;
|
|
330
|
+
|
|
331
|
+
// Try exact match first, then pattern match
|
|
332
|
+
for (const [pattern, handler] of resourceHandlers) {
|
|
333
|
+
// Convert pattern like 'deeppipe://documents/{id}' to regex
|
|
334
|
+
const regex = new RegExp('^' + pattern.replace(/\{(\w+)\}/g, '([^/]+)') + '$');
|
|
335
|
+
if (regex.test(uri)) {
|
|
336
|
+
try {
|
|
337
|
+
return await handler(uri);
|
|
338
|
+
} catch (error: any) {
|
|
339
|
+
return {
|
|
340
|
+
contents: [{
|
|
341
|
+
uri,
|
|
342
|
+
mimeType: 'application/json',
|
|
343
|
+
text: JSON.stringify({ error: error?.message ?? 'Resource read failed' }),
|
|
344
|
+
}],
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
contents: [{
|
|
352
|
+
uri,
|
|
353
|
+
mimeType: 'application/json',
|
|
354
|
+
text: JSON.stringify({ error: `Resource not found: ${uri}` }),
|
|
355
|
+
}],
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// ── List Prompts ───────────────────────────────────────────────
|
|
360
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
361
|
+
prompts: prompts.map((p) => ({
|
|
362
|
+
name: p.name,
|
|
363
|
+
description: p.description,
|
|
364
|
+
arguments: p.arguments,
|
|
365
|
+
})),
|
|
366
|
+
}));
|
|
367
|
+
|
|
368
|
+
// ── Get Prompt ─────────────────────────────────────────────────
|
|
369
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
370
|
+
const { name, arguments: args } = request.params;
|
|
371
|
+
const handler = promptHandlers.get(name);
|
|
372
|
+
|
|
373
|
+
if (!handler) {
|
|
374
|
+
return {
|
|
375
|
+
messages: [{
|
|
376
|
+
role: 'user' as const,
|
|
377
|
+
content: { type: 'text' as const, text: `Prompt "${name}" not found.` },
|
|
378
|
+
}],
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
return await handler(args as Record<string, string> | undefined);
|
|
384
|
+
} catch (error: any) {
|
|
385
|
+
return {
|
|
386
|
+
messages: [{
|
|
387
|
+
role: 'user' as const,
|
|
388
|
+
content: { type: 'text' as const, text: `Error loading prompt: ${error?.message ?? error}` },
|
|
389
|
+
}],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// ── Connect Transport ──────────────────────────────────────────
|
|
395
|
+
if (config.MCP_TRANSPORT === 'sse') {
|
|
396
|
+
// SSE transport requires a running HTTP server; for self-contained usage, default to stdio
|
|
397
|
+
logger.info('SSE transport not yet configured for standalone mode. Using stdio.');
|
|
398
|
+
const transport = new StdioServerTransport();
|
|
399
|
+
await server.connect(transport);
|
|
400
|
+
logger.info('MCP Server connected via stdio (fallback from SSE)');
|
|
401
|
+
} else {
|
|
402
|
+
const transport = new StdioServerTransport();
|
|
403
|
+
await server.connect(transport);
|
|
404
|
+
logger.info('MCP Server connected via stdio');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
logger.info('Ready for requests.');
|
|
408
|
+
process.stderr.write('__MCP_READY__\n'); // signal test client / launchers
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ── Graceful shutdown ────────────────────────────────────────────────
|
|
412
|
+
process.on('SIGINT', () => {
|
|
413
|
+
logger.info('Shutting down...');
|
|
414
|
+
pythonManager.stopAll();
|
|
415
|
+
process.exit(0);
|
|
416
|
+
});
|
|
417
|
+
process.on('SIGTERM', () => {
|
|
418
|
+
logger.info('Shutting down...');
|
|
419
|
+
pythonManager.stopAll();
|
|
420
|
+
process.exit(0);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// ── Start ────────────────────────────────────────────────────────────
|
|
424
|
+
main().catch((err) => {
|
|
425
|
+
logger.error('Fatal startup error', undefined, err);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"composite": true
|
|
7
|
+
},
|
|
8
|
+
"include": ["src/**/*"],
|
|
9
|
+
"exclude": ["dist"],
|
|
10
|
+
"references": [
|
|
11
|
+
{ "path": "../core" },
|
|
12
|
+
{ "path": "../deeppipe" },
|
|
13
|
+
{ "path": "../piste" },
|
|
14
|
+
{ "path": "../precis" },
|
|
15
|
+
{ "path": "../clinical" }
|
|
16
|
+
]
|
|
17
|
+
}
|
package/setup.mjs
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ╔══════════════════════════════════════════════════════════════════╗
|
|
4
|
+
* ║ Unified MCP Server — Setup (uv-powered) ║
|
|
5
|
+
* ║ ║
|
|
6
|
+
* ║ Uses uv (Astral) — the industry standard for MCP servers. ║
|
|
7
|
+
* ║ No Python pre-installed. No pip. No MAX_PATH issues. ║
|
|
8
|
+
* ║ One command: node setup.mjs ║
|
|
9
|
+
* ╚══════════════════════════════════════════════════════════════════╝
|
|
10
|
+
*/
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import { existsSync, readFileSync, mkdirSync } from 'node:fs';
|
|
13
|
+
import { resolve, dirname } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const ROOT = resolve(__dirname);
|
|
18
|
+
const UV = resolve(ROOT, '.vendor', process.platform === 'win32' ? 'uv.exe' : 'uv');
|
|
19
|
+
const TARGET = resolve(ROOT, '.python-packages');
|
|
20
|
+
const ENV_FILE = resolve(ROOT, '.env');
|
|
21
|
+
|
|
22
|
+
// ANSI
|
|
23
|
+
const T='\x1b[32m✓\x1b[0m', X='\x1b[31m✗\x1b[0m', W='\x1b[33m⚠\x1b[0m';
|
|
24
|
+
const B='\x1b[1m', D='\x1b[2m', C='\x1b[36m', R='\x1b[0m';
|
|
25
|
+
const HR=D+'─'.repeat(55)+R;
|
|
26
|
+
let fail=false;
|
|
27
|
+
function head(t) { console.log(`\n${C}${B} ${t}${R}\n ${HR}`); }
|
|
28
|
+
function ok(m) { console.log(` ${T} ${m}`); }
|
|
29
|
+
function err(m) { console.log(` ${X} ${m}`); fail=true; }
|
|
30
|
+
function warn(m){ console.log(` ${W} ${m}`); }
|
|
31
|
+
function info(m){ console.log(` ${m}`); }
|
|
32
|
+
// execSync with shell — reliable on all platforms
|
|
33
|
+
function sh(cmd, opts={}) { return execSync(cmd, { cwd:ROOT, stdio:'pipe', encoding:'utf8', timeout:300_000, ...opts }); }
|
|
34
|
+
|
|
35
|
+
console.log(`\n${C}${B} ╔══════════════════════════════════════════════════╗${R}`);
|
|
36
|
+
console.log(`${C}${B} ║ MCP Agentic Pipelines — Setup (uv) ║${R}`);
|
|
37
|
+
console.log(`${C}${B} ║ 5 integrations · 31 tools · One command ║${R}`);
|
|
38
|
+
console.log(`${C}${B} ╚══════════════════════════════════════════════════╝${R}`);
|
|
39
|
+
|
|
40
|
+
// 1. Node.js
|
|
41
|
+
head('Node.js Runtime');
|
|
42
|
+
const nodeV = process.version;
|
|
43
|
+
if (parseInt(nodeV.slice(1)) >= 18) ok(`Node.js ${nodeV}`);
|
|
44
|
+
else { err(`Need Node.js 18+. Found ${nodeV}`); process.exit(1); }
|
|
45
|
+
|
|
46
|
+
// 2. npm
|
|
47
|
+
head('Node.js Packages (npm)');
|
|
48
|
+
try { sh('node -e "require.resolve(\\"@unified-mcp/core\\")"'); ok('npm ready'); }
|
|
49
|
+
catch {
|
|
50
|
+
info('Running npm install...');
|
|
51
|
+
try { sh('npm install --prefer-offline', { stdio:'inherit' }); ok('npm complete'); }
|
|
52
|
+
catch(e) { err(e.message); process.exit(1); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 3. uv
|
|
56
|
+
head('Package Manager (uv)');
|
|
57
|
+
if (!existsSync(UV)) {
|
|
58
|
+
err('.vendor/uv.exe not found. Download it:');
|
|
59
|
+
info('https://github.com/astral-sh/uv/releases/latest');
|
|
60
|
+
info('Place uv.exe in .vendor/ and re-run.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
ok(`uv ${sh(`"${UV}" --version`).trim()}`);
|
|
64
|
+
|
|
65
|
+
// 3b. Ensure proper Python (uv managed — no Store Python SSL issues)
|
|
66
|
+
head('Python Runtime');
|
|
67
|
+
const isStorePython = (() => {
|
|
68
|
+
try {
|
|
69
|
+
const pyPath = sh(`"${UV}" python find 3.11`).trim();
|
|
70
|
+
return pyPath.includes('WindowsApps'); // Microsoft Store Python has broken SSL
|
|
71
|
+
} catch { return false; }
|
|
72
|
+
})();
|
|
73
|
+
|
|
74
|
+
if (isStorePython) {
|
|
75
|
+
warn('Microsoft Store Python detected — SSL is broken (sandboxed).');
|
|
76
|
+
info('Installing uv-managed Python 3.11 (proper SSL, no sandbox)...');
|
|
77
|
+
info(' This downloads ~25 MB — may take a minute on slow connections.');
|
|
78
|
+
try {
|
|
79
|
+
sh(`"${UV}" python install 3.11`, { stdio:'inherit', timeout: 180_000 });
|
|
80
|
+
const managed = sh(`"${UV}" python find 3.11`).trim();
|
|
81
|
+
ok(`uv-managed Python ready → ${managed}`);
|
|
82
|
+
} catch(e) {
|
|
83
|
+
warn(`uv-managed Python install failed: ${e.message.split('\n')[0]}`);
|
|
84
|
+
info('Python tools (piste, precis) will be unavailable.');
|
|
85
|
+
info('Fix: install python.org Python from https://python.org');
|
|
86
|
+
info('Then re-run: node setup.mjs');
|
|
87
|
+
// Non-fatal — npm-based tools still work
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
const pyPath = sh(`"${UV}" python find 3.11`).trim();
|
|
91
|
+
ok(`Python 3.11 → ${pyPath}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 4. Python packages
|
|
95
|
+
head('Python Packages (uv pip)');
|
|
96
|
+
mkdirSync(TARGET, { recursive: true });
|
|
97
|
+
const PKGS = ['dspy-ai','litellm','python-dotenv','fastapi','uvicorn','pydantic','numpy','nltk','sqlalchemy','httpx'];
|
|
98
|
+
const MODS = ['dspy','litellm','dotenv','fastapi','uvicorn','pydantic','numpy','nltk','sqlalchemy','httpx'];
|
|
99
|
+
|
|
100
|
+
info(`Installing ${PKGS.length} packages (uv is 10-100x faster than pip)...`);
|
|
101
|
+
try {
|
|
102
|
+
sh(`"${UV}" pip install ${PKGS.join(' ')} --target "${TARGET}" --python 3.11`, { stdio:'inherit' });
|
|
103
|
+
// Verify by checking file system (avoids Store Python SSL issues)
|
|
104
|
+
let good = true;
|
|
105
|
+
for (const mod of MODS) {
|
|
106
|
+
if (existsSync(resolve(TARGET, mod)) || existsSync(resolve(TARGET, `${mod}.py`))) {
|
|
107
|
+
ok(mod.padEnd(14));
|
|
108
|
+
} else {
|
|
109
|
+
// Some packages install under a different name (e.g. python-dotenv → dotenv)
|
|
110
|
+
const altCheck = [mod, mod.replace(/-/g, '_'), `python_${mod}`];
|
|
111
|
+
const found = altCheck.some(a => existsSync(resolve(TARGET, a)) || existsSync(resolve(TARGET, `${a}.py`)));
|
|
112
|
+
if (found) ok(mod.padEnd(14));
|
|
113
|
+
else { err(mod.padEnd(14)); good = false; }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (good) ok(`All ${PKGS.length} packages verified`);
|
|
117
|
+
} catch(e) {
|
|
118
|
+
err(`uv install failed: ${e.message.split('\n')[0]}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (fail) process.exit(1);
|
|
122
|
+
|
|
123
|
+
// 5. .env
|
|
124
|
+
head('Environment (.env)');
|
|
125
|
+
const KEYS = ['DEEPSEEK_API_KEY','OPENAI_API_KEY','GROQ_API_KEY','ELEVENLABS_API_KEY','TAVILY_API_KEY','SERPER_API_KEY','GOOGLE_CSE_API_KEY','GOOGLE_CSE_ID'];
|
|
126
|
+
if (existsSync(ENV_FILE)) {
|
|
127
|
+
const env = readFileSync(ENV_FILE, 'utf8'); let n = 0;
|
|
128
|
+
for (const k of KEYS) {
|
|
129
|
+
if (new RegExp(`^${k}=.+`,'m').test(env)) { ok(k.padEnd(22)); n++; }
|
|
130
|
+
else warn(`${k.padEnd(22)}${D}(optional)${R}`);
|
|
131
|
+
}
|
|
132
|
+
info(`\n ${n}/${KEYS.length} keys configured`);
|
|
133
|
+
} else { warn('.env not found — create one with your API keys'); }
|
|
134
|
+
|
|
135
|
+
// Summary
|
|
136
|
+
console.log(`\n${HR}`);
|
|
137
|
+
if (fail) { console.log(`\n${X} Setup failed. Fix and re-run: node setup.mjs\n`); process.exit(1); }
|
|
138
|
+
console.log(`\n${T} ${B}Setup complete — all dependencies ready.${R}\n`);
|
|
139
|
+
console.log(` ${B}Start server:${R} ${C}npx mcp-agentic-pipelines${R}`);
|
|
140
|
+
console.log(` ${B}Run tests:${R} ${C}node test.mjs${R}`);
|
|
141
|
+
console.log(` ${B}MCP config:${R} ${D}{"command":"npx","args":["mcp-agentic-pipelines"]}${R}\n`);
|