@critical-path/mcp 0.7.0 → 0.8.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 +25 -0
- package/README.md +14 -0
- package/dist/bin/args.d.ts +16 -0
- package/dist/bin/args.d.ts.map +1 -0
- package/dist/bin/args.js +45 -0
- package/dist/bin/args.js.map +1 -0
- package/dist/bin/args.test.d.ts +2 -0
- package/dist/bin/args.test.d.ts.map +1 -0
- package/dist/bin/args.test.js +24 -0
- package/dist/bin/args.test.js.map +1 -0
- package/dist/bin/cli.js +13 -17
- package/dist/bin/cli.js.map +1 -1
- package/package.json +3 -3
- package/src/bin/args.test.ts +28 -0
- package/src/bin/args.ts +50 -0
- package/src/bin/cli.ts +13 -16
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @critical-path/mcp
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6dd5529: Add request context, authentication hooks and actor attribution.
|
|
8
|
+
|
|
9
|
+
**@critical-path/core**
|
|
10
|
+
- New `engine.withActor(actor)` returns a per-request view that attributes every mutation (activity log, comment authors, reactions, time entries, attachment uploaders, task reporters) to the actor and ignores identity fields supplied in payloads. New exported `Actor` type.
|
|
11
|
+
|
|
12
|
+
**@critical-path/server**
|
|
13
|
+
- New router options, accepted by all adapters as a second argument: `getContext` (resolve the caller per request), `requireAuth` (401 without a user), `basePath` (exact mount path), and `cors` (origin allow-list, credentials, or `false`).
|
|
14
|
+
- With a resolved user, identity fields in request bodies (`actorId`, `authorId`, `userId`, `uploaderId`) are ignored. Reaction routes no longer require `userId` in the body when a user is resolved.
|
|
15
|
+
- `createSvelteKitHandler` options take `getContext(event)` so `event.locals` is available; `configOrRouter` is now optional.
|
|
16
|
+
- New `createUniversalHandler(config, options)` for Workers, Deno, Bun and Hono.
|
|
17
|
+
- `handleRequest(request, { getContext })` lets custom adapters supply a per-request resolver.
|
|
18
|
+
|
|
19
|
+
**@critical-path/mcp**
|
|
20
|
+
- CLI `--api` mode reads `CRITICAL_PATH_API_TOKEN` (sent as a Bearer token) and accepts repeatable `--header "Name: value"`, warning when credentials would go over plain http to a remote host.
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [6dd5529]
|
|
25
|
+
- @critical-path/core@0.23.0
|
|
26
|
+
- @critical-path/client@0.14.8
|
|
27
|
+
|
|
3
28
|
## 0.7.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -43,6 +43,20 @@ npx @critical-path/mcp --api http://localhost:3000/api/critical-path
|
|
|
43
43
|
npx @critical-path/mcp
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
For APIs protected with `requireAuth`, set `CRITICAL_PATH_API_TOKEN` (sent as `Authorization: Bearer <token>`) or pass `--header "Name: value"` (repeatable). Prefer the environment variable for secrets, since command-line flags are visible in process lists. The CLI warns when credentials would be sent over plain `http` to a non-local host.
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"critical-path": {
|
|
52
|
+
"command": "npx",
|
|
53
|
+
"args": ["-y", "@critical-path/mcp", "--api", "https://app.example.com/api/critical-path"],
|
|
54
|
+
"env": { "CRITICAL_PATH_API_TOKEN": "<token>" }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
46
60
|
### Claude Desktop Configuration
|
|
47
61
|
|
|
48
62
|
Add the following to your `claude_desktop_config.json`:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface CliOptions {
|
|
2
|
+
db?: string;
|
|
3
|
+
api?: string;
|
|
4
|
+
headers: string[];
|
|
5
|
+
help?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseArgs(args: string[]): CliOptions;
|
|
8
|
+
/**
|
|
9
|
+
* Builds request headers for `--api` mode from repeated `--header "Name: value"` flags and the
|
|
10
|
+
* `CRITICAL_PATH_API_TOKEN` environment variable (sent as `Authorization: Bearer <token>`).
|
|
11
|
+
* Prefer the environment variable for secrets: command-line flags are visible in process lists.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildApiHeaders(headerFlags: string[], env: Record<string, string | undefined>): Record<string, string>;
|
|
14
|
+
/** True when credentials would be sent in cleartext to a host other than this machine. */
|
|
15
|
+
export declare function isInsecureRemote(apiUrl: string): boolean;
|
|
16
|
+
//# sourceMappingURL=args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/bin/args.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAepD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAatH;AAED,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAIxD"}
|
package/dist/bin/args.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function parseArgs(args) {
|
|
2
|
+
const options = { headers: [] };
|
|
3
|
+
for (let i = 0; i < args.length; i++) {
|
|
4
|
+
const arg = args[i];
|
|
5
|
+
if (arg === '--help' || arg === '-h') {
|
|
6
|
+
options.help = true;
|
|
7
|
+
}
|
|
8
|
+
else if (arg === '--db' && args[i + 1]) {
|
|
9
|
+
options.db = args[++i];
|
|
10
|
+
}
|
|
11
|
+
else if (arg === '--api' && args[i + 1]) {
|
|
12
|
+
options.api = args[++i];
|
|
13
|
+
}
|
|
14
|
+
else if (arg === '--header' && args[i + 1]) {
|
|
15
|
+
options.headers.push(args[++i]);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return options;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Builds request headers for `--api` mode from repeated `--header "Name: value"` flags and the
|
|
22
|
+
* `CRITICAL_PATH_API_TOKEN` environment variable (sent as `Authorization: Bearer <token>`).
|
|
23
|
+
* Prefer the environment variable for secrets: command-line flags are visible in process lists.
|
|
24
|
+
*/
|
|
25
|
+
export function buildApiHeaders(headerFlags, env) {
|
|
26
|
+
const headers = {};
|
|
27
|
+
if (env.CRITICAL_PATH_API_TOKEN) {
|
|
28
|
+
headers['Authorization'] = `Bearer ${env.CRITICAL_PATH_API_TOKEN}`;
|
|
29
|
+
}
|
|
30
|
+
for (const flag of headerFlags) {
|
|
31
|
+
const separator = flag.indexOf(':');
|
|
32
|
+
if (separator <= 0) {
|
|
33
|
+
throw new Error(`Invalid --header "${flag}". Expected "Name: value".`);
|
|
34
|
+
}
|
|
35
|
+
headers[flag.slice(0, separator).trim()] = flag.slice(separator + 1).trim();
|
|
36
|
+
}
|
|
37
|
+
return headers;
|
|
38
|
+
}
|
|
39
|
+
/** True when credentials would be sent in cleartext to a host other than this machine. */
|
|
40
|
+
export function isInsecureRemote(apiUrl) {
|
|
41
|
+
const url = new URL(apiUrl);
|
|
42
|
+
const localHosts = ['localhost', '127.0.0.1', '[::1]'];
|
|
43
|
+
return url.protocol === 'http:' && !localHosts.includes(url.hostname) && !url.hostname.endsWith('.localhost');
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/bin/args.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,MAAM,OAAO,GAAe,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACrC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;QACtB,CAAC;aAAM,IAAI,GAAG,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,GAAG,KAAK,OAAO,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1B,CAAC;aAAM,IAAI,GAAG,KAAK,UAAU,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC7C,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,WAAqB,EAAE,GAAuC;IAC5F,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,GAAG,CAAC,uBAAuB,EAAE,CAAC;QAChC,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,GAAG,CAAC,uBAAuB,EAAE,CAAC;IACrE,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,4BAA4B,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9E,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,UAAU,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IACvD,OAAO,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AAChH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.test.d.ts","sourceRoot":"","sources":["../../src/bin/args.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseArgs, buildApiHeaders, isInsecureRemote } from './args.js';
|
|
3
|
+
describe('MCP CLI arguments', () => {
|
|
4
|
+
it('collects repeated --header flags', () => {
|
|
5
|
+
const options = parseArgs(['--api', 'https://x.test/api', '--header', 'X-Tenant: acme', '--header', 'X-Trace: 1']);
|
|
6
|
+
expect(options.api).toBe('https://x.test/api');
|
|
7
|
+
expect(options.headers).toEqual(['X-Tenant: acme', 'X-Trace: 1']);
|
|
8
|
+
});
|
|
9
|
+
it('builds headers from the token env var and flags, with flags taking precedence', () => {
|
|
10
|
+
expect(buildApiHeaders(['X-Tenant: acme'], { CRITICAL_PATH_API_TOKEN: 'secret' })).toEqual({
|
|
11
|
+
Authorization: 'Bearer secret',
|
|
12
|
+
'X-Tenant': 'acme'
|
|
13
|
+
});
|
|
14
|
+
expect(buildApiHeaders(['Authorization: Basic abc'], { CRITICAL_PATH_API_TOKEN: 'secret' }).Authorization).toBe('Basic abc');
|
|
15
|
+
expect(() => buildApiHeaders(['no-colon'], {})).toThrow(/Expected "Name: value"/);
|
|
16
|
+
});
|
|
17
|
+
it('flags plain http to remote hosts only', () => {
|
|
18
|
+
expect(isInsecureRemote('http://api.example.com')).toBe(true);
|
|
19
|
+
expect(isInsecureRemote('https://api.example.com')).toBe(false);
|
|
20
|
+
expect(isInsecureRemote('http://localhost:3000/api')).toBe(false);
|
|
21
|
+
expect(isInsecureRemote('http://127.0.0.1:3000')).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=args.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.test.js","sourceRoot":"","sources":["../../src/bin/args.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAEzE,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;QACnH,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC/C,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+EAA+E,EAAE,GAAG,EAAE;QACvF,MAAM,CAAC,eAAe,CAAC,CAAC,gBAAgB,CAAC,EAAE,EAAE,uBAAuB,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YACzF,aAAa,EAAE,eAAe;YAC9B,UAAU,EAAE,MAAM;SACnB,CAAC,CAAC;QACH,MAAM,CAAC,eAAe,CAAC,CAAC,0BAA0B,CAAC,EAAE,EAAE,uBAAuB,EAAE,QAAQ,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,CAC7G,WAAW,CACZ,CAAC;QACF,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,gBAAgB,CAAC,wBAAwB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChE,MAAM,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClE,MAAM,CAAC,gBAAgB,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/bin/cli.js
CHANGED
|
@@ -2,22 +2,7 @@
|
|
|
2
2
|
import { CriticalPathEngine, SQLiteStore, InMemoryStore } from '@critical-path/core';
|
|
3
3
|
import { CriticalPathClient } from '@critical-path/client';
|
|
4
4
|
import { createCriticalPathMcpServer, startStdioServer } from '../server/index.js';
|
|
5
|
-
|
|
6
|
-
const options = {};
|
|
7
|
-
for (let i = 0; i < args.length; i++) {
|
|
8
|
-
const arg = args[i];
|
|
9
|
-
if (arg === '--help' || arg === '-h') {
|
|
10
|
-
options.help = true;
|
|
11
|
-
}
|
|
12
|
-
else if (arg === '--db' && args[i + 1]) {
|
|
13
|
-
options.db = args[++i];
|
|
14
|
-
}
|
|
15
|
-
else if (arg === '--api' && args[i + 1]) {
|
|
16
|
-
options.api = args[++i];
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return options;
|
|
20
|
-
}
|
|
5
|
+
import { parseArgs, buildApiHeaders, isInsecureRemote } from './args.js';
|
|
21
6
|
function showHelp() {
|
|
22
7
|
console.log(`
|
|
23
8
|
Critical Path MCP Server (Stdio)
|
|
@@ -25,9 +10,13 @@ Critical Path MCP Server (Stdio)
|
|
|
25
10
|
Usage:
|
|
26
11
|
npx @critical-path/mcp [options]
|
|
27
12
|
|
|
13
|
+
Environment:
|
|
14
|
+
CRITICAL_PATH_API_TOKEN Sent as "Authorization: Bearer <token>" in --api mode
|
|
15
|
+
|
|
28
16
|
Options:
|
|
29
17
|
--db <path> Path to SQLite database file (uses SQLiteStore)
|
|
30
18
|
--api <url> Base URL of remote Critical Path server (uses CriticalPathClient)
|
|
19
|
+
--header <h> Extra request header for --api, as "Name: value" (repeatable)
|
|
31
20
|
--help, -h Display this help message
|
|
32
21
|
|
|
33
22
|
Examples:
|
|
@@ -37,6 +26,9 @@ Examples:
|
|
|
37
26
|
# Run against remote Next.js or SvelteKit route handler
|
|
38
27
|
npx @critical-path/mcp --api http://localhost:3000/api/critical-path
|
|
39
28
|
|
|
29
|
+
# Authenticate against a protected API (token read from the environment)
|
|
30
|
+
CRITICAL_PATH_API_TOKEN=secret npx @critical-path/mcp --api https://app.example.com/api/critical-path
|
|
31
|
+
|
|
40
32
|
# Run with in-memory store (for testing)
|
|
41
33
|
npx @critical-path/mcp
|
|
42
34
|
`);
|
|
@@ -50,7 +42,11 @@ async function main() {
|
|
|
50
42
|
}
|
|
51
43
|
let server;
|
|
52
44
|
if (options.api) {
|
|
53
|
-
const
|
|
45
|
+
const headers = buildApiHeaders(options.headers, process.env);
|
|
46
|
+
if (Object.keys(headers).length > 0 && isInsecureRemote(options.api)) {
|
|
47
|
+
console.error('[Critical Path MCP] Warning: sending credentials over plain http to a remote host.');
|
|
48
|
+
}
|
|
49
|
+
const client = new CriticalPathClient({ baseUrl: options.api, headers });
|
|
54
50
|
server = createCriticalPathMcpServer({ client, name: 'critical-path-api-mcp' });
|
|
55
51
|
console.error(`[Critical Path MCP] Connecting to remote API at ${options.api} over stdio...`);
|
|
56
52
|
}
|
package/dist/bin/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/bin/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/bin/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAEzE,SAAS,QAAQ;IACf,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2Bb,CAAC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAEhC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,QAAQ,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,MAAM,CAAC;IAEX,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9D,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACrE,OAAO,CAAC,KAAK,CAAC,oFAAoF,CAAC,CAAC;QACtG,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,kBAAkB,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QACzE,MAAM,GAAG,2BAA2B,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC,CAAC;QAChF,OAAO,CAAC,KAAK,CAAC,mDAAmD,OAAO,CAAC,GAAG,gBAAgB,CAAC,CAAC;IAChG,CAAC;SAAM,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACjD,MAAM,GAAG,2BAA2B,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,EAAE,CAAC,CAAC;QACnF,OAAO,CAAC,KAAK,CAAC,oDAAoD,OAAO,CAAC,EAAE,iBAAiB,CAAC,CAAC;IACjG,CAAC;SAAM,CAAC;QACN,MAAM,KAAK,GAAG,IAAI,aAAa,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACjD,MAAM,GAAG,2BAA2B,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,4BAA4B,EAAE,CAAC,CAAC;QACrF,OAAO,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;IACjF,CAAC;IAED,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,GAAG,CAAC,CAAC;IACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@critical-path/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Standard Model Context Protocol (MCP) server and client-side WebMCP support for Critical Path",
|
|
5
5
|
"author": "Jack James",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.6.0",
|
|
36
36
|
"zod": "^3.24.0",
|
|
37
|
-
"@critical-path/client": "0.14.
|
|
38
|
-
"@critical-path/core": "0.
|
|
37
|
+
"@critical-path/client": "0.14.8",
|
|
38
|
+
"@critical-path/core": "0.23.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^24.0.0",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseArgs, buildApiHeaders, isInsecureRemote } from './args.js';
|
|
3
|
+
|
|
4
|
+
describe('MCP CLI arguments', () => {
|
|
5
|
+
it('collects repeated --header flags', () => {
|
|
6
|
+
const options = parseArgs(['--api', 'https://x.test/api', '--header', 'X-Tenant: acme', '--header', 'X-Trace: 1']);
|
|
7
|
+
expect(options.api).toBe('https://x.test/api');
|
|
8
|
+
expect(options.headers).toEqual(['X-Tenant: acme', 'X-Trace: 1']);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('builds headers from the token env var and flags, with flags taking precedence', () => {
|
|
12
|
+
expect(buildApiHeaders(['X-Tenant: acme'], { CRITICAL_PATH_API_TOKEN: 'secret' })).toEqual({
|
|
13
|
+
Authorization: 'Bearer secret',
|
|
14
|
+
'X-Tenant': 'acme'
|
|
15
|
+
});
|
|
16
|
+
expect(buildApiHeaders(['Authorization: Basic abc'], { CRITICAL_PATH_API_TOKEN: 'secret' }).Authorization).toBe(
|
|
17
|
+
'Basic abc'
|
|
18
|
+
);
|
|
19
|
+
expect(() => buildApiHeaders(['no-colon'], {})).toThrow(/Expected "Name: value"/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('flags plain http to remote hosts only', () => {
|
|
23
|
+
expect(isInsecureRemote('http://api.example.com')).toBe(true);
|
|
24
|
+
expect(isInsecureRemote('https://api.example.com')).toBe(false);
|
|
25
|
+
expect(isInsecureRemote('http://localhost:3000/api')).toBe(false);
|
|
26
|
+
expect(isInsecureRemote('http://127.0.0.1:3000')).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
});
|
package/src/bin/args.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface CliOptions {
|
|
2
|
+
db?: string;
|
|
3
|
+
api?: string;
|
|
4
|
+
headers: string[];
|
|
5
|
+
help?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function parseArgs(args: string[]): CliOptions {
|
|
9
|
+
const options: CliOptions = { headers: [] };
|
|
10
|
+
for (let i = 0; i < args.length; i++) {
|
|
11
|
+
const arg = args[i];
|
|
12
|
+
if (arg === '--help' || arg === '-h') {
|
|
13
|
+
options.help = true;
|
|
14
|
+
} else if (arg === '--db' && args[i + 1]) {
|
|
15
|
+
options.db = args[++i];
|
|
16
|
+
} else if (arg === '--api' && args[i + 1]) {
|
|
17
|
+
options.api = args[++i];
|
|
18
|
+
} else if (arg === '--header' && args[i + 1]) {
|
|
19
|
+
options.headers.push(args[++i]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return options;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds request headers for `--api` mode from repeated `--header "Name: value"` flags and the
|
|
27
|
+
* `CRITICAL_PATH_API_TOKEN` environment variable (sent as `Authorization: Bearer <token>`).
|
|
28
|
+
* Prefer the environment variable for secrets: command-line flags are visible in process lists.
|
|
29
|
+
*/
|
|
30
|
+
export function buildApiHeaders(headerFlags: string[], env: Record<string, string | undefined>): Record<string, string> {
|
|
31
|
+
const headers: Record<string, string> = {};
|
|
32
|
+
if (env.CRITICAL_PATH_API_TOKEN) {
|
|
33
|
+
headers['Authorization'] = `Bearer ${env.CRITICAL_PATH_API_TOKEN}`;
|
|
34
|
+
}
|
|
35
|
+
for (const flag of headerFlags) {
|
|
36
|
+
const separator = flag.indexOf(':');
|
|
37
|
+
if (separator <= 0) {
|
|
38
|
+
throw new Error(`Invalid --header "${flag}". Expected "Name: value".`);
|
|
39
|
+
}
|
|
40
|
+
headers[flag.slice(0, separator).trim()] = flag.slice(separator + 1).trim();
|
|
41
|
+
}
|
|
42
|
+
return headers;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** True when credentials would be sent in cleartext to a host other than this machine. */
|
|
46
|
+
export function isInsecureRemote(apiUrl: string): boolean {
|
|
47
|
+
const url = new URL(apiUrl);
|
|
48
|
+
const localHosts = ['localhost', '127.0.0.1', '[::1]'];
|
|
49
|
+
return url.protocol === 'http:' && !localHosts.includes(url.hostname) && !url.hostname.endsWith('.localhost');
|
|
50
|
+
}
|
package/src/bin/cli.ts
CHANGED
|
@@ -2,21 +2,7 @@
|
|
|
2
2
|
import { CriticalPathEngine, SQLiteStore, InMemoryStore } from '@critical-path/core';
|
|
3
3
|
import { CriticalPathClient } from '@critical-path/client';
|
|
4
4
|
import { createCriticalPathMcpServer, startStdioServer } from '../server/index.js';
|
|
5
|
-
|
|
6
|
-
function parseArgs(args: string[]) {
|
|
7
|
-
const options: { db?: string; api?: string; help?: boolean } = {};
|
|
8
|
-
for (let i = 0; i < args.length; i++) {
|
|
9
|
-
const arg = args[i];
|
|
10
|
-
if (arg === '--help' || arg === '-h') {
|
|
11
|
-
options.help = true;
|
|
12
|
-
} else if (arg === '--db' && args[i + 1]) {
|
|
13
|
-
options.db = args[++i];
|
|
14
|
-
} else if (arg === '--api' && args[i + 1]) {
|
|
15
|
-
options.api = args[++i];
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return options;
|
|
19
|
-
}
|
|
5
|
+
import { parseArgs, buildApiHeaders, isInsecureRemote } from './args.js';
|
|
20
6
|
|
|
21
7
|
function showHelp() {
|
|
22
8
|
console.log(`
|
|
@@ -25,9 +11,13 @@ Critical Path MCP Server (Stdio)
|
|
|
25
11
|
Usage:
|
|
26
12
|
npx @critical-path/mcp [options]
|
|
27
13
|
|
|
14
|
+
Environment:
|
|
15
|
+
CRITICAL_PATH_API_TOKEN Sent as "Authorization: Bearer <token>" in --api mode
|
|
16
|
+
|
|
28
17
|
Options:
|
|
29
18
|
--db <path> Path to SQLite database file (uses SQLiteStore)
|
|
30
19
|
--api <url> Base URL of remote Critical Path server (uses CriticalPathClient)
|
|
20
|
+
--header <h> Extra request header for --api, as "Name: value" (repeatable)
|
|
31
21
|
--help, -h Display this help message
|
|
32
22
|
|
|
33
23
|
Examples:
|
|
@@ -37,6 +27,9 @@ Examples:
|
|
|
37
27
|
# Run against remote Next.js or SvelteKit route handler
|
|
38
28
|
npx @critical-path/mcp --api http://localhost:3000/api/critical-path
|
|
39
29
|
|
|
30
|
+
# Authenticate against a protected API (token read from the environment)
|
|
31
|
+
CRITICAL_PATH_API_TOKEN=secret npx @critical-path/mcp --api https://app.example.com/api/critical-path
|
|
32
|
+
|
|
40
33
|
# Run with in-memory store (for testing)
|
|
41
34
|
npx @critical-path/mcp
|
|
42
35
|
`);
|
|
@@ -54,7 +47,11 @@ async function main() {
|
|
|
54
47
|
let server;
|
|
55
48
|
|
|
56
49
|
if (options.api) {
|
|
57
|
-
const
|
|
50
|
+
const headers = buildApiHeaders(options.headers, process.env);
|
|
51
|
+
if (Object.keys(headers).length > 0 && isInsecureRemote(options.api)) {
|
|
52
|
+
console.error('[Critical Path MCP] Warning: sending credentials over plain http to a remote host.');
|
|
53
|
+
}
|
|
54
|
+
const client = new CriticalPathClient({ baseUrl: options.api, headers });
|
|
58
55
|
server = createCriticalPathMcpServer({ client, name: 'critical-path-api-mcp' });
|
|
59
56
|
console.error(`[Critical Path MCP] Connecting to remote API at ${options.api} over stdio...`);
|
|
60
57
|
} else if (options.db) {
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/index.test.ts","./src/index.ts","./src/bin/cli.ts","./src/server/index.ts","./src/tools/definitions.test.ts","./src/tools/definitions.ts","./src/web/index.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/index.test.ts","./src/index.ts","./src/bin/args.test.ts","./src/bin/args.ts","./src/bin/cli.ts","./src/server/index.ts","./src/tools/definitions.test.ts","./src/tools/definitions.ts","./src/web/index.ts"],"version":"5.9.3"}
|