From 5d8dbcd5dfd027152db5abb57ac31ac7fda502da Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Sun, 13 Sep 2026 15:38:45 -0400 Subject: [PATCH 1/3] Add Mobile v4 Getting Started page for the public Docs MCP server. Document the hosted, no-key NativePHP docs MCP under getting-started (order 275) with connect snippets and a short tool list, linking out to /mcp for the full client matrix. --- .../docs/mobile/4/getting-started/mcp.md | 67 +++++++++++++++++++ tests/Feature/DocsMcpServerPageTest.php | 10 +++ 2 files changed, 77 insertions(+) create mode 100644 resources/views/docs/mobile/4/getting-started/mcp.md diff --git a/resources/views/docs/mobile/4/getting-started/mcp.md b/resources/views/docs/mobile/4/getting-started/mcp.md new file mode 100644 index 00000000..2a2e7d8b --- /dev/null +++ b/resources/views/docs/mobile/4/getting-started/mcp.md @@ -0,0 +1,67 @@ +--- +title: MCP Docs Server +order: 275 +--- + +The full NativePHP documentation — for both [Mobile](/docs/mobile/getting-started/introduction) and [Desktop](/docs/desktop/getting-started/introduction) — is available over [MCP](https://modelcontextprotocol.io). Agents such as Claude Code, Cursor, and Copilot can search and read the docs while they work, instead of relying on training-data memory alone. + +It's hosted by NativePHP. There's nothing to install and no API key to create — just point your agent at this URL: + +``` +https://nativephp.com/api/mcp/message +``` + +## Quick connect + +### Claude Code + +```shell +claude mcp add --transport http nativephp-docs https://nativephp.com/api/mcp/message +``` + +Or commit `.mcp.json` to your repo so the whole team picks it up: + +```json +{ + "mcpServers": { + "nativephp-docs": { + "type": "http", + "url": "https://nativephp.com/api/mcp/message" + } + } +} +``` + +### Cursor + +Create `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` to enable it everywhere: + +```json +{ + "mcpServers": { + "nativephp-docs": { + "type": "http", + "url": "https://nativephp.com/api/mcp/message" + } + } +} +``` + +## What your agent can do + +Once connected, your agent gets these tools: + +- **`search_docs`** — full-text search across every platform and version +- **`get_page`** — fetch a full page by path (e.g. `mobile/4/plugins/core/camera`) +- **`get_navigation`** — the sidebar for a platform and version +- **`list_apis`** — list pages in a version's `apis` section (Mobile v1/v2 only) +- **`search_plugins`** — search the public plugin marketplace +- **`get_plugin`** — fetch one marketplace plugin by composer name + +## Pair it with Laravel Boost + +This MCP tells your agent what NativePHP _can_ do. [Laravel Boost](https://laravel.com/ai/boost) tells it about _your_ application — routes, models, config, and package versions. Running both together works better than either alone. + +## More clients and details + +For VS Code / Copilot, `mcp-remote`, REST mirrors, rate limits, and the full client matrix, see the [Docs MCP Server](/mcp) page. diff --git a/tests/Feature/DocsMcpServerPageTest.php b/tests/Feature/DocsMcpServerPageTest.php index 580ac45f..5d82af46 100644 --- a/tests/Feature/DocsMcpServerPageTest.php +++ b/tests/Feature/DocsMcpServerPageTest.php @@ -137,4 +137,14 @@ public function the_docs_no_longer_carry_a_duplicate_copy_of_this_page(): void 'The MCP server is documented once, at '.route('mcp').'.', ); } + + #[Test] + public function mobile_v4_getting_started_documents_the_public_mcp_endpoint(): void + { + $this->withoutVite() + ->get('/docs/mobile/4/getting-started/mcp') + ->assertOk() + ->assertSee('MCP Docs Server') + ->assertSee('https://nativephp.com/api/mcp/message'); + } } From 63f323cfa680916b52f681a9bc60653bec99cbdf Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Sun, 13 Sep 2026 15:41:51 -0400 Subject: [PATCH 2/3] Clarify list_apis is legacy v1/v2 on Mobile MCP docs page. Mobile v3+ documents native APIs under Plugins; point v4 Getting Started readers at search_docs / plugins for latest. --- resources/views/docs/mobile/4/getting-started/mcp.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/docs/mobile/4/getting-started/mcp.md b/resources/views/docs/mobile/4/getting-started/mcp.md index 2a2e7d8b..83c0c21b 100644 --- a/resources/views/docs/mobile/4/getting-started/mcp.md +++ b/resources/views/docs/mobile/4/getting-started/mcp.md @@ -51,12 +51,12 @@ Create `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` to enable it Once connected, your agent gets these tools: -- **`search_docs`** — full-text search across every platform and version +- **`search_docs`** — full-text search across every platform and version (use this for current Mobile v4 docs) - **`get_page`** — fetch a full page by path (e.g. `mobile/4/plugins/core/camera`) - **`get_navigation`** — the sidebar for a platform and version -- **`list_apis`** — list pages in a version's `apis` section (Mobile v1/v2 only) -- **`search_plugins`** — search the public plugin marketplace +- **`search_plugins`** — search the public plugin marketplace (where Mobile v3+ native APIs live) - **`get_plugin`** — fetch one marketplace plugin by composer name +- **`list_apis`** — list pages in a version's legacy `apis` section (**Mobile v1/v2 only**). From Mobile v3 onward there is no `apis` section — native capabilities are documented under [Plugins](/docs/mobile/4/plugins/introduction). Prefer `search_docs`, `get_navigation`, or `search_plugins` for the latest docs. ## Pair it with Laravel Boost From 13a476a4db789e56d2bb5bb3190a6c556bbbdb88 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Sun, 13 Sep 2026 15:51:54 -0400 Subject: [PATCH 3/3] Replace Docs MCP list_apis with list_edge_components. Agents building SuperNative/EDGE apps need the component catalog more than the legacy Mobile v1/v2 apis section. Defaults version to latest per platform. --- app/Http/Controllers/McpController.php | 40 ++++---- app/Services/DocsSearchService.php | 5 +- .../docs/mobile/4/getting-started/mcp.md | 4 +- resources/views/mcp-content.md | 15 +-- routes/api.php | 2 +- tests/Feature/DocsMcpServerPageTest.php | 99 ++++++++++++++++++- tests/Feature/McpSecurityTest.php | 6 +- 7 files changed, 137 insertions(+), 34 deletions(-) diff --git a/app/Http/Controllers/McpController.php b/app/Http/Controllers/McpController.php index 19b344e1..d3c73f91 100644 --- a/app/Http/Controllers/McpController.php +++ b/app/Http/Controllers/McpController.php @@ -97,11 +97,11 @@ public function pageApi(string $platform, string $version, string $path): JsonRe return response()->json(['page' => $page]); } - public function apisApi(string $platform, string $version): JsonResponse + public function edgeComponentsApi(string $platform, string $version): JsonResponse { - $apis = $this->docsSearch->listApis($platform, $version); + $components = $this->docsSearch->listEdgeComponents($platform, $version); - return response()->json(['apis' => $apis]); + return response()->json(['edge_components' => $components]); } public function navigationApi(string $platform, string $version): JsonResponse @@ -182,22 +182,22 @@ protected function getToolDefinitions(): array ], ], [ - 'name' => 'list_apis', - 'description' => 'List all native APIs for a platform/version', + 'name' => 'list_edge_components', + 'description' => 'List EDGE / SuperNative UI components documented for a platform/version so agents build the NativePHP way (native UI via Blade EDGE components). Defaults to the latest version for the platform when version is omitted.', 'inputSchema' => [ 'type' => 'object', 'properties' => [ 'platform' => [ 'type' => 'string', 'enum' => ['desktop', 'mobile'], - 'description' => 'Platform to list APIs for', + 'description' => 'Platform to list EDGE components for (default: mobile)', + 'default' => 'mobile', ], 'version' => [ 'type' => 'string', - 'description' => 'Version number', + 'description' => 'Version number (optional; defaults to the latest for the platform)', ], ], - 'required' => ['platform', 'version'], ], ], [ @@ -285,7 +285,7 @@ protected function handleToolCall(string $name, array $args): array return match ($name) { 'search_docs' => $this->toolSearchDocs($args), 'get_page' => $this->toolGetPage($args), - 'list_apis' => $this->toolListApis($args), + 'list_edge_components' => $this->toolListEdgeComponents($args), 'get_navigation' => $this->toolGetNavigation($args), 'search_plugins' => $this->toolSearchPlugins($args), 'get_plugin' => $this->toolGetPlugin($args), @@ -350,27 +350,29 @@ protected function toolGetPage(array $args): array ]; } - protected function toolListApis(array $args): array + protected function toolListEdgeComponents(array $args): array { - $platform = $args['platform'] ?? ''; - $version = $args['version'] ?? ''; + $platform = $args['platform'] ?? 'mobile'; + $latestVersions = $this->docsSearch->getLatestVersions(); + $version = $args['version'] ?? ($latestVersions[$platform] ?? ''); - $apis = $this->docsSearch->listApis($platform, $version); + $components = $this->docsSearch->listEdgeComponents($platform, $version); - if (empty($apis)) { + if (empty($components)) { return [ - 'content' => [['type' => 'text', 'text' => "No APIs found for {$platform} v{$version}"]], + 'content' => [['type' => 'text', 'text' => "No EDGE components found for {$platform} v{$version}"]], ]; } - $formatted = collect($apis)->map(function ($api) { - $desc = $api['description'] ?: 'No description'; + $formatted = collect($components)->map(function ($component) { + $desc = $component['description'] ?: 'No description'; + $path = $component['id']; - return "- **{$api['title']}** ({$api['slug']})\n {$desc}"; + return "- **{$component['title']}** ({$component['slug']})\n Path: {$path}\n {$desc}"; })->join("\n"); return [ - 'content' => [['type' => 'text', 'text' => "# {$platform} v{$version} APIs\n\n{$formatted}"]], + 'content' => [['type' => 'text', 'text' => "# {$platform} v{$version} EDGE components\n\n{$formatted}"]], ]; } diff --git a/app/Services/DocsSearchService.php b/app/Services/DocsSearchService.php index 249f7f4c..bf41d620 100644 --- a/app/Services/DocsSearchService.php +++ b/app/Services/DocsSearchService.php @@ -85,14 +85,15 @@ public function getPageByPath(string $path): ?array return $this->getPage($platform, $version, implode('/', $parts), $slug); } - public function listApis(string $platform, string $version): array + public function listEdgeComponents(string $platform, string $version): array { if (! $this->sanitizePlatform($platform) || ! $this->sanitizeVersion($version)) { return []; } return collect($this->getAllPages($platform, $version)) - ->filter(fn ($page) => $page['section'] === 'apis') + ->filter(fn ($page) => $page['section'] === 'edge-components' + || str_starts_with($page['section'], 'edge-components/')) ->sortBy('order') ->values() ->toArray(); diff --git a/resources/views/docs/mobile/4/getting-started/mcp.md b/resources/views/docs/mobile/4/getting-started/mcp.md index 83c0c21b..dc3c89de 100644 --- a/resources/views/docs/mobile/4/getting-started/mcp.md +++ b/resources/views/docs/mobile/4/getting-started/mcp.md @@ -52,11 +52,11 @@ Create `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` to enable it Once connected, your agent gets these tools: - **`search_docs`** — full-text search across every platform and version (use this for current Mobile v4 docs) -- **`get_page`** — fetch a full page by path (e.g. `mobile/4/plugins/core/camera`) +- **`get_page`** — fetch a full page by path (e.g. `mobile/4/plugins/core/camera` or `mobile/4/edge-components/button`) - **`get_navigation`** — the sidebar for a platform and version +- **`list_edge_components`** — list EDGE / SuperNative UI components for a platform (defaults to latest mobile) so agents build native UI via Blade EDGE components - **`search_plugins`** — search the public plugin marketplace (where Mobile v3+ native APIs live) - **`get_plugin`** — fetch one marketplace plugin by composer name -- **`list_apis`** — list pages in a version's legacy `apis` section (**Mobile v1/v2 only**). From Mobile v3 onward there is no `apis` section — native capabilities are documented under [Plugins](/docs/mobile/4/plugins/introduction). Prefer `search_docs`, `get_navigation`, or `search_plugins` for the latest docs. ## Pair it with Laravel Boost diff --git a/resources/views/mcp-content.md b/resources/views/mcp-content.md index d50e9e8e..2a8f7125 100644 --- a/resources/views/mcp-content.md +++ b/resources/views/mcp-content.md @@ -118,12 +118,15 @@ Returns the whole sidebar for a `platform` and `version`, grouped by section and in the order you see it on the site. Useful when an agent wants to orient itself before searching, or to check whether a topic is documented at all. -### `list_apis` +### `list_edge_components` -Lists the pages in a version's `apis` section. That section only exists in the -Mobile v1 and v2 docs — from v3 onwards the native APIs are documented under -Plugins, and the Desktop docs have no `apis` section at all. For anything -current, use `get_navigation` or `search_docs` instead. +Lists the EDGE / SuperNative UI components documented for a `platform` and +optional `version` — the Blade components agents should use to build native +UI the NativePHP way. Defaults to `mobile` and the latest published version for +that platform when those args are omitted. Each result includes a path you can +hand straight to `get_page` (for example `mobile/4/edge-components/button`). +Mobile v2+ ships an `edge-components` section; Desktop currently has none, so +the list is empty there. ### `search_plugins` @@ -161,7 +164,7 @@ MCP client: - `/api/mcp/search?q=camera&platform=mobile` — search results as JSON - `/api/mcp/page/{platform}/{version}/{section}/{slug}` — a single page - `/api/mcp/navigation/{platform}/{version}` — the docs navigation tree -- `/api/mcp/apis/{platform}/{version}` — the `apis` section listing +- `/api/mcp/edge-components/{platform}/{version}` — EDGE / SuperNative component listing - `/api/mcp/plugins?q=camera&type=free&limit=10` — marketplace plugin search - `/api/mcp/plugins/{vendor}/{package}` — one marketplace plugin - `/api/mcp/health` — liveness check, and the versions currently published diff --git a/routes/api.php b/routes/api.php index 49db610e..4eb7fea7 100644 --- a/routes/api.php +++ b/routes/api.php @@ -30,7 +30,7 @@ Route::get('page/{platform}/{version}/{path}', [McpController::class, 'pageApi']) ->where('path', '.*') ->name('mcp.api.page'); - Route::get('apis/{platform}/{version}', [McpController::class, 'apisApi'])->name('mcp.api.apis'); + Route::get('edge-components/{platform}/{version}', [McpController::class, 'edgeComponentsApi'])->name('mcp.api.edge-components'); Route::get('navigation/{platform}/{version}', [McpController::class, 'navigationApi'])->name('mcp.api.navigation'); Route::get('plugins', [McpController::class, 'pluginsSearchApi'])->name('mcp.api.plugins.search'); diff --git a/tests/Feature/DocsMcpServerPageTest.php b/tests/Feature/DocsMcpServerPageTest.php index 5d82af46..dca6adc5 100644 --- a/tests/Feature/DocsMcpServerPageTest.php +++ b/tests/Feature/DocsMcpServerPageTest.php @@ -75,7 +75,7 @@ public function the_documented_message_endpoint_lists_the_documented_tools(): vo $tools = collect($response->json('result.tools'))->pluck('name')->all(); $this->assertEqualsCanonicalizing( - ['search_docs', 'get_page', 'list_apis', 'get_navigation', 'search_plugins', 'get_plugin'], + ['search_docs', 'get_page', 'list_edge_components', 'get_navigation', 'search_plugins', 'get_plugin'], $tools, ); } @@ -147,4 +147,101 @@ public function mobile_v4_getting_started_documents_the_public_mcp_endpoint(): v ->assertSee('MCP Docs Server') ->assertSee('https://nativephp.com/api/mcp/message'); } + + #[Test] + public function list_edge_components_returns_mobile_v4_components(): void + { + $response = $this->postJson('/api/mcp/message', [ + 'jsonrpc' => '2.0', + 'id' => 1, + 'method' => 'tools/call', + 'params' => [ + 'name' => 'list_edge_components', + 'arguments' => ['platform' => 'mobile', 'version' => '4'], + ], + ]); + + $response->assertOk(); + + $text = $response->json('result.content.0.text'); + + $this->assertStringContainsString('mobile v4 EDGE components', $text); + $this->assertStringContainsString('button', $text); + $this->assertStringContainsString('text', $text); + $this->assertStringContainsString('Path: mobile/4/edge-components/button', $text); + $this->assertStringNotContainsString('Unknown tool', $text); + } + + #[Test] + public function list_edge_components_defaults_version_to_latest_mobile(): void + { + $latest = app(DocsSearchService::class)->getLatestVersions()['mobile']; + + $response = $this->postJson('/api/mcp/message', [ + 'jsonrpc' => '2.0', + 'id' => 1, + 'method' => 'tools/call', + 'params' => [ + 'name' => 'list_edge_components', + 'arguments' => ['platform' => 'mobile'], + ], + ]); + + $response->assertOk(); + + $text = $response->json('result.content.0.text'); + + $this->assertStringContainsString("mobile v{$latest} EDGE components", $text); + $this->assertStringContainsString("Path: mobile/{$latest}/edge-components/", $text); + } + + #[Test] + public function list_apis_is_no_longer_registered(): void + { + $list = $this->postJson('/api/mcp/message', [ + 'jsonrpc' => '2.0', + 'id' => 1, + 'method' => 'tools/list', + ]); + + $tools = collect($list->json('result.tools'))->pluck('name')->all(); + $this->assertNotContains('list_apis', $tools); + + $call = $this->postJson('/api/mcp/message', [ + 'jsonrpc' => '2.0', + 'id' => 2, + 'method' => 'tools/call', + 'params' => [ + 'name' => 'list_apis', + 'arguments' => ['platform' => 'mobile', 'version' => '2'], + ], + ]); + + $this->assertTrue($call->json('result.isError')); + $this->assertStringContainsString('Unknown tool: list_apis', $call->json('result.content.0.text')); + } + + #[Test] + public function the_edge_components_rest_endpoint_lists_mobile_v4_components(): void + { + $response = $this->getJson('/api/mcp/edge-components/mobile/4'); + + $response->assertOk() + ->assertJsonStructure(['edge_components' => [['title', 'slug', 'description', 'section', 'id']]]); + + $slugs = collect($response->json('edge_components'))->pluck('slug'); + + $this->assertTrue($slugs->contains('button')); + $this->assertTrue($slugs->contains('text')); + $this->assertTrue( + collect($response->json('edge_components'))->every(fn ($c) => $c['section'] === 'edge-components' + || str_starts_with($c['section'], 'edge-components/')), + ); + } + + #[Test] + public function the_legacy_apis_rest_endpoint_is_gone(): void + { + $this->getJson('/api/mcp/apis/mobile/2')->assertNotFound(); + } } diff --git a/tests/Feature/McpSecurityTest.php b/tests/Feature/McpSecurityTest.php index ebeefc65..41e9200f 100644 --- a/tests/Feature/McpSecurityTest.php +++ b/tests/Feature/McpSecurityTest.php @@ -70,12 +70,12 @@ public function test_page_api_rejects_an_absolute_looking_section(): void $response->assertStatus(404); } - public function test_apis_endpoint_rejects_invalid_platform(): void + public function test_edge_components_endpoint_rejects_invalid_platform(): void { - $response = $this->getJson('/api/mcp/apis/../1'); + $response = $this->getJson('/api/mcp/edge-components/../1'); $response->assertStatus(200); - $response->assertJson(['apis' => []]); + $response->assertJson(['edge_components' => []]); } public function test_navigation_endpoint_rejects_invalid_version(): void