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
6 changes: 6 additions & 0 deletions .changeset/oss-telemetry-client-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ontos-ai/knowhere-sdk": patch
"@ontos-ai/knowhere-mcp": patch
---

Auto-attach official `created_by_client` / `client_version` document metadata on job creates so OSS telemetry can attribute client mix. Caller-provided metadata still wins.
13 changes: 13 additions & 0 deletions packages/mcp/src/__tests__/document-metadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, expect, it } from 'vitest';

import { MCP_DOCUMENT_METADATA_DEFAULTS } from '../document-metadata.js';
import { VERSION } from '../version.js';

describe('MCP_DOCUMENT_METADATA_DEFAULTS', () => {
it('identifies the MCP client with the package version', () => {
expect(MCP_DOCUMENT_METADATA_DEFAULTS).toEqual({
createdByClient: 'mcp',
clientVersion: VERSION,
});
});
});
3 changes: 3 additions & 0 deletions packages/mcp/src/__tests__/mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it, vi, type Mock } from 'vitest';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';

import { MCP_DOCUMENT_METADATA_DEFAULTS } from '../document-metadata.js';
import { createKnowhereMcpServer } from '../index.js';
import type {
Knowhere,
Expand Down Expand Up @@ -127,6 +128,7 @@ describe('knowhere MCP wrapper', () => {
namespace: undefined,
localDocumentId: 'local-report',
dataId: undefined,
documentMetadata: { ...MCP_DOCUMENT_METADATA_DEFAULTS },
model: 'base',
ocr: true,
docType: undefined,
Expand Down Expand Up @@ -289,6 +291,7 @@ describe('knowhere MCP wrapper', () => {
namespace: undefined,
localDocumentId: 'local-report',
dataId: undefined,
documentMetadata: { ...MCP_DOCUMENT_METADATA_DEFAULTS },
model: 'advanced',
ocr: undefined,
docType: undefined,
Expand Down
11 changes: 11 additions & 0 deletions packages/mcp/src/document-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { VERSION } from './version.js';

/**
* Official MCP client identity for job `document_metadata`.
* Caller-provided keys win; defaults fill missing keys only when merged
* upstream by the Node SDK `jobs.create` helper.
*/
export const MCP_DOCUMENT_METADATA_DEFAULTS = {
createdByClient: 'mcp',
clientVersion: VERSION,
} as const;
5 changes: 4 additions & 1 deletion packages/mcp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import {
DiskParsedDocumentStorage,
Knowhere,
VERSION,
ValidationError,
type AuthTokenProvider,
type Knowledge,
Expand All @@ -16,7 +15,9 @@ import {
import * as z from 'zod/v4';

import type { Permission } from './auth.js';
import { MCP_DOCUMENT_METADATA_DEFAULTS } from './document-metadata.js';
import { createKnowhereToolResult } from './tool-result-formatter.js';
import { VERSION } from './version.js';

const parsingParamsSchema = z
.object({
Expand Down Expand Up @@ -83,6 +84,7 @@ export async function createKnowhereMcpServer(
namespace: input.namespace,
localDocumentId: input.localDocumentId,
dataId: input.dataId,
documentMetadata: { ...MCP_DOCUMENT_METADATA_DEFAULTS },
...toFlatParsingParams(input.parsingParams),
}),
),
Expand Down Expand Up @@ -111,6 +113,7 @@ export async function createKnowhereMcpServer(
namespace: input.namespace,
localDocumentId: input.localDocumentId,
dataId: input.dataId,
documentMetadata: { ...MCP_DOCUMENT_METADATA_DEFAULTS },
...toFlatParsingParams(input.parsingParams),
}),
),
Expand Down
1 change: 1 addition & 0 deletions packages/mcp/src/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const VERSION = '2.1.4';
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ export { Knowhere, Knowhere as default } from './client.js';
// Version
export { VERSION } from './version.js';

// Document metadata helpers (official-client telemetry defaults)
export {
mergeDocumentMetadataDefaults,
NODE_SDK_DOCUMENT_METADATA_DEFAULTS,
} from './lib/document-metadata.js';

// Types
export type {
// Client types
Expand Down
42 changes: 42 additions & 0 deletions src/lib/__tests__/document-metadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest';

import { VERSION } from '../../version.js';
import {
mergeDocumentMetadataDefaults,
NODE_SDK_DOCUMENT_METADATA_DEFAULTS,
} from '../document-metadata.js';

describe('mergeDocumentMetadataDefaults', () => {
it('fills createdByClient and clientVersion when metadata is omitted', () => {
expect(mergeDocumentMetadataDefaults(NODE_SDK_DOCUMENT_METADATA_DEFAULTS)).toEqual({
createdByClient: 'node-sdk',
clientVersion: VERSION,
});
});

it('fills only missing keys when caller provides partial metadata', () => {
expect(
mergeDocumentMetadataDefaults(NODE_SDK_DOCUMENT_METADATA_DEFAULTS, {
title: 'Report.pdf',
}),
).toEqual({
createdByClient: 'node-sdk',
clientVersion: VERSION,
title: 'Report.pdf',
});
});

it('lets caller overrides win for createdByClient and clientVersion', () => {
expect(
mergeDocumentMetadataDefaults(NODE_SDK_DOCUMENT_METADATA_DEFAULTS, {
createdByClient: 'cli',
clientVersion: '9.9.9',
title: 'Report.pdf',
}),
).toEqual({
createdByClient: 'cli',
clientVersion: '9.9.9',
title: 'Report.pdf',
});
});
});
30 changes: 30 additions & 0 deletions src/lib/document-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { DocumentMetadata } from '../types/params.js';
import { VERSION } from '../version.js';

/**
* Official Knowhere client identity written into job `document_metadata`.
*
* Wire format (snake_case on the wire after HTTP serialization):
* `{ created_by_client, client_version }`.
*
* Merge rule used by every official client: caller-provided keys win;
* defaults only fill keys that are missing.
*/
export const NODE_SDK_DOCUMENT_METADATA_DEFAULTS = {
createdByClient: 'node-sdk',
clientVersion: VERSION,
} as const satisfies DocumentMetadata;

/**
* Merge official-client defaults under caller-provided document metadata.
* Existing keys on `provided` are preserved; defaults fill gaps only.
*/
export function mergeDocumentMetadataDefaults(
defaults: DocumentMetadata,
provided?: DocumentMetadata | null,
): DocumentMetadata {
if (!provided) {
return { ...defaults };
}
return { ...defaults, ...provided };
}
34 changes: 34 additions & 0 deletions src/resources/__tests__/jobs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Jobs } from '../jobs.js';
import { VERSION } from '../../version.js';
import { InvalidStateError, NotFoundError } from '../../errors/index.js';
import type { HttpClient } from '../../lib/http-client.js';
import type { Job } from '../../types/job.js';
Expand Down Expand Up @@ -77,6 +78,10 @@ describe('Jobs Resource', () => {
expect(mockHttpClient.post).toHaveBeenCalledWith('/v2/jobs', {
sourceType: 'url',
sourceUrl: 'https://example.com/doc.pdf',
documentMetadata: {
createdByClient: 'node-sdk',
clientVersion: VERSION,
},
});
});

Expand Down Expand Up @@ -230,11 +235,40 @@ describe('Jobs Resource', () => {
expect.objectContaining({
documentMetadata: {
createdByClient: 'notebook',
clientVersion: VERSION,
title: 'Document.pdf',
},
}),
);
});

it('should let caller clientVersion override the SDK default', async () => {
mockHttpClient.post.mockResolvedValue({
jobId: 'job-override',
status: 'pending',
sourceType: 'url',
createdAt: new Date(),
});

await jobs.create({
sourceType: 'url',
sourceUrl: 'https://example.com/doc.pdf',
documentMetadata: {
createdByClient: 'cli',
clientVersion: '9.9.9',
},
});

expect(mockHttpClient.post).toHaveBeenCalledWith(
'/v2/jobs',
expect.objectContaining({
documentMetadata: {
createdByClient: 'cli',
clientVersion: '9.9.9',
},
}),
);
});
});

describe('get', () => {
Expand Down
18 changes: 16 additions & 2 deletions src/resources/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { BaseResource } from './base.js';
import type { Job, JobResult } from '../types/job.js';
import type { CreateJobParams, UploadParams, WaitOptions, LoadOptions } from '../types/params.js';
import type { ParseResult } from '../types/result.js';
import {
mergeDocumentMetadataDefaults,
NODE_SDK_DOCUMENT_METADATA_DEFAULTS,
} from '../lib/document-metadata.js';
import { uploadFile } from '../lib/upload.js';
import { pollJobStatus } from '../lib/polling.js';
import { parseResult } from '../lib/result-parser.js';
Expand All @@ -15,10 +19,20 @@ export class Jobs extends BaseResource {
private pendingUploadJobs = new Map<string, Job>();

/**
* Create a new parsing job
* Create a new parsing job.
*
* Auto-attaches `documentMetadata.createdByClient` / `clientVersion` for
* OSS telemetry. Caller-provided metadata keys win; defaults fill gaps only.
*/
async create(params: CreateJobParams): Promise<Job> {
const job = await this.httpClient.post<Job>(this.endpoint('/jobs'), params);
const documentMetadata = mergeDocumentMetadataDefaults(
NODE_SDK_DOCUMENT_METADATA_DEFAULTS,
params.documentMetadata,
);
const job = await this.httpClient.post<Job>(this.endpoint('/jobs'), {
...params,
documentMetadata,
});
if (job.uploadUrl) {
this.pendingUploadJobs.set(job.jobId, job);
}
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = '0.1.0';
export const VERSION = '2.1.3';
Loading