Manakavoo commited on
Commit
8205f96
·
verified ·
1 Parent(s): 54f4b67

Convert to proper MCP server with stdio transport

Browse files
Files changed (1) hide show
  1. app.py +83 -276
app.py CHANGED
@@ -1,290 +1,97 @@
1
- import gradio as gr
 
 
 
 
 
 
 
 
 
2
  import json
3
- from typing import Dict, List, Any, Optional
 
 
 
 
 
 
 
4
 
5
- # Form schema - represents a job application form
6
  FORM_SCHEMA = {
7
  "title": "Job Application Form",
8
  "description": "Complete your job application",
9
  "fields": [
10
- {
11
- "id": "full_name",
12
- "label": "What is your full name?",
13
- "type": "text",
14
- "required": True,
15
- "validation": {"min_length": 2}
16
- },
17
- {
18
- "id": "email",
19
- "label": "What is your email address?",
20
- "type": "email",
21
- "required": True,
22
- "validation": {"pattern": r".+@.+\..+"}
23
- },
24
- {
25
- "id": "phone",
26
- "label": "What is your phone number?",
27
- "type": "text",
28
- "required": True,
29
- "validation": {"pattern": r"^[0-9]{10}$"}
30
- },
31
- {
32
- "id": "experience",
33
- "label": "How many years of experience do you have?",
34
- "type": "number",
35
- "required": True,
36
- "validation": {"min": 0, "max": 50}
37
- },
38
- {
39
- "id": "role",
40
- "label": "Which role are you applying for?",
41
- "type": "select",
42
- "required": True,
43
- "options": ["Software Engineer", "Data Scientist", "Product Manager", "Designer"]
44
- },
45
- {
46
- "id": "availability",
47
- "label": "Can you start immediately?",
48
- "type": "boolean",
49
- "required": True
50
- }
51
  ]
52
  }
53
 
54
- # ==============================================
55
- # MCP TOOL IMPLEMENTATIONS
56
- # ==============================================
57
-
58
- def mcp_get_form_schema() -> Dict:
59
- """MCP Tool: get_form_schema
60
- Returns the complete form schema definition.
61
- """
62
- return {
63
- "success": True,
64
- "schema": FORM_SCHEMA,
65
- "total_fields": len(FORM_SCHEMA["fields"])
66
- }
67
-
68
- def mcp_get_next_question(filled_fields: Dict) -> Dict:
69
- """MCP Tool: get_next_question
70
- Given filled fields, returns the next unanswered question.
71
- """
72
- for field in FORM_SCHEMA["fields"]:
73
- if field["id"] not in filled_fields:
74
- return {
75
- "success": True,
76
- "field_id": field["id"],
77
- "question": field["label"],
78
- "type": field["type"],
79
- "required": field["required"],
80
- "options": field.get("options"),
81
- "is_complete": False
82
- }
83
-
84
- return {
85
- "success": True,
86
- "is_complete": True,
87
- "message": "All questions answered!"
88
- }
89
-
90
- def mcp_validate_answer(field_id: str, answer: Any) -> Dict:
91
- """MCP Tool: validate_answer
92
- Validates an answer against field constraints.
93
- """
94
- field = next((f for f in FORM_SCHEMA["fields"] if f["id"] == field_id), None)
95
-
96
- if not field:
97
- return {"success": False, "error": "Invalid field ID"}
98
-
99
- # Check required
100
- if field["required"] and (answer is None or answer == ""):
101
- return {"success": False, "error": "This field is required"}
102
-
103
- # Type validation
104
- if field["type"] == "email":
105
- import re
106
- if not re.match(r".+@.+\..+", str(answer)):
107
- return {"success": False, "error": "Invalid email format"}
108
-
109
- elif field["type"] == "number":
110
- try:
111
- num = float(answer)
112
- if "validation" in field:
113
- if "min" in field["validation"] and num < field["validation"]["min"]:
114
- return {"success": False, "error": f"Must be at least {field['validation']['min']}"}
115
- if "max" in field["validation"] and num > field["validation"]["max"]:
116
- return {"success": False, "error": f"Must be at most {field['validation']['max']}"}
117
- except ValueError:
118
- return {"success": False, "error": "Must be a number"}
119
-
120
- elif field["type"] == "select":
121
- if answer not in field.get("options", []):
122
- return {"success": False, "error": f"Please choose from: {', '.join(field['options'])}"}
123
-
124
- elif field["type"] == "text":
125
- if "validation" in field and "min_length" in field["validation"]:
126
- if len(str(answer)) < field["validation"]["min_length"]:
127
- return {"success": False, "error": f"Must be at least {field['validation']['min_length']} characters"}
128
-
129
- return {"success": True, "message": "Valid answer"}
130
-
131
- def mcp_save_answer(field_id: str, answer: Any, session_data: Dict) -> Dict:
132
- """MCP Tool: save_answer
133
- Saves the answer to session storage.
134
- """
135
- session_data[field_id] = answer
136
- return {
137
- "success": True,
138
- "message": "Answer saved",
139
- "progress": f"{len(session_data)}/{len(FORM_SCHEMA['fields'])}"
140
- }
141
-
142
- def mcp_submit_form(session_data: Dict) -> Dict:
143
- """MCP Tool: submit_form
144
- Submits the completed form.
145
- """
146
- if len(session_data) < len(FORM_SCHEMA["fields"]):
147
- return {
148
- "success": False,
149
- "error": "Form incomplete",
150
- "missing_fields": len(FORM_SCHEMA["fields"]) - len(session_data)
151
- }
152
-
153
- return {
154
- "success": True,
155
- "message": "Form submitted successfully!",
156
- "submission_id": "SUB-" + str(hash(json.dumps(session_data, sort_keys=True)))[-8:],
157
- "data": session_data
158
- }
159
-
160
- # ==============================================
161
- # CONVERSATIONAL AGENT
162
- # ==============================================
163
 
164
- def process_message(message: str, history: List, session_data: Dict) -> tuple:
165
- """Process user message and generate agent response."""
166
-
167
- # Initialize session
168
- if not session_data:
169
- schema_result = mcp_get_form_schema()
170
- history.append((None, f"👋 Welcome! I'm your conversational form assistant.\n\n**{schema_result['schema']['title']}**\n{schema_result['schema']['description']}\n\nI'll guide you through {schema_result['total_fields']} questions. Let's get started!"))
171
-
172
- # Ask first question
173
- next_q = mcp_get_next_question(session_data)
174
- history.append((None, f"**Question 1/{len(FORM_SCHEMA['fields'])}**\n\n{next_q['question']}"))
175
-
176
- if next_q.get('options'):
177
- history.append((None, f"Please choose one: {', '.join(next_q['options'])}"))
178
-
179
- return history, session_data
180
-
181
- # Get current question
182
- next_q = mcp_get_next_question(session_data)
183
-
184
- if next_q["is_complete"]:
185
- # Try to submit
186
- submit_result = mcp_submit_form(session_data)
187
- if submit_result["success"]:
188
- history.append((message, f"✅ **{submit_result['message']}**\n\nSubmission ID: `{submit_result['submission_id']}`\n\n**Your Responses:**\n" + "\n".join([f"• {k}: {v}" for k, v in session_data.items()])))
189
- history.append((None, "Thank you for completing the form! You can refresh to start a new submission."))
190
- return history, session_data
191
-
192
- # Validate answer
193
- field_id = next_q["field_id"]
194
- validation = mcp_validate_answer(field_id, message)
195
-
196
- if not validation["success"]:
197
- history.append((message, f"❌ **Validation Error:** {validation['error']}\n\nPlease try again."))
198
- history.append((None, next_q["question"]))
199
- return history, session_data
200
-
201
- # Save answer
202
- save_result = mcp_save_answer(field_id, message, session_data)
203
- history.append((message, f"✓ Got it! Progress: {save_result['progress']}"))
204
-
205
- # Get next question
206
- next_q = mcp_get_next_question(session_data)
207
-
208
- if next_q["is_complete"]:
209
- # Submit form
210
- submit_result = mcp_submit_form(session_data)
211
- if submit_result["success"]:
212
- history.append((None, f"✅ **{submit_result['message']}**\n\nSubmission ID: `{submit_result['submission_id']}`\n\n**Your Responses:**\n" + "\n".join([f"• {k}: {v}" for k, v in session_data.items()])))
213
- else:
214
- current_num = len(session_data) + 1
215
- history.append((None, f"**Question {current_num}/{len(FORM_SCHEMA['fields'])}**\n\n{next_q['question']}"))
216
-
217
- if next_q.get('options'):
218
- history.append((None, f"Please choose one: {', '.join(next_q['options'])}"))
219
-
220
- return history, session_data
221
 
222
- # ==============================================
223
- # GRADIO INTERFACE
224
- # ==============================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
- with gr.Blocks(title="Conversational Form Agent", theme=gr.themes.Soft()) as demo:
227
- gr.Markdown(
228
- """
229
- # 💬 Conversational Form-Filling Agent
230
- ### MCP 1st Birthday Hackathon - Track 1: Building MCP
231
-
232
- Replace traditional web forms with an intelligent conversational experience powered by the **Model Context Protocol (MCP)**.
233
-
234
- **How it works:**
235
- 1. The agent loads a form schema via MCP tools
236
- 2. Asks you questions one by one
237
- 3. Validates your answers in real-time
238
- 4. Saves your progress automatically
239
- 5. Submits the form when complete
240
-
241
- **MCP Tools Used:** `get_form_schema`, `get_next_question`, `validate_answer`, `save_answer`, `submit_form`
242
-
243
- ---
244
-
245
- Type **"start"** to begin!
246
- """
247
- )
248
-
249
- session_state = gr.State({})
250
-
251
- chatbot = gr.Chatbot(
252
- label="Form Interview",
253
- height=500,
254
- show_label=True,
255
- avatar_images=(None, "https://huggingface.co/front/assets/huggingface_logo-noborder.svg")
256
- )
257
-
258
- with gr.Row():
259
- msg = gr.Textbox(
260
- label="Your Answer",
261
- placeholder="Type your answer here...",
262
- scale=4
263
- )
264
- submit_btn = gr.Button("Send", variant="primary", scale=1)
265
-
266
- gr.Markdown(
267
- """
268
- ---
269
-
270
- **About this project:**
271
- This conversational form agent demonstrates how MCP can transform traditional form-filling into an interactive,
272
- guided conversation. Built for the MCP 1st Birthday Hackathon.
273
-
274
- **Tech Stack:** Python, Gradio, Model Context Protocol (MCP)
275
- """
276
- )
277
-
278
- def submit(message, history, session):
279
- if message.strip().lower() == "start" and not session:
280
- history = []
281
- session = {}
282
-
283
- history, session = process_message(message, history, session)
284
- return "", history, session
285
-
286
- msg.submit(submit, [msg, chatbot, session_state], [msg, chatbot, session_state])
287
- submit_btn.click(submit, [msg, chatbot, session_state], [msg, chatbot, session_state])
288
 
289
  if __name__ == "__main__":
290
- demo.launch()
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Conversational Form-Filling MCP Server
4
+ MCP 1st Birthday Hackathon - Track 1: Building with MCP
5
+
6
+ This MCP server provides tools for conversational form filling.
7
+ Connect it to any MCP client (Claude Desktop, VS Code, Cursor, etc.)
8
+ """
9
+
10
+ import asyncio
11
  import json
12
+ import re
13
+ import hashlib
14
+ import time
15
+ from typing import Any, Dict
16
+ from mcp.server.models import InitializationOptions
17
+ from mcp.server import NotificationOptions, Server
18
+ import mcp.server.stdio
19
+ import mcp.types as types
20
 
21
+ # Form schema
22
  FORM_SCHEMA = {
23
  "title": "Job Application Form",
24
  "description": "Complete your job application",
25
  "fields": [
26
+ {"id": "full_name", "label": "What is your full name?", "type": "text", "required": True, "validation": {"min_length": 2}},
27
+ {"id": "email", "label": "What is your email address?", "type": "email", "required": True},
28
+ {"id": "phone", "label": "What is your phone number? (XXX-XXX-XXXX)", "type": "phone", "required": True},
29
+ {"id": "experience", "label": "Years of experience?", "type": "number", "required": True},
30
+ {"id": "role", "label": "Role applying for?", "type": "choice", "required": True, "options": ["Software Engineer", "Product Manager", "Designer", "Data Scientist"]},
31
+ {"id": "availability", "label": "When can you start?", "type": "choice", "required": True, "options": ["Immediately", "2 weeks", "1 month", "2+ months"]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  ]
33
  }
34
 
35
+ form_data: Dict[str, Any] = {}
36
+ server = Server("conversational-form-agent")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ @server.list_tools()
39
+ async def handle_list_tools() -> list[types.Tool]:
40
+ return [
41
+ types.Tool(name="get_form_schema", description="Get form schema", inputSchema={"type": "object", "properties": {}}),
42
+ types.Tool(name="get_next_question", description="Get next question", inputSchema={"type": "object", "properties": {"filled_data": {"type": "object"}}}),
43
+ types.Tool(name="validate_answer", description="Validate answer", inputSchema={"type": "object", "properties": {"field_name": {"type": "string"}, "value": {"type": "string"}}, "required": ["field_name", "value"]}),
44
+ types.Tool(name="save_answer", description="Save answer", inputSchema={"type": "object", "properties": {"field_name": {"type": "string"}, "value": {"type": "string"}}, "required": ["field_name", "value"]}),
45
+ types.Tool(name="submit_form", description="Submit form", inputSchema={"type": "object", "properties": {"filled_data": {"type": "object"}}, "required": ["filled_data"]})
46
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
+ @server.call_tool()
49
+ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.TextContent]:
50
+ if arguments is None:
51
+ arguments = {}
52
+
53
+ if name == "get_form_schema":
54
+ return [types.TextContent(type="text", text=json.dumps(FORM_SCHEMA, indent=2))]
55
+
56
+ elif name == "get_next_question":
57
+ filled_data = arguments.get("filled_data", {})
58
+ for field in FORM_SCHEMA["fields"]:
59
+ if field["required"] and field["id"] not in filled_data:
60
+ result = {"field": field, "progress": f"{len(filled_data)}/{len(FORM_SCHEMA['fields'])}", "question": field["label"]}
61
+ if field["type"] == "choice":
62
+ result["options"] = field["options"]
63
+ return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
64
+ return [types.TextContent(type="text", text=json.dumps({"status": "complete"}, indent=2))]
65
+
66
+ elif name == "validate_answer":
67
+ field_name = arguments.get("field_name")
68
+ value = arguments.get("value")
69
+ field = next((f for f in FORM_SCHEMA["fields"] if f["id"] == field_name), None)
70
+ if not field:
71
+ return [types.TextContent(type="text", text=json.dumps({"valid": False, "error": "Unknown field"}, indent=2))]
72
+ # Basic validation - expand as needed
73
+ return [types.TextContent(type="text", text=json.dumps({"valid": True}, indent=2))]
74
+
75
+ elif name == "save_answer":
76
+ field_name = arguments.get("field_name")
77
+ value = arguments.get("value")
78
+ form_data[field_name] = value
79
+ return [types.TextContent(type="text", text=json.dumps({"success": True, "total_saved": len(form_data)}, indent=2))]
80
+
81
+ elif name == "submit_form":
82
+ filled_data = arguments.get("filled_data", {})
83
+ submission_id = hashlib.md5(f"{time.time()}{json.dumps(filled_data)}".encode()).hexdigest()[:12]
84
+ return [types.TextContent(type="text", text=json.dumps({"success": True, "submission_id": f"sub_{submission_id}"}, indent=2))]
85
+
86
+ raise ValueError(f"Unknown tool: {name}")
87
 
88
+ async def main():
89
+ async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
90
+ await server.run(read_stream, write_stream, InitializationOptions(
91
+ server_name="conversational-form-agent",
92
+ server_version="1.0.0",
93
+ capabilities=server.get_capabilities(notification_options=NotificationOptions(), experimental_capabilities={})
94
+ ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  if __name__ == "__main__":
97
+ asyncio.run(main())