Skip to content

Commit a00306e

Browse files
feat: add Inception Mercury provider
Integrate the Inception Labs Mercury 2 provider with settings, schema updates, and migrations. Update provider-aware configs and docs to reflect the new provider and reasoning options. Made-with: Cursor
1 parent a7bc08d commit a00306e

18 files changed

Lines changed: 344 additions & 55 deletions

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to OpenSuiteMCP will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.4.0] - 2026-02-28
9+
10+
### ✨ Added
11+
12+
- **Inception Labs provider**
13+
- Added Mercury 2 as a selectable provider for speed and enhanced reasoning
14+
- New Inception API key support in Settings and user configuration
15+
- OpenAI-compatible chat completions integration with Mercury 2
16+
- Reasoning effort parameters forwarded for Mercury 2 reasoning mode
17+
18+
### 🧰 Technical
19+
20+
- Added `@ai-sdk/openai-compatible` for Inception Labs integration
21+
22+
---
23+
824
## [2.3.0] - 2026-01-30
925

1026
### ✨ Added
@@ -344,6 +360,7 @@ First stable release of OpenSuiteMCP - an open source, production-ready NetSuite
344360

345361
---
346362

363+
[2.4.0]: https://github.com/unstackedapps/opensuitemcp/releases/tag/v2.4.0
347364
[2.3.0]: https://github.com/unstackedapps/opensuitemcp/releases/tag/v2.3.0
348365
[2.2.0]: https://github.com/unstackedapps/opensuitemcp/releases/tag/v2.2.0
349366
[2.1.0]: https://github.com/unstackedapps/opensuitemcp/releases/tag/v2.1.0

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
# <img src="./app/icon.svg" alt="OpenSuiteMCP Icon" width="24" height="24" /><span style="font-weight: 200;"> OpenSuite</span>MCP NetSuite AI Assistant
22

3-
An AI-powered chat assistant that integrates with NetSuite via MCP (Model Context Protocol), enabling natural language interactions with your NetSuite data. Built with Next.js, Vercel AI SDK, and supporting multiple AI providers (Google Gemini, Anthropic Claude, and OpenAI GPT).
3+
An AI-powered chat assistant that integrates with NetSuite via MCP (Model Context Protocol), enabling natural language interactions with your NetSuite data. Built with Next.js, Vercel AI SDK, and supporting multiple AI providers (Google Gemini, Anthropic Claude, OpenAI GPT, and Inception Labs Mercury).
44

55
<img src="./docs/screenshot1.png" alt="OpenSuiteMCP Main UI" width="100%" />
66

7-
*Chat interface in dark mode showing the welcome prompt and main UI.*
7+
_Chat interface in dark mode showing the welcome prompt and main UI._
88

99
### NetSuite MCP Tool Integration
1010

1111
The assistant seamlessly integrates with NetSuite's MCP tools to execute complex queries and retrieve real-time data directly from your NetSuite instance.
1212

1313
<img src="./docs/screenshot2.png" alt="NetSuite MCP Tool Usage" width="100%" />
1414

15-
*Example query requesting the top ten customers by sales order count, demonstrating the power of NetSuite MCP tool usage in the app.*
15+
_Example query requesting the top ten customers by sales order count, demonstrating the power of NetSuite MCP tool usage in the app._
1616

1717
## Quick Start
1818

@@ -52,8 +52,9 @@ The assistant seamlessly integrates with NetSuite's MCP tools to execute complex
5252

5353
5. **Configure your API key:**
5454
- Open the **Settings** modal from the sidebar
55-
- Enter your AI provider API key (Google, Anthropic, or OpenAI)
56-
- API keys are encrypted and stored securely in your database
55+
56+
- Enter your AI provider API key (Google, Anthropic, OpenAI, or Inception Labs)
57+
- API keys are encrypted and stored securely in your database
5758

5859
The app will be running at [http://localhost:3000](http://localhost:3000).
5960

app/(chat)/actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export async function generateTitleFromUserMessage({
4343
}: {
4444
message: UIMessage;
4545
apiKey?: string | null;
46-
provider?: "google" | "anthropic" | "openai";
46+
provider?: "google" | "anthropic" | "openai" | "inception";
4747
}): Promise<{ title: string; summary: string | null }> {
4848
const text = getTextFromMessage(message);
4949

app/api/chat/route.ts

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -117,19 +117,25 @@ export async function POST(request: Request) {
117117
} else {
118118
// Get user API key and provider for title generation
119119
let titleApiKey: string | null = null;
120-
let titleProvider: "google" | "anthropic" | "openai" = "google";
120+
let titleProvider: "google" | "anthropic" | "openai" | "inception" =
121+
"google";
121122
if (session.user?.id) {
122123
try {
123124
const settings = await getUserSettings({ userId: session.user.id });
124125
titleProvider =
125-
(settings?.aiProvider as "google" | "anthropic" | "openai") ||
126-
"google";
126+
(settings?.aiProvider as
127+
| "google"
128+
| "anthropic"
129+
| "openai"
130+
| "inception") || "google";
127131
const apiKeyField =
128132
titleProvider === "anthropic"
129133
? settings?.anthropicApiKey
130134
: titleProvider === "openai"
131135
? settings?.openaiApiKey
132-
: settings?.googleApiKey;
136+
: titleProvider === "inception"
137+
? settings?.inceptionApiKey
138+
: settings?.googleApiKey;
133139
if (apiKeyField) {
134140
titleApiKey = decrypt(apiKeyField);
135141
}
@@ -187,7 +193,11 @@ export async function POST(request: Request) {
187193
try {
188194
// Get user settings (API key, provider, timezone, and maxIterations)
189195
let userApiKey: string | null = null;
190-
let userProviderType: "google" | "anthropic" | "openai" = "google";
196+
let userProviderType:
197+
| "google"
198+
| "anthropic"
199+
| "openai"
200+
| "inception" = "google";
191201
let userTimezone = "UTC";
192202
let userMaxIterations = 10; // Default to 10
193203
let selectedSearchDomainIds: string[] = [];
@@ -203,12 +213,16 @@ export async function POST(request: Request) {
203213
hasGoogleKey: !!settings?.googleApiKey,
204214
hasAnthropicKey: !!settings?.anthropicApiKey,
205215
hasOpenAIKey: !!settings?.openaiApiKey,
216+
hasInceptionKey: !!settings?.inceptionApiKey,
206217
maxIterations: settings?.maxIterations,
207218
});
208219
if (settings) {
209220
userProviderType =
210-
(settings.aiProvider as "google" | "anthropic" | "openai") ||
211-
"google";
221+
(settings.aiProvider as
222+
| "google"
223+
| "anthropic"
224+
| "openai"
225+
| "inception") || "google";
212226
// Parse maxIterations, default to 10 if invalid
213227
const maxIterationsValue = settings.maxIterations
214228
? Number.parseInt(settings.maxIterations, 10)
@@ -226,7 +240,9 @@ export async function POST(request: Request) {
226240
? settings.anthropicApiKey
227241
: userProviderType === "openai"
228242
? settings.openaiApiKey
229-
: settings.googleApiKey;
243+
: userProviderType === "inception"
244+
? settings.inceptionApiKey
245+
: settings.googleApiKey;
230246

231247
if (apiKeyField) {
232248
try {
@@ -277,7 +293,13 @@ export async function POST(request: Request) {
277293
} catch (error) {
278294
// If no API key is configured, return an error to the user
279295
const providerName =
280-
userProviderType === "anthropic" ? "Anthropic" : "Google";
296+
userProviderType === "anthropic"
297+
? "Anthropic"
298+
: userProviderType === "openai"
299+
? "OpenAI"
300+
: userProviderType === "inception"
301+
? "Inception Labs"
302+
: "Google";
281303
const errorMessage =
282304
error instanceof Error
283305
? error.message
@@ -433,34 +455,51 @@ export async function POST(request: Request) {
433455
tools: allToolsWithConfig,
434456
// Apply reasoning/thinking config based on provider
435457
// Both use similar token budgets (4K) for thinking/reasoning
436-
...(modelId === "chat-model-reasoning" && {
437-
providerOptions:
438-
userProviderType === "google"
439-
? {
458+
...(modelId === "chat-model-reasoning" &&
459+
(userProviderType === "google"
460+
? {
461+
providerOptions: {
440462
google: {
441463
thinkingConfig: {
442464
thinkingBudget: 4096, // Allocates 4K tokens for thought
443465
includeThoughts: true,
444466
},
445467
},
446-
}
447-
: userProviderType === "anthropic"
448-
? {
468+
},
469+
}
470+
: userProviderType === "anthropic"
471+
? {
472+
providerOptions: {
449473
anthropic: {
450474
thinking: {
451475
type: "enabled",
452476
budgetTokens: 4096, // Matches Google's 4K token budget
453477
},
454478
},
455-
}
456-
: {
457-
// OpenAI reasoning configuration for o4-mini
458-
openai: {
459-
reasoningEffort: "high", // Equivalent to adjusting thinking budget
460-
reasoningSummary: "detailed", // Show thought process in UI
461-
},
462479
},
463-
}),
480+
}
481+
: userProviderType === "openai"
482+
? {
483+
providerOptions: {
484+
// OpenAI reasoning configuration for o4-mini
485+
openai: {
486+
reasoningEffort: "high", // Equivalent to adjusting thinking budget
487+
reasoningSummary: "detailed", // Show thought process in UI
488+
},
489+
},
490+
}
491+
: userProviderType === "inception"
492+
? {
493+
providerOptions: {
494+
// Inception Labs reasoning configuration for Mercury 2
495+
inception: {
496+
reasoningEffort: "high",
497+
reasoning_summary: true,
498+
reasoning_summary_wait: false,
499+
},
500+
},
501+
}
502+
: {})),
464503
experimental_telemetry: {
465504
isEnabled: isProductionEnvironment,
466505
functionId: "stream-text",

app/api/settings/route.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ const settingsSchema = z.object({
88
googleApiKey: z.string().optional().nullable(),
99
anthropicApiKey: z.string().optional().nullable(),
1010
openaiApiKey: z.string().optional().nullable(),
11-
aiProvider: z.enum(["google", "anthropic", "openai"]).optional().nullable(),
11+
inceptionApiKey: z.string().optional().nullable(),
12+
aiProvider: z
13+
.enum(["google", "anthropic", "openai", "inception"])
14+
.optional()
15+
.nullable(),
1216
netsuiteAccountId: z.string().max(64).optional().nullable(),
1317
netsuiteClientId: z.string().max(128).optional().nullable(),
1418
timezone: z.string().max(64).optional().nullable(),
@@ -39,6 +43,7 @@ export async function GET() {
3943
hasGoogleKey: !!settings?.googleApiKey,
4044
hasAnthropicKey: !!settings?.anthropicApiKey,
4145
hasOpenAIKey: !!settings?.openaiApiKey,
46+
hasInceptionKey: !!settings?.inceptionApiKey,
4247
aiProvider: settings?.aiProvider,
4348
});
4449

@@ -47,6 +52,7 @@ export async function GET() {
4752
googleApiKey: null,
4853
anthropicApiKey: null,
4954
openaiApiKey: null,
55+
inceptionApiKey: null,
5056
aiProvider: "google",
5157
netsuiteAccountId: null,
5258
netsuiteClientId: null,
@@ -105,18 +111,36 @@ export async function GET() {
105111
console.log("[Settings API] No OpenAI key in DB");
106112
}
107113

114+
let decryptedInceptionKey: string | null = null;
115+
if (settings.inceptionApiKey) {
116+
try {
117+
decryptedInceptionKey = decrypt(settings.inceptionApiKey);
118+
console.log("[Settings API] Successfully decrypted Inception key");
119+
} catch (error) {
120+
console.error(
121+
"[Settings API] Error decrypting Inception API key on GET:",
122+
error,
123+
);
124+
decryptedInceptionKey = null;
125+
}
126+
} else {
127+
console.log("[Settings API] No Inception key in DB");
128+
}
129+
108130
// Ensure aiProvider is always a valid value
109131
const provider =
110132
settings.aiProvider === "google" ||
111133
settings.aiProvider === "anthropic" ||
112-
settings.aiProvider === "openai"
134+
settings.aiProvider === "openai" ||
135+
settings.aiProvider === "inception"
113136
? settings.aiProvider
114137
: "google";
115138

116139
const response = {
117140
googleApiKey: decryptedGoogleKey,
118141
anthropicApiKey: decryptedAnthropicKey,
119142
openaiApiKey: decryptedOpenAIKey,
143+
inceptionApiKey: decryptedInceptionKey,
120144
aiProvider: provider,
121145
netsuiteAccountId: settings.netsuiteAccountId,
122146
netsuiteClientId: settings.netsuiteClientId,
@@ -129,10 +153,12 @@ export async function GET() {
129153
hasGoogleKey: !!response.googleApiKey,
130154
hasAnthropicKey: !!response.anthropicApiKey,
131155
hasOpenAIKey: !!response.openaiApiKey,
156+
hasInceptionKey: !!response.inceptionApiKey,
132157
aiProvider: response.aiProvider,
133158
googleKeyLength: response.googleApiKey?.length ?? 0,
134159
anthropicKeyLength: response.anthropicApiKey?.length ?? 0,
135160
openaiKeyLength: response.openaiApiKey?.length ?? 0,
161+
inceptionKeyLength: response.inceptionApiKey?.length ?? 0,
136162
});
137163

138164
return NextResponse.json(response);
@@ -234,11 +260,39 @@ export async function POST(request: Request) {
234260
encryptedOpenAIKey = existing.openaiApiKey;
235261
}
236262

263+
// Encrypt Inception API key if provided
264+
let encryptedInceptionKey: string | null | undefined;
265+
if (validated.inceptionApiKey !== undefined) {
266+
const trimmedKey = validated.inceptionApiKey?.trim();
267+
if (trimmedKey) {
268+
try {
269+
encryptedInceptionKey = encrypt(trimmedKey);
270+
} catch (error) {
271+
console.error(
272+
"[Settings] Error encrypting Inception API key:",
273+
error,
274+
);
275+
return NextResponse.json(
276+
{
277+
error:
278+
"Failed to encrypt Inception API key. Please check ENCRYPTION_KEY environment variable.",
279+
},
280+
{ status: 500 },
281+
);
282+
}
283+
} else {
284+
encryptedInceptionKey = null;
285+
}
286+
} else if (existing?.inceptionApiKey) {
287+
encryptedInceptionKey = existing.inceptionApiKey;
288+
}
289+
237290
await upsertUserSettings({
238291
userId: session.user.id,
239292
googleApiKey: encryptedGoogleKey,
240293
anthropicApiKey: encryptedAnthropicKey,
241294
openaiApiKey: encryptedOpenAIKey,
295+
inceptionApiKey: encryptedInceptionKey,
242296
aiProvider: validated.aiProvider,
243297
netsuiteAccountId: validated.netsuiteAccountId,
244298
netsuiteClientId: validated.netsuiteClientId,

app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const metadata: Metadata = {
1010
metadataBase: new URL("https://chat.vercel.ai"),
1111
title: "OpenSuiteMCP",
1212
description:
13-
"OpenSuiteMCP NetSuite AI Assistant, powered by Gemini and Next.js",
13+
"OpenSuiteMCP NetSuite AI Assistant, powered by Gemini, Claude, GPT, and Mercury",
1414
};
1515

1616
export const viewport = {

components/chat.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ export function Chat({
338338
/responseBody/i,
339339
/api\.openai\.com/i,
340340
/api\.anthropic\.com/i,
341+
/api\.inceptionlabs\.ai/i,
341342
/generativeai\.googleapis\.com/i,
342343
];
343344

components/model-selector.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ async function fetchSettings() {
2626
aiProvider: (data.aiProvider || "google") as
2727
| "google"
2828
| "anthropic"
29-
| "openai",
29+
| "openai"
30+
| "inception",
3031
};
3132
}
3233

components/multimodal-input.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ async function fetchSettings() {
279279
aiProvider: (data.aiProvider || "google") as
280280
| "google"
281281
| "anthropic"
282-
| "openai",
282+
| "openai"
283+
| "inception",
283284
};
284285
}
285286

0 commit comments

Comments
 (0)