From f6062740ee98074fb66bba8b84c1b58615ec5b00 Mon Sep 17 00:00:00 2001 From: Vincent Horvath Date: Sat, 20 Sep 2025 01:39:12 -0400 Subject: [PATCH 1/6] add optional resource annotations --- src/types.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/types.ts b/src/types.ts index 262e3b623..e89d1063d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -518,6 +518,28 @@ export const BlobResourceContentsSchema = ResourceContentsSchema.extend({ blob: Base64Schema, }); +/** + * Optional annotations providing clients additional context about a resource. + */ +export const ResourceAnnotationsSchema = z + .object({ + /** + * Intended audience(s) for the resource. + */ + audience: z.optional(z.array(z.enum(["user", "assistant"]))), + + /** + * Importance hint for the resource, from 0 (least) to 1 (most). + */ + priority: z.optional(z.number().min(0).max(1)), + + /** + * ISO 8601 timestamp for the most recent modification. + */ + lastModified: z.optional(z.string().datetime({ offset: true })), + }) + .passthrough(); + /** * A known resource that the server is capable of reading. */ @@ -539,6 +561,11 @@ export const ResourceSchema = BaseMetadataSchema.extend({ */ mimeType: z.optional(z.string()), + /** + * Optional annotations for the client. + */ + annotations: z.optional(ResourceAnnotationsSchema), + /** * An optional list of icons for this resource. */ @@ -1608,6 +1635,7 @@ export type PaginatedResult = Infer; export type ResourceContents = Infer; export type TextResourceContents = Infer; export type BlobResourceContents = Infer; +export type ResourceAnnotations = Infer; export type Resource = Infer; export type ResourceTemplate = Infer; export type ListResourcesRequest = Infer; From 432f50f215a233df7022cb7749474ee9d68b7839 Mon Sep 17 00:00:00 2001 From: Vincent Horvath Date: Sat, 20 Sep 2025 01:56:47 -0400 Subject: [PATCH 2/6] add annotation metadata tests --- src/server/mcp.test.ts | 10 ++++++++++ src/spec.types.test.ts | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index d9142702f..1146cefb1 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -2111,6 +2111,11 @@ describe("resource()", () => { { description: "Test resource", mimeType: "text/plain", + annotations: { + audience: ["assistant"], + priority: 0.42, + lastModified: "2025-01-12T15:00:58Z" + } }, async () => ({ contents: [ @@ -2140,6 +2145,11 @@ describe("resource()", () => { expect(result.resources).toHaveLength(1); expect(result.resources[0].description).toBe("Test resource"); expect(result.resources[0].mimeType).toBe("text/plain"); + expect(result.resources[0].annotations).toEqual({ + audience: ["assistant"], + priority: 0.42, + lastModified: "2025-01-12T15:00:58Z" + }); }); /*** diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index 5aa497f4a..71a4a14c8 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -439,6 +439,13 @@ function checkBlobResourceContents( sdk = spec; spec = sdk; } +function checkResourceAnnotations( + sdk: RemovePassthrough, + spec: SpecTypes.Annotations +) { + sdk = spec; + spec = sdk; +} function checkResource( sdk: RemovePassthrough, spec: SpecTypes.Resource From 5c913f39b5301be20ed550eccebfdde5bef2b2da Mon Sep 17 00:00:00 2001 From: Konstantin Konstantinov Date: Tue, 2 Dec 2025 22:29:03 +0200 Subject: [PATCH 3/6] merge commit --- src/server/mcp.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index 76bee2a71..7dc4742e6 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -2315,7 +2315,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { version: '1.0' }); - const mockDate = new Date().toISOString() + const mockDate = new Date().toISOString(); mcpServer.resource( 'test', 'test://resource', From 9f14269dd0888159840b6ef6abded9276941e4c5 Mon Sep 17 00:00:00 2001 From: Konstantin Konstantinov Date: Tue, 2 Dec 2025 22:32:21 +0200 Subject: [PATCH 4/6] add annotations in all places per spec --- src/types.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/types.ts b/src/types.ts index 2fe0cfada..56311fb81 100644 --- a/src/types.ts +++ b/src/types.ts @@ -868,6 +868,12 @@ export const ResourceTemplateSchema = z.object({ */ mimeType: z.optional(z.string()), + + /** + * Optional annotations for the client. + */ + annotations: AnnotationsSchema.optional(), + /** * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) * for notes on _meta usage. @@ -1060,6 +1066,11 @@ export const TextContentSchema = z.object({ */ text: z.string(), + /** + * Optional annotations for the client. + */ + annotations: AnnotationsSchema.optional(), + /** * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) * for notes on _meta usage. @@ -1081,6 +1092,11 @@ export const ImageContentSchema = z.object({ */ mimeType: z.string(), + /** + * Optional annotations for the client. + */ + annotations: AnnotationsSchema.optional(), + /** * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) * for notes on _meta usage. @@ -1102,6 +1118,11 @@ export const AudioContentSchema = z.object({ */ mimeType: z.string(), + /** + * Optional annotations for the client. + */ + annotations: AnnotationsSchema.optional(), + /** * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) * for notes on _meta usage. @@ -1145,6 +1166,10 @@ export const ToolUseContentSchema = z export const EmbeddedResourceSchema = z.object({ type: z.literal('resource'), resource: z.union([TextResourceContentsSchema, BlobResourceContentsSchema]), + /** + * Optional annotations for the client. + */ + annotations: AnnotationsSchema.optional(), /** * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) * for notes on _meta usage. From 959d2350fc6c4c007869b98f8ba718f43544fb7e Mon Sep 17 00:00:00 2001 From: Konstantin Konstantinov Date: Tue, 2 Dec 2025 22:32:36 +0200 Subject: [PATCH 5/6] prettier fix --- src/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 56311fb81..744877db1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -868,7 +868,6 @@ export const ResourceTemplateSchema = z.object({ */ mimeType: z.optional(z.string()), - /** * Optional annotations for the client. */ From 96e9f95ffb2956eb89f082c51bae2c4592d73846 Mon Sep 17 00:00:00 2001 From: Konstantin Konstantinov Date: Wed, 3 Dec 2025 07:16:51 +0200 Subject: [PATCH 6/6] add tests for annotations --- src/types.test.ts | 63 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/src/types.test.ts b/src/types.test.ts index 4570a443a..e0b17c628 100644 --- a/src/types.test.ts +++ b/src/types.test.ts @@ -95,68 +95,118 @@ describe('Types', () => { describe('ContentBlock', () => { test('should validate text content', () => { + const mockDate = new Date().toISOString(); const textContent = { type: 'text', - text: 'Hello, world!' + text: 'Hello, world!', + annotations: { + audience: ['user'], + priority: 0.5, + lastModified: mockDate + } }; const result = ContentBlockSchema.safeParse(textContent); expect(result.success).toBe(true); if (result.success) { expect(result.data.type).toBe('text'); + expect(result.data.annotations).toEqual({ + audience: ['user'], + priority: 0.5, + lastModified: mockDate + }); } }); test('should validate image content', () => { + const mockDate = new Date().toISOString(); const imageContent = { type: 'image', data: 'aGVsbG8=', // base64 encoded "hello" - mimeType: 'image/png' + mimeType: 'image/png', + annotations: { + audience: ['user'], + priority: 0.5, + lastModified: mockDate + } }; const result = ContentBlockSchema.safeParse(imageContent); expect(result.success).toBe(true); if (result.success) { expect(result.data.type).toBe('image'); + expect(result.data.annotations).toEqual({ + audience: ['user'], + priority: 0.5, + lastModified: mockDate + }); } }); test('should validate audio content', () => { + const mockDate = new Date().toISOString(); const audioContent = { type: 'audio', data: 'aGVsbG8=', // base64 encoded "hello" - mimeType: 'audio/mp3' + mimeType: 'audio/mp3', + annotations: { + audience: ['user'], + priority: 0.5, + lastModified: mockDate + } }; const result = ContentBlockSchema.safeParse(audioContent); expect(result.success).toBe(true); if (result.success) { expect(result.data.type).toBe('audio'); + expect(result.data.annotations).toEqual({ + audience: ['user'], + priority: 0.5, + lastModified: mockDate + }); } }); test('should validate resource link content', () => { + const mockDate = new Date().toISOString(); const resourceLink = { type: 'resource_link', uri: 'file:///path/to/file.txt', name: 'file.txt', - mimeType: 'text/plain' + mimeType: 'text/plain', + annotations: { + audience: ['user'], + priority: 0.5, + lastModified: new Date().toISOString() + } }; const result = ContentBlockSchema.safeParse(resourceLink); expect(result.success).toBe(true); if (result.success) { expect(result.data.type).toBe('resource_link'); + expect(result.data.annotations).toEqual({ + audience: ['user'], + priority: 0.5, + lastModified: mockDate + }); } }); test('should validate embedded resource content', () => { + const mockDate = new Date().toISOString(); const embeddedResource = { type: 'resource', resource: { uri: 'file:///path/to/file.txt', mimeType: 'text/plain', text: 'File contents' + }, + annotations: { + audience: ['user'], + priority: 0.5, + lastModified: mockDate } }; @@ -164,6 +214,11 @@ describe('Types', () => { expect(result.success).toBe(true); if (result.success) { expect(result.data.type).toBe('resource'); + expect(result.data.annotations).toEqual({ + audience: ['user'], + priority: 0.5, + lastModified: mockDate + }); } }); });