create-caspian-app 0.2.0-beta.26 → 0.2.0-beta.28
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/dist/.github/copilot-instructions.md +18 -13
- package/dist/AGENTS.md +17 -12
- package/dist/pyproject.toml +1 -1
- package/dist/settings/restart-mcp.ts +148 -0
- package/dist/settings/utils.ts +8 -4
- package/dist/src/lib/mcp/fastmcp.json +15 -0
- package/dist/src/lib/mcp/mcp_server.py +91 -0
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
- When `prisma/schema.prisma` changes, follow this order: run `npx prisma migrate dev`; if the change affects seed flow or `prisma/seed.ts`, run `npx prisma generate` and then `npx prisma db seed`; then run `npx ppy generate` so the Python ORM stays aligned with the schema.
|
|
14
14
|
- Reuse the existing Python database layer in `src/lib/prisma/**`; do not create a second app-owned database abstraction unless the user explicitly asks for one.
|
|
15
15
|
- Treat `src/lib/prisma/__init__.py`, `src/lib/prisma/db.py`, `src/lib/prisma/models.py`, and `settings/prisma-schema.json` as generated outputs owned by `npx ppy generate`; do not create or hand-edit them manually.
|
|
16
|
+
- When `caspian.config.json` has `mcp: true`, treat `src/lib/mcp/mcp_server.py` as the app-owned FastMCP server and `src/lib/mcp/fastmcp.json` as the default MCP config. Use `npm run mcp` or `fastmcp run src/lib/mcp/fastmcp.json`; do not assume root `fastmcp.json` auto-discovery.
|
|
16
17
|
- Keep auth policy in `src/lib/auth/auth_config.py` and keep auth bootstrap, middleware wiring, and provider registration in `main.py`.
|
|
17
18
|
- Use PulsePoint and `pp.rpc(...)` as the default frontend and client-to-server contract unless the user requests another stack.
|
|
18
19
|
- Treat `pp-component` on routes, layouts, and components, and `type="text/pp"` on owned PulsePoint scripts, as compiler-injected by the Python side; do not add them manually in authored templates unless the task is explicitly about runtime internals.
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
|
|
35
36
|
- Keep `src/lib/` for app-owned shared code, service wrappers, and reusable helpers.
|
|
36
37
|
- Reuse the generated `src/lib/prisma/` package for Python database access, but do not hand-edit files under `src/lib/prisma/`; regenerate them with `npx ppy generate` after schema changes.
|
|
38
|
+
- Keep app-owned MCP tools in `src/lib/mcp/mcp_server.py` and keep the default FastMCP config in `src/lib/mcp/fastmcp.json`. If those locations change, update `settings/restart-mcp.ts` and the MCP docs together.
|
|
37
39
|
- Keep auth policy in `src/lib/auth/auth_config.py`. Keep auth bootstrap and middleware order changes in `main.py`.
|
|
38
40
|
|
|
39
41
|
### `public/js/main.js`
|
|
@@ -77,17 +79,20 @@
|
|
|
77
79
|
|
|
78
80
|
- These files are the local documentation layer, not the runtime. Verify every behavior claim against the actual code that runs.
|
|
79
81
|
- Use this verification order:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
1. `caspian.config.json`, then `main.py`, `src/lib/**`, `public/js/**`, `prisma/**`, `src/app/**`
|
|
83
|
+
2. `.venv/Lib/site-packages/casp/**`
|
|
84
|
+
3. the markdown file being edited
|
|
83
85
|
- Keep repo-specific facts accurate when they matter:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
-
|
|
86
|
+
- `caspian.config.json` is the first config file to read for enabled workspace features and scan directories
|
|
87
|
+
- this workspace already has `src/lib/prisma/**`
|
|
88
|
+
- this workspace's app-owned FastMCP server lives in `src/lib/mcp/mcp_server.py`
|
|
89
|
+
- the default FastMCP config lives in `src/lib/mcp/fastmcp.json`
|
|
90
|
+
- `package.json` starts MCP through `npm run mcp`, which runs `settings/restart-mcp.ts`; manual FastMCP runs should pass the explicit nested config path because root auto-discovery does not find it
|
|
91
|
+
- auth policy lives in `src/lib/auth/auth_config.py`
|
|
92
|
+
- PulsePoint runtime lives in `public/js/pp-reactive-v2.js`
|
|
93
|
+
- `pp-component` is injected by the Python render pipeline, and `main.py` rewrites authored body scripts to `type="text/pp"`; authored route, layout, and component templates should not add those attributes manually
|
|
94
|
+
- route, layout, and component templates must keep a single top-level lowercase HTML root for `pp-component` injection, with any owned plain `<script>` kept inside that same root
|
|
95
|
+
- dynamic route params are passed to `page()` as a single positional `dict`
|
|
96
|
+
- `layout()` is sync-only in the installed runtime
|
|
97
|
+
- `StateManager` persistence depends on `request.state.session`, which is not bridged from `request.session` in the current `main.py`
|
|
98
|
+
- Keep `index.md` and cross-links aligned when adding or changing pages.
|
package/dist/AGENTS.md
CHANGED
|
@@ -39,11 +39,13 @@ Before making feature, tooling, or scaffolding decisions, read `caspian.config.j
|
|
|
39
39
|
- App-level auth policy lives in `src/lib/auth/auth_config.py`.
|
|
40
40
|
- `main.py` applies auth settings with `configure_auth(build_auth_settings())` and registers `GithubProvider()` plus `GoogleProvider()`.
|
|
41
41
|
- This workspace already has an app-owned Python database layer in `src/lib/prisma/`.
|
|
42
|
+
- This workspace now has an app-owned FastMCP server at `src/lib/mcp/mcp_server.py`, with `src/lib/mcp/fastmcp.json` as the default MCP server spec.
|
|
43
|
+
- `package.json` starts MCP through `npm run mcp`, which runs `settings/restart-mcp.ts` and prefers `src/lib/mcp/fastmcp.json` before legacy root configs. Direct FastMCP runs should pass the explicit nested config path.
|
|
42
44
|
- Reuse `src/lib/prisma/prisma`, `PrismaClient`, generated models, and helper types instead of creating a second Python database abstraction.
|
|
43
45
|
- Prisma schema source of truth is `prisma/schema.prisma`.
|
|
44
46
|
- The schema-change workflow in this workspace is: `npx prisma migrate dev`; if seed flow or `prisma/seed.ts` is involved, run `npx prisma generate` and then `npx prisma db seed`; then run `npx ppy generate`.
|
|
45
47
|
- `npx ppy generate` owns `src/lib/prisma/__init__.py`, `src/lib/prisma/db.py`, `src/lib/prisma/models.py`, and `settings/prisma-schema.json`; do not hand-edit those generated files.
|
|
46
|
-
- `caspian.config.json` is the first config file to check for enabled workspace features. In the current workspace it sets `backendOnly: false`, `tailwindcss: true`, `mcp:
|
|
48
|
+
- `caspian.config.json` is the first config file to check for enabled workspace features. In the current workspace it sets `backendOnly: false`, `tailwindcss: true`, `mcp: true`, `prisma: true`, `typescript: false`, and `componentScanDirs: ["src"]`.
|
|
47
49
|
- PulsePoint runtime code is shipped in `public/js/pp-reactive-v2.js` and loaded from `public/js/main.js`.
|
|
48
50
|
- `pp-component` is injected by the Python render pipeline onto page, layout, and component roots; authored route and component templates should not add it manually.
|
|
49
51
|
- `main.py` runs `transform_scripts(...)`, so authored body `<script>` tags are rewritten to `<script type="text/pp">` in rendered HTML; route, layout, and component templates should write plain `<script>` in source.
|
|
@@ -60,17 +62,18 @@ Before making feature, tooling, or scaffolding decisions, read `caspian.config.j
|
|
|
60
62
|
|
|
61
63
|
Use this map before making changes.
|
|
62
64
|
|
|
63
|
-
| Task area
|
|
64
|
-
|
|
|
65
|
-
| Project layout and file placement
|
|
66
|
-
| Feature availability and tooling switches | `caspian.config.json`
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
65
|
+
| Task area | Read first | Verify against |
|
|
66
|
+
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
|
|
67
|
+
| Project layout and file placement | `node_modules/caspian-utils/dist/docs/index.md`, `node_modules/caspian-utils/dist/docs/project-structure.md` | current workspace tree |
|
|
68
|
+
| Feature availability and tooling switches | `caspian.config.json` | current workspace tree, `main.py`, `prisma/**`, `public/js/**` |
|
|
69
|
+
| MCP server layout and launch flow | `node_modules/caspian-utils/dist/docs/mcp.md` | `settings/restart-mcp.ts`, `package.json`, `src/lib/mcp/**` |
|
|
70
|
+
| Routing, layouts, metadata | `node_modules/caspian-utils/dist/docs/routing.md` | `main.py`, `.venv/Lib/site-packages/casp/layout.py` |
|
|
71
|
+
| Auth, sessions, RBAC, providers | `node_modules/caspian-utils/dist/docs/auth.md` | `src/lib/auth/auth_config.py`, `main.py`, `.venv/Lib/site-packages/casp/auth.py` |
|
|
72
|
+
| RPC, data loading, streaming, uploads | `node_modules/caspian-utils/dist/docs/fetch-data.md`, `node_modules/caspian-utils/dist/docs/pulsepoint.md` | `.venv/Lib/site-packages/casp/rpc.py`, `public/js/pp-reactive-v2.js`, `main.py` |
|
|
73
|
+
| Server state | `node_modules/caspian-utils/dist/docs/state.md` | `.venv/Lib/site-packages/casp/state_manager.py`, `main.py` |
|
|
74
|
+
| Page caching | `node_modules/caspian-utils/dist/docs/cache.md` | `.venv/Lib/site-packages/casp/cache_handler.py`, `main.py` |
|
|
75
|
+
| Validation | `node_modules/caspian-utils/dist/docs/validation.md` | `.venv/Lib/site-packages/casp/validate.py` |
|
|
76
|
+
| Database and seed flow | `node_modules/caspian-utils/dist/docs/database.md` | `prisma/schema.prisma`, `prisma/seed.ts`, `src/lib/prisma/**` |
|
|
74
77
|
|
|
75
78
|
## Editing Rules
|
|
76
79
|
|
|
@@ -82,6 +85,7 @@ Use this map before making changes.
|
|
|
82
85
|
- When `prisma/schema.prisma` changes, run `npx prisma migrate dev` first. If seed flow or `prisma/seed.ts` is involved, run `npx prisma generate` and then `npx prisma db seed`. After that, run `npx ppy generate` so the Python ORM layer and `settings/prisma-schema.json` stay aligned.
|
|
83
86
|
- Keep auth policy in `src/lib/auth/auth_config.py`.
|
|
84
87
|
- Keep auth bootstrap, middleware ordering, provider wiring, and router behavior in `main.py`.
|
|
88
|
+
- When MCP is enabled, keep the app-owned FastMCP server in `src/lib/mcp/mcp_server.py` and the default config in `src/lib/mcp/fastmcp.json`. If those paths move, update `settings/restart-mcp.ts` and the MCP docs together.
|
|
85
89
|
- Use PulsePoint and `pp.rpc(...)` as the default frontend and browser-to-server contract unless the user explicitly wants another stack.
|
|
86
90
|
- Treat `pp-component` as a framework-owned attribute on authored templates. Document it, but do not manually add it in normal route or component HTML.
|
|
87
91
|
- Treat `type="text/pp"` on PulsePoint scripts as a render-time attribute too. In authored route, layout, and component HTML, write plain `<script>` and let Caspian rewrite it.
|
|
@@ -96,6 +100,7 @@ Use this map before making changes.
|
|
|
96
100
|
The packaged docs in this workspace are already mostly aligned with the installed runtime, but keep these repo-specific clarifications in mind:
|
|
97
101
|
|
|
98
102
|
- `database.md` is the source doc for the Prisma and Python ORM workflow in this repo: schema changes go through `npx prisma migrate dev`, optional `npx prisma generate` plus `npx prisma db seed`, then `npx ppy generate`, and generated ORM files under `src/lib/prisma/` plus `settings/prisma-schema.json` are not hand-edited.
|
|
103
|
+
- `mcp.md` is the source doc for the nested FastMCP config path, the app-owned MCP server location, direct FastMCP command usage, and the workspace runner discovery order.
|
|
99
104
|
- `state.md` is correct to warn that cross-request persistence depends on `request.state.session`, which is not bridged in the current `main.py`.
|
|
100
105
|
- `routing.md`, `components.md`, `auth.md`, `fetch-data.md`, `cache.md`, `pulsepoint.md`, and `validation.md` should continue to be validated against the installed `casp` package before any behavior claims are changed.
|
|
101
106
|
|
package/dist/pyproject.toml
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import caspianConfig from "../caspian.config.json";
|
|
4
|
+
import { createRestartableProcess, onExit } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
const projectRoot = process.cwd();
|
|
7
|
+
const localFastMcp =
|
|
8
|
+
process.platform === "win32"
|
|
9
|
+
? join(projectRoot, ".venv", "Scripts", "fastmcp.exe")
|
|
10
|
+
: join(projectRoot, ".venv", "bin", "fastmcp");
|
|
11
|
+
|
|
12
|
+
const fastMcpCommand = existsSync(localFastMcp) ? localFastMcp : "fastmcp";
|
|
13
|
+
|
|
14
|
+
const suppressedLinePatterns = [
|
|
15
|
+
/AuthlibDeprecationWarning/,
|
|
16
|
+
/^It will be compatible before version 2\.0\.0\.$/,
|
|
17
|
+
/^from authlib\.jose import /,
|
|
18
|
+
/^INFO:\s+Started server process/,
|
|
19
|
+
/^INFO:\s+Waiting for application startup\.$/,
|
|
20
|
+
/^INFO:\s+Application startup complete\.$/,
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function getServerSpec(): string | null {
|
|
24
|
+
const explicitSpec = process.env.MCP_SERVER_SPEC?.trim();
|
|
25
|
+
if (explicitSpec) {
|
|
26
|
+
return explicitSpec;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const defaultSpecs = ["src/lib/mcp/fastmcp.json", "fastmcp.json", "mcp.json"];
|
|
30
|
+
for (const relativeSpec of defaultSpecs) {
|
|
31
|
+
if (existsSync(join(projectRoot, relativeSpec))) {
|
|
32
|
+
return relativeSpec;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildArgs(serverSpec: string): string[] {
|
|
40
|
+
const args = ["run", serverSpec, "--no-banner"];
|
|
41
|
+
const transport = process.env.MCP_TRANSPORT?.trim();
|
|
42
|
+
const host = process.env.MCP_HOST?.trim();
|
|
43
|
+
const port = process.env.MCP_PORT?.trim();
|
|
44
|
+
const path = process.env.MCP_PATH?.trim();
|
|
45
|
+
const logLevel = process.env.MCP_LOG_LEVEL?.trim();
|
|
46
|
+
|
|
47
|
+
if (transport) args.push("--transport", transport);
|
|
48
|
+
if (host) args.push("--host", host);
|
|
49
|
+
if (port) args.push("--port", port);
|
|
50
|
+
if (path) args.push("--path", path);
|
|
51
|
+
if (logLevel) args.push("--log-level", logLevel);
|
|
52
|
+
|
|
53
|
+
return args;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createLineHandler(handleLine: (line: string) => void) {
|
|
57
|
+
let buffer = "";
|
|
58
|
+
|
|
59
|
+
return (chunk: Buffer) => {
|
|
60
|
+
buffer += chunk.toString();
|
|
61
|
+
const lines = buffer.split(/\r?\n/);
|
|
62
|
+
buffer = lines.pop() ?? "";
|
|
63
|
+
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
handleLine(line);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createMcpOutputHandler() {
|
|
71
|
+
let waitingForReadyUrl = false;
|
|
72
|
+
let readyReported = false;
|
|
73
|
+
|
|
74
|
+
return (line: string) => {
|
|
75
|
+
const trimmed = line.trim();
|
|
76
|
+
if (!trimmed) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (suppressedLinePatterns.some((pattern) => pattern.test(trimmed))) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (trimmed.includes("Starting MCP server")) {
|
|
85
|
+
waitingForReadyUrl = true;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (trimmed.includes("transport '") && trimmed.endsWith(" on")) {
|
|
90
|
+
waitingForReadyUrl = true;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const urlMatch = trimmed.match(/https?:\/\/\S+/);
|
|
95
|
+
if (
|
|
96
|
+
urlMatch &&
|
|
97
|
+
(waitingForReadyUrl || trimmed.includes("Uvicorn running on"))
|
|
98
|
+
) {
|
|
99
|
+
waitingForReadyUrl = false;
|
|
100
|
+
if (!readyReported) {
|
|
101
|
+
console.log(`[mcp] Ready at ${urlMatch[0]}`);
|
|
102
|
+
readyReported = true;
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (trimmed.startsWith("INFO:")) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
waitingForReadyUrl = false;
|
|
112
|
+
|
|
113
|
+
if (/traceback|exception|error/i.test(trimmed)) {
|
|
114
|
+
console.error(`[mcp] ${trimmed}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(`[mcp] ${trimmed}`);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!caspianConfig.mcp) {
|
|
123
|
+
console.log("[mcp] Disabled in caspian.config.json, skipping MCP startup.");
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const serverSpec = getServerSpec();
|
|
128
|
+
|
|
129
|
+
if (!serverSpec) {
|
|
130
|
+
console.log(
|
|
131
|
+
"[mcp] Enabled, but no FastMCP server spec was found. Create src/lib/mcp/fastmcp.json or set MCP_SERVER_SPEC to a FastMCP config, file, or URL.",
|
|
132
|
+
);
|
|
133
|
+
process.exit(0);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const handleMcpOutput = createMcpOutputHandler();
|
|
137
|
+
|
|
138
|
+
const runner = createRestartableProcess({
|
|
139
|
+
name: "mcp",
|
|
140
|
+
cmd: fastMcpCommand,
|
|
141
|
+
args: buildArgs(serverSpec),
|
|
142
|
+
startMessage: "[mcp] Starting MCP server...",
|
|
143
|
+
onStdout: createLineHandler(handleMcpOutput),
|
|
144
|
+
onStderr: createLineHandler(handleMcpOutput),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
runner.start();
|
|
148
|
+
onExit(() => runner.stop());
|
package/dist/settings/utils.ts
CHANGED
|
@@ -40,7 +40,7 @@ export function createSrcWatcher(
|
|
|
40
40
|
logPrefix?: string;
|
|
41
41
|
usePolling?: boolean;
|
|
42
42
|
interval?: number;
|
|
43
|
-
}
|
|
43
|
+
},
|
|
44
44
|
): FSWatcher {
|
|
45
45
|
const {
|
|
46
46
|
exts,
|
|
@@ -87,7 +87,7 @@ export class DebouncedWorker {
|
|
|
87
87
|
constructor(
|
|
88
88
|
private work: () => Promise<void> | void,
|
|
89
89
|
private debounceMs = 350,
|
|
90
|
-
private name = "worker"
|
|
90
|
+
private name = "worker",
|
|
91
91
|
) {}
|
|
92
92
|
|
|
93
93
|
schedule(reason?: string) {
|
|
@@ -124,6 +124,7 @@ export function createRestartableProcess(spec: {
|
|
|
124
124
|
cmd: string;
|
|
125
125
|
args?: string[];
|
|
126
126
|
stdio?: "inherit" | [any, any, any];
|
|
127
|
+
startMessage?: string;
|
|
127
128
|
gracefulSignal?: NodeJS.Signals;
|
|
128
129
|
forceKillAfterMs?: number;
|
|
129
130
|
windowsKillTree?: boolean;
|
|
@@ -135,6 +136,7 @@ export function createRestartableProcess(spec: {
|
|
|
135
136
|
cmd,
|
|
136
137
|
args = [],
|
|
137
138
|
stdio = ["ignore", "pipe", "pipe"],
|
|
139
|
+
startMessage,
|
|
138
140
|
gracefulSignal = "SIGINT",
|
|
139
141
|
forceKillAfterMs = 2000,
|
|
140
142
|
windowsKillTree = true,
|
|
@@ -145,7 +147,9 @@ export function createRestartableProcess(spec: {
|
|
|
145
147
|
let child: ChildProcess | null = null;
|
|
146
148
|
|
|
147
149
|
function start() {
|
|
148
|
-
console.log(
|
|
150
|
+
console.log(
|
|
151
|
+
startMessage ?? `[${name}] Starting: ${cmd} ${args.join(" ")}`.trim(),
|
|
152
|
+
);
|
|
149
153
|
child = spawn(cmd, args, { stdio, windowsHide: true });
|
|
150
154
|
|
|
151
155
|
child.stdout?.on("data", (buf: Buffer) => {
|
|
@@ -172,7 +176,7 @@ export function createRestartableProcess(spec: {
|
|
|
172
176
|
function killOnWindows(pid: number): Promise<void> {
|
|
173
177
|
return new Promise((resolve) => {
|
|
174
178
|
const cp = execFile("taskkill", ["/F", "/T", "/PID", String(pid)], () =>
|
|
175
|
-
resolve()
|
|
179
|
+
resolve(),
|
|
176
180
|
);
|
|
177
181
|
cp.on("error", () => resolve());
|
|
178
182
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
|
3
|
+
"source": {
|
|
4
|
+
"type": "filesystem",
|
|
5
|
+
"path": "src/lib/mcp/mcp_server.py",
|
|
6
|
+
"entrypoint": "mcp"
|
|
7
|
+
},
|
|
8
|
+
"deployment": {
|
|
9
|
+
"transport": "streamable-http",
|
|
10
|
+
"host": "127.0.0.1",
|
|
11
|
+
"port": 5101,
|
|
12
|
+
"path": "/mcp",
|
|
13
|
+
"log_level": "INFO"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from fastmcp import FastMCP
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
|
11
|
+
CASPIAN_CONFIG_PATH = PROJECT_ROOT / "caspian.config.json"
|
|
12
|
+
PACKAGE_JSON_PATH = PROJECT_ROOT / "package.json"
|
|
13
|
+
FILES_LIST_PATH = PROJECT_ROOT / "settings" / "files-list.json"
|
|
14
|
+
COMPONENT_MAP_PATH = PROJECT_ROOT / "settings" / "component-map.json"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
mcp = FastMCP(
|
|
18
|
+
name="Mapka MCP",
|
|
19
|
+
instructions=(
|
|
20
|
+
"Read-only workspace metadata for the Mapka Caspian application. "
|
|
21
|
+
"Use these tools for project configuration, generated file inventory, and component discovery."
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _load_json(path: Path, fallback: Any) -> Any:
|
|
27
|
+
if not path.exists():
|
|
28
|
+
return fallback
|
|
29
|
+
|
|
30
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
31
|
+
return json.load(handle)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _normalize_paths(paths: list[str]) -> list[str]:
|
|
35
|
+
return [item.removeprefix("./").replace("\\", "/") for item in paths]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@mcp.tool
|
|
39
|
+
def project_info() -> dict[str, Any]:
|
|
40
|
+
"""Return the core Caspian and package metadata for this workspace."""
|
|
41
|
+
|
|
42
|
+
config = _load_json(CASPIAN_CONFIG_PATH, {})
|
|
43
|
+
package = _load_json(PACKAGE_JSON_PATH, {})
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
"projectName": config.get("projectName") or package.get("name") or PROJECT_ROOT.name,
|
|
47
|
+
"projectRoot": str(PROJECT_ROOT),
|
|
48
|
+
"packageVersion": package.get("version"),
|
|
49
|
+
"caspianVersion": config.get("version"),
|
|
50
|
+
"browserSyncTarget": config.get("bsTarget"),
|
|
51
|
+
"featureFlags": {
|
|
52
|
+
"backendOnly": config.get("backendOnly"),
|
|
53
|
+
"tailwindcss": config.get("tailwindcss"),
|
|
54
|
+
"mcp": config.get("mcp"),
|
|
55
|
+
"prisma": config.get("prisma"),
|
|
56
|
+
"typescript": config.get("typescript"),
|
|
57
|
+
},
|
|
58
|
+
"componentScanDirs": config.get("componentScanDirs", []),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@mcp.tool
|
|
63
|
+
def workspace_files(kind: Literal["all", "app", "public"] = "all") -> dict[str, Any]:
|
|
64
|
+
"""Return the generated workspace file inventory, optionally filtered by area."""
|
|
65
|
+
|
|
66
|
+
files = _normalize_paths(_load_json(FILES_LIST_PATH, []))
|
|
67
|
+
|
|
68
|
+
if kind == "app":
|
|
69
|
+
selected = [path for path in files if path.startswith("src/app/")]
|
|
70
|
+
elif kind == "public":
|
|
71
|
+
selected = [path for path in files if path.startswith("public/")]
|
|
72
|
+
else:
|
|
73
|
+
selected = files
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
"kind": kind,
|
|
77
|
+
"count": len(selected),
|
|
78
|
+
"files": selected,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@mcp.tool
|
|
83
|
+
def component_inventory() -> dict[str, Any]:
|
|
84
|
+
"""Return the latest generated component inventory for the workspace."""
|
|
85
|
+
|
|
86
|
+
components = _load_json(COMPONENT_MAP_PATH, [])
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
"count": len(components),
|
|
90
|
+
"components": components,
|
|
91
|
+
}
|