signal-relay-mcp 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/LICENSE +21 -0
- package/README.md +239 -0
- package/package.json +59 -0
- package/src/api-client.ts +311 -0
- package/src/index.ts +661 -0
- package/src/tools.ts +233 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 SocioLogic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Signal Relay - SocioLogic MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://modelcontextprotocol.io)
|
|
5
|
+
|
|
6
|
+
A remote MCP (Model Context Protocol) server that connects AI agents to [SocioLogic's](https://sociologic.ai) synthetic persona platform. Interview realistic customer personas, run multi-persona research campaigns, and export board-ready reports—all through natural conversation.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **15 MCP Tools** - Full access to personas, campaigns, focus groups, and credits
|
|
11
|
+
- **High-Fidelity Personas** - Synthetic personas with consistent demographics, psychographics, and behavior
|
|
12
|
+
- **Semantic Memory** - RAG-powered memory retrieval for persona continuity across conversations
|
|
13
|
+
- **Edge Deployed** - Runs on Cloudflare Workers (300+ locations, <50ms latency)
|
|
14
|
+
- **Secure** - API key authentication, request validation, no data stored on edge
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### Use the Hosted Server (Recommended)
|
|
19
|
+
|
|
20
|
+
The fastest way to get started is using our hosted server at `https://mcp.sociologicai.com`.
|
|
21
|
+
|
|
22
|
+
1. **Get an API key** at [sociologic.ai/dashboard/api-keys](https://sociologic.ai/dashboard/api-keys) (100 free credits on signup)
|
|
23
|
+
|
|
24
|
+
2. **Configure your MCP client:**
|
|
25
|
+
|
|
26
|
+
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"sociologic": {
|
|
31
|
+
"transport": "http",
|
|
32
|
+
"url": "https://mcp.sociologicai.com",
|
|
33
|
+
"headers": {
|
|
34
|
+
"X-API-Key": "YOUR_API_KEY"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Claude Code** (`.mcp.json` in your project):
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"sociologic": {
|
|
46
|
+
"transport": "http",
|
|
47
|
+
"url": "https://mcp.sociologicai.com",
|
|
48
|
+
"headers": {
|
|
49
|
+
"X-API-Key": "YOUR_API_KEY"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
3. **Start chatting!** Ask Claude to interview personas, create campaigns, or explore the marketplace.
|
|
57
|
+
|
|
58
|
+
## Self-Hosting
|
|
59
|
+
|
|
60
|
+
Deploy your own instance to Cloudflare Workers:
|
|
61
|
+
|
|
62
|
+
### Prerequisites
|
|
63
|
+
|
|
64
|
+
- [Node.js](https://nodejs.org/) v18+
|
|
65
|
+
- [Cloudflare account](https://dash.cloudflare.com/sign-up) (free tier works)
|
|
66
|
+
|
|
67
|
+
### Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Clone the repository
|
|
71
|
+
git clone https://github.com/SocioLogicAI/signal-relay.git
|
|
72
|
+
cd signal-relay
|
|
73
|
+
|
|
74
|
+
# Install dependencies
|
|
75
|
+
npm install
|
|
76
|
+
|
|
77
|
+
# Login to Cloudflare
|
|
78
|
+
npx wrangler login
|
|
79
|
+
|
|
80
|
+
# Deploy
|
|
81
|
+
npx wrangler deploy
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Your server will be available at `https://sociologic-mcp-server.<your-subdomain>.workers.dev`
|
|
85
|
+
|
|
86
|
+
### Local Development
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm run dev
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Starts a local server at `http://localhost:8787`.
|
|
93
|
+
|
|
94
|
+
## Available Tools
|
|
95
|
+
|
|
96
|
+
| Tool | Description |
|
|
97
|
+
|------|-------------|
|
|
98
|
+
| `sociologic_list_personas` | List synthetic personas from marketplace or private collection |
|
|
99
|
+
| `sociologic_get_persona` | Get detailed persona information (demographics, psychographics, traits) |
|
|
100
|
+
| `sociologic_create_persona` | Generate a new persona from natural language description |
|
|
101
|
+
| `sociologic_interview_persona` | Conduct adversarial interview with a persona |
|
|
102
|
+
| `sociologic_get_persona_memories` | Retrieve persona's semantic memories via vector search |
|
|
103
|
+
| `sociologic_list_campaigns` | List research campaigns with status and results |
|
|
104
|
+
| `sociologic_get_campaign` | Get campaign details including interviews and findings |
|
|
105
|
+
| `sociologic_create_campaign` | Create multi-persona research campaign with custom questions |
|
|
106
|
+
| `sociologic_execute_campaign` | Execute draft campaign (async background processing) |
|
|
107
|
+
| `sociologic_export_campaign` | Export campaign results as PDF or JSON |
|
|
108
|
+
| `sociologic_list_focus_groups` | List focus groups for cohort-based research |
|
|
109
|
+
| `sociologic_get_focus_group` | Get focus group details with member personas |
|
|
110
|
+
| `sociologic_create_focus_group` | Create new focus group to organize personas |
|
|
111
|
+
| `sociologic_add_personas_to_focus_group` | Add personas to an existing focus group |
|
|
112
|
+
| `sociologic_get_credits_balance` | Check current credits balance and usage |
|
|
113
|
+
|
|
114
|
+
## API Endpoints
|
|
115
|
+
|
|
116
|
+
| Endpoint | Method | Description |
|
|
117
|
+
|----------|--------|-------------|
|
|
118
|
+
| `/` | POST | JSON-RPC 2.0 endpoint for MCP protocol |
|
|
119
|
+
| `/health` | GET | Health check (requires API key) |
|
|
120
|
+
| `/info` | GET | Server information and available tools |
|
|
121
|
+
|
|
122
|
+
## Example Usage
|
|
123
|
+
|
|
124
|
+
### Interview a Persona
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
curl -X POST https://mcp.sociologicai.com/ \
|
|
128
|
+
-H "Content-Type: application/json" \
|
|
129
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
130
|
+
-d '{
|
|
131
|
+
"jsonrpc": "2.0",
|
|
132
|
+
"id": 1,
|
|
133
|
+
"method": "tools/call",
|
|
134
|
+
"params": {
|
|
135
|
+
"name": "sociologic_interview_persona",
|
|
136
|
+
"arguments": {
|
|
137
|
+
"slug": "enterprise-buyer",
|
|
138
|
+
"message": "What would make you hesitant to try a new AI product?"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}'
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### List Available Personas
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
curl -X POST https://mcp.sociologicai.com/ \
|
|
148
|
+
-H "Content-Type: application/json" \
|
|
149
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
150
|
+
-d '{
|
|
151
|
+
"jsonrpc": "2.0",
|
|
152
|
+
"id": 1,
|
|
153
|
+
"method": "tools/call",
|
|
154
|
+
"params": {
|
|
155
|
+
"name": "sociologic_list_personas",
|
|
156
|
+
"arguments": {
|
|
157
|
+
"visibility": "public",
|
|
158
|
+
"per_page": 10
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}'
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Configuration
|
|
165
|
+
|
|
166
|
+
### Environment Variables
|
|
167
|
+
|
|
168
|
+
| Variable | Description | Default |
|
|
169
|
+
|----------|-------------|---------|
|
|
170
|
+
| `SOCIOLOGIC_API_URL` | Backend API URL | `https://www.sociologic.ai` |
|
|
171
|
+
|
|
172
|
+
### wrangler.toml
|
|
173
|
+
|
|
174
|
+
```toml
|
|
175
|
+
[vars]
|
|
176
|
+
SOCIOLOGIC_API_URL = "https://www.sociologic.ai"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Security
|
|
180
|
+
|
|
181
|
+
- **API keys** are passed via `X-API-Key` header or `Authorization: Bearer` header
|
|
182
|
+
- **Request size** limited to 1MB to prevent DoS
|
|
183
|
+
- **Input validation** via Zod schemas on all tool parameters
|
|
184
|
+
- **No data stored** on edge - all data flows through to the SocioLogic API
|
|
185
|
+
|
|
186
|
+
### Rate Limiting
|
|
187
|
+
|
|
188
|
+
For production deployments, we recommend enabling Cloudflare's rate limiting:
|
|
189
|
+
|
|
190
|
+
1. Go to Cloudflare Dashboard > Security > WAF > Rate limiting rules
|
|
191
|
+
2. Create a rule: 100 requests per 10 seconds per IP
|
|
192
|
+
|
|
193
|
+
## Architecture
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
┌─────────────────┐ ┌─────────────────────┐ ┌─────────────────┐
|
|
197
|
+
│ │ │ │ │ │
|
|
198
|
+
│ MCP Client │─────▶│ Cloudflare Worker │─────▶│ SocioLogic API │
|
|
199
|
+
│ (Claude, etc) │ │ (This Server) │ │ │
|
|
200
|
+
│ │◀─────│ │◀─────│ │
|
|
201
|
+
└─────────────────┘ └─────────────────────┘ └─────────────────┘
|
|
202
|
+
│ │ │
|
|
203
|
+
│ MCP Protocol │ REST API │
|
|
204
|
+
│ (JSON-RPC 2.0) │ (HTTPS) │
|
|
205
|
+
└─────────────────────────┴──────────────────────────┘
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Pricing
|
|
209
|
+
|
|
210
|
+
| Operation | Credits |
|
|
211
|
+
|-----------|---------|
|
|
212
|
+
| List personas | 1 |
|
|
213
|
+
| Get persona | 1 |
|
|
214
|
+
| Create persona | 5-50 (by fidelity tier) |
|
|
215
|
+
| Interview persona | 1 per message |
|
|
216
|
+
| Campaign execution | Varies by size |
|
|
217
|
+
|
|
218
|
+
**Free tier:** 100 credits on signup. See [sociologic.ai/pricing](https://sociologic.ai/pricing) for details.
|
|
219
|
+
|
|
220
|
+
## Contributing
|
|
221
|
+
|
|
222
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
223
|
+
|
|
224
|
+
1. Fork the repository
|
|
225
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
226
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
227
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
228
|
+
5. Open a Pull Request
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
233
|
+
|
|
234
|
+
## Links
|
|
235
|
+
|
|
236
|
+
- [SocioLogic Website](https://sociologic.ai)
|
|
237
|
+
- [API Documentation](https://sociologic.ai/docs)
|
|
238
|
+
- [MCP Protocol Specification](https://modelcontextprotocol.io)
|
|
239
|
+
- [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/)
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "signal-relay-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP Server for SocioLogic Revenue Intelligence Platform - Interview synthetic personas, run research campaigns, and export insights",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"dev": "wrangler dev",
|
|
10
|
+
"deploy": "wrangler deploy",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/SocioLogicAI/signal-relay.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://sociologic.ai/signal-relay",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/SocioLogicAI/signal-relay/issues"
|
|
21
|
+
},
|
|
22
|
+
"author": {
|
|
23
|
+
"name": "SocioLogic",
|
|
24
|
+
"url": "https://sociologic.ai"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
37
|
+
"zod": "^3.24.1",
|
|
38
|
+
"zod-to-json-schema": "^3.24.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@cloudflare/workers-types": "^4.20241230.0",
|
|
42
|
+
"typescript": "^5.7.2",
|
|
43
|
+
"wrangler": "^4.59.2"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"mcp",
|
|
47
|
+
"model-context-protocol",
|
|
48
|
+
"sociologic",
|
|
49
|
+
"ai",
|
|
50
|
+
"personas",
|
|
51
|
+
"synthetic-users",
|
|
52
|
+
"market-research",
|
|
53
|
+
"customer-intelligence",
|
|
54
|
+
"revenue-intelligence",
|
|
55
|
+
"interviews",
|
|
56
|
+
"focus-groups"
|
|
57
|
+
],
|
|
58
|
+
"license": "MIT"
|
|
59
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SocioLogic API Client
|
|
3
|
+
*
|
|
4
|
+
* Wraps all API calls to the SocioLogic REST API.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface SocioLogicConfig {
|
|
8
|
+
apiUrl: string;
|
|
9
|
+
apiKey: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ApiResponse<T> {
|
|
13
|
+
data?: T;
|
|
14
|
+
error?: {
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
details?: unknown;
|
|
18
|
+
};
|
|
19
|
+
meta?: {
|
|
20
|
+
request_id: string;
|
|
21
|
+
credits_used?: number;
|
|
22
|
+
credits_remaining?: number;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class SocioLogicClient {
|
|
28
|
+
private apiUrl: string;
|
|
29
|
+
private apiKey: string;
|
|
30
|
+
|
|
31
|
+
constructor(config: SocioLogicConfig) {
|
|
32
|
+
this.apiUrl = config.apiUrl.replace(/\/$/, ""); // Remove trailing slash
|
|
33
|
+
this.apiKey = config.apiKey;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private async request<T>(
|
|
37
|
+
method: string,
|
|
38
|
+
path: string,
|
|
39
|
+
body?: unknown,
|
|
40
|
+
queryParams?: Record<string, string | number | boolean | undefined>,
|
|
41
|
+
timeoutMs: number = 30000 // 30 second default timeout
|
|
42
|
+
): Promise<ApiResponse<T>> {
|
|
43
|
+
const url = new URL(`${this.apiUrl}${path}`);
|
|
44
|
+
|
|
45
|
+
// Add query parameters
|
|
46
|
+
if (queryParams) {
|
|
47
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
48
|
+
if (value !== undefined) {
|
|
49
|
+
url.searchParams.set(key, String(value));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const headers: Record<string, string> = {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
"X-API-Key": this.apiKey,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Setup timeout with AbortController
|
|
60
|
+
const controller = new AbortController();
|
|
61
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
62
|
+
|
|
63
|
+
const options: RequestInit = {
|
|
64
|
+
method,
|
|
65
|
+
headers,
|
|
66
|
+
signal: controller.signal,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (body && method !== "GET") {
|
|
70
|
+
options.body = JSON.stringify(body);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch(url.toString(), options);
|
|
75
|
+
|
|
76
|
+
// Handle HTTP errors
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
// Try to parse error response body
|
|
79
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
80
|
+
let errorCode = `HTTP_${response.status}`;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const errorBody = await response.json() as ApiResponse<unknown>;
|
|
84
|
+
if (errorBody.error) {
|
|
85
|
+
errorMessage = errorBody.error.message || errorMessage;
|
|
86
|
+
errorCode = errorBody.error.code || errorCode;
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// If JSON parsing fails, use the status text
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
error: {
|
|
94
|
+
code: errorCode,
|
|
95
|
+
message: errorMessage,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const json = await response.json() as ApiResponse<T>;
|
|
101
|
+
return json;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// Handle timeout
|
|
104
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
105
|
+
return {
|
|
106
|
+
error: {
|
|
107
|
+
code: "TIMEOUT",
|
|
108
|
+
message: `Request timed out after ${timeoutMs}ms`,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Handle other network errors
|
|
114
|
+
return {
|
|
115
|
+
error: {
|
|
116
|
+
code: "NETWORK_ERROR",
|
|
117
|
+
message: error instanceof Error ? error.message : "Network request failed",
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
} finally {
|
|
121
|
+
// Always clear timeout to prevent memory leaks
|
|
122
|
+
clearTimeout(timeoutId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ============================================
|
|
127
|
+
// PERSONAS
|
|
128
|
+
// ============================================
|
|
129
|
+
|
|
130
|
+
async listPersonas(params: {
|
|
131
|
+
visibility?: "public" | "private" | "all";
|
|
132
|
+
category?: string;
|
|
133
|
+
fidelity_tier?: string;
|
|
134
|
+
search?: string;
|
|
135
|
+
page?: number;
|
|
136
|
+
per_page?: number;
|
|
137
|
+
}) {
|
|
138
|
+
return this.request<{
|
|
139
|
+
data: unknown[];
|
|
140
|
+
pagination: {
|
|
141
|
+
total: number;
|
|
142
|
+
page: number;
|
|
143
|
+
per_page: number;
|
|
144
|
+
total_pages: number;
|
|
145
|
+
};
|
|
146
|
+
}>("GET", "/api/v1/personas", undefined, params);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async getPersona(slug: string) {
|
|
150
|
+
return this.request<unknown>("GET", `/api/v1/personas/${encodeURIComponent(slug)}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async createPersona(params: {
|
|
154
|
+
description: string;
|
|
155
|
+
fidelity_tier?: string;
|
|
156
|
+
}) {
|
|
157
|
+
return this.request<unknown>("POST", "/api/v1/personas", params);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async interviewPersona(
|
|
161
|
+
slug: string,
|
|
162
|
+
params: {
|
|
163
|
+
message: string;
|
|
164
|
+
conversation_id?: string;
|
|
165
|
+
include_memory?: boolean;
|
|
166
|
+
save_conversation?: boolean;
|
|
167
|
+
stream?: boolean;
|
|
168
|
+
}
|
|
169
|
+
) {
|
|
170
|
+
// Note: Streaming is handled differently - this returns non-streaming response
|
|
171
|
+
return this.request<{
|
|
172
|
+
response: string;
|
|
173
|
+
conversation_id: string;
|
|
174
|
+
persona: {
|
|
175
|
+
id: string;
|
|
176
|
+
slug: string;
|
|
177
|
+
name: string;
|
|
178
|
+
};
|
|
179
|
+
memory_context_used: boolean;
|
|
180
|
+
}>("POST", `/api/v1/personas/${encodeURIComponent(slug)}/interview`, {
|
|
181
|
+
...params,
|
|
182
|
+
stream: false, // Force non-streaming for MCP
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async getPersonaMemories(
|
|
187
|
+
slug: string,
|
|
188
|
+
params: {
|
|
189
|
+
query?: string;
|
|
190
|
+
limit?: number;
|
|
191
|
+
}
|
|
192
|
+
) {
|
|
193
|
+
return this.request<{
|
|
194
|
+
memories: Array<{
|
|
195
|
+
id: string;
|
|
196
|
+
content: string;
|
|
197
|
+
similarity?: number;
|
|
198
|
+
created_at: string;
|
|
199
|
+
}>;
|
|
200
|
+
}>("GET", `/api/v1/personas/${encodeURIComponent(slug)}/memories`, undefined, params);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================
|
|
204
|
+
// CAMPAIGNS
|
|
205
|
+
// ============================================
|
|
206
|
+
|
|
207
|
+
async listCampaigns(params: {
|
|
208
|
+
status?: string;
|
|
209
|
+
limit?: number;
|
|
210
|
+
offset?: number;
|
|
211
|
+
include_interviews?: boolean;
|
|
212
|
+
}) {
|
|
213
|
+
return this.request<unknown[]>("GET", "/api/v1/campaigns", undefined, params);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async getCampaign(id: string) {
|
|
217
|
+
return this.request<unknown>("GET", `/api/v1/campaigns/${encodeURIComponent(id)}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async createCampaign(params: {
|
|
221
|
+
name: string;
|
|
222
|
+
description?: string;
|
|
223
|
+
questions: Array<{
|
|
224
|
+
id: string;
|
|
225
|
+
text: string;
|
|
226
|
+
type?: string;
|
|
227
|
+
required?: boolean;
|
|
228
|
+
options?: string[];
|
|
229
|
+
}>;
|
|
230
|
+
persona_brief?: string;
|
|
231
|
+
persona_count?: number;
|
|
232
|
+
fidelity_tier?: string;
|
|
233
|
+
existing_persona_ids?: string[];
|
|
234
|
+
focus_group_ids?: string[];
|
|
235
|
+
research_context?: {
|
|
236
|
+
subjectName: string;
|
|
237
|
+
subjectDescription: string;
|
|
238
|
+
currentChallenge: string;
|
|
239
|
+
areasToExplore?: string[];
|
|
240
|
+
knownIssues?: string[];
|
|
241
|
+
};
|
|
242
|
+
}) {
|
|
243
|
+
return this.request<unknown>("POST", "/api/v1/campaigns", params);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async executeCampaign(id: string) {
|
|
247
|
+
return this.request<{
|
|
248
|
+
status: string;
|
|
249
|
+
message: string;
|
|
250
|
+
}>("POST", `/api/v1/campaigns/${encodeURIComponent(id)}/execute`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async exportCampaign(id: string, format: "pdf" | "json" = "pdf") {
|
|
254
|
+
// For PDF, we return the URL rather than the binary
|
|
255
|
+
const encodedId = encodeURIComponent(id);
|
|
256
|
+
if (format === "pdf") {
|
|
257
|
+
return {
|
|
258
|
+
data: {
|
|
259
|
+
export_url: `${this.apiUrl}/api/v1/campaigns/${encodedId}/export?format=pdf`,
|
|
260
|
+
format: "pdf",
|
|
261
|
+
message: "Use the export_url to download the PDF report. Include your API key in the X-API-Key header.",
|
|
262
|
+
},
|
|
263
|
+
meta: { request_id: `mcp_${Date.now()}` },
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return this.request<unknown>("GET", `/api/v1/campaigns/${encodedId}/export`, undefined, { format });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ============================================
|
|
270
|
+
// FOCUS GROUPS
|
|
271
|
+
// ============================================
|
|
272
|
+
|
|
273
|
+
async listFocusGroups(params: {
|
|
274
|
+
limit?: number;
|
|
275
|
+
offset?: number;
|
|
276
|
+
}) {
|
|
277
|
+
return this.request<unknown[]>("GET", "/api/v1/focus-groups", undefined, params);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async getFocusGroup(id: string) {
|
|
281
|
+
return this.request<unknown>("GET", `/api/v1/focus-groups/${encodeURIComponent(id)}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async createFocusGroup(params: {
|
|
285
|
+
name: string;
|
|
286
|
+
description?: string;
|
|
287
|
+
}) {
|
|
288
|
+
return this.request<unknown>("POST", "/api/v1/focus-groups", params);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async addPersonasToFocusGroup(focusGroupId: string, personaIds: string[]) {
|
|
292
|
+
return this.request<unknown>(
|
|
293
|
+
"POST",
|
|
294
|
+
`/api/v1/focus-groups/${encodeURIComponent(focusGroupId)}/personas`,
|
|
295
|
+
{ persona_ids: personaIds }
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ============================================
|
|
300
|
+
// AUTH / CREDITS
|
|
301
|
+
// ============================================
|
|
302
|
+
|
|
303
|
+
async getCreditsBalance() {
|
|
304
|
+
return this.request<{
|
|
305
|
+
valid: boolean;
|
|
306
|
+
credits_balance: number;
|
|
307
|
+
credits_used_total: number;
|
|
308
|
+
rate_limit_tier: string;
|
|
309
|
+
}>("GET", "/api/v1/auth/validate");
|
|
310
|
+
}
|
|
311
|
+
}
|