@mifactory-bot/spec-api 1.0.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/index.js +98 -0
- package/package.json +18 -0
- package/server.json +37 -0
package/index.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const bodyParser = require('body-parser');
|
|
3
|
+
const Stripe = require('stripe');
|
|
4
|
+
const fetch = require('node-fetch');
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
app.use(bodyParser.json());
|
|
8
|
+
|
|
9
|
+
const API_KEY = process.env.API_KEY || 'secret-api-key';
|
|
10
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
|
11
|
+
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
|
12
|
+
|
|
13
|
+
app.use((req, res, next) => {
|
|
14
|
+
const key = req.headers['x-api-key'];
|
|
15
|
+
if (!key || key !== API_KEY) {
|
|
16
|
+
return res.status(401).json({ error: 'Unauthorized' });
|
|
17
|
+
}
|
|
18
|
+
next();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function callClaudeApi(prompt) {
|
|
22
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
'x-api-key': ANTHROPIC_API_KEY
|
|
27
|
+
},
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
model: 'claude-haiku-3-5',
|
|
30
|
+
messages: [{ role: 'user', content: prompt }]
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const text = await response.text();
|
|
35
|
+
throw new Error('Claude API error: ' + text);
|
|
36
|
+
}
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
return data.completion || data.choices?.[0]?.message?.content || '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildConvertPrompt(text) {
|
|
42
|
+
return `Given the following document text:\n"""\n${text}\n"""\nExtract and structure it as an agent-readable specification with these fields:\n- Objective\n- Acceptance criteria\n- Constraints\n- Subtasks\n- Success metrics\nReturn the specification in JSON format.`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildValidatePrompt(spec) {
|
|
46
|
+
return `You are a quality assessor for agent-readable specifications.
|
|
47
|
+
Given this JSON specification:\n${JSON.stringify(spec, null, 2)}
|
|
48
|
+
Provide a quality score from 0 to 100 and suggestions to improve it in JSON format.`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
app.post('/spec/convert', async (req, res) => {
|
|
52
|
+
const { documentText, paymentMethodId } = req.body;
|
|
53
|
+
if (!documentText || !paymentMethodId) {
|
|
54
|
+
return res.status(400).json({ error: 'Missing documentText or paymentMethodId' });
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
// Charge $0.10 USD
|
|
58
|
+
await stripe.paymentIntents.create({
|
|
59
|
+
amount: 10, // cents
|
|
60
|
+
currency: 'usd',
|
|
61
|
+
payment_method: paymentMethodId,
|
|
62
|
+
confirm: true
|
|
63
|
+
});
|
|
64
|
+
const prompt = buildConvertPrompt(documentText);
|
|
65
|
+
const spec = await callClaudeApi(prompt);
|
|
66
|
+
res.json({ specification: JSON.parse(spec) });
|
|
67
|
+
} catch (err) {
|
|
68
|
+
res.status(500).json({ error: err.message });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
app.post('/spec/validate', async (req, res) => {
|
|
73
|
+
const { specification, paymentMethodId } = req.body;
|
|
74
|
+
if (!specification || !paymentMethodId) {
|
|
75
|
+
return res.status(400).json({ error: 'Missing specification or paymentMethodId' });
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
// Charge $0.05 USD
|
|
79
|
+
await stripe.paymentIntents.create({
|
|
80
|
+
amount: 5, // cents
|
|
81
|
+
currency: 'usd',
|
|
82
|
+
payment_method: paymentMethodId,
|
|
83
|
+
confirm: true
|
|
84
|
+
});
|
|
85
|
+
const prompt = buildValidatePrompt(specification);
|
|
86
|
+
const result = await callClaudeApi(prompt);
|
|
87
|
+
res.json({ validation: JSON.parse(result) });
|
|
88
|
+
} catch (err) {
|
|
89
|
+
res.status(500).json({ error: err.message });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
app.get('/health', (req, res) => {
|
|
94
|
+
res.json({ status: 'ok' });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const PORT = process.env.PORT || 3000;
|
|
98
|
+
app.listen(PORT, () => console.log(`spec-api listening on port ${PORT}`));
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mifactory-bot/spec-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "node index.js"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"express": "^4.18.2",
|
|
10
|
+
"stripe": "^14.0.0",
|
|
11
|
+
"node-fetch": "^2.7.0"
|
|
12
|
+
},
|
|
13
|
+
"mcpName": "io.github.mifactory-bot/mifactory-spec-api",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/mifactory-bot/mifactory-spec-api.git"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.mifactory-bot/mifactory-spec-api",
|
|
4
|
+
"description": "Convert any document into agent-readable specs. Accepts criteria, constraints and subtasks. $0.10/convert.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/mifactory-bot/mifactory-spec-api",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "@mifactory-bot/spec-api",
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "streamable-http",
|
|
17
|
+
"url": "https://spec-api-mcp.vercel.app/spec/convert"
|
|
18
|
+
},
|
|
19
|
+
"environmentVariables": [
|
|
20
|
+
{
|
|
21
|
+
"name": "STRIPE_SECRET_KEY",
|
|
22
|
+
"description": "Stripe secret key",
|
|
23
|
+
"isRequired": true,
|
|
24
|
+
"isSecret": true,
|
|
25
|
+
"format": "string"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "ANTHROPIC_API_KEY",
|
|
29
|
+
"description": "Anthropic API key",
|
|
30
|
+
"isRequired": true,
|
|
31
|
+
"isSecret": true,
|
|
32
|
+
"format": "string"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|