#12

How to setup a dynamic Resource Template

Opendjshubsopened 1y ago

I have successfully created a regular/static resource template; however, I am having trouble developing a resource template that mirrors the structure and content of the TypeScript SDK repository on GitHub, specifically the README file’s “Resources” section.

LINK: https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#resources

// Dynamic resource with parameters
server.resource(
  "user-profile",
  new ResourceTemplate("users://{userId}/profile", { list: undefined }),
  async (uri, { userId }) => ({
    contents: [{
      uri: uri.href,
      text: `Profile data for user ${userId}`
    }]
  })
);

Any suggestions on how to mimic this?

Activity

11
quuu1y ago

@djshubs i just tried adding a resource and it worked well for me?

here's a screenshot of using the MCP Inspector to fetch the resource on my next.js MCP server:

let me know if you have a screen recording showing off a question or concern!

djshubs1y ago

@quuu Can you show me the code of how you are referencing it? I'm getting a bunch of type errors. It says it's expecting a string. Can't seem to get ResourceTemplate type either.

quuu1y ago

@quuu Can you show me the code of how you are referencing it? I'm getting a bunch of type errors. It says it's expecting a string. Can't seem to get ResourceTemplate type either.

here's my full code:

import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMcpHandler } from "@vercel/mcp-adapter";
import { z } from "zod";

const handler = createMcpHandler(
  (server) => {
    server.tool(
      "roll_dice",
      "Rolls an N-sided die",
      { sides: z.number().int().min(2) },
      async ({ sides }) => {
        const value = 1 + Math.floor(Math.random() * sides);
        return {
          content: [{ type: "text", text: `🎲 You rolled a ${value}!` }],
        };
      }
    );
    server.resource(
      "user-profile",
      new ResourceTemplate("users://{userId}/profile", { list: undefined }),
      async (uri, { userId }) => ({
        contents: [
          {
            uri: uri.href,
            text: `Profile data for user ${userId}`,
          },
        ],
      })
    );
  },
  {},
  {
    redisUrl: process.env.REDIS_URL,
    basePath: "/",
    verboseLogs: true,
    maxDuration: 800,
  }
);

export { handler as GET, handler as POST, handler as DELETE };
AndonMitev1y ago

Hey @quuu can you help how you manage to run it on the inspector I do have

import { gigsService } from '@/lib/services/GigsService';
import { createMcpHandler } from '@vercel/mcp-adapter';
import { z } from 'zod';

const handler = createMcpHandler(
  (server) => {
    // @ts-ignore
    server.tool('list_gigs', 'List all gigs', {}, async () => {
      const gigs = await gigsService.getGigs();
      return {
        content: [{ type: 'text', text: JSON.stringify(gigs) }],
      };
    });
  },
  {
    // Optional server options
    instructions: 'You are a helpful assistant that can list all gigs.',
  },
  {
    // Optional redis config
    redisUrl: process.env.REDIS_URL,
    basePath: '/api/sse/gigs',
    maxDuration: 800,
    verboseLogs: true,
  },
);
export { handler as GET, handler as POST, handler as DELETE };

I'm seeing

on Request payload:

url: http://localhost:3000/api/sse/gigs/mcp
transportType: sse

But then on the response. I do see:

code: 405
event: {code: 405, message: "Non-200 status code (405)"}
code: 405
message: "Non-200 status code (405)"

I see from the logs is trying to do GET

 GET /api/sse/gigs/mcp 405 in 408ms

which I do have

export { handler as GET, handler as POST, handler as DELETE };

but still fails with 405 any suggestions

quuu1y ago

Hey @quuu can you help how you manage to run it on the inspector I do have

import { gigsService } from '@/lib/services/GigsService';
import { createMcpHandler } from '@vercel/mcp-adapter';
import { z } from 'zod';

const handler = createMcpHandler(
  (server) => {
    // @ts-ignore
    server.tool('list_gigs', 'List all gigs', {}, async () => {
      const gigs = await gigsService.getGigs();
      return {
        content: [{ type: 'text', text: JSON.stringify(gigs) }],
      };
    });
  },
  {
    // Optional server options
    instructions: 'You are a helpful assistant that can list all gigs.',
  },
  {
    // Optional redis config
    redisUrl: process.env.REDIS_URL,
    basePath: '/api/sse/gigs',
    maxDuration: 800,
    verboseLogs: true,
  },
);
export { handler as GET, handler as POST, handler as DELETE };

I'm seeing

on Request payload:

url: http://localhost:3000/api/sse/gigs/mcp
transportType: sse

But then on the response. I do see:

code: 405
event: {code: 405, message: "Non-200 status code (405)"}
code: 405
message: "Non-200 status code (405)"

I see from the logs is trying to do GET

 GET /api/sse/gigs/mcp 405 in 408ms

which I do have

export { handler as GET, handler as POST, handler as DELETE };

but still fails with 405 any suggestions

@AndonMitev whats the path of your mcp file?

AndonMitev1y ago

Hey @quuu can you help how you manage to run it on the inspector I do have

import { gigsService } from '@/lib/services/GigsService';
import { createMcpHandler } from '@vercel/mcp-adapter';
import { z } from 'zod';

const handler = createMcpHandler(
  (server) => {
    // @ts-ignore
    server.tool('list_gigs', 'List all gigs', {}, async () => {
      const gigs = await gigsService.getGigs();
      return {
        content: [{ type: 'text', text: JSON.stringify(gigs) }],
      };
    });
  },
  {
    // Optional server options
    instructions: 'You are a helpful assistant that can list all gigs.',
  },
  {
    // Optional redis config
    redisUrl: process.env.REDIS_URL,
    basePath: '/api/sse/gigs',
    maxDuration: 800,
    verboseLogs: true,
  },
);
export { handler as GET, handler as POST, handler as DELETE };

I'm seeing on Request payload:

url: http://localhost:3000/api/sse/gigs/mcp
transportType: sse

But then on the response. I do see:

code: 405
event: {code: 405, message: "Non-200 status code (405)"}
code: 405
message: "Non-200 status code (405)"

I see from the logs is trying to do GET

 GET /api/sse/gigs/mcp 405 in 408ms

which I do have export { handler as GET, handler as POST, handler as DELETE }; but still fails with 405 any suggestions

@AndonMitev whats the path of your mcp file?

Hey @quuu

Path to the MCP entry point

app/api/sse/gigs/[...transport]/route.ts

quuu1y ago

@AndonMitev where do you see:

url: http://localhost:3000/api/sse/gigs/mcp
transportType: sse

your setup looks right, connecting to /api/sse/gigs/mcp will use StreamableHTTP though while /api/sse/gigs/sse will use SSE

AndonMitev1y ago

@AndonMitev where do you see:

url: http://localhost:3000/api/sse/gigs/mcp
transportType: sse

your setup looks right, connecting to /api/sse/gigs/mcp will use StreamableHTTP though while /api/sse/gigs/sse will use SSE

Ok sorry maybe I do not understand something, this is my code:

https://pastebin.com/ytxmmhEE

I've updated the path it's /api/mcp/gigs/[...transport]/route.ts

Inside the inspector I try with

http://localhost:3000/api/mcp/gigs/mcp or http://localhost:3000/api/mcp/gigs/sse

also switching between different transport types can't make it work with any, i gotta check what is streamable http is this the best way, better than sse ( I'm not sure if i'm running sse tbh xaxa) :)

quuu1y ago

@AndonMitev that looks right to me!

i'm writing up a longer article on the difference between SSE and StreamableHTTP, but they're different methods of transport

StreambleHTTP is the new way, SSE is getting deprecated

what is the exact error you see when you use those url's?

i created a similar set up to you and it's connecting fine

when StreamableHTTP is the transport, and i'm connecting to /api/mcp/gigs/mcp and SSE is the transport when I'm connecting to /api/mcp/gigs/sse

AndonMitev1y ago

@AndonMitev that looks right to me!

i'm writing up a longer article on the difference between SSE and StreamableHTTP, but they're different methods of transport

StreambleHTTP is the new way, SSE is getting deprecated

what is the exact error you see when you use those url's?

i created a similar set up to you and it's connecting fine

when StreamableHTTP is the transport, and i'm connecting to /api/mcp/gigs/mcp and SSE is the transport when I'm connecting to /api/mcp/gigs/sse

@quuu with this setup,

I'm getting:

Connected to Streamable HTTP transport
Connected MCP client to server transport
Created streamable web app transport a03de765-b5b7-41c1-9ec3-36693db0bcd2
Error from MCP server: TypeError: fetch failed
    at node:internal/deps/undici/undici:13502:13
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async StreamableHTTPClientTransport.send (file:///Users/andonmitev/.npm/_npx/5a9d879542beca3a/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js:248:30) {
  [cause]: AggregateError [ECONNREFUSED]: 
      at internalConnectMultiple (node:net:1139:18)
      at afterConnectMultiple (node:net:1712:7) {
    code: 'ECONNREFUSED',
    [errors]: [ [Error], [Error] ]
  }
}

This is the full url: http://localhost:3000/api/mcp/gigs/mcp ( updated from api/sse to api/mcp but it's all good on claude and cursor is working perfectly fine all day actually just on the inspector fails :( )

Also version of the packages that I'm currently using:

"@vercel/mcp-adapter": "^0.9.1",
"@modelcontextprotocol/sdk": "1.12.0",

any clue @quuu 🤔

quuu1y ago

@AndonMitev i know you're not testing in claude desktop, but I've seen similar reports from claude desktop where localhost isn't known by the client, and so you'd have to use your actual IP:

https://github.com/modelcontextprotocol/servers/issues/1065

let me know if using the actual ip works for you