Skip to content

Commit 35d0441

Browse files
jonathanMLDevzho
andauthored
Add branded core and Alliance ServerConfig types (#179)
* Add branded CoreServerConfig / AllianceServerConfig to prevent cross-setup misuse * - Added hasConfig() and getConfigIfSet() on ServerContext - In the context branch, when no opts.config and !ctx.hasConfig(), call resolveAllianceConfig({}) before tool registration - Added test: unconfigured context + PINECONE_API_KEY env → Alliance gate defaults applied - ServerContext<T> generic + CoreServerContext = ServerContext<CoreServerConfig> - SetupCoreServerOptions.context is now CoreServerContext only - createServer / createIsolatedContext overloaded by config brand - Alliance uses internal setupCoreServerOnContext(ctx) (not exported from the public barrel) instead of setupCoreServer({ context }) * - Added AllianceServerContext = ServerContext<AllianceServerConfig> and exported it from core/alliance barrels - Narrowed SetupAllianceServerOptions.context to AllianceServerContext - Extended config-branding.compile-test.ts with context-path @ts-expect-error cases (both directions) * addressed ai reviews * addressed review Updated to #unreleased-branded-serverconfig-types (matches GitHub’s anchor for that heading, same pattern as other unreleased sections). setup.ts — when opts.context is passed without opts.config, validate getConfigIfSet() via assertCoreServerConfig. alliance/setup.ts — symmetric check with assertAllianceServerConfig when the context already has config. --------- Co-authored-by: zho <jornathanm910923@gmail.com>
1 parent 7b8df0d commit 35d0441

21 files changed

Lines changed: 418 additions & 97 deletions

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ docs/
3535

3636
# Testing
3737
coverage/
38+
dist/__tests__/
3839

3940
# Logs
4041
*.log

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ Tagged releases are published to npm from GitHub Actions when a **GitHub Release
88

99
## [Unreleased]
1010

11+
### Changed
12+
13+
- **Library:** `resolveConfig()` returns `CoreServerConfig`; `resolveAllianceConfig()` returns `AllianceServerConfig`. `setupCoreServer` / `setupAllianceServer` accept only their respective branded config and context types (`CoreServerContext` / `AllianceServerContext`). `ServerConfig` remains an alias for `ServerConfigBase` on read paths (`ctx.getConfig()`). See [MIGRATION.md](docs/MIGRATION.md#unreleased-branded-serverconfig-types).
14+
1115
## [0.3.0] - 2026-06-23
1216

1317
### Added

docs/MIGRATION.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,37 @@ Core `resolveConfig` throws `Missing Pinecone index name: …` when the index is
352352

353353
---
354354

355+
## Unreleased: Branded ServerConfig types
356+
357+
**Rationale:** `resolveConfig()` and `resolveAllianceConfig()` produced structurally identical configs. Passing Alliance-resolved config or context into `setupCoreServer()` compiled but silently changed suggest-flow gate behavior (and related defaults).
358+
359+
**Who is affected:** Library embedders typing resolver returns or setup arguments as `ServerConfig` instead of the branded types.
360+
361+
**Migration:**
362+
363+
Use branded resolver outputs with matching setup entry points and context factories:
364+
365+
```typescript
366+
import { createServer, resolveConfig, setupCoreServer } from '@will-cppa/pinecone-read-only-mcp';
367+
import { resolveAllianceConfig, setupAllianceServer } from '@will-cppa/pinecone-read-only-mcp/alliance';
368+
369+
const coreCfg = resolveConfig({ apiKey, indexName });
370+
await setupCoreServer({ context: createServer(coreCfg) });
371+
372+
const allianceCfg = resolveAllianceConfig({ apiKey });
373+
await setupAllianceServer({ context: createServer(allianceCfg) });
374+
```
375+
376+
**Compile-time errors (intentional):**
377+
378+
- `setupCoreServer(allianceCfg)` — Alliance config on core setup
379+
- `setupCoreServer({ context: createServer(allianceCfg) })` — Alliance context on core setup
380+
- `setupAllianceServer(coreCfg)` and `setupAllianceServer({ context: createServer(coreCfg) })` — symmetric misuse
381+
382+
**Unchanged:** `ServerConfig` / `ServerConfigBase` remain valid for reading fields from `ctx.getConfig()` and other read-only config paths.
383+
384+
---
385+
355386
## Migrating to v0.2.0
356387

357388
### Namespace trimming and suggest-flow

examples/alliance/custom-url-generator.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@
1717
import {
1818
createServer,
1919
PineconeClient,
20-
resolveConfig,
2120
type UrlGenerationResult,
2221
} from '@will-cppa/pinecone-read-only-mcp';
23-
import { setupAllianceServer } from '@will-cppa/pinecone-read-only-mcp/alliance';
22+
import { resolveAllianceConfig, setupAllianceServer } from '@will-cppa/pinecone-read-only-mcp/alliance';
2423

2524
async function main(): Promise<void> {
2625
const apiKey = process.env['PINECONE_API_KEY']?.trim();
@@ -31,7 +30,7 @@ async function main(): Promise<void> {
3130
);
3231
return;
3332
}
34-
const config = resolveConfig({ apiKey, indexName });
33+
const config = resolveAllianceConfig({ apiKey, indexName });
3534

3635
const ctx = createServer(config);
3736
ctx.setClient(

examples/alliance/guided-query-demo.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@
1818
import {
1919
createServer,
2020
PineconeClient,
21-
resolveConfig,
2221
} from '@will-cppa/pinecone-read-only-mcp';
23-
import { setupAllianceServer } from '@will-cppa/pinecone-read-only-mcp/alliance';
22+
import { resolveAllianceConfig, setupAllianceServer } from '@will-cppa/pinecone-read-only-mcp/alliance';
2423

2524
async function main(): Promise<void> {
2625
const apiKey = process.env['PINECONE_API_KEY']?.trim();
@@ -33,7 +32,7 @@ async function main(): Promise<void> {
3332
return;
3433
}
3534

36-
const config = resolveConfig({ apiKey, indexName });
35+
const config = resolveAllianceConfig({ apiKey, indexName });
3736
const ctx = createServer(config);
3837
ctx.setClient(
3938
new PineconeClient({

examples/alliance/suggest-flow-demo.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919
import {
2020
createServer,
2121
PineconeClient,
22-
resolveConfig,
2322
} from '@will-cppa/pinecone-read-only-mcp';
24-
import { setupAllianceServer } from '@will-cppa/pinecone-read-only-mcp/alliance';
23+
import { resolveAllianceConfig, setupAllianceServer } from '@will-cppa/pinecone-read-only-mcp/alliance';
2524

2625
async function main(): Promise<void> {
2726
const apiKey = process.env['PINECONE_API_KEY']?.trim();
@@ -34,7 +33,7 @@ async function main(): Promise<void> {
3433
return;
3534
}
3635

37-
const config = resolveConfig({ apiKey, indexName });
36+
const config = resolveAllianceConfig({ apiKey, indexName });
3837
const ctx = createServer(config);
3938
ctx.setClient(
4039
new PineconeClient({
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Compile-time assertions for ServerConfig branding (Issue #172).
3+
*
4+
* Included in `tsc` / `npm run typecheck` (not `*.test.ts`). Misuse cases use
5+
* `@ts-expect-error` so brand regressions fail CI when the guard stops working.
6+
*/
7+
8+
import { resolveAllianceConfig } from '../alliance/config.js';
9+
import { resolveConfig } from '../core/config.js';
10+
import { createServer } from '../core/server/server-context.js';
11+
import { setupCoreServer } from '../core/setup.js';
12+
import { setupAllianceServer } from '../alliance/setup.js';
13+
14+
const coreCfg = resolveConfig({ apiKey: 'k', indexName: 'idx' });
15+
const allianceCfg = resolveAllianceConfig({ apiKey: 'k' });
16+
17+
void setupCoreServer(coreCfg);
18+
void setupAllianceServer(allianceCfg);
19+
20+
// @ts-expect-error Alliance config must not be passed to core setup
21+
void setupCoreServer(allianceCfg);
22+
23+
// @ts-expect-error Core config must not be passed to Alliance setup
24+
void setupAllianceServer(coreCfg);
25+
26+
const allianceCtx = createServer(allianceCfg);
27+
// @ts-expect-error Alliance context must not be passed to core setup
28+
void setupCoreServer({ context: allianceCtx });
29+
30+
const coreCtx = createServer(coreCfg);
31+
// @ts-expect-error Core context must not be passed to Alliance setup
32+
void setupAllianceServer({ context: coreCtx });

src/alliance/config.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
import {
66
asBool,
7+
brandAllianceConfig,
78
resolveConfig,
89
trimOptional,
10+
type AllianceServerConfig,
911
type ConfigOverrides,
10-
type ServerConfig,
1112
} from '../core/config.js';
1213

1314
/** C++ Alliance default dense index when env/CLI omit `PINECONE_INDEX_NAME`. */
@@ -20,7 +21,7 @@ export const ALLIANCE_DEFAULT_RERANK_MODEL = 'bge-reranker-v2-m3';
2021
export const DEFAULT_ALLIANCE_RERANK_MODEL = ALLIANCE_DEFAULT_RERANK_MODEL;
2122

2223
/**
23-
* Build {@link ServerConfig} for Alliance CLI and `setupAllianceServer`.
24+
* Build {@link AllianceServerConfig} for Alliance CLI and `setupAllianceServer`.
2425
* Fills index and rerank from Alliance defaults when unset, then calls core `resolveConfig`.
2526
*
2627
* **Suggest-flow gate default:** After core resolution, `disableSuggestFlow` is overridden to
@@ -37,7 +38,7 @@ export const DEFAULT_ALLIANCE_RERANK_MODEL = ALLIANCE_DEFAULT_RERANK_MODEL;
3738
export function resolveAllianceConfig(
3839
overrides: ConfigOverrides = {},
3940
env: NodeJS.ProcessEnv = process.env
40-
): ServerConfig {
41+
): AllianceServerConfig {
4142
const indexName =
4243
trimOptional(overrides.indexName) ??
4344
trimOptional(env['PINECONE_INDEX_NAME']) ??
@@ -49,5 +50,7 @@ export function resolveAllianceConfig(
4950
const cfg = resolveConfig({ ...overrides, indexName, rerankModel }, env);
5051
const disableSuggestFlow =
5152
overrides.disableSuggestFlow ?? asBool(env['PINECONE_DISABLE_SUGGEST_FLOW'], false);
52-
return { ...cfg, disableSuggestFlow };
53+
return brandAllianceConfig({ ...cfg, disableSuggestFlow });
5354
}
55+
56+
export type { AllianceServerConfig } from '../core/config.js';

src/alliance/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export {
1010
DEFAULT_ALLIANCE_RERANK_MODEL,
1111
resolveAllianceConfig,
1212
} from './config.js';
13+
export type { AllianceServerConfig } from './config.js';
14+
export type { AllianceServerContext } from '../core/server/server-context.js';
1315
export { setupAllianceServer, type SetupAllianceServerOptions } from './setup.js';
1416
export {
1517
registerBuiltinUrlGenerators,

src/alliance/setup.ts

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,40 @@
11
import { ALLIANCE_SERVER_INSTRUCTIONS } from '../constants.js';
2-
import type { ServerConfig } from '../core/config.js';
3-
import { resolveDefaultServerContext, type ServerContext } from '../core/server/server-context.js';
2+
import type { AllianceServerConfig, ServerConfigBase } from '../core/config.js';
3+
import { getServerConfigLineage } from '../core/config.js';
4+
import { createServer, type AllianceServerContext } from '../core/server/server-context.js';
45
import { resolveAllianceConfig } from './config.js';
5-
import { setupCoreServer, type ServerHandle, type SetupCoreServerOptions } from '../core/setup.js';
6+
import { setupCoreServerOnContext, type ServerHandle } from '../core/setup.js';
67
import { registerBuiltinUrlGenerators } from './url-builtins.js';
78
import { registerSuggestQueryParamsTool } from './tools/suggest-query-params-tool.js';
89

910
/**
1011
* Options for {@link setupAllianceServer}.
1112
*/
1213
export type SetupAllianceServerOptions = {
13-
config?: ServerConfig;
14-
context?: ServerContext;
14+
config?: AllianceServerConfig;
15+
context?: AllianceServerContext;
1516
/** MCP server instructions; defaults to {@link ALLIANCE_SERVER_INSTRUCTIONS}. */
1617
instructions?: string;
1718
};
1819

19-
function isServerConfig(value: unknown): value is ServerConfig {
20-
return (
21-
typeof value === 'object' &&
22-
value !== null &&
23-
typeof (value as ServerConfig).apiKey === 'string' &&
24-
typeof (value as ServerConfig).indexName === 'string'
25-
);
20+
function isServerConfig(value: unknown): value is AllianceServerConfig {
21+
if (typeof value !== 'object' || value === null) {
22+
return false;
23+
}
24+
const base = value as ServerConfigBase;
25+
if (typeof base.apiKey !== 'string' || typeof base.indexName !== 'string') {
26+
return false;
27+
}
28+
return getServerConfigLineage(base) === 'alliance';
29+
}
30+
31+
function assertAllianceServerConfig(config: ServerConfigBase): AllianceServerConfig {
32+
if (getServerConfigLineage(config) !== 'alliance') {
33+
throw new TypeError(
34+
'Expected AllianceServerConfig. Use setupCoreServer for core-branded config.'
35+
);
36+
}
37+
return config as AllianceServerConfig;
2638
}
2739

2840
function isSetupAllianceServerOptions(value: unknown): value is SetupAllianceServerOptions {
@@ -38,7 +50,7 @@ function isSetupAllianceServerOptions(value: unknown): value is SetupAllianceSer
3850
}
3951

4052
function normalizeSetupAllianceArgs(
41-
configOrOptions?: ServerConfig | SetupAllianceServerOptions,
53+
configOrOptions?: AllianceServerConfig | SetupAllianceServerOptions,
4254
legacyOptions?: Pick<SetupAllianceServerOptions, 'instructions'>
4355
): SetupAllianceServerOptions {
4456
if (configOrOptions === undefined) {
@@ -50,7 +62,9 @@ function normalizeSetupAllianceArgs(
5062
if (isSetupAllianceServerOptions(configOrOptions)) {
5163
return { ...configOrOptions, ...legacyOptions };
5264
}
53-
throw new TypeError('configOrOptions must be a ServerConfig or SetupAllianceServerOptions');
65+
throw new TypeError(
66+
'configOrOptions must be an AllianceServerConfig or SetupAllianceServerOptions'
67+
);
5468
}
5569

5670
/**
@@ -60,29 +74,43 @@ function normalizeSetupAllianceArgs(
6074
* When `config` is omitted, resolves env via {@link resolveAllianceConfig} (Alliance index/rerank defaults when unset).
6175
*/
6276
export async function setupAllianceServer(
63-
configOrOptions?: ServerConfig | SetupAllianceServerOptions,
77+
configOrOptions?: AllianceServerConfig | SetupAllianceServerOptions,
6478
legacyOptions?: Pick<SetupAllianceServerOptions, 'instructions'>
6579
): Promise<ServerHandle> {
6680
const opts = normalizeSetupAllianceArgs(configOrOptions, legacyOptions);
6781
const instructions = opts.instructions ?? ALLIANCE_SERVER_INSTRUCTIONS;
6882

83+
if (opts.config) {
84+
assertAllianceServerConfig(opts.config);
85+
}
86+
6987
let server: ServerHandle;
70-
let resolvedCtx: ServerContext;
88+
let resolvedCtx: AllianceServerContext;
7189

7290
if (opts.context) {
7391
resolvedCtx = opts.context;
74-
const coreOpts: SetupCoreServerOptions = { context: resolvedCtx, instructions };
7592
if (opts.config !== undefined) {
76-
coreOpts.config = opts.config;
93+
if (resolvedCtx.hasInjectedClient()) {
94+
throw new Error(
95+
'Passing both config and context clears an injected Pinecone client. ' +
96+
'Omit config when reusing a pre-configured context, or call setClient() after setup.'
97+
);
98+
}
99+
resolvedCtx.setConfig(opts.config);
100+
} else if (!resolvedCtx.hasConfig()) {
101+
resolvedCtx.setConfig(resolveAllianceConfig({}));
102+
} else {
103+
const stored = resolvedCtx.getConfigIfSet();
104+
if (stored) {
105+
assertAllianceServerConfig(stored);
106+
}
77107
}
78-
server = await setupCoreServer(coreOpts);
108+
server = await setupCoreServerOnContext(resolvedCtx, instructions);
79109
} else {
80110
const config = opts.config ?? resolveAllianceConfig({});
81-
server = await setupCoreServer({
82-
config: opts.config ?? config,
83-
instructions,
84-
});
85-
resolvedCtx = resolveDefaultServerContext();
111+
const ctx = createServer(config);
112+
server = await setupCoreServerOnContext(ctx, instructions);
113+
resolvedCtx = ctx;
86114
}
87115

88116
registerBuiltinUrlGenerators(resolvedCtx);

0 commit comments

Comments
 (0)