@adminforth/mcp 1.4.0 → 1.4.2
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/dist/apiTools.d.ts +3 -2
- package/dist/apiTools.js +6 -9
- package/dist/custom/McpConfirm.vue +11 -12
- package/dist/endpoints/tools.d.ts +2 -1
- package/dist/endpoints/tools.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -2
- package/dist/resourceVisibility.d.ts +9 -0
- package/dist/resourceVisibility.js +27 -0
- package/dist/schemas/tools.js +1 -1
- package/package.json +1 -1
package/dist/apiTools.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Tool } from '@modelcontextprotocol/server';
|
|
2
2
|
import type { McpAdminPanelConfirmation, McpConnection } from './adminPanelConfirmation.js';
|
|
3
|
+
import type { McpResourceVisibility } from './resourceVisibility.js';
|
|
3
4
|
import type { McpPageSize } from './types.js';
|
|
4
5
|
import type { AdminUser, IAdminForth } from 'adminforth';
|
|
5
6
|
export declare class AdminForthApiTools {
|
|
6
7
|
private readonly adminforth;
|
|
7
|
-
private readonly
|
|
8
|
+
private readonly resourceVisibility;
|
|
8
9
|
private readonly toolTimeoutMs;
|
|
9
10
|
private readonly adminPanelConfirmation;
|
|
10
|
-
constructor(adminforth: IAdminForth,
|
|
11
|
+
constructor(adminforth: IAdminForth, resourceVisibility: McpResourceVisibility, pageSize: McpPageSize, toolTimeoutMs: number, adminPanelConfirmation: McpAdminPanelConfirmation | undefined);
|
|
11
12
|
private readonly toolOverrides;
|
|
12
13
|
private fullCatalog?;
|
|
13
14
|
private readOnlyCatalog?;
|
package/dist/apiTools.js
CHANGED
|
@@ -159,12 +159,12 @@ function createDirectResponse() {
|
|
|
159
159
|
}
|
|
160
160
|
export class AdminForthApiTools {
|
|
161
161
|
adminforth;
|
|
162
|
-
|
|
162
|
+
resourceVisibility;
|
|
163
163
|
toolTimeoutMs;
|
|
164
164
|
adminPanelConfirmation;
|
|
165
|
-
constructor(adminforth,
|
|
165
|
+
constructor(adminforth, resourceVisibility, pageSize, toolTimeoutMs, adminPanelConfirmation) {
|
|
166
166
|
this.adminforth = adminforth;
|
|
167
|
-
this.
|
|
167
|
+
this.resourceVisibility = resourceVisibility;
|
|
168
168
|
this.toolTimeoutMs = toolTimeoutMs;
|
|
169
169
|
this.adminPanelConfirmation = adminPanelConfirmation;
|
|
170
170
|
this.toolOverrides = createToolOverrides(pageSize, adminforth);
|
|
@@ -239,12 +239,9 @@ export class AdminForthApiTools {
|
|
|
239
239
|
if (!schema) {
|
|
240
240
|
return { output: { error: `Unknown tool: ${params.name}` }, isError: true };
|
|
241
241
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
output: { error: 'This resource is not available through MCP.' },
|
|
246
|
-
isError: true,
|
|
247
|
-
};
|
|
242
|
+
const resourceId = params.arguments?.resourceId;
|
|
243
|
+
if (typeof resourceId === 'string' && !await this.resourceVisibility.isVisible(params.adminUser, resourceId)) {
|
|
244
|
+
return { output: { error: `Resource ${resourceId} not found` }, isError: true };
|
|
248
245
|
}
|
|
249
246
|
const override = this.toolOverrides[params.name];
|
|
250
247
|
const args = { ...override?.defaultArguments, ...params.arguments };
|
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="af-mcp-confirm flex min-h-screen items-center justify-center bg-gray-100 p-4 dark:bg-gray-800">
|
|
3
|
-
<div class="w-full max-w-lg rounded-lg bg-white p-6 shadow dark:bg-gray-700">
|
|
3
|
+
<div v-if="resultMessage || error" class="w-full max-w-lg rounded-lg bg-white p-6 shadow dark:bg-gray-700">
|
|
4
4
|
<template v-if="pending">
|
|
5
5
|
<h1 class="text-xl font-medium text-gray-900 dark:text-white">{{ title }}</h1>
|
|
6
6
|
<p class="mt-3 text-sm text-gray-600 dark:text-gray-300">
|
|
7
7
|
<b>{{ pending.connectionName }}</b>
|
|
8
8
|
{{ $t('asks to run this action on your behalf. | asks to run these actions on your behalf.', pending.actions.length) }}
|
|
9
9
|
</p>
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
</ul>
|
|
17
|
-
</template>
|
|
18
|
-
<Spinner v-else class="mx-auto mt-4 h-8 w-8" />
|
|
10
|
+
<p class="mt-4 text-sm font-medium" :class="failed ? 'text-red-700 dark:text-red-300' : 'text-green-700 dark:text-green-300'">
|
|
11
|
+
{{ resultMessage }}
|
|
12
|
+
</p>
|
|
13
|
+
<ul v-if="failedErrors.length" class="mt-2 list-disc space-y-1 pl-5 text-sm text-red-700 dark:text-red-300">
|
|
14
|
+
<li v-for="(failedError, i) in failedErrors" :key="i">{{ failedError }}</li>
|
|
15
|
+
</ul>
|
|
19
16
|
</template>
|
|
20
|
-
<template v-else
|
|
17
|
+
<template v-else>
|
|
21
18
|
<h1 class="text-xl font-medium text-gray-900 dark:text-white">{{ $t('Cannot run the action') }}</h1>
|
|
22
19
|
<p class="mt-3 text-sm text-gray-600 dark:text-gray-300">{{ error }}</p>
|
|
23
20
|
</template>
|
|
24
|
-
<Spinner v-else class="mx-auto h-8 w-8" />
|
|
25
21
|
</div>
|
|
22
|
+
<Spinner v-else-if="running" class="h-8 w-8" />
|
|
26
23
|
</div>
|
|
27
24
|
</template>
|
|
28
25
|
|
|
@@ -49,6 +46,7 @@ const route = useRoute();
|
|
|
49
46
|
const pending = ref<PendingConfirmation | null>(null);
|
|
50
47
|
const error = ref('');
|
|
51
48
|
const failed = ref(false);
|
|
49
|
+
const running = ref(false);
|
|
52
50
|
const resultMessage = ref('');
|
|
53
51
|
const failedErrors = ref<string[]>([]);
|
|
54
52
|
const ids = computed(() => pending.value!.actions.map((action) => action.id));
|
|
@@ -90,6 +88,7 @@ async function confirm() {
|
|
|
90
88
|
}
|
|
91
89
|
return;
|
|
92
90
|
}
|
|
91
|
+
running.value = true;
|
|
93
92
|
const response = await callAdminForthApi({ method: 'POST', path: '/mcp/confirmation/run', body: { ids: ids.value, verificationResult } });
|
|
94
93
|
if (!response || response.error) {
|
|
95
94
|
failed.value = true;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { IAdminForth, IHttpServer } from 'adminforth';
|
|
2
|
-
|
|
2
|
+
import type { McpResourceVisibility } from '../resourceVisibility.js';
|
|
3
|
+
export declare function registerToolEndpoints(server: IHttpServer, adminforth: IAdminForth, resourceVisibility: McpResourceVisibility): void;
|
package/dist/endpoints/tools.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { resourcesListResponseSchema } from '../schemas/tools.js';
|
|
2
|
-
export function registerToolEndpoints(server, adminforth) {
|
|
2
|
+
export function registerToolEndpoints(server, adminforth, resourceVisibility) {
|
|
3
3
|
server.endpoint({
|
|
4
4
|
method: 'GET',
|
|
5
5
|
path: '/get_resources_list',
|
|
6
|
-
description: 'Lists the resourceId and label of every resource (data table)
|
|
6
|
+
description: 'Lists the resourceId and label of every resource (data table) whose records the user may list or show. Call this first to discover valid resourceId values before using get_resource, get_resource_data, aggregate, create_record, update_record, delete_record or other resource tools.',
|
|
7
7
|
agent: {
|
|
8
8
|
onlyReadsData: true,
|
|
9
9
|
},
|
|
10
10
|
response_schema: resourcesListResponseSchema,
|
|
11
|
-
handler: async ({ tr }) => {
|
|
12
|
-
const resources = await Promise.all(
|
|
11
|
+
handler: async ({ adminUser, tr }) => {
|
|
12
|
+
const resources = await Promise.all((await resourceVisibility.visibleResources(adminUser)).map(async (resource) => ({
|
|
13
13
|
resourceId: resource.resourceId,
|
|
14
14
|
label: await tr(resource.label, `resource.${resource.resourceId}`),
|
|
15
15
|
})));
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { registerToolEndpoints } from './endpoints/tools.js';
|
|
|
12
12
|
import { AuthSecretRepository } from './repositories/authSecret.js';
|
|
13
13
|
import { setupOAuth } from './oauth/setup.js';
|
|
14
14
|
import { requestExtra } from './requestExtra.js';
|
|
15
|
+
import { McpResourceVisibility } from './resourceVisibility.js';
|
|
15
16
|
import { createServerReadOnly } from './serverReadOnly.js';
|
|
16
17
|
import { FETCH_SKILL_TOOL_NAME, McpSkills } from './skills.js';
|
|
17
18
|
import { findContextualTwoFactorsAuth } from './twoFactorContext.js';
|
|
@@ -42,6 +43,7 @@ export default class AdminForthMcpPlugin extends AdminForthPlugin {
|
|
|
42
43
|
authSecretStore;
|
|
43
44
|
oauth;
|
|
44
45
|
apiTools;
|
|
46
|
+
resourceVisibility;
|
|
45
47
|
adminPanelConfirmation;
|
|
46
48
|
skills;
|
|
47
49
|
urls;
|
|
@@ -99,7 +101,8 @@ export default class AdminForthMcpPlugin extends AdminForthPlugin {
|
|
|
99
101
|
default: this.options.pageSize?.default ?? DEFAULT_PAGE_SIZE,
|
|
100
102
|
max: this.options.pageSize?.max ?? MAX_PAGE_SIZE,
|
|
101
103
|
};
|
|
102
|
-
this.
|
|
104
|
+
this.resourceVisibility = new McpResourceVisibility(adminforth, new Set([this.options.authSecretResource.resourceId]));
|
|
105
|
+
this.apiTools = new AdminForthApiTools(adminforth, this.resourceVisibility, pageSize, toolTimeoutMs, this.adminPanelConfirmation);
|
|
103
106
|
this.skills = new McpSkills(this.customFolderPath, {
|
|
104
107
|
'pageSize.default': pageSize.default,
|
|
105
108
|
'pageSize.max': pageSize.max,
|
|
@@ -128,7 +131,7 @@ export default class AdminForthMcpPlugin extends AdminForthPlugin {
|
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
setupEndpoints(server) {
|
|
131
|
-
registerToolEndpoints(server, this.adminforth);
|
|
134
|
+
registerToolEndpoints(server, this.adminforth, this.resourceVisibility);
|
|
132
135
|
registerSettingsEndpoints(server, this.authSecretStore, this.urls.mcpUrl, this.serverReadOnly);
|
|
133
136
|
registerOAuthEndpoints(server, this.oauth, adminApiPrefix(this.adminforth.config.baseUrl));
|
|
134
137
|
if (this.adminPanelConfirmation)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type AdminForthResource, type AdminUser, type IAdminForth } from 'adminforth';
|
|
2
|
+
export declare class McpResourceVisibility {
|
|
3
|
+
private readonly adminforth;
|
|
4
|
+
private readonly hiddenResourceIds;
|
|
5
|
+
constructor(adminforth: IAdminForth, hiddenResourceIds: ReadonlySet<string>);
|
|
6
|
+
visibleResources(adminUser: AdminUser): Promise<AdminForthResource[]>;
|
|
7
|
+
isVisible(adminUser: AdminUser, resourceId: string): Promise<boolean>;
|
|
8
|
+
private canListOrShow;
|
|
9
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ActionCheckSource, interpretResource, } from 'adminforth';
|
|
2
|
+
export class McpResourceVisibility {
|
|
3
|
+
adminforth;
|
|
4
|
+
hiddenResourceIds;
|
|
5
|
+
constructor(adminforth, hiddenResourceIds) {
|
|
6
|
+
this.adminforth = adminforth;
|
|
7
|
+
this.hiddenResourceIds = hiddenResourceIds;
|
|
8
|
+
}
|
|
9
|
+
async visibleResources(adminUser) {
|
|
10
|
+
const resources = this.adminforth.config.resources;
|
|
11
|
+
const visible = await Promise.all(resources.map((resource) => this.canListOrShow(adminUser, resource)));
|
|
12
|
+
return resources.filter((_, index) => visible[index]);
|
|
13
|
+
}
|
|
14
|
+
async isVisible(adminUser, resourceId) {
|
|
15
|
+
const resource = this.adminforth.config.resources.find((candidate) => candidate.resourceId === resourceId);
|
|
16
|
+
return resource !== undefined && this.canListOrShow(adminUser, resource);
|
|
17
|
+
}
|
|
18
|
+
async canListOrShow(adminUser, resource) {
|
|
19
|
+
if (this.hiddenResourceIds.has(resource.resourceId))
|
|
20
|
+
return false;
|
|
21
|
+
const [{ allowedActions: { list } }, { allowedActions: { show } }] = await Promise.all([
|
|
22
|
+
interpretResource(adminUser, resource, {}, ActionCheckSource.ListRequest, this.adminforth),
|
|
23
|
+
interpretResource(adminUser, resource, {}, ActionCheckSource.ShowRequest, this.adminforth),
|
|
24
|
+
]);
|
|
25
|
+
return list === true || show === true;
|
|
26
|
+
}
|
|
27
|
+
}
|
package/dist/schemas/tools.js
CHANGED
|
@@ -3,5 +3,5 @@ export const resourcesListResponseSchema = z.object({
|
|
|
3
3
|
resources: z.array(z.object({
|
|
4
4
|
resourceId: z.string().describe('Resource identifier. Pass it as resourceId to get_resource, get_resource_data, aggregate, create_record, update_record, delete_record and other resource tools. Call get_resource with it to get the columns and allowed actions of the resource.'),
|
|
5
5
|
label: z.string().describe('Human readable resource name, translated for the current user.'),
|
|
6
|
-
})).describe('Every resource
|
|
6
|
+
})).describe('Every resource whose records the user may list or show. Other actions are checked when a resource is used, so the user may still be forbidden to create, edit or delete records of a listed resource.'),
|
|
7
7
|
});
|