Skip to main content
Glama
metro.md27 kB
--- title: metro.config.js description: A reference of available configurations in Metro. --- See more information about **metro.config.js** in the [customizing Metro guide](/guides/customizing-metro/). ## Environment variables Expo CLI can load environment variables from **.env** files. Learn more about how to use environment variables in Expo CLI in the [environment variables guide](/guides/environment-variables/). EAS CLI uses a different mechanism for environment variables, except when it invokes Expo CLI for compiling and bundling. Learn more about [environment variables in EAS](/build-reference/variables/). If you are migrating an older project, then you should ignore local env files by adding the following to your **.gitignore**: ```sh .gitignore # local env files .env*.local ``` ### Disabling dotenv files Dotenv file loading can be fully disabled in Expo CLI by enabling the `EXPO_NO_DOTENV` environment variable, before invoking any Expo CLI command. ### Disabling `EXPO_PUBLIC_`-prefixed client environment variables Environment variables prefixed with `EXPO_PUBLIC_` will be exposed to the app at build-time. For example, `EXPO_PUBLIC_API_KEY` will be available as `process.env.EXPO_PUBLIC_API_KEY`. Client environment variable inlining can be disabled with the environment variable `EXPO_NO_CLIENT_ENV_VARS=1`, this must be defined before any bundling is performed. ## CSS > **info** CSS support is under development and currently only works on web. Expo supports CSS in your project. You can import CSS files from any component. CSS Modules are also supported. <Tab label="SDK 50 and above"> CSS support is enabled by default. You can disable the feature by setting `isCSSEnabled` in the Metro config. ```js metro.config.js /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname, { // Disable CSS support. isCSSEnabled: false, }); ``` </Tab> <Tab label="SDK 49"> To enable CSS support, set `isCSSEnabled` to `true` in the Metro config. ```js metro.config.js /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname, { isCSSEnabled: true, }); ``` </Tab> ### Global CSS > **warning** Global styles are web-only, usage will cause your application to diverge visually on native. You can import a CSS file from any component. The CSS will be applied to the entire page. Here, we'll define a global style for the class name `.container`: ```css styles.css .container { background-color: red; } ``` We can then use the class name in our component by importing the stylesheet and using `.container`: ```jsx App.js|collapseHeight=470 return ( <> {/* Use `className` to assign the style with React DOM components. */} <div className="container">Hello World</div> {/* Use `style` with the following syntax to append class names in React Native for web. */} Hello World </> ); } ``` You can also import stylesheets that are vendored in libraries, just like you would any node module: ```js index.js // Applies the styles app-wide. ``` - On native, all global stylesheets are automatically ignored. - Hot reloading is supported for global stylesheets, simply save the file and the changes will be applied. ### CSS Modules > **warning** CSS Modules for native are under development and currently only work on web. CSS Modules are a way to scope CSS to a specific component. This is useful for avoiding naming collisions and for ensuring that styles are only applied to the intended component. In Expo, CSS Modules are defined by creating a file with the `.module.css` extension. The file can be imported from any component. The exported value is an object with the class names as keys and the web-only scoped names as the values. The import `unstable_styles` can be used to access `react-native-web`-safe styles. CSS Modules support platform extensions to allow you to define different styles for different platforms. For example, you can define a `module.ios.css` and `module.android.css` file to define styles for Android and iOS respectively. You'll need to import without the extension, for example: ```diff App.js // Importing `./App.module.ios.css`: - import styles from './App.module.css'; + import styles from './App.module'; ``` Flipping the extension, for example, `App.ios.module.css` will not work and result in a universal module named `App.ios.module`. > You cannot pass styles to the `className` prop of a React Native or React Native for web component. Instead, you must use the `style` prop. ```jsx App.js|collapseHeight=470 return ( <> Hello World Hello World {/* Web-only usage: */} <p className={styles.text}>Hello World</p> </> ); } ``` ```css App.module.css .text { color: red; } ``` - On web, all CSS values are available. CSS is not processed or auto-prefixed like it is with the React Native Web `StyleSheet` API. You can use `postcss.config.js` to autoprefix your CSS. - CSS Modules use [lightningcss](https://github.com/parcel-bundler/lightningcss) under the hood, check [the issues](https://github.com/parcel-bundler/lightningcss/issues) for unsupported features. ### PostCSS > Changing the Post CSS or `browserslist` config will require you to clear the Metro cache: `npx expo start --clear` | `npx expo export --clear`. [PostCSS](https://github.com/postcss/postcss) can be customized by adding a `postcss.config.json` file to the root of your project. This file should export a function that returns a PostCSS configuration object. For example: ```json postcss.config.json { "plugins": { "autoprefixer": {} } } ``` Both `postcss.config.json` and `postcss.config.js` are supported, but `postcss.config.json` enables better caching. ### SASS Expo Metro has _partial_ support for SCSS/SASS. To setup, install the `sass` package in your project: Then, ensure [CSS is setup](#css) in the **metro.config.js** file. - When `sass` is installed, then modules without extensions will be resolved in the following order: `scss`, `sass`, `css`. - Only use the intended syntax with `sass` files. - Importing other files from inside a scss/sass file is not currently supported. ### Tailwind > **info** Standard Tailwind CSS supports only web platform. For universal support, use a library such as [NativeWind](https://www.nativewind.dev/), which allows creating styled React Native components with Tailwind CSS. ## Extending the Babel transformer Expo's Metro config uses a custom `transformer.babelTransformerPath` value to ensure `expo-babel-preset` is always used and web/Node.js environments are supported. If you want to extend the Babel transformer, import the upstream transformer from `@expo/metro-config/babel-transformer` instead of `metro-react-native-babel-transformer`. For example: ```js metro.transformer.js const upstreamTransformer = require('@expo/metro-config/babel-transformer'); module.exports.transform = async ({ src, filename, options }) => { // Do something custom for SVG files... if (filename.endsWith('.svg')) { src = '...'; } // Pass the source through the upstream Expo transformer. return upstreamTransformer.transform({ src, filename, options }); }; ``` ## Custom resolving Expo CLI extends the default Metro resolver to add features like Web, Server, and tsconfig aliases support. You can similarly customize the default resolution behavior of Metro by chaining the `config.resolver.resolveRequest` function. ```tsx metro.config.js|collapseHeight=470 const { getDefaultConfig } = require('expo/metro-config'); /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname); config.resolver.resolveRequest = (context, moduleName, platform) => { if (moduleName.startsWith('my-custom-resolver:')) { // Logic to resolve the module name to a file path... // NOTE: Throw an error if there is no resolution. return { filePath: 'path/to/file', type: 'sourceFile', }; } // Ensure you call the default resolver. return context.resolveRequest(context, moduleName, platform); }; module.exports = config; ``` Unlike traditional bundlers, Metro shared the same resolver function across all platforms. As a result, you can mutate the resolution settings dynamically on each request with the `context` object. ### Mocking modules If you want a module to be empty for a given platform, you can return a `type: 'empty'` object from the resolver. The following example will cause `lodash` to be empty on web: ```tsx metro.config.js const { getDefaultConfig } = require('expo/metro-config'); /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname); config.resolver.resolveRequest = (context, moduleName, platform) => { if (platform === 'web' && moduleName === 'lodash') { return { type: 'empty', }; } // Ensure you call the default resolver. return context.resolveRequest(context, moduleName, platform); }; module.exports = config; ``` This technique is equivalent to using empty externals in Webpack or Vite, but with the added benefit of being able to target specific platforms. ### Virtual modules Metro doesn't support virtual modules at the moment. One technique you can use to obtain similar behavior is to create a module in the `node_modules/.cache/...` directory and redirect the resolution to that file. The following example will create a module at `node_modules/.cache/virtual/virtual-module.js` and redirect the resolution of `virtual:my-module` to that file: ```tsx metro.config.js const path = require('path'); const fs = require('fs'); const { getDefaultConfig } = require('expo/metro-config'); /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname); const virtualPath = path.resolve(__dirname, 'node_modules/.cache/virtual/virtual-module.js'); // Create the virtual module in a generated directory... fs.mkdirSync(path.dirname(virtualPath), { recursive: true }); fs.writeFileSync(virtualPath, 'export default "Hello World";'); config.resolver.resolveRequest = (context, moduleName, platform) => { if (moduleName === 'virtual:my-module') { return { filePath: virtualPath, type: 'sourceFile', }; } // Ensure you call the default resolver. return context.resolveRequest(context, moduleName, platform); }; module.exports = config; ``` This can be used to emulate `externals` with custom imports. For example, if you want to redirect `require('expo')` to something custom like `SystemJS.require('expo')`, you can create a virtual module that exports `SystemJS.require('expo')` and redirect the resolution of `expo` to that file. ## Custom transforming > Transformations are heavily cached in Metro. If you update something, use the `--clear` flag to see updates. For example, `npx expo start --clear`. Metro doesn't have a very expressive plugin system for transforming files, instead opt to use the [**babel.config.js**](../config/babel/) and caller object to customize the transformation. ```js babel.config.js module.exports = function (api) { // Get the platform that Expo CLI is transforming for. const platform = api.caller(caller => (caller ? caller.platform : 'ios')); // Detect if the bundling operation is for Hermes engine or not, e.g. `'hermes'` | `undefined`. const engine = api.caller(caller => (caller ? caller.engine : null)); // Is bundling for a server environment, e.g. API Routes. const isServer = api.caller(caller => (caller ? caller.isServer : false)); // Is bundling for development or production. const isDev = api.caller(caller => caller ? caller.isDev : process.env.BABEL_ENV === 'development' || process.env.NODE_ENV === 'development' ); // Ensure the config is not cached otherwise the platform will not be updated. api.cache(false); // You can alternatively provide a more robust CONFIG cache invalidation: // api.cache.invalidate(() => platform); return { presets: ['babel-preset-expo'], plugins: [ // Add a plugin based on the platform... platform === 'web' && 'my-plugin', // Ensure you filter out falsy values. ].filter(Boolean), }; }; ``` If the caller doesn't have `engine`, `platform`, `bundler`, and so on, then ensure you are using `@expo/metro-config/babel-transformer` for the transformer. If you're using a custom transformer then it may need to extend the Expo transformer. Always try to implement custom logic in the resolver if possible, caching is much simpler and easier to reason about. For example, if you need to remap an import, it's simpler and faster to resolve to a static file with the resolver than to parse all possible import methods and remap them with the transformer. Always use `babel-preset-expo` as the default Babel preset, this ensures the transformation is always compatible with Expo runtimes. `babel-preset-expo` uses all of the caller inputs internally to optimize for a given platform, engine, and environment. ## Node.js built-ins When bundling for a server environment, Expo's Metro config automatically supports externalizing Node.js built-in modules (`fs`, `path`, `node:crypto`, and more) based on the current Node.js version. If the CLI is bundling for a browser environment, then built-ins will first check if the module is installed locally, then fallback on an empty shim. For example, if you install `path` for use in the browser, this can be used, otherwise, the module will automatically be skipped. ## Environment settings Expo's Metro config injects build settings that can be used in the client bundle via environment variables. All variables will be inlined and cannot be used dynamically. For example, `process.env["EXPO_BASE_URL"]` won't work. - `process.env.EXPO_BASE_URL` exposes the base URL defined in `experiments.baseUrl`. This is used in Expo Router to respect the production base URL for deployment. > These environment variables will not be defined in test environments! ## Bundle splitting > This feature is web-only in SDK 50. In SDK 50, Expo CLI automatically splits web bundles into multiple chunks based on async imports in production. This feature requires `@expo/metro-runtime` to be installed and imported somewhere in the entry bundle (available by default in Expo Router). Shared dependencies of async bundles are merged into a single chunk to reduce the number of requests. For example, if you have two async bundles that import `lodash`, then the library is merged into a single initial chunk. As of SDK 50, the chunk splitting heuristic cannot be customized. For example: ```js math.js return a + b; } ``` ```js index.js // This will be split into a separate chunk. import('./math').then(math => { console.log(math.add(1, 2)); }); ``` When you run `npx expo export -p web`, the bundles are split into multiple files, and the entry bundle is added to the main HTML file. `@expo/metro-runtime` adds the runtime code that loads and evaluates the async bundles. ## Source map debug ID > Available from SDK 50 on all platforms. If a bundle is exported with an external source map, a [**Debug ID**](https://sentry.engineering/blog/the-case-for-debug-ids) annotation will be added to the end of the file, along with a matching `debugId` in the source map for corresponding the files together. If no source maps are exported, or inline source maps are used then this annotation will not be added. ```js // <all source code> //# debugId=<deterministic chunk hash> ``` The associated `*.js.map` or `*.hbc.map` source map will be a JSON file containing an equivalent `debugId` property. The `debugId` will be injected before hermes bytecode generation to ensure matching in all cases. The `debugId` is a deterministic hash of the bundle's contents without the external bundle splitting references. This is the same value used to create a chunks filename but formatted as a UUID. For example, `431b98e2-c997-4975-a3d9-2987710abd44`. `@expo/metro-config` injects `debugId` during `npx expo export` and `npx expo export:embed`. Any additional optimization steps in `npx expo export:embed` like Hermes bytecode generation will need to have the `debugId` injected manually. ## Metro require runtime You can optionally enable a custom Metro `require` implementation with the environment variable `EXPO_USE_METRO_REQUIRE=1`. This runtime has the following features: - String module IDs that are human-readable and make missing module errors easier to follow. - Deterministic IDs that are the same between runs and across modules (required for React Server Components in development). - Removed support for legacy RAM bundles. ## Magic import comments > Available from SDK 52 on all platforms. Server environments such as Workers, and Node.js support import arbitrary files at runtime, so you may want to keep `import` syntax in-tact instead of using Metro's require system. You can opt-out dynamic imports with the `/* @metro-ignore */` comment in `import()` statements. ```js // Manually ensure `./my-module.js` is included in the correct spot relative to the module. const myModule = await import(/* @metro-ignore */ './my-module.js'); ``` Expo CLI will skip the `./my-module.js` dependency and assume that the developer has manually added it to the output bundle. Internally, this is used for exporting custom server code that dynamically switches between files based on the request. Avoid using this syntax for native bundles since `import()` is generally not available in React Native with Hermes enabled. ## Asset imports When assets are imported, a virtual module is created to represent the data required for importing the asset. On native platforms, an asset will be a numeric ID: `1`, `2`, `3`, and so on, which can be looked up using `require("@react-native/assets-registry/registry").getAssetByID(<NUMBER>)`. On web and server platforms, the asset will change depending on the file type. If the file is an image, then the asset will be `{ uri: string, width?: number, height?: number }`, otherwise the asset will be a `string` representing the remote URL for the asset. The assets can be used as follows: ```js function Demo() { return ; } ``` In API routes, you can always assume the type of the asset will not be a number: ```js const ImageData = await fetch( new URL( // Access the asset URI. asset.uri, // Append to the current request URL origin. req.url ) ).then(res => res.arrayBuffer()); return new Response(ImageData, { headers: { 'Content-Type': 'image/png', }, }); } ``` ## Bare workflow setup > This guide is versioned and will need to be revisited when upgrading/downgrading Expo. Alternatively, use [Expo Prebuild](/workflow/prebuild) for fully automated setup. Projects that don't use [Expo Prebuild](/workflow/prebuild) must configure native files to ensure the Expo Metro config is always used to bundle the project. {/* If this isn't done, then features like [aliases](/guides/typescript/#path-aliases-optional), [absolute imports](/guides/typescript/#absolute-imports-optional), asset hashing, and more will not work. */} These modifications are meant to replace `npx react-native bundle` and `npx react-native start` with `npx expo export:embed` and `npx expo start` respectively. ### metro.config.js Ensure the **metro.config.js** extends `expo/metro-config`: ```js const { getDefaultConfig } = require('expo/metro-config'); const config = getDefaultConfig(__dirname); module.exports = config; ``` ### `android/app/build.gradle` The Android `app/build.gradle` must be configured to use Expo CLI for production bundling. Modify the `react` config object: ```diff react { ... + // Use Expo CLI to bundle the app, this ensures the Metro config + // works correctly with Expo projects. + cliFile = new File(["node", "--print", "require.resolve('@expo/cli')"].execute(null, rootDir).text.trim()) + bundleCommand = "export:embed" } ``` ### `ios/<Project>.xcodeproj/project.pbxproj` In your `ios/<Project>.xcodeproj/project.pbxproj` file, replace the following scripts: #### `"Start Packager"` Remove the "Start Packager" script in SDK 50 and higher. The dev server must be started with `npx expo` before/after running the app. ```diff - FD10A7F022414F080027D42C /* Start Packager */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Start Packager"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\nexport RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > `$NODE_BINARY --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/.packager.env'\"`\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open `$NODE_BINARY --print \"require('path').dirname(require.resolve('expo/package.json')) + '/scripts/launchPackager.command'\"` || echo \"Can't start packager automatically\"\n fi\nfi\n"; - showEnvVarsInLog = 0; - }; ``` #### "Bundle React Native code and images" ```diff + shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli')\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n"; ``` **Alternatively**, in the Xcode project, select the "Bundle React Native code and images" build phase and add the following modifications: ```diff if [[ -f "$PODS_ROOT/../.xcode.env" ]]; then source "$PODS_ROOT/../.xcode.env" fi if [[ -f "$PODS_ROOT/../.xcode.env.local" ]]; then source "$PODS_ROOT/../.xcode.env.local" fi # The project root by default is one level up from the ios directory if [[ "$CONFIGURATION" = *Debug* ]]; then export SKIP_BUNDLING=1 fi + if [[ -z "$ENTRY_FILE" ]]; then + # Set the entry JS file using the bundler's entry resolution. + export ENTRY_FILE="$("$NODE_BINARY" -e "require('expo/scripts/resolveAppEntry')" "$PROJECT_ROOT" ios absolute | tail -n 1)" + fi + if [[ -z "$CLI_PATH" ]]; then + # Use Expo CLI + export CLI_PATH="$("$NODE_BINARY" --print "require.resolve('@expo/cli')")" + fi + if [[ -z "$BUNDLE_COMMAND" ]]; then + # Default Expo CLI command for bundling + export BUNDLE_COMMAND="export:embed" + fi `"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"` ``` > You can set `CLI_PATH`, `BUNDLE_COMMAND`, and `ENTRY_FILE` environment variables to overwrite these defaults. ### Custom entry file By default, React Native only supports using a root `index.js` file as the entry file (or platform-specific variation like `index.ios.js`). Expo projects allow using any entry file, but this requires addition bare setup. #### Development Development mode entry files can be enabled by using the [`expo-dev-client`](../sdk/dev-client/) package. Alternatively you can add the following configuration: In the `ios/[project]/AppDelegate.mm` file: ```diff - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { #if DEBUG - return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; + return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"]; #else return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif } ``` In the `android/app/src/main/java/**/MainApplication.java`: ```diff @Override protected String getJSMainModuleName() { - return "index"; + return ".expo/.virtual-metro-entry"; } ``` #### Production In your `ios/<Project>.xcodeproj/project.pbxproj` file, replace the `"Bundle React Native code and images"` script to set `$ENTRY_FILE` according using Metro: ```diff + shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli')\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n"; ``` The Android `app/build.gradle` must be configured to use Metro module resolution to find the root entry file. Modify the `react` config object: ```diff + def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() react { + entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) } ```

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jaksm/expo-docs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server