@adminforth/mcp 1.1.2 → 1.2.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.
Files changed (47) hide show
  1. package/README.md +2 -2
  2. package/dist/apiTools.d.ts +6 -11
  3. package/dist/apiTools.js +38 -40
  4. package/dist/authSecretStore.d.ts +10 -12
  5. package/dist/authSecretStore.js +42 -115
  6. package/dist/clientInfo.js +1 -1
  7. package/dist/custom/McpSettings.vue +3 -13
  8. package/dist/endpoints/mcp.d.ts +2 -0
  9. package/dist/endpoints/mcp.js +24 -0
  10. package/dist/endpoints/oAuth.d.ts +3 -0
  11. package/dist/{oauthEndpoints.js → endpoints/oAuth.js} +15 -34
  12. package/dist/endpoints/settings.d.ts +3 -0
  13. package/dist/endpoints/settings.js +44 -0
  14. package/dist/endpoints/tools.d.ts +2 -0
  15. package/dist/endpoints/tools.js +19 -0
  16. package/dist/index.d.ts +1 -4
  17. package/dist/index.js +25 -172
  18. package/dist/mcpProtocol.d.ts +9 -22
  19. package/dist/mcpProtocol.js +23 -118
  20. package/dist/{oauth.d.ts → oauth/authorizationServer.d.ts} +3 -13
  21. package/dist/{oauth.js → oauth/authorizationServer.js} +4 -17
  22. package/dist/{oauthClientMetadata.d.ts → oauth/clientMetadata.d.ts} +1 -5
  23. package/dist/{oauthClientMetadata.js → oauth/clientMetadata.js} +8 -28
  24. package/dist/oauth/redirectUris.d.ts +4 -0
  25. package/dist/oauth/redirectUris.js +18 -0
  26. package/dist/oauth/setup.d.ts +6 -0
  27. package/dist/oauth/setup.js +25 -0
  28. package/dist/repositories/authSecret.d.ts +31 -0
  29. package/dist/repositories/authSecret.js +86 -0
  30. package/dist/requestExtra.d.ts +2 -0
  31. package/dist/requestExtra.js +10 -0
  32. package/dist/schemas/oAuth.d.ts +13 -0
  33. package/dist/schemas/oAuth.js +12 -0
  34. package/dist/schemas/settings.d.ts +10 -0
  35. package/dist/schemas/settings.js +8 -0
  36. package/dist/schemas/tools.d.ts +7 -0
  37. package/dist/schemas/tools.js +7 -0
  38. package/dist/skills.d.ts +2 -2
  39. package/dist/types.d.ts +3 -10
  40. package/dist/urls.d.ts +9 -5
  41. package/dist/urls.js +9 -1
  42. package/package.json +9 -4
  43. package/dist/oauthEndpoints.d.ts +0 -3
  44. /package/dist/{inputSchema.d.ts → compactInputSchema.d.ts} +0 -0
  45. /package/dist/{inputSchema.js → compactInputSchema.js} +0 -0
  46. /package/dist/{oauthError.d.ts → oauth/errors.d.ts} +0 -0
  47. /package/dist/{oauthError.js → oauth/errors.js} +0 -0
@@ -2,12 +2,11 @@ import dns from 'node:dns';
2
2
  import https from 'node:https';
3
3
  import net from 'node:net';
4
4
  import { logger } from 'adminforth';
5
- import { OAuthError } from './oauthError.js';
5
+ import { clientMetadataDocumentSchema } from '../schemas/oAuth.js';
6
+ import { OAuthError } from './errors.js';
6
7
  const MAX_DOCUMENT_BYTES = 5 * 1024;
7
8
  const FETCH_TIMEOUT_MS = 5000;
8
- // client_name is shown on the consent page and stored as the connection name.
9
9
  const MAX_CLIENT_NAME_LENGTH = 100;
10
- const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
11
10
  const specialPurposeAddresses = new net.BlockList();
12
11
  [
13
12
  ['0.0.0.0', 8], ['10.0.0.0', 8], ['100.64.0.0', 10], ['127.0.0.0', 8], ['169.254.0.0', 16],
@@ -57,23 +56,6 @@ export function isClientIdUrl(clientId) {
57
56
  && !net.isIP(url.hostname)
58
57
  && !url.hostname.startsWith('[');
59
58
  }
60
- export function isLoopbackUrl(url) {
61
- return url.protocol === 'http:' && LOOPBACK_HOSTS.has(url.hostname);
62
- }
63
- /** Native clients listen on a random loopback port, so RFC 8252 lets the port differ from the registered one. */
64
- export function redirectUriMatches(requested, registered) {
65
- const registeredUrl = new URL(registered);
66
- if (!isLoopbackUrl(registeredUrl))
67
- return requested.href === registeredUrl.href;
68
- return requested.protocol === registeredUrl.protocol
69
- && requested.hostname === registeredUrl.hostname
70
- && requested.pathname === registeredUrl.pathname
71
- && requested.search === registeredUrl.search
72
- && requested.hash === registeredUrl.hash;
73
- }
74
- export function isAllowedRedirectUri(redirectUri) {
75
- return redirectUri.protocol === 'https:' || isLoopbackUrl(redirectUri);
76
- }
77
59
  function fetchJson(url) {
78
60
  return new Promise((resolve, reject) => {
79
61
  const request = https.get(url, {
@@ -116,9 +98,9 @@ function fetchJson(url) {
116
98
  * that passed isClientIdUrl.
117
99
  */
118
100
  export async function fetchClientMetadata(clientId) {
119
- let document;
101
+ let fetched;
120
102
  try {
121
- document = await fetchJson(clientId);
103
+ fetched = await fetchJson(clientId);
122
104
  }
123
105
  catch (error) {
124
106
  // The reason stays in the log: resolved addresses, HTTP statuses and parse errors quoting the response
@@ -126,13 +108,11 @@ export async function fetchClientMetadata(clientId) {
126
108
  logger.warn(`AdminForthMcpPlugin: failed to fetch client metadata document ${clientId}: ${error.message}`);
127
109
  throw new OAuthError('invalid_client', 'Could not fetch the client metadata document');
128
110
  }
129
- if (typeof document?.client_name !== 'string'
130
- || !Array.isArray(document.redirect_uris)
131
- || !document.redirect_uris.length
132
- || !document.redirect_uris.every((uri) => typeof uri === 'string' && URL.canParse(uri))
133
- || document.token_endpoint_auth_method !== 'none') {
134
- throw new OAuthError('invalid_client', 'Client metadata document must have client_name, redirect_uris and token_endpoint_auth_method "none"');
111
+ const parsed = clientMetadataDocumentSchema.safeParse(fetched);
112
+ if (!parsed.success) {
113
+ throw new OAuthError('invalid_client', 'Client metadata document must have client_id, client_name, redirect_uris and token_endpoint_auth_method "none"');
135
114
  }
115
+ const document = parsed.data;
136
116
  if (document.client_id !== clientId) {
137
117
  throw new OAuthError('invalid_client', 'client_id in the client metadata document does not match its URL');
138
118
  }
@@ -0,0 +1,4 @@
1
+ export declare function isLoopbackUrl(url: URL): boolean;
2
+ /** Native clients listen on a random loopback port, so RFC 8252 lets the port differ from the registered one. */
3
+ export declare function redirectUriMatches(requested: URL, registered: string): boolean;
4
+ export declare function isAllowedRedirectUri(redirectUri: URL): boolean;
@@ -0,0 +1,18 @@
1
+ const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
2
+ export function isLoopbackUrl(url) {
3
+ return url.protocol === 'http:' && LOOPBACK_HOSTS.has(url.hostname);
4
+ }
5
+ /** Native clients listen on a random loopback port, so RFC 8252 lets the port differ from the registered one. */
6
+ export function redirectUriMatches(requested, registered) {
7
+ const registeredUrl = new URL(registered);
8
+ if (!isLoopbackUrl(registeredUrl))
9
+ return requested.href === registeredUrl.href;
10
+ return requested.protocol === registeredUrl.protocol
11
+ && requested.hostname === registeredUrl.hostname
12
+ && requested.pathname === registeredUrl.pathname
13
+ && requested.search === registeredUrl.search
14
+ && requested.hash === registeredUrl.hash;
15
+ }
16
+ export function isAllowedRedirectUri(redirectUri) {
17
+ return redirectUri.protocol === 'https:' || isLoopbackUrl(redirectUri);
18
+ }
@@ -0,0 +1,6 @@
1
+ import { type IAdminForth } from 'adminforth';
2
+ import type { McpAuthSecretStore } from '../authSecretStore.js';
3
+ import type { PluginOptions } from '../types.js';
4
+ import { type McpUrls } from '../urls.js';
5
+ import { McpOAuth } from './authorizationServer.js';
6
+ export declare function setupOAuth(adminforth: IAdminForth, authSecretStore: McpAuthSecretStore, urls: McpUrls, options: PluginOptions, consentPageFile: string): McpOAuth;
@@ -0,0 +1,25 @@
1
+ import { logger } from 'adminforth';
2
+ import { CONSENT_PAGE_PATH } from '../urls.js';
3
+ import { McpOAuth } from './authorizationServer.js';
4
+ import { isLoopbackUrl } from './redirectUris.js';
5
+ export function setupOAuth(adminforth, authSecretStore, urls, options, consentPageFile) {
6
+ // OAuth sends codes and tokens through these endpoints, so they must be served over TLS (RFC 6749 §3.1);
7
+ // plain http is left for a local admin panel.
8
+ const origin = new URL(urls.adminPanelUrl);
9
+ if (origin.protocol !== 'https:' && !isLoopbackUrl(origin)) {
10
+ throw new Error(`AdminForthMcpPlugin: adminPanelOrigin must use https with OAuth sign-in, got "${origin.origin}"; `
11
+ + 'http is allowed only for localhost and 127.0.0.1');
12
+ }
13
+ adminforth.config.customization.customPages.push({
14
+ path: CONSENT_PAGE_PATH,
15
+ component: {
16
+ file: consentPageFile,
17
+ meta: { sidebarAndHeader: 'none' },
18
+ },
19
+ });
20
+ const isProduction = process.env.NODE_ENV === 'production';
21
+ if (isProduction && options.devOAuthClients?.length) {
22
+ logger.warn('AdminForthMcpPlugin: devOAuthClients are ignored because NODE_ENV is "production"');
23
+ }
24
+ return new McpOAuth(adminforth, authSecretStore, urls, isProduction ? [] : options.devOAuthClients, options.readOnly);
25
+ }
@@ -0,0 +1,31 @@
1
+ import { type AdminUser, type CreateResourceRecordResult, type DeleteResourceRecordResult, type HttpExtra, type IAdminForth } from 'adminforth';
2
+ import type { McpAuthSecretResourceOptions, McpClientInfo } from '../types.js';
3
+ export interface AuthSecret {
4
+ id: string;
5
+ name: string;
6
+ secretHash: string;
7
+ userId: string;
8
+ createdAt: string;
9
+ lastUsedAt: string | null;
10
+ lastUsedByAgent: McpClientInfo | null;
11
+ readOnly: boolean;
12
+ oauthClientId: string | null;
13
+ }
14
+ export type NewAuthSecret = Pick<AuthSecret, 'id' | 'name' | 'secretHash' | 'userId' | 'readOnly' | 'oauthClientId'>;
15
+ export declare class AuthSecretRepository {
16
+ private readonly adminforth;
17
+ private readonly fields;
18
+ private readonly resource;
19
+ constructor(adminforth: IAdminForth, fields: McpAuthSecretResourceOptions);
20
+ listByUser(userId: string): Promise<AuthSecret[]>;
21
+ findById(id: string): Promise<AuthSecret | null>;
22
+ findBySecretHash(secretHash: string): Promise<AuthSecret | null>;
23
+ create(authSecret: NewAuthSecret, adminUser: AdminUser, extra: HttpExtra): Promise<CreateResourceRecordResult>;
24
+ deleteByIdAndUser(id: string, adminUser: AdminUser, extra: HttpExtra): Promise<DeleteResourceRecordResult | null>;
25
+ deleteById(id: string): Promise<void>;
26
+ updateSecretHash(id: string, secretHash: string): Promise<void>;
27
+ updateUsage(id: string, client?: McpClientInfo): Promise<void>;
28
+ private records;
29
+ private findOne;
30
+ private toAuthSecret;
31
+ }
@@ -0,0 +1,86 @@
1
+ import { Filters, Sorts, } from 'adminforth';
2
+ export class AuthSecretRepository {
3
+ adminforth;
4
+ fields;
5
+ resource;
6
+ constructor(adminforth, fields) {
7
+ this.adminforth = adminforth;
8
+ this.fields = fields;
9
+ this.resource = adminforth.config.resources.find((resource) => resource.resourceId === fields.resourceId);
10
+ }
11
+ async listByUser(userId) {
12
+ const records = await this.records().list(Filters.EQ(this.fields.userIdField, userId), undefined, undefined, Sorts.DESC(this.fields.createdAtField));
13
+ return records.map((record) => this.toAuthSecret(record));
14
+ }
15
+ findById(id) {
16
+ return this.findOne(Filters.EQ(this.fields.idField, id));
17
+ }
18
+ findBySecretHash(secretHash) {
19
+ return this.findOne(Filters.EQ(this.fields.secretHashField, secretHash));
20
+ }
21
+ create(authSecret, adminUser, extra) {
22
+ const fields = this.fields;
23
+ return this.adminforth.createResourceRecord({
24
+ resource: this.resource,
25
+ adminUser,
26
+ extra,
27
+ record: {
28
+ [fields.idField]: authSecret.id,
29
+ [fields.nameField]: authSecret.name,
30
+ [fields.secretHashField]: authSecret.secretHash,
31
+ [fields.userIdField]: authSecret.userId,
32
+ [fields.createdAtField]: new Date().toISOString(),
33
+ [fields.lastUsedAtField]: null,
34
+ [fields.lastUsedByAgentField]: null,
35
+ [fields.readOnlyField]: authSecret.readOnly,
36
+ [fields.oauthClientIdField]: authSecret.oauthClientId,
37
+ },
38
+ });
39
+ }
40
+ async deleteByIdAndUser(id, adminUser, extra) {
41
+ const record = await this.records().get(Filters.AND(Filters.EQ(this.fields.idField, id), Filters.EQ(this.fields.userIdField, adminUser.pk)));
42
+ if (!record)
43
+ return null;
44
+ return this.adminforth.deleteResourceRecord({
45
+ resource: this.resource,
46
+ record,
47
+ recordId: id,
48
+ adminUser,
49
+ extra,
50
+ });
51
+ }
52
+ async deleteById(id) {
53
+ await this.records().delete(id);
54
+ }
55
+ async updateSecretHash(id, secretHash) {
56
+ await this.records().update(id, { [this.fields.secretHashField]: secretHash });
57
+ }
58
+ async updateUsage(id, client) {
59
+ await this.records().update(id, {
60
+ [this.fields.lastUsedAtField]: new Date().toISOString(),
61
+ ...(client && { [this.fields.lastUsedByAgentField]: JSON.stringify(client) }),
62
+ });
63
+ }
64
+ records() {
65
+ return this.adminforth.resource(this.fields.resourceId);
66
+ }
67
+ async findOne(filter) {
68
+ const record = await this.records().get(filter);
69
+ return record ? this.toAuthSecret(record) : null;
70
+ }
71
+ toAuthSecret(record) {
72
+ const fields = this.fields;
73
+ const lastUsedByAgent = record[fields.lastUsedByAgentField];
74
+ return {
75
+ id: record[fields.idField],
76
+ name: record[fields.nameField],
77
+ secretHash: record[fields.secretHashField],
78
+ userId: record[fields.userIdField],
79
+ createdAt: record[fields.createdAtField],
80
+ lastUsedAt: record[fields.lastUsedAtField],
81
+ lastUsedByAgent: lastUsedByAgent && JSON.parse(lastUsedByAgent),
82
+ readOnly: record[fields.readOnlyField],
83
+ oauthClientId: record[fields.oauthClientIdField],
84
+ };
85
+ }
86
+ }
@@ -0,0 +1,2 @@
1
+ import type { HttpExtra, IAdminForthEndpointHandlerInput } from 'adminforth';
2
+ export declare function requestExtra(input: IAdminForthEndpointHandlerInput): HttpExtra;
@@ -0,0 +1,10 @@
1
+ export function requestExtra(input) {
2
+ return {
3
+ body: input.body,
4
+ query: input.query,
5
+ headers: input.headers,
6
+ cookies: input.cookies,
7
+ requestUrl: input.requestUrl,
8
+ response: input.response,
9
+ };
10
+ }
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ export declare const resolveAuthorizationBodySchema: z.ZodObject<{
3
+ request: z.ZodString;
4
+ approved: z.ZodBoolean;
5
+ readOnly: z.ZodBoolean;
6
+ }, z.core.$strip>;
7
+ export declare const clientMetadataDocumentSchema: z.ZodObject<{
8
+ client_id: z.ZodString;
9
+ client_name: z.ZodString;
10
+ redirect_uris: z.ZodArray<z.ZodString>;
11
+ token_endpoint_auth_method: z.ZodLiteral<"none">;
12
+ }, z.core.$strip>;
13
+ export type ResolveAuthorizationBody = z.infer<typeof resolveAuthorizationBodySchema>;
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ export const resolveAuthorizationBodySchema = z.object({
3
+ request: z.string(),
4
+ approved: z.boolean(),
5
+ readOnly: z.boolean(),
6
+ });
7
+ export const clientMetadataDocumentSchema = z.object({
8
+ client_id: z.string(),
9
+ client_name: z.string(),
10
+ redirect_uris: z.array(z.string().refine((uri) => URL.canParse(uri))).min(1),
11
+ token_endpoint_auth_method: z.literal('none'),
12
+ });
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ export declare const createAuthSecretBodySchema: z.ZodObject<{
3
+ name: z.ZodString;
4
+ readOnly: z.ZodBoolean;
5
+ }, z.core.$strip>;
6
+ export declare const revokeAuthSecretBodySchema: z.ZodObject<{
7
+ id: z.ZodString;
8
+ }, z.core.$strip>;
9
+ export type CreateAuthSecretBody = z.infer<typeof createAuthSecretBodySchema>;
10
+ export type RevokeAuthSecretBody = z.infer<typeof revokeAuthSecretBodySchema>;
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ export const createAuthSecretBodySchema = z.object({
3
+ name: z.string().min(1),
4
+ readOnly: z.boolean(),
5
+ });
6
+ export const revokeAuthSecretBodySchema = z.object({
7
+ id: z.string(),
8
+ });
@@ -0,0 +1,7 @@
1
+ import { z } from 'zod';
2
+ export declare const resourcesListResponseSchema: z.ZodObject<{
3
+ resources: z.ZodArray<z.ZodObject<{
4
+ resourceId: z.ZodString;
5
+ label: z.ZodString;
6
+ }, z.core.$strip>>;
7
+ }, z.core.$strip>;
@@ -0,0 +1,7 @@
1
+ import { z } from 'zod';
2
+ export const resourcesListResponseSchema = z.object({
3
+ resources: z.array(z.object({
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
+ label: z.string().describe('Human readable resource name, translated for the current user.'),
6
+ })).describe('Every resource of the admin panel. Access is checked when a resource is used, so the user may still be forbidden to list, show, create, edit or delete records of a listed resource.'),
7
+ });
package/dist/skills.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { McpToolDefinition } from './apiTools.js';
1
+ import type { Tool } from '@modelcontextprotocol/server';
2
2
  export declare const FETCH_SKILL_TOOL_NAME = "fetch_skill";
3
3
  type TemplateValues = Record<string, string | number>;
4
4
  /**
@@ -10,7 +10,7 @@ export declare class McpSkills {
10
10
  private readonly skills;
11
11
  constructor(customFolderPath: string, values: TemplateValues);
12
12
  serverInstructions(): string;
13
- toolDefinition(): McpToolDefinition;
13
+ toolDefinition(): Tool;
14
14
  call(args: Record<string, unknown> | undefined): {
15
15
  output: unknown;
16
16
  isError: boolean;
package/dist/types.d.ts CHANGED
@@ -9,11 +9,7 @@ export interface McpAuthSecretResourceOptions {
9
9
  lastUsedAtField: string;
10
10
  lastUsedByAgentField: string;
11
11
  readOnlyField: string;
12
- /**
13
- * Client ID Metadata Document URL of the MCP client connected through OAuth; empty for personal auth secrets.
14
- * Setting it enables OAuth sign-in, which also needs adminPanelOrigin. Without it only auth secrets work.
15
- */
16
- oauthClientIdField?: string;
12
+ oauthClientIdField: string;
17
13
  }
18
14
  /** An MCP client allowed to connect through OAuth, as its Client ID Metadata Document describes it. */
19
15
  export interface McpOAuthClient {
@@ -22,9 +18,7 @@ export interface McpOAuthClient {
22
18
  redirectUris: string[];
23
19
  }
24
20
  export interface McpPageSize {
25
- /** Rows get_resource_data returns when the client does not pass limit. */
26
21
  default: number;
27
- /** Most rows get_resource_data returns per call; larger limits are capped. */
28
22
  max: number;
29
23
  }
30
24
  export interface PluginOptions extends PluginsCommonOptions {
@@ -32,10 +26,9 @@ export interface PluginOptions extends PluginsCommonOptions {
32
26
  /**
33
27
  * Public origin of this AdminForth installation, without the AdminForth baseUrl path, e.g. https://example.com.
34
28
  * The MCP server URL shown on the settings page, the OAuth issuer and resource URLs are built from it, so it must
35
- * be the origin MCP clients connect to. Required with OAuth sign-in; without it the settings page shows the MCP URL
36
- * of the address it was opened at.
29
+ * be the origin MCP clients connect to.
37
30
  */
38
- adminPanelOrigin?: string;
31
+ adminPanelOrigin: string;
39
32
  /**
40
33
  * MCP clients resolved from this list instead of fetching their Client ID Metadata Document, so OAuth can be
41
34
  * tried locally without a public https URL. Ignored when NODE_ENV is "production": there every client_id is
package/dist/urls.d.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  export declare const MCP_PATH = "/mcp";
2
- /** Path prefix of AdminForth API endpoints, e.g. `/admin/adminapi/v1`. */
2
+ export declare const CONSENT_PAGE_PATH = "/mcp-authorize";
3
+ export declare const OAUTH_PATHS: {
4
+ protectedResourceMetadata: string;
5
+ authorizationServerMetadata: string;
6
+ jwks: string;
7
+ authorize: string;
8
+ token: string;
9
+ authorization: string;
10
+ };
3
11
  export declare function adminApiPrefix(baseUrl: string): string;
4
- /**
5
- * Public URLs of the plugin, built once from adminPanelOrigin: the settings page, OAuth metadata and the MCP server
6
- * info must all name the same URL, because OAuth accepts tokens only for the exact MCP resource URL.
7
- */
8
12
  export interface McpUrls {
9
13
  apiUrl: string;
10
14
  mcpUrl: string;
package/dist/urls.js CHANGED
@@ -1,5 +1,13 @@
1
1
  export const MCP_PATH = '/mcp';
2
- /** Path prefix of AdminForth API endpoints, e.g. `/admin/adminapi/v1`. */
2
+ export const CONSENT_PAGE_PATH = '/mcp-authorize';
3
+ export const OAUTH_PATHS = {
4
+ protectedResourceMetadata: `${MCP_PATH}/oauth-protected-resource`,
5
+ authorizationServerMetadata: `${MCP_PATH}/.well-known/openid-configuration`,
6
+ jwks: `${MCP_PATH}/oauth/jwks`,
7
+ authorize: `${MCP_PATH}/oauth/authorize`,
8
+ token: `${MCP_PATH}/oauth/token`,
9
+ authorization: `${MCP_PATH}/oauth/authorization`,
10
+ };
3
11
  export function adminApiPrefix(baseUrl) {
4
12
  return `${baseUrl}/adminapi/v1`;
5
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/mcp",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Expose AdminForth APIs as an authenticated MCP server",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "scripts": {
28
28
  "build": "tsc && rsync -av --exclude 'node_modules' custom dist/",
29
- "test": "pnpm build && node --test tests/*.test.mjs"
29
+ "test": "rm -rf dist && pnpm build && node --test tests/*.test.mjs"
30
30
  },
31
31
  "keywords": [
32
32
  "adminforth",
@@ -40,14 +40,17 @@
40
40
  "author": "DevForth (https://devforth.io)",
41
41
  "license": "MIT",
42
42
  "devDependencies": {
43
+ "@types/express": "^5.0.6",
43
44
  "@types/node": "latest",
44
45
  "adminforth": "^3.32.0",
46
+ "express": "^5.2.1",
45
47
  "semantic-release": "^24.2.1",
46
48
  "semantic-release-slack-bot": "^4.0.2",
47
49
  "typescript": "^5.7.3"
48
50
  },
49
51
  "peerDependencies": {
50
- "adminforth": "^3.32.0"
52
+ "adminforth": "^3.32.0",
53
+ "express": "^4.21.0 || ^5.0.0"
51
54
  },
52
55
  "release": {
53
56
  "plugins": [
@@ -75,6 +78,8 @@
75
78
  ]
76
79
  },
77
80
  "dependencies": {
78
- "yaml": "^2.9.1"
81
+ "@modelcontextprotocol/server": "^2.3.1",
82
+ "yaml": "^2.9.1",
83
+ "zod": "^4.6.5"
79
84
  }
80
85
  }
@@ -1,3 +0,0 @@
1
- import { type IHttpServer } from 'adminforth';
2
- import { type McpOAuth } from './oauth.js';
3
- export declare function setupOAuthEndpoints(server: IHttpServer, oauth: McpOAuth, apiPrefix: string): void;
File without changes
File without changes
File without changes