@process-cart/mcp 0.1.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/LICENSE +21 -0
- package/README.md +160 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +21 -0
- package/dist/cli.d.ts +45 -0
- package/dist/cli.js +196 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/render.d.ts +139 -0
- package/dist/render.js +582 -0
- package/dist/server.d.ts +57 -0
- package/dist/server.js +261 -0
- package/dist/tools/add-comment.d.ts +6 -0
- package/dist/tools/add-comment.js +27 -0
- package/dist/tools/complete-task.d.ts +5 -0
- package/dist/tools/complete-task.js +21 -0
- package/dist/tools/decide-approval.d.ts +8 -0
- package/dist/tools/decide-approval.js +37 -0
- package/dist/tools/find-runs.d.ts +18 -0
- package/dist/tools/find-runs.js +74 -0
- package/dist/tools/get-activity.d.ts +10 -0
- package/dist/tools/get-activity.js +61 -0
- package/dist/tools/get-run.d.ts +3 -0
- package/dist/tools/get-run.js +16 -0
- package/dist/tools/get-workflow-schema.d.ts +3 -0
- package/dist/tools/get-workflow-schema.js +16 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +33 -0
- package/dist/tools/list-people.d.ts +7 -0
- package/dist/tools/list-people.js +55 -0
- package/dist/tools/list-workflows.d.ts +9 -0
- package/dist/tools/list-workflows.js +70 -0
- package/dist/tools/reopen-task.d.ts +5 -0
- package/dist/tools/reopen-task.js +21 -0
- package/dist/tools/run-output.d.ts +6 -0
- package/dist/tools/run-output.js +13 -0
- package/dist/tools/set-fields.d.ts +5 -0
- package/dist/tools/set-fields.js +33 -0
- package/dist/tools/shared.d.ts +94 -0
- package/dist/tools/shared.js +97 -0
- package/dist/tools/start-run.d.ts +9 -0
- package/dist/tools/start-run.js +29 -0
- package/dist/tools/update-run.d.ts +10 -0
- package/dist/tools/update-run.js +31 -0
- package/dist/tools/whoami.d.ts +1 -0
- package/dist/tools/whoami.js +34 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +8 -0
- package/package.json +58 -0
- package/server.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Process Cart
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# @process-cart/mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server for
|
|
4
|
+
[Process Cart](https://developers.processcart.com). Add it to Claude Desktop, Claude Code, Cursor
|
|
5
|
+
or any other MCP client, give it an API key, and the assistant can find a workflow, start a run,
|
|
6
|
+
fill in its fields, complete its tasks, decide approvals, comment and read what happened.
|
|
7
|
+
|
|
8
|
+
It runs locally over stdio, calls the Process Cart API (`/api/v1`) with your key, and is built on
|
|
9
|
+
[`@process-cart/sdk`](https://www.npmjs.com/package/@process-cart/sdk). Full guide:
|
|
10
|
+
<https://developers.processcart.com/guides/mcp>.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npx -y @process-cart/mcp
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Your MCP client runs that command for you (see below). It needs Node.js 22.12 or later and a
|
|
19
|
+
Process Cart API key: an Admin creates one under **Settings → API**. A key acts on its whole
|
|
20
|
+
workspace, so give the assistant a key of its own with only the scopes it needs, and try it with
|
|
21
|
+
a sandbox (`pc_test_…`) key first.
|
|
22
|
+
|
|
23
|
+
## Configure
|
|
24
|
+
|
|
25
|
+
### Claude Desktop
|
|
26
|
+
|
|
27
|
+
**Settings → Developer → Edit Config**, then add to `claude_desktop_config.json` and restart:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"processcart": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["-y", "@process-cart/mcp"],
|
|
35
|
+
"env": { "PROCESSCART_API_KEY": "pc_live_…" }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
On Windows, if `npx` does not start, use `"command": "cmd"` with
|
|
42
|
+
`"args": ["/c", "npx", "-y", "@process-cart/mcp"]`.
|
|
43
|
+
|
|
44
|
+
### Claude Code
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
claude mcp add processcart -e PROCESSCART_API_KEY=pc_live_… -- npx -y @process-cart/mcp
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
To share it through a project's checked-in `.mcp.json`, keep the key out of the file: Claude Code
|
|
51
|
+
expands `${VAR}` there, so register the variable (single quotes stop your shell expanding it) and
|
|
52
|
+
let each teammate export their own key:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
claude mcp add processcart --scope project -e 'PROCESSCART_API_KEY=${PROCESSCART_API_KEY}' -- npx -y @process-cart/mcp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Cursor
|
|
59
|
+
|
|
60
|
+
The same `mcpServers` block as Claude Desktop, in `~/.cursor/mcp.json` (every project) or
|
|
61
|
+
`.cursor/mcp.json` (one project).
|
|
62
|
+
|
|
63
|
+
## Environment and flags
|
|
64
|
+
|
|
65
|
+
| Setting | What it does |
|
|
66
|
+
| ----------------------- | -------------------------------------------------------------------------------------- |
|
|
67
|
+
| `PROCESSCART_API_KEY` | Your API key (`pc_live_…` or `pc_test_…`). Required. |
|
|
68
|
+
| `PROCESSCART_BASE_URL` | The API base URL. Default `https://app.processcart.com/api`. |
|
|
69
|
+
| `PROCESSCART_MCP_DEBUG` | `1` logs each tool call (name, outcome, duration) to stderr — never the key or values. |
|
|
70
|
+
| `--api-key <key>` | The key as a flag (prefer the environment: a command line is visible to others). |
|
|
71
|
+
| `--base-url <url>` | The base URL as a flag. |
|
|
72
|
+
| `--read-only` | Offer the read tools only, whatever the key allows. |
|
|
73
|
+
| `--help`, `--version` | Print the usage or the version and exit. |
|
|
74
|
+
|
|
75
|
+
The base URL must use `https`; plain `http` is accepted only for a server on this machine
|
|
76
|
+
(`localhost`, `127.x.x.x`, `::1`). Nothing but the MCP protocol is written to stdout; diagnostics go
|
|
77
|
+
to stderr, with anything shaped like a key cut to its prefix (`pc_live_…`). A missing or malformed
|
|
78
|
+
key, or an `http` URL to another host, exits with status 2 and a message on stderr.
|
|
79
|
+
|
|
80
|
+
## Tools
|
|
81
|
+
|
|
82
|
+
| Tool | What it does | Scope |
|
|
83
|
+
| --------------------- | -------------------------------------------------------------------------------- | ------------------ |
|
|
84
|
+
| `whoami` | The workspace, the key, its scopes, the rate limit, the status labels, the tools | any key |
|
|
85
|
+
| `list_workflows` | Workflows the key can see; `q` searches names and descriptions | `workflows:read` |
|
|
86
|
+
| `get_workflow_schema` | A workflow's fields by key, with types, allowed values and the start form | `workflows:read` |
|
|
87
|
+
| `start_run` | Start a run with field values, priority, labels and a due date | `runs:write` |
|
|
88
|
+
| `find_runs` | Search runs by name, workflow, status, priority, labels, assignee, due window | `runs:read` |
|
|
89
|
+
| `get_run` | One run in full: tasks, fields, revision, app link | `runs:read` |
|
|
90
|
+
| `set_fields` | Set field values by key, all or nothing | `runs:write` |
|
|
91
|
+
| `complete_task` | Complete a task; a refusal names the required fields still blank | `runs:write` |
|
|
92
|
+
| `reopen_task` | Put a completed task back to to-do | `runs:write` |
|
|
93
|
+
| `decide_approval` | Approve or reject an approval task (a rejection needs a comment) | `approvals:decide` |
|
|
94
|
+
| `add_comment` | Comment on a task | `runs:write` |
|
|
95
|
+
| `get_activity` | A run's trail, newest first, with its latest comments | `runs:read` |
|
|
96
|
+
| `update_run` | Change a run's name, status, priority, labels or due date | `runs:write` |
|
|
97
|
+
| `list_people` | Members and groups, with the ids `find_runs` filters by | `users:read` |
|
|
98
|
+
|
|
99
|
+
Each tool answers Markdown for the model plus JSON as `structuredContent`. A run answer is the
|
|
100
|
+
whole run — tasks grouped by section with their keys, fields as "Label (`key`): value", and the
|
|
101
|
+
current `revision` — and its JSON is the API's Run resource, so no second call is needed to see
|
|
102
|
+
the result of a write. `find_runs` carries one summary per run (id, name, status, priority,
|
|
103
|
+
labels, dates, revision, workflow, link, task progress and the open tasks — no tasks or fields),
|
|
104
|
+
`list_people` carries `{ users, groups, complete }`, and `whoami` the API's `/v1/me` answer plus a
|
|
105
|
+
`server` member. `get_run` flags required fields that are still blank only when the key also has
|
|
106
|
+
`workflows:read` and the run is on the published version; otherwise `complete_task` names them.
|
|
107
|
+
|
|
108
|
+
At startup the server calls `GET /v1/me` (one attempt, at most 10 seconds) and registers only the
|
|
109
|
+
tools the key can use. A read-only key, or `--read-only`, gets the read tools only, and the
|
|
110
|
+
server's instructions then say it is read-only. If the key cannot be read at startup, the server
|
|
111
|
+
starts with the read tools only and `whoami` reports the error.
|
|
112
|
+
|
|
113
|
+
## Safety
|
|
114
|
+
|
|
115
|
+
- No admin tools, and nothing deletes: no tool deletes a run, workflow or comment; `set_fields`
|
|
116
|
+
and `update_run` can overwrite or clear values; `reopen_task` and a rejection send work back.
|
|
117
|
+
Those four carry the MCP `destructiveHint`. Webhooks, schedules, automation rules, views,
|
|
118
|
+
workflow edits, files, CSV and Trash stay in the app and the REST API.
|
|
119
|
+
- Run names, field values, task titles, comments and activity are written by people, guests
|
|
120
|
+
included: the instructions and the read tools' descriptions tell the model to treat them as
|
|
121
|
+
data, never as instructions, and every such string is rendered on one line.
|
|
122
|
+
- A client's cancellation aborts the API request in flight.
|
|
123
|
+
- Every write carries an `Idempotency-Key`, so a retried request is applied once.
|
|
124
|
+
- The write tools take `expectedRevision` (sent as `Expected-Revision`): a write that raced
|
|
125
|
+
another change is refused with `409 revision_mismatch`, naming the current revision.
|
|
126
|
+
- API failures come back as tool results (`isError: true`) with the code, every field problem,
|
|
127
|
+
`currentRevision` on a 409 and the wait on a 429.
|
|
128
|
+
- The key is sent only to the API and never logged.
|
|
129
|
+
|
|
130
|
+
## Programmatic use
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
134
|
+
import { createServer } from "@process-cart/mcp";
|
|
135
|
+
|
|
136
|
+
const server = await createServer({ apiKey: process.env.PROCESSCART_API_KEY!, readOnly: true });
|
|
137
|
+
await server.connect(new StdioServerTransport());
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`createServer({ apiKey, baseUrl?, readOnly?, fetch?, log?, debug?, startupTimeoutMs? })` resolves
|
|
141
|
+
to an `McpServer` with the key's tools registered; connect it to any MCP transport.
|
|
142
|
+
|
|
143
|
+
## Maintainers
|
|
144
|
+
|
|
145
|
+
Publish with **pnpm**, not npm: the manifest depends on `@process-cart/sdk` as `workspace:^`, which
|
|
146
|
+
`pnpm publish` rewrites to the SDK's real range (`^0.1.0`) in the tarball, and `npm publish` does
|
|
147
|
+
not.
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
pnpm --filter @process-cart/sdk build
|
|
151
|
+
cd packages/mcp && pnpm publish --access public --no-git-checks
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`prepack` rebuilds `dist/`. `pnpm pack --dry-run` shows the tarball. Bump `version` in
|
|
155
|
+
`package.json`, `src/version.ts` and both versions in `server.json` together (`version.test.ts`
|
|
156
|
+
checks them). The MCP Registry listing (`server.json`, name `com.processcart/mcp`) is pending.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `processcart-mcp`: the stdio entry point. Over stdio, stdout carries only
|
|
3
|
+
// the MCP transport, so any stray console output is routed to stderr before
|
|
4
|
+
// the server starts. The parser is re-exported for tests; importing this
|
|
5
|
+
// module does not start the server unless it is the script node runs.
|
|
6
|
+
import { isEntryPoint, main } from "./cli.js";
|
|
7
|
+
import { redactKeys } from "./server.js";
|
|
8
|
+
export { parseArgs } from "./cli.js";
|
|
9
|
+
if (isEntryPoint(import.meta.url, process.argv[1])) {
|
|
10
|
+
const toStderr = (...args) => console.error(...args);
|
|
11
|
+
console.log = toStderr;
|
|
12
|
+
console.info = toStderr;
|
|
13
|
+
console.debug = toStderr;
|
|
14
|
+
main().then((code) => {
|
|
15
|
+
if (code !== null)
|
|
16
|
+
process.exitCode = code;
|
|
17
|
+
}, (error) => {
|
|
18
|
+
process.stderr.write(`[processcart-mcp] ${redactKeys(error instanceof Error ? error.message : String(error))}\n`);
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
});
|
|
21
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
export declare const HELP = "processcart-mcp 0.1.0 \u2014 the Process Cart MCP server (stdio)\n\nUsage:\n npx -y @process-cart/mcp [options]\n\nEnvironment:\n PROCESSCART_API_KEY A Process Cart API key (pc_live_\u2026 or pc_test_\u2026). Required.\n PROCESSCART_BASE_URL The API base URL (default https://app.processcart.com/api). https; plain\n http only for a server on this machine (localhost, 127.x.x.x, ::1).\n PROCESSCART_MCP_DEBUG Set to 1 to log each tool call (name, outcome, duration) to stderr.\n\nOptions:\n --api-key <key> The API key (prefer the environment variable: a flag is visible to\n other processes on this machine).\n --base-url <url> The API base URL.\n --read-only Offer the read tools only, whatever the key allows.\n -h, --help Show this help.\n -v, --version Show the version.\n\nDocs: https://developers.processcart.com/guides/mcp\n";
|
|
3
|
+
export interface ServeOptions {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
readOnly: boolean;
|
|
7
|
+
debug: boolean;
|
|
8
|
+
}
|
|
9
|
+
export type CliCommand = {
|
|
10
|
+
kind: "help";
|
|
11
|
+
} | {
|
|
12
|
+
kind: "version";
|
|
13
|
+
} | {
|
|
14
|
+
kind: "serve";
|
|
15
|
+
options: ServeOptions;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "error";
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
/** A loopback host (`localhost`, `127.0.0.0/8`, `::1`): the one place plain http may go. */
|
|
21
|
+
export declare function isLoopbackHost(hostname: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Parses the command line and environment. Flags win over the environment.
|
|
24
|
+
* Never echoes the key back in a message: an unknown option is named without
|
|
25
|
+
* its value, and a bare argument that looks like a key is not repeated.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseArgs(argv: readonly string[], env: Readonly<Record<string, string | undefined>>): CliCommand;
|
|
28
|
+
/** The process surface `main` uses, injectable for tests. */
|
|
29
|
+
export interface CliIo {
|
|
30
|
+
argv: readonly string[];
|
|
31
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
32
|
+
stdin: Readable;
|
|
33
|
+
stdout: Writable;
|
|
34
|
+
stderr: Pick<Writable, "write">;
|
|
35
|
+
/** Called once the client goes away (stdin ends) or a signal arrives. */
|
|
36
|
+
exit: (code: number) => void;
|
|
37
|
+
fetch?: typeof fetch;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Runs the CLI. Resolves to an exit code for --help, --version and usage
|
|
41
|
+
* errors (2), or to null once the server is serving on stdio.
|
|
42
|
+
*/
|
|
43
|
+
export declare function main(io?: CliIo): Promise<number | null>;
|
|
44
|
+
/** True when `moduleUrl` is the script node was started with (symlinks and npx shims resolved). */
|
|
45
|
+
export declare function isEntryPoint(moduleUrl: string, argv1: string | undefined): boolean;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// The command line: `processcart-mcp` reads its key and options, builds the
|
|
2
|
+
// server and serves it over stdio. Over stdio, stdout IS the MCP transport —
|
|
3
|
+
// every diagnostic goes to stderr. Only --help and --version print to stdout,
|
|
4
|
+
// and they exit before any transport starts.
|
|
5
|
+
import { realpathSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
import { DEFAULT_BASE_URL } from "@process-cart/sdk";
|
|
10
|
+
import { createServer, redactKeys } from "./server.js";
|
|
11
|
+
import { MCP_VERSION } from "./version.js";
|
|
12
|
+
export const HELP = `processcart-mcp ${MCP_VERSION} — the Process Cart MCP server (stdio)
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
npx -y @process-cart/mcp [options]
|
|
16
|
+
|
|
17
|
+
Environment:
|
|
18
|
+
PROCESSCART_API_KEY A Process Cart API key (pc_live_… or pc_test_…). Required.
|
|
19
|
+
PROCESSCART_BASE_URL The API base URL (default ${DEFAULT_BASE_URL}). https; plain
|
|
20
|
+
http only for a server on this machine (localhost, 127.x.x.x, ::1).
|
|
21
|
+
PROCESSCART_MCP_DEBUG Set to 1 to log each tool call (name, outcome, duration) to stderr.
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--api-key <key> The API key (prefer the environment variable: a flag is visible to
|
|
25
|
+
other processes on this machine).
|
|
26
|
+
--base-url <url> The API base URL.
|
|
27
|
+
--read-only Offer the read tools only, whatever the key allows.
|
|
28
|
+
-h, --help Show this help.
|
|
29
|
+
-v, --version Show the version.
|
|
30
|
+
|
|
31
|
+
Docs: https://developers.processcart.com/guides/mcp
|
|
32
|
+
`;
|
|
33
|
+
const API_KEY_PATTERN = /^pc_(live|test)_\S+$/;
|
|
34
|
+
function truthy(value) {
|
|
35
|
+
return value !== undefined && /^(1|true|yes|on)$/i.test(value.trim());
|
|
36
|
+
}
|
|
37
|
+
/** A loopback host (`localhost`, `127.0.0.0/8`, `::1`): the one place plain http may go. */
|
|
38
|
+
export function isLoopbackHost(hostname) {
|
|
39
|
+
const host = hostname.toLowerCase();
|
|
40
|
+
return (host === "localhost" ||
|
|
41
|
+
host === "[::1]" ||
|
|
42
|
+
host === "::1" ||
|
|
43
|
+
/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parses the command line and environment. Flags win over the environment.
|
|
47
|
+
* Never echoes the key back in a message: an unknown option is named without
|
|
48
|
+
* its value, and a bare argument that looks like a key is not repeated.
|
|
49
|
+
*/
|
|
50
|
+
export function parseArgs(argv, env) {
|
|
51
|
+
let apiKey;
|
|
52
|
+
let baseUrl;
|
|
53
|
+
let readOnly = false;
|
|
54
|
+
let version = false;
|
|
55
|
+
for (let i = 0; i < argv.length; i++) {
|
|
56
|
+
const arg = argv[i];
|
|
57
|
+
if (arg === "-h" || arg === "--help")
|
|
58
|
+
return { kind: "help" };
|
|
59
|
+
if (arg === "-v" || arg === "--version") {
|
|
60
|
+
version = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg === "--read-only") {
|
|
64
|
+
readOnly = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const match = /^(--api-key|--base-url)(?:=(.*))?$/.exec(arg);
|
|
68
|
+
if (match) {
|
|
69
|
+
const value = match[2] ?? argv[i + 1];
|
|
70
|
+
if (match[2] === undefined)
|
|
71
|
+
i += 1;
|
|
72
|
+
if (value === undefined ||
|
|
73
|
+
value === "" ||
|
|
74
|
+
(match[2] === undefined && value.startsWith("--"))) {
|
|
75
|
+
return { kind: "error", message: `${match[1]} needs a value` };
|
|
76
|
+
}
|
|
77
|
+
if (match[1] === "--api-key")
|
|
78
|
+
apiKey = value;
|
|
79
|
+
else
|
|
80
|
+
baseUrl = value;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (/pc_(live|test)_/.test(arg) && !arg.startsWith("-")) {
|
|
84
|
+
return {
|
|
85
|
+
kind: "error",
|
|
86
|
+
message: "an API key is not an argument: pass the key as --api-key or PROCESSCART_API_KEY (prefer the environment variable)",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const name = arg.split("=", 1)[0];
|
|
90
|
+
return { kind: "error", message: `unknown option ${JSON.stringify(redactKeys(name))}` };
|
|
91
|
+
}
|
|
92
|
+
if (version)
|
|
93
|
+
return { kind: "version" };
|
|
94
|
+
const key = (apiKey ?? env.PROCESSCART_API_KEY ?? "").trim();
|
|
95
|
+
if (key === "") {
|
|
96
|
+
return {
|
|
97
|
+
kind: "error",
|
|
98
|
+
message: "no API key: set PROCESSCART_API_KEY (an Admin creates keys under Settings → API in Process Cart)",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (!API_KEY_PATTERN.test(key)) {
|
|
102
|
+
return {
|
|
103
|
+
kind: "error",
|
|
104
|
+
message: "the API key is not a Process Cart API key (they start with pc_live_ or pc_test_)",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const base = (baseUrl ?? env.PROCESSCART_BASE_URL ?? "").trim() || DEFAULT_BASE_URL;
|
|
108
|
+
let parsed;
|
|
109
|
+
try {
|
|
110
|
+
parsed = new URL(base);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return { kind: "error", message: `the base URL ${JSON.stringify(base)} is not a URL` };
|
|
114
|
+
}
|
|
115
|
+
if (parsed.protocol !== "https:" &&
|
|
116
|
+
!(parsed.protocol === "http:" && isLoopbackHost(parsed.hostname))) {
|
|
117
|
+
return {
|
|
118
|
+
kind: "error",
|
|
119
|
+
message: "the base URL must use https (http only for a server on this machine: localhost, 127.x.x.x or ::1), so the API key never travels in cleartext",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
kind: "serve",
|
|
124
|
+
options: { apiKey: key, baseUrl: base, readOnly, debug: truthy(env.PROCESSCART_MCP_DEBUG) },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function processIo() {
|
|
128
|
+
return {
|
|
129
|
+
argv: process.argv.slice(2),
|
|
130
|
+
env: process.env,
|
|
131
|
+
stdin: process.stdin,
|
|
132
|
+
stdout: process.stdout,
|
|
133
|
+
stderr: process.stderr,
|
|
134
|
+
exit: (code) => process.exit(code),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Runs the CLI. Resolves to an exit code for --help, --version and usage
|
|
139
|
+
* errors (2), or to null once the server is serving on stdio.
|
|
140
|
+
*/
|
|
141
|
+
export async function main(io = processIo()) {
|
|
142
|
+
const command = parseArgs(io.argv, io.env);
|
|
143
|
+
if (command.kind === "help") {
|
|
144
|
+
io.stdout.write(HELP);
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
if (command.kind === "version") {
|
|
148
|
+
io.stdout.write(`${MCP_VERSION}\n`);
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
if (command.kind === "error") {
|
|
152
|
+
io.stderr.write(`processcart-mcp: ${redactKeys(command.message)}\nRun processcart-mcp --help for usage.\n`);
|
|
153
|
+
return 2;
|
|
154
|
+
}
|
|
155
|
+
const { options } = command;
|
|
156
|
+
// Every diagnostic is redacted: MCP clients keep server stderr in log files.
|
|
157
|
+
const log = (message) => void io.stderr.write(`[processcart-mcp] ${redactKeys(message)}\n`);
|
|
158
|
+
let server;
|
|
159
|
+
try {
|
|
160
|
+
server = await createServer({
|
|
161
|
+
apiKey: options.apiKey,
|
|
162
|
+
baseUrl: options.baseUrl,
|
|
163
|
+
readOnly: options.readOnly,
|
|
164
|
+
debug: options.debug,
|
|
165
|
+
log,
|
|
166
|
+
...(io.fetch ? { fetch: io.fetch } : {}),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
log(`could not start: ${error instanceof Error ? error.message : String(error)}`);
|
|
171
|
+
return 2;
|
|
172
|
+
}
|
|
173
|
+
const transport = new StdioServerTransport(io.stdin, io.stdout);
|
|
174
|
+
let closing = false;
|
|
175
|
+
const shutdown = () => {
|
|
176
|
+
if (closing)
|
|
177
|
+
return;
|
|
178
|
+
closing = true;
|
|
179
|
+
void server.close().finally(() => io.exit(0));
|
|
180
|
+
};
|
|
181
|
+
io.stdin.once("end", shutdown);
|
|
182
|
+
io.stdin.once("close", shutdown);
|
|
183
|
+
await server.connect(transport);
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
/** True when `moduleUrl` is the script node was started with (symlinks and npx shims resolved). */
|
|
187
|
+
export function isEntryPoint(moduleUrl, argv1) {
|
|
188
|
+
if (!argv1)
|
|
189
|
+
return false;
|
|
190
|
+
try {
|
|
191
|
+
return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(path.resolve(argv1));
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { buildInstructions, createServer, INSTRUCTIONS, problemOf, selectTools, STARTUP_TIMEOUT_MS, type CreateServerOptions, } from "./server.js";
|
|
2
|
+
export { TOOLS } from "./tools/index.js";
|
|
3
|
+
export type { ToolAccess, ToolContext, ToolDefinition, ToolOutput } from "./tools/index.js";
|
|
4
|
+
export { MCP_VERSION, SERVER_NAME } from "./version.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @process-cart/mcp — the programmatic entry point. Most people run the CLI
|
|
2
|
+
// (`npx -y @process-cart/mcp`); `createServer` builds the same server for a
|
|
3
|
+
// host that brings its own transport.
|
|
4
|
+
export { buildInstructions, createServer, INSTRUCTIONS, problemOf, selectTools, STARTUP_TIMEOUT_MS, } from "./server.js";
|
|
5
|
+
export { TOOLS } from "./tools/index.js";
|
|
6
|
+
export { MCP_VERSION, SERVER_NAME } from "./version.js";
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { ActivityItem, Comment, GetWorkflowSchemaResponse, Group, Me, ProblemFieldError, Run, RunField, User, WorkflowSummary } from "@process-cart/sdk";
|
|
2
|
+
/** Caps that keep one answer readable. */
|
|
3
|
+
export declare const RENDER_LIMITS: {
|
|
4
|
+
/** Tasks listed in a run before "… and N more tasks". */
|
|
5
|
+
readonly tasks: 60;
|
|
6
|
+
/** Fields listed in a run (across its tasks) before "… and N more fields". */
|
|
7
|
+
readonly fields: 120;
|
|
8
|
+
/** Fields listed in a workflow schema. */
|
|
9
|
+
readonly schemaFields: 150;
|
|
10
|
+
/** Characters of one value, description or comment before it is cut. */
|
|
11
|
+
readonly value: 300;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Free text written by a person — a name, title, label, choice, value or
|
|
15
|
+
* comment — made safe to interpolate into one Markdown line: every line
|
|
16
|
+
* terminator becomes " / ", other control characters are dropped and runs of
|
|
17
|
+
* whitespace collapse, so the text can never start a line of its own (a
|
|
18
|
+
* heading, a list item, a line posing as an instruction). With `max`, it is
|
|
19
|
+
* also cut to that many characters.
|
|
20
|
+
*/
|
|
21
|
+
export declare function text(value: string, max?: number): string;
|
|
22
|
+
/** Cuts `text` to `max` characters with an ellipsis. */
|
|
23
|
+
export declare function truncate(text: string, max?: number): string;
|
|
24
|
+
/** An ISO instant without its milliseconds (`2026-10-13T01:09:28Z`); a dash for null. */
|
|
25
|
+
export declare function instant(value: string | null | undefined): string;
|
|
26
|
+
/** The "pass this cursor" sentence every paged answer ends with. */
|
|
27
|
+
export declare function pagingLine(nextCursor: string | null | undefined, what: string): string;
|
|
28
|
+
/** A canonical value that counts as blank (what a required field may not be). */
|
|
29
|
+
export declare function isBlankValue(field: Pick<RunField, "type" | "value">): boolean;
|
|
30
|
+
export interface RenderRunOptions {
|
|
31
|
+
/**
|
|
32
|
+
* The field keys the run's workflow version marks required, when known
|
|
33
|
+
* (the server reads them once per version). Null or absent: no flags.
|
|
34
|
+
*/
|
|
35
|
+
requiredKeys?: ReadonlySet<string> | null;
|
|
36
|
+
/** A sentence placed under the heading (what the tool just did). */
|
|
37
|
+
lead?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Task counts over the visible tasks (what "progress" means in the app). */
|
|
40
|
+
export declare function runProgress(run: Pick<Run, "tasks">): {
|
|
41
|
+
done: number;
|
|
42
|
+
total: number;
|
|
43
|
+
};
|
|
44
|
+
/** The full run: header, then its tasks grouped by section with each task's fields beneath it. */
|
|
45
|
+
export declare function renderRun(run: Run, options?: RenderRunOptions): string;
|
|
46
|
+
/** A run without its tasks and fields: what find_runs returns per item. */
|
|
47
|
+
export interface RunSummary {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
status: Run["status"];
|
|
51
|
+
priority: Run["priority"];
|
|
52
|
+
labels: string[];
|
|
53
|
+
dueAt: string | null;
|
|
54
|
+
keyDate: string | null;
|
|
55
|
+
revision: number;
|
|
56
|
+
updatedAt: string;
|
|
57
|
+
startedAt: string;
|
|
58
|
+
completedAt: string | null;
|
|
59
|
+
deletedAt: string | null;
|
|
60
|
+
workflow: Run["workflow"];
|
|
61
|
+
url: string;
|
|
62
|
+
progress: {
|
|
63
|
+
done: number;
|
|
64
|
+
total: number;
|
|
65
|
+
};
|
|
66
|
+
openTasks: {
|
|
67
|
+
key: string;
|
|
68
|
+
title: string;
|
|
69
|
+
}[];
|
|
70
|
+
}
|
|
71
|
+
export declare function summarizeRun(run: Run): RunSummary;
|
|
72
|
+
/** One page of runs, one entry each (get_run shows a run's tasks and fields). */
|
|
73
|
+
export declare function renderRunList(runs: readonly Run[], nextCursor: string | null): string;
|
|
74
|
+
export declare function renderWorkflowList(workflows: readonly WorkflowSummary[], nextCursor: string | null, options?: {
|
|
75
|
+
query?: string;
|
|
76
|
+
totalMatches?: number;
|
|
77
|
+
}): string;
|
|
78
|
+
/** One writable field as get_workflow_schema describes it. */
|
|
79
|
+
export interface SchemaFieldSummary {
|
|
80
|
+
key: string;
|
|
81
|
+
label: string;
|
|
82
|
+
type: string;
|
|
83
|
+
format: string | null;
|
|
84
|
+
taskKey: string | null;
|
|
85
|
+
sectionKey: string | null;
|
|
86
|
+
/** In the run-start (kickoff) form. */
|
|
87
|
+
kickoff: boolean;
|
|
88
|
+
/** Must be sent to start_run (a required kickoff field no rule can hide). */
|
|
89
|
+
requiredAtStart: boolean;
|
|
90
|
+
/** Required at start unless a visibility rule hides it. */
|
|
91
|
+
requiredUnlessHidden: boolean;
|
|
92
|
+
/** Allowed values (dropdown / multi-choice choices, subtask item keys). */
|
|
93
|
+
choices: string[];
|
|
94
|
+
/** Bounds and patterns, as short phrases. */
|
|
95
|
+
constraints: string[];
|
|
96
|
+
/** The field's help text and value notes, as the schema words them. */
|
|
97
|
+
notes: string | null;
|
|
98
|
+
}
|
|
99
|
+
/** Reads the API's per-workflow JSON Schemas into one summary per writable field, start fields first. */
|
|
100
|
+
export declare function summarizeSchema(schema: GetWorkflowSchemaResponse): SchemaFieldSummary[];
|
|
101
|
+
export declare function renderSchema(schema: GetWorkflowSchemaResponse): string;
|
|
102
|
+
export declare function renderComment(comment: Comment): string;
|
|
103
|
+
/** The comments get_activity shows: the last ones it read, how many it read, and whether that was all. */
|
|
104
|
+
export interface CommentWindow {
|
|
105
|
+
/** The newest comments read, oldest first. */
|
|
106
|
+
shown: readonly Comment[];
|
|
107
|
+
/** How many comments were read (every one of them when `complete`). */
|
|
108
|
+
total: number;
|
|
109
|
+
/** False when the scan stopped at its cap: the run has more comments than `total`. */
|
|
110
|
+
complete?: boolean;
|
|
111
|
+
}
|
|
112
|
+
export declare function renderActivity(items: readonly ActivityItem[], nextCursor: string | null, comments?: CommentWindow | null): string;
|
|
113
|
+
export declare function renderPeople(users: readonly User[], groups: readonly Group[], options?: {
|
|
114
|
+
query?: string;
|
|
115
|
+
truncated?: boolean;
|
|
116
|
+
}): string;
|
|
117
|
+
export interface ServerMode {
|
|
118
|
+
/** `--read-only` was passed. */
|
|
119
|
+
readOnlyFlag: boolean;
|
|
120
|
+
baseUrl: string;
|
|
121
|
+
/** The tools this server registered, by name. */
|
|
122
|
+
tools: readonly string[];
|
|
123
|
+
/** Why startup could not read the key (the tool list then holds read tools only). */
|
|
124
|
+
startupProblem: string | null;
|
|
125
|
+
}
|
|
126
|
+
export declare function renderMe(me: Me, mode: ServerMode): string;
|
|
127
|
+
/** The fields of a failure the renderer reads (a `ProcessCartError`, or any error mapped to this shape). */
|
|
128
|
+
export interface ProblemView {
|
|
129
|
+
status: number;
|
|
130
|
+
code: string;
|
|
131
|
+
title: string;
|
|
132
|
+
detail?: string;
|
|
133
|
+
requestId: string | null;
|
|
134
|
+
errors?: readonly ProblemFieldError[];
|
|
135
|
+
retryAfter?: number;
|
|
136
|
+
currentRevision?: number;
|
|
137
|
+
}
|
|
138
|
+
/** The error as a model reads it: what failed, every field problem, and what to do next. */
|
|
139
|
+
export declare function renderProblem(problem: ProblemView): string;
|