nvidia-nim-mcp 1.0.0

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/dist/client.js ADDED
@@ -0,0 +1,178 @@
1
+ import axios from "axios";
2
+ import axiosRetry from "axios-retry";
3
+ import { getConfig } from "./config.js";
4
+ import { logger } from "./logger.js";
5
+ const config = getConfig();
6
+ // ─── Rate Limiter ─────────────────────────────────────────────────────────────
7
+ class RateLimiter {
8
+ requests = [];
9
+ maxRequests;
10
+ windowMs = 60_000;
11
+ constructor(maxRequests) {
12
+ this.maxRequests = maxRequests;
13
+ }
14
+ async acquire() {
15
+ const now = Date.now();
16
+ this.requests = this.requests.filter((t) => now - t < this.windowMs);
17
+ if (this.requests.length >= this.maxRequests) {
18
+ const oldest = this.requests[0];
19
+ const waitMs = this.windowMs - (now - oldest) + 10;
20
+ logger.warn(`Rate limit reached. Waiting ${waitMs}ms`);
21
+ await new Promise((res) => setTimeout(res, waitMs));
22
+ return this.acquire();
23
+ }
24
+ this.requests.push(now);
25
+ }
26
+ }
27
+ // ─── Client ───────────────────────────────────────────────────────────────────
28
+ export class NIMClient {
29
+ http;
30
+ rateLimiter;
31
+ constructor() {
32
+ this.rateLimiter = new RateLimiter(config.MAX_REQUESTS_PER_MINUTE);
33
+ this.http = axios.create({
34
+ baseURL: config.NVIDIA_NIM_BASE_URL,
35
+ timeout: config.REQUEST_TIMEOUT_MS,
36
+ headers: {
37
+ Authorization: `Bearer ${config.NVIDIA_API_KEY}`,
38
+ "Content-Type": "application/json",
39
+ "User-Agent": `${config.MCP_SERVER_NAME}/${config.MCP_SERVER_VERSION}`,
40
+ },
41
+ });
42
+ axiosRetry(this.http, {
43
+ retries: config.MAX_RETRIES,
44
+ retryDelay: (retryCount) => {
45
+ const delay = config.RETRY_DELAY_MS * Math.pow(2, retryCount - 1);
46
+ logger.warn(`Retry attempt ${retryCount}, waiting ${delay}ms`);
47
+ return delay;
48
+ },
49
+ retryCondition: (error) => {
50
+ const status = error.response?.status;
51
+ // Retry on 429, 500, 502, 503, 504
52
+ return (axiosRetry.isNetworkError(error) ||
53
+ status === 429 ||
54
+ (status !== undefined && status >= 500));
55
+ },
56
+ onRetry: (retryCount, error) => {
57
+ logger.warn(`Retrying request (${retryCount}/${config.MAX_RETRIES})`, {
58
+ status: error.response?.status,
59
+ message: error.message,
60
+ });
61
+ },
62
+ });
63
+ // Request interceptor for logging
64
+ this.http.interceptors.request.use((req) => {
65
+ logger.debug("NIM API request", {
66
+ method: req.method?.toUpperCase(),
67
+ url: req.url,
68
+ model: req.data?.model,
69
+ });
70
+ return req;
71
+ });
72
+ // Response interceptor for logging
73
+ this.http.interceptors.response.use((res) => {
74
+ logger.debug("NIM API response", {
75
+ status: res.status,
76
+ usage: res.data?.usage,
77
+ });
78
+ return res;
79
+ }, (err) => {
80
+ logger.error("NIM API error", {
81
+ status: err.response?.status,
82
+ data: err.response?.data,
83
+ message: err.message,
84
+ });
85
+ return Promise.reject(this.normalizeError(err));
86
+ });
87
+ }
88
+ normalizeError(error) {
89
+ const status = error.response?.status;
90
+ const data = error.response?.data;
91
+ const apiMsg = data?.detail ||
92
+ data?.message ||
93
+ error.message;
94
+ if (status === 401)
95
+ return new Error(`Authentication failed: ${apiMsg}`);
96
+ if (status === 403)
97
+ return new Error(`Authorization failed: ${apiMsg}`);
98
+ if (status === 404)
99
+ return new Error(`Model not found: ${apiMsg}`);
100
+ if (status === 422)
101
+ return new Error(`Invalid request: ${apiMsg}`);
102
+ if (status === 429)
103
+ return new Error(`Rate limit exceeded: ${apiMsg}`);
104
+ if (status && status >= 500)
105
+ return new Error(`NVIDIA NIM server error (${status}): ${apiMsg}`);
106
+ return new Error(`NIM API error: ${apiMsg}`);
107
+ }
108
+ async chatCompletion(request) {
109
+ await this.rateLimiter.acquire();
110
+ // Apply defaults
111
+ const payload = {
112
+ temperature: config.DEFAULT_TEMPERATURE,
113
+ top_p: config.DEFAULT_TOP_P,
114
+ max_tokens: Math.min(request.max_tokens ?? config.DEFAULT_MAX_TOKENS, config.MAX_TOKENS_PER_REQUEST),
115
+ ...request,
116
+ stream: false,
117
+ };
118
+ const { data } = await this.http.post("/chat/completions", payload);
119
+ return data;
120
+ }
121
+ async *chatCompletionStream(request) {
122
+ await this.rateLimiter.acquire();
123
+ const payload = {
124
+ temperature: config.DEFAULT_TEMPERATURE,
125
+ top_p: config.DEFAULT_TOP_P,
126
+ max_tokens: Math.min(request.max_tokens ?? config.DEFAULT_MAX_TOKENS, config.MAX_TOKENS_PER_REQUEST),
127
+ ...request,
128
+ stream: true,
129
+ };
130
+ const response = await this.http.post("/chat/completions", payload, {
131
+ responseType: "stream",
132
+ });
133
+ const stream = response.data;
134
+ for await (const chunk of stream) {
135
+ const lines = chunk
136
+ .toString("utf8")
137
+ .split("\n")
138
+ .filter((l) => l.startsWith("data: "));
139
+ for (const line of lines) {
140
+ const json = line.slice(6).trim();
141
+ if (json === "[DONE]")
142
+ return;
143
+ try {
144
+ const parsed = JSON.parse(json);
145
+ const content = parsed.choices?.[0]?.delta?.content;
146
+ if (content)
147
+ yield content;
148
+ }
149
+ catch {
150
+ // skip malformed chunks
151
+ }
152
+ }
153
+ }
154
+ }
155
+ async embeddings(request) {
156
+ await this.rateLimiter.acquire();
157
+ const { data } = await this.http.post("/embeddings", request);
158
+ return data;
159
+ }
160
+ async rerank(request) {
161
+ await this.rateLimiter.acquire();
162
+ const { data } = await this.http.post("/ranking", request);
163
+ return data;
164
+ }
165
+ async listModels() {
166
+ await this.rateLimiter.acquire();
167
+ try {
168
+ const { data } = await this.http.get("/models");
169
+ return data.data.map((m) => m.id);
170
+ }
171
+ catch {
172
+ // If the API doesn't support listing, return catalog
173
+ return Object.keys(NIM_MODELS_EXPORT);
174
+ }
175
+ }
176
+ }
177
+ // Re-export for convenience
178
+ import { NIM_MODELS as NIM_MODELS_EXPORT } from "./models.js";
@@ -0,0 +1,24 @@
1
+ export declare class ConfigError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare function getConfig(): {
5
+ NVIDIA_API_KEY?: string;
6
+ NVIDIA_NIM_BASE_URL?: string;
7
+ NVIDIA_CHAT_MODEL?: string;
8
+ NVIDIA_EMBEDDINGS_MODEL?: string;
9
+ NVIDIA_RERANKING_MODEL?: string;
10
+ MCP_SERVER_NAME?: string;
11
+ MCP_SERVER_VERSION?: string;
12
+ MCP_SERVER_PORT?: number;
13
+ LOG_LEVEL?: "error" | "warn" | "info" | "debug" | "silly";
14
+ MAX_REQUESTS_PER_MINUTE?: number;
15
+ MAX_TOKENS_PER_REQUEST?: number;
16
+ REQUEST_TIMEOUT_MS?: number;
17
+ MAX_RETRIES?: number;
18
+ RETRY_DELAY_MS?: number;
19
+ DEFAULT_MODEL?: string;
20
+ DEFAULT_TEMPERATURE?: number;
21
+ DEFAULT_TOP_P?: number;
22
+ DEFAULT_MAX_TOKENS?: number;
23
+ };
24
+ export type Config = ReturnType<typeof getConfig>;
package/dist/config.js ADDED
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ const ConfigSchema = z.object({
3
+ // NVIDIA API
4
+ NVIDIA_API_KEY: z.string().min(1, "NVIDIA_API_KEY is required"),
5
+ NVIDIA_NIM_BASE_URL: z
6
+ .string()
7
+ .url()
8
+ .default("https://integrate.api.nvidia.com/v1"),
9
+ NVIDIA_CHAT_MODEL: z.string().optional(),
10
+ NVIDIA_EMBEDDINGS_MODEL: z.string().optional(),
11
+ NVIDIA_RERANKING_MODEL: z.string().optional(),
12
+ // Server
13
+ MCP_SERVER_NAME: z.string().default("nvidia-nim-mcp"),
14
+ MCP_SERVER_VERSION: z.string().default("1.0.0"),
15
+ MCP_SERVER_PORT: z.coerce.number().int().positive().default(8080),
16
+ LOG_LEVEL: z
17
+ .enum(["error", "warn", "info", "debug", "silly"])
18
+ .default("info"),
19
+ // Rate Limiting
20
+ MAX_REQUESTS_PER_MINUTE: z.coerce.number().int().positive().default(40),
21
+ MAX_TOKENS_PER_REQUEST: z.coerce.number().int().positive().default(4096),
22
+ REQUEST_TIMEOUT_MS: z.coerce.number().int().positive().default(120000),
23
+ // Retry
24
+ MAX_RETRIES: z.coerce.number().int().min(0).max(10).default(3),
25
+ RETRY_DELAY_MS: z.coerce.number().int().positive().default(1000),
26
+ // Defaults
27
+ DEFAULT_MODEL: z.string().default("z-ai/glm5"),
28
+ DEFAULT_TEMPERATURE: z.coerce.number().min(0).max(2).default(0.3),
29
+ DEFAULT_TOP_P: z.coerce.number().min(0).max(1).default(0.95),
30
+ DEFAULT_MAX_TOKENS: z.coerce.number().int().positive().default(4096),
31
+ });
32
+ export class ConfigError extends Error {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = 'ConfigError';
36
+ }
37
+ }
38
+ function loadConfig() {
39
+ const result = ConfigSchema.safeParse(process.env);
40
+ if (!result.success) {
41
+ const errors = result.error.issues
42
+ .map((e) => ` - ${e.path.join(".")}: ${e.message}`)
43
+ .join("\n");
44
+ throw new ConfigError(`Configuration validation failed:\n${errors}`);
45
+ }
46
+ return result.data;
47
+ }
48
+ export function getConfig() {
49
+ return loadConfig();
50
+ }
@@ -0,0 +1,26 @@
1
+ import { NIMClient } from "./client.js";
2
+ export type ToolResult = {
3
+ content: Array<{
4
+ type: "text";
5
+ text: string;
6
+ }>;
7
+ isError?: false;
8
+ } | {
9
+ content: Array<{
10
+ type: "text";
11
+ text: string;
12
+ }>;
13
+ isError: true;
14
+ };
15
+ export declare class ToolHandlers {
16
+ private readonly client;
17
+ constructor(client: NIMClient);
18
+ handle(toolName: string, rawArgs: unknown): Promise<ToolResult>;
19
+ private chatCompletion;
20
+ private textGeneration;
21
+ private createEmbeddings;
22
+ private rerankPassages;
23
+ private functionCalling;
24
+ private listModels;
25
+ private getModelInfo;
26
+ }
@@ -0,0 +1,255 @@
1
+ import { NIM_MODELS, getModelsByCategory, getModel } from "./models.js";
2
+ import { getConfig } from "./config.js";
3
+ import { logger } from "./logger.js";
4
+ import { ChatCompletionSchema, TextGenerationSchema, EmbeddingsSchema, RerankSchema, FunctionCallingSchema, ListModelsSchema, ModelInfoSchema, } from "./tools.js";
5
+ const config = getConfig();
6
+ function ok(data) {
7
+ return {
8
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
9
+ };
10
+ }
11
+ function err(message) {
12
+ return {
13
+ content: [{ type: "text", text: `Error: ${message}` }],
14
+ isError: true,
15
+ };
16
+ }
17
+ export class ToolHandlers {
18
+ client;
19
+ constructor(client) {
20
+ this.client = client;
21
+ }
22
+ async handle(toolName, rawArgs) {
23
+ const start = Date.now();
24
+ logger.info(`Executing tool: ${toolName}`);
25
+ try {
26
+ let result;
27
+ switch (toolName) {
28
+ case "chat_completion":
29
+ result = await this.chatCompletion(rawArgs);
30
+ break;
31
+ case "text_generation":
32
+ result = await this.textGeneration(rawArgs);
33
+ break;
34
+ case "create_embeddings":
35
+ result = await this.createEmbeddings(rawArgs);
36
+ break;
37
+ case "rerank_passages":
38
+ result = await this.rerankPassages(rawArgs);
39
+ break;
40
+ case "function_calling":
41
+ result = await this.functionCalling(rawArgs);
42
+ break;
43
+ case "list_models":
44
+ result = await this.listModels(rawArgs);
45
+ break;
46
+ case "get_model_info":
47
+ result = await this.getModelInfo(rawArgs);
48
+ break;
49
+ default:
50
+ result = err(`Unknown tool: ${toolName}`);
51
+ }
52
+ logger.info(`Tool ${toolName} completed in ${Date.now() - start}ms`);
53
+ return result;
54
+ }
55
+ catch (e) {
56
+ const msg = e instanceof Error ? e.message : String(e);
57
+ logger.error(`Tool ${toolName} failed: ${msg}`, { error: e });
58
+ return err(msg);
59
+ }
60
+ }
61
+ // ─── Handlers ─────────────────────────────────────────────────────────────
62
+ async chatCompletion(rawArgs) {
63
+ const args = ChatCompletionSchema.parse(rawArgs);
64
+ const model = args.model ?? config.DEFAULT_MODEL;
65
+ const messages = args.messages.map(msg => ({
66
+ role: msg.role,
67
+ content: msg.content
68
+ }));
69
+ if (args.system_prompt) {
70
+ messages.unshift({ role: "system", content: args.system_prompt });
71
+ }
72
+ const response = await this.client.chatCompletion({
73
+ model,
74
+ messages,
75
+ temperature: args.temperature,
76
+ top_p: args.top_p,
77
+ max_tokens: args.max_tokens,
78
+ stop: args.stop,
79
+ frequency_penalty: args.frequency_penalty,
80
+ presence_penalty: args.presence_penalty,
81
+ seed: args.seed,
82
+ });
83
+ return ok({
84
+ id: response.id,
85
+ model: response.model,
86
+ content: response.choices[0].message.content,
87
+ finish_reason: response.choices[0].finish_reason,
88
+ usage: response.usage,
89
+ });
90
+ }
91
+ async textGeneration(rawArgs) {
92
+ const args = TextGenerationSchema.parse(rawArgs);
93
+ const model = args.model ?? config.DEFAULT_MODEL;
94
+ const messages = [];
95
+ if (args.system_prompt) {
96
+ messages.push({ role: "system", content: args.system_prompt });
97
+ }
98
+ messages.push({ role: "user", content: args.prompt });
99
+ const response = await this.client.chatCompletion({
100
+ model,
101
+ messages,
102
+ temperature: args.temperature,
103
+ max_tokens: args.max_tokens,
104
+ stop: args.stop,
105
+ });
106
+ return ok({
107
+ model: response.model,
108
+ text: response.choices[0].message.content,
109
+ finish_reason: response.choices[0].finish_reason,
110
+ usage: response.usage,
111
+ });
112
+ }
113
+ async createEmbeddings(rawArgs) {
114
+ const args = EmbeddingsSchema.parse(rawArgs);
115
+ const model = args.model ?? "nvidia/nv-embed-v1";
116
+ const response = await this.client.embeddings({
117
+ model,
118
+ input: args.input,
119
+ encoding_format: args.encoding_format,
120
+ truncate: args.truncate,
121
+ });
122
+ const inputs = Array.isArray(args.input) ? args.input : [args.input];
123
+ return ok({
124
+ model: response.model,
125
+ embeddings: response.data.map((d, i) => ({
126
+ index: d.index,
127
+ text: inputs[i],
128
+ dimensions: d.embedding.length,
129
+ embedding: d.embedding,
130
+ })),
131
+ usage: response.usage,
132
+ });
133
+ }
134
+ async rerankPassages(rawArgs) {
135
+ const args = RerankSchema.parse(rawArgs);
136
+ const model = args.model ?? "nvidia/nv-rerankqa-mistral-4b-v3";
137
+ // Normalize passages
138
+ const passages = args.passages.map((p) => typeof p === "string" ? { text: p } : { text: p.text });
139
+ const response = await this.client.rerank({
140
+ model,
141
+ query: args.query,
142
+ passages,
143
+ truncate: args.truncate,
144
+ });
145
+ let rankings = response.rankings;
146
+ if (args.top_k) {
147
+ rankings = rankings.slice(0, args.top_k);
148
+ }
149
+ return ok({
150
+ query: args.query,
151
+ model,
152
+ rankings: rankings.map((r) => ({
153
+ rank: rankings.indexOf(r) + 1,
154
+ original_index: r.index,
155
+ score: r.logit,
156
+ text: r.passage.text,
157
+ })),
158
+ usage: response.usage,
159
+ });
160
+ }
161
+ async functionCalling(rawArgs) {
162
+ const args = FunctionCallingSchema.parse(rawArgs);
163
+ const model = args.model ?? config.DEFAULT_MODEL;
164
+ const response = await this.client.chatCompletion({
165
+ model,
166
+ messages: args.messages.map(msg => ({
167
+ role: msg.role,
168
+ content: msg.content
169
+ })),
170
+ tools: args.tools.map(tool => ({
171
+ type: tool.type,
172
+ function: {
173
+ name: tool.function.name,
174
+ description: tool.function.description,
175
+ parameters: tool.function.parameters
176
+ }
177
+ })),
178
+ tool_choice: args.tool_choice,
179
+ temperature: args.temperature,
180
+ max_tokens: args.max_tokens,
181
+ });
182
+ const choice = response.choices[0];
183
+ return ok({
184
+ model: response.model,
185
+ finish_reason: choice.finish_reason,
186
+ message: choice.message.content,
187
+ tool_calls: choice.message.tool_calls?.map((tc) => ({
188
+ id: tc.id,
189
+ function: tc.function.name,
190
+ arguments: (() => {
191
+ try {
192
+ return JSON.parse(tc.function.arguments);
193
+ }
194
+ catch {
195
+ return tc.function.arguments;
196
+ }
197
+ })(),
198
+ })),
199
+ usage: response.usage,
200
+ });
201
+ }
202
+ async listModels(rawArgs) {
203
+ const args = ListModelsSchema.parse(rawArgs);
204
+ let models;
205
+ if (args.category === "all") {
206
+ models = Object.values(NIM_MODELS);
207
+ }
208
+ else {
209
+ models = getModelsByCategory(args.category);
210
+ }
211
+ return ok({
212
+ total: models.length,
213
+ category: args.category,
214
+ models: models.map((m) => ({
215
+ id: m.id,
216
+ name: m.name,
217
+ category: m.category,
218
+ description: m.description,
219
+ context_length: m.contextLength,
220
+ supports_streaming: m.supportsStreaming,
221
+ supports_function_calling: m.supportsFunctionCalling,
222
+ supports_vision: m.supportsVision ?? false,
223
+ })),
224
+ });
225
+ }
226
+ async getModelInfo(rawArgs) {
227
+ const args = ModelInfoSchema.parse(rawArgs);
228
+ const model = getModel(args.model_id);
229
+ if (!model) {
230
+ return err(`Model '${args.model_id}' not found in catalog. Use list_models to see available models.`);
231
+ }
232
+ return ok({
233
+ id: model.id,
234
+ name: model.name,
235
+ category: model.category,
236
+ description: model.description,
237
+ context_length: model.contextLength,
238
+ supports_streaming: model.supportsStreaming,
239
+ supports_function_calling: model.supportsFunctionCalling,
240
+ supports_vision: model.supportsVision ?? false,
241
+ recommended_use_cases: getUseCases(model.category),
242
+ });
243
+ }
244
+ }
245
+ function getUseCases(category) {
246
+ const useCases = {
247
+ language: ["Chat", "Summarization", "Translation", "Q&A", "Content generation"],
248
+ embedding: ["Semantic search", "RAG", "Clustering", "Similarity comparison"],
249
+ reranking: ["RAG re-ranking", "Search quality improvement", "Relevance scoring"],
250
+ vision: ["Image understanding", "Visual Q&A", "Document analysis"],
251
+ code: ["Code generation", "Code review", "Debugging", "Documentation"],
252
+ multimodal: ["Multi-modal tasks", "Image + text processing"],
253
+ };
254
+ return useCases[category] ?? [];
255
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import dotenv from "dotenv";
3
+ dotenv.config();
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { CallToolRequestSchema, ListToolsRequestSchema, InitializeRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
7
+ import { getConfig } from "./config.js";
8
+ import { logger } from "./logger.js";
9
+ import { NIMClient } from "./client.js";
10
+ import { ToolHandlers } from "./handlers.js";
11
+ import { TOOL_DEFINITIONS } from "./tools.js";
12
+ const config = getConfig();
13
+ // ─── Graceful Shutdown ────────────────────────────────────────────────────────
14
+ function setupShutdownHandlers(server) {
15
+ const shutdown = async (signal) => {
16
+ logger.info(`Received ${signal}, shutting down gracefully...`);
17
+ try {
18
+ await server.close();
19
+ logger.info("Server closed successfully");
20
+ process.exit(0);
21
+ }
22
+ catch (err) {
23
+ logger.error("Error during shutdown", { error: err });
24
+ process.exit(1);
25
+ }
26
+ };
27
+ process.on("SIGINT", () => shutdown("SIGINT"));
28
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
29
+ process.on("uncaughtException", (error) => {
30
+ logger.error("Uncaught exception", { error });
31
+ process.exit(1);
32
+ });
33
+ process.on("unhandledRejection", (reason) => {
34
+ logger.error("Unhandled rejection", { reason });
35
+ process.exit(1);
36
+ });
37
+ }
38
+ // ─── Main ─────────────────────────────────────────────────────────────────────
39
+ async function main() {
40
+ logger.info("Starting NVIDIA NIM MCP Server", {
41
+ name: config.MCP_SERVER_NAME,
42
+ version: config.MCP_SERVER_VERSION,
43
+ baseUrl: config.NVIDIA_NIM_BASE_URL,
44
+ defaultModel: config.DEFAULT_MODEL,
45
+ logLevel: config.LOG_LEVEL,
46
+ maxRPM: config.MAX_REQUESTS_PER_MINUTE,
47
+ maxTokens: config.MAX_TOKENS_PER_REQUEST,
48
+ timeout: config.REQUEST_TIMEOUT_MS,
49
+ maxRetries: config.MAX_RETRIES,
50
+ });
51
+ // Initialize NIM client
52
+ const nimClient = new NIMClient();
53
+ const toolHandlers = new ToolHandlers(nimClient);
54
+ // Create MCP server
55
+ const server = new Server({
56
+ name: config.MCP_SERVER_NAME,
57
+ version: config.MCP_SERVER_VERSION,
58
+ }, {
59
+ capabilities: {
60
+ tools: {},
61
+ },
62
+ });
63
+ setupShutdownHandlers(server);
64
+ // ─── Handler: Initialize ─────────────────────────────────────────────────
65
+ server.setRequestHandler(InitializeRequestSchema, async (request) => {
66
+ logger.info("Client initialized", {
67
+ clientName: request.params.clientInfo?.name,
68
+ clientVersion: request.params.clientInfo?.version,
69
+ });
70
+ return {
71
+ protocolVersion: "2024-11-05",
72
+ capabilities: { tools: {} },
73
+ serverInfo: {
74
+ name: config.MCP_SERVER_NAME,
75
+ version: config.MCP_SERVER_VERSION,
76
+ },
77
+ };
78
+ });
79
+ // ─── Handler: List Tools ──────────────────────────────────────────────────
80
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
81
+ logger.debug("Listing tools");
82
+ return { tools: TOOL_DEFINITIONS };
83
+ });
84
+ // ─── Handler: Call Tool ───────────────────────────────────────────────────
85
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
86
+ const { name, arguments: args } = request.params;
87
+ logger.info(`Tool call received: ${name}`, {
88
+ hasArgs: args !== undefined,
89
+ });
90
+ const result = await toolHandlers.handle(name, args ?? {});
91
+ if (result.isError) {
92
+ logger.warn(`Tool ${name} returned an error`, {
93
+ error: result.content[0]?.text,
94
+ });
95
+ }
96
+ return result;
97
+ });
98
+ // ─── Start Transport ──────────────────────────────────────────────────────
99
+ const transport = new StdioServerTransport();
100
+ logger.info("Connecting MCP server via stdio transport...");
101
+ await server.connect(transport);
102
+ logger.info("NVIDIA NIM MCP Server is ready and listening");
103
+ }
104
+ main().catch((err) => {
105
+ logger.error("Fatal error during startup", { error: err });
106
+ process.exit(1);
107
+ });
@@ -0,0 +1,9 @@
1
+ import winston from "winston";
2
+ import { Config } from "./config.js";
3
+ export interface LoggerOptions {
4
+ logLevel: Config['LOG_LEVEL'];
5
+ isProduction: boolean;
6
+ mcpServerVersion: string;
7
+ }
8
+ export declare let logger: winston.Logger;
9
+ export declare function initLogger(options: LoggerOptions): winston.Logger;