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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { callSdk, getOpikApi, getRequestOptions } from '../utils/opik-sdk.js';
|
|
2
|
+
import { registerResource, registerResourceTemplate } from '../tools/registration.js';
|
|
3
|
+
const DEFAULT_PAGE = 1;
|
|
4
|
+
const DEFAULT_SIZE = 10;
|
|
5
|
+
const MAX_SIZE = 100;
|
|
6
|
+
function parsePositiveInt(value, fallback, max = MAX_SIZE) {
|
|
7
|
+
const parsed = Number.parseInt(value || '', 10);
|
|
8
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
9
|
+
return fallback;
|
|
10
|
+
}
|
|
11
|
+
return Math.min(parsed, max);
|
|
12
|
+
}
|
|
13
|
+
function toReadError(uri, message) {
|
|
14
|
+
return {
|
|
15
|
+
contents: [
|
|
16
|
+
{
|
|
17
|
+
uri,
|
|
18
|
+
text: JSON.stringify({ error: message }, null, 2),
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function includeToolset(enabled, toolset) {
|
|
24
|
+
return enabled.has(toolset);
|
|
25
|
+
}
|
|
26
|
+
export function loadOpikResources(server, config) {
|
|
27
|
+
const enabledToolsets = new Set(config.enabledToolsets);
|
|
28
|
+
const hasTraceRead = includeToolset(enabledToolsets, 'core') ||
|
|
29
|
+
includeToolset(enabledToolsets, 'expert-trace-actions');
|
|
30
|
+
const hasPromptRead = includeToolset(enabledToolsets, 'expert-prompts');
|
|
31
|
+
const hasDatasetRead = includeToolset(enabledToolsets, 'expert-datasets');
|
|
32
|
+
const hasProjectRead = includeToolset(enabledToolsets, 'core');
|
|
33
|
+
registerResource(server, 'workspace-info', 'opik://workspace-info', 'Workspace information for the configured Opik MCP server.', async () => ({
|
|
34
|
+
contents: [
|
|
35
|
+
{
|
|
36
|
+
uri: 'opik://workspace-info',
|
|
37
|
+
text: JSON.stringify({
|
|
38
|
+
name: config.workspaceName,
|
|
39
|
+
apiUrl: config.apiBaseUrl,
|
|
40
|
+
selfHosted: config.isSelfHosted,
|
|
41
|
+
enabledToolsets: config.enabledToolsets,
|
|
42
|
+
}, null, 2),
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
}));
|
|
46
|
+
if (hasProjectRead) {
|
|
47
|
+
// Backward-compatible static URI while encouraging template usage for paging.
|
|
48
|
+
registerResource(server, 'projects-list', 'opik://projects-list', 'Project listing for the configured Opik workspace (first page only).', async () => {
|
|
49
|
+
const api = getOpikApi();
|
|
50
|
+
const response = await callSdk(() => api.projects.findProjects({ page: 1, size: 25 }));
|
|
51
|
+
if (!response.data) {
|
|
52
|
+
return toReadError('opik://projects-list', response.error || 'Failed to fetch projects');
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
contents: [
|
|
56
|
+
{
|
|
57
|
+
uri: 'opik://projects-list',
|
|
58
|
+
text: JSON.stringify(response.data, null, 2),
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
registerResourceTemplate(server, 'projects-page', 'opik://projects/{page}/{size}', 'Paginated project listing by page and size.', async (uri, variables) => {
|
|
64
|
+
const page = parsePositiveInt(variables.page, DEFAULT_PAGE);
|
|
65
|
+
const size = parsePositiveInt(variables.size, DEFAULT_SIZE);
|
|
66
|
+
const api = getOpikApi();
|
|
67
|
+
const response = await callSdk(() => api.projects.findProjects({ page, size }));
|
|
68
|
+
if (!response.data) {
|
|
69
|
+
return toReadError(uri.toString(), response.error || 'Failed to fetch projects');
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
contents: [{ uri: uri.toString(), text: JSON.stringify(response.data, null, 2) }],
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (hasPromptRead) {
|
|
77
|
+
registerResourceTemplate(server, 'prompts-page', 'opik://prompts/{page}/{size}', 'Paginated prompt listing by page and size.', async (uri, variables) => {
|
|
78
|
+
const page = parsePositiveInt(variables.page, DEFAULT_PAGE);
|
|
79
|
+
const size = parsePositiveInt(variables.size, DEFAULT_SIZE);
|
|
80
|
+
const api = getOpikApi();
|
|
81
|
+
const response = await callSdk(() => api.prompts.getPrompts({ page, size }));
|
|
82
|
+
if (!response.data) {
|
|
83
|
+
return toReadError(uri.toString(), response.error || 'Failed to fetch prompts');
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
contents: [{ uri: uri.toString(), text: JSON.stringify(response.data, null, 2) }],
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
registerResourceTemplate(server, 'prompt-latest', 'opik://prompt/{name}', 'Latest prompt version by prompt name.', async (uri, variables) => {
|
|
90
|
+
const name = decodeURIComponent(variables.name || '');
|
|
91
|
+
if (!name) {
|
|
92
|
+
return toReadError(uri.toString(), 'Prompt name is required');
|
|
93
|
+
}
|
|
94
|
+
const api = getOpikApi();
|
|
95
|
+
const response = await callSdk(() => api.prompts.retrievePromptVersion({ name }));
|
|
96
|
+
if (!response.data) {
|
|
97
|
+
return toReadError(uri.toString(), response.error || 'Failed to fetch prompt');
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
contents: [{ uri: uri.toString(), text: JSON.stringify(response.data, null, 2) }],
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
registerResourceTemplate(server, 'prompt-commit', 'opik://prompt/{name}/{commit}', 'Specific prompt version by prompt name and commit.', async (uri, variables) => {
|
|
104
|
+
const name = decodeURIComponent(variables.name || '');
|
|
105
|
+
const commit = decodeURIComponent(variables.commit || '');
|
|
106
|
+
if (!name || !commit) {
|
|
107
|
+
return toReadError(uri.toString(), 'Prompt name and commit are required');
|
|
108
|
+
}
|
|
109
|
+
const api = getOpikApi();
|
|
110
|
+
const response = await callSdk(() => api.prompts.retrievePromptVersion({ name, commit }));
|
|
111
|
+
if (!response.data) {
|
|
112
|
+
return toReadError(uri.toString(), response.error || 'Failed to fetch prompt version');
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
contents: [{ uri: uri.toString(), text: JSON.stringify(response.data, null, 2) }],
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (hasDatasetRead) {
|
|
120
|
+
registerResourceTemplate(server, 'datasets-page', 'opik://datasets/{page}/{size}', 'Paginated dataset listing by page and size.', async (uri, variables) => {
|
|
121
|
+
const page = parsePositiveInt(variables.page, DEFAULT_PAGE);
|
|
122
|
+
const size = parsePositiveInt(variables.size, DEFAULT_SIZE);
|
|
123
|
+
const api = getOpikApi();
|
|
124
|
+
const response = await callSdk(() => api.datasets.findDatasets({ page, size }));
|
|
125
|
+
if (!response.data) {
|
|
126
|
+
return toReadError(uri.toString(), response.error || 'Failed to fetch datasets');
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
contents: [{ uri: uri.toString(), text: JSON.stringify(response.data, null, 2) }],
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
registerResourceTemplate(server, 'dataset-by-id', 'opik://dataset/{datasetId}', 'Dataset details by dataset ID.', async (uri, variables) => {
|
|
133
|
+
const datasetId = decodeURIComponent(variables.datasetId || '');
|
|
134
|
+
if (!datasetId) {
|
|
135
|
+
return toReadError(uri.toString(), 'datasetId is required');
|
|
136
|
+
}
|
|
137
|
+
const api = getOpikApi();
|
|
138
|
+
const response = await callSdk(() => api.datasets.getDatasetById(datasetId));
|
|
139
|
+
if (!response.data) {
|
|
140
|
+
return toReadError(uri.toString(), response.error || 'Failed to fetch dataset');
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
contents: [{ uri: uri.toString(), text: JSON.stringify(response.data, null, 2) }],
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
registerResourceTemplate(server, 'dataset-items-page', 'opik://dataset/{datasetId}/items/{page}/{size}', 'Paginated dataset items by dataset ID, page, and size.', async (uri, variables) => {
|
|
147
|
+
const datasetId = decodeURIComponent(variables.datasetId || '');
|
|
148
|
+
const page = parsePositiveInt(variables.page, DEFAULT_PAGE);
|
|
149
|
+
const size = parsePositiveInt(variables.size, 25, 500);
|
|
150
|
+
if (!datasetId) {
|
|
151
|
+
return toReadError(uri.toString(), 'datasetId is required');
|
|
152
|
+
}
|
|
153
|
+
const api = getOpikApi();
|
|
154
|
+
const response = await callSdk(() => api.datasets.getDatasetItems(datasetId, { page, size }));
|
|
155
|
+
if (!response.data) {
|
|
156
|
+
return toReadError(uri.toString(), response.error || 'Failed to fetch dataset items');
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
contents: [{ uri: uri.toString(), text: JSON.stringify(response.data, null, 2) }],
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (hasTraceRead) {
|
|
164
|
+
registerResourceTemplate(server, 'trace-by-id', 'opik://trace/{traceId}', 'Trace details by trace ID.', async (uri, variables) => {
|
|
165
|
+
const traceId = decodeURIComponent(variables.traceId || '');
|
|
166
|
+
if (!traceId) {
|
|
167
|
+
return toReadError(uri.toString(), 'traceId is required');
|
|
168
|
+
}
|
|
169
|
+
const api = getOpikApi();
|
|
170
|
+
const response = await callSdk(() => api.traces.getTraceById(traceId, getRequestOptions()));
|
|
171
|
+
if (!response.data) {
|
|
172
|
+
return toReadError(uri.toString(), response.error || 'Failed to fetch trace');
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
contents: [{ uri: uri.toString(), text: JSON.stringify(response.data, null, 2) }],
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
registerResourceTemplate(server, 'traces-by-project-page', 'opik://traces/{projectId}/{page}/{size}', 'Paginated traces by project ID, page, and size.', async (uri, variables) => {
|
|
179
|
+
const projectId = decodeURIComponent(variables.projectId || '');
|
|
180
|
+
const page = parsePositiveInt(variables.page, DEFAULT_PAGE);
|
|
181
|
+
const size = parsePositiveInt(variables.size, DEFAULT_SIZE);
|
|
182
|
+
if (!projectId) {
|
|
183
|
+
return toReadError(uri.toString(), 'projectId is required');
|
|
184
|
+
}
|
|
185
|
+
const api = getOpikApi();
|
|
186
|
+
const response = await callSdk(() => api.traces.getTracesByProject({ projectId, page, size }, getRequestOptions()));
|
|
187
|
+
if (!response.data) {
|
|
188
|
+
return toReadError(uri.toString(), response.error || 'Failed to fetch traces');
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
contents: [{ uri: uri.toString(), text: JSON.stringify(response.data, null, 2) }],
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return server;
|
|
196
|
+
}
|
package/build/test-client.js
CHANGED
|
@@ -397,11 +397,9 @@ async function main() {
|
|
|
397
397
|
// Create the client
|
|
398
398
|
const client = new Client({
|
|
399
399
|
name: 'test-client',
|
|
400
|
-
version: '
|
|
400
|
+
version: '2.0.0',
|
|
401
401
|
}, {
|
|
402
|
-
capabilities: {
|
|
403
|
-
tools: {}, // We're interested in tools
|
|
404
|
-
},
|
|
402
|
+
capabilities: {},
|
|
405
403
|
});
|
|
406
404
|
try {
|
|
407
405
|
// Connect to the server
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getCapabilitiesDescription, getEnabledCapabilities, opikCapabilities, } from '../utils/capabilities.js';
|
|
3
|
+
import { getAllExampleTasks, getExampleForTask } from '../utils/examples.js';
|
|
4
|
+
import { getAllMetricsInfo, getMetricInfo } from '../utils/metrics-info.js';
|
|
5
|
+
import { getTracingInfo } from '../utils/tracing-info.js';
|
|
6
|
+
import { registerTool } from './registration.js';
|
|
7
|
+
function formatTopicHelp(topic) {
|
|
8
|
+
if (topic === 'general') {
|
|
9
|
+
const general = opikCapabilities.general;
|
|
10
|
+
return [
|
|
11
|
+
'# General API Information',
|
|
12
|
+
'',
|
|
13
|
+
`- API Version: ${general.apiVersion}`,
|
|
14
|
+
`- Authentication: ${general.authentication}`,
|
|
15
|
+
`- Rate Limit: ${general.rateLimit}`,
|
|
16
|
+
`- Supported Formats: ${general.supportedFormats.join(', ')}`,
|
|
17
|
+
].join('\n');
|
|
18
|
+
}
|
|
19
|
+
const section = opikCapabilities[topic];
|
|
20
|
+
return [
|
|
21
|
+
`# ${topic.charAt(0).toUpperCase()}${topic.slice(1)}`,
|
|
22
|
+
'',
|
|
23
|
+
`Available: ${section.available ? 'Yes' : 'No'}`,
|
|
24
|
+
'',
|
|
25
|
+
'## Features',
|
|
26
|
+
...section.features.map(feature => `- ${feature}`),
|
|
27
|
+
'',
|
|
28
|
+
'## Limitations',
|
|
29
|
+
...section.limitations.map(limitation => `- ${limitation}`),
|
|
30
|
+
].join('\n');
|
|
31
|
+
}
|
|
32
|
+
export const loadCapabilitiesTools = (server, config) => {
|
|
33
|
+
registerTool(server, 'get-server-info', 'Return server configuration and enabled Opik capabilities.', {}, async (_args) => {
|
|
34
|
+
const serverInfo = {
|
|
35
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
36
|
+
isSelfHosted: config.isSelfHosted,
|
|
37
|
+
workspaceName: config.workspaceName,
|
|
38
|
+
transport: config.transport,
|
|
39
|
+
mcpName: config.mcpName,
|
|
40
|
+
mcpVersion: config.mcpVersion,
|
|
41
|
+
mcpDefaultWorkspace: config.mcpDefaultWorkspace,
|
|
42
|
+
enabledToolsets: config.enabledToolsets,
|
|
43
|
+
hasApiKey: Boolean(config.apiKey),
|
|
44
|
+
capabilities: getEnabledCapabilities(config),
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: 'text',
|
|
50
|
+
text: JSON.stringify(serverInfo, null, 2),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}, { requiresApiKey: false });
|
|
55
|
+
registerTool(server, 'get-opik-help', 'Return Opik capability documentation, optionally filtered by topic.', {
|
|
56
|
+
topic: z
|
|
57
|
+
.enum(['prompts', 'projects', 'traces', 'metrics', 'general'])
|
|
58
|
+
.optional()
|
|
59
|
+
.describe('Optional capability topic to describe'),
|
|
60
|
+
}, async (args) => {
|
|
61
|
+
if (args.topic) {
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: formatTopicHelp(args.topic),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: 'text',
|
|
75
|
+
text: getCapabilitiesDescription(config),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}, { requiresApiKey: false });
|
|
80
|
+
registerTool(server, 'get-opik-examples', 'Return Opik usage examples for a requested task.', {
|
|
81
|
+
task: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('Optional task name, e.g. "create prompt", "log trace", "evaluate response"'),
|
|
85
|
+
}, async (args) => {
|
|
86
|
+
const tasks = getAllExampleTasks();
|
|
87
|
+
const example = getExampleForTask(args.task);
|
|
88
|
+
if (!example) {
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: 'text',
|
|
93
|
+
text: args.task
|
|
94
|
+
? `No specific example found for task: ${args.task}. Available tasks: ${tasks.join(', ')}`
|
|
95
|
+
: `Available tasks: ${tasks.join(', ')}`,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: 'text',
|
|
104
|
+
text: [
|
|
105
|
+
`# Example: ${example.title}`,
|
|
106
|
+
'',
|
|
107
|
+
'## Description',
|
|
108
|
+
example.description,
|
|
109
|
+
'',
|
|
110
|
+
'## Steps',
|
|
111
|
+
...example.steps.map((step, index) => `${index + 1}. ${step}`),
|
|
112
|
+
'',
|
|
113
|
+
'## Code Example',
|
|
114
|
+
example.codeExample.trim(),
|
|
115
|
+
].join('\n'),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
}, { requiresApiKey: false });
|
|
120
|
+
registerTool(server, 'get-opik-metrics-info', 'Return Opik metric definitions and usage guidance.', {
|
|
121
|
+
metric: z
|
|
122
|
+
.string()
|
|
123
|
+
.optional()
|
|
124
|
+
.describe('Optional metric name (e.g. hallucination, answerrelevance, moderation)'),
|
|
125
|
+
}, async (args) => {
|
|
126
|
+
if (args.metric) {
|
|
127
|
+
const metric = getMetricInfo(args.metric);
|
|
128
|
+
if (!metric) {
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: 'text',
|
|
133
|
+
text: `Metric not found: ${args.metric}`,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
content: [
|
|
140
|
+
{
|
|
141
|
+
type: 'text',
|
|
142
|
+
text: JSON.stringify(metric, null, 2),
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: 'text',
|
|
151
|
+
text: JSON.stringify(getAllMetricsInfo(), null, 2),
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
}, { requiresApiKey: false });
|
|
156
|
+
registerTool(server, 'get-opik-tracing-info', 'Return tracing guidance for traces, spans, feedback, search, and visualization.', {
|
|
157
|
+
topic: z
|
|
158
|
+
.enum(['traces', 'spans', 'feedback', 'search', 'visualization'])
|
|
159
|
+
.optional()
|
|
160
|
+
.describe('Optional tracing topic'),
|
|
161
|
+
}, async (args) => {
|
|
162
|
+
const info = getTracingInfo(args.topic);
|
|
163
|
+
if (!info) {
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: 'text',
|
|
168
|
+
text: `No tracing info found for topic: ${args.topic}`,
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{
|
|
176
|
+
type: 'text',
|
|
177
|
+
text: JSON.stringify(info, null, 2),
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}, { requiresApiKey: false });
|
|
182
|
+
return server;
|
|
183
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
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 loadDatasetTools = (server) => {
|
|
6
|
+
registerTool(server, 'list-datasets', 'List datasets with optional name filtering.', {
|
|
7
|
+
page: pageSchema,
|
|
8
|
+
size: sizeSchema(10),
|
|
9
|
+
name: z.string().optional().describe('Optional dataset name filter.'),
|
|
10
|
+
workspaceName: workspaceNameSchema,
|
|
11
|
+
}, async (args) => {
|
|
12
|
+
const { page = 1, size = 10, name, workspaceName } = args;
|
|
13
|
+
const api = getOpikApi();
|
|
14
|
+
const response = await callSdk(() => api.datasets.findDatasets({ page, size, name }, getRequestOptions(workspaceName)));
|
|
15
|
+
if (!response.data) {
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: 'text', text: response.error || 'Failed to fetch datasets' }],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
type: 'text',
|
|
24
|
+
text: `Found ${response.data.total} datasets (page ${response.data.page} of ${Math.ceil(response.data.total / response.data.size)})`,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: JSON.stringify(response.data.content, null, 2),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
registerTool(server, 'get-dataset-by-id', 'Get full details for a dataset by ID.', {
|
|
34
|
+
datasetId: z.string().min(1).describe('Dataset ID.'),
|
|
35
|
+
workspaceName: workspaceNameSchema,
|
|
36
|
+
}, async (args) => {
|
|
37
|
+
const { datasetId, workspaceName } = args;
|
|
38
|
+
const api = getOpikApi();
|
|
39
|
+
const response = await callSdk(() => api.datasets.getDatasetById(datasetId, getRequestOptions(workspaceName)));
|
|
40
|
+
if (!response.data) {
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: 'text', text: response.error || 'Failed to fetch dataset' }],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
registerTool(server, 'create-dataset', 'Create a dataset for evaluations and experiments.', {
|
|
50
|
+
name: z.string().min(1).describe('Dataset name.'),
|
|
51
|
+
description: z.string().optional().describe('Optional dataset description.'),
|
|
52
|
+
workspaceName: workspaceNameSchema,
|
|
53
|
+
}, async (args) => {
|
|
54
|
+
const { name, description, workspaceName } = args;
|
|
55
|
+
const api = getOpikApi();
|
|
56
|
+
const response = await callSdk(() => api.datasets.createDataset({
|
|
57
|
+
name,
|
|
58
|
+
...(description && { description }),
|
|
59
|
+
}, getRequestOptions(workspaceName)));
|
|
60
|
+
if (response.error) {
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: 'text', text: response.error || 'Failed to create dataset' }],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
content: [
|
|
67
|
+
{
|
|
68
|
+
type: 'text',
|
|
69
|
+
text: `Successfully created dataset: ${name}`,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
registerTool(server, 'delete-dataset', 'Delete a dataset by ID.', {
|
|
75
|
+
datasetId: z.string().min(1).describe('Dataset ID.'),
|
|
76
|
+
workspaceName: workspaceNameSchema,
|
|
77
|
+
}, async (args) => {
|
|
78
|
+
const { datasetId, workspaceName } = args;
|
|
79
|
+
const api = getOpikApi();
|
|
80
|
+
const response = await callSdk(() => api.datasets.deleteDataset(datasetId, getRequestOptions(workspaceName)));
|
|
81
|
+
if (response.error) {
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: 'text', text: response.error || 'Failed to delete dataset' }],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: 'text', text: `Successfully deleted dataset ${datasetId}` }],
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
registerTool(server, 'list-dataset-items', 'List items in a dataset.', {
|
|
91
|
+
datasetId: z.string().min(1).describe('Dataset ID.'),
|
|
92
|
+
page: pageSchema,
|
|
93
|
+
size: sizeSchema(25, 500),
|
|
94
|
+
workspaceName: workspaceNameSchema,
|
|
95
|
+
}, async (args) => {
|
|
96
|
+
const { datasetId, page = 1, size = 25, workspaceName } = args;
|
|
97
|
+
const api = getOpikApi();
|
|
98
|
+
const response = await callSdk(() => api.datasets.getDatasetItems(datasetId, { page, size }, getRequestOptions(workspaceName)));
|
|
99
|
+
if (!response.data) {
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: 'text', text: response.error || 'Failed to fetch dataset items' }],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: 'text',
|
|
108
|
+
text: `Found ${response.data.total} dataset items (page ${response.data.page} of ${Math.ceil(response.data.total / response.data.size)})`,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'text',
|
|
112
|
+
text: JSON.stringify(response.data.content, null, 2),
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
registerTool(server, 'create-dataset-item', 'Create one dataset item with input, expected output, and metadata.', {
|
|
118
|
+
datasetId: z.string().min(1).describe('Dataset ID.'),
|
|
119
|
+
input: z.record(z.any()).describe('Input payload.'),
|
|
120
|
+
expectedOutput: z.record(z.any()).optional().describe('Optional expected output payload.'),
|
|
121
|
+
metadata: z.record(z.any()).optional().describe('Optional metadata payload.'),
|
|
122
|
+
workspaceName: workspaceNameSchema,
|
|
123
|
+
}, async (args) => {
|
|
124
|
+
const { datasetId, input, expectedOutput, metadata, workspaceName } = args;
|
|
125
|
+
const api = getOpikApi();
|
|
126
|
+
const response = await callSdk(() => api.datasets.createOrUpdateDatasetItems({
|
|
127
|
+
datasetId,
|
|
128
|
+
items: [
|
|
129
|
+
{
|
|
130
|
+
source: 'manual',
|
|
131
|
+
data: {
|
|
132
|
+
input,
|
|
133
|
+
...(expectedOutput && { expected_output: expectedOutput }),
|
|
134
|
+
...(metadata && { metadata }),
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
}, getRequestOptions(workspaceName)));
|
|
139
|
+
if (response.error) {
|
|
140
|
+
return {
|
|
141
|
+
content: [{ type: 'text', text: response.error || 'Failed to create dataset item' }],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
content: [
|
|
146
|
+
{
|
|
147
|
+
type: 'text',
|
|
148
|
+
text: `Successfully created dataset item in dataset ${datasetId}`,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
registerTool(server, 'delete-dataset-item', 'Delete a dataset item by ID.', {
|
|
154
|
+
itemId: z.string().min(1).describe('Dataset item ID.'),
|
|
155
|
+
workspaceName: workspaceNameSchema,
|
|
156
|
+
}, async (args) => {
|
|
157
|
+
const { itemId, workspaceName } = args;
|
|
158
|
+
const api = getOpikApi();
|
|
159
|
+
const response = await callSdk(() => api.datasets.deleteDatasetItems({
|
|
160
|
+
itemIds: [itemId],
|
|
161
|
+
}, getRequestOptions(workspaceName)));
|
|
162
|
+
if (response.error) {
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: 'text', text: response.error || 'Failed to delete dataset item' }],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
content: [{ type: 'text', text: `Successfully deleted dataset item ${itemId}` }],
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
return server;
|
|
172
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { registerTool } from './registration.js';
|
|
2
2
|
const integrationDocs = `
|
|
3
3
|
# OPIK Agentic Onboarding
|
|
4
4
|
|
|
@@ -91,10 +91,10 @@ If issues are reported:
|
|
|
91
91
|
4. Apply and revalidate.
|
|
92
92
|
`;
|
|
93
93
|
export const loadIntegrationTools = (server) => {
|
|
94
|
-
server
|
|
94
|
+
registerTool(server, 'opik-integration-docs', 'Provides detailed documentation on how to integrate Opik with your LLM application', {}, async (_args) => {
|
|
95
95
|
return {
|
|
96
96
|
content: [{ type: 'text', text: integrationDocs }],
|
|
97
97
|
};
|
|
98
|
-
});
|
|
98
|
+
}, { requiresApiKey: false });
|
|
99
99
|
return server;
|
|
100
100
|
};
|