@svadmin/mcp 0.2.5 → 0.2.6

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.
Files changed (2) hide show
  1. package/package.json +7 -3
  2. package/src/index.test.ts +0 -250
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@svadmin/mcp",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "MCP (Model Context Protocol) server — expose svadmin DataProvider as tools for AI agents",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "files": [
8
- "src/**/*"
8
+ "src/**/*.ts",
9
+ "!src/**/*.test.*",
10
+ "!src/**/*.spec.*",
11
+ "!src/**/vitest.config.*",
12
+ "!src/**/setupTest.*"
9
13
  ],
10
14
  "main": "src/index.ts",
11
15
  "types": "src/index.ts",
@@ -16,7 +20,7 @@
16
20
  }
17
21
  },
18
22
  "peerDependencies": {
19
- "@svadmin/core": "^0.23.3"
23
+ "@svadmin/core": "^0.25.3"
20
24
  },
21
25
  "license": "MIT",
22
26
  "publishConfig": {
package/src/index.test.ts DELETED
@@ -1,250 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
- import { createMCPServer } from './index';
3
- import type { MCPRequest } from './index';
4
-
5
- // ─── Mock DataProvider ───────────────────────────────────────
6
-
7
- interface MockFilter { field: string; operator: string; value: unknown }
8
- interface MockPagination { current?: number; pageSize?: number }
9
-
10
- const mockData: Record<string, Record<string, unknown>[]> = {
11
- posts: [
12
- { id: '1', title: 'Hello World', status: 'published' },
13
- { id: '2', title: 'Draft Post', status: 'draft' },
14
- { id: '3', title: 'Another Post', status: 'published' },
15
- ],
16
- users: [
17
- { id: '1', name: 'Alice', email: 'alice@test.com' },
18
- { id: '2', name: 'Bob', email: 'bob@test.com' },
19
- ],
20
- };
21
-
22
- const mockDataProvider = {
23
- getList: async (params: { resource: string; pagination?: MockPagination; filters?: MockFilter[] }) => {
24
- let data = [...(mockData[params.resource] ?? [])];
25
- if (params.filters && Array.isArray(params.filters)) {
26
- for (const f of params.filters) {
27
- data = data.filter(item => item[f.field] === f.value);
28
- }
29
- }
30
- const page = params.pagination?.current ?? 1;
31
- const size = params.pagination?.pageSize ?? 10;
32
- const start = (page - 1) * size;
33
- return { data: data.slice(start, start + size), total: data.length };
34
- },
35
- getOne: async (params: { resource: string; id: string | number }) => {
36
- const item = (mockData[params.resource] ?? []).find(r => r.id === params.id);
37
- if (!item) throw new Error(`Not found: ${params.resource}/${params.id}`);
38
- return { data: item };
39
- },
40
- getMany: async (params: { resource: string; ids: (string | number)[] }) => {
41
- const items = (mockData[params.resource] ?? []).filter(r => params.ids.includes(r.id as string));
42
- return { data: items };
43
- },
44
- create: async (params: { resource: string; variables: unknown }) => {
45
- const newItem = { id: String(Date.now()), ...(params.variables as Record<string, unknown>) };
46
- return { data: newItem };
47
- },
48
- update: async (params: { resource: string; id: string | number; variables: unknown }) => {
49
- const existing = (mockData[params.resource] ?? []).find(r => r.id === params.id);
50
- const updated = { ...existing, ...(params.variables as Record<string, unknown>) };
51
- return { data: updated };
52
- },
53
- deleteOne: async (params: { resource: string; id: string | number }) => {
54
- const item = (mockData[params.resource] ?? []).find(r => r.id === params.id);
55
- return { data: item ?? { id: params.id } };
56
- },
57
- getApiUrl: () => 'http://localhost:3000/api',
58
- };
59
-
60
- // ─── Helper ──────────────────────────────────────────────────
61
-
62
- function makeRequest(method: string, params?: Record<string, unknown>): MCPRequest {
63
- return { jsonrpc: '2.0', id: 1, method, params };
64
- }
65
-
66
- // ─── Tests ───────────────────────────────────────────────────
67
-
68
- describe('createMCPServer', () => {
69
- const server = createMCPServer({
70
- dataProvider: mockDataProvider as Parameters<typeof createMCPServer>[0]['dataProvider'],
71
- resources: ['posts', 'users'],
72
- });
73
-
74
- test('generates correct number of tools (read + write)', () => {
75
- const tools = server.getTools();
76
- // getList, getOne, getMany, create, update, delete = 6
77
- expect(tools).toHaveLength(6);
78
- expect(tools.map(t => t.name)).toEqual([
79
- 'svadmin_getList', 'svadmin_getOne', 'svadmin_getMany',
80
- 'svadmin_create', 'svadmin_update', 'svadmin_delete',
81
- ]);
82
- });
83
-
84
- test('readOnly mode excludes write tools', () => {
85
- const readOnlyServer = createMCPServer({
86
- dataProvider: mockDataProvider as Parameters<typeof createMCPServer>[0]['dataProvider'],
87
- resources: ['posts'],
88
- readOnly: true,
89
- });
90
- const tools = readOnlyServer.getTools();
91
- expect(tools).toHaveLength(3);
92
- expect(tools.map(t => t.name)).toEqual([
93
- 'svadmin_getList', 'svadmin_getOne', 'svadmin_getMany',
94
- ]);
95
- });
96
-
97
- test('tool resource enum matches configured resources', () => {
98
- const tools = server.getTools();
99
- const getListTool = tools.find(t => t.name === 'svadmin_getList')!;
100
- expect(getListTool.inputSchema.properties.resource.enum).toEqual(['posts', 'users']);
101
- });
102
-
103
- test('getServerInfo returns correct metadata', () => {
104
- const info = server.getServerInfo();
105
- expect(info.name).toBe('@svadmin/mcp');
106
- expect(info.version).toBe('0.1.0');
107
- expect(info.tools).toHaveLength(6);
108
- });
109
- });
110
-
111
- describe('MCP JSON-RPC handling', () => {
112
- const server = createMCPServer({
113
- dataProvider: mockDataProvider as Parameters<typeof createMCPServer>[0]['dataProvider'],
114
- resources: ['posts', 'users'],
115
- });
116
-
117
- test('initialize handshake', async () => {
118
- const res = await server.handleRequest(makeRequest('initialize'));
119
- expect(res.error).toBeUndefined();
120
- const result = res.result as Record<string, unknown>;
121
- expect(result.protocolVersion).toBe('2024-11-05');
122
- expect((result.serverInfo as Record<string, unknown>).name).toBe('@svadmin/mcp');
123
- });
124
-
125
- test('tools/list returns all tools', async () => {
126
- const res = await server.handleRequest(makeRequest('tools/list'));
127
- expect(res.error).toBeUndefined();
128
- const result = res.result as { tools: unknown[] };
129
- expect(result.tools).toHaveLength(6);
130
- });
131
-
132
- test('tools/call getList returns paginated data', async () => {
133
- const res = await server.handleRequest(makeRequest('tools/call', {
134
- name: 'svadmin_getList',
135
- arguments: { resource: 'posts', page: 1, pageSize: 2 },
136
- }));
137
-
138
- expect(res.error).toBeUndefined();
139
- const result = res.result as { content: { type: string; text: string }[] };
140
- const parsed = JSON.parse(result.content[0].text);
141
- expect(parsed.data).toHaveLength(2);
142
- expect(parsed.total).toBe(3);
143
- });
144
-
145
- test('tools/call getList with filters', async () => {
146
- const res = await server.handleRequest(makeRequest('tools/call', {
147
- name: 'svadmin_getList',
148
- arguments: {
149
- resource: 'posts',
150
- filters: JSON.stringify([{ field: 'status', operator: 'eq', value: 'draft' }]),
151
- },
152
- }));
153
-
154
- expect(res.error).toBeUndefined();
155
- const result = res.result as { content: { type: string; text: string }[] };
156
- const parsed = JSON.parse(result.content[0].text);
157
- expect(parsed.data).toHaveLength(1);
158
- expect(parsed.data[0].title).toBe('Draft Post');
159
- });
160
-
161
- test('tools/call getOne returns single record', async () => {
162
- const res = await server.handleRequest(makeRequest('tools/call', {
163
- name: 'svadmin_getOne',
164
- arguments: { resource: 'users', id: '1' },
165
- }));
166
-
167
- expect(res.error).toBeUndefined();
168
- const result = res.result as { content: { type: string; text: string }[] };
169
- const parsed = JSON.parse(result.content[0].text);
170
- expect(parsed.data.name).toBe('Alice');
171
- });
172
-
173
- test('tools/call getMany returns multiple records', async () => {
174
- const res = await server.handleRequest(makeRequest('tools/call', {
175
- name: 'svadmin_getMany',
176
- arguments: { resource: 'users', ids: '1,2' },
177
- }));
178
-
179
- expect(res.error).toBeUndefined();
180
- const result = res.result as { content: { type: string; text: string }[] };
181
- const parsed = JSON.parse(result.content[0].text);
182
- expect(parsed.data).toHaveLength(2);
183
- });
184
-
185
- test('tools/call create returns new record', async () => {
186
- const res = await server.handleRequest(makeRequest('tools/call', {
187
- name: 'svadmin_create',
188
- arguments: {
189
- resource: 'posts',
190
- data: JSON.stringify({ title: 'New Post', status: 'draft' }),
191
- },
192
- }));
193
-
194
- expect(res.error).toBeUndefined();
195
- const result = res.result as { content: { type: string; text: string }[] };
196
- const parsed = JSON.parse(result.content[0].text);
197
- expect(parsed.data.title).toBe('New Post');
198
- expect(parsed.data.id).toBeDefined();
199
- });
200
-
201
- test('tools/call update returns modified record', async () => {
202
- const res = await server.handleRequest(makeRequest('tools/call', {
203
- name: 'svadmin_update',
204
- arguments: {
205
- resource: 'posts',
206
- id: '1',
207
- data: JSON.stringify({ title: 'Updated Title' }),
208
- },
209
- }));
210
-
211
- expect(res.error).toBeUndefined();
212
- const result = res.result as { content: { type: string; text: string }[] };
213
- const parsed = JSON.parse(result.content[0].text);
214
- expect(parsed.data.title).toBe('Updated Title');
215
- });
216
-
217
- test('tools/call delete returns deleted record', async () => {
218
- const res = await server.handleRequest(makeRequest('tools/call', {
219
- name: 'svadmin_delete',
220
- arguments: { resource: 'posts', id: '2' },
221
- }));
222
-
223
- expect(res.error).toBeUndefined();
224
- const result = res.result as { content: { type: string; text: string }[] };
225
- const parsed = JSON.parse(result.content[0].text);
226
- expect(parsed.data.id).toBe('2');
227
- });
228
-
229
- test('unknown tool returns error', async () => {
230
- const res = await server.handleRequest(makeRequest('tools/call', {
231
- name: 'svadmin_nope',
232
- arguments: {},
233
- }));
234
-
235
- expect(res.error).toBeDefined();
236
- expect(res.error!.code).toBe(-32602);
237
- });
238
-
239
- test('unknown method returns error', async () => {
240
- const res = await server.handleRequest(makeRequest('whatever'));
241
- expect(res.error).toBeDefined();
242
- expect(res.error!.code).toBe(-32601);
243
- });
244
-
245
- test('missing tool name returns error', async () => {
246
- const res = await server.handleRequest(makeRequest('tools/call', {}));
247
- expect(res.error).toBeDefined();
248
- expect(res.error!.code).toBe(-32602);
249
- });
250
- });