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 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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![MCP](https://img.shields.io/badge/MCP-Compatible-blue)](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
+ }