Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ Tagged releases are published to npm from GitHub Actions when a **GitHub Release

- **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).
- **`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.
- **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)

### Changed

- **Breaking (`list_sources`):** response shape `sources: string[]` → `sources: { name, description? }[]`. Migration: [MIGRATION.md § Unreleased list_sources](docs/MIGRATION.md#unreleased-list_sources-response-shape).
- **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).
- **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)
- **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).

## [0.4.0] - 2026-06-24
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![License: BSL-1.0](https://img.shields.io/badge/License-BSL--1.0-blue.svg)](https://opensource.org/licenses/BSL-1.0)
[![CI](https://github.com/cppalliance/pinecone-read-only-mcp-typescript/workflows/CI/badge.svg)](https://github.com/cppalliance/pinecone-read-only-mcp-typescript/actions)

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.
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.

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

Expand Down
2 changes: 1 addition & 1 deletion docs/PACKAGE_SPLIT_EVAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ Both packages would share the same four production dependencies from the root `p

| Dependency | Current range | Alignment note |
| ---------- | ------------- | -------------- |
| `@modelcontextprotocol/sdk` | `^1.25.3` | MCP protocol surface; must stay aligned across core and Alliance |
| `@modelcontextprotocol/sdk` | `^1.29.0` | MCP protocol surface; must stay aligned across core and Alliance |
| `@pinecone-database/pinecone` | `^7.1.0` | Client API used by `PineconeClient`; core owns the wrapper |
| `dotenv` | `^17.2.3` | Env loading in config resolvers |
| `zod` | `^4.3.6` | Tool input schemas and response validation |
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"prepack": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.3",
"@modelcontextprotocol/sdk": "^1.29.0",
"@pinecone-database/pinecone": "^8.0.0",
"dotenv": "^17.2.3",
"zod": "^4.3.6"
Expand Down
171 changes: 171 additions & 0 deletions src/__tests__/mcp-rc-readiness.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { afterEach, describe, expect, it } from 'vitest';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
import { LATEST_PROTOCOL_VERSION, type JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
import { SERVER_NAME, SERVER_VERSION } from '../constants.js';
import { setupCoreServer, teardownServer, type ServerHandle } from '../core/setup.js';
import { setupAllianceServer } from '../alliance/setup.js';
import { resolveAllianceConfig } from '../alliance/config.js';
import { createIsolatedContext } from '../core/server/server-context.js';
import {
createTestServerContext,
isolateFromDefaultContext,
makeMockPineconeClient,
} from '../core/server/tools/test-helpers.js';

/**
* End-to-end MCP verification harness (#202).
*
* Drives the real `McpServer` over an in-memory transport with the SDK's own
* `Client`, so a future SDK bump (the 2026-07-28 RC protocol revision, once it
* ships) is verified in one place: the initialize handshake and protocol
* negotiation, the full registered tool surface, and a round-trip tool call.
* The protocol assertions key off the SDK's `LATEST_PROTOCOL_VERSION`, so when
* the RC SDK is pinned they re-check the server against the new revision with no
* edits here. If the RC is not published in time, this still guards the current
* pinned SDK.
*/

const CORE_TOOLS = [
'list_namespaces',
'namespace_router',
'count',
'query',
'keyword_search',
'query_documents',
'generate_urls',
'guided_query',
].sort();

/** A fresh, isolated core server (own context + mock client) so several can coexist. */
async function freshCoreServer(namespaces: string[] = ['ns']): Promise<ServerHandle> {
const ctx = createTestServerContext({ client: makeMockPineconeClient(namespaces) as never });
return setupCoreServer({ context: ctx });
}

/** Link the SDK Client to a live server over paired in-memory transports. */
async function connectClient(server: ServerHandle): Promise<Client> {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
const client = new Client({ name: 'rc-readiness-harness', version: '0.0.0' });
await client.connect(clientTransport);
return client;
}

/** Send a raw JSON-RPC initialize and return the negotiated result. */
async function rawInitialize(
server: ServerHandle,
requestedProtocolVersion: string
): Promise<{ protocolVersion: string; serverInfo: { name: string; version: string } }> {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
try {
return await new Promise<{
protocolVersion: string;
serverInfo: { name: string; version: string };
}>((resolve, reject) => {
clientTransport.onmessage = (message: JSONRPCMessage) => {
if (!('id' in message) || message.id !== 1) return;
if ('error' in message) {
reject(new Error(message.error.message));
} else if ('result' in message) {
resolve(
message.result as {
protocolVersion: string;
serverInfo: { name: string; version: string };
}
);
}
};
void clientTransport
.start()
.then(() =>
clientTransport.send({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: requestedProtocolVersion,
capabilities: {},
clientInfo: { name: 'raw-harness', version: '0.0.0' },
},
})
)
.catch(reject);
});
} finally {
await clientTransport.close();
}
}

describe('MCP RC-readiness harness (#202)', () => {
afterEach(() => {
teardownServer();
isolateFromDefaultContext();
});

it('initializes over the transport and round-trips server metadata', async () => {
const server = await freshCoreServer();
const client = await connectClient(server);

// A resolved connect proves the initialize handshake and protocol
// negotiation succeeded: the SDK Client throws if the server answers with a
// protocolVersion outside SUPPORTED_PROTOCOL_VERSIONS.
expect(client.getServerVersion()).toEqual({ name: SERVER_NAME, version: SERVER_VERSION });
expect(client.getServerCapabilities()?.tools).toBeDefined();
expect(client.getInstructions()).toBeTruthy();

await client.close();
});

it('exposes the full core tool surface through tools/list', async () => {
const server = await freshCoreServer();
const client = await connectClient(server);

const names = (await client.listTools()).tools.map((t) => t.name).sort();
expect(names).toEqual(CORE_TOOLS);

await client.close();
});

it('registers the Alliance suggest_query_params tool on top of the core surface', async () => {
const ctx = createIsolatedContext(
resolveAllianceConfig({ apiKey: 'sk-test', indexName: 'test-index' }),
{ client: makeMockPineconeClient(['ns']) as never }
);
const server = await setupAllianceServer({ context: ctx });
const client = await connectClient(server);

const names = (await client.listTools()).tools.map((t) => t.name);
for (const core of CORE_TOOLS) expect(names).toContain(core);
expect(names).toContain('suggest_query_params');

await client.close();
});

it('round-trips a tool call over the transport', async () => {
const server = await freshCoreServer(['alpha', 'beta']);
const client = await connectClient(server);

const res = await client.callTool({ name: 'list_namespaces', arguments: {} });
expect(res.isError ?? false).toBe(false);
// The seeded namespaces must round-trip through the handler and transport,
// not just any array, so a regression returning empty/garbage content fails.
const text = (res.content as Array<{ type: string; text: string }>)[0]?.text ?? '';
expect(text).toContain('alpha');
expect(text).toContain('beta');

await client.close();
});

it('negotiates the SDK latest protocol version and falls back for an unknown request', async () => {
// A server connects to one transport for its lifetime, so use a fresh one per probe.
const negotiated = await rawInitialize(await freshCoreServer(), LATEST_PROTOCOL_VERSION);
expect(negotiated.protocolVersion).toBe(LATEST_PROTOCOL_VERSION);
expect(negotiated.serverInfo).toMatchObject({ name: SERVER_NAME, version: SERVER_VERSION });

// An unsupported request must not error; the server falls back to its latest.
const fallback = await rawInitialize(await freshCoreServer(), 'not-a-real-protocol-version');
expect(fallback.protocolVersion).toBe(LATEST_PROTOCOL_VERSION);
});
});
Loading