@schmadev/mcp-server 0.0.0-stage → 1.0.1

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.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ var server = new Server({
8
+ name: "schma-mcp",
9
+ version: "1.0.0"
10
+ }, {
11
+ capabilities: { tools: {} }
12
+ });
13
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
14
+ tools: [
15
+ {
16
+ name: "list_content_types",
17
+ description: "List all content types in the CMS project",
18
+ inputSchema: { type: "object", properties: {}, required: [] }
19
+ },
20
+ {
21
+ name: "get_entries",
22
+ description: "Get published entries of a given content type",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ contentType: { type: "string", description: 'The content type slug e.g. "post"' },
27
+ limit: { type: "number", description: "Max entries to return (default 10)" },
28
+ status: { type: "string", enum: ["published", "draft", "all"] }
29
+ },
30
+ required: ["contentType"]
31
+ }
32
+ },
33
+ {
34
+ name: "create_entry",
35
+ description: "Create a new content entry",
36
+ inputSchema: {
37
+ type: "object",
38
+ properties: {
39
+ contentType: { type: "string" },
40
+ data: { type: "object", description: "Field values matching the schema" },
41
+ status: { type: "string", enum: ["draft", "published"], default: "draft" }
42
+ },
43
+ required: ["contentType", "data"]
44
+ }
45
+ },
46
+ {
47
+ name: "update_entry",
48
+ description: "Update an existing content entry by ID",
49
+ inputSchema: {
50
+ type: "object",
51
+ properties: {
52
+ entryId: { type: "string" },
53
+ data: { type: "object" }
54
+ },
55
+ required: ["entryId", "data"]
56
+ }
57
+ },
58
+ {
59
+ name: "get_schema",
60
+ description: "Get schema fields for a content type",
61
+ inputSchema: {
62
+ type: "object",
63
+ properties: {
64
+ contentType: { type: "string", description: "The content type slug" }
65
+ },
66
+ required: ["contentType"]
67
+ }
68
+ },
69
+ {
70
+ name: "publish_entry",
71
+ description: "Publish an existing content entry by ID",
72
+ inputSchema: {
73
+ type: "object",
74
+ properties: {
75
+ entryId: { type: "string" }
76
+ },
77
+ required: ["entryId"]
78
+ }
79
+ },
80
+ {
81
+ name: "search_content",
82
+ description: "Search content semantically across all content types",
83
+ inputSchema: {
84
+ type: "object",
85
+ properties: {
86
+ query: { type: "string", description: "Natural language search query" }
87
+ },
88
+ required: ["query"]
89
+ }
90
+ }
91
+ ]
92
+ }));
93
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
94
+ const { name, arguments: args } = request.params;
95
+ const apiBase = process.env.SCHMA_API_URL || "http://localhost:3001";
96
+ const token = process.env.SCHMA_API_KEY || "";
97
+ const tenantSlug = process.env.SCHMA_TENANT_SLUG || "";
98
+ const headers = {
99
+ "Content-Type": "application/json"
100
+ };
101
+ if (token) {
102
+ headers["Authorization"] = `Bearer ${token}`;
103
+ }
104
+ if (tenantSlug) {
105
+ headers["x-tenant-slug"] = tenantSlug;
106
+ }
107
+ try {
108
+ switch (name) {
109
+ case "list_content_types": {
110
+ const res = await fetch(`${apiBase}/content-types`, { headers });
111
+ const types = await res.json();
112
+ return { content: [{ type: "text", text: JSON.stringify(types, null, 2) }] };
113
+ }
114
+ case "get_entries": {
115
+ const limit = args.limit ? String(args.limit) : "10";
116
+ const status = args.status ? String(args.status) : "published";
117
+ const params = new URLSearchParams({
118
+ typeSlug: String(args.contentType),
119
+ limit,
120
+ status
121
+ });
122
+ const res = await fetch(`${apiBase}/entries?${params}`, { headers });
123
+ const entries = await res.json();
124
+ return { content: [{ type: "text", text: JSON.stringify(entries, null, 2) }] };
125
+ }
126
+ case "create_entry": {
127
+ const res = await fetch(`${apiBase}/entries`, {
128
+ method: "POST",
129
+ headers,
130
+ body: JSON.stringify({ typeSlug: args.contentType, dataJson: args.data, status: args.status ?? "draft" })
131
+ });
132
+ const entry = await res.json();
133
+ return { content: [{ type: "text", text: `Created entry: ${entry.id || JSON.stringify(entry)}` }] };
134
+ }
135
+ case "update_entry": {
136
+ const res = await fetch(`${apiBase}/entries/${args.entryId}`, {
137
+ method: "PUT",
138
+ headers,
139
+ body: JSON.stringify({ dataJson: args.data })
140
+ });
141
+ const entry = await res.json();
142
+ return { content: [{ type: "text", text: `Updated: ${entry.id || JSON.stringify(entry)}` }] };
143
+ }
144
+ case "get_schema": {
145
+ const res = await fetch(`${apiBase}/content-types/${args.contentType}`, { headers });
146
+ const schema = await res.json();
147
+ return { content: [{ type: "text", text: JSON.stringify(schema, null, 2) }] };
148
+ }
149
+ case "publish_entry": {
150
+ const res = await fetch(`${apiBase}/entries/${args.entryId}`, {
151
+ method: "PUT",
152
+ headers,
153
+ body: JSON.stringify({ status: "published" })
154
+ });
155
+ const entry = await res.json();
156
+ return { content: [{ type: "text", text: `Published: ${entry.id || JSON.stringify(entry)}` }] };
157
+ }
158
+ case "search_content": {
159
+ const params = new URLSearchParams({
160
+ text: String(args.query),
161
+ limit: "10"
162
+ });
163
+ const res = await fetch(`${apiBase}/v1/delivery/entries/similar?${params}`, { headers });
164
+ const results = await res.json();
165
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
166
+ }
167
+ default:
168
+ throw new Error(`Unknown tool: ${name}`);
169
+ }
170
+ } catch (err) {
171
+ return { content: [{ type: "text", text: `Error calling tool ${name}: ${err.message}` }] };
172
+ }
173
+ });
174
+ async function main() {
175
+ const transport = new StdioServerTransport();
176
+ await server.connect(transport);
177
+ }
178
+ main().catch(console.error);
package/dist/index.mjs ADDED
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ var server = new Server({
8
+ name: "schma-mcp",
9
+ version: "1.0.0"
10
+ }, {
11
+ capabilities: { tools: {} }
12
+ });
13
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
14
+ tools: [
15
+ {
16
+ name: "list_content_types",
17
+ description: "List all content types in the CMS project",
18
+ inputSchema: { type: "object", properties: {}, required: [] }
19
+ },
20
+ {
21
+ name: "get_entries",
22
+ description: "Get published entries of a given content type",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ contentType: { type: "string", description: 'The content type slug e.g. "post"' },
27
+ limit: { type: "number", description: "Max entries to return (default 10)" },
28
+ status: { type: "string", enum: ["published", "draft", "all"] }
29
+ },
30
+ required: ["contentType"]
31
+ }
32
+ },
33
+ {
34
+ name: "create_entry",
35
+ description: "Create a new content entry",
36
+ inputSchema: {
37
+ type: "object",
38
+ properties: {
39
+ contentType: { type: "string" },
40
+ data: { type: "object", description: "Field values matching the schema" },
41
+ status: { type: "string", enum: ["draft", "published"], default: "draft" }
42
+ },
43
+ required: ["contentType", "data"]
44
+ }
45
+ },
46
+ {
47
+ name: "update_entry",
48
+ description: "Update an existing content entry by ID",
49
+ inputSchema: {
50
+ type: "object",
51
+ properties: {
52
+ entryId: { type: "string" },
53
+ data: { type: "object" }
54
+ },
55
+ required: ["entryId", "data"]
56
+ }
57
+ },
58
+ {
59
+ name: "search_content",
60
+ description: "Search content semantically across all content types",
61
+ inputSchema: {
62
+ type: "object",
63
+ properties: {
64
+ query: { type: "string", description: "Natural language search query" }
65
+ },
66
+ required: ["query"]
67
+ }
68
+ }
69
+ ]
70
+ }));
71
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
72
+ const { name, arguments: args } = request.params;
73
+ const apiBase = process.env.SCHMA_API_URL || "http://localhost:3001";
74
+ const token = process.env.SCHMA_API_KEY || "";
75
+ const headers = {
76
+ "Content-Type": "application/json"
77
+ };
78
+ if (token) {
79
+ headers["Authorization"] = `Bearer ${token}`;
80
+ }
81
+ try {
82
+ switch (name) {
83
+ case "list_content_types": {
84
+ const res = await fetch(`${apiBase}/content-types`, { headers });
85
+ const types = await res.json();
86
+ return { content: [{ type: "text", text: JSON.stringify(types, null, 2) }] };
87
+ }
88
+ case "get_entries": {
89
+ const limit = args.limit ? String(args.limit) : "10";
90
+ const status = args.status ? String(args.status) : "published";
91
+ const params = new URLSearchParams({
92
+ limit,
93
+ status
94
+ });
95
+ const res = await fetch(`${apiBase}/entries/${args.contentType}?${params}`, { headers });
96
+ const entries = await res.json();
97
+ return { content: [{ type: "text", text: JSON.stringify(entries, null, 2) }] };
98
+ }
99
+ case "create_entry": {
100
+ const res = await fetch(`${apiBase}/entries/${args.contentType}`, {
101
+ method: "POST",
102
+ headers,
103
+ body: JSON.stringify({ data: args.data, status: args.status ?? "draft" })
104
+ });
105
+ const entry = await res.json();
106
+ return { content: [{ type: "text", text: `Created entry: ${entry.id || JSON.stringify(entry)}` }] };
107
+ }
108
+ case "update_entry": {
109
+ const res = await fetch(`${apiBase}/entries/${args.entryId}`, {
110
+ method: "PATCH",
111
+ headers,
112
+ body: JSON.stringify({ data: args.data })
113
+ });
114
+ const entry = await res.json();
115
+ return { content: [{ type: "text", text: `Updated: ${entry.id || JSON.stringify(entry)}` }] };
116
+ }
117
+ case "search_content": {
118
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Semantic search not yet implemented." }, null, 2) }] };
119
+ }
120
+ default:
121
+ throw new Error(`Unknown tool: ${name}`);
122
+ }
123
+ } catch (err) {
124
+ return { content: [{ type: "text", text: `Error calling tool ${name}: ${err.message}` }] };
125
+ }
126
+ });
127
+ async function main() {
128
+ const transport = new StdioServerTransport();
129
+ await server.connect(transport);
130
+ }
131
+ main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,28 @@
1
1
  {
2
2
  "name": "@schmadev/mcp-server",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.1",
4
+ "description": "Schma CMS MCP Server for AI IDE Integration",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "schma-mcp": "dist/index.js"
9
+ },
10
+ "dependencies": {
11
+ "@modelcontextprotocol/sdk": "^1.4.1",
12
+ "zod": "^4.6.5"
13
+ },
14
+ "devDependencies": {
15
+ "@schmadev/typescript-config": "0.0.1",
16
+ "@types/node": "^20.0.0",
17
+ "tsup": "^8.0.0",
18
+ "typescript": "^5.0.0",
19
+ "vitest": "^5.0.3"
20
+ },
21
+ "license": "MIT",
22
+ "scripts": {
23
+ "test": "vitest run --passWithNoTests",
24
+ "build": "tsup src/index.ts --format esm --dts",
25
+ "dev": "tsup src/index.ts --format esm --watch",
26
+ "start": "node dist/index.js"
27
+ }
6
28
  }
package/src/index.ts ADDED
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
6
+
7
+ const server = new Server({
8
+ name: 'schma-mcp',
9
+ version: '1.0.0',
10
+ }, {
11
+ capabilities: { tools: {} }
12
+ });
13
+
14
+ // -- Tool: list content types --
15
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
16
+ tools: [
17
+ {
18
+ name: 'list_content_types',
19
+ description: 'List all content types in the CMS project',
20
+ inputSchema: { type: 'object', properties: {}, required: [] }
21
+ },
22
+ {
23
+ name: 'get_entries',
24
+ description: 'Get published entries of a given content type',
25
+ inputSchema: {
26
+ type: 'object',
27
+ properties: {
28
+ contentType: { type: 'string', description: 'The content type slug e.g. "post"' },
29
+ limit: { type: 'number', description: 'Max entries to return (default 10)' },
30
+ status: { type: 'string', enum: ['published', 'draft', 'all'] }
31
+ },
32
+ required: ['contentType']
33
+ }
34
+ },
35
+ {
36
+ name: 'create_entry',
37
+ description: 'Create a new content entry',
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {
41
+ contentType: { type: 'string' },
42
+ data: { type: 'object', description: 'Field values matching the schema' },
43
+ status: { type: 'string', enum: ['draft', 'published'], default: 'draft' }
44
+ },
45
+ required: ['contentType', 'data']
46
+ }
47
+ },
48
+ {
49
+ name: 'update_entry',
50
+ description: 'Update an existing content entry by ID',
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ entryId: { type: 'string' },
55
+ data: { type: 'object' }
56
+ },
57
+ required: ['entryId', 'data']
58
+ }
59
+ },
60
+ {
61
+ name: 'get_schema',
62
+ description: 'Get schema fields for a content type',
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ contentType: { type: 'string', description: 'The content type slug' }
67
+ },
68
+ required: ['contentType']
69
+ }
70
+ },
71
+ {
72
+ name: 'publish_entry',
73
+ description: 'Publish an existing content entry by ID',
74
+ inputSchema: {
75
+ type: 'object',
76
+ properties: {
77
+ entryId: { type: 'string' }
78
+ },
79
+ required: ['entryId']
80
+ }
81
+ },
82
+ {
83
+ name: 'search_content',
84
+ description: 'Search content semantically across all content types',
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {
88
+ query: { type: 'string', description: 'Natural language search query' }
89
+ },
90
+ required: ['query']
91
+ }
92
+ }
93
+ ]
94
+ }));
95
+
96
+ // -- Tool call handler --
97
+ server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
98
+ const { name, arguments: args } = request.params;
99
+
100
+ const apiBase = process.env.SCHMA_API_URL || 'http://localhost:3001';
101
+ const token = process.env.SCHMA_API_KEY || '';
102
+ const tenantSlug = process.env.SCHMA_TENANT_SLUG || '';
103
+
104
+ const headers: Record<string, string> = {
105
+ 'Content-Type': 'application/json'
106
+ };
107
+
108
+ if (token) {
109
+ headers['Authorization'] = `Bearer ${token}`;
110
+ }
111
+ if (tenantSlug) {
112
+ headers['x-tenant-slug'] = tenantSlug;
113
+ }
114
+
115
+ try {
116
+ switch (name) {
117
+ case 'list_content_types': {
118
+ const res = await fetch(`${apiBase}/content-types`, { headers });
119
+ const types = await res.json();
120
+ return { content: [{ type: 'text', text: JSON.stringify(types, null, 2) }] };
121
+ }
122
+
123
+ case 'get_entries': {
124
+ const limit = args.limit ? String(args.limit) : '10';
125
+ const status = args.status ? String(args.status) : 'published';
126
+
127
+ const params = new URLSearchParams({
128
+ typeSlug: String(args.contentType),
129
+ limit,
130
+ status
131
+ });
132
+
133
+ const res = await fetch(`${apiBase}/entries?${params}`, { headers });
134
+ const entries = await res.json();
135
+ return { content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }] };
136
+ }
137
+
138
+ case 'create_entry': {
139
+ const res = await fetch(`${apiBase}/entries`, {
140
+ method: 'POST',
141
+ headers,
142
+ body: JSON.stringify({ typeSlug: args.contentType, dataJson: args.data, status: args.status ?? 'draft' })
143
+ });
144
+ const entry = await res.json();
145
+ return { content: [{ type: 'text', text: `Created entry: ${entry.id || JSON.stringify(entry)}` }] };
146
+ }
147
+
148
+ case 'update_entry': {
149
+ const res = await fetch(`${apiBase}/entries/${args.entryId}`, {
150
+ method: 'PUT',
151
+ headers,
152
+ body: JSON.stringify({ dataJson: args.data })
153
+ });
154
+ const entry = await res.json();
155
+ return { content: [{ type: 'text', text: `Updated: ${entry.id || JSON.stringify(entry)}` }] };
156
+ }
157
+
158
+ case 'get_schema': {
159
+ const res = await fetch(`${apiBase}/content-types/${args.contentType}`, { headers });
160
+ const schema = await res.json();
161
+ return { content: [{ type: 'text', text: JSON.stringify(schema, null, 2) }] };
162
+ }
163
+
164
+ case 'publish_entry': {
165
+ const res = await fetch(`${apiBase}/entries/${args.entryId}`, {
166
+ method: 'PUT',
167
+ headers,
168
+ body: JSON.stringify({ status: 'published' })
169
+ });
170
+ const entry = await res.json();
171
+ return { content: [{ type: 'text', text: `Published: ${entry.id || JSON.stringify(entry)}` }] };
172
+ }
173
+
174
+ case 'search_content': {
175
+ const params = new URLSearchParams({
176
+ text: String(args.query),
177
+ limit: '10'
178
+ });
179
+
180
+ const res = await fetch(`${apiBase}/v1/delivery/entries/similar?${params}`, { headers });
181
+ const results = await res.json();
182
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
183
+ }
184
+
185
+ default:
186
+ throw new Error(`Unknown tool: ${name}`);
187
+ }
188
+ } catch (err: any) {
189
+ return { content: [{ type: 'text', text: `Error calling tool ${name}: ${err.message}` }] };
190
+ }
191
+ });
192
+
193
+ // Start the server on stdio (Cursor / Claude Code picks it up)
194
+ async function main() {
195
+ const transport = new StdioServerTransport();
196
+ await server.connect(transport);
197
+ }
198
+
199
+ main().catch(console.error);
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "@schmadev/typescript-config/base.json",
3
+ "compilerOptions": {
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "dist"
7
+ },
8
+ "include": ["src"],
9
+ "exclude": ["node_modules", "dist"]
10
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.