@omnitend/dashboard-for-laravel 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +397 -0
- package/dist/components/base/DAccordion.vue.d.ts +12 -0
- package/dist/components/base/DAccordionItem.vue.d.ts +12 -0
- package/dist/components/base/DAlert.vue.d.ts +12 -0
- package/dist/components/base/DAvatar.vue.d.ts +12 -0
- package/dist/components/base/DBadge.vue.d.ts +12 -0
- package/dist/components/base/DBreadcrumb.vue.d.ts +12 -0
- package/dist/components/base/DButton.vue.d.ts +29 -0
- package/dist/components/base/DButtonGroup.vue.d.ts +12 -0
- package/dist/components/base/DButtonToolbar.vue.d.ts +12 -0
- package/dist/components/base/DCard.vue.d.ts +12 -0
- package/dist/components/base/DCarousel.vue.d.ts +12 -0
- package/dist/components/base/DCarouselSlide.vue.d.ts +12 -0
- package/dist/components/base/DCol.vue.d.ts +12 -0
- package/dist/components/base/DCollapse.vue.d.ts +12 -0
- package/dist/components/base/DContainer.vue.d.ts +12 -0
- package/dist/components/base/DDropdown.vue.d.ts +12 -0
- package/dist/components/base/DDropdownDivider.vue.d.ts +2 -0
- package/dist/components/base/DDropdownItem.vue.d.ts +12 -0
- package/dist/components/base/DForm.vue.d.ts +12 -0
- package/dist/components/base/DFormCheckbox.vue.d.ts +12 -0
- package/dist/components/base/DFormGroup.vue.d.ts +12 -0
- package/dist/components/base/DFormInput.vue.d.ts +2 -0
- package/dist/components/base/DFormInvalidFeedback.vue.d.ts +12 -0
- package/dist/components/base/DFormRadio.vue.d.ts +12 -0
- package/dist/components/base/DFormSelect.vue.d.ts +12 -0
- package/dist/components/base/DFormSpinbutton.vue.d.ts +12 -0
- package/dist/components/base/DFormTags.vue.d.ts +12 -0
- package/dist/components/base/DFormText.vue.d.ts +12 -0
- package/dist/components/base/DFormTextarea.vue.d.ts +2 -0
- package/dist/components/base/DImage.vue.d.ts +12 -0
- package/dist/components/base/DInputGroup.vue.d.ts +12 -0
- package/dist/components/base/DLink.vue.d.ts +12 -0
- package/dist/components/base/DListGroup.vue.d.ts +12 -0
- package/dist/components/base/DListGroupItem.vue.d.ts +12 -0
- package/dist/components/base/DModal.vue.d.ts +12 -0
- package/dist/components/base/DNav.vue.d.ts +12 -0
- package/dist/components/base/DNavItem.vue.d.ts +12 -0
- package/dist/components/base/DNavbar.vue.d.ts +12 -0
- package/dist/components/base/DNavbarBrand.vue.d.ts +12 -0
- package/dist/components/base/DNavbarNav.vue.d.ts +12 -0
- package/dist/components/base/DNavbarToggle.vue.d.ts +12 -0
- package/dist/components/base/DOffcanvas.vue.d.ts +12 -0
- package/dist/components/base/DOverlay.vue.d.ts +12 -0
- package/dist/components/base/DPagination.vue.d.ts +2 -0
- package/dist/components/base/DPlaceholder.vue.d.ts +12 -0
- package/dist/components/base/DPopover.vue.d.ts +12 -0
- package/dist/components/base/DProgress.vue.d.ts +12 -0
- package/dist/components/base/DRow.vue.d.ts +12 -0
- package/dist/components/base/DSpinner.vue.d.ts +2 -0
- package/dist/components/base/DTab.vue.d.ts +12 -0
- package/dist/components/base/DTable.vue.d.ts +26 -0
- package/dist/components/base/DTabs.vue.d.ts +12 -0
- package/dist/components/base/DToast.vue.d.ts +12 -0
- package/dist/components/base/DToaster.vue.d.ts +12 -0
- package/dist/components/base/DTooltip.vue.d.ts +12 -0
- package/dist/components/extended/DXBasicForm.vue.d.ts +39 -0
- package/dist/components/extended/DXDashboard.vue.d.ts +52 -0
- package/dist/components/extended/DXDashboardNavbar.vue.d.ts +53 -0
- package/dist/components/extended/DXDashboardSidebar.vue.d.ts +37 -0
- package/dist/components/extended/DXForm.vue.d.ts +31 -0
- package/dist/components/extended/DXTable.vue.d.ts +190 -0
- package/dist/composables/defineForm.d.ts +35 -0
- package/dist/composables/useForm.d.ts +46 -0
- package/dist/composables/useToast.d.ts +1 -0
- package/dist/dashboard-for-laravel.js +17748 -0
- package/dist/dashboard-for-laravel.js.map +1 -0
- package/dist/dashboard-for-laravel.umd.cjs +11 -0
- package/dist/dashboard-for-laravel.umd.cjs.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/style.css +5 -0
- package/dist/types/index.d.ts +37 -0
- package/dist/types/navigation.d.ts +17 -0
- package/dist/utils/api.d.ts +30 -0
- package/docs/public/api-reference.json +1932 -0
- package/docs/public/docs-map.md +85 -0
- package/docs/public/llms.txt +110 -0
- package/package.json +116 -0
- package/resources/css/theme.scss +219 -0
- package/resources/js/components/base/DAccordion.vue +21 -0
- package/resources/js/components/base/DAccordionItem.vue +14 -0
- package/resources/js/components/base/DAlert.vue +14 -0
- package/resources/js/components/base/DAvatar.vue +21 -0
- package/resources/js/components/base/DBadge.vue +14 -0
- package/resources/js/components/base/DBreadcrumb.vue +21 -0
- package/resources/js/components/base/DButton.vue +58 -0
- package/resources/js/components/base/DButtonGroup.vue +21 -0
- package/resources/js/components/base/DButtonToolbar.vue +21 -0
- package/resources/js/components/base/DCard.vue +35 -0
- package/resources/js/components/base/DCarousel.vue +21 -0
- package/resources/js/components/base/DCarouselSlide.vue +14 -0
- package/resources/js/components/base/DCol.vue +14 -0
- package/resources/js/components/base/DCollapse.vue +34 -0
- package/resources/js/components/base/DContainer.vue +14 -0
- package/resources/js/components/base/DDropdown.vue +16 -0
- package/resources/js/components/base/DDropdownDivider.vue +7 -0
- package/resources/js/components/base/DDropdownItem.vue +14 -0
- package/resources/js/components/base/DForm.vue +21 -0
- package/resources/js/components/base/DFormCheckbox.vue +14 -0
- package/resources/js/components/base/DFormGroup.vue +11 -0
- package/resources/js/components/base/DFormInput.vue +7 -0
- package/resources/js/components/base/DFormInvalidFeedback.vue +16 -0
- package/resources/js/components/base/DFormRadio.vue +21 -0
- package/resources/js/components/base/DFormSelect.vue +14 -0
- package/resources/js/components/base/DFormSpinbutton.vue +21 -0
- package/resources/js/components/base/DFormTags.vue +21 -0
- package/resources/js/components/base/DFormText.vue +16 -0
- package/resources/js/components/base/DFormTextarea.vue +7 -0
- package/resources/js/components/base/DImage.vue +21 -0
- package/resources/js/components/base/DInputGroup.vue +21 -0
- package/resources/js/components/base/DLink.vue +21 -0
- package/resources/js/components/base/DListGroup.vue +21 -0
- package/resources/js/components/base/DListGroupItem.vue +14 -0
- package/resources/js/components/base/DModal.vue +11 -0
- package/resources/js/components/base/DNav.vue +14 -0
- package/resources/js/components/base/DNavItem.vue +14 -0
- package/resources/js/components/base/DNavbar.vue +21 -0
- package/resources/js/components/base/DNavbarBrand.vue +14 -0
- package/resources/js/components/base/DNavbarNav.vue +14 -0
- package/resources/js/components/base/DNavbarToggle.vue +14 -0
- package/resources/js/components/base/DOffcanvas.vue +11 -0
- package/resources/js/components/base/DOverlay.vue +21 -0
- package/resources/js/components/base/DPagination.vue +7 -0
- package/resources/js/components/base/DPlaceholder.vue +21 -0
- package/resources/js/components/base/DPopover.vue +21 -0
- package/resources/js/components/base/DProgress.vue +21 -0
- package/resources/js/components/base/DRow.vue +14 -0
- package/resources/js/components/base/DSpinner.vue +7 -0
- package/resources/js/components/base/DTab.vue +14 -0
- package/resources/js/components/base/DTable.vue +62 -0
- package/resources/js/components/base/DTabs.vue +21 -0
- package/resources/js/components/base/DToast.vue +16 -0
- package/resources/js/components/base/DToaster.vue +16 -0
- package/resources/js/components/base/DTooltip.vue +21 -0
- package/resources/js/components/extended/DXBasicForm.vue +177 -0
- package/resources/js/components/extended/DXDashboard.vue +208 -0
- package/resources/js/components/extended/DXDashboardNavbar.vue +112 -0
- package/resources/js/components/extended/DXDashboardSidebar.vue +233 -0
- package/resources/js/components/extended/DXForm.vue +44 -0
- package/resources/js/components/extended/DXTable.vue +1345 -0
- package/resources/js/composables/defineForm.ts +78 -0
- package/resources/js/composables/useForm.ts +272 -0
- package/resources/js/composables/useToast.ts +1 -0
- package/resources/js/index.ts +118 -0
- package/resources/js/types/index.ts +61 -0
- package/resources/js/types/navigation.ts +19 -0
- package/resources/js/utils/api.ts +182 -0
- package/scripts/mcp-server.mjs +359 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
export interface ApiError {
|
|
2
|
+
message: string;
|
|
3
|
+
errors: Record<string, string[]>;
|
|
4
|
+
status: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ApiResponse<T = unknown> {
|
|
8
|
+
data: T;
|
|
9
|
+
response: Response;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface RequestOptions extends Omit<RequestInit, "body"> {
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
body?: BodyInit | null; // allow FormData/Blob/etc.
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class ApiClient {
|
|
18
|
+
private baseURL = "";
|
|
19
|
+
private defaultHeaders: Record<string, string> = {
|
|
20
|
+
Accept: "application/json",
|
|
21
|
+
"Content-Type": "application/json", // removed automatically for FormData
|
|
22
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
setBaseURL(url: string) {
|
|
26
|
+
this.baseURL = url.replace(/\/+$/, "");
|
|
27
|
+
}
|
|
28
|
+
setDefaultHeader(key: string, value: string) {
|
|
29
|
+
this.defaultHeaders[key] = value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private getCsrfToken(): string {
|
|
33
|
+
if (typeof document === 'undefined') {
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
const el = document.querySelector('meta[name="csrf-token"]');
|
|
37
|
+
return el?.getAttribute("content") ?? "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private toQuery(params: Record<string, unknown> = {}): string {
|
|
41
|
+
const search = new URLSearchParams();
|
|
42
|
+
const append = (k: string, v: unknown) => {
|
|
43
|
+
if (v === undefined || v === null) return;
|
|
44
|
+
if (Array.isArray(v)) v.forEach((x) => append(`${k}[]`, x));
|
|
45
|
+
else if (typeof v === "object") {
|
|
46
|
+
for (const [ck, cv] of Object.entries(
|
|
47
|
+
v as Record<string, unknown>,
|
|
48
|
+
)) {
|
|
49
|
+
append(`${k}[${ck}]`, cv);
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
search.append(k, String(v));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
for (const [k, v] of Object.entries(params)) append(k, v);
|
|
56
|
+
const qs = search.toString();
|
|
57
|
+
return qs ? `?${qs}` : "";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private handleError(response: Response, data: any): never {
|
|
61
|
+
const error: ApiError = {
|
|
62
|
+
message: data?.message ?? "An error occurred",
|
|
63
|
+
errors: (data?.errors as Record<string, string[]>) ?? {},
|
|
64
|
+
status: response.status,
|
|
65
|
+
};
|
|
66
|
+
switch (response.status) {
|
|
67
|
+
case 422:
|
|
68
|
+
error.message = data?.message ?? "Validation failed";
|
|
69
|
+
break;
|
|
70
|
+
case 401:
|
|
71
|
+
error.message = "Unauthenticated. Please log in.";
|
|
72
|
+
break;
|
|
73
|
+
case 403:
|
|
74
|
+
error.message = "Forbidden. You do not have permission.";
|
|
75
|
+
break;
|
|
76
|
+
case 404:
|
|
77
|
+
error.message = "Resource not found.";
|
|
78
|
+
break;
|
|
79
|
+
case 419:
|
|
80
|
+
error.message = "Page expired. Please refresh and try again.";
|
|
81
|
+
break;
|
|
82
|
+
case 500:
|
|
83
|
+
error.message = "Server error. Please try again later.";
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async request<T = unknown>(
|
|
90
|
+
url: string,
|
|
91
|
+
options: RequestOptions = {},
|
|
92
|
+
): Promise<ApiResponse<T>> {
|
|
93
|
+
const isFormData =
|
|
94
|
+
typeof FormData !== "undefined" && options.body instanceof FormData;
|
|
95
|
+
|
|
96
|
+
const headers: Record<string, string> = {
|
|
97
|
+
...this.defaultHeaders,
|
|
98
|
+
...(isFormData
|
|
99
|
+
? {}
|
|
100
|
+
: { "Content-Type": this.defaultHeaders["Content-Type"] }),
|
|
101
|
+
"X-CSRF-TOKEN": this.getCsrfToken(),
|
|
102
|
+
...options.headers,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Let fetch set the multipart boundary if FormData
|
|
106
|
+
if (isFormData) delete headers["Content-Type"];
|
|
107
|
+
|
|
108
|
+
const config: RequestOptions = {
|
|
109
|
+
credentials: "same-origin",
|
|
110
|
+
...options,
|
|
111
|
+
headers,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const resp = await fetch(this.baseURL + url, config);
|
|
115
|
+
|
|
116
|
+
// Fast-path for 204/205
|
|
117
|
+
if (resp.status === 204 || resp.status === 205) {
|
|
118
|
+
if (!resp.ok) {
|
|
119
|
+
this.handleError(resp, null);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { data: undefined as unknown as T, response: resp };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const ctype = resp.headers.get("Content-Type") || "";
|
|
126
|
+
const isJson = /\bjson\b/i.test(ctype);
|
|
127
|
+
const parsed = isJson
|
|
128
|
+
? await resp.json().catch(() => ({}))
|
|
129
|
+
: await resp.text();
|
|
130
|
+
|
|
131
|
+
if (!resp.ok) {
|
|
132
|
+
this.handleError(resp, parsed);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { data: parsed as T, response: resp };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async get<T = unknown>(
|
|
139
|
+
url: string,
|
|
140
|
+
params: Record<string, unknown> = {},
|
|
141
|
+
options: Omit<RequestOptions, "method"> = {},
|
|
142
|
+
) {
|
|
143
|
+
const full = `${url}${this.toQuery(params)}`;
|
|
144
|
+
return this.request<T>(full, { ...options, method: "GET" });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async post<T = unknown>(
|
|
148
|
+
url: string,
|
|
149
|
+
data: any = {},
|
|
150
|
+
options: Omit<RequestOptions, "method" | "body"> = {},
|
|
151
|
+
) {
|
|
152
|
+
const body = data instanceof FormData ? data : JSON.stringify(data);
|
|
153
|
+
return this.request<T>(url, { ...options, method: "POST", body });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async put<T = unknown>(
|
|
157
|
+
url: string,
|
|
158
|
+
data: any = {},
|
|
159
|
+
options: Omit<RequestOptions, "method" | "body"> = {},
|
|
160
|
+
) {
|
|
161
|
+
const body = data instanceof FormData ? data : JSON.stringify(data);
|
|
162
|
+
return this.request<T>(url, { ...options, method: "PUT", body });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async patch<T = unknown>(
|
|
166
|
+
url: string,
|
|
167
|
+
data: any = {},
|
|
168
|
+
options: Omit<RequestOptions, "method" | "body"> = {},
|
|
169
|
+
) {
|
|
170
|
+
const body = data instanceof FormData ? data : JSON.stringify(data);
|
|
171
|
+
return this.request<T>(url, { ...options, method: "PATCH", body });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async delete<T = unknown>(
|
|
175
|
+
url: string,
|
|
176
|
+
options: Omit<RequestOptions, "method"> = {},
|
|
177
|
+
) {
|
|
178
|
+
return this.request<T>(url, { ...options, method: "DELETE" });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const api = new ApiClient();
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server for Dashboard for Laravel Documentation
|
|
5
|
+
*
|
|
6
|
+
* Provides AI agents with structured access to documentation via MCP tools.
|
|
7
|
+
* Works with Claude Desktop, Claude Code, and any MCP-compatible client.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
+
import {
|
|
13
|
+
CallToolRequestSchema,
|
|
14
|
+
ListToolsRequestSchema,
|
|
15
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
16
|
+
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
17
|
+
import { join, dirname } from 'path';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const rootDir = join(__dirname, '..');
|
|
22
|
+
|
|
23
|
+
// Load generated documentation files
|
|
24
|
+
function loadApiReference() {
|
|
25
|
+
const path = join(rootDir, 'docs/public/api-reference.json');
|
|
26
|
+
if (!existsSync(path)) {
|
|
27
|
+
throw new Error('API reference not found. Run: npm run docs:generate:ai');
|
|
28
|
+
}
|
|
29
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function loadDocsMap() {
|
|
33
|
+
const path = join(rootDir, 'docs/public/docs-map.md');
|
|
34
|
+
if (!existsSync(path)) {
|
|
35
|
+
throw new Error('Docs map not found. Run: npm run docs:generate:ai');
|
|
36
|
+
}
|
|
37
|
+
return readFileSync(path, 'utf8');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function loadLlmsTxt() {
|
|
41
|
+
const path = join(rootDir, 'docs/public/llms.txt');
|
|
42
|
+
if (!existsSync(path)) {
|
|
43
|
+
throw new Error('llms.txt not found. Run: npm run docs:generate:ai');
|
|
44
|
+
}
|
|
45
|
+
return readFileSync(path, 'utf8');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Read guide/documentation files
|
|
49
|
+
function readGuide(slug) {
|
|
50
|
+
const guidePath = join(rootDir, `docs/src/pages/guide/${slug}.md`);
|
|
51
|
+
if (!existsSync(guidePath)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return readFileSync(guidePath, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Create MCP server
|
|
58
|
+
const server = new Server(
|
|
59
|
+
{
|
|
60
|
+
name: 'dashboard-for-laravel-docs',
|
|
61
|
+
version: '0.3.1',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
capabilities: {
|
|
65
|
+
tools: {},
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// List available tools
|
|
71
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
72
|
+
return {
|
|
73
|
+
tools: [
|
|
74
|
+
{
|
|
75
|
+
name: 'list_components',
|
|
76
|
+
description: 'List all components, optionally filtered by category (base/extended) or tag',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: {
|
|
80
|
+
category: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
enum: ['base', 'extended'],
|
|
83
|
+
description: 'Filter by component category',
|
|
84
|
+
},
|
|
85
|
+
tag: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'Filter by tag (e.g., forms, tables, navigation)',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'get_component',
|
|
94
|
+
description: 'Get detailed API information for a specific component (props, events, slots)',
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
name: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'Component name (e.g., DButton, DXTable)',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
required: ['name'],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'search_components',
|
|
108
|
+
description: 'Search components by name or description',
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
query: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: 'Search query',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
required: ['query'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'get_guide',
|
|
122
|
+
description: 'Get a documentation guide (installation, forms, theming, typescript, getting-started)',
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
slug: {
|
|
127
|
+
type: 'string',
|
|
128
|
+
enum: ['installation', 'forms', 'theming', 'typescript', 'getting-started'],
|
|
129
|
+
description: 'Guide slug',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
required: ['slug'],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'get_overview',
|
|
137
|
+
description: 'Get complete documentation overview (llms.txt content)',
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'get_docs_map',
|
|
145
|
+
description: 'Get hierarchical map of all available documentation',
|
|
146
|
+
inputSchema: {
|
|
147
|
+
type: 'object',
|
|
148
|
+
properties: {},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Handle tool calls
|
|
156
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
157
|
+
const { name, arguments: args } = request.params;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
switch (name) {
|
|
161
|
+
case 'list_components': {
|
|
162
|
+
const apiRef = loadApiReference();
|
|
163
|
+
let components = [
|
|
164
|
+
...apiRef.components.base.map(c => ({ ...c, category: 'base' })),
|
|
165
|
+
...apiRef.components.extended.map(c => ({ ...c, category: 'extended' })),
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
// Filter by category
|
|
169
|
+
if (args.category) {
|
|
170
|
+
components = components.filter(c => c.category === args.category);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Filter by tag (from frontmatter)
|
|
174
|
+
if (args.tag) {
|
|
175
|
+
const tag = args.tag.toLowerCase();
|
|
176
|
+
components = components.filter(c => {
|
|
177
|
+
const mdxPath = join(rootDir, `docs/src/pages/components/${c.category}/${c.name}.mdx`);
|
|
178
|
+
if (!existsSync(mdxPath)) return false;
|
|
179
|
+
const content = readFileSync(mdxPath, 'utf8');
|
|
180
|
+
const tagsMatch = content.match(/^tags:\s*(.+)$/m);
|
|
181
|
+
if (!tagsMatch) return false;
|
|
182
|
+
return tagsMatch[1].toLowerCase().includes(tag);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
content: [
|
|
188
|
+
{
|
|
189
|
+
type: 'text',
|
|
190
|
+
text: JSON.stringify(
|
|
191
|
+
{
|
|
192
|
+
total: components.length,
|
|
193
|
+
components: components.map(c => ({
|
|
194
|
+
name: c.name,
|
|
195
|
+
category: c.category,
|
|
196
|
+
description: c.description,
|
|
197
|
+
propsCount: c.props.length,
|
|
198
|
+
eventsCount: c.events.length,
|
|
199
|
+
slotsCount: c.slots.length,
|
|
200
|
+
})),
|
|
201
|
+
},
|
|
202
|
+
null,
|
|
203
|
+
2
|
|
204
|
+
),
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
case 'get_component': {
|
|
211
|
+
const apiRef = loadApiReference();
|
|
212
|
+
const allComponents = [
|
|
213
|
+
...apiRef.components.base,
|
|
214
|
+
...apiRef.components.extended,
|
|
215
|
+
];
|
|
216
|
+
const component = allComponents.find(c => c.name === args.name);
|
|
217
|
+
|
|
218
|
+
if (!component) {
|
|
219
|
+
return {
|
|
220
|
+
content: [
|
|
221
|
+
{
|
|
222
|
+
type: 'text',
|
|
223
|
+
text: `Component "${args.name}" not found. Available components: ${allComponents.map(c => c.name).join(', ')}`,
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
isError: true,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
content: [
|
|
232
|
+
{
|
|
233
|
+
type: 'text',
|
|
234
|
+
text: JSON.stringify(component, null, 2),
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
case 'search_components': {
|
|
241
|
+
const apiRef = loadApiReference();
|
|
242
|
+
const allComponents = [
|
|
243
|
+
...apiRef.components.base,
|
|
244
|
+
...apiRef.components.extended,
|
|
245
|
+
];
|
|
246
|
+
const query = args.query.toLowerCase();
|
|
247
|
+
|
|
248
|
+
const matches = allComponents.filter(c =>
|
|
249
|
+
c.name.toLowerCase().includes(query) ||
|
|
250
|
+
c.description.toLowerCase().includes(query) ||
|
|
251
|
+
c.filePath.toLowerCase().includes(query)
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: 'text',
|
|
258
|
+
text: JSON.stringify(
|
|
259
|
+
{
|
|
260
|
+
query: args.query,
|
|
261
|
+
matches: matches.length,
|
|
262
|
+
components: matches.map(c => ({
|
|
263
|
+
name: c.name,
|
|
264
|
+
category: c.category,
|
|
265
|
+
description: c.description,
|
|
266
|
+
filePath: c.filePath,
|
|
267
|
+
})),
|
|
268
|
+
},
|
|
269
|
+
null,
|
|
270
|
+
2
|
|
271
|
+
),
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
case 'get_guide': {
|
|
278
|
+
const content = readGuide(args.slug);
|
|
279
|
+
if (!content) {
|
|
280
|
+
return {
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: 'text',
|
|
284
|
+
text: `Guide "${args.slug}" not found.`,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
isError: true,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
content: [
|
|
293
|
+
{
|
|
294
|
+
type: 'text',
|
|
295
|
+
text: content,
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
case 'get_overview': {
|
|
302
|
+
const llmsTxt = loadLlmsTxt();
|
|
303
|
+
return {
|
|
304
|
+
content: [
|
|
305
|
+
{
|
|
306
|
+
type: 'text',
|
|
307
|
+
text: llmsTxt,
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
case 'get_docs_map': {
|
|
314
|
+
const docsMap = loadDocsMap();
|
|
315
|
+
return {
|
|
316
|
+
content: [
|
|
317
|
+
{
|
|
318
|
+
type: 'text',
|
|
319
|
+
text: docsMap,
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
default:
|
|
326
|
+
return {
|
|
327
|
+
content: [
|
|
328
|
+
{
|
|
329
|
+
type: 'text',
|
|
330
|
+
text: `Unknown tool: ${name}`,
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
isError: true,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
} catch (error) {
|
|
337
|
+
return {
|
|
338
|
+
content: [
|
|
339
|
+
{
|
|
340
|
+
type: 'text',
|
|
341
|
+
text: `Error: ${error.message}`,
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
isError: true,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Start server
|
|
350
|
+
async function main() {
|
|
351
|
+
const transport = new StdioServerTransport();
|
|
352
|
+
await server.connect(transport);
|
|
353
|
+
console.error('Dashboard for Laravel MCP Server running on stdio');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
main().catch((error) => {
|
|
357
|
+
console.error('Server error:', error);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
});
|