foundationworks-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 +138 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +316 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# foundationworks-mcp
|
|
2
|
+
|
|
3
|
+
Connect your AI coding agent to your product specs.
|
|
4
|
+
|
|
5
|
+
> Design in Foundation → Connect via MCP → Vibe code your product
|
|
6
|
+
|
|
7
|
+
## What is this?
|
|
8
|
+
|
|
9
|
+
MCP server that gives Claude, Cursor, Windsurf, and other AI agents access to your PRDs and TDDs in [Foundation](https://foundationworks.io).
|
|
10
|
+
|
|
11
|
+
**Foundation** is a design workspace for creating product requirements (PRDs) and technical design documents (TDDs). This MCP server lets your AI coding agent read those specs directly, so it builds what you actually designed.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### 1. Get your token
|
|
16
|
+
|
|
17
|
+
**Existing user:** Settings → Agent Tokens → Create Token
|
|
18
|
+
|
|
19
|
+
**New here?** [Sign up at foundationworks.io](https://foundationworks.io)
|
|
20
|
+
|
|
21
|
+
### 2. Configure your AI tool
|
|
22
|
+
|
|
23
|
+
**Claude Desktop** (`~/.claude/config.json`):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"foundation": {
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": ["-y", "foundationworks-mcp"],
|
|
31
|
+
"env": {
|
|
32
|
+
"FOUNDATION_TOKEN": "mcp_your_token_here"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Claude Code** (`~/.claude/settings.json`):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"foundation": {
|
|
45
|
+
"command": "npx",
|
|
46
|
+
"args": ["-y", "foundationworks-mcp"],
|
|
47
|
+
"env": {
|
|
48
|
+
"FOUNDATION_TOKEN": "mcp_your_token_here"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Cursor** (MCP settings):
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"foundation": {
|
|
60
|
+
"command": "npx",
|
|
61
|
+
"args": ["-y", "foundationworks-mcp"],
|
|
62
|
+
"env": {
|
|
63
|
+
"FOUNDATION_TOKEN": "mcp_your_token_here"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Zed** (`~/.config/zed/settings.json`):
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"context_servers": {
|
|
74
|
+
"foundation": {
|
|
75
|
+
"command": "npx",
|
|
76
|
+
"args": ["-y", "foundationworks-mcp"],
|
|
77
|
+
"env": {
|
|
78
|
+
"FOUNDATION_TOKEN": "mcp_your_token_here"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. Start building
|
|
86
|
+
|
|
87
|
+
Ask your AI: *"Check my PRD for the authentication requirements"*
|
|
88
|
+
|
|
89
|
+
## Tools
|
|
90
|
+
|
|
91
|
+
| Tool | Description |
|
|
92
|
+
|------|-------------|
|
|
93
|
+
| `foundation_menu` | List available sections with summaries, keywords, and token counts |
|
|
94
|
+
| `foundation_fetch` | Get full content for specific sections |
|
|
95
|
+
| `foundation_search` | Semantic search across your specs |
|
|
96
|
+
| `foundation_comment` | Post deviation reports or suggestions back to your docs |
|
|
97
|
+
|
|
98
|
+
## Example Workflow
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
You: "What does the PRD say about user authentication?"
|
|
102
|
+
|
|
103
|
+
AI: [calls foundation_menu to see available sections]
|
|
104
|
+
AI: [calls foundation_search with "user authentication"]
|
|
105
|
+
AI: [calls foundation_fetch to get the relevant sections]
|
|
106
|
+
AI: "Based on your PRD, authentication should use..."
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
When implementation differs from spec:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
AI: [calls foundation_comment with a deviation_report]
|
|
113
|
+
AI: "I've noted that we're using JWT instead of sessions as specified.
|
|
114
|
+
Posted a deviation report to the PRD for your team to review."
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Environment Variables
|
|
118
|
+
|
|
119
|
+
| Variable | Required | Default | Description |
|
|
120
|
+
|----------|----------|---------|-------------|
|
|
121
|
+
| `FOUNDATION_TOKEN` | Yes | — | Your agent token from Foundation |
|
|
122
|
+
| `FOUNDATION_API_URL` | No | `https://app.foundationworks.io/api/mcp` | API base URL |
|
|
123
|
+
|
|
124
|
+
## Why use this?
|
|
125
|
+
|
|
126
|
+
**Without Foundation MCP:**
|
|
127
|
+
- Copy-paste specs into chat
|
|
128
|
+
- AI forgets context between sessions
|
|
129
|
+
- No record of what the AI actually built vs. what was designed
|
|
130
|
+
|
|
131
|
+
**With Foundation MCP:**
|
|
132
|
+
- AI reads your specs directly
|
|
133
|
+
- Specs stay up-to-date in one place
|
|
134
|
+
- Deviation reports track where implementation differs from design
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Foundation MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Provides MCP (Model Context Protocol) access to Foundation PRD/TDD documents.
|
|
6
|
+
* Enables AI coding agents to intelligently select and fetch relevant context.
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* FOUNDATION_TOKEN - Required: Your Foundation agent token (mcp_xxx)
|
|
10
|
+
* FOUNDATION_API_URL - Optional: API base URL (default: https://app.foundationworks.io/api/mcp)
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Foundation MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Provides MCP (Model Context Protocol) access to Foundation PRD/TDD documents.
|
|
6
|
+
* Enables AI coding agents to intelligently select and fetch relevant context.
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* FOUNDATION_TOKEN - Required: Your Foundation agent token (mcp_xxx)
|
|
10
|
+
* FOUNDATION_API_URL - Optional: API base URL (default: https://app.foundationworks.io/api/mcp)
|
|
11
|
+
*/
|
|
12
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
13
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
// Configuration
|
|
16
|
+
const FOUNDATION_TOKEN = process.env.FOUNDATION_TOKEN;
|
|
17
|
+
const FOUNDATION_API_URL = process.env.FOUNDATION_API_URL || "https://app.foundationworks.io/api/mcp";
|
|
18
|
+
if (!FOUNDATION_TOKEN) {
|
|
19
|
+
console.error("Error: FOUNDATION_TOKEN environment variable is required");
|
|
20
|
+
console.error("Get your token from Foundation Settings → Agent Tokens");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
// Tool definitions
|
|
24
|
+
const tools = [
|
|
25
|
+
{
|
|
26
|
+
name: "foundation_menu",
|
|
27
|
+
description: `Get a menu of all available PRD/TDD sections with summaries, keywords, and token counts.
|
|
28
|
+
Use this first to understand what context is available and decide which sections to fetch.
|
|
29
|
+
Returns section metadata without full content to minimize token usage.`,
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {},
|
|
33
|
+
required: [],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "foundation_fetch",
|
|
38
|
+
description: `Fetch full content for specific document sections.
|
|
39
|
+
Use after foundation_menu to retrieve only the sections you need.
|
|
40
|
+
Supports both PRD and TDD sections with optional prefix (e.g., 'tdd.api_design').`,
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
sections: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "Comma-separated section keys to fetch (e.g., 'epics,background' or 'prd.epics,tdd.api_design')",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
required: ["sections"],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "foundation_search",
|
|
54
|
+
description: `Search across PRD/TDD sections using combined keyword and semantic matching.
|
|
55
|
+
Returns relevant sections ranked by relevance score (keyword 60% + semantic 40%).
|
|
56
|
+
Use when you need to find specific information but don't know which section contains it.`,
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
query: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "Search query (e.g., 'authentication flow' or 'database schema')",
|
|
63
|
+
},
|
|
64
|
+
limit: {
|
|
65
|
+
type: "number",
|
|
66
|
+
description: "Maximum number of results (default: 10, max: 50)",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
required: ["query"],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "foundation_comment",
|
|
74
|
+
description: `Post a comment on a document section. Use for:
|
|
75
|
+
- deviation_report: When implementation differs from spec
|
|
76
|
+
- suggestion: Proposing improvements to the document
|
|
77
|
+
- discussion: General questions or notes
|
|
78
|
+
|
|
79
|
+
Requires read access to the document type.`,
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
document_type: {
|
|
84
|
+
type: "string",
|
|
85
|
+
enum: ["prd", "tdd"],
|
|
86
|
+
description: "Type of document to comment on",
|
|
87
|
+
},
|
|
88
|
+
document_id: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "Document ID (from context menu response)",
|
|
91
|
+
},
|
|
92
|
+
section_key: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Section key to attach comment to (e.g., 'epics', 'api_design')",
|
|
95
|
+
},
|
|
96
|
+
content: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "Comment content (max 10000 characters)",
|
|
99
|
+
},
|
|
100
|
+
comment_type: {
|
|
101
|
+
type: "string",
|
|
102
|
+
enum: ["deviation_report", "suggestion", "discussion"],
|
|
103
|
+
description: "Type of comment (default: deviation_report)",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
required: ["document_type", "document_id", "section_key", "content"],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
// API client
|
|
111
|
+
async function apiRequest(endpoint, options = {}) {
|
|
112
|
+
const url = `${FOUNDATION_API_URL}${endpoint}`;
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(url, {
|
|
115
|
+
...options,
|
|
116
|
+
headers: {
|
|
117
|
+
Authorization: `Bearer ${FOUNDATION_TOKEN}`,
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
...options.headers,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
const data = await response.json();
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const errorMessage = data.detail?.message || data.detail || data.error || "Unknown error";
|
|
125
|
+
return { success: false, error: `${response.status}: ${errorMessage}` };
|
|
126
|
+
}
|
|
127
|
+
return { success: true, data };
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const message = error instanceof Error ? error.message : "Network error";
|
|
131
|
+
return { success: false, error: message };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Tool handlers
|
|
135
|
+
async function handleGetContextMenu() {
|
|
136
|
+
const result = await apiRequest("/context-menu");
|
|
137
|
+
if (!result.success) {
|
|
138
|
+
return `Error fetching context menu: ${result.error}`;
|
|
139
|
+
}
|
|
140
|
+
const data = result.data;
|
|
141
|
+
// Format for easy reading
|
|
142
|
+
let output = "# Available Context\n\n";
|
|
143
|
+
if (data.data.prd) {
|
|
144
|
+
output += `## PRD: ${data.data.prd.title}\n`;
|
|
145
|
+
output += `ID: ${data.data.prd.id}\n\n`;
|
|
146
|
+
for (const section of data.data.prd.sections) {
|
|
147
|
+
output += `### ${section.title} (${section.key})\n`;
|
|
148
|
+
output += `- Tokens: ${section.token_count}\n`;
|
|
149
|
+
output += `- Keywords: ${section.keywords.join(", ") || "none"}\n`;
|
|
150
|
+
if (section.summary) {
|
|
151
|
+
output += `- Summary: ${section.summary}\n`;
|
|
152
|
+
}
|
|
153
|
+
output += "\n";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (data.data.tdd) {
|
|
157
|
+
output += `## TDD: ${data.data.tdd.title}\n`;
|
|
158
|
+
output += `ID: ${data.data.tdd.id}\n\n`;
|
|
159
|
+
for (const section of data.data.tdd.sections) {
|
|
160
|
+
output += `### ${section.title} (${section.key})\n`;
|
|
161
|
+
output += `- Tokens: ${section.token_count}\n`;
|
|
162
|
+
output += `- Keywords: ${section.keywords.join(", ") || "none"}\n`;
|
|
163
|
+
if (section.summary) {
|
|
164
|
+
output += `- Summary: ${section.summary}\n`;
|
|
165
|
+
}
|
|
166
|
+
output += "\n";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
output += `---\nTotal tokens if fetching all: ${data.data.total_tokens}\n`;
|
|
170
|
+
output += `\nUse foundation_fetch with specific section keys to fetch full content.`;
|
|
171
|
+
return output;
|
|
172
|
+
}
|
|
173
|
+
async function handleGetContext(sections) {
|
|
174
|
+
const result = await apiRequest(`/context?sections=${encodeURIComponent(sections)}`);
|
|
175
|
+
if (!result.success) {
|
|
176
|
+
return `Error fetching context: ${result.error}`;
|
|
177
|
+
}
|
|
178
|
+
const data = result.data;
|
|
179
|
+
let output = "# Requested Context\n\n";
|
|
180
|
+
if (data.data.prd) {
|
|
181
|
+
output += "## PRD Sections\n\n";
|
|
182
|
+
for (const [key, section] of Object.entries(data.data.prd)) {
|
|
183
|
+
output += `### ${section.title} (${key})\n`;
|
|
184
|
+
output += "```json\n";
|
|
185
|
+
output += JSON.stringify(section.content, null, 2);
|
|
186
|
+
output += "\n```\n\n";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (data.data.tdd) {
|
|
190
|
+
output += "## TDD Sections\n\n";
|
|
191
|
+
for (const [key, section] of Object.entries(data.data.tdd)) {
|
|
192
|
+
output += `### ${section.title} (${key})\n`;
|
|
193
|
+
output += "```json\n";
|
|
194
|
+
output += JSON.stringify(section.content, null, 2);
|
|
195
|
+
output += "\n```\n\n";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
output += `---\nTotal tokens: ${data.data.total_tokens}`;
|
|
199
|
+
return output;
|
|
200
|
+
}
|
|
201
|
+
async function handleSearchContext(query, limit) {
|
|
202
|
+
const params = new URLSearchParams({ q: query });
|
|
203
|
+
if (limit)
|
|
204
|
+
params.set("limit", String(limit));
|
|
205
|
+
const result = await apiRequest(`/search?${params}`);
|
|
206
|
+
if (!result.success) {
|
|
207
|
+
return `Error searching: ${result.error}`;
|
|
208
|
+
}
|
|
209
|
+
const data = result.data;
|
|
210
|
+
let output = `# Search Results for "${data.data.query}"\n\n`;
|
|
211
|
+
output += `Found ${data.data.total_matches} matching sections:\n\n`;
|
|
212
|
+
for (const match of data.data.matches) {
|
|
213
|
+
output += `## ${match.section_title} (${match.document_type}.${match.section_key})\n`;
|
|
214
|
+
output += `- Relevance: ${match.relevance} (${match.relevance_score})\n`;
|
|
215
|
+
output += `- Matched keywords: ${match.matched_keywords.join(", ") || "semantic match"}\n`;
|
|
216
|
+
output += `- Tokens: ${match.token_count}\n`;
|
|
217
|
+
if (match.summary) {
|
|
218
|
+
output += `- Summary: ${match.summary}\n`;
|
|
219
|
+
}
|
|
220
|
+
output += "\n";
|
|
221
|
+
}
|
|
222
|
+
output += `\nUse foundation_fetch with section keys to fetch full content.`;
|
|
223
|
+
return output;
|
|
224
|
+
}
|
|
225
|
+
async function handlePostComment(documentType, documentId, sectionKey, content, commentType = "deviation_report") {
|
|
226
|
+
const result = await apiRequest("/comments", {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
document_type: documentType,
|
|
230
|
+
document_id: documentId,
|
|
231
|
+
section_key: sectionKey,
|
|
232
|
+
content,
|
|
233
|
+
comment_type: commentType,
|
|
234
|
+
}),
|
|
235
|
+
});
|
|
236
|
+
if (!result.success) {
|
|
237
|
+
return `Error posting comment: ${result.error}`;
|
|
238
|
+
}
|
|
239
|
+
const data = result.data;
|
|
240
|
+
return `Comment posted successfully!\n- ID: ${data.id}\n- Type: ${data.comment_type}\n- Status: ${data.status}`;
|
|
241
|
+
}
|
|
242
|
+
// Create and configure server
|
|
243
|
+
const server = new Server({
|
|
244
|
+
name: "foundationworks-mcp",
|
|
245
|
+
version: "0.1.0",
|
|
246
|
+
}, {
|
|
247
|
+
capabilities: {
|
|
248
|
+
tools: {},
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
// Register handlers
|
|
252
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
253
|
+
tools,
|
|
254
|
+
}));
|
|
255
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
256
|
+
const { name, arguments: args } = request.params;
|
|
257
|
+
try {
|
|
258
|
+
let result;
|
|
259
|
+
switch (name) {
|
|
260
|
+
case "foundation_menu":
|
|
261
|
+
result = await handleGetContextMenu();
|
|
262
|
+
break;
|
|
263
|
+
case "foundation_fetch":
|
|
264
|
+
if (!args?.sections || typeof args.sections !== "string") {
|
|
265
|
+
result = "Error: 'sections' parameter is required";
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
result = await handleGetContext(args.sections);
|
|
269
|
+
}
|
|
270
|
+
break;
|
|
271
|
+
case "foundation_search":
|
|
272
|
+
if (!args?.query || typeof args.query !== "string") {
|
|
273
|
+
result = "Error: 'query' parameter is required";
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
result = await handleSearchContext(args.query, typeof args.limit === "number" ? args.limit : undefined);
|
|
277
|
+
}
|
|
278
|
+
break;
|
|
279
|
+
case "foundation_comment":
|
|
280
|
+
if (!args?.document_type ||
|
|
281
|
+
!args?.document_id ||
|
|
282
|
+
!args?.section_key ||
|
|
283
|
+
!args?.content) {
|
|
284
|
+
result =
|
|
285
|
+
"Error: document_type, document_id, section_key, and content are required";
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
result = await handlePostComment(args.document_type, args.document_id, args.section_key, args.content, args.comment_type || "deviation_report");
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
default:
|
|
292
|
+
result = `Unknown tool: ${name}`;
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
content: [{ type: "text", text: result }],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
300
|
+
return {
|
|
301
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
302
|
+
isError: true,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
// Start server
|
|
307
|
+
async function main() {
|
|
308
|
+
const transport = new StdioServerTransport();
|
|
309
|
+
await server.connect(transport);
|
|
310
|
+
console.error("Foundation MCP Server started");
|
|
311
|
+
}
|
|
312
|
+
main().catch((error) => {
|
|
313
|
+
console.error("Fatal error:", error);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
});
|
|
316
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "foundationworks-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Connect your AI coding agent to your product specs in Foundation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"foundationworks-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"start:local": "node -r dotenv/config dist/index.js",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"test:e2e": "vitest run tests/e2e.test.ts",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"foundation",
|
|
24
|
+
"prd",
|
|
25
|
+
"tdd",
|
|
26
|
+
"ai-agents"
|
|
27
|
+
],
|
|
28
|
+
"author": "Foundation",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^20.10.0",
|
|
35
|
+
"dotenv": "^16.4.0",
|
|
36
|
+
"typescript": "^5.3.0",
|
|
37
|
+
"vitest": "^2.1.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md"
|
|
45
|
+
],
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/foundationworks/foundation.git",
|
|
49
|
+
"directory": "packages/mcp-server"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://foundationworks.io",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/foundationworks/foundation/issues"
|
|
54
|
+
}
|
|
55
|
+
}
|