opik-mcp 0.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -11
- package/build/cli.js +10 -8
- package/build/config.js +91 -27
- package/build/index.js +56 -76
- package/build/prompts/core-prompts.js +52 -0
- package/build/resources/opik-resources.js +196 -0
- package/build/test-client.js +2 -4
- package/build/tools/capabilities.js +183 -0
- package/build/tools/dataset.js +172 -0
- package/build/tools/integration.js +3 -3
- package/build/tools/metrics.js +51 -47
- package/build/tools/project.js +69 -48
- package/build/tools/prompt.js +88 -49
- package/build/tools/registration.js +110 -0
- package/build/tools/schema.js +13 -0
- package/build/tools/trace.js +319 -378
- package/build/transports/streamable-http-transport.js +298 -0
- package/build/transports/types.js +0 -3
- package/build/utils/capabilities.js +34 -4
- package/build/utils/opik-sdk.js +119 -0
- package/build/utils/remote-auth.js +175 -0
- package/build/utils/request-context.js +32 -0
- package/package.json +25 -20
- package/build/transports/sse-transport.js +0 -169
package/build/tools/metrics.js
CHANGED
|
@@ -1,62 +1,66 @@
|
|
|
1
|
-
import { makeApiRequest } from '../utils/api.js';
|
|
2
1
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
2
|
+
import { callSdk, getOpikApi, getRequestOptions, mapMetricType, resolveProjectIdentifier, } from '../utils/opik-sdk.js';
|
|
3
|
+
import { registerTool } from './registration.js';
|
|
4
|
+
import { isoDateSchema, workspaceNameSchema } from './schema.js';
|
|
4
5
|
export const loadMetricTools = (server) => {
|
|
5
|
-
server
|
|
6
|
-
metricName: z
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
registerTool(server, 'get-metrics', 'Get project metrics for a date range and optional metric type.', {
|
|
7
|
+
metricName: z
|
|
8
|
+
.string()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe('Optional metric type/alias (TRACE_COUNT, TOKEN_USAGE, COST, DURATION, FEEDBACK).'),
|
|
11
|
+
projectId: z.string().optional().describe('Optional project ID.'),
|
|
12
|
+
projectName: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Optional project name (alternative to projectId).'),
|
|
16
|
+
startDate: isoDateSchema,
|
|
17
|
+
endDate: isoDateSchema,
|
|
18
|
+
workspaceName: workspaceNameSchema,
|
|
11
19
|
}, async (args) => {
|
|
12
|
-
const { metricName, projectId, projectName, startDate, endDate } = args;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
// If no project specified, we need to find one for the API to work
|
|
26
|
-
const projectsResponse = await makeApiRequest(`/v1/private/projects?page=1&size=1`);
|
|
27
|
-
if (projectsResponse.data &&
|
|
28
|
-
projectsResponse.data.content &&
|
|
29
|
-
projectsResponse.data.content.length > 0) {
|
|
30
|
-
const firstProject = projectsResponse.data.content[0];
|
|
31
|
-
queryParams.push(`project_id=${firstProject.id}`);
|
|
32
|
-
logToFile(`No project specified, using first available: ${firstProject.name} (${firstProject.id})`);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
return {
|
|
36
|
-
content: [
|
|
37
|
-
{
|
|
38
|
-
type: 'text',
|
|
39
|
-
text: 'Error: No project ID or name provided, and no projects found',
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
};
|
|
43
|
-
}
|
|
20
|
+
const { metricName, projectId, projectName, startDate, endDate, workspaceName } = args;
|
|
21
|
+
const resolved = await resolveProjectIdentifier(projectId, projectName, workspaceName);
|
|
22
|
+
if (resolved.error || (!resolved.projectId && !resolved.projectName)) {
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: 'text',
|
|
27
|
+
text: `Error: ${resolved.error || 'No project available for metrics query'}`,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
44
31
|
}
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
32
|
+
if (!resolved.projectId) {
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: 'text',
|
|
37
|
+
text: 'Error: Metrics queries require a resolvable project ID',
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
51
41
|
}
|
|
52
|
-
const
|
|
42
|
+
const metricType = mapMetricType(metricName);
|
|
43
|
+
const api = getOpikApi();
|
|
44
|
+
const response = await callSdk(() => api.projects.getProjectMetrics(resolved.projectId, {
|
|
45
|
+
...(metricType && { metricType }),
|
|
46
|
+
interval: 'DAILY',
|
|
47
|
+
...(startDate && { intervalStart: new Date(startDate) }),
|
|
48
|
+
...(endDate && { intervalEnd: new Date(endDate) }),
|
|
49
|
+
}, getRequestOptions(workspaceName)));
|
|
53
50
|
if (!response.data) {
|
|
54
51
|
return {
|
|
55
52
|
content: [{ type: 'text', text: response.error || 'Failed to fetch metrics' }],
|
|
56
53
|
};
|
|
57
54
|
}
|
|
55
|
+
const metricWarning = metricName && !metricType
|
|
56
|
+
? `\nNote: metricName "${metricName}" is not a known metric type in the SDK and was ignored.`
|
|
57
|
+
: '';
|
|
58
58
|
return {
|
|
59
59
|
content: [
|
|
60
|
+
{
|
|
61
|
+
type: 'text',
|
|
62
|
+
text: `Metrics for project ${resolved.projectId}${metricWarning}`,
|
|
63
|
+
},
|
|
60
64
|
{
|
|
61
65
|
type: 'text',
|
|
62
66
|
text: JSON.stringify(response.data, null, 2),
|
package/build/tools/project.js
CHANGED
|
@@ -1,53 +1,74 @@
|
|
|
1
|
-
import { makeApiRequest } from '../utils/api.js';
|
|
2
1
|
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
import { callSdk, getOpikApi, getRequestOptions } from '../utils/opik-sdk.js';
|
|
3
|
+
import { registerTool } from './registration.js';
|
|
4
|
+
import { pageSchema, sizeSchema, workspaceNameSchema } from './schema.js';
|
|
5
|
+
export const loadProjectTools = (server, options = {}) => {
|
|
6
|
+
const { includeReadOps = true, includeMutations = true } = options;
|
|
7
|
+
if (includeReadOps) {
|
|
8
|
+
registerTool(server, 'list-projects', 'List projects in the active workspace to find IDs for traces and metrics operations.', {
|
|
9
|
+
page: pageSchema,
|
|
10
|
+
size: sizeSchema(10),
|
|
11
|
+
workspaceName: workspaceNameSchema,
|
|
12
|
+
}, async (args) => {
|
|
13
|
+
const { page, size, workspaceName } = args;
|
|
14
|
+
const api = getOpikApi();
|
|
15
|
+
const response = await callSdk(() => api.projects.findProjects({ page, size }, getRequestOptions(workspaceName)));
|
|
16
|
+
if (!response.data) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: 'text', text: response.error || 'Failed to fetch projects' }],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
13
21
|
return {
|
|
14
|
-
content: [
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: 'text',
|
|
25
|
+
text: `Found ${response.data.total} projects (page ${response.data.page} of ${Math.ceil(response.data.total / response.data.size)})`,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'text',
|
|
29
|
+
text: JSON.stringify(response.data.content, null, 2),
|
|
30
|
+
},
|
|
31
|
+
],
|
|
15
32
|
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
33
|
+
}, {
|
|
34
|
+
title: 'List Projects',
|
|
35
|
+
annotations: {
|
|
36
|
+
readOnlyHint: true,
|
|
37
|
+
destructiveHint: false,
|
|
38
|
+
idempotentHint: true,
|
|
39
|
+
openWorldHint: false,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (includeMutations) {
|
|
44
|
+
registerTool(server, 'create-project', 'Create a new project for traces, prompts, and evaluation runs.', {
|
|
45
|
+
name: z.string().min(1).describe('Project name.'),
|
|
46
|
+
description: z.string().optional().describe('Optional project description.'),
|
|
47
|
+
workspaceName: workspaceNameSchema,
|
|
48
|
+
}, async (args) => {
|
|
49
|
+
const { name, description, workspaceName } = args;
|
|
50
|
+
const api = getOpikApi();
|
|
51
|
+
const response = await callSdk(() => api.projects.createProject({
|
|
52
|
+
name,
|
|
53
|
+
...(description && { description }),
|
|
54
|
+
}, getRequestOptions(workspaceName)));
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: 'text',
|
|
59
|
+
text: response.error || 'Successfully created project',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}, {
|
|
64
|
+
title: 'Create Project',
|
|
65
|
+
annotations: {
|
|
66
|
+
readOnlyHint: false,
|
|
67
|
+
destructiveHint: false,
|
|
68
|
+
idempotentHint: false,
|
|
69
|
+
openWorldHint: false,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
52
73
|
return server;
|
|
53
74
|
};
|
package/build/tools/prompt.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { makeApiRequest } from '../utils/api.js';
|
|
2
1
|
import { z } from 'zod';
|
|
2
|
+
import { callSdk, getOpikApi } from '../utils/opik-sdk.js';
|
|
3
|
+
import { registerTool } from './registration.js';
|
|
4
|
+
import { pageSchema, sizeSchema } from './schema.js';
|
|
3
5
|
export const loadPromptTools = (server) => {
|
|
4
|
-
server
|
|
5
|
-
page:
|
|
6
|
-
size:
|
|
7
|
-
name: z.string().optional().describe('
|
|
6
|
+
registerTool(server, 'get-prompts', 'List prompts with optional name filtering.', {
|
|
7
|
+
page: pageSchema,
|
|
8
|
+
size: sizeSchema(10),
|
|
9
|
+
name: z.string().optional().describe('Optional prompt name filter.'),
|
|
8
10
|
}, async (args) => {
|
|
9
11
|
const { page, size, name } = args;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
url += `&name=${encodeURIComponent(name)}`;
|
|
13
|
-
const response = await makeApiRequest(url);
|
|
12
|
+
const api = getOpikApi();
|
|
13
|
+
const response = await callSdk(() => api.prompts.getPrompts({ page, size, name }));
|
|
14
14
|
if (!response.data) {
|
|
15
15
|
return {
|
|
16
16
|
content: [{ type: 'text', text: response.error || 'Failed to fetch prompts' }],
|
|
@@ -29,21 +29,18 @@ export const loadPromptTools = (server) => {
|
|
|
29
29
|
],
|
|
30
30
|
};
|
|
31
31
|
});
|
|
32
|
-
server
|
|
33
|
-
name: z.string().min(1).describe('
|
|
34
|
-
description: z.string().optional().describe('
|
|
35
|
-
tags: z.array(z.string()).optional().describe('
|
|
32
|
+
registerTool(server, 'create-prompt', 'Create a prompt definition.', {
|
|
33
|
+
name: z.string().min(1).describe('Prompt name.'),
|
|
34
|
+
description: z.string().optional().describe('Optional prompt description.'),
|
|
35
|
+
tags: z.array(z.string().min(1)).optional().describe('Optional prompt tags.'),
|
|
36
36
|
}, async (args) => {
|
|
37
37
|
const { name, description, tags } = args;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
method: 'POST',
|
|
45
|
-
body: JSON.stringify(requestBody),
|
|
46
|
-
});
|
|
38
|
+
const api = getOpikApi();
|
|
39
|
+
const response = await callSdk(() => api.prompts.createPrompt({
|
|
40
|
+
name,
|
|
41
|
+
...(description && { description }),
|
|
42
|
+
...(tags && { metadata: { tags } }),
|
|
43
|
+
}));
|
|
47
44
|
return {
|
|
48
45
|
content: [
|
|
49
46
|
{
|
|
@@ -53,18 +50,36 @@ export const loadPromptTools = (server) => {
|
|
|
53
50
|
],
|
|
54
51
|
};
|
|
55
52
|
});
|
|
56
|
-
server
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
registerTool(server, 'get-prompt-by-id', 'Get a prompt by ID.', {
|
|
54
|
+
promptId: z.string().min(1).describe('Prompt ID.'),
|
|
55
|
+
}, async (args) => {
|
|
56
|
+
const { promptId } = args;
|
|
57
|
+
const api = getOpikApi();
|
|
58
|
+
const response = await callSdk(() => api.prompts.getPromptById(promptId));
|
|
59
|
+
if (!response.data) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: 'text', text: response.error || 'Failed to fetch prompt' }],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: JSON.stringify(response.data, null, 2),
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
registerTool(server, 'get-prompt-version', 'Get a specific prompt version by name and optional commit.', {
|
|
74
|
+
name: z.string().min(1).describe('Prompt name.'),
|
|
75
|
+
commit: z.string().optional().describe('Optional commit/version identifier.'),
|
|
59
76
|
}, async (args) => {
|
|
60
77
|
const { name, commit } = args;
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
body: JSON.stringify(requestBody),
|
|
67
|
-
});
|
|
78
|
+
const api = getOpikApi();
|
|
79
|
+
const response = await callSdk(() => api.prompts.retrievePromptVersion({
|
|
80
|
+
name,
|
|
81
|
+
...(commit && { commit }),
|
|
82
|
+
}));
|
|
68
83
|
if (!response.data) {
|
|
69
84
|
return {
|
|
70
85
|
content: [{ type: 'text', text: response.error || 'Failed to fetch prompt version' }],
|
|
@@ -79,25 +94,49 @@ export const loadPromptTools = (server) => {
|
|
|
79
94
|
],
|
|
80
95
|
};
|
|
81
96
|
});
|
|
82
|
-
server
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
registerTool(server, 'delete-prompt', 'Delete a prompt by ID.', {
|
|
98
|
+
promptId: z.string().min(1).describe('Prompt ID.'),
|
|
99
|
+
}, async (args) => {
|
|
100
|
+
const { promptId } = args;
|
|
101
|
+
const api = getOpikApi();
|
|
102
|
+
const response = await callSdk(() => api.prompts.deletePrompt(promptId));
|
|
103
|
+
if (response.error) {
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: 'text', text: response.error || 'Failed to delete prompt' }],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: 'text',
|
|
112
|
+
text: `Successfully deleted prompt ${promptId}`,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
registerTool(server, 'save-prompt-version', 'Create a new prompt version.', {
|
|
118
|
+
name: z.string().min(1).describe('Prompt name.'),
|
|
119
|
+
template: z.string().min(1).describe('Prompt template body.'),
|
|
120
|
+
changeDescription: z
|
|
121
|
+
.string()
|
|
122
|
+
.optional()
|
|
123
|
+
.describe('Optional summary of changes in this version.'),
|
|
124
|
+
change_description: z.string().optional().describe('Deprecated alias for changeDescription.'),
|
|
86
125
|
metadata: z.record(z.any()).optional().describe('Additional metadata for the prompt version'),
|
|
87
|
-
type: z.enum(['mustache', 'jinja2']).optional().describe('Template
|
|
126
|
+
type: z.enum(['mustache', 'jinja2']).optional().describe('Template format.'),
|
|
88
127
|
}, async (args) => {
|
|
89
|
-
const { name, template, change_description, metadata, type } = args;
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
version
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
});
|
|
128
|
+
const { name, template, change_description, changeDescription, metadata, type } = args;
|
|
129
|
+
const resolvedChangeDescription = changeDescription ?? change_description;
|
|
130
|
+
const api = getOpikApi();
|
|
131
|
+
const response = await callSdk(() => api.prompts.createPromptVersion({
|
|
132
|
+
name,
|
|
133
|
+
version: {
|
|
134
|
+
template,
|
|
135
|
+
...(resolvedChangeDescription && { changeDescription: resolvedChangeDescription }),
|
|
136
|
+
...(metadata && { metadata }),
|
|
137
|
+
...(type && { type }),
|
|
138
|
+
},
|
|
139
|
+
}));
|
|
101
140
|
if (!response.data) {
|
|
102
141
|
return {
|
|
103
142
|
content: [{ type: 'text', text: response.error || 'Failed to create prompt version' }],
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { runWithRequestContext } from '../utils/request-context.js';
|
|
2
|
+
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import config from '../config.js';
|
|
4
|
+
const MISSING_API_KEY_MESSAGE = [
|
|
5
|
+
'This Opik MCP request requires an API key.',
|
|
6
|
+
'Set OPIK_API_KEY in the environment where the server runs,',
|
|
7
|
+
'or send Authorization: Bearer <token> with MCP requests.',
|
|
8
|
+
'If you are onboarding in a coding agent or MCP client, start with setup guidance tools',
|
|
9
|
+
'like get-opik-help or get-server-info, then add your key and retry.',
|
|
10
|
+
].join(' ');
|
|
11
|
+
function inferAnnotations(name) {
|
|
12
|
+
const readPrefixes = ['get-', 'list-', 'search-', 'read-'];
|
|
13
|
+
const mutatePrefixes = ['create-', 'delete-', 'update-', 'add-', 'save-'];
|
|
14
|
+
if (readPrefixes.some(prefix => name.startsWith(prefix))) {
|
|
15
|
+
return {
|
|
16
|
+
readOnlyHint: true,
|
|
17
|
+
destructiveHint: false,
|
|
18
|
+
idempotentHint: true,
|
|
19
|
+
openWorldHint: false,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (mutatePrefixes.some(prefix => name.startsWith(prefix))) {
|
|
23
|
+
return {
|
|
24
|
+
readOnlyHint: false,
|
|
25
|
+
destructiveHint: name.startsWith('delete-'),
|
|
26
|
+
idempotentHint: false,
|
|
27
|
+
openWorldHint: false,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
function withRequestContext(handler, requiresApiKey = true) {
|
|
33
|
+
return (...args) => {
|
|
34
|
+
const extra = [...args]
|
|
35
|
+
.reverse()
|
|
36
|
+
.find(arg => arg && typeof arg === 'object' && 'authInfo' in arg);
|
|
37
|
+
const authInfo = extra?.authInfo;
|
|
38
|
+
const context = {
|
|
39
|
+
apiKey: authInfo?.token,
|
|
40
|
+
workspaceName: authInfo?.extra?.workspaceName,
|
|
41
|
+
};
|
|
42
|
+
if (requiresApiKey && !(context.apiKey || config.apiKey)) {
|
|
43
|
+
return {
|
|
44
|
+
content: [
|
|
45
|
+
{
|
|
46
|
+
type: 'text',
|
|
47
|
+
text: MISSING_API_KEY_MESSAGE,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return runWithRequestContext(context, () => handler(...args));
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function registerTool(server, name, description, inputSchema, handler, options = {}) {
|
|
56
|
+
const wrappedHandler = withRequestContext(handler, options.requiresApiKey !== false);
|
|
57
|
+
if (typeof server.registerTool === 'function') {
|
|
58
|
+
const inferredAnnotations = inferAnnotations(name);
|
|
59
|
+
const mergedAnnotations = {
|
|
60
|
+
...inferredAnnotations,
|
|
61
|
+
...options.annotations,
|
|
62
|
+
};
|
|
63
|
+
server.registerTool(name, {
|
|
64
|
+
...(options.title && { title: options.title }),
|
|
65
|
+
description,
|
|
66
|
+
inputSchema,
|
|
67
|
+
...(Object.keys(mergedAnnotations).length > 0 && { annotations: mergedAnnotations }),
|
|
68
|
+
...(options.outputSchema && { outputSchema: options.outputSchema }),
|
|
69
|
+
...(options._meta && { _meta: options._meta }),
|
|
70
|
+
}, wrappedHandler);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
server.tool(name, description, inputSchema, wrappedHandler);
|
|
74
|
+
}
|
|
75
|
+
export function registerResource(server, name, uri, description, readCallback) {
|
|
76
|
+
const wrappedReadCallback = withRequestContext(readCallback);
|
|
77
|
+
if (typeof server.registerResource === 'function') {
|
|
78
|
+
server.registerResource(name, uri, {
|
|
79
|
+
description,
|
|
80
|
+
}, wrappedReadCallback);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
server.resource(name, uri, wrappedReadCallback);
|
|
84
|
+
}
|
|
85
|
+
export function registerResourceTemplate(server, name, uriTemplate, description, readCallback, listCallback) {
|
|
86
|
+
const wrappedReadCallback = withRequestContext(readCallback);
|
|
87
|
+
const wrappedListCallback = listCallback ? withRequestContext(listCallback) : undefined;
|
|
88
|
+
const template = new ResourceTemplate(uriTemplate, {
|
|
89
|
+
list: wrappedListCallback,
|
|
90
|
+
});
|
|
91
|
+
if (typeof server.registerResource === 'function') {
|
|
92
|
+
server.registerResource(name, template, {
|
|
93
|
+
description,
|
|
94
|
+
}, wrappedReadCallback);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
server.resource(name, template, wrappedReadCallback);
|
|
98
|
+
}
|
|
99
|
+
export function registerPrompt(server, name, description, argsSchema, handler, options = {}) {
|
|
100
|
+
const wrappedHandler = withRequestContext(handler);
|
|
101
|
+
if (typeof server.registerPrompt === 'function') {
|
|
102
|
+
server.registerPrompt(name, {
|
|
103
|
+
...(options.title && { title: options.title }),
|
|
104
|
+
description,
|
|
105
|
+
argsSchema,
|
|
106
|
+
}, wrappedHandler);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
server.prompt(name, description, argsSchema, wrappedHandler);
|
|
110
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const pageSchema = z.number().int().min(1).default(1).describe('1-based page number.');
|
|
3
|
+
export const sizeSchema = (defaultSize, max = 100) => z.number().int().min(1).max(max).default(defaultSize).describe(`Page size (1-${max}).`);
|
|
4
|
+
export const workspaceNameSchema = z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.optional()
|
|
8
|
+
.describe('Workspace override for local/stdio mode. Ignored when remote token-to-workspace mapping is enforced.');
|
|
9
|
+
export const isoDateSchema = z
|
|
10
|
+
.string()
|
|
11
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('Date in YYYY-MM-DD format.');
|