Skip to content

Commit 02014e0

Browse files
authored
test: MCP verification harness + bump SDK floor to ^1.29.0 (#202) (#205)
* test: MCP verification harness + bump SDK floor to ^1.29.0 (#202) Bump the declared @modelcontextprotocol/sdk floor from ^1.25.3 to ^1.29.0 to match the version the lockfile already resolved, readying the server for the upcoming MCP RC protocol revision. McpServer construction, tool registration, and the stdio transport use current SDK APIs and are unchanged. Add an end-to-end verification harness that drives the real server over an in-memory transport with the SDK client: initialize + protocol negotiation, the full registered tool surface (core + Alliance), and a round-trip tool call. The protocol assertions key off the SDK's LATEST_PROTOCOL_VERSION, so pinning the RC SDK re-runs the check with no test edits. Sync the SDK version references in README, PACKAGE_SPLIT_EVAL, and CHANGELOG. * Address #205 review: correct stdio CHANGELOG claim + harden raw probe - CHANGELOG: the harness verifies McpServer construction and tool registration via server.connect() over the in-memory transport; the production StdioServerTransport wiring is unchanged and reviewed, not exercised (drop the overclaim that stdio is re-verified). - Raw initialize probe closes its client transport in a finally, and its onmessage handler is typed with the SDK JSONRPCMessage instead of any. * Format mcp-rc-readiness.test.ts with Prettier The raw-probe rework tripped the format:check gate; apply prettier --write.
1 parent 42fad89 commit 02014e0

6 files changed

Lines changed: 177 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ Tagged releases are published to npm from GitHub Actions when a **GitHub Release
1212

1313
- **Per-source and per-namespace private config:** optional `description` (source-level) and `namespaces` map (`description` + declared `metadata_schema`) in JSON config files (`PINECONE_CONFIG_FILE` only — not inline `PINECONE_SOURCES`). Declared schemas skip live sampling; stale declared namespaces surface as `config_warnings` in `list_namespaces`. See [CONFIGURATION.md](docs/CONFIGURATION.md#json-config-file).
1414
- **`list_namespaces`:** optional per-namespace `schema_source` (`declared` | `sampled`), optional per-namespace `description` (from private config), and top-level `config_warnings` when private config declarations do not match live Pinecone data.
15+
- **Tests:** MCP verification harness ([src/__tests__/mcp-rc-readiness.test.ts](src/__tests__/mcp-rc-readiness.test.ts)) that drives the real server over an in-memory transport with the SDK client (initialize and protocol negotiation, the full registered tool surface, and a round-trip tool call), so a future SDK or MCP protocol bump is verified in one place. Protocol assertions key off the SDK's `LATEST_PROTOCOL_VERSION`, so pinning the RC SDK re-runs the check with no test edits. (#202)
1516

1617
### Changed
1718

1819
- **Breaking (`list_sources`):** response shape `sources: string[]``sources: { name, description? }[]`. Migration: [MIGRATION.md § Unreleased list_sources](docs/MIGRATION.md#unreleased-list_sources-response-shape).
1920
- **Breaking (library API):** `PineconeClient.listNamespacesWithMetadata()` now returns `{ namespaces, warnings }` instead of a bare array. Migration: [MIGRATION.md § Unreleased PineconeClient.listNamespacesWithMetadata](docs/MIGRATION.md#unreleased-pineconeclientlistnamespaceswithmetadata-return-shape).
21+
- **Dependencies:** Bumped the declared `@modelcontextprotocol/sdk` floor from `^1.25.3` to `^1.29.0` to match the resolved version and ready the server for the upcoming MCP RC protocol revision. `McpServer` construction and tool registration are re-verified by the harness above through `server.connect()` over the SDK in-memory transport; the production `StdioServerTransport` wiring in `src/index.ts` is unchanged and was reviewed, not exercised, by the harness. (#202)
2022
- **Instructions:** Trimmed operator/install/deploy content (env-var setup, misconfiguration note, Alliance CLI index/rerank defaults, stderr logging config) from `CORE_SERVER_INSTRUCTIONS` and `ALLIANCE_INSTRUCTIONS_APPENDIX` — reduces per-session token cost; no behavior change. Replaced colliding Alliance appendix steps 4–5 with unnumbered "Manual Alliance flow" bullets (includes `PINECONE_DISABLE_SUGGEST_FLOW=true` escape clause). Full detail remains in [docs/CONFIGURATION.md](docs/CONFIGURATION.md).
2123

2224
### Removed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![License: BSL-1.0](https://img.shields.io/badge/License-BSL--1.0-blue.svg)](https://opensource.org/licenses/BSL-1.0)
66
[![CI](https://github.com/cppalliance/pinecone-read-only-mcp-typescript/workflows/CI/badge.svg)](https://github.com/cppalliance/pinecone-read-only-mcp-typescript/actions)
77

8-
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that implements the MCP specification via `@modelcontextprotocol/sdk` v1.25+ and provides semantic search over Pinecone vector databases using hybrid search (dense + sparse) with reranking.
8+
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that implements the MCP specification via `@modelcontextprotocol/sdk` v1.29+ and provides semantic search over Pinecone vector databases using hybrid search (dense + sparse) with reranking.
99

1010
Current version: 0.4.0 (npm `latest` after publish). Pin `@0.4.0` in install and MCP config for reproducible upgrades.
1111

docs/PACKAGE_SPLIT_EVAL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ Both packages would share the same four production dependencies from the root `p
182182

183183
| Dependency | Current range | Alignment note |
184184
| ---------- | ------------- | -------------- |
185-
| `@modelcontextprotocol/sdk` | `^1.25.3` | MCP protocol surface; must stay aligned across core and Alliance |
185+
| `@modelcontextprotocol/sdk` | `^1.29.0` | MCP protocol surface; must stay aligned across core and Alliance |
186186
| `@pinecone-database/pinecone` | `^7.1.0` | Client API used by `PineconeClient`; core owns the wrapper |
187187
| `dotenv` | `^17.2.3` | Env loading in config resolvers |
188188
| `zod` | `^4.3.6` | Tool input schemas and response validation |

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"prepack": "npm run build"
7474
},
7575
"dependencies": {
76-
"@modelcontextprotocol/sdk": "^1.25.3",
76+
"@modelcontextprotocol/sdk": "^1.29.0",
7777
"@pinecone-database/pinecone": "^8.0.0",
7878
"dotenv": "^17.2.3",
7979
"zod": "^4.3.6"
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { afterEach, describe, expect, it } from 'vitest';
2+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
4+
import { LATEST_PROTOCOL_VERSION, type JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
5+
import { SERVER_NAME, SERVER_VERSION } from '../constants.js';
6+
import { setupCoreServer, teardownServer, type ServerHandle } from '../core/setup.js';
7+
import { setupAllianceServer } from '../alliance/setup.js';
8+
import { resolveAllianceConfig } from '../alliance/config.js';
9+
import { createIsolatedContext } from '../core/server/server-context.js';
10+
import {
11+
createTestServerContext,
12+
isolateFromDefaultContext,
13+
makeMockPineconeClient,
14+
} from '../core/server/tools/test-helpers.js';
15+
16+
/**
17+
* End-to-end MCP verification harness (#202).
18+
*
19+
* Drives the real `McpServer` over an in-memory transport with the SDK's own
20+
* `Client`, so a future SDK bump (the 2026-07-28 RC protocol revision, once it
21+
* ships) is verified in one place: the initialize handshake and protocol
22+
* negotiation, the full registered tool surface, and a round-trip tool call.
23+
* The protocol assertions key off the SDK's `LATEST_PROTOCOL_VERSION`, so when
24+
* the RC SDK is pinned they re-check the server against the new revision with no
25+
* edits here. If the RC is not published in time, this still guards the current
26+
* pinned SDK.
27+
*/
28+
29+
const CORE_TOOLS = [
30+
'list_namespaces',
31+
'namespace_router',
32+
'count',
33+
'query',
34+
'keyword_search',
35+
'query_documents',
36+
'generate_urls',
37+
'guided_query',
38+
].sort();
39+
40+
/** A fresh, isolated core server (own context + mock client) so several can coexist. */
41+
async function freshCoreServer(namespaces: string[] = ['ns']): Promise<ServerHandle> {
42+
const ctx = createTestServerContext({ client: makeMockPineconeClient(namespaces) as never });
43+
return setupCoreServer({ context: ctx });
44+
}
45+
46+
/** Link the SDK Client to a live server over paired in-memory transports. */
47+
async function connectClient(server: ServerHandle): Promise<Client> {
48+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
49+
await server.connect(serverTransport);
50+
const client = new Client({ name: 'rc-readiness-harness', version: '0.0.0' });
51+
await client.connect(clientTransport);
52+
return client;
53+
}
54+
55+
/** Send a raw JSON-RPC initialize and return the negotiated result. */
56+
async function rawInitialize(
57+
server: ServerHandle,
58+
requestedProtocolVersion: string
59+
): Promise<{ protocolVersion: string; serverInfo: { name: string; version: string } }> {
60+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
61+
await server.connect(serverTransport);
62+
try {
63+
return await new Promise<{
64+
protocolVersion: string;
65+
serverInfo: { name: string; version: string };
66+
}>((resolve, reject) => {
67+
clientTransport.onmessage = (message: JSONRPCMessage) => {
68+
if (!('id' in message) || message.id !== 1) return;
69+
if ('error' in message) {
70+
reject(new Error(message.error.message));
71+
} else if ('result' in message) {
72+
resolve(
73+
message.result as {
74+
protocolVersion: string;
75+
serverInfo: { name: string; version: string };
76+
}
77+
);
78+
}
79+
};
80+
void clientTransport
81+
.start()
82+
.then(() =>
83+
clientTransport.send({
84+
jsonrpc: '2.0',
85+
id: 1,
86+
method: 'initialize',
87+
params: {
88+
protocolVersion: requestedProtocolVersion,
89+
capabilities: {},
90+
clientInfo: { name: 'raw-harness', version: '0.0.0' },
91+
},
92+
})
93+
)
94+
.catch(reject);
95+
});
96+
} finally {
97+
await clientTransport.close();
98+
}
99+
}
100+
101+
describe('MCP RC-readiness harness (#202)', () => {
102+
afterEach(() => {
103+
teardownServer();
104+
isolateFromDefaultContext();
105+
});
106+
107+
it('initializes over the transport and round-trips server metadata', async () => {
108+
const server = await freshCoreServer();
109+
const client = await connectClient(server);
110+
111+
// A resolved connect proves the initialize handshake and protocol
112+
// negotiation succeeded: the SDK Client throws if the server answers with a
113+
// protocolVersion outside SUPPORTED_PROTOCOL_VERSIONS.
114+
expect(client.getServerVersion()).toEqual({ name: SERVER_NAME, version: SERVER_VERSION });
115+
expect(client.getServerCapabilities()?.tools).toBeDefined();
116+
expect(client.getInstructions()).toBeTruthy();
117+
118+
await client.close();
119+
});
120+
121+
it('exposes the full core tool surface through tools/list', async () => {
122+
const server = await freshCoreServer();
123+
const client = await connectClient(server);
124+
125+
const names = (await client.listTools()).tools.map((t) => t.name).sort();
126+
expect(names).toEqual(CORE_TOOLS);
127+
128+
await client.close();
129+
});
130+
131+
it('registers the Alliance suggest_query_params tool on top of the core surface', async () => {
132+
const ctx = createIsolatedContext(
133+
resolveAllianceConfig({ apiKey: 'sk-test', indexName: 'test-index' }),
134+
{ client: makeMockPineconeClient(['ns']) as never }
135+
);
136+
const server = await setupAllianceServer({ context: ctx });
137+
const client = await connectClient(server);
138+
139+
const names = (await client.listTools()).tools.map((t) => t.name);
140+
for (const core of CORE_TOOLS) expect(names).toContain(core);
141+
expect(names).toContain('suggest_query_params');
142+
143+
await client.close();
144+
});
145+
146+
it('round-trips a tool call over the transport', async () => {
147+
const server = await freshCoreServer(['alpha', 'beta']);
148+
const client = await connectClient(server);
149+
150+
const res = await client.callTool({ name: 'list_namespaces', arguments: {} });
151+
expect(res.isError ?? false).toBe(false);
152+
// The seeded namespaces must round-trip through the handler and transport,
153+
// not just any array, so a regression returning empty/garbage content fails.
154+
const text = (res.content as Array<{ type: string; text: string }>)[0]?.text ?? '';
155+
expect(text).toContain('alpha');
156+
expect(text).toContain('beta');
157+
158+
await client.close();
159+
});
160+
161+
it('negotiates the SDK latest protocol version and falls back for an unknown request', async () => {
162+
// A server connects to one transport for its lifetime, so use a fresh one per probe.
163+
const negotiated = await rawInitialize(await freshCoreServer(), LATEST_PROTOCOL_VERSION);
164+
expect(negotiated.protocolVersion).toBe(LATEST_PROTOCOL_VERSION);
165+
expect(negotiated.serverInfo).toMatchObject({ name: SERVER_NAME, version: SERVER_VERSION });
166+
167+
// An unsupported request must not error; the server falls back to its latest.
168+
const fallback = await rawInitialize(await freshCoreServer(), 'not-a-real-protocol-version');
169+
expect(fallback.protocolVersion).toBe(LATEST_PROTOCOL_VERSION);
170+
});
171+
});

0 commit comments

Comments
 (0)