@critical-path/mcp 0.8.0 → 0.10.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/CHANGELOG.md +62 -0
- package/README.md +4 -2
- package/dist/index.test.js +17 -5
- package/dist/index.test.js.map +1 -1
- package/dist/server/index.d.ts +8 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -1
- package/dist/server/index.js.map +1 -1
- package/dist/tools/definitions.d.ts +31 -26
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +53 -229
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/definitions.test.js +10 -2
- package/dist/tools/definitions.test.js.map +1 -1
- package/package.json +4 -3
- package/src/index.test.ts +20 -5
- package/src/server/index.ts +12 -2
- package/src/tools/definitions.test.ts +12 -2
- package/src/tools/definitions.ts +63 -235
package/src/index.test.ts
CHANGED
|
@@ -216,7 +216,7 @@ describe('@critical-path/mcp', () => {
|
|
|
216
216
|
expect(await engine.getTask(task.id)).not.toBeNull();
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
-
it('validates tool arguments and
|
|
219
|
+
it('validates tool arguments and rejects undeclared fields', async () => {
|
|
220
220
|
const source = await engine.createProject({ name: 'Source', key: 'SRC' });
|
|
221
221
|
const other = await engine.createProject({ name: 'Other', key: 'OTH' });
|
|
222
222
|
const task = await engine.createTask({ projectId: source.id, title: 'Stay put' });
|
|
@@ -226,13 +226,28 @@ describe('@critical-path/mcp', () => {
|
|
|
226
226
|
expect(invalid.isError).toBe(true);
|
|
227
227
|
expect((invalid.content as Array<{ text: string }>)[0].text).toContain('Invalid arguments');
|
|
228
228
|
|
|
229
|
-
await client.callTool({
|
|
229
|
+
const undeclared = await client.callTool({
|
|
230
230
|
name: 'update_task',
|
|
231
231
|
arguments: { id: task.id, title: 'Renamed', projectId: other.id }
|
|
232
232
|
});
|
|
233
|
-
|
|
234
|
-
expect(
|
|
235
|
-
|
|
233
|
+
expect(undeclared.isError).toBe(true);
|
|
234
|
+
expect((undeclared.content as Array<{ text: string }>)[0].text).toContain('projectId');
|
|
235
|
+
const unchanged = await engine.getTask(task.id);
|
|
236
|
+
expect(unchanged?.title).toBe('Stay put');
|
|
237
|
+
expect(unchanged?.projectId).toBe(source.id);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('attributes engine writes to the configured actor', async () => {
|
|
241
|
+
const project = await engine.createProject({ name: 'Actors', key: 'ACT' });
|
|
242
|
+
const task = await engine.createTask({ projectId: project.id, title: 'T' });
|
|
243
|
+
|
|
244
|
+
const defaultClient = await connect({ engine });
|
|
245
|
+
const first = await defaultClient.callTool({ name: 'add_comment', arguments: { taskId: task.id, content: 'hi' } });
|
|
246
|
+
expect(JSON.parse((first.content as Array<{ text: string }>)[0].text).authorId).toBe('mcp-agent');
|
|
247
|
+
|
|
248
|
+
const namedClient = await connect({ engine, actor: { userId: 'claude', actorType: 'agent' } });
|
|
249
|
+
const second = await namedClient.callTool({ name: 'add_comment', arguments: { taskId: task.id, content: 'hi' } });
|
|
250
|
+
expect(JSON.parse((second.content as Array<{ text: string }>)[0].text).authorId).toBe('claude');
|
|
236
251
|
});
|
|
237
252
|
|
|
238
253
|
it('marks destructive tools with MCP annotations', async () => {
|
package/src/server/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
ErrorCode,
|
|
11
11
|
McpError
|
|
12
12
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
-
import type { CriticalPathEngine } from '@critical-path/core';
|
|
13
|
+
import type { Actor, CriticalPathEngine } from '@critical-path/core';
|
|
14
14
|
import type { CriticalPathClient } from '@critical-path/client';
|
|
15
15
|
import { ALL_TOOLS, parseToolArgs, type BackendContext } from '../tools/definitions.js';
|
|
16
16
|
|
|
@@ -20,10 +20,20 @@ export interface CriticalPathMcpServerOptions {
|
|
|
20
20
|
name?: string;
|
|
21
21
|
version?: string;
|
|
22
22
|
tools?: string[];
|
|
23
|
+
/**
|
|
24
|
+
* Identity that changes made through an `engine` are attributed to (activity log, comment
|
|
25
|
+
* authors, ...). Defaults to `DEFAULT_MCP_ACTOR`. With a `client`, identity comes from the API's
|
|
26
|
+
* authentication (e.g. the `Authorization` header passed to the client) instead.
|
|
27
|
+
*/
|
|
28
|
+
actor?: Actor;
|
|
23
29
|
}
|
|
24
30
|
|
|
31
|
+
export const DEFAULT_MCP_ACTOR: Actor = Object.freeze({ userId: 'mcp-agent', username: 'MCP Agent', actorType: 'agent' });
|
|
32
|
+
|
|
25
33
|
export function createCriticalPathMcpServer(options: CriticalPathMcpServerOptions): Server {
|
|
26
|
-
const target: BackendContext | undefined = options.engine
|
|
34
|
+
const target: BackendContext | undefined = options.engine
|
|
35
|
+
? options.engine.withActor(options.actor ?? DEFAULT_MCP_ACTOR)
|
|
36
|
+
: options.client;
|
|
27
37
|
if (!target) {
|
|
28
38
|
throw new Error('Either engine or client must be provided to createCriticalPathMcpServer.');
|
|
29
39
|
}
|
|
@@ -2,8 +2,8 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import { ALL_TOOLS } from './definitions.js';
|
|
3
3
|
|
|
4
4
|
describe('MCP tool definitions', () => {
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// inputSchema is generated from zodSchema by defineTool; this guards the generation itself
|
|
6
|
+
// (e.g. a zod feature that zod-to-json-schema cannot express would drop fields here).
|
|
7
7
|
it.each(ALL_TOOLS.map((t) => [t.name, t] as const))('%s: zodSchema and inputSchema agree', (_name, tool) => {
|
|
8
8
|
const json = tool.inputSchema as { properties?: Record<string, unknown>; required?: string[] };
|
|
9
9
|
const shape = tool.zodSchema.shape as Record<string, { isOptional(): boolean }>;
|
|
@@ -16,4 +16,14 @@ describe('MCP tool definitions', () => {
|
|
|
16
16
|
.sort()
|
|
17
17
|
).toEqual([...(json.required ?? [])].sort());
|
|
18
18
|
});
|
|
19
|
+
|
|
20
|
+
it('generates closed object schemas with descriptions for every tool', () => {
|
|
21
|
+
for (const tool of ALL_TOOLS) {
|
|
22
|
+
expect(tool.inputSchema).toMatchObject({ type: 'object', additionalProperties: false });
|
|
23
|
+
expect(tool.inputSchema).not.toHaveProperty('$schema');
|
|
24
|
+
}
|
|
25
|
+
const updateTask = ALL_TOOLS.find((t) => t.name === 'update_task')!;
|
|
26
|
+
expect((updateTask.inputSchema as any).properties.isBlocked.description).toBe('Whether the task is blocked');
|
|
27
|
+
});
|
|
19
28
|
});
|
|
29
|
+
|