fastmode-mcp 1.0.3 → 1.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/README.md +32 -4
- package/dist/index.js +165 -2
- package/dist/lib/api-client.d.ts +1 -1
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/tools/get-started.d.ts +1 -1
- package/dist/tools/get-started.d.ts.map +1 -1
- package/dist/tools/get-started.js +93 -0
- package/dist/tools/portal-clients.d.ts +54 -0
- package/dist/tools/portal-clients.d.ts.map +1 -0
- package/dist/tools/portal-clients.js +388 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,27 +138,55 @@ The MCP server automatically downloads prebuilt binaries for your platform durin
|
|
|
138
138
|
|
|
139
139
|
Add to `~/.claude/settings.json`:
|
|
140
140
|
|
|
141
|
+
**macOS/Linux:**
|
|
141
142
|
```json
|
|
142
143
|
{
|
|
143
144
|
"mcpServers": {
|
|
144
145
|
"fastmode": {
|
|
145
|
-
"command": "
|
|
146
|
-
"args": ["-
|
|
146
|
+
"command": "/bin/zsh",
|
|
147
|
+
"args": ["-l", "-c", "npx -y fastmode-mcp"]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Windows:**
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"mcpServers": {
|
|
157
|
+
"fastmode": {
|
|
158
|
+
"command": "cmd",
|
|
159
|
+
"args": ["/c", "npx -y fastmode-mcp"]
|
|
147
160
|
}
|
|
148
161
|
}
|
|
149
162
|
}
|
|
150
163
|
```
|
|
151
164
|
|
|
165
|
+
> **Why the shell wrapper?** VS Code extensions don't inherit your shell's PATH, so if you use nvm, volta, or homebrew for Node.js, the simple `npx` command won't be found. The shell wrapper loads your profile first.
|
|
166
|
+
|
|
152
167
|
### For Cursor
|
|
153
168
|
|
|
154
169
|
Add to your Cursor MCP settings (`~/.cursor/mcp.json` or project-level `.cursor/mcp.json`):
|
|
155
170
|
|
|
171
|
+
**macOS/Linux:**
|
|
156
172
|
```json
|
|
157
173
|
{
|
|
158
174
|
"mcpServers": {
|
|
159
175
|
"fastmode": {
|
|
160
|
-
"command": "
|
|
161
|
-
"args": ["-
|
|
176
|
+
"command": "/bin/zsh",
|
|
177
|
+
"args": ["-l", "-c", "npx -y fastmode-mcp"]
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Windows:**
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"mcpServers": {
|
|
187
|
+
"fastmode": {
|
|
188
|
+
"command": "cmd",
|
|
189
|
+
"args": ["/c", "npx -y fastmode-mcp"]
|
|
162
190
|
}
|
|
163
191
|
}
|
|
164
192
|
}
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,7 @@ const sync_schema_1 = require("./tools/sync-schema");
|
|
|
51
51
|
const generate_samples_1 = require("./tools/generate-samples");
|
|
52
52
|
const get_started_1 = require("./tools/get-started");
|
|
53
53
|
const cms_items_1 = require("./tools/cms-items");
|
|
54
|
+
const portal_clients_1 = require("./tools/portal-clients");
|
|
54
55
|
// Server instructions - embedded in tool descriptions since MCP SDK doesn't have a dedicated field
|
|
55
56
|
// The get_started tool description acts as the primary entry point guidance for agents
|
|
56
57
|
const server = new index_js_1.Server({
|
|
@@ -73,8 +74,8 @@ const TOOLS = [
|
|
|
73
74
|
properties: {
|
|
74
75
|
intent: {
|
|
75
76
|
type: 'string',
|
|
76
|
-
enum: ['explore', 'add_content', 'update_schema', 'convert', 'deploy'],
|
|
77
|
-
description: 'What you want to do: explore (see projects), add_content (create/edit items), update_schema (add collections/fields), convert (new website), deploy (push changes)',
|
|
77
|
+
enum: ['explore', 'add_content', 'update_schema', 'convert', 'deploy', 'manage_clients'],
|
|
78
|
+
description: 'What you want to do: explore (see projects), add_content (create/edit items), update_schema (add collections/fields), convert (new website), deploy (push changes), manage_clients (invite/manage portal clients)',
|
|
78
79
|
},
|
|
79
80
|
projectId: {
|
|
80
81
|
type: 'string',
|
|
@@ -540,6 +541,129 @@ const TOOLS = [
|
|
|
540
541
|
required: ['projectId', 'collectionSlug'],
|
|
541
542
|
},
|
|
542
543
|
},
|
|
544
|
+
// Portal Client Management
|
|
545
|
+
{
|
|
546
|
+
name: 'invite_client',
|
|
547
|
+
description: 'Invite a client to the project portal. Sends an invitation with a unique link. The client creates a password and gets access to manage content. Portal is auto-enabled on the project if not already active. Default permissions: cms.read, cms.write, editor, forms.read.',
|
|
548
|
+
inputSchema: {
|
|
549
|
+
type: 'object',
|
|
550
|
+
properties: {
|
|
551
|
+
projectId: {
|
|
552
|
+
type: 'string',
|
|
553
|
+
description: 'Project ID (UUID) or project name.',
|
|
554
|
+
},
|
|
555
|
+
email: {
|
|
556
|
+
type: 'string',
|
|
557
|
+
description: 'Client email address to invite.',
|
|
558
|
+
},
|
|
559
|
+
name: {
|
|
560
|
+
type: 'string',
|
|
561
|
+
description: 'Optional: Client name for the invitation.',
|
|
562
|
+
},
|
|
563
|
+
permissions: {
|
|
564
|
+
type: 'array',
|
|
565
|
+
items: { type: 'string' },
|
|
566
|
+
description: 'Optional: Array of permissions. Available: cms.read, cms.write, editor, forms.read, dns, api, notifications, billing. Defaults to [cms.read, cms.write, editor, forms.read] if not specified.',
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
required: ['projectId', 'email'],
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: 'list_clients',
|
|
574
|
+
description: 'List all portal clients (users) who have access to this project. Shows their email, name, permissions, and last login.',
|
|
575
|
+
inputSchema: {
|
|
576
|
+
type: 'object',
|
|
577
|
+
properties: {
|
|
578
|
+
projectId: {
|
|
579
|
+
type: 'string',
|
|
580
|
+
description: 'Project ID (UUID) or project name.',
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
required: ['projectId'],
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
name: 'list_invitations',
|
|
588
|
+
description: 'List pending portal invitations for this project. Shows invitations that have been sent but not yet accepted.',
|
|
589
|
+
inputSchema: {
|
|
590
|
+
type: 'object',
|
|
591
|
+
properties: {
|
|
592
|
+
projectId: {
|
|
593
|
+
type: 'string',
|
|
594
|
+
description: 'Project ID (UUID) or project name.',
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
required: ['projectId'],
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
name: 'update_client_permissions',
|
|
602
|
+
description: 'Update the permissions for an existing portal client. Use list_clients to get the access ID. Available permissions: cms.read, cms.write, editor, forms.read, dns, api, notifications, billing.',
|
|
603
|
+
inputSchema: {
|
|
604
|
+
type: 'object',
|
|
605
|
+
properties: {
|
|
606
|
+
projectId: {
|
|
607
|
+
type: 'string',
|
|
608
|
+
description: 'Project ID (UUID) or project name.',
|
|
609
|
+
},
|
|
610
|
+
accessId: {
|
|
611
|
+
type: 'string',
|
|
612
|
+
description: 'The access ID of the client (from list_clients).',
|
|
613
|
+
},
|
|
614
|
+
permissions: {
|
|
615
|
+
type: 'array',
|
|
616
|
+
items: { type: 'string' },
|
|
617
|
+
description: 'The new set of permissions to assign. This replaces ALL existing permissions.',
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
required: ['projectId', 'accessId', 'permissions'],
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: 'revoke_client_access',
|
|
625
|
+
description: 'Revoke a client\'s portal access. REQUIRES explicit user confirmation before calling. Ask the user to confirm first. The client will no longer be able to access the portal.',
|
|
626
|
+
inputSchema: {
|
|
627
|
+
type: 'object',
|
|
628
|
+
properties: {
|
|
629
|
+
projectId: {
|
|
630
|
+
type: 'string',
|
|
631
|
+
description: 'Project ID (UUID) or project name.',
|
|
632
|
+
},
|
|
633
|
+
accessId: {
|
|
634
|
+
type: 'string',
|
|
635
|
+
description: 'The access ID of the client to revoke (from list_clients).',
|
|
636
|
+
},
|
|
637
|
+
confirmRevoke: {
|
|
638
|
+
type: 'boolean',
|
|
639
|
+
description: 'Must be true. Only set this after the user has explicitly confirmed they want to revoke access.',
|
|
640
|
+
},
|
|
641
|
+
},
|
|
642
|
+
required: ['projectId', 'accessId', 'confirmRevoke'],
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: 'cancel_invitation',
|
|
647
|
+
description: 'Cancel a pending portal invitation. REQUIRES explicit user confirmation before calling. The invitation link will no longer work.',
|
|
648
|
+
inputSchema: {
|
|
649
|
+
type: 'object',
|
|
650
|
+
properties: {
|
|
651
|
+
projectId: {
|
|
652
|
+
type: 'string',
|
|
653
|
+
description: 'Project ID (UUID) or project name.',
|
|
654
|
+
},
|
|
655
|
+
invitationId: {
|
|
656
|
+
type: 'string',
|
|
657
|
+
description: 'The invitation ID to cancel (from list_invitations).',
|
|
658
|
+
},
|
|
659
|
+
confirmCancel: {
|
|
660
|
+
type: 'boolean',
|
|
661
|
+
description: 'Must be true. Only set this after the user has explicitly confirmed they want to cancel the invitation.',
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
required: ['projectId', 'invitationId', 'confirmCancel'],
|
|
665
|
+
},
|
|
666
|
+
},
|
|
543
667
|
];
|
|
544
668
|
// Handle list tools request
|
|
545
669
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
@@ -662,6 +786,45 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
662
786
|
fieldSlug: params.fieldSlug,
|
|
663
787
|
});
|
|
664
788
|
break;
|
|
789
|
+
case 'invite_client':
|
|
790
|
+
result = await (0, portal_clients_1.inviteClient)({
|
|
791
|
+
projectId: params.projectId,
|
|
792
|
+
email: params.email,
|
|
793
|
+
name: params.name,
|
|
794
|
+
permissions: params.permissions,
|
|
795
|
+
});
|
|
796
|
+
break;
|
|
797
|
+
case 'list_clients':
|
|
798
|
+
result = await (0, portal_clients_1.listClients)({
|
|
799
|
+
projectId: params.projectId,
|
|
800
|
+
});
|
|
801
|
+
break;
|
|
802
|
+
case 'list_invitations':
|
|
803
|
+
result = await (0, portal_clients_1.listInvitations)({
|
|
804
|
+
projectId: params.projectId,
|
|
805
|
+
});
|
|
806
|
+
break;
|
|
807
|
+
case 'update_client_permissions':
|
|
808
|
+
result = await (0, portal_clients_1.updateClientPermissions)({
|
|
809
|
+
projectId: params.projectId,
|
|
810
|
+
accessId: params.accessId,
|
|
811
|
+
permissions: params.permissions,
|
|
812
|
+
});
|
|
813
|
+
break;
|
|
814
|
+
case 'revoke_client_access':
|
|
815
|
+
result = await (0, portal_clients_1.revokeClientAccess)({
|
|
816
|
+
projectId: params.projectId,
|
|
817
|
+
accessId: params.accessId,
|
|
818
|
+
confirmRevoke: params.confirmRevoke,
|
|
819
|
+
});
|
|
820
|
+
break;
|
|
821
|
+
case 'cancel_invitation':
|
|
822
|
+
result = await (0, portal_clients_1.cancelInvitation)({
|
|
823
|
+
projectId: params.projectId,
|
|
824
|
+
invitationId: params.invitationId,
|
|
825
|
+
confirmCancel: params.confirmCancel,
|
|
826
|
+
});
|
|
827
|
+
break;
|
|
665
828
|
default:
|
|
666
829
|
return {
|
|
667
830
|
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export declare function getAuthRequiredMessage(): string;
|
|
|
35
35
|
*/
|
|
36
36
|
export interface ApiRequestOptions {
|
|
37
37
|
tenantId?: string;
|
|
38
|
-
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
38
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
39
39
|
body?: unknown;
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,SAAS,CAAC,CAgB5D;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAKxC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED;;GAEG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAW5D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAS/C;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,SAAS,CAAC,CAgB5D;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAKxC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED;;GAEG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAW5D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAS/C;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG,QAAQ,CAAC,CAoEjC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,IAAI,QAAQ,CAOlE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAEvD;AAYD;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAwCnH;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,CAAM,GAChD,OAAO,CAAC;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG,QAAQ,CAAC,CAOjC"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 2. Intent Inference - Determine what user wants to do
|
|
7
7
|
* 3. Guided Execution - Provide exact tool calls with real values
|
|
8
8
|
*/
|
|
9
|
-
export type Intent = 'explore' | 'add_content' | 'update_schema' | 'convert' | 'deploy';
|
|
9
|
+
export type Intent = 'explore' | 'add_content' | 'update_schema' | 'convert' | 'deploy' | 'manage_clients';
|
|
10
10
|
export interface GetStartedInput {
|
|
11
11
|
intent?: Intent;
|
|
12
12
|
projectId?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-started.d.ts","sourceRoot":"","sources":["../../src/tools/get-started.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA8CH,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,eAAe,GAAG,SAAS,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"get-started.d.ts","sourceRoot":"","sources":["../../src/tools/get-started.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA8CH,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,eAAe,GAAG,SAAS,GAAG,QAAQ,GAAG,gBAAgB,CAAC;AAE3G,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2rBD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAgHxE"}
|
|
@@ -101,6 +101,11 @@ get_started(intent: "deploy", projectId: "${firstProject.id}")
|
|
|
101
101
|
get_started(intent: "convert")
|
|
102
102
|
\`\`\`
|
|
103
103
|
|
|
104
|
+
### Manage portal clients
|
|
105
|
+
\`\`\`
|
|
106
|
+
get_started(intent: "manage_clients", projectId: "${firstProject.id}")
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
104
109
|
## Available Intents
|
|
105
110
|
|
|
106
111
|
| Intent | Description |
|
|
@@ -110,6 +115,7 @@ get_started(intent: "convert")
|
|
|
110
115
|
| \`update_schema\` | Add collections or fields |
|
|
111
116
|
| \`convert\` | Build a new website from scratch |
|
|
112
117
|
| \`deploy\` | Push changes to an existing site |
|
|
118
|
+
| \`manage_clients\` | Invite/manage client portal users |
|
|
113
119
|
`;
|
|
114
120
|
return output;
|
|
115
121
|
}
|
|
@@ -578,6 +584,74 @@ deploy_package(
|
|
|
578
584
|
`;
|
|
579
585
|
return output;
|
|
580
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Build the manage_clients response
|
|
589
|
+
*/
|
|
590
|
+
function buildManageClientsResponse(project) {
|
|
591
|
+
return `# Fast Mode MCP - Manage Portal Clients
|
|
592
|
+
|
|
593
|
+
## Project: ${project.name}
|
|
594
|
+
**URL:** https://${project.subdomain}.fastmode.ai
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
## Workflow: Client Portal Management
|
|
599
|
+
|
|
600
|
+
### Step 1: See who has access
|
|
601
|
+
\`\`\`
|
|
602
|
+
list_clients(projectId: "${project.id}")
|
|
603
|
+
\`\`\`
|
|
604
|
+
|
|
605
|
+
### Step 2: See pending invitations
|
|
606
|
+
\`\`\`
|
|
607
|
+
list_invitations(projectId: "${project.id}")
|
|
608
|
+
\`\`\`
|
|
609
|
+
|
|
610
|
+
### Step 3: Invite a new client
|
|
611
|
+
\`\`\`
|
|
612
|
+
invite_client(
|
|
613
|
+
projectId: "${project.id}",
|
|
614
|
+
email: "client@example.com",
|
|
615
|
+
name: "Client Name"
|
|
616
|
+
)
|
|
617
|
+
\`\`\`
|
|
618
|
+
|
|
619
|
+
### Step 4: Update permissions
|
|
620
|
+
\`\`\`
|
|
621
|
+
update_client_permissions(
|
|
622
|
+
projectId: "${project.id}",
|
|
623
|
+
accessId: "ACCESS_ID_FROM_LIST",
|
|
624
|
+
permissions: ["cms.read", "cms.write", "editor"]
|
|
625
|
+
)
|
|
626
|
+
\`\`\`
|
|
627
|
+
|
|
628
|
+
### Step 5: Revoke access (requires user confirmation)
|
|
629
|
+
\`\`\`
|
|
630
|
+
revoke_client_access(
|
|
631
|
+
projectId: "${project.id}",
|
|
632
|
+
accessId: "ACCESS_ID",
|
|
633
|
+
confirmRevoke: true
|
|
634
|
+
)
|
|
635
|
+
\`\`\`
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
## Available Permissions
|
|
640
|
+
|
|
641
|
+
| Permission | Description |
|
|
642
|
+
|------------|-------------|
|
|
643
|
+
| \`cms.read\` | View collection items |
|
|
644
|
+
| \`cms.write\` | Create/edit/archive/delete items |
|
|
645
|
+
| \`editor\` | Access visual editor |
|
|
646
|
+
| \`forms.read\` | View form submissions |
|
|
647
|
+
| \`dns\` | Manage DNS settings |
|
|
648
|
+
| \`api\` | Access API/integrations |
|
|
649
|
+
| \`notifications\` | Manage notification rules |
|
|
650
|
+
| \`billing\` | View plans and manage billing |
|
|
651
|
+
|
|
652
|
+
**Default permissions:** cms.read, cms.write, editor, forms.read
|
|
653
|
+
`;
|
|
654
|
+
}
|
|
581
655
|
/**
|
|
582
656
|
* Build unauthenticated response
|
|
583
657
|
*/
|
|
@@ -689,6 +763,25 @@ Could not find project: "${projectId}"
|
|
|
689
763
|
Use \`list_projects\` to see available projects.`;
|
|
690
764
|
}
|
|
691
765
|
return buildDeployResponse(context.selectedProject);
|
|
766
|
+
case 'manage_clients':
|
|
767
|
+
if (!context.selectedProject) {
|
|
768
|
+
if (!projectId) {
|
|
769
|
+
return `# Project Required
|
|
770
|
+
|
|
771
|
+
To manage clients, specify which project:
|
|
772
|
+
\`\`\`
|
|
773
|
+
get_started(intent: "manage_clients", projectId: "your-project-id")
|
|
774
|
+
\`\`\`
|
|
775
|
+
|
|
776
|
+
${buildExploreResponse(context)}`;
|
|
777
|
+
}
|
|
778
|
+
return `# Project Not Found
|
|
779
|
+
|
|
780
|
+
Could not find project: "${projectId}"
|
|
781
|
+
|
|
782
|
+
Use \`list_projects\` to see available projects.`;
|
|
783
|
+
}
|
|
784
|
+
return buildManageClientsResponse(context.selectedProject);
|
|
692
785
|
case 'explore':
|
|
693
786
|
default:
|
|
694
787
|
// If projectId provided, show detailed project info
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portal Client Management Tools
|
|
3
|
+
*
|
|
4
|
+
* Tools for inviting clients, managing permissions, and revoking access
|
|
5
|
+
* to the client portal via the portal-admin API.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Invite a client to the project portal
|
|
9
|
+
*/
|
|
10
|
+
export declare function inviteClient(params: {
|
|
11
|
+
projectId: string;
|
|
12
|
+
email: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
permissions?: string[];
|
|
15
|
+
}): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* List all portal clients with access to this project
|
|
18
|
+
*/
|
|
19
|
+
export declare function listClients(params: {
|
|
20
|
+
projectId: string;
|
|
21
|
+
}): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* List pending portal invitations
|
|
24
|
+
*/
|
|
25
|
+
export declare function listInvitations(params: {
|
|
26
|
+
projectId: string;
|
|
27
|
+
}): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Update a portal client's permissions
|
|
30
|
+
*/
|
|
31
|
+
export declare function updateClientPermissions(params: {
|
|
32
|
+
projectId: string;
|
|
33
|
+
accessId: string;
|
|
34
|
+
permissions: string[];
|
|
35
|
+
}): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Revoke a client's portal access
|
|
38
|
+
* REQUIRES confirmRevoke: true to execute
|
|
39
|
+
*/
|
|
40
|
+
export declare function revokeClientAccess(params: {
|
|
41
|
+
projectId: string;
|
|
42
|
+
accessId: string;
|
|
43
|
+
confirmRevoke: boolean;
|
|
44
|
+
}): Promise<string>;
|
|
45
|
+
/**
|
|
46
|
+
* Cancel a pending portal invitation
|
|
47
|
+
* REQUIRES confirmCancel: true to execute
|
|
48
|
+
*/
|
|
49
|
+
export declare function cancelInvitation(params: {
|
|
50
|
+
projectId: string;
|
|
51
|
+
invitationId: string;
|
|
52
|
+
confirmCancel: boolean;
|
|
53
|
+
}): Promise<string>;
|
|
54
|
+
//# sourceMappingURL=portal-clients.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal-clients.d.ts","sourceRoot":"","sources":["../../src/tools/portal-clients.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiHH;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkDlB;AAuBD;;GAEG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE;IACxC,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB;AAmDD;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB;AA4CD;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0DlB;AAkBD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsDlB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsDlB"}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Portal Client Management Tools
|
|
4
|
+
*
|
|
5
|
+
* Tools for inviting clients, managing permissions, and revoking access
|
|
6
|
+
* to the client portal via the portal-admin API.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.inviteClient = inviteClient;
|
|
10
|
+
exports.listClients = listClients;
|
|
11
|
+
exports.listInvitations = listInvitations;
|
|
12
|
+
exports.updateClientPermissions = updateClientPermissions;
|
|
13
|
+
exports.revokeClientAccess = revokeClientAccess;
|
|
14
|
+
exports.cancelInvitation = cancelInvitation;
|
|
15
|
+
const api_client_1 = require("../lib/api-client");
|
|
16
|
+
const device_flow_1 = require("../lib/device-flow");
|
|
17
|
+
// ============ Helpers ============
|
|
18
|
+
const PERMISSION_DESCRIPTIONS = {
|
|
19
|
+
'cms.read': 'View collection items',
|
|
20
|
+
'cms.write': 'Create/edit/archive/delete items',
|
|
21
|
+
'editor': 'Access visual editor',
|
|
22
|
+
'forms.read': 'View form submissions',
|
|
23
|
+
'dns': 'Manage DNS settings',
|
|
24
|
+
'api': 'Access API/integrations',
|
|
25
|
+
'notifications': 'Manage notification rules',
|
|
26
|
+
'billing': 'View plans and manage billing',
|
|
27
|
+
};
|
|
28
|
+
const ALL_PERMISSIONS = Object.keys(PERMISSION_DESCRIPTIONS);
|
|
29
|
+
/**
|
|
30
|
+
* Type guard to check if prepare result failed
|
|
31
|
+
*/
|
|
32
|
+
function prepFailed(prep) {
|
|
33
|
+
return !prep.success;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Helper to ensure authentication and resolve project ID
|
|
37
|
+
*/
|
|
38
|
+
async function prepareRequest(projectId) {
|
|
39
|
+
if (await (0, api_client_1.needsAuthentication)()) {
|
|
40
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
41
|
+
if (!authResult.authenticated) {
|
|
42
|
+
return { success: false, message: authResult.message };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const resolved = await (0, api_client_1.resolveProjectId)(projectId);
|
|
46
|
+
if ('error' in resolved) {
|
|
47
|
+
return { success: false, message: `# Project Not Found\n\n${resolved.error}` };
|
|
48
|
+
}
|
|
49
|
+
return { success: true, tenantId: resolved.tenantId };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Format permissions as a readable list
|
|
53
|
+
*/
|
|
54
|
+
function formatPermissions(permissions) {
|
|
55
|
+
return permissions
|
|
56
|
+
.map(p => `- \`${p}\` — ${PERMISSION_DESCRIPTIONS[p] || 'Unknown permission'}`)
|
|
57
|
+
.join('\n');
|
|
58
|
+
}
|
|
59
|
+
// ============ Exported Functions ============
|
|
60
|
+
/**
|
|
61
|
+
* Invite a client to the project portal
|
|
62
|
+
*/
|
|
63
|
+
async function inviteClient(params) {
|
|
64
|
+
const prep = await prepareRequest(params.projectId);
|
|
65
|
+
if (prepFailed(prep))
|
|
66
|
+
return prep.message;
|
|
67
|
+
// Validate permissions if provided
|
|
68
|
+
if (params.permissions) {
|
|
69
|
+
const invalid = params.permissions.filter(p => !ALL_PERMISSIONS.includes(p));
|
|
70
|
+
if (invalid.length > 0) {
|
|
71
|
+
return `# Invalid Permissions\n\nUnknown permissions: ${invalid.map(p => `\`${p}\``).join(', ')}\n\n**Available permissions:**\n${ALL_PERMISSIONS.map(p => `- \`${p}\``).join('\n')}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const body = { email: params.email };
|
|
75
|
+
if (params.name)
|
|
76
|
+
body.name = params.name;
|
|
77
|
+
if (params.permissions)
|
|
78
|
+
body.permissions = params.permissions;
|
|
79
|
+
const response = await (0, api_client_1.apiRequest)('/api/portal-admin/invitations', {
|
|
80
|
+
tenantId: prep.tenantId,
|
|
81
|
+
method: 'POST',
|
|
82
|
+
body,
|
|
83
|
+
});
|
|
84
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
85
|
+
if ((0, api_client_1.needsAuthError)(response)) {
|
|
86
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
87
|
+
if (!authResult.authenticated)
|
|
88
|
+
return authResult.message;
|
|
89
|
+
const retry = await (0, api_client_1.apiRequest)('/api/portal-admin/invitations', {
|
|
90
|
+
tenantId: prep.tenantId,
|
|
91
|
+
method: 'POST',
|
|
92
|
+
body,
|
|
93
|
+
});
|
|
94
|
+
if ((0, api_client_1.isApiError)(retry)) {
|
|
95
|
+
return `# Error Inviting Client\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
|
|
96
|
+
}
|
|
97
|
+
return formatInviteResponse(retry.data);
|
|
98
|
+
}
|
|
99
|
+
return `# Error Inviting Client\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
|
|
100
|
+
}
|
|
101
|
+
return formatInviteResponse(response.data);
|
|
102
|
+
}
|
|
103
|
+
function formatInviteResponse(invite) {
|
|
104
|
+
const permissions = invite.permissions;
|
|
105
|
+
return `# Client Invited Successfully
|
|
106
|
+
|
|
107
|
+
**Email:** ${invite.email}${invite.name ? `\n**Name:** ${invite.name}` : ''}
|
|
108
|
+
**Expires:** ${new Date(invite.expiresAt).toLocaleDateString()}
|
|
109
|
+
|
|
110
|
+
## Invite Link
|
|
111
|
+
|
|
112
|
+
> ${invite.inviteUrl}
|
|
113
|
+
|
|
114
|
+
**Share this link with the client.** They will create a password and get portal access. The link expires in 7 days.
|
|
115
|
+
|
|
116
|
+
## Permissions Granted
|
|
117
|
+
${formatPermissions(permissions)}
|
|
118
|
+
|
|
119
|
+
**Note:** The client portal has been auto-enabled for this project.
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* List all portal clients with access to this project
|
|
124
|
+
*/
|
|
125
|
+
async function listClients(params) {
|
|
126
|
+
const prep = await prepareRequest(params.projectId);
|
|
127
|
+
if (prepFailed(prep))
|
|
128
|
+
return prep.message;
|
|
129
|
+
const response = await (0, api_client_1.apiRequest)('/api/portal-admin/users', { tenantId: prep.tenantId, method: 'GET' });
|
|
130
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
131
|
+
if ((0, api_client_1.needsAuthError)(response)) {
|
|
132
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
133
|
+
if (!authResult.authenticated)
|
|
134
|
+
return authResult.message;
|
|
135
|
+
const retry = await (0, api_client_1.apiRequest)('/api/portal-admin/users', { tenantId: prep.tenantId, method: 'GET' });
|
|
136
|
+
if ((0, api_client_1.isApiError)(retry)) {
|
|
137
|
+
return `# Error Listing Clients\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
|
|
138
|
+
}
|
|
139
|
+
return formatClientsList(retry.data, params.projectId);
|
|
140
|
+
}
|
|
141
|
+
return `# Error Listing Clients\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
|
|
142
|
+
}
|
|
143
|
+
return formatClientsList(response.data, params.projectId);
|
|
144
|
+
}
|
|
145
|
+
function formatClientsList(clients, projectId) {
|
|
146
|
+
if (!clients || clients.length === 0) {
|
|
147
|
+
return `# No Portal Clients
|
|
148
|
+
|
|
149
|
+
No clients have access to this project's portal yet.
|
|
150
|
+
|
|
151
|
+
To invite a client:
|
|
152
|
+
\`\`\`
|
|
153
|
+
invite_client(projectId: "${projectId}", email: "client@example.com")
|
|
154
|
+
\`\`\`
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
let output = `# Portal Clients
|
|
158
|
+
|
|
159
|
+
Found ${clients.length} client${clients.length !== 1 ? 's' : ''} with access:
|
|
160
|
+
|
|
161
|
+
| Name | Email | Permissions | Last Login | Access ID |
|
|
162
|
+
|------|-------|-------------|------------|-----------|
|
|
163
|
+
`;
|
|
164
|
+
for (const client of clients) {
|
|
165
|
+
const name = client.name || '-';
|
|
166
|
+
const perms = client.permissions.join(', ');
|
|
167
|
+
const lastLogin = client.lastLoginAt
|
|
168
|
+
? new Date(client.lastLoginAt).toLocaleDateString()
|
|
169
|
+
: 'Never';
|
|
170
|
+
output += `| ${name} | ${client.email} | ${perms} | ${lastLogin} | \`${client.id}\` |\n`;
|
|
171
|
+
}
|
|
172
|
+
output += `
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Actions
|
|
176
|
+
|
|
177
|
+
**Update permissions:**
|
|
178
|
+
\`\`\`
|
|
179
|
+
update_client_permissions(projectId: "${projectId}", accessId: "ACCESS_ID", permissions: ["cms.read", "cms.write"])
|
|
180
|
+
\`\`\`
|
|
181
|
+
|
|
182
|
+
**Revoke access** (requires user confirmation):
|
|
183
|
+
\`\`\`
|
|
184
|
+
revoke_client_access(projectId: "${projectId}", accessId: "ACCESS_ID", confirmRevoke: true)
|
|
185
|
+
\`\`\`
|
|
186
|
+
`;
|
|
187
|
+
return output;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* List pending portal invitations
|
|
191
|
+
*/
|
|
192
|
+
async function listInvitations(params) {
|
|
193
|
+
const prep = await prepareRequest(params.projectId);
|
|
194
|
+
if (prepFailed(prep))
|
|
195
|
+
return prep.message;
|
|
196
|
+
const response = await (0, api_client_1.apiRequest)('/api/portal-admin/invitations', { tenantId: prep.tenantId, method: 'GET' });
|
|
197
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
198
|
+
if ((0, api_client_1.needsAuthError)(response)) {
|
|
199
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
200
|
+
if (!authResult.authenticated)
|
|
201
|
+
return authResult.message;
|
|
202
|
+
const retry = await (0, api_client_1.apiRequest)('/api/portal-admin/invitations', { tenantId: prep.tenantId, method: 'GET' });
|
|
203
|
+
if ((0, api_client_1.isApiError)(retry)) {
|
|
204
|
+
return `# Error Listing Invitations\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
|
|
205
|
+
}
|
|
206
|
+
return formatInvitationsList(retry.data, params.projectId);
|
|
207
|
+
}
|
|
208
|
+
return `# Error Listing Invitations\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
|
|
209
|
+
}
|
|
210
|
+
return formatInvitationsList(response.data, params.projectId);
|
|
211
|
+
}
|
|
212
|
+
function formatInvitationsList(invitations, projectId) {
|
|
213
|
+
if (!invitations || invitations.length === 0) {
|
|
214
|
+
return `# No Pending Invitations
|
|
215
|
+
|
|
216
|
+
There are no pending invitations for this project.
|
|
217
|
+
|
|
218
|
+
To invite a client:
|
|
219
|
+
\`\`\`
|
|
220
|
+
invite_client(projectId: "${projectId}", email: "client@example.com")
|
|
221
|
+
\`\`\`
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
let output = `# Pending Invitations
|
|
225
|
+
|
|
226
|
+
Found ${invitations.length} pending invitation${invitations.length !== 1 ? 's' : ''}:
|
|
227
|
+
|
|
228
|
+
| Email | Name | Permissions | Expires | ID |
|
|
229
|
+
|-------|------|-------------|---------|----|
|
|
230
|
+
`;
|
|
231
|
+
for (const inv of invitations) {
|
|
232
|
+
const name = inv.name || '-';
|
|
233
|
+
const perms = inv.permissions.join(', ');
|
|
234
|
+
const expires = new Date(inv.expiresAt).toLocaleDateString();
|
|
235
|
+
output += `| ${inv.email} | ${name} | ${perms} | ${expires} | \`${inv.id}\` |\n`;
|
|
236
|
+
}
|
|
237
|
+
output += `
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Actions
|
|
241
|
+
|
|
242
|
+
**Cancel an invitation** (requires user confirmation):
|
|
243
|
+
\`\`\`
|
|
244
|
+
cancel_invitation(projectId: "${projectId}", invitationId: "INVITATION_ID", confirmCancel: true)
|
|
245
|
+
\`\`\`
|
|
246
|
+
`;
|
|
247
|
+
return output;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Update a portal client's permissions
|
|
251
|
+
*/
|
|
252
|
+
async function updateClientPermissions(params) {
|
|
253
|
+
const prep = await prepareRequest(params.projectId);
|
|
254
|
+
if (prepFailed(prep))
|
|
255
|
+
return prep.message;
|
|
256
|
+
// Validate permissions
|
|
257
|
+
const invalid = params.permissions.filter(p => !ALL_PERMISSIONS.includes(p));
|
|
258
|
+
if (invalid.length > 0) {
|
|
259
|
+
return `# Invalid Permissions\n\nUnknown permissions: ${invalid.map(p => `\`${p}\``).join(', ')}\n\n**Available permissions:**\n${ALL_PERMISSIONS.map(p => `- \`${p}\``).join('\n')}`;
|
|
260
|
+
}
|
|
261
|
+
const response = await (0, api_client_1.apiRequest)(`/api/portal-admin/users/${params.accessId}`, {
|
|
262
|
+
tenantId: prep.tenantId,
|
|
263
|
+
method: 'PATCH',
|
|
264
|
+
body: { permissions: params.permissions },
|
|
265
|
+
});
|
|
266
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
267
|
+
if ((0, api_client_1.needsAuthError)(response)) {
|
|
268
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
269
|
+
if (!authResult.authenticated)
|
|
270
|
+
return authResult.message;
|
|
271
|
+
const retry = await (0, api_client_1.apiRequest)(`/api/portal-admin/users/${params.accessId}`, {
|
|
272
|
+
tenantId: prep.tenantId,
|
|
273
|
+
method: 'PATCH',
|
|
274
|
+
body: { permissions: params.permissions },
|
|
275
|
+
});
|
|
276
|
+
if ((0, api_client_1.isApiError)(retry)) {
|
|
277
|
+
return `# Error Updating Permissions\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
|
|
278
|
+
}
|
|
279
|
+
return formatPermissionsUpdate(retry.data);
|
|
280
|
+
}
|
|
281
|
+
return `# Error Updating Permissions\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
|
|
282
|
+
}
|
|
283
|
+
return formatPermissionsUpdate(response.data);
|
|
284
|
+
}
|
|
285
|
+
function formatPermissionsUpdate(client) {
|
|
286
|
+
return `# Permissions Updated
|
|
287
|
+
|
|
288
|
+
**Client:** ${client.email}${client.name ? ` (${client.name})` : ''}
|
|
289
|
+
**Access ID:** \`${client.id}\`
|
|
290
|
+
|
|
291
|
+
## Updated Permissions
|
|
292
|
+
${formatPermissions(client.permissions)}
|
|
293
|
+
`;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Revoke a client's portal access
|
|
297
|
+
* REQUIRES confirmRevoke: true to execute
|
|
298
|
+
*/
|
|
299
|
+
async function revokeClientAccess(params) {
|
|
300
|
+
if (params.confirmRevoke !== true) {
|
|
301
|
+
return `# Confirmation Required
|
|
302
|
+
|
|
303
|
+
**You must get explicit permission from the user before revoking client access.**
|
|
304
|
+
|
|
305
|
+
To revoke access for client with access ID "${params.accessId}":
|
|
306
|
+
|
|
307
|
+
1. Ask the user: "Are you sure you want to revoke this client's portal access?"
|
|
308
|
+
2. Only after the user confirms, call this tool again with \`confirmRevoke: true\`
|
|
309
|
+
|
|
310
|
+
**Never revoke access without user confirmation.**
|
|
311
|
+
`;
|
|
312
|
+
}
|
|
313
|
+
const prep = await prepareRequest(params.projectId);
|
|
314
|
+
if (prepFailed(prep))
|
|
315
|
+
return prep.message;
|
|
316
|
+
const response = await (0, api_client_1.apiRequest)(`/api/portal-admin/users/${params.accessId}`, { tenantId: prep.tenantId, method: 'DELETE' });
|
|
317
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
318
|
+
if ((0, api_client_1.needsAuthError)(response)) {
|
|
319
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
320
|
+
if (!authResult.authenticated)
|
|
321
|
+
return authResult.message;
|
|
322
|
+
const retry = await (0, api_client_1.apiRequest)(`/api/portal-admin/users/${params.accessId}`, { tenantId: prep.tenantId, method: 'DELETE' });
|
|
323
|
+
if ((0, api_client_1.isApiError)(retry)) {
|
|
324
|
+
return `# Error Revoking Access\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
|
|
325
|
+
}
|
|
326
|
+
return `# Client Access Revoked
|
|
327
|
+
|
|
328
|
+
Successfully revoked portal access for access ID \`${params.accessId}\`.
|
|
329
|
+
|
|
330
|
+
The client can no longer access the portal for this project.
|
|
331
|
+
`;
|
|
332
|
+
}
|
|
333
|
+
return `# Error Revoking Access\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
|
|
334
|
+
}
|
|
335
|
+
return `# Client Access Revoked
|
|
336
|
+
|
|
337
|
+
Successfully revoked portal access for access ID \`${params.accessId}\`.
|
|
338
|
+
|
|
339
|
+
The client can no longer access the portal for this project.
|
|
340
|
+
`;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Cancel a pending portal invitation
|
|
344
|
+
* REQUIRES confirmCancel: true to execute
|
|
345
|
+
*/
|
|
346
|
+
async function cancelInvitation(params) {
|
|
347
|
+
if (params.confirmCancel !== true) {
|
|
348
|
+
return `# Confirmation Required
|
|
349
|
+
|
|
350
|
+
**You must get explicit permission from the user before canceling an invitation.**
|
|
351
|
+
|
|
352
|
+
To cancel invitation "${params.invitationId}":
|
|
353
|
+
|
|
354
|
+
1. Ask the user: "Are you sure you want to cancel this invitation?"
|
|
355
|
+
2. Only after the user confirms, call this tool again with \`confirmCancel: true\`
|
|
356
|
+
|
|
357
|
+
**Never cancel invitations without user confirmation.**
|
|
358
|
+
`;
|
|
359
|
+
}
|
|
360
|
+
const prep = await prepareRequest(params.projectId);
|
|
361
|
+
if (prepFailed(prep))
|
|
362
|
+
return prep.message;
|
|
363
|
+
const response = await (0, api_client_1.apiRequest)(`/api/portal-admin/invitations/${params.invitationId}`, { tenantId: prep.tenantId, method: 'DELETE' });
|
|
364
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
365
|
+
if ((0, api_client_1.needsAuthError)(response)) {
|
|
366
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
367
|
+
if (!authResult.authenticated)
|
|
368
|
+
return authResult.message;
|
|
369
|
+
const retry = await (0, api_client_1.apiRequest)(`/api/portal-admin/invitations/${params.invitationId}`, { tenantId: prep.tenantId, method: 'DELETE' });
|
|
370
|
+
if ((0, api_client_1.isApiError)(retry)) {
|
|
371
|
+
return `# Error Canceling Invitation\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
|
|
372
|
+
}
|
|
373
|
+
return `# Invitation Canceled
|
|
374
|
+
|
|
375
|
+
Successfully canceled invitation \`${params.invitationId}\`.
|
|
376
|
+
|
|
377
|
+
The invitation link will no longer work.
|
|
378
|
+
`;
|
|
379
|
+
}
|
|
380
|
+
return `# Error Canceling Invitation\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
|
|
381
|
+
}
|
|
382
|
+
return `# Invitation Canceled
|
|
383
|
+
|
|
384
|
+
Successfully canceled invitation \`${params.invitationId}\`.
|
|
385
|
+
|
|
386
|
+
The invitation link will no longer work.
|
|
387
|
+
`;
|
|
388
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastmode-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP server for FastMode CMS. Convert websites, validate packages, and deploy directly to FastMode. Includes authentication, project creation, schema sync, and one-click deployment.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|