agentsclimarketplace

Nfs add mcp

Skill juncoding/nextjs-fullstack-starter/skills/nfs-add-mcp

Claude Code plugin: scaffold and maintain lightweight back-office apps on pure Next.js (App Router) — Server Components for reads, Server Actions for writes, services in src/server/modules/. No tRPC.

Install
npx -y skills add juncoding/nextjs-fullstack-starter --skill nfs-add-mcp

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Add an MCP (Model Context Protocol) entry point to an existing project scaffolded with nextjs-fullstack-starter. Use when the user wants AI clients (Claude Desktop, Cursor) to query the project's data over OAuth, mentions MCP, wants to expose tools to AI, or invokes /nfs-add-mcp. Wires up /api/mcp/route.ts inside an (mcp) route group, the MCP plugin in Better Auth (the project's OAuth provider), the .well-known OAuth discovery endpoints, a tool registry at src/server/mcp/registry.ts, one example tool, and the migration for the three OAuth tables. Requires Better Auth to be wired — if not, prompts to run /nfs-add-auth first.

SKILL.md

8.5 KB, as published. Nobody here has run it

Add an MCP route to an existing scaffold

Use when a project from nextjs-fullstack-starter needs an MCP entry point so AI clients can call services over OAuth.

Pre-flight checks

  1. Refuse if Better Auth isn't wired. MCP uses Better Auth's mcp plugin as its OAuth provider. If src/server/auth/index.ts doesn't exist, tell the user to run /nfs-add-auth first.
  2. Refuse if src/server/mcp/ already exists. Suggest auditing what's there instead.
  3. Refuse if src/app/(mcp)/mcp/route.ts already exists.

Plan

  1. Update src/server/auth/index.ts to add the mcp plugin alongside nextCookies.
  2. Add src/app/(mcp)/mcp/route.ts — the POST handler that consumes the MCP transport, wrapped with withMcpAuth.
  3. Add src/app/.well-known/oauth-authorization-server/route.ts + oauth-protected-resource/route.ts for OAuth discovery.
  4. Add src/server/mcp/registry.ts — central tool registry.
  5. Add src/server/mcp/tools/_example.ts — one example tool that wraps the example service.
  6. Add the three Better Auth OAuth tables (OauthApplication, OauthAccessToken, OauthConsent) to schema.prisma.
  7. Run pnpm prisma migrate dev --name add_mcp_oauth.
  8. Update CLAUDE.md.

File templates

src/server/auth/index.ts (updated)

import "server-only";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { nextCookies } from "better-auth/next-js";
import { mcp } from "better-auth/plugins";
import { db } from "@/server/db/client";
import { env } from "@/env";

export const auth = betterAuth({
  database: prismaAdapter(db, { provider: "postgresql" }),
  baseURL: env.BETTER_AUTH_URL,
  secret: env.BETTER_AUTH_SECRET,
  emailAndPassword: { enabled: true, autoSignIn: true },
  plugins: [
    nextCookies(),
    mcp({
      loginPage: "/login",
    }),
  ],
});

export type Session = typeof auth.$Infer.Session;

src/app/(mcp)/mcp/route.ts

import { withMcpAuth } from "better-auth/plugins";
import { auth } from "@/server/auth";
import { mcpRegistry } from "@/server/mcp/registry";

export const POST = withMcpAuth(auth, async (req, session) => {
  return mcpRegistry.handle(req, { userId: session.user.id });
});

src/server/mcp/registry.ts

import "server-only";
import { exampleSearchTool } from "./tools/_example";

const TOOLS = {
  example_search: exampleSearchTool,
} as const;

type ToolName = keyof typeof TOOLS;

export const mcpRegistry = {
  list() {
    return Object.entries(TOOLS).map(([name, tool]) => ({
      name,
      description: tool.description,
      inputSchema: tool.inputSchema,
    }));
  },

  async handle(req: Request, ctx: { userId: string }) {
    const body = await req.json();
    if (body.method === "tools/list") {
      return Response.json({ tools: this.list() });
    }
    if (body.method === "tools/call") {
      const { name, arguments: args } = body.params;
      const tool = TOOLS[name as ToolName];
      if (!tool) {
        return Response.json({ error: { code: -32601, message: `Unknown tool: ${name}` } });
      }
      const result = await tool.run(ctx.userId, args);
      return Response.json({ content: [{ type: "text", text: JSON.stringify(result) }] });
    }
    return Response.json({ error: { code: -32601, message: "Unsupported method" } });
  },
};

src/server/mcp/tools/_example.ts

import "server-only";
import { z } from "zod";
import { exampleService } from "@/server/modules/_example/_example.service";

const InputSchema = z.object({
  q: z.string().optional(),
  limit: z.number().int().min(1).max(50).optional(),
});

export const exampleSearchTool = {
  description: "Search examples by query string. Returns up to `limit` results.",
  inputSchema: {
    type: "object",
    properties: {
      q: { type: "string", description: "Search query" },
      limit: { type: "number", description: "Max results (default 20)" },
    },
  },
  async run(userId: string, args: unknown) {
    const input = InputSchema.parse(args);
    return exampleService.list(userId, input);
  },
};

The tool wraps the existing service. Permissions and audit are inherited unchanged — the service still calls requirePermission(userId, "example:read").

.well-known OAuth discovery routes

Better Auth's MCP plugin exposes the discovery metadata through helper handlers. The shape:

// src/app/.well-known/oauth-authorization-server/route.ts
import { auth } from "@/server/auth";

export const GET = async () => {
  const metadata = await auth.api.getMcpDiscoveryMetadata();
  return Response.json(metadata);
};
// src/app/.well-known/oauth-protected-resource/route.ts
import { auth } from "@/server/auth";

export const GET = async () => {
  const metadata = await auth.api.getMcpProtectedResourceMetadata();
  return Response.json(metadata);
};

If the Better Auth MCP API surface is different in your installed version, consult the upstream docs and adapt — the principle is "expose the metadata Better Auth provides."

Prisma — three OAuth tables

Add to schema.prisma:

model OauthApplication {
  id           String   @id @default(cuid())
  name         String
  clientId     String   @unique
  clientSecret String?
  redirectURLs String
  type         String
  metadata     String?
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
}

model OauthAccessToken {
  id                    String    @id @default(cuid())
  accessToken           String    @unique
  refreshToken          String?   @unique
  accessTokenExpiresAt  DateTime?
  refreshTokenExpiresAt DateTime?
  clientId              String
  userId                String
  scopes                String
  createdAt             DateTime  @default(now())
  updatedAt             DateTime  @updatedAt

  @@index([userId])
  @@index([clientId])
}

model OauthConsent {
  id           String   @id @default(cuid())
  clientId     String
  userId       String
  scopes       String
  consentGiven Boolean
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt

  @@unique([clientId, userId])
}

Migrate: pnpm prisma migrate dev --name add_mcp_oauth.

Update CLAUDE.md

Add a section:

## MCP

Read-only MCP endpoint at `POST /mcp`, gated by OAuth via Better Auth's `mcp` plugin.

- Tools live under `src/server/mcp/tools/`, registered in `src/server/mcp/registry.ts`.
- Each tool wraps an existing service method — permissions + audit inherited unchanged.
- Local dev caveat: Claude Desktop only accepts HTTPS URLs, so testing against `http://localhost` requires a tunnel (cloudflared, ngrok). Production / staging serve HTTPS natively.

To add a new tool:
1. Write the tool file under `src/server/mcp/tools/<name>.ts`. Wrap an existing service.
2. Register it in `src/server/mcp/registry.ts`.
3. No DB migration needed.

Verification

pnpm install
pnpm prisma generate
pnpm prisma migrate dev --name add_mcp_oauth
pnpm verify

Smoke test with a real MCP client:

  1. Run the app locally on HTTPS (tunnel if needed).
  2. In Claude Desktop, add the MCP server URL.
  3. Trigger the OAuth flow (login → consent → token).
  4. Ask Claude to call example_search.

Anti-patterns to refuse

  • Adding MCP tools that don't wrap services. Inline DB queries in tools break the permission/audit invariants. Always wrap a service.
  • Tools that take userId from the args. The userId comes from withMcpAuth's session, not the client. Don't trust client-supplied identity.
  • Tools without input validation. Use Zod on every tool's input — the MCP client may be a different model than the one you tested with.
  • Mutating tools without explicit user confirmation. For Phase 1, ship read-only — every tool calls a list / findById / search service method. Add write tools only after explicit safe-write design (per-tool dry-run mode, idempotency keys, an audit row tagged with source: "mcp" so staff can review what an AI client did).

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.