Skip to content

my text AdminForth/1966/-adminforth-mcp-optimization-r - #2

Open
srelon wants to merge 2 commits into
mainfrom
feature/AdminForth/1970/please-introduce-same-oauth-au
Open

srelon wants to merge 2 commits into
mainfrom
feature/AdminForth/1970/please-introduce-same-oauth-au

Conversation

@srelon

@srelon srelon commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

No description provided.

Comment thread authSecretStore.ts
}

/** Swaps a refresh token for a new one, so a leaked refresh token stops working after its next use. */
async rotateRefreshToken(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if attacker steals refresh and will use it before legit user, legit user will be kicked and attacker will be left in session. OAuth 2.1 strongly recommends to revoke full grant on double refresh reuse

Comment thread oauthEndpoints.ts
path: OAUTH_PATHS.authorize,
noAuth: true,
handler: async (input) => oauthResponse(input, async () => {
const consentPageUrl = await oauth.authorize(input.query);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method exposes reflector proxy possibility because does outgoing cache without any cache/noAuth
Ideas?

Comment thread mcpProtocol.ts
// The JSON round trip drops functions and undefined values that handler responses may carry, which YAML cannot serialize.
function serializeToolOutput(output: unknown): string {
return typeof output === 'string' ? output : JSON.stringify(output, null, 2);
return typeof output === 'string' ? output : YAML.stringify(JSON.parse(JSON.stringify(output)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srelon what will be here if handler returns undefined? totally possible =)

Comment thread index.ts

const BEARER_SECRET_RE = /^Bearer (afmcp_[A-Za-z0-9_-]+)$/i;
const BEARER_TOKEN_RE = /^Bearer (\S+)$/i;
const MCP_PATH = '/mcp';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srelon this is SSOT violation (partially was already here but now with OAuth can lead to bad consequences) better return mcpUrl somewhere e.g. GET /mcp/auth-secrets

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivictbor you can write in more detail? I'm not sure that I understood correctly

Comment thread oauth.ts
redirectUri: params.redirect_uri,
codeChallenge: params.code_challenge,
state: typeof query.state === 'string' ? query.state : undefined,
loopbackRedirect: isLoopbackRedirectUri(params.redirect_uri),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srelon I think u parse redfirect url 4 times, but it can be done once - minor but anyway, as concept of wasting CPU loops - in terms of code perfection better do it once and reuse

Comment thread apiTools.ts
* Returns a copy of `target` without the value at `pathParts`. Only objects along the path are copied,
* because handler responses may share nested objects with the AdminForth config.
*/
function omitPath(target: unknown, pathParts: string[]): unknown {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nod bad but I would say it is YAGNI violation and more like upfront optimisation

omitPath/omitPaths plus GET_RESOURCE_FRONTEND_ONLY_PATHS add a small path DSL (dot-separated keys, a [] suffix that maps over arrays, recursive copy-on-write). It is used in exactly one place, for 4 hardcoded paths, and the response shape is fully known from AdminForthResourceFrontend. Nothing else needs path-based omission, so the abstraction is speculative.

It makes it a little bit hardread - reader has to understand the DSL to find out what actually gets removed, and the unknown in/out drops the typing we already have.

Plain destructuring does the same thing, is typed, and stays copy-on-write, so the response still never mutates the shared config objects.

kinda

// Drops frontend-only parts of the get_resource response that are useless for MCP clients and only waste their context.
// Copies instead of deleting: the response shares nested objects with the AdminForth config.
function withoutFrontendOnlyFields({ resource }: GetResourceOutput): GetResourceOutput {
  const { pageInjections, ...options } = resource.options;
  return {
    resource: {
      ...resource,
      columns: resource.columns.map(({ filterOptions, components, ...column }) => column),
      options: {
        ...options,
        actions: options.actions?.map(({ customComponent, ...action }) => action),
      },
    },
  };
}

project: (output, { detailed }) => (
  detailed
    ? withoutFrontendOnlyFields(output as GetResourceOutput)
    : essentialResource(output as GetResourceOutput)
),

well readable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants