@codespar/mcp-take-blip 0.1.0 → 0.1.2
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 +117 -0
- package/dist/index.js +42 -5
- package/package.json +3 -2
- package/server.json +30 -0
- package/src/__tests__/index.test.ts +50 -0
- package/src/index.ts +27 -5
- package/tsconfig.json +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @codespar/mcp-take-blip
|
|
2
|
+
|
|
3
|
+
> MCP server for **Take Blip** — chatbot and messaging platform
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@codespar/mcp-take-blip)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
### Claude Desktop
|
|
11
|
+
|
|
12
|
+
Add to `~/.config/claude/claude_desktop_config.json`:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"mcpServers": {
|
|
17
|
+
"take-blip": {
|
|
18
|
+
"command": "npx",
|
|
19
|
+
"args": ["-y", "@codespar/mcp-take-blip"],
|
|
20
|
+
"env": {
|
|
21
|
+
"TAKE_BLIP_BOT_ID": "your-bot-id",
|
|
22
|
+
"TAKE_BLIP_ACCESS_KEY": "your-access-key"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Claude Code
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
claude mcp add take-blip -- npx @codespar/mcp-take-blip
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Cursor / VS Code
|
|
36
|
+
|
|
37
|
+
Add to `.cursor/mcp.json` or `.vscode/mcp.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"servers": {
|
|
42
|
+
"take-blip": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "@codespar/mcp-take-blip"],
|
|
45
|
+
"env": {
|
|
46
|
+
"TAKE_BLIP_BOT_ID": "your-bot-id",
|
|
47
|
+
"TAKE_BLIP_ACCESS_KEY": "your-access-key"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Tools
|
|
55
|
+
|
|
56
|
+
| Tool | Description |
|
|
57
|
+
|------|-------------|
|
|
58
|
+
| `send_message` | Send a message to a contact via Take Blip |
|
|
59
|
+
| `get_contacts` | List contacts in Take Blip |
|
|
60
|
+
| `create_contact` | Create a contact in Take Blip |
|
|
61
|
+
| `get_threads` | Get message threads (recent conversations) |
|
|
62
|
+
| `send_notification` | Send a notification message to a contact |
|
|
63
|
+
| `get_analytics` | Get chatbot analytics and metrics |
|
|
64
|
+
| `create_broadcast` | Create a broadcast distribution list and send messages |
|
|
65
|
+
| `get_chatbot_flow` | Get chatbot flow/builder configuration |
|
|
66
|
+
|
|
67
|
+
## Authentication
|
|
68
|
+
|
|
69
|
+
Take Blip uses a Key-based auth header computed from the bot ID and access key.
|
|
70
|
+
|
|
71
|
+
## Sandbox / Testing
|
|
72
|
+
|
|
73
|
+
Take Blip offers a free account for testing. Create a bot to get started.
|
|
74
|
+
|
|
75
|
+
### Get your credentials
|
|
76
|
+
|
|
77
|
+
1. Go to [Take Blip](https://portal.blip.ai)
|
|
78
|
+
2. Create a free account and a chatbot
|
|
79
|
+
3. Navigate to bot settings to get the bot identifier and access key
|
|
80
|
+
4. Set the environment variables
|
|
81
|
+
|
|
82
|
+
## Environment Variables
|
|
83
|
+
|
|
84
|
+
| Variable | Required | Description |
|
|
85
|
+
|----------|----------|-------------|
|
|
86
|
+
| `TAKE_BLIP_BOT_ID` | Yes | Bot identifier |
|
|
87
|
+
| `TAKE_BLIP_ACCESS_KEY` | Yes | Bot access key |
|
|
88
|
+
|
|
89
|
+
## Roadmap
|
|
90
|
+
|
|
91
|
+
### v0.2 (planned)
|
|
92
|
+
- `update_contact` — Update contact information
|
|
93
|
+
- `delete_contact` — Delete a contact
|
|
94
|
+
- `get_message_history` — Get message history for a contact
|
|
95
|
+
- `create_scheduled_message` — Schedule a message for later delivery
|
|
96
|
+
- `get_team_metrics` — Get team performance metrics
|
|
97
|
+
|
|
98
|
+
### v0.3 (planned)
|
|
99
|
+
- `flow_management` — Create and manage conversational flows
|
|
100
|
+
- `ai_model_integration` — Integrate custom AI models into flows
|
|
101
|
+
|
|
102
|
+
Want to contribute? [Open a PR](https://github.com/codespar/mcp-dev-brasil) or [request a tool](https://github.com/codespar/mcp-dev-brasil/issues).
|
|
103
|
+
|
|
104
|
+
## Links
|
|
105
|
+
|
|
106
|
+
- [Take Blip Website](https://blip.ai)
|
|
107
|
+
- [Take Blip API Documentation](https://docs.blip.ai)
|
|
108
|
+
- [MCP Dev Brasil](https://github.com/codespar/mcp-dev-brasil)
|
|
109
|
+
- [Landing Page](https://codespar.dev/mcp)
|
|
110
|
+
|
|
111
|
+
## Enterprise
|
|
112
|
+
|
|
113
|
+
Need governance, budget limits, and audit trails for agent payments? [CodeSpar Enterprise](https://codespar.dev/enterprise) adds policy engine, payment routing, and compliance templates on top of these MCP servers.
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
23
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
25
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
24
26
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
25
27
|
const BOT_ID = process.env.TAKE_BLIP_BOT_ID || "";
|
|
26
28
|
const ACCESS_KEY = process.env.TAKE_BLIP_ACCESS_KEY || "";
|
|
@@ -246,11 +248,46 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
246
248
|
}
|
|
247
249
|
});
|
|
248
250
|
async function main() {
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
251
|
+
if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
|
|
252
|
+
const { default: express } = await import("express");
|
|
253
|
+
const { randomUUID } = await import("node:crypto");
|
|
254
|
+
const app = express();
|
|
255
|
+
app.use(express.json());
|
|
256
|
+
const transports = new Map();
|
|
257
|
+
app.get("/health", (_req, res) => res.json({ status: "ok", sessions: transports.size }));
|
|
258
|
+
app.post("/mcp", async (req, res) => {
|
|
259
|
+
const sid = req.headers["mcp-session-id"];
|
|
260
|
+
if (sid && transports.has(sid)) {
|
|
261
|
+
await transports.get(sid).handleRequest(req, res, req.body);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (!sid && isInitializeRequest(req.body)) {
|
|
265
|
+
const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
|
|
266
|
+
t.onclose = () => { if (t.sessionId)
|
|
267
|
+
transports.delete(t.sessionId); };
|
|
268
|
+
const s = new Server({ name: "mcp-take-blip", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
269
|
+
server._requestHandlers.forEach((v, k) => s._requestHandlers.set(k, v));
|
|
270
|
+
server._notificationHandlers?.forEach((v, k) => s._notificationHandlers.set(k, v));
|
|
271
|
+
await s.connect(t);
|
|
272
|
+
await t.handleRequest(req, res, req.body);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
|
|
276
|
+
});
|
|
277
|
+
app.get("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
|
|
278
|
+
await transports.get(sid).handleRequest(req, res);
|
|
279
|
+
else
|
|
280
|
+
res.status(400).send("Invalid session"); });
|
|
281
|
+
app.delete("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
|
|
282
|
+
await transports.get(sid).handleRequest(req, res);
|
|
283
|
+
else
|
|
284
|
+
res.status(400).send("Invalid session"); });
|
|
285
|
+
const port = Number(process.env.MCP_PORT) || 3000;
|
|
286
|
+
app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
const transport = new StdioServerTransport();
|
|
290
|
+
await server.connect(transport);
|
|
252
291
|
}
|
|
253
|
-
const transport = new StdioServerTransport();
|
|
254
|
-
await server.connect(transport);
|
|
255
292
|
}
|
|
256
293
|
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codespar/mcp-take-blip",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "MCP server for Take Blip — chatbots, messaging, contacts, broadcasts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,5 +26,6 @@
|
|
|
26
26
|
"messaging",
|
|
27
27
|
"whatsapp",
|
|
28
28
|
"brazil"
|
|
29
|
-
]
|
|
29
|
+
],
|
|
30
|
+
"mcpName": "io.github.codespar/mcp-take-blip"
|
|
30
31
|
}
|
package/server.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.codespar/mcp-take-blip",
|
|
4
|
+
"description": "MCP server for Take Blip — chatbots, messaging, contacts, broadcasts",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/codespar/mcp-dev-brasil",
|
|
7
|
+
"source": "github",
|
|
8
|
+
"subfolder": "packages/communication/take-blip"
|
|
9
|
+
},
|
|
10
|
+
"version": "0.1.2",
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "npm",
|
|
14
|
+
"identifier": "@codespar/mcp-take-blip",
|
|
15
|
+
"version": "0.1.2",
|
|
16
|
+
"transport": {
|
|
17
|
+
"type": "stdio"
|
|
18
|
+
},
|
|
19
|
+
"environmentVariables": [
|
|
20
|
+
{
|
|
21
|
+
"name": "TAKE_BLIP_API_KEY",
|
|
22
|
+
"description": "API key for take-blip",
|
|
23
|
+
"isRequired": true,
|
|
24
|
+
"format": "string",
|
|
25
|
+
"isSecret": true
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
let listToolsHandler: Function;
|
|
4
|
+
let callToolHandler: Function;
|
|
5
|
+
|
|
6
|
+
vi.mock("@modelcontextprotocol/sdk/server/index.js", () => {
|
|
7
|
+
class FakeServer {
|
|
8
|
+
constructor() {}
|
|
9
|
+
setRequestHandler(schema: any, handler: Function) {
|
|
10
|
+
if (JSON.stringify(schema).includes("tools/list")) listToolsHandler = handler;
|
|
11
|
+
if (JSON.stringify(schema).includes("tools/call")) callToolHandler = handler;
|
|
12
|
+
}
|
|
13
|
+
connect() { return Promise.resolve(); }
|
|
14
|
+
}
|
|
15
|
+
return { Server: FakeServer };
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
vi.mock("@modelcontextprotocol/sdk/server/stdio.js", () => ({ StdioServerTransport: class {} }));
|
|
19
|
+
|
|
20
|
+
process.env.TAKE_BLIP_BOT_ID = "testbot";
|
|
21
|
+
process.env.TAKE_BLIP_ACCESS_KEY = "test-key";
|
|
22
|
+
|
|
23
|
+
const mockFetch = vi.fn();
|
|
24
|
+
global.fetch = mockFetch as any;
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
vi.resetModules();
|
|
28
|
+
listToolsHandler = undefined as any;
|
|
29
|
+
callToolHandler = undefined as any;
|
|
30
|
+
mockFetch.mockReset();
|
|
31
|
+
global.fetch = mockFetch as any;
|
|
32
|
+
await import("../index.js");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("mcp-take-blip", () => {
|
|
36
|
+
it("should register 8 tools", async () => {
|
|
37
|
+
const result = await listToolsHandler();
|
|
38
|
+
expect(result.tools).toHaveLength(8);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should call correct API endpoint for get_contacts", async () => {
|
|
42
|
+
mockFetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ status: "success", resource: { items: [] } }) });
|
|
43
|
+
|
|
44
|
+
await callToolHandler({ params: { name: "get_contacts", arguments: {} } });
|
|
45
|
+
|
|
46
|
+
const [url, opts] = mockFetch.mock.calls[0];
|
|
47
|
+
expect(url).toContain("msging.net/commands");
|
|
48
|
+
expect(opts.method).toBe("POST");
|
|
49
|
+
});
|
|
50
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
25
25
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
26
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
27
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
26
28
|
import {
|
|
27
29
|
CallToolRequestSchema,
|
|
28
30
|
ListToolsRequestSchema,
|
|
@@ -261,12 +263,32 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
261
263
|
});
|
|
262
264
|
|
|
263
265
|
async function main() {
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
|
|
266
|
+
if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
|
|
267
|
+
const { default: express } = await import("express");
|
|
268
|
+
const { randomUUID } = await import("node:crypto");
|
|
269
|
+
const app = express();
|
|
270
|
+
app.use(express.json());
|
|
271
|
+
const transports = new Map<string, StreamableHTTPServerTransport>();
|
|
272
|
+
app.get("/health", (_req: any, res: any) => res.json({ status: "ok", sessions: transports.size }));
|
|
273
|
+
app.post("/mcp", async (req: any, res: any) => {
|
|
274
|
+
const sid = req.headers["mcp-session-id"] as string | undefined;
|
|
275
|
+
if (sid && transports.has(sid)) { await transports.get(sid)!.handleRequest(req, res, req.body); return; }
|
|
276
|
+
if (!sid && isInitializeRequest(req.body)) {
|
|
277
|
+
const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
|
|
278
|
+
t.onclose = () => { if (t.sessionId) transports.delete(t.sessionId); };
|
|
279
|
+
const s = new Server({ name: "mcp-take-blip", version: "0.1.0" }, { capabilities: { tools: {} } }); (server as any)._requestHandlers.forEach((v: any, k: any) => (s as any)._requestHandlers.set(k, v)); (server as any)._notificationHandlers?.forEach((v: any, k: any) => (s as any)._notificationHandlers.set(k, v)); await s.connect(t);
|
|
280
|
+
await t.handleRequest(req, res, req.body); return;
|
|
281
|
+
}
|
|
282
|
+
res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
|
|
283
|
+
});
|
|
284
|
+
app.get("/mcp", async (req: any, res: any) => { const sid = req.headers["mcp-session-id"] as string; if (sid && transports.has(sid)) await transports.get(sid)!.handleRequest(req, res); else res.status(400).send("Invalid session"); });
|
|
285
|
+
app.delete("/mcp", async (req: any, res: any) => { const sid = req.headers["mcp-session-id"] as string; if (sid && transports.has(sid)) await transports.get(sid)!.handleRequest(req, res); else res.status(400).send("Invalid session"); });
|
|
286
|
+
const port = Number(process.env.MCP_PORT) || 3000;
|
|
287
|
+
app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
|
|
288
|
+
} else {
|
|
289
|
+
const transport = new StdioServerTransport();
|
|
290
|
+
await server.connect(transport);
|
|
267
291
|
}
|
|
268
|
-
const transport = new StdioServerTransport();
|
|
269
|
-
await server.connect(transport);
|
|
270
292
|
}
|
|
271
293
|
|
|
272
294
|
main().catch(console.error);
|