@mapled/mcp 0.1.0

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/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @mapled/mcp
2
+
3
+ MCP server for [Mapled](https://mapled.io) — a hosted headless CMS built for sites created with AI. Connect it to Claude, Cursor, or any MCP-capable agent, and the agent can model collections, fill in content, and wire the site up end to end.
4
+
5
+ ## Setup
6
+
7
+ Create a token in Mapled: project → Integrations → **Connect with AI**. Then add the server to your agent's MCP config:
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "mapled": {
13
+ "command": "npx",
14
+ "args": ["-y", "@mapled/mcp"],
15
+ "env": { "MAPLED_MCP_TOKEN": "mcp_live_…" }
16
+ }
17
+ }
18
+ }
19
+ ```
20
+
21
+ For Claude Code:
22
+
23
+ ```bash
24
+ claude mcp add mapled -e MAPLED_MCP_TOKEN=mcp_live_… -- npx -y @mapled/mcp
25
+ ```
26
+
27
+ The token is scoped to one project. Set `MAPLED_API_URL` only if you're not on the default `https://api.mapled.io`.
28
+
29
+ ## Tools
30
+
31
+ | Tool | What it does |
32
+ | --- | --- |
33
+ | `get_schema` | Read the project's collections and fields |
34
+ | `create_collection` | Add a collection or single |
35
+ | `add_field` | Add a field (short_text, long_text, rich_text, slug, image, number, boolean, date) |
36
+ | `add_records` | Insert draft records |
37
+ | `list_records` | Read a collection's draft records |
38
+ | `create_form` / `list_forms` | Set up public forms with spam protection |
39
+ | `get_connection` | Delivery key + API URL for wiring the site (`@mapled/next`) |
40
+ | `configure_revalidation` | Point the publish webhook at the site, get the signing secret |
41
+
42
+ Agents write drafts only — publishing stays with humans in the Mapled app, and nothing changes on the live site until someone presses Publish.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { createApiClient, createTools } from "./tools.js";
5
+ /** Mapled MCP server. Auth and scope come from the environment:
6
+ MAPLED_MCP_TOKEN — the project token from Connect with AI (required)
7
+ MAPLED_API_URL — API origin (default https://api.mapled.io) */
8
+ const token = process.env.MAPLED_MCP_TOKEN;
9
+ if (!token) {
10
+ console.error("MAPLED_MCP_TOKEN is missing. Create one in Mapled: project → Integrations → Connect with AI.");
11
+ process.exit(1);
12
+ }
13
+ const baseUrl = process.env.MAPLED_API_URL ?? "https://api.mapled.io";
14
+ const server = new McpServer({ name: "mapled", version: "0.1.0" });
15
+ const api = createApiClient(baseUrl, token);
16
+ for (const tool of createTools(api)) {
17
+ // The SDK's generic inference recurses on our union of shapes; the
18
+ // runtime contract is identical, so erase the generics here.
19
+ server.tool(tool.name, tool.description, tool.schema, async (args) => {
20
+ try {
21
+ const result = await tool.handler(args);
22
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23
+ }
24
+ catch (err) {
25
+ return {
26
+ isError: true,
27
+ content: [
28
+ {
29
+ type: "text",
30
+ text: err instanceof Error ? err.message : "Mapled request failed.",
31
+ },
32
+ ],
33
+ };
34
+ }
35
+ });
36
+ }
37
+ const transport = new StdioServerTransport();
38
+ await server.connect(transport);
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ /** Tool definitions for the Mapled agent API. Kept transport-agnostic so
3
+ they can be unit-tested without a live MCP session. */
4
+ export type ApiClient = {
5
+ request: (method: "GET" | "POST" | "PATCH", path: string, body?: unknown) => Promise<unknown>;
6
+ };
7
+ export declare function createApiClient(baseUrl: string, token: string): ApiClient;
8
+ export declare const FIELD_TYPES: readonly ["short_text", "long_text", "rich_text", "slug", "image", "number", "boolean", "date"];
9
+ export type ToolDef = {
10
+ name: string;
11
+ description: string;
12
+ schema: z.ZodRawShape;
13
+ handler: (args: never) => Promise<unknown>;
14
+ };
15
+ export declare function createTools(api: ApiClient): ToolDef[];
package/dist/tools.js ADDED
@@ -0,0 +1,126 @@
1
+ import { z } from "zod";
2
+ export function createApiClient(baseUrl, token) {
3
+ return {
4
+ async request(method, path, body) {
5
+ const res = await fetch(`${baseUrl}${path}`, {
6
+ method,
7
+ headers: {
8
+ authorization: `Bearer ${token}`,
9
+ ...(body !== undefined ? { "content-type": "application/json" } : {}),
10
+ },
11
+ body: body !== undefined ? JSON.stringify(body) : undefined,
12
+ });
13
+ const payload = (await res.json().catch(() => null));
14
+ if (!res.ok) {
15
+ throw new Error(payload?.error?.message ?? `Mapled API error (${res.status}).`);
16
+ }
17
+ return payload;
18
+ },
19
+ };
20
+ }
21
+ export const FIELD_TYPES = [
22
+ "short_text",
23
+ "long_text",
24
+ "rich_text",
25
+ "slug",
26
+ "image",
27
+ "number",
28
+ "boolean",
29
+ "date",
30
+ ];
31
+ export function createTools(api) {
32
+ return [
33
+ {
34
+ name: "get_schema",
35
+ description: "Read the project's full content schema: collections with their fields.",
36
+ schema: {},
37
+ handler: async () => api.request("GET", "/v1/agent/schema"),
38
+ },
39
+ {
40
+ name: "create_collection",
41
+ description: "Create a collection (many records of one shape) or a single (exactly one record, e.g. a homepage).",
42
+ schema: {
43
+ displayName: z.string().min(1).max(120),
44
+ kind: z.enum(["collection", "single"]).optional(),
45
+ },
46
+ handler: async (args) => api.request("POST", "/v1/agent/collections", {
47
+ displayName: args.displayName,
48
+ kind: args.kind ?? "collection",
49
+ }),
50
+ },
51
+ {
52
+ name: "add_field",
53
+ description: "Add a field to a collection. Types: short_text, long_text, rich_text, slug, image, number, boolean, date.",
54
+ schema: {
55
+ collectionKey: z.string().min(1).max(120),
56
+ displayName: z.string().min(1).max(120),
57
+ type: z.enum(FIELD_TYPES),
58
+ required: z.boolean().optional(),
59
+ helpText: z.string().max(500).optional(),
60
+ },
61
+ handler: async (args) => api.request("POST", `/v1/agent/collections/${encodeURIComponent(args.collectionKey)}/fields`, {
62
+ displayName: args.displayName,
63
+ type: args.type,
64
+ required: args.required ?? false,
65
+ helpText: args.helpText,
66
+ }),
67
+ },
68
+ {
69
+ name: "add_records",
70
+ description: "Insert up to 100 records into a collection. Each record maps field keys to values.",
71
+ schema: {
72
+ collectionKey: z.string().min(1).max(120),
73
+ records: z.array(z.record(z.string(), z.unknown())).min(1).max(100),
74
+ },
75
+ handler: async (args) => api.request("POST", `/v1/agent/collections/${encodeURIComponent(args.collectionKey)}/records`, { records: args.records }),
76
+ },
77
+ {
78
+ name: "create_form",
79
+ description: "Create a form the site can post submissions to. Returns the public submit endpoint; submissions are posted as flat JSON with the project delivery key in the X-Mapled-Key header. Include an empty hidden _gotcha honeypot field in the HTML form.",
80
+ schema: {
81
+ displayName: z.string().min(1).max(80),
82
+ key: z
83
+ .string()
84
+ .regex(/^[a-z][a-z0-9-]{0,63}$/)
85
+ .optional(),
86
+ spamProtection: z.boolean().optional(),
87
+ },
88
+ handler: async (args) => api.request("POST", "/v1/agent/forms", args),
89
+ },
90
+ {
91
+ name: "list_forms",
92
+ description: "List the project's forms (key, name, spam protection).",
93
+ schema: {},
94
+ handler: async () => api.request("GET", "/v1/agent/forms"),
95
+ },
96
+ {
97
+ name: "list_records",
98
+ description: "List a collection's draft records (id, title, data).",
99
+ schema: {
100
+ collectionKey: z.string().min(1).max(120),
101
+ },
102
+ handler: async (args) => api.request("GET", `/v1/agent/collections/${encodeURIComponent(args.collectionKey)}/records`),
103
+ },
104
+ {
105
+ name: "get_connection",
106
+ description: "Get what the site needs to read published Mapled content: the delivery key and API URL. " +
107
+ "Wire-up: npm install @mapled/next, put the key in the site's env as MAPLED_KEY, then " +
108
+ 'createClient({ key: process.env.MAPLED_KEY! }).getRecords("<collection>") in server components. ' +
109
+ "Content appears on the site only after a human presses Publish in Mapled.",
110
+ schema: {},
111
+ handler: async () => api.request("GET", "/v1/agent/connection"),
112
+ },
113
+ {
114
+ name: "configure_revalidation",
115
+ description: "Point Mapled's publish webhook at the site so published changes appear instantly. " +
116
+ "Pass the site's public revalidate URL (with @mapled/next: mount createRevalidateHandler " +
117
+ "from \"@mapled/next/server\" at /api/mapled/revalidate and pass that URL here). " +
118
+ "Returns the signing secret — store it in the site's env as MAPLED_WEBHOOK_SECRET. " +
119
+ "Local and private URLs are rejected; use the deployed site's URL.",
120
+ schema: {
121
+ url: z.string().min(8).max(2048),
122
+ },
123
+ handler: async (args) => api.request("PATCH", "/v1/agent/webhook", { url: args.url }),
124
+ },
125
+ ];
126
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@mapled/mcp",
3
+ "version": "0.1.0",
4
+ "description": "Mapled MCP server: lets AI agents build schema and content for one Mapled project.",
5
+ "license": "MIT",
6
+ "homepage": "https://mapled.io",
7
+ "keywords": [
8
+ "mapled",
9
+ "mcp",
10
+ "model-context-protocol",
11
+ "cms",
12
+ "headless-cms",
13
+ "ai-agents"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "type": "module",
19
+ "bin": {
20
+ "mapled-mcp": "dist/index.js"
21
+ },
22
+ "main": "dist/index.js",
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc -p tsconfig.json",
28
+ "test": "vitest run",
29
+ "typecheck": "tsc --noEmit -p tsconfig.json"
30
+ },
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.30.0",
33
+ "zod": "^3.25.0"
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "^5.8.0",
37
+ "vitest": "^3.2.0"
38
+ }
39
+ }