Skip to content

Commit ccff5c9

Browse files
jonathanMLDevzho
andauthored
refactor: introduce ServerContext and createServer (cppalliance#130)
* added server-context.ts and updated related files * use disableSuggestFlow * addressed CI test error * addressed ai review * addressed ai reviews --------- Co-authored-by: zho <jornathanm910923@gmail.com>
1 parent ee4058a commit ccff5c9

19 files changed

Lines changed: 891 additions & 268 deletions

docs/MIGRATION.md

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,67 @@ This guide is for **library and MCP client authors** upgrading from earlier **0.
66

77
Under [semver 0.y.z](https://semver.org/spec/v2.0.0.html#spec-item-4), **0.1.x → 0.2.0 is a breaking minor** — pin `@0.2.0` only after reading this guide.
88

9+
## Unreleased: `ServerContext` instance APIs (phase 1)
10+
11+
**Rationale:** Process-global singletons (Pinecone client slot, config, URL registry, suggest-flow gate, namespaces cache) complicate testing and multi-tenant embedding. Phase 1 introduces an opt-in **`ServerContext`** without removing legacy getters.
12+
13+
**Now (0.2.x — unchanged for existing embedders):**
14+
15+
```ts
16+
import { PineconeClient, setPineconeClient } from '@will-cppa/pinecone-read-only-mcp';
17+
import {
18+
resolveAllianceConfig,
19+
setupAllianceServer,
20+
} from '@will-cppa/pinecone-read-only-mcp/alliance';
21+
22+
const config = resolveAllianceConfig({ apiKey: process.env.PINECONE_API_KEY! });
23+
setPineconeClient(
24+
new PineconeClient({
25+
/* ... */
26+
})
27+
);
28+
const server = await setupAllianceServer(config);
29+
```
30+
31+
Module-level helpers (`getPineconeClient`, `registerUrlGenerator`, `requireSuggested`, etc.) continue to work; they delegate to a process-default context.
32+
33+
**New (opt-in instance path):**
34+
35+
```ts
36+
import { createServer, PineconeClient } from '@will-cppa/pinecone-read-only-mcp';
37+
import {
38+
resolveAllianceConfig,
39+
setupAllianceServer,
40+
} from '@will-cppa/pinecone-read-only-mcp/alliance';
41+
42+
const config = resolveAllianceConfig({ apiKey: process.env.PINECONE_API_KEY! });
43+
const ctx = createServer(config); // installs process-default + returns instance
44+
ctx.setClient(
45+
new PineconeClient({
46+
apiKey: config.apiKey,
47+
indexName: config.indexName,
48+
sparseIndexName: config.sparseIndexName,
49+
rerankModel: config.rerankModel,
50+
defaultTopK: config.defaultTopK,
51+
requestTimeoutMs: config.requestTimeoutMs,
52+
})
53+
);
54+
const server = await setupAllianceServer(config); // uses process-default ctx for migrated tools
55+
```
56+
57+
For custom tool wiring, pass `ctx` to migrated registrars:
58+
59+
```ts
60+
import { registerQueryTool, registerCountTool, registerListNamespacesTool } from ''; // internal today
61+
registerQueryTool(server, ctx);
62+
```
63+
64+
**Later (future minors/major):** Legacy module getters will be marked `### Deprecated` per [deprecation-policy.md](./deprecation-policy.md). Multi-tenant HTTP embedders should use one `ServerContext` per session rather than sharing process-global state.
65+
66+
See also [deprecation-policy.md § Future instance APIs](./deprecation-policy.md#future-instance-apis-servercontext).
67+
68+
---
69+
970
## Unreleased: core vs Alliance config defaults
1071

1172
**Rationale:** Generic npm consumers must not silently connect to Alliance infrastructure or inherit Alliance rerank settings when using `resolveConfig` from the package root.
@@ -70,12 +131,12 @@ const ns = userInput.trim();
70131

71132
**`code` values (discriminated union):**
72133

73-
| `code` | `recoverable` | Notes |
74-
| ------ | --------------- | ----- |
75-
| `FLOW_GATE` | `true` | Suggestion: call `suggest_query_params` for the namespace first |
76-
| `VALIDATION` | `true` | **`field` required** — input or `metadata_filter` dot-path |
77-
| `PINECONE_ERROR` | `true` or `false` | Upstream / network / Pinecone failures |
78-
| `TIMEOUT` | `true` | Outbound deadline exceeded |
134+
| `code` | `recoverable` | Notes |
135+
| ---------------- | ----------------- | --------------------------------------------------------------- |
136+
| `FLOW_GATE` | `true` | Suggestion: call `suggest_query_params` for the namespace first |
137+
| `VALIDATION` | `true` | **`field` required** — input or `metadata_filter` dot-path |
138+
| `PINECONE_ERROR` | `true` or `false` | Upstream / network / Pinecone failures |
139+
| `TIMEOUT` | `true` | Outbound deadline exceeded |
79140

80141
**Migration steps:**
81142

@@ -104,12 +165,12 @@ const ns = userInput.trim();
104165

105166
**Rationale:** Align routing hints with the unified `query` tool vocabulary.
106167

107-
| Old (legacy) | New |
108-
| ------------ | --- |
109-
| `query_fast` | `fast` |
110-
| `query_detailed` | `detailed` |
111-
| `count` | `count` (unchanged) |
112-
| _(n/a)_ | `full` (explicit preset) |
168+
| Old (legacy) | New |
169+
| ---------------- | ------------------------ |
170+
| `query_fast` | `fast` |
171+
| `query_detailed` | `detailed` |
172+
| `count` | `count` (unchanged) |
173+
| _(n/a)_ | `full` (explicit preset) |
113174

114175
**Migration steps:**
115176

@@ -122,10 +183,10 @@ const ns = userInput.trim();
122183

123184
**Rationale:** One hybrid tool with a `preset` knob instead of duplicate registrations.
124185

125-
| Legacy tool call | New `query` call |
126-
| ---------------- | ---------------- |
127-
| `query_fast({ ...params })` | `query({ ...params, preset: 'fast' })` |
128-
| `query_detailed({ ...params })` | `query({ ...params, preset: 'detailed' })` |
186+
| Legacy tool call | New `query` call |
187+
| ------------------------------- | --------------------------------------------------------------- |
188+
| `query_fast({ ...params })` | `query({ ...params, preset: 'fast' })` |
189+
| `query_detailed({ ...params })` | `query({ ...params, preset: 'detailed' })` |
129190
| Custom / explicit rerank+fields | `query({ ...params, preset: 'full', use_reranking?, fields? })` |
130191

131192
**Example:**

docs/deprecation-policy.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ Security fixes may break behavior when required; document impact in CHANGELOG an
6666

6767
## Future instance APIs (`ServerContext`)
6868

69-
A planned refactor introduces **`ServerContext`** and **`createServer(config)`** while keeping legacy module-level getters during a transition. That work will:
69+
Phase 1 of the **`ServerContext`** / **`createServer(config)`** refactor is available while legacy module-level getters remain supported. See [MIGRATION.md § ServerContext instance APIs](./MIGRATION.md#unreleased-servercontext-instance-apis-phase-1) for upgrade steps.
70+
71+
That work will:
7072

7173
- Add new instance APIs without removing legacy getters in the same release.
7274
- Document legacy getters under `### Deprecated` with a named removal target per this policy.
7375
- Link migration steps from [MIGRATION.md](./MIGRATION.md) to this document.
7476

75-
Until that migration guide is published, treat this section as the policy constraint for that refactor.
76-
7777
## CHANGELOG format for breaking changes
7878

7979
This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Each version block should use the sections that apply: `### Added`, `### Changed`, `### Deprecated`, `### Removed`, `### Fixed`.

examples/alliance/library-embedding-demo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* Pattern (mirrors `src/index.ts`):
55
* 1. `resolveAllianceConfig({ apiKey, indexName, ... })` — Alliance index/rerank defaults when unset.
6-
* 2. `new PineconeClient({ ... })` + `setPineconeClient(client)`.
6+
* 2. `new PineconeClient({ ... })` + `setPineconeClient(client)` (legacy), or `createServer(config)` + `ctx.setClient(...)`.
77
* 3. `await setupAllianceServer(config)` then `server.connect(transport)`.
88
*
99
* **Single process:** `setupAllianceServer` registers tools against process-global

src/core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
export { setPineconeClient } from './server/client-context.js';
10+
export { ServerContext, createServer, getDefaultServerContext } from './server/server-context.js';
1011
export {
1112
validateMetadataFilter,
1213
validateMetadataFilterDetailed,

src/core/server/client-context.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
import { PineconeClient } from '../pinecone-client.js';
2-
3-
// Global Pinecone client (initialized lazily)
4-
let pineconeClient: PineconeClient | null = null;
2+
import { getDefaultServerContext } from './server-context.js';
53

64
/** Return the shared Pinecone client; throws if setPineconeClient has not been called. */
75
export function getPineconeClient(): PineconeClient {
8-
if (!pineconeClient) {
9-
throw new Error('Pinecone client not initialized. Call setPineconeClient first.');
10-
}
11-
return pineconeClient;
6+
return getDefaultServerContext().getClientIfSet();
127
}
138

149
/** Set the shared Pinecone client used by all MCP tools. */
1510
export function setPineconeClient(client: PineconeClient): void {
16-
pineconeClient = client;
11+
getDefaultServerContext().setClient(client);
1712
}
1813

1914
/** Clear the shared client (used by {@link teardownServer} and tests). */
2015
export function clearPineconeClient(): void {
21-
pineconeClient = null;
16+
getDefaultServerContext().clearClient();
2217
}

src/core/server/config-context.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import type { ServerConfig } from '../config.js';
2-
import { resolveConfig } from '../config.js';
3-
4-
let activeConfig: ServerConfig | null = null;
2+
import {
3+
getDefaultServerContext,
4+
setDefaultServerContext,
5+
setPendingServerConfig,
6+
} from './server-context.js';
57

68
/** Replace the process-global server config (called from setup with CLI/env-derived config). */
79
export function setServerConfig(config: ServerConfig): void {
8-
activeConfig = config;
10+
setPendingServerConfig(config);
911
}
1012

1113
/** Clear active config so the next `getServerConfig()` resolves again (used by {@link teardownServer}). */
1214
export function resetServerConfig(): void {
13-
activeConfig = null;
15+
setDefaultServerContext(null);
1416
}
1517

1618
/**
@@ -22,8 +24,5 @@ export function resetServerConfig(): void {
2224
* pass config from `resolveAllianceConfig()` into `setupAllianceServer(config)`.
2325
*/
2426
export function getServerConfig(): ServerConfig {
25-
if (!activeConfig) {
26-
activeConfig = resolveConfig({});
27-
}
28-
return activeConfig;
27+
return getDefaultServerContext().getConfig();
2928
}
Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
1-
import { getServerConfig } from './config-context.js';
2-
import { getPineconeClient } from './client-context.js';
1+
import { getDefaultServerContext, type NamespaceInfo } from './server-context.js';
32

4-
export type NamespaceInfo = {
5-
namespace: string;
6-
recordCount: number;
7-
metadata: Record<string, string>;
8-
};
9-
10-
type CacheEntry = {
11-
data: NamespaceInfo[];
12-
expiresAt: number;
13-
};
14-
15-
let namespacesCache: CacheEntry | null = null;
3+
export type { NamespaceInfo };
164

175
/**
186
* Return namespace list with metadata; uses an in-memory cache whose TTL is
@@ -23,24 +11,10 @@ export async function getNamespacesWithCache(): Promise<{
2311
cache_hit: boolean;
2412
expires_at: number;
2513
}> {
26-
const now = Date.now();
27-
if (namespacesCache && now < namespacesCache.expiresAt) {
28-
return {
29-
data: namespacesCache.data,
30-
cache_hit: true,
31-
expires_at: namespacesCache.expiresAt,
32-
};
33-
}
34-
35-
const client = getPineconeClient();
36-
const data = await client.listNamespacesWithMetadata();
37-
const ttlMs = getServerConfig().cacheTtlMs;
38-
const expiresAt = now + ttlMs;
39-
namespacesCache = { data, expiresAt };
40-
return { data, cache_hit: false, expires_at: expiresAt };
14+
return getDefaultServerContext().getNamespacesWithCache();
4115
}
4216

4317
/** Clear the namespaces cache so the next call to getNamespacesWithCache refetches. */
4418
export function invalidateNamespacesCache(): void {
45-
namespacesCache = null;
19+
getDefaultServerContext().invalidateNamespacesCache();
4620
}

0 commit comments

Comments
 (0)